Xenia is an experimental emulator for the Xbox 360. For more information, see the
[main Xenia wiki](https://github.com/xenia-project/xenia/wiki).
**Interested in supporting the core contributors?** Visit
[Xenia Project on Patreon](https://www.patreon.com/xenia_project).
Come chat with us about **emulator-related topics** on [Discord](https://discord.gg/Q9mxZf9).
For developer chat join `#dev` but stay on topic. Lurking is not only fine, but encouraged!
Please check the [FAQ](https://github.com/xenia-project/xenia/wiki/FAQ) page before asking questions.
We've got jobs/lives/etc, so don't expect instant answers.
Discussing illegal activities will get you banned.
## Status
Buildbot | Status | Releases
-------- | ------ | --------
[Windows](https://ci.appveyor.com/project/benvanik/xenia/branch/master) | [](https://ci.appveyor.com/project/benvanik/xenia/branch/master) | [Latest](https://github.com/xenia-project/release-builds-windows/releases/latest) ◦ [All](https://github.com/xenia-project/release-builds-windows/releases)
[Linux](https://cloud.drone.io/xenia-project/xenia) | [](https://cloud.drone.io/xenia-project/xenia)
Quite a few real games run. Quite a few don't.
See the [Game compatibility list](https://github.com/xenia-project/game-compatibility/issues)
for currently tracked games, and feel free to contribute your own updates,
screenshots, and information there following the [existing conventions](https://github.com/xenia-project/game-compatibility/blob/master/README.md).
## Disclaimer
The goal of this project is to experiment, research, and educate on the topic
of emulation of modern devices and operating systems. **It is not for enabling
illegal activity**. All information is obtained via reverse engineering of
legally purchased devices and games and information made public on the internet
(you'd be surprised what's indexed on Google...).
## Quickstart
See the [Quickstart](https://github.com/xenia-project/xenia/wiki/Quickstart) page.
## Building
See [building.md](docs/building.md) for setup and information about the
`xb` script. When writing code, check the [style guide](docs/style_guide.md)
and be sure to run clang-format!
## Contributors Wanted!
Have some spare time, know advanced C++, and want to write an emulator?
Contribute! There's a ton of work that needs to be done, a lot of which
is wide open greenfield fun.
**For general rules and guidelines please see [CONTRIBUTING.md](.github/CONTRIBUTING.md).**
Fixes and optimizations are always welcome (please!), but in addition to
that there are some major work areas still untouched:
* Help work through [missing functionality/bugs in games](https://github.com/xenia-project/xenia/labels/compat)
* Reduce the size of Xenia's [huge log files](https://github.com/xenia-project/xenia/issues/1526)
* Skilled with Linux? A strong contributor is needed to [help with porting](https://github.com/xenia-project/xenia/labels/platform-linux)
See more projects [good for contributors](https://github.com/xenia-project/xenia/labels/good%20first%20issue). It's a good idea to ask on Discord and check the issues page before beginning work on
something.
## FAQ
See the [frequently asked questions](https://github.com/xenia-project/xenia/wiki/FAQ) page.
================================================
FILE: android/android_studio_project/.gitignore
================================================
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties
================================================
FILE: android/android_studio_project/app/.gitignore
================================================
/build
================================================
FILE: android/android_studio_project/app/build.gradle
================================================
plugins {
id 'com.android.application'
}
android {
compileSdkVersion 33
ndkVersion '25.0.8775105'
defaultConfig {
applicationId 'jp.xenia.emulator'
// 24 (7.0) - Vulkan.
minSdkVersion 24
targetSdkVersion 33
versionCode 1
versionName 'Prototype'
externalNativeBuild {
ndkBuild {
arguments 'NDK_APPLICATION_MK:=../../../build/xenia.Application.mk',
'PREMAKE_ANDROIDNDK_PLATFORMS:=Android-ARM64',
'PREMAKE_ANDROIDNDK_PLATFORMS+=Android-x86_64',
// ndk.jobs doesn't work as of Gradle 7.1.0.
"-j${Runtime.runtime.availableProcessors()}",
// Work around "Bad file descriptor" on Windows on NDK r22+.
'--output-sync=none'
// For the app, don't build the executables designed for running from a terminal.
// To build the executables, run ndk-build manually.
targets 'xenia-app'
}
}
ndk {
abiFilters 'arm64-v8a', 'x86_64'
stl 'c++_static'
}
}
buildTypes {
release {
externalNativeBuild {
ndkBuild {
arguments 'PREMAKE_ANDROIDNDK_CONFIGURATIONS:=Release'
}
}
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
debug {
applicationIdSuffix '.debug'
debuggable true
externalNativeBuild {
ndkBuild {
arguments 'PREMAKE_ANDROIDNDK_CONFIGURATIONS:=Debug'
}
}
}
checked {
applicationIdSuffix '.checked'
debuggable true
externalNativeBuild {
ndkBuild {
arguments 'PREMAKE_ANDROIDNDK_CONFIGURATIONS:=Checked'
}
}
}
}
flavorDimensions 'distribution'
productFlavors {
github {
dimension 'distribution'
applicationIdSuffix '.github'
}
googlePlay {
dimension 'distribution'
// TODO(Triang3l): Provide a signing config for core contributors only.
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
externalNativeBuild {
ndkBuild {
path file('../../../build/xenia.wks.Android.mk')
}
}
}
dependencies {
implementation 'org.jetbrains:annotations:15.0'
}
================================================
FILE: android/android_studio_project/app/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
================================================
FILE: android/android_studio_project/app/src/main/AndroidManifest.xml
================================================
================================================
FILE: android/android_studio_project/app/src/main/java/jp/xenia/XeniaRuntimeException.java
================================================
package jp.xenia;
/**
* Base class for all unchecked exceptions thrown by the Xenia project components.
*/
public class XeniaRuntimeException extends RuntimeException {
public XeniaRuntimeException() {
}
public XeniaRuntimeException(final String name) {
super(name);
}
public XeniaRuntimeException(final String name, final Throwable cause) {
super(name, cause);
}
public XeniaRuntimeException(final Exception cause) {
super(cause);
}
}
================================================
FILE: android/android_studio_project/app/src/main/java/jp/xenia/emulator/GpuTraceViewerActivity.java
================================================
package jp.xenia.emulator;
import android.os.Bundle;
public class GpuTraceViewerActivity extends WindowedAppActivity {
@Override
protected String getWindowedAppIdentifier() {
return "xenia_gpu_vulkan_trace_viewer";
}
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_gpu_trace_viewer);
setWindowSurfaceView(findViewById(R.id.gpu_trace_viewer_surface_view));
}
}
================================================
FILE: android/android_studio_project/app/src/main/java/jp/xenia/emulator/LauncherActivity.java
================================================
package jp.xenia.emulator;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
public class LauncherActivity extends Activity {
private static final int REQUEST_OPEN_GPU_TRACE_VIEWER = 0;
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_launcher);
}
@Override
protected void onActivityResult(
final int requestCode, final int resultCode, final Intent data) {
if (requestCode == REQUEST_OPEN_GPU_TRACE_VIEWER && resultCode == RESULT_OK) {
final Uri uri = data.getData();
if (uri != null) {
final Intent gpuTraceViewerIntent = new Intent(this, GpuTraceViewerActivity.class);
final Bundle gpuTraceViewerLaunchArguments = new Bundle();
gpuTraceViewerLaunchArguments.putString("target_trace_file", uri.toString());
gpuTraceViewerIntent.putExtra(
WindowedAppActivity.EXTRA_CVARS, gpuTraceViewerLaunchArguments);
startActivity(gpuTraceViewerIntent);
}
}
}
public void onLaunchGpuTraceViewerClick(final View view) {
final Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("application/octet-stream");
startActivityForResult(intent, REQUEST_OPEN_GPU_TRACE_VIEWER);
}
public void onLaunchWindowDemoClick(final View view) {
startActivity(new Intent(this, WindowDemoActivity.class));
}
}
================================================
FILE: android/android_studio_project/app/src/main/java/jp/xenia/emulator/WindowDemoActivity.java
================================================
package jp.xenia.emulator;
import android.os.Bundle;
public class WindowDemoActivity extends WindowedAppActivity {
@Override
protected String getWindowedAppIdentifier() {
return "xenia_ui_window_vulkan_demo";
}
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_window_demo);
setWindowSurfaceView(findViewById(R.id.window_demo_surface_view));
}
}
================================================
FILE: android/android_studio_project/app/src/main/java/jp/xenia/emulator/WindowSurfaceView.java
================================================
package jp.xenia.emulator;
import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.SurfaceView;
public class WindowSurfaceView extends SurfaceView {
public WindowSurfaceView(final Context context) {
super(context);
// Native drawing is invoked from onDraw.
setWillNotDraw(false);
}
public WindowSurfaceView(final Context context, final AttributeSet attrs) {
super(context, attrs);
setWillNotDraw(false);
}
public WindowSurfaceView(
final Context context, final AttributeSet attrs, final int defStyleAttr) {
super(context, attrs, defStyleAttr);
setWillNotDraw(false);
}
public WindowSurfaceView(
final Context context, final AttributeSet attrs, final int defStyleAttr,
final int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
setWillNotDraw(false);
}
@Override
protected void onDraw(final Canvas canvas) {
final Context context = getContext();
if (!(context instanceof WindowedAppActivity)) {
return;
}
final WindowedAppActivity activity = (WindowedAppActivity) context;
activity.onWindowSurfaceDraw(false);
}
}
================================================
FILE: android/android_studio_project/app/src/main/java/jp/xenia/emulator/WindowedAppActivity.java
================================================
package jp.xenia.emulator;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.res.AssetManager;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.View;
import org.jetbrains.annotations.Nullable;
import jp.xenia.XeniaRuntimeException;
public abstract class WindowedAppActivity extends Activity {
// The EXTRA_CVARS value literal is also used in the native code.
/**
* Name of the Bundle intent extra containing Xenia config variable launch arguments.
*/
public static final String EXTRA_CVARS = "jp.xenia.emulator.WindowedAppActivity.EXTRA_CVARS";
static {
System.loadLibrary("xenia-app");
}
private final WindowSurfaceListener mWindowSurfaceListener = new WindowSurfaceListener();
// May be 0 while destroying (mainly while the superclass is).
private long mAppContext = 0;
@Nullable
private WindowSurfaceView mWindowSurfaceView = null;
private native long initializeWindowedAppOnCreate(
String windowedAppIdentifier, AssetManager assetManager);
private native void onDestroyNative(long appContext);
private native void onWindowSurfaceLayoutChange(
long appContext, int left, int top, int right, int bottom);
private native boolean onWindowSurfaceMotionEvent(long appContext, MotionEvent event);
private native void onWindowSurfaceChanged(long appContext, Surface windowSurface);
private native void paintWindow(long appContext, boolean forcePaint);
protected abstract String getWindowedAppIdentifier();
protected void setWindowSurfaceView(@Nullable final WindowSurfaceView windowSurfaceView) {
if (mWindowSurfaceView == windowSurfaceView) {
return;
}
// Detach from the old surface.
if (mWindowSurfaceView != null) {
mWindowSurfaceView.getHolder().removeCallback(mWindowSurfaceListener);
mWindowSurfaceView.setOnTouchListener(null);
mWindowSurfaceView.setOnGenericMotionListener(null);
mWindowSurfaceView.removeOnLayoutChangeListener(mWindowSurfaceListener);
mWindowSurfaceView = null;
if (mAppContext != 0) {
onWindowSurfaceChanged(mAppContext, null);
}
}
if (windowSurfaceView == null) {
return;
}
mWindowSurfaceView = windowSurfaceView;
// FIXME(Triang3l): This doesn't work if the layout has already been performed.
mWindowSurfaceView.addOnLayoutChangeListener(mWindowSurfaceListener);
mWindowSurfaceView.setOnGenericMotionListener(mWindowSurfaceListener);
mWindowSurfaceView.setOnTouchListener(mWindowSurfaceListener);
final SurfaceHolder windowSurfaceHolder = mWindowSurfaceView.getHolder();
windowSurfaceHolder.addCallback(mWindowSurfaceListener);
// If setting after the creation of the surface.
if (mAppContext != 0) {
final Surface windowSurface = windowSurfaceHolder.getSurface();
if (windowSurface != null) {
onWindowSurfaceChanged(mAppContext, windowSurface);
}
}
}
public void onWindowSurfaceDraw(final boolean forcePaint) {
if (mAppContext == 0) {
return;
}
paintWindow(mAppContext, forcePaint);
}
// Used from the native WindowedAppContext. May be called from non-UI threads.
@SuppressWarnings("UnusedDeclaration")
protected void postInvalidateWindowSurface() {
if (mWindowSurfaceView == null) {
return;
}
mWindowSurfaceView.postInvalidate();
}
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final String windowedAppIdentifier = getWindowedAppIdentifier();
mAppContext = initializeWindowedAppOnCreate(windowedAppIdentifier, getAssets());
if (mAppContext == 0) {
finish();
throw new XeniaRuntimeException(
"Error initializing the windowed app " + windowedAppIdentifier);
}
}
@Override
protected void onDestroy() {
setWindowSurfaceView(null);
if (mAppContext != 0) {
onDestroyNative(mAppContext);
}
mAppContext = 0;
super.onDestroy();
}
private class WindowSurfaceListener implements
View.OnGenericMotionListener,
View.OnLayoutChangeListener,
View.OnTouchListener,
SurfaceHolder.Callback2 {
@Override
public void onLayoutChange(
final View v, final int left, final int top, final int right, final int bottom,
final int oldLeft, final int oldTop, final int oldRight, final int oldBottom) {
if (mAppContext != 0) {
onWindowSurfaceLayoutChange(mAppContext, left, top, right, bottom);
}
}
@Override
public boolean onGenericMotion(final View view, final MotionEvent event) {
if (mAppContext == 0) {
return false;
}
return onWindowSurfaceMotionEvent(mAppContext, event);
}
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouch(final View view, final MotionEvent event) {
if (mAppContext == 0) {
return false;
}
return onWindowSurfaceMotionEvent(mAppContext, event);
}
@Override
public void surfaceCreated(final SurfaceHolder holder) {
if (mAppContext == 0) {
return;
}
onWindowSurfaceChanged(mAppContext, holder.getSurface());
}
@Override
public void surfaceChanged(
final SurfaceHolder holder, final int format, final int width, final int height) {
if (mAppContext == 0) {
return;
}
onWindowSurfaceChanged(mAppContext, holder.getSurface());
}
@Override
public void surfaceDestroyed(final SurfaceHolder holder) {
if (mAppContext == 0) {
return;
}
onWindowSurfaceChanged(mAppContext, null);
}
@Override
public void surfaceRedrawNeeded(final SurfaceHolder holder) {
onWindowSurfaceDraw(true);
}
}
}
================================================
FILE: android/android_studio_project/app/src/main/res/drawable/ic_launcher_background.xml
================================================
================================================
FILE: android/android_studio_project/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
================================================
================================================
FILE: android/android_studio_project/app/src/main/res/layout/activity_gpu_trace_viewer.xml
================================================
================================================
FILE: android/android_studio_project/app/src/main/res/layout/activity_launcher.xml
================================================
================================================
FILE: android/android_studio_project/app/src/main/res/layout/activity_window_demo.xml
================================================
================================================
FILE: android/android_studio_project/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
================================================
================================================
FILE: android/android_studio_project/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
================================================
================================================
FILE: android/android_studio_project/app/src/main/res/values/strings.xml
================================================
XeniaGPU Trace ViewerWindow Demo
================================================
FILE: android/android_studio_project/build.gradle
================================================
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.2.1'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
mavenCentral()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
================================================
FILE: android/android_studio_project/gradle/wrapper/gradle-wrapper.properties
================================================
#Mon Nov 01 23:19:20 MSK 2021
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
================================================
FILE: android/android_studio_project/gradle.properties
================================================
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app"s APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Automatically convert third-party libraries to use AndroidX
android.enableJetifier=true
================================================
FILE: android/android_studio_project/gradlew
================================================
#!/usr/bin/env sh
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"
================================================
FILE: android/android_studio_project/gradlew.bat
================================================
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
================================================
FILE: android/android_studio_project/settings.gradle
================================================
include ':app'
rootProject.name = "Xenia"
================================================
FILE: assets/icon/LICENSE
================================================
Attribution-ShareAlike 4.0 International
=======================================================================
Creative Commons Corporation ("Creative Commons") is not a law firm and
does not provide legal services or legal advice. Distribution of
Creative Commons public licenses does not create a lawyer-client or
other relationship. Creative Commons makes its licenses and related
information available on an "as-is" basis. Creative Commons gives no
warranties regarding its licenses, any material licensed under their
terms and conditions, or any related information. Creative Commons
disclaims all liability for damages resulting from their use to the
fullest extent possible.
Using Creative Commons Public Licenses
Creative Commons public licenses provide a standard set of terms and
conditions that creators and other rights holders may use to share
original works of authorship and other material subject to copyright
and certain other rights specified in the public license below. The
following considerations are for informational purposes only, are not
exhaustive, and do not form part of our licenses.
Considerations for licensors: Our public licenses are
intended for use by those authorized to give the public
permission to use material in ways otherwise restricted by
copyright and certain other rights. Our licenses are
irrevocable. Licensors should read and understand the terms
and conditions of the license they choose before applying it.
Licensors should also secure all rights necessary before
applying our licenses so that the public can reuse the
material as expected. Licensors should clearly mark any
material not subject to the license. This includes other CC-
licensed material, or material used under an exception or
limitation to copyright. More considerations for licensors:
wiki.creativecommons.org/Considerations_for_licensors
Considerations for the public: By using one of our public
licenses, a licensor grants the public permission to use the
licensed material under specified terms and conditions. If
the licensor's permission is not necessary for any reason--for
example, because of any applicable exception or limitation to
copyright--then that use is not regulated by the license. Our
licenses grant only permissions under copyright and certain
other rights that a licensor has authority to grant. Use of
the licensed material may still be restricted for other
reasons, including because others have copyright or other
rights in the material. A licensor may make special requests,
such as asking that all changes be marked or described.
Although not required by our licenses, you are encouraged to
respect those requests where reasonable. More considerations
for the public:
wiki.creativecommons.org/Considerations_for_licensees
=======================================================================
Creative Commons Attribution-ShareAlike 4.0 International Public
License
By exercising the Licensed Rights (defined below), You accept and agree
to be bound by the terms and conditions of this Creative Commons
Attribution-ShareAlike 4.0 International Public License ("Public
License"). To the extent this Public License may be interpreted as a
contract, You are granted the Licensed Rights in consideration of Your
acceptance of these terms and conditions, and the Licensor grants You
such rights in consideration of benefits the Licensor receives from
making the Licensed Material available under these terms and
conditions.
Section 1 -- Definitions.
a. Adapted Material means material subject to Copyright and Similar
Rights that is derived from or based upon the Licensed Material
and in which the Licensed Material is translated, altered,
arranged, transformed, or otherwise modified in a manner requiring
permission under the Copyright and Similar Rights held by the
Licensor. For purposes of this Public License, where the Licensed
Material is a musical work, performance, or sound recording,
Adapted Material is always produced where the Licensed Material is
synched in timed relation with a moving image.
b. Adapter's License means the license You apply to Your Copyright
and Similar Rights in Your contributions to Adapted Material in
accordance with the terms and conditions of this Public License.
c. BY-SA Compatible License means a license listed at
creativecommons.org/compatiblelicenses, approved by Creative
Commons as essentially the equivalent of this Public License.
d. Copyright and Similar Rights means copyright and/or similar rights
closely related to copyright including, without limitation,
performance, broadcast, sound recording, and Sui Generis Database
Rights, without regard to how the rights are labeled or
categorized. For purposes of this Public License, the rights
specified in Section 2(b)(1)-(2) are not Copyright and Similar
Rights.
e. Effective Technological Measures means those measures that, in the
absence of proper authority, may not be circumvented under laws
fulfilling obligations under Article 11 of the WIPO Copyright
Treaty adopted on December 20, 1996, and/or similar international
agreements.
f. Exceptions and Limitations means fair use, fair dealing, and/or
any other exception or limitation to Copyright and Similar Rights
that applies to Your use of the Licensed Material.
g. License Elements means the license attributes listed in the name
of a Creative Commons Public License. The License Elements of this
Public License are Attribution and ShareAlike.
h. Licensed Material means the artistic or literary work, database,
or other material to which the Licensor applied this Public
License.
i. Licensed Rights means the rights granted to You subject to the
terms and conditions of this Public License, which are limited to
all Copyright and Similar Rights that apply to Your use of the
Licensed Material and that the Licensor has authority to license.
j. Licensor means the individual(s) or entity(ies) granting rights
under this Public License.
k. Share means to provide material to the public by any means or
process that requires permission under the Licensed Rights, such
as reproduction, public display, public performance, distribution,
dissemination, communication, or importation, and to make material
available to the public including in ways that members of the
public may access the material from a place and at a time
individually chosen by them.
l. Sui Generis Database Rights means rights other than copyright
resulting from Directive 96/9/EC of the European Parliament and of
the Council of 11 March 1996 on the legal protection of databases,
as amended and/or succeeded, as well as other essentially
equivalent rights anywhere in the world.
m. You means the individual or entity exercising the Licensed Rights
under this Public License. Your has a corresponding meaning.
Section 2 -- Scope.
a. License grant.
1. Subject to the terms and conditions of this Public License,
the Licensor hereby grants You a worldwide, royalty-free,
non-sublicensable, non-exclusive, irrevocable license to
exercise the Licensed Rights in the Licensed Material to:
a. reproduce and Share the Licensed Material, in whole or
in part; and
b. produce, reproduce, and Share Adapted Material.
2. Exceptions and Limitations. For the avoidance of doubt, where
Exceptions and Limitations apply to Your use, this Public
License does not apply, and You do not need to comply with
its terms and conditions.
3. Term. The term of this Public License is specified in Section
6(a).
4. Media and formats; technical modifications allowed. The
Licensor authorizes You to exercise the Licensed Rights in
all media and formats whether now known or hereafter created,
and to make technical modifications necessary to do so. The
Licensor waives and/or agrees not to assert any right or
authority to forbid You from making technical modifications
necessary to exercise the Licensed Rights, including
technical modifications necessary to circumvent Effective
Technological Measures. For purposes of this Public License,
simply making modifications authorized by this Section 2(a)
(4) never produces Adapted Material.
5. Downstream recipients.
a. Offer from the Licensor -- Licensed Material. Every
recipient of the Licensed Material automatically
receives an offer from the Licensor to exercise the
Licensed Rights under the terms and conditions of this
Public License.
b. Additional offer from the Licensor -- Adapted Material.
Every recipient of Adapted Material from You
automatically receives an offer from the Licensor to
exercise the Licensed Rights in the Adapted Material
under the conditions of the Adapter's License You apply.
c. No downstream restrictions. You may not offer or impose
any additional or different terms or conditions on, or
apply any Effective Technological Measures to, the
Licensed Material if doing so restricts exercise of the
Licensed Rights by any recipient of the Licensed
Material.
6. No endorsement. Nothing in this Public License constitutes or
may be construed as permission to assert or imply that You
are, or that Your use of the Licensed Material is, connected
with, or sponsored, endorsed, or granted official status by,
the Licensor or others designated to receive attribution as
provided in Section 3(a)(1)(A)(i).
b. Other rights.
1. Moral rights, such as the right of integrity, are not
licensed under this Public License, nor are publicity,
privacy, and/or other similar personality rights; however, to
the extent possible, the Licensor waives and/or agrees not to
assert any such rights held by the Licensor to the limited
extent necessary to allow You to exercise the Licensed
Rights, but not otherwise.
2. Patent and trademark rights are not licensed under this
Public License.
3. To the extent possible, the Licensor waives any right to
collect royalties from You for the exercise of the Licensed
Rights, whether directly or through a collecting society
under any voluntary or waivable statutory or compulsory
licensing scheme. In all other cases the Licensor expressly
reserves any right to collect such royalties.
Section 3 -- License Conditions.
Your exercise of the Licensed Rights is expressly made subject to the
following conditions.
a. Attribution.
1. If You Share the Licensed Material (including in modified
form), You must:
a. retain the following if it is supplied by the Licensor
with the Licensed Material:
i. identification of the creator(s) of the Licensed
Material and any others designated to receive
attribution, in any reasonable manner requested by
the Licensor (including by pseudonym if
designated);
ii. a copyright notice;
iii. a notice that refers to this Public License;
iv. a notice that refers to the disclaimer of
warranties;
v. a URI or hyperlink to the Licensed Material to the
extent reasonably practicable;
b. indicate if You modified the Licensed Material and
retain an indication of any previous modifications; and
c. indicate the Licensed Material is licensed under this
Public License, and include the text of, or the URI or
hyperlink to, this Public License.
2. You may satisfy the conditions in Section 3(a)(1) in any
reasonable manner based on the medium, means, and context in
which You Share the Licensed Material. For example, it may be
reasonable to satisfy the conditions by providing a URI or
hyperlink to a resource that includes the required
information.
3. If requested by the Licensor, You must remove any of the
information required by Section 3(a)(1)(A) to the extent
reasonably practicable.
b. ShareAlike.
In addition to the conditions in Section 3(a), if You Share
Adapted Material You produce, the following conditions also apply.
1. The Adapter's License You apply must be a Creative Commons
license with the same License Elements, this version or
later, or a BY-SA Compatible License.
2. You must include the text of, or the URI or hyperlink to, the
Adapter's License You apply. You may satisfy this condition
in any reasonable manner based on the medium, means, and
context in which You Share Adapted Material.
3. You may not offer or impose any additional or different terms
or conditions on, or apply any Effective Technological
Measures to, Adapted Material that restrict exercise of the
rights granted under the Adapter's License You apply.
Section 4 -- Sui Generis Database Rights.
Where the Licensed Rights include Sui Generis Database Rights that
apply to Your use of the Licensed Material:
a. for the avoidance of doubt, Section 2(a)(1) grants You the right
to extract, reuse, reproduce, and Share all or a substantial
portion of the contents of the database;
b. if You include all or a substantial portion of the database
contents in a database in which You have Sui Generis Database
Rights, then the database in which You have Sui Generis Database
Rights (but not its individual contents) is Adapted Material,
including for purposes of Section 3(b); and
c. You must comply with the conditions in Section 3(a) if You Share
all or a substantial portion of the contents of the database.
For the avoidance of doubt, this Section 4 supplements and does not
replace Your obligations under this Public License where the Licensed
Rights include other Copyright and Similar Rights.
Section 5 -- Disclaimer of Warranties and Limitation of Liability.
a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
c. The disclaimer of warranties and limitation of liability provided
above shall be interpreted in a manner that, to the extent
possible, most closely approximates an absolute disclaimer and
waiver of all liability.
Section 6 -- Term and Termination.
a. This Public License applies for the term of the Copyright and
Similar Rights licensed here. However, if You fail to comply with
this Public License, then Your rights under this Public License
terminate automatically.
b. Where Your right to use the Licensed Material has terminated under
Section 6(a), it reinstates:
1. automatically as of the date the violation is cured, provided
it is cured within 30 days of Your discovery of the
violation; or
2. upon express reinstatement by the Licensor.
For the avoidance of doubt, this Section 6(b) does not affect any
right the Licensor may have to seek remedies for Your violations
of this Public License.
c. For the avoidance of doubt, the Licensor may also offer the
Licensed Material under separate terms or conditions or stop
distributing the Licensed Material at any time; however, doing so
will not terminate this Public License.
d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
License.
Section 7 -- Other Terms and Conditions.
a. The Licensor shall not be bound by any additional or different
terms or conditions communicated by You unless expressly agreed.
b. Any arrangements, understandings, or agreements regarding the
Licensed Material not stated herein are separate from and
independent of the terms and conditions of this Public License.
Section 8 -- Interpretation.
a. For the avoidance of doubt, this Public License does not, and
shall not be interpreted to, reduce, limit, restrict, or impose
conditions on any use of the Licensed Material that could lawfully
be made without permission under this Public License.
b. To the extent possible, if any provision of this Public License is
deemed unenforceable, it shall be automatically reformed to the
minimum extent necessary to make it enforceable. If the provision
cannot be reformed, it shall be severed from this Public License
without affecting the enforceability of the remaining terms and
conditions.
c. No term or condition of this Public License will be waived and no
failure to comply consented to unless expressly agreed to by the
Licensor.
d. Nothing in this Public License constitutes or may be interpreted
as a limitation upon, or waiver of, any privileges and immunities
that apply to the Licensor or You, including from the legal
processes of any jurisdiction or authority.
=======================================================================
Creative Commons is not a party to its public
licenses. Notwithstanding, Creative Commons may elect to apply one of
its public licenses to material it publishes and in those instances
will be considered the “Licensor.” The text of the Creative Commons
public licenses is dedicated to the public domain under the CC0 Public
Domain Dedication. Except for the limited purpose of indicating that
material is shared under a Creative Commons public license or as
otherwise permitted by the Creative Commons policies published at
creativecommons.org/policies, Creative Commons does not authorize the
use of the trademark "Creative Commons" or any other trademark or logo
of Creative Commons without its prior written consent including,
without limitation, in connection with any unauthorized modifications
to any of its public licenses or any other arrangements,
understandings, or agreements concerning use of licensed material. For
the avoidance of doubt, this paragraph does not form part of the
public licenses.
Creative Commons may be contacted at creativecommons.org.
================================================
FILE: docs/building.md
================================================
# Building
You must have a 64-bit machine for building and running the project. Always
run your system updater before building and make sure you have the latest
drivers.
## Setup
### Windows
* Windows 7 or later
* [Visual Studio 2022, Visual Studio 2019, or Visual Studio 2017](https://www.visualstudio.com/downloads/)
* For Visual Studio 2022, MSBuild `v142` must be used due to a compiler bug; See [#2003](https://github.com/xenia-project/xenia/issues/2003).
* [Python 3.6+](https://www.python.org/downloads/)
* Ensure Python is in PATH.
* Windows 11 SDK version 10.0.22000.0 (for Visual Studio 2019, this or any newer version)
```
git clone https://github.com/xenia-project/xenia.git
cd xenia
xb setup
# Build on command line (add --config=release for release):
xb build
# Pull latest changes, rebase, update submodules, and run premake:
xb pull
# Run premake and open Visual Studio (run the 'xenia-app' project):
xb devenv
# Run premake to update the sln/vcproj's:
xb premake
# Format code to the style guide:
xb format
```
#### Debugging
VS behaves oddly with the debug paths. Open the 'xenia-app' project properties
and set the 'Command' to `$(SolutionDir)$(TargetPath)` and the
'Working Directory' to `$(SolutionDir)..\..`. You can specify flags and
the file to run in the 'Command Arguments' field (or use `--flagfile=flags.txt`).
By default logs are written to a file with the name of the executable. You can
override this with `--log_file=log.txt`.
If running under Visual Studio and you want to look at the JIT'ed code
(available around 0xA0000000) you should pass `--emit_source_annotations` to
get helpful spacers/movs in the disassembly.
### Linux
Linux support is extremely experimental and presently incomplete.
The build script uses LLVM/Clang 9. GCC while it should work in theory, is not easily
interchangeable right now.
* Normal building via `xb build` uses Make.
* [CodeLite](https://codelite.org) is supported. `xb devenv` will generate a workspace and attempt to open it. Your distribution's version may be out of date so check their website.
* Experimental CMake generation is available to facilitate use of other IDEs such as [CLion](https://www.jetbrains.com/clion/). If `clion` is available inside `$PATH`, `xb devenv` will start it. Otherwise `build/CMakeLists.txt` needs to be generated by invoking `xb premake --devenv=cmake` manually.
Clang-9 or newer should be available from system repositories on all up to date distributions.
You will also need some development libraries. To get them on an Ubuntu system:
```bash
sudo apt-get install libgtk-3-dev libpthread-stubs0-dev liblz4-dev libx11-dev libx11-xcb-dev libvulkan-dev libsdl2-dev libiberty-dev libunwind-dev libc++-dev libc++abi-dev
```
In addition, you will need up to date Vulkan libraries and drivers for your hardware, which most distributions have in their standard repositories nowadays.
## Running
To make life easier you can set the program startup arguments in your IDE to something like `--log_file=stdout /path/to/Default.xex` to log to console rather than a file and start up the emulator right away.
================================================
FILE: docs/cpu.md
================================================
# CPU Documentation
## The JIT

The JIT is the core of Xenia. It translates Xenon PowerPC code into native
code runnable on the host computer.
There are 3 phases to translation:
1. Translation to IR (intermediate representation)
2. IR compilation/optimization
3. Backend emission
PowerPC instructions are translated to Xenia's intermediate representation
format in src/xenia/cpu/ppc/ppc_emit_*.cc (e.g. processor control is done in
[ppc_emit_control.cc](../src/xenia/cpu/ppc/ppc_emit_control.cc)). HIR opcodes
are relatively simple opcodes such that any host can define an implementation.
After the HIR is generated, it is ran through a compiler to prep it for generation.
The compiler is ran in a series of passes, the order of which is defined in
[ppc_translator.cc](../src/xenia/cpu/ppc/ppc_translator.cc). Some passes are
essential to the successful generation, while others are merely for optimization
purposes. Compiler passes are defined in src/xenia/cpu/compiler/passes with
descriptive class names.
Finally, the backend consumes the HIR and emits code that runs natively on the
host. Currently, the only backend that exists is the x64 backend, with all the
emission done in
[x64_sequences.cc](../src/xenia/cpu/backend/x64/x64_sequences.cc).
## ABI
Xenia guest functions are not directly callable, but rather must be called
through APIs provided by Xenia. Xenia will first execute a thunk to transition
the host context to a state dependent on the JIT backend, and that will call the
guest code.
### x64
Transition thunks defined in [x64_backend.cc](../src/xenia/cpu/backend/x64/x64_backend.cc#L389).
Registers are stored on the stack as defined by [StackLayout::Thunk](../src/xenia/cpu/backend/x64/x64_stack_layout.h#L96)
for later transitioning back to the host.
Some registers are reserved for usage by the JIT to store temporary variables.
See: [X64Emitter::gpr_reg_map_ and X64Emitter::xmm_reg_map_](../src/xenia/cpu/backend/x64/x64_emitter.cc#L57).
#### Integer Registers
Register | Usage
--- | ---
RAX | Scratch
RBX | JIT temp
RCX | Scratch
RDX | Scratch
RSP | Stack Pointer
RBP | Unused
RSI | PowerPC Context
RDI | Virtual Memory Base
R8-R11 | Unused (parameters)
R12-R15 | JIT temp
#### Floating Point Registers
Register | Usage
--- | ---
XMM0-XMM5 | Scratch
XMM6-XMM15 | JIT temp
## Memory
Xenia defines virtual memory as a mapped range beginning at Memory::virtual_membase(),
and physical memory as another mapped range from Memory::physical_membase()
(usually 0x100000000 and 0x200000000, respectively). If the default bases are
not available, they are shifted left 1 bit until an available range is found.
The guest only has access to these ranges, nothing else.
### Map
```
0x00000000 - 0x3FFFFFFF (1024mb) - virtual 4k pages
0x40000000 - 0x7FFFFFFF (1024mb) - virtual 64k pages
0x80000000 - 0x8BFFFFFF ( 192mb) - xex 64k pages
0x8C000000 - 0x8FFFFFFF ( 64mb) - xex 64k pages (encrypted)
0x90000000 - 0x9FFFFFFF ( 256mb) - xex 4k pages
0xA0000000 - 0xBFFFFFFF ( 512mb) - physical 64k pages (overlapped)
0xC0000000 - 0xDFFFFFFF - physical 16mb pages (overlapped)
0xE0000000 - 0xFFFFFFFF - physical 4k pages (overlapped)
```
Virtual pages are usually allocated by NtAllocateVirtualMemory, and
physical pages are usually allocated by MmAllocatePhysicalMemoryEx.
Virtual pages mapped to physical memory are also mapped to the physical membase,
i.e. virtual 0xA0000000 == physical 0x00000000
The 0xE0000000-0xFFFFFFFF range is mapped to physical memory with a single 4 KB
page offset. On Windows, memory mappings must be aligned to 64 KB, so the offset
has to be added when guest addresses are converted to host addresses in the
translated CPU code. This can't be faked other ways because calculations
involving the offset are built into games - see the following sequence:
```
srwi r9, r10, 20 # r9 = r10 >> 20
clrlwi r10, r10, 3 # r10 = r10 & 0x1FFFFFFF (physical address)
addi r11, r9, 0x200
rlwinm r11, r11, 0,19,19 # r11 = r11 & 0x1000
add r11, r11, r10 # add 1 page to addresses > 0xE0000000
# r11 = addess passed to GPU
```
## Memory Management
TODO
## References
### PowerPC
The processor in the 360 is a 64-bit PowerPC chip running in 32-bit mode.
Programs are still allowed to use 64-bit PowerPC instructions, and registers
are 64-bit as well, but 32-bit instructions will run in 32-bit mode.
The CPU is largely similar to the PPC part in the PS3, so Cell documents
often line up for the core instructions. The 360 adds some additional AltiVec
instructions, though, which are only documented in a few places (like the gcc source code, etc).
* [Free60 Info](https://free60project.github.io/wiki/Xenon_(CPU))
* [Power ISA docs](https://web.archive.org/web/20140603115759/https://www.power.org/wp-content/uploads/2012/07/PowerISA_V2.06B_V2_PUBLIC.pdf) (aka 'PowerISA')
* [PowerPC Programming Environments Manual](https://web.archive.org/web/20141028181028/https://www-01.ibm.com/chips/techlib/techlib.nsf/techdocs/F7E732FF811F783187256FDD004D3797/$file/pem_64bit_v3.0.2005jul15.pdf) (aka 'pem_64')
* [PowerPC Vector PEM](https://web.archive.org/web/20130502201029/https://www-01.ibm.com/chips/techlib/techlib.nsf/techdocs/C40E4C6133B31EE8872570B500791108/$file/vector_simd_pem_v_2.07c_26Oct2006_cell.pdf)
* [AltiVec PEM](https://web.archive.org/web/20151110180336/https://cache.freescale.com/files/32bit/doc/ref_manual/ALTIVECPEM.pdf)
* [VMX128 Opcodes](http://biallas.net/doc/vmx128/vmx128.txt)
* [AltiVec Decoding](https://github.com/kakaroto/ps3ida/blob/master/plugins/PPCAltivec/src/main.cpp)
### x64
* [Intel Manuals](https://software.intel.com/en-us/articles/intel-sdm)
* [Combined Intel Manuals](https://www.intel.com/content/dam/www/public/us/en/documents/manuals/64-ia-32-architectures-software-developer-manual-325462.pdf)
* [Apple AltiVec/SSE Migration Guide](https://developer.apple.com/legacy/library/documentation/Performance/Conceptual/Accelerate_sse_migration/Accelerate_sse_migration.pdf)
================================================
FILE: docs/cpu_todo.md
================================================
# CPU TODO
There are many improvements that can be done under `xe::cpu` to improve
debugging, performance (both to JIT and of generated code), and portability.
Some are in various states of completion, and others are just thoughts that need
more exploring.
## Debugging Improvements
### Reproducable X64 Emission
It'd be useful to be able to run a PPC function through the entire pipeline and
spit out x64 that is byte-for-byte identical across runs. This would allow
automated verification, bulk analysis, etc. Currently `X64Emitter::Emplace`
will relocate the x64 when placing it in memory, which will be at a different
location each time. Instead it would be nice to have the xbyak `calcJmpAddress`
that performs the relocations use the address of our choosing.
### Sampling Profiler
Once we have stack walking it'd be nice to take something like
[micro-profiler](https://code.google.com/p/micro-profiler/) and augment it to
support our system. This would let us run continuous performance analysis and
track hotspots in JITed code without a large performance impact. Automatically
showing the top hot functions in the debugger could help track down poor
translation much faster.
### Intel Architecture Code Analyzer Support
The [Intel ACA](https://software.intel.com/en-us/articles/intel-architecture-code-analyzer)
is a nifty tool that, given a kernel of x64, can detail theoretical performance
characteristics on different processors down to cycle timings and potential
bottlenecks on memory/execution units. It's designed to run on elf/obj/etc files
however it simply looks for special markers in the code. Having something that
walks the code cache and dumps a specially formatted file with the markers
around basic blocks could allow running the tool in bulk, or alternatively being
able to invoke it one-off by dumping a specific x64 block to disk and processing
it for display when looking at the code in the debugger would be useful.
I've done some early experiments with this and its possible to pass just a
bin file with the markers and the x64.
### Function Tracing/Coverage Information
`function_trace_data.h` contains the `FunctionTraceData` struct, which is
currently partially populated by the x64 backend. This enables tracking of which
threads a function is called on, function call count, recent callers of the
function, and even instruction-level counts.
This is all only partially implemented, though, and there's no tool to read it
out. This would be nice to get integrated into the debugger so that it can
overlay the information when viewing a function, but also useful in aggregate to
find hot functions/code paths or enhance callstacks by automatically annotating
thread information.
#### Block-level Counting
Currently the code assumes each instruction has a count, however this is
expensive and often unneeded as it can be done on a block level and then the
instruction counts can be derived from that. This can reduce the overhead (both
in memory and accounting time) by an order of magnitude.
### On-Stack Context Inspection
Currently the debugger only works with `--store_all_context_values`, as it can
only get the values of PPC registers when they are stored to the PPC context
after each instruction. As this can slow things down by ~10-20% it could be
useful to be able to preserve the optimized and register-allocated HIR so that
host registers holding context values can be derived on demand. Or, we could
just make `--store_all_context_values` faster.
## JIT Performance Improvements
### Reduce HIR Size
Currently there are a lot of pointers stored within `Instr`, `Value`, and
related types. These are big 8B values that eat a lot of memory and really
hurt the cache (especially with all the block/instruction walking done).
Aligning everything to 16B values in the arena and using 16bit indices
(or something) could shrink things a lot.
### Serialize Code Cache
The x64 code cache is currently set up to use fixed memory addresses and is even
represented as mapped memory. It should be fairly easy to back this with a file
and have all code written to disk. Adding more metadata, or perhaps a side-car
file, would allow for the code to be written to disk. On future runs the code
cache could load this data (by mapping the file containing the code right into
memory) and short cut JIT'ing entirely.
It would be possible to use a common container format (ELF/etc), however there's
elegance in not requiring any additional steps beyond the memory mapping. Such
containers could be useful for running static tools against, though.
## Portability Improvements
### Emulated Opcode Layer
Having a way to use emulated variants for any HIR opcode in a backend would
help when writing a new backend as well as when verifying the existing backends.
This may look like a C library with functions for each opcode/type pairing and
utilities to call out to them. Something like the x64 backend could then call
out to these with CallNativeSafe (or some faster equivalent) and something like
an interpreter backend would be fairly trivial to write.
## X64 Backend Improvements
### Implement Emulated Instructions
There are a ton of half-implemented HIR opcodes that call out to C++ to do their
work. These are extremely expensive as they incur a full guest-to-host thunk
(~hundreds of instructions!). Basically, any of the `Emulate*`/`CallNativeSafe`
functions in `x64_sequences.cc` need to be replaced with proper AVX/AVX2
variants.
### Increase Register Availability
Currently only a few x64 registers are usable (due to reservations by the
backend or ABI conflicts). Though register pressure is surprisingly light in
most cases there are pathological cases that result in a lot of spills. By
freeing up some of the registers these spills could be reduced.
### Constant Pooling
This may make sense as a compiler pass instead.
Right now, particular sequences of instructions are nasty - such as anything
using `LoadConstantXmm` to load non-zero or non-1 vec128's. Instead of doing the
super fat (20-30byte!) constant loads as they are done now it may be better to
keep a per-function constant table and instead use RIP-relative addressing (or
something) to use the memory-form AVX instructions.
For example, right now this:
```
v82.v128 = [0,1,2,3]
v83.v128 = or v81.v128, v82.128
```
Translates to (something like):
```
mov([rsp+0x...], 0x00000000)
mov([rsp+0x...+4], 0x00000001)
mov([rsp+0x...+8], 0x00000002)
mov([rsp+0x...+12], 0x00000003)
vmovdqa(xmm2, [rsp+0x...])
vor(xmm2, xmm2, xmm2)
```
Where as it could be:
```
vor(xmm2, xmm2, [rip+0x...])
```
Whether the cost of doing the constant de-dupe is worth it remains to be seen.
Right now it's wasting a lot of instruction cache space, increasing decode time,
and potentially using a lot more memory bandwidth.
## Optimization Improvements
### Speed Up RegisterAllocationPass
Currently the slowest pass, this could be improved by requiring less use
tracking or perhaps maintaining the use tracking in other passes. A faster
SortUsageList (radix or something fancy?) may be helpful as well.
### More Opcodes in ConstantPropagationPass
There's a few HIR opcodes with no handling, and others with minimal handling.
It'd be nice to know what paths need improvement and add them, as any work here
makes things free later on.
### Cross-Block ConstantPropagationPass
Constant propagation currently only occurs within a single block. This makes it
difficult to optimize common PPC patterns like loading the constants 0 or 1 into
a register before a loop and other loads of expensive altivec values.
Either ControlFlowAnalysisPass or DataFlowAnalysisPass could be piggy-backed to
track constant load_context/store_context's across block bounds and propagate
the values. This is simpler than dynamic values as no phi functions or anything
fancy needs to happen.
### Add TypePropagationPass
There are many extensions/truncations in generated code right now due to
various load/stores of varying widths. Being able to find and short-
circuit the conversions early on would make following passes cleaner
and faster as they'd have to trace through fewer value definitions and there'd
be less extraneous movs in the final code.
Example (after ContextPromotion):
```
v82.i32 = truncate v81.i64
v83.i32 = and v82.i32, 3F
v85.i64 = zero_extend v84.i32
```
Becomes (after DCE/etc):
```
v85.i64 = and v81.i64, 3F
```
### Enhance MemorySequenceCombinationPass with Extend/Truncate
Currently this pass will look for byte_swap and merge that into loads/stores.
This allows for better final codegen at the cost of making optimization more
difficult, so it only happens at the end of the process.
There's currently TODOs in there for adding extend/truncate support, which
will extend what it does with swaps to also merge the
sign_extend/zero_extend/truncate into the matching load/store. This allows for
the x64 backend to generate the proper mov's that do these operations without
requiring additional steps. Note that if we had a LIR and a peephole optimizer
this would be better done there.
Load with swap and extend:
```
v1.i32 = load v0
v2.i32 = byte_swap v1.i32
v3.i64 = zero_extend v2.i32
```
Becomes:
```
v1.i64 = load_convert v0, [swap|i32->i64,zero]
```
Store with truncate and swap:
```
v1.i64 = ...
v2.i32 = truncate v1.i64
v3.i32 = byte_swap v2.i32
store v0, v3.i32
```
Becomes:
```
store_convert v0, v1.i64, [swap|i64->i32,trunc]
```
### Add DeadStoreEliminationPass
Generic DSE pass, removing all redundant stores. ContextPromotion may be
able to take care of most of these, as the input assembly is generally
pretty optimized already. This pass would mainly be looking for introduced
stores, such as those from comparisons.
Currently ControlFlowAnalysisPass will annotate blocks with incoming/outgoing
edges as well as dominators, and that could be used to check whether stores into
the context are used in their destination block or instead overwritten
(currently they almost never are).
If this pass was able to remove a good number of the stores then the comparisons
would also be removed with dead code elimination and dramatically reduce branch
overhead.
Example:
```
:
v0 = compare_ult ... (later removed by DCE)
v1 = compare_ugt ... (later removed by DCE)
v2 = compare_eq ...
store_context +300, v0 <-- removed
store_context +301, v1 <-- removed
store_context +302, v2 <-- removed
branch_true v1, ...
:
v3 = compare_ult ...
v4 = compare_ugt ...
v5 = compare_eq ...
store_context +300, v3 <-- these may be required if at end of function
store_context +301, v4 or before a call
store_context +302, v5
branch_true v5, ...
```
### Add X64CanonicalizationPass
For various opcodes add copies/commute the arguments to match x64
operand semantics. This makes code generation easier and if done
before register allocation can prevent a lot of extra shuffling in
the emitted code.
Example:
```
:
v0 = ...
v1 = ...
v2 = add v0, v1 <-- v1 now unused
```
Becomes:
```
v0 = ...
v1 = ...
v1 = add v1, v0 <-- src1 = dest/src, so reuse for both
by commuting and setting dest = src1
```
### Add MergeLocalSlotsPass
As the RegisterAllocationPass runs it generates load_local/store_local as it
spills. Currently each set of locals is unique to each block, which in very
large functions can result in a lot of locals that are only used briefly. It
may be useful to use the results of the ControlFlowAnalysisPass to track local
liveness and merge the slots so they are reused when they cannot possibly be
live at the same time. This saves stack space and potentially improves cache
behavior.
================================================
FILE: docs/gpu.md
================================================
# GPU Documentation
## The Xenos Chip
The [Xenos](https://en.wikipedia.org/wiki/Xenos_\(graphics_chip\)) is a graphics
chip designed by AMD based off of the R5xx architecture.
### Command Processing
The Xenos runs commands supplied to it directly by the DirectX bare-bones driver
via a ringbuffer located in system memory.
The bulk of the command processing code is located at
[src/xenia/gpu/command_processor.cc](../src/xenia/gpu/command_processor.cc)
### EDRAM
The Xenos uses special high-speed memory located on the same die as the chip to
store framebuffers/render targets.
TODO: More documentation
## Options
### General
See the top of [src/xenia/gpu/gpu_flags.cc](../src/xenia/gpu/gpu_flags.cc).
`--vsync=false` will attempt to render the game as fast as possible instead of
waiting for a fixed 60hz timer.
### Vulkan
See the top of [src/xenia/gpu/vulkan/vulkan_gpu_flags.cc](../src/xenia/gpu/vulkan/vulkan_gpu_flags.cc).
`vulkan_dump_disasm=true` "Dump shader disassembly. NVIDIA only supported."
## Tools
### Shaders
#### Shader Dumps
Adding `--dump_shaders=path/` will write all translated shaders to the given
path with names based on input hash (so they'll be stable across runs).
Binaries containing the original microcode will be placed side-by-side with
the dumped output to make it easy to pipe to `xe-gpu-shader-compiler`.
#### xe-gpu-shader-compiler
A standalone shader compiler exists to allow for quick shader translation
testing. You can pass a binary ucode shader in and get either disassembled
ucode or translated source out. This is best used through the Shader
Playground tool.
```
xe-gpu-shader-compiler \
--shader_input=input_file.bin.vs (or .fs)
--shader_output=output_file.txt
--shader_output_type=ucode (or spirvtext)
```
#### Shader Playground
Built separately (for now) under [tools/shader-playground/](../tools/shader-playground/)
is a GUI for interactive shader assembly, disassembly, validation, and
translation.

Entering shader microcode on the left will invoke the XNA Game Studio
D3D compiler to translate the ucode to binary. The D3D compiler is then
used to disassemble the binary and display the optimized form. If
`xe-gpu-shader-compiler` has been built the ucode will be passed to that
for disassembly and that will then be passed through D3D compiler. If
the output of D3D compiler on the xenia disassembly doesn't match the
original D3D compiler output the box will turn red, indicating that the
disassembly is broken. Finally, the right most box will show the
translated shader in the desired format.
For more information and setup instructions see
[tools/shader-playground/README.md](../tools/shader-playground/README.md).
### xe-gpu-trace-viewer
To quickly iterate on graphical issues, xenia can dump frames (or sequences of
frames) while running that can be opened and inspected in a separate app.
The basic workflow is:
1. Capture the frame in game (using F4) or a stream of frames.
2. Add the file path to the xe-gpu-trace-viewer Debugging command line in
Visual Studio.
3. Launch xe-gpu-trace-viewer.
4. Poke around, find issues, etc.
5. Modify code.
6. Build and relaunch.
7. Goto 4.
#### Capturing Frames
First, specify a path to capture traces to with
`--trace_gpu_prefix=path/file_prefix_`. All files will have a randomish name
based on that.
When running xenia.exe you can hit F4 at any time to capture the next frame the
game tries to draw (up until a VdSwap call). The file can be used immediately.
#### Capturing Sequences
Passing `--trace_gpu_stream` will write all frames rendered to a file, allowing
you to seek through them in the trace viewer. These files will get large.
## References
### Command Buffer/Registers
Registers documented at [src/xenia/gpu/register_table.inc](../src/xenia/gpu/register_table.inc).
PM4 commands documented at [src/xenia/gpu/xenos.h](../src/xenia/gpu/xenos.h#L521).
#### Performance Counters that may be read back by D3D
They are 64-bit values and have a high and low 32-bit register as well as a `SELECT` register each:
- CP_PERFCOUNTER0
- RBBM_PERFCOUNTER0
- RBBM_PERFCOUNTER1
- SQ_PERFCOUNTER0
- SQ_PERFCOUNTER1
- SQ_PERFCOUNTER2
- SQ_PERFCOUNTER3
- VGT_PERFCOUNTER0
- VGT_PERFCOUNTER1
- VGT_PERFCOUNTER2
- VGT_PERFCOUNTER3
- VC_PERFCOUNTER0
- VC_PERFCOUNTER1
- VC_PERFCOUNTER2
- VC_PERFCOUNTER3
- PA_SU_PERFCOUNTER0
- PA_SU_PERFCOUNTER1
- PA_SU_PERFCOUNTER2
- PA_SU_PERFCOUNTER3
- PA_SC_PERFCOUNTER0
- PA_SC_PERFCOUNTER1
- PA_SC_PERFCOUNTER2
- PA_SC_PERFCOUNTER3
- HZ_PERFCOUNTER0
- HZ_PERFCOUNTER1
- TCR_PERFCOUNTER0
- TCR_PERFCOUNTER1
- TCM_PERFCOUNTER0
- TCM_PERFCOUNTER1
- TCF_PERFCOUNTER0
- TCF_PERFCOUNTER1
- TCF_PERFCOUNTER2
- TCF_PERFCOUNTER3
- TCF_PERFCOUNTER4
- TCF_PERFCOUNTER5
- TCF_PERFCOUNTER6
- TCF_PERFCOUNTER7
- TCF_PERFCOUNTER8
- TCF_PERFCOUNTER9
- TCF_PERFCOUNTER10
- TCF_PERFCOUNTER11
- TP0_PERFCOUNTER0
- TP0_PERFCOUNTER1
- TP1_PERFCOUNTER0
- TP1_PERFCOUNTER1
- TP2_PERFCOUNTER0
- TP2_PERFCOUNTER1
- TP3_PERFCOUNTER0
- TP3_PERFCOUNTER1
- SX_PERFCOUNTER0
- BC_PERFCOUNTER0
- BC_PERFCOUNTER1
- BC_PERFCOUNTER2
- BC_PERFCOUNTER3
- MC0_PERFCOUNTER0
- MC1_PERFCOUNTER0
- MH_PERFCOUNTER0
- MH_PERFCOUNTER1
- MH_PERFCOUNTER2
- BIF_PERFCOUNTER0
### Shaders
* [LLVM R600 Tables](https://llvm.org/viewvc/llvm-project/llvm/trunk/lib/Target/AMDGPU/R600Instructions.td)
** The opcode formats don't match, but the name->psuedo code is correct.
* [xemit](https://github.com/gligli/libxemit/blob/master/xemitops.c)
================================================
FILE: docs/instruction_tracing.md
================================================
In x64_tracers.cc:
Enable tracing:
```
#define ITRACE 1 <--- for only ppc instructions
#define DTRACE 1 <--- add HIR data
```
If tracing data, run with the following flags:
```
--store_all_context_values
```
By default, tracing will start at the beginning and only for the specified
thread.
Change traced thread by thread creation ID:
```
#define TARGET_THREAD 4
```
To only trace at a certain point, change default trace flag to false:
```
bool trace_enabled = true;
```
Add a breakpoint:
```
--break_on_instruction=0x821009A4
```
On break, add the following to the Watch window and set it to true:
```
xe::cpu::backend::x64::trace_enabled
```
Continue, and watch stuff appear in the log.
================================================
FILE: docs/kernel.md
================================================
# Kernel Documentation
## Kernel shims
Xenia implements all kernel APIs as native functions under the host.
When a module is loaded, the loader will find all kernel imports, put a syscall in
their place, then lookup a kernel export and link it to each import. The JIT will
generate a sequence of instructions to call into Xenia's export if it encounters a syscall.
Currently, there are two ways an export can be defined -
[for example](../src/xenia/kernel/xboxkrnl/xboxkrnl_audio.cc):
* `SHIM_CALL XAudioGetSpeakerConfig_shim(PPCContext* ppc_context, KernelState* kernel_state)`
* `dword_result_t XAudioGetSpeakerConfig(lpdword_t config_ptr)`
The `SHIM_CALL` convention is deprecated, but allows a closer look at the internals of how
calls are done. `ppc_context` is the guest PowerPC context, which holds all guest
registers. Function parameters are fetched from r3...r10 (`SHIM_GET_ARG_32`), and
additional parameters are loaded from the stack. The return value (if there is one)
is stored in r3 (`SHIM_SET_RETURN_32`).
Details on how calls transition from guest -> host can be found in the [cpu documentation](cpu.md).
The newer convention does the same, but uses templates to automate the process
of loading arguments and setting a return value.
## Kernel Modules
Xenia has an implementation of two xbox kernel modules, xboxkrnl.exe and xam.xex
### xboxkrnl.exe - Xbox kernel
Defined under src/xenia/kernel/xboxkrnl.
This is a slightly modified version of the NT kernel. Most of the APIs
are equivalent to ones you'd find on MSDN or other online sources.
Source files are organized into groups of APIs.
### xam.xex - Xbox Auxiliary Methods
Defined under src/xenia/kernel/xam.
This module implements functionality specific to the Xbox.
================================================
FILE: docs/ppc/vmx128.txt
================================================
2006/09/01 Revision 1.2
-----------------------------------------------------------------
This is a description of the VMX128-type opcodes found on
the xbox360 processor. I figured this out by looking at
various disassmblies, so there might some errors and
missing instructions. Some instructions have unknown
semantics for me.
See comments or corrections to sb#biallas.net
=================================================================
Conventions:
VD128, VS128: 5 lower bits of a VMX128 vector register
number
VDh: upper 2 bits of VD128
(so register number is (VDh << 5 | VD128))
VA128: same as VD128
A: bit 6 of VA128
a: bit 5 of VA128
(so register number is (A<<6 | a<<5 | VA128))
VB128: same as VD128
VBh: same as VDh
VC128: 3 bits of a VMX128 vector register number
(you can only use vr0-vr7 here)
RA, RB: general purpose register number
UIMM: unsigned immediate value
SIMM: signed immediate value
PERMh: upper 3 bits of a permutation
PERMl: lower 5 bits of a permutation
x, y, z: unknown immediate values
=================================================================
lvewx128 Load Vector128 Element Word Indexed
|0 0 0 1 0 0| VD128 | RA | RB |0 0 0 1 0 0 0|VDh|1 1|
lvewx128 vr(VD128), r(RA), r(RB)
=================================================================
lvlx128 Load Vector128 Left Indexed
|0 0 0 1 0 0| VD128 | RA | RB |1 0 0 0 0 0 0|VDh|1 1|
lvlx128 vr(VD128), r(RA), r(RB)
=================================================================
lvrx128 Load Vector128 Right Indexed
|0 0 0 1 0 0| VD128 | RA | RB |1 0 0 0 1 0 0|VDh|1 1|
lvrx128 vr(VD128), r(RA), r(RB)
=================================================================
lvlxl128 Load Vector128 Left Indexed LRU
|0 0 0 1 0 0| VD128 | RA | RB |1 1 0 0 0 0 0|VDh|1 1|
lvlxl128 vr(VD128), r(RA), r(RB)
=================================================================
lvrxl128 Load Vector128 Right Indexed LRU
|0 0 0 1 0 0| VD128 | RA | RB |1 1 0 0 1 0 0|VDh|1 1|
lvrxl128 vr(VD128), r(RA), r(RB)
=================================================================
lvsl128 Load Vector128 for Shift Left
|0 0 0 1 0 0| VD128 | RA | RB |0 0 0 0 0 0 0|VDh|1 1|
lvsl128 vr(VD128), r(RA), r(RB)
=================================================================
lvsr128 Load Vector128 for Shift Right
|0 0 0 1 0 0| VD128 | RA | RB |0 0 0 0 1 0 0|VDh|1 1|
lvsr128 vr(VD128), r(RA), r(RB)
=================================================================
lvx128 Load Vector128 Indexed
|0 0 0 1 0 0| VD128 | RA | RB |0 0 0 1 1 0 0|VDh|1 1|
lvx128 vr(VD128), r(RA), r(RB)
=================================================================
lvxl128 Load Vector128 Indexed LRU
|0 0 0 1 0 0| VD128 | RA | RB |0 1 0 1 1 0 0|VDh|1 1|
lvxl128 vr(VD128), r(RA), r(RB)
=================================================================
stewx128 Store Vector128 Element Word Indexed
|0 0 0 1 0 0| VS128 | RA | RB |0 1 1 0 0 0 0|VDh|1 1|
stvewx128 vr(VS128), r(RA), r(RB)
=================================================================
stvlx128 Store Vector128 Left Indexed
|0 0 0 1 0 0| VS128 | RA | RB |1 0 1 0 0 0 0|VDh|1 1|
stvlx128 vr(VS128), r(RA), r(RB)
=================================================================
stvlxl128 Store Vector128 Left Indexed LRU
|0 0 0 1 0 0| VS128 | RA | RB |1 1 1 0 0 0 0|VDh|1 1|
lvlxl128 vr(VS128), r(RA), r(RB)
=================================================================
stvrx128 Store Vector128 Right Indexed
|0 0 0 1 0 0| VS128 | RA | RB |1 0 1 0 1 0 0|VDh|1 1|
stvrx128 vr(VS128), r(RA), r(RB)
=================================================================
stvrxl128 Store Vector128 Right Indexed LRU
|0 0 0 1 0 0| VS128 | RA | RB |1 1 1 0 1 0 0|VDh|1 1|
stvrxl128 vr(VS128), r(RA), r(RB)
=================================================================
stvx128 Store Vector128 Indexed
|0 0 0 1 0 0| VS128 | RA | RB |0 0 1 1 1 0 0|VDh|1 1|
stvx128 vr(VS128), r(RA), r(RB)
=================================================================
stvxl128 Store Vector128 Indexed LRU
|0 0 0 1 0 0| VS128 | RA | RB |0 1 1 1 1 0 0|VDh|1 1|
stvxl128 vr(VS128), r(RA), r(RB)
=================================================================
vaddfp128 Vector128 Add Floating Point
|0 0 0 1 0 1| VD128 | VA128 | VB128 |A|0 0 0 0|a|1|VDh|VBh|
vaddfp128 vr(VD128), vr(VA128), vr(VB128)
=================================================================
vand128 Vector128 Logical AND
|0 0 0 1 0 1| VD128 | VA128 | VB128 |A|1 0 0 0|a|1|VDh|VBh|
vand128 vr(VD128), vr(VA128), vr(VB128)
=================================================================
vandc128 Vector128 Logical AND
with Complement
|0 0 0 1 0 1| VD128 | VA128 | VB128 |A|1 0 1 0|a|1|VDh|VBh|
vandc128 vr(VD128), vr(VA128), vr(VB128)
=================================================================
vcfpsxws128 Vector128 Convert From Floating-Point to
Signed Fixed-Point Word Saturate
|0 0 0 1 1 0| VD128 | SIMM | VB128 |0 1 0 0 0 1 1|VDh|VBh|
vcfpsxws128 vr(VD128), vr(VB128), SIMM
=================================================================
vcfpuxws128 Vector128 Convert From Floating-Point to
Unsigned Fixed-Point Word Saturate
|0 0 0 1 1 0| VD128 | UIMM | VB128 |0 1 0 0 1 1 1|VDh|VBh|
vcfpuxws128 vr(VD128), vr(VB128), UIMM
=================================================================
vcmpbfp128 Vector128 Compare Bounds
Floating Point
|0 0 0 1 1 0| VD128 | VA128 | VB128 |A|0 1 1|R|a|0|VDh|VBh|
vcmpbfp128 vr(VD128), vr(VA128), vr(VB128) (R == 0)
vcmpbfp128. vr(VD128), vr(VA128), vr(VB128) (R == 1)
=================================================================
vcmpeqfp128 Vector128 Compare Equal-to
Floating Point
|0 0 0 1 1 0| VD128 | VA128 | VB128 |A|0 0 0|R|a|0|VDh|VBh|
vcmpeqfp128 vr(VD128), vr(VA128), vr(VB128) (R == 0)
vcmpeqfp128. vr(VD128), vr(VA128), vr(VB128) (R == 1)
=================================================================
vcmpequw128 Vector128 Compare Equal-to
Unsigned Word
|0 0 0 1 1 0| VD128 | VA128 | VB128 |A|1 0 0|R|a|0|VDh|VBh|
vcmpequw128 vr(VD128), vr(VA128), vr(VB128) (R == 0)
vcmpequw128. vr(VD128), vr(VA128), vr(VB128) (R == 1)
=================================================================
vcmpgefp128 Vector128 Compare Greater-Than-
or-Equal-to Floating Point
|0 0 0 1 1 0| VD128 | VA128 | VB128 |A|0 0 1|R|a|0|VDh|VBh|
vcmpgefp128 vr(VD128), vr(VA128), vr(VB128) (R == 0)
vcmpgefp128. vr(VD128), vr(VA128), vr(VB128) (R == 1)
=================================================================
vcmpgtfp128 Vector128 Compare Greater-Than
Floating-Point
|0 0 0 1 1 0| VD128 | VA128 | VB128 |A|0 1 0|R|a|0|VDh|VBh|
vcmpgtfp128 vr(VD128), vr(VA128), vr(VB128) (R == 0)
vcmpgtfp128. vr(VD128), vr(VA128), vr(VB128) (R == 1)
=================================================================
vcsxwfp128 Vector128 Convert From Signed Fixed-Point
Word to Floating-Point
|0 0 0 1 1 0| VD128 | UIMM | VB128 |0 1 0 1 0 1 1|VDh|VBh|
vcsxwfp128 vr(VD128), vr(VB128), SIMM
=================================================================
vcuxwfp128 Vector128 Convert From Unsigned Fixed-Point
Word to Floating-Point
|0 0 0 1 1 0| VD128 | UIMM | VB128 |0 1 0 1 1 1 1|VDh|VBh|
vcuxwfp128 vr(VD128), vr(VB128), UIMM
=================================================================
vexptefp128 Vector128 2 Raised to the Exponent
Estimate Floating Point
|0 0 0 1 1 0| VD128 |0 0 0 0 0| VB128 |1 1 0 1 0 1 1|VDh|VBh|
vexptefp128 vr(VD128), vr(VB128)
=================================================================
vlogefp128 Vector128 Log2 Estimate
Floating Point
|0 0 0 1 1 0| VD128 |0 0 0 0 0| VB128 |1 1 0 1 1 1 1|VDh|VBh|
vlogefp128 vr(VD128), vr(VB128)
=================================================================
vmaddcfp128 Vector128 Multiply Add
Floating Point
|0 0 0 1 0 1| VDS128 | VA128 | VB128 |A|0 1 0 0|a|1|VDh|VBh|
vmaddcfp128 vr(VDS128), vr(VA128), vr(VSD128), vr(VB128)
=================================================================
vmaddfp128 Vector128 Multiply Add
Floating Point
|0 0 0 1 0 1| VDS128 | VA128 | VB128 |A|0 0 1 1|a|1|VDh|VBh|
vmaddfp128 vr(VDS128), vr(VA128), vr(VB128), vr(VDS128)
=================================================================
vmaxfp128 Vector128 Maximum
Floating Point
|0 0 0 1 1 0| VD128 | VA128 | VB128 |A|1 0 1 0|a|0|VDh|VBh|
vmaxfp128 vr(VD128), vr(VA128), vr(VB128)
=================================================================
vminfp128 Vector128 Minimum
Floating Point
|0 0 0 1 1 0| VD128 | VA128 | VB128 |A|1 0 1 1|a|0|VDh|VBh|
vminfp128 vr(VD128), vr(VA128), vr(VB128)
=================================================================
vmrghw128 Vector128 Merge High Word
|0 0 0 1 1 0| VD128 | VA128 | VB128 |A|1 1 0 0|a|0|VDh|VBh|
vmrghw128 vr(VD128), vr(VA128), vr(VB128)
=================================================================
vmrglw128 Vector128 Merge Low Word
|0 0 0 1 1 0| VD128 | VA128 | VB128 |A|1 1 0 1|a|0|VDh|VBh|
vmrglw128 vr(VD128), vr(VA128), vr(VB128)
=================================================================
vmsum3fp128 Vector128 Multiply Sum 3-way
Floating Point
|0 0 0 1 0 1| VD128 | VA128 | VB128 |A|0 1 1 0|a|1|VDh|VBh|
vmsub3fp128 vr(VD128), vr(VA128), vr(VB128)
=================================================================
vmsum4fp128 Vector128 Multiply Sum 4-way
Floating-Point
|0 0 0 1 0 1| VD128 | VA128 | VB128 |A|0 1 1 1|a|1|VDh|VBh|
vmsub4fp128 vr(VD128), vr(VA128), vr(VB128)
=================================================================
vmulfp128 Vector128 Multiply
Floating-Point
|0 0 0 1 0 1| VD128 | VA128 | VB128 |A|0 0 1 0|a|1|VDh|VBh|
vmulfp128 vr(VD128), vr(VA128), vr(VB128)
=================================================================
vnmsubfp128 Vector128 Negative Multiply-Subtract
Floating Point
|0 0 0 1 0 1| VDS128 | VA128 | VB128 |A|0 1 0 1|a|1|VDh|VBh|
vnmsubfp128 vr(VDS128), vr(VA128), vr(VB128), vr(VDS128)
=================================================================
vnor128 Vector128 Logical NOR
|0 0 0 1 0 1| VD128 | VA128 | VB128 |A|1 0 1 0|a|1|VDh|VBh|
vnor128 vr(VD128), vr(VA128), vr(VB128)
=================================================================
vor128 Vector128 Logical OR
|0 0 0 1 0 1| VD128 | VA128 | VB128 |A|1 0 1 1|a|1|VDh|VBh|
vor128 vr(VD128), vr(VA128), vr(VB128)
=================================================================
vperm128 Vector128 Permutation
|0 0 0 1 0 1| VD128 | VA128 | VB128 |A|0| VC |a|0|VDh|VBh|
vperm128 vr(VD128), vr(VA128), vr(VB128), vr(VC)
=================================================================
vpermwi128 Vector128 Permutate Word Immediate
|0 0 0 1 1 0| VD128 | PERMl | VB128 |0|1|PERMh|0|1|VDh|VBh|
vpermwi128 vr(VD128), vr(VB128), (PERMh << 5 | PERMl)
=================================================================
vpkd3d128 Vector128 Pack D3Dtype, Rotate Left
Immediate and Mask Insert
|0 0 0 1 1 0| VD128 | x | y | VB128 |1 1 0| z |0 1|VDh|VBh|
vpkd3d128 vr(VD128), vr(VB128), x, y, z
=================================================================
vpkshss128 Vector128 Pack Signed Half Word
Signed Saturate
|0 0 0 1 0 1| VD128 | VA128 | VB128 |A|1 0 0 0|a|0|VDh|VBh|
vpkshss128 vr(VD128), vr(VA128), vr(VB128)
=================================================================
vpkshus128 Vector128 Pack Signed Half Word
Unsigned Saturate
|0 0 0 1 0 1| VD128 | VA128 | VB128 |A|1 0 0 1|a|0|VDh|VBh|
vpkshus128 vr(VD128), vr(VA128), vr(VB128)
=================================================================
vpkswss128 Vector128 Pack Signed Word
Signed Saturate
|0 0 0 1 0 1| VD128 | VA128 | VB128 |A|1 0 1 0|a|0|VDh|VBh|
vpkswss128 vr(VD128), vr(VA128), vr(VB128)
=================================================================
vpkswus128 Vector128 Pack Signed Word
Unsigned Saturate
|0 0 0 1 0 1| VD128 | VA128 | VB128 |A|1 0 1 1|a|0|VDh|VBh|
vpkswus128 vr(VD128), vr(VA128), vr(VB128)
=================================================================
vpkuhum128 Vector128 Pack Unsigned Half Word
Unsigned Modulo
|0 0 0 1 0 1| VD128 | VA128 | VB128 |A|1 1 0 0|a|0|VDh|VBh|
vpkuhum128 vr(VD128), vr(VA128), vr(VB128)
=================================================================
vpkuhus128 Vector128 Pack Unsigned Half Word
Unsigned Saturate
|0 0 0 1 0 1| VD128 | VA128 | VB128 |A|1 1 0 1|a|0|VDh|VBh|
vpkuhus128 vr(VD128), vr(VA128), vr(VB128)
=================================================================
vpkuwum128 Vector128 Pack Unsigned Word
Unsigned Modulo
|0 0 0 1 0 1| VD128 | VA128 | VB128 |A|1 1 1 0|a|0|VDh|VBh|
vpkuwum128 vr(VD128), vr(VA128), vr(VB128)
=================================================================
vpkuwus128 Vector128 Pack Unsigned Word
Unsigned Saturate
|0 0 0 1 0 1| VD128 | VA128 | VB128 |A|1 1 1 1|a|0|VDh|VBh|
vpkuwus128 vr(VD128), vr(VA128), vr(VB128)
=================================================================
vrefp128 Vector128 Reciprocal Estimate
Floating Point
|0 0 0 1 1 0| VD128 |0 0 0 0 0| VB128 |1 1 0 0 0 1 1|VDh|VBh|
vrefp128 vr(VD128), vr(VB128)
=================================================================
vrfim128 Vector128 Round to Floating-Point
Integer toward -oo
|0 0 0 1 1 0| VD128 |0 0 0 0 0| VB128 |0 1 1 0 0 1 1|VDh|VBh|
vrfim128 vr(VD128), vr(VB128)
=================================================================
vrfin128 Vector128 Round to Floating-Point
Integer toward Nearest
|0 0 0 1 1 0| VD128 |0 0 0 0 0| VB128 |0 1 1 0 1 1 1|VDh|VBh|
vrfin128 vr(VD128), vr(VB128)
=================================================================
vrfip128 Vector128 Round to Floating-Point
Integer toward +oo
|0 0 0 1 1 0| VD128 |0 0 0 0 0| VB128 |0 1 1 1 0 1 1|VDh|VBh|
vrfip128 vr(VD128), vr(VB128)
=================================================================
vrfiz128 Vector128 Round to Floating-Point
Integer toward Zero
|0 0 0 1 1 0| VD128 |0 0 0 0 0| VB128 |0 1 1 1 1 1 1|VDh|VBh|
vrfiz128 vr(VD128), vr(VB128)
=================================================================
vrlimi128 Vector128 Rotate Left Immediate
and Mask Insert
|0 0 0 1 1 0| VD128 | UIMM | VB128 |1 1 1| z |0 1|VDh|VBh|
vrlimi128 vr(VD128), vr(VB128), UIMM, z
=================================================================
vrlw128 Vector128 Rotate Left Word
|0 0 0 1 0 1| VD128 | VA128 | VB128 |A|0 0 0 1|a|1|VDh|VBh|
vrlw128 vr(VD128), vr(VA128), vr(VB128)
=================================================================
vrsqrtefp128 Vector128 Reciprocal Square Root
Estimate Floating Point
|0 0 0 1 1 0| VD128 |0 0 0 0 0| VB128 |1 1 0 0 1 1 1|VDh|VBh|
vrsqrtefp128 vr(VD128), vr(VB128)
=================================================================
vsel128 Vector128 Select
|0 0 0 1 0 1| VDS128 | VA128 | VB128 |A|1 1 0 1|a|1|VDh|VBh|
vsel128 vr(VDS128), vr(VA128), vr(VB128), vr(VDS128)
=================================================================
vsldoi128 Vector128 Shift Left Double
by Octet Immediate
|0 0 0 1 0 0| VD128 | VA128 | VB128 |A| SHB |a|1|VDh|VBh|
vsldoi128 vr(VD128), vr(VA128), vr(VB128), SHB
=================================================================
vslo128 Vector128 Shift Left Octet
|0 0 0 1 0 1| VD128 | VA128 | VB128 |A|1 1 1 0|a|1|VDh|VBh|
vslo128 vr(VD128), vr(VA128), vr(VB128)
=================================================================
vslw128 Vector128 Shift Left Word
|0 0 0 1 1 0| VD128 | VA128 | VB128 |A|0 0 1 1|a|1|VDh|VBh|
vslw128 vr(VD128), vr(VA128), vr(VB128)
=================================================================
vspltisw128 Vector128 Splat Immediate
Signed Word
|0 0 0 1 1 0| VD128 | SIMM | VB128 |1 1 1 0 1 1 1|VDh|VBh|
vspltisw128 vr(VD128), vr(VB128), SIMM
=================================================================
vspltw128 Vector128 Splat Word
|0 0 0 1 1 0| VD128 | UIMM | VB128 |1 1 1 0 0 1 1|VDh|VBh|
vspltw128 vr(VD128), vr(VB128), UIMM
=================================================================
vsraw128 Vector128 Shift Right
Arithmetic Word
|0 0 0 1 1 0| VD128 | VA128 | VB128 |A|0 1 0 1|a|1|VDh|VBh|
vsraw128 vr(VD128), vr(VA128), vr(VB128)
=================================================================
vsro128 Vector128 Shift Right Octet
|0 0 0 1 1 0| VD128 | VA128 | VB128 |A|1 1 1 1|a|1|VDh|VBh|
vsro128 vr(VD128), vr(VA128), vr(VB128)
=================================================================
vsrw128 Vector128 Shift Right Word
|0 0 0 1 1 0| VD128 | VA128 | VB128 |A|0 1 1 1|a|1|VDh|VBh|
vsrw128 vr(VD128), vr(VA128), vr(VB128)
=================================================================
vsubfp128 Vector128 Subtract Floating Point
|0 0 0 1 0 1| VD128 | VA128 | VB128 |A|0 0 0 1|a|1|VDh|VBh|
vsubfp128 vr(VD128), vr(VA128), vr(VB128)
=================================================================
vupkd3d128 Vector128 Unpack D3Dtype
|0 0 0 1 1 0| VD128 | UIMM | VB128 |1 1 1 1 1 1 1|VDh|VBh|
vupkd3d128 vr(VD128), vr(VB128), UIMM
=================================================================
vupkhsb128 Vector128 Unpack
High Signed Byte
|0 0 0 1 1 0| VD128 |0 0 0 0 0| VB128 |0 1 1 1 0 0 0|VDh|VBh|
vupkhsb128 vr(VD128), vr(VB128)
=================================================================
vupklsb128 Vector128 Unpack
Low Signed Byte
|0 0 0 1 1 0| VD128 |0 0 0 0 0| VB128 |0 1 1 1 1 0 0|VDh|VBh|
vupkhsb128 vr(VD128), vr(VB128)
=================================================================
vxor128 Vector128 Logical XOR
|0 0 0 1 0 1| VD128 | VA128 | VB128 |A|1 1 0 0|a|1|VDh|VBh|
vxor128 vr(VD128), vr(VA128), vr(VB128)
================================================
FILE: docs/style_guide.md
================================================
# 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)
is followed and cpplint is the source of truth. When in doubt, defer to what
code in the project already does.
Base rules:
* 80 column line length max
* LF (Unix-style) line endings
* 2-space soft tabs, no TABs!
* [Google Style Guide](https://google.github.io/styleguide/cppguide.html) for naming/casing/etc
* Sort includes according to the [style guide rules](https://google.github.io/styleguide/cppguide.html#Names_and_Order_of_Includes)
* Comments are properly punctuated (that means capitalization and periods, etc)
* TODO's must be attributed like `// TODO(yourgithubname): foo.`
Code that really breaks from the formatting rules will not be accepted, as then
no one else can use clang-format on the code without also touching all your
lines.
### Why?
To quote the [Google Style Guide](https://google.github.io/styleguide/cppguide.html):
```
One way in which we keep the code base manageable is by enforcing consistency.
It is very important that any programmer be able to look at another's code and
quickly understand it. Maintaining a uniform style and following conventions
means that we can more easily use "pattern-matching" to infer what various
symbols are and what invariants are true about them. Creating common, required
idioms and patterns makes code much easier to understand. In some cases there
might be good arguments for changing certain style rules, but we nonetheless
keep things as they are in order to preserve consistency.
```
## Buildbot Verification
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).
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
version.
## Tools
### clang-format
clang-format with the Google style is used to format all files. I recommend
installing/wiring it up to your editor of choice so that you don't even have to
think about tabs and wrapping and such.
#### Command Line
To use the `xb format` auto-formatter, you need to have a `clang-format` on your
PATH. If you're on Windows you can do this by installing an LLVM binary package
from [the LLVM downloads page](https://llvm.org/releases/download.html). If you
install it to the default location the `xb format` command will find it
automatically even if you don't choose to put all of LLVM onto your PATH.
#### Visual Studio
Grab the official [experimental Visual Studio plugin](https://llvm.org/builds/).
To switch to the Google style go Tools -> Options -> LLVM/Clang -> ClangFormat
and set Style to Google. Then use ctrl-r/ctrl-f to trigger the formatting.
Unfortunately it only does the cursor by default, so you'll have to select the
whole doc and invoke it to get it all done.
If you have a better option, let me know!
#### Xcode
Install [Alcatraz](https://github.com/alcatraz/Alcatraz) to get the [ClangFormat](https://github.com/travisjeffery/ClangFormat-Xcode)
package. Set it to use the Google style and format on save. Never think about
tabs or linefeeds or whatever again.
### cpplint
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.
================================================
FILE: premake5.lua
================================================
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")
location(build_root)
targetdir(build_bin)
objdir(build_obj)
-- Define an ARCH variable
-- Only use this to enable architecture-specific functionality.
if os.istarget("linux") then
ARCH = os.outputof("uname -p")
else
ARCH = "unknown"
end
includedirs({
".",
"src",
"third_party",
})
defines({
"_UNICODE",
"UNICODE",
})
cppdialect("C++17")
exceptionhandling("On")
rtti("On")
symbols("On")
-- TODO(DrChat): Find a way to disable this on other architectures.
if ARCH ~= "ppc64" then
filter("architecture:x86_64")
vectorextensions("AVX")
filter({})
end
characterset("Unicode")
flags({
"FatalWarnings", -- Treat warnings as errors.
})
filter("kind:StaticLib")
defines({
"_LIB",
})
filter("configurations:Checked")
runtime("Debug")
optimize("Off")
defines({
"DEBUG",
})
filter({"configurations:Checked", "platforms:Windows"})
buildoptions({
"/RTCsu", -- Full Run-Time Checks.
})
filter({"configurations:Checked", "platforms:Linux"})
defines({
"_GLIBCXX_DEBUG", -- libstdc++ debug mode
})
filter("configurations:Debug")
runtime("Release")
optimize("Off")
defines({
"DEBUG",
"_NO_DEBUG_HEAP=1",
})
filter({"configurations:Debug", "platforms:Linux"})
defines({
"_GLIBCXX_DEBUG", -- make dbg symbols work on some distros
})
filter("configurations:Release")
runtime("Release")
defines({
"NDEBUG",
"_NO_DEBUG_HEAP=1",
})
optimize("Speed")
inlining("Auto")
flags({
"LinkTimeOptimization",
})
-- Not using floatingpoint("Fast") - NaN checks are used in some places
-- (though rarely), overall preferable to avoid any functional differences
-- between debug and release builds, and to have calculations involved in GPU
-- (especially anything that may affect vertex position invariance) and CPU
-- (such as constant propagation) emulation as predictable as possible,
-- including handling of specials since games make assumptions about them.
filter("platforms:Linux")
system("linux")
toolset("clang")
buildoptions({
-- "-mlzcnt", -- (don't) Assume lzcnt is supported.
})
pkg_config.all("gtk+-x11-3.0")
links({
"stdc++fs",
"dl",
"lz4",
"pthread",
"rt",
})
filter({"platforms:Linux", "kind:*App"})
linkgroups("On")
filter({"platforms:Linux", "language:C++", "toolset:gcc"})
disablewarnings({
"unused-result"
})
filter({"platforms:Linux", "toolset:gcc"})
if ARCH == "ppc64" then
buildoptions({
"-m32",
"-mpowerpc64"
})
linkoptions({
"-m32",
"-mpowerpc64"
})
end
filter({"platforms:Linux", "language:C++", "toolset:clang"})
disablewarnings({
"deprecated-register"
})
filter({"platforms:Linux", "language:C++", "toolset:clang", "files:*.cc or *.cpp"})
buildoptions({
"-stdlib=libstdc++",
})
filter("platforms:Android-*")
system("android")
systemversion("24")
cppstl("c++")
staticruntime("On")
-- Hidden visibility is needed to prevent dynamic relocations in FFmpeg
-- AArch64 Neon libavcodec assembly with PIC (accesses extern lookup tables
-- using `adrp` and `add`, without the Global Object Table, expecting that all
-- FFmpeg symbols that aren't a part of the FFmpeg API are hidden by FFmpeg's
-- original build system) by resolving those relocations at link time instead.
visibility("Hidden")
links({
"android",
"dl",
"log",
})
filter("platforms:Windows")
system("windows")
toolset("msc")
buildoptions({
"/utf-8", -- 'build correctly on systems with non-Latin codepages'.
-- Mark warnings as severe
"/w14839", -- non-standard use of class 'type' as an argument to a variadic function
"/w14840", -- non-portable use of class 'type' as an argument to a variadic function
-- Disable warnings
"/wd4100", -- Unreferenced parameters are ok.
"/wd4201", -- Nameless struct/unions are ok.
"/wd4512", -- 'assignment operator was implicitly defined as deleted'.
"/wd4127", -- 'conditional expression is constant'.
"/wd4324", -- 'structure was padded due to alignment specifier'.
"/wd4189", -- 'local variable is initialized but not referenced'.
})
flags({
"MultiProcessorCompile", -- Multiprocessor compilation.
"NoMinimalRebuild", -- Required for /MP above.
})
defines({
"_CRT_NONSTDC_NO_DEPRECATE",
"_CRT_SECURE_NO_WARNINGS",
"WIN32",
"_WIN64=1",
"_AMD64=1",
})
linkoptions({
"/ignore:4006", -- Ignores complaints about empty obj files.
"/ignore:4221",
})
links({
"ntdll",
"wsock32",
"ws2_32",
"xinput",
"comctl32",
"shcore",
"shlwapi",
"dxguid",
"bcrypt",
})
-- Embed the manifest for things like dependencies and DPI awareness.
filter({"platforms:Windows", "kind:ConsoleApp or WindowedApp"})
files({
"src/xenia/base/app_win32.manifest"
})
-- Create scratch/ path
if not os.isdir("scratch") then
os.mkdir("scratch")
end
workspace("xenia")
uuid("931ef4b0-6170-4f7a-aaf2-0fece7632747")
startproject("xenia-app")
if os.istarget("android") then
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.
-- 10.0.19041.0: D3D12_HEAP_FLAG_CREATE_NOT_ZEROED.
-- 10.0.22000.0: DWMWA_WINDOW_CORNER_PREFERENCE.
filter("action:vs2017")
systemversion("10.0.22000.0")
filter("action:vs2019")
systemversion("10.0")
filter({})
end
end
configurations({"Checked", "Debug", "Release"})
include("third_party/aes_128.lua")
include("third_party/capstone.lua")
include("third_party/dxbc.lua")
include("third_party/discord-rpc.lua")
include("third_party/cxxopts.lua")
include("third_party/cpptoml.lua")
include("third_party/FFmpeg/premake5.lua")
include("third_party/fmt.lua")
include("third_party/glslang-spirv.lua")
include("third_party/imgui.lua")
include("third_party/mspack.lua")
include("third_party/snappy.lua")
include("third_party/xxhash.lua")
if not os.istarget("android") then
-- SDL2 requires sdl2-config, and as of November 2020 isn't high-quality on
-- Android yet, most importantly in game controllers - the keycode and axis
-- enums are being ruined during conversion to SDL2 enums resulting in only
-- one controller (Nvidia Shield) being supported, digital triggers are also
-- not supported; lifecycle management (especially surface loss) is also
-- complicated.
include("third_party/SDL2.lua")
end
-- Disable treating warnings as fatal errors for all third party projects, as
-- well as other things relevant only to Xenia itself.
for _, prj in ipairs(premake.api.scope.current.solution.projects) do
project(prj.name)
removefiles({
"src/xenia/base/app_win32.manifest"
})
removeflags({
"FatalWarnings",
})
end
include("src/xenia")
include("src/xenia/app")
include("src/xenia/app/discord")
include("src/xenia/apu")
include("src/xenia/apu/nop")
include("src/xenia/base")
include("src/xenia/cpu")
include("src/xenia/cpu/backend/x64")
include("src/xenia/debug/ui")
include("src/xenia/gpu")
include("src/xenia/gpu/null")
include("src/xenia/gpu/vulkan")
include("src/xenia/hid")
include("src/xenia/hid/nop")
include("src/xenia/kernel")
include("src/xenia/ui")
include("src/xenia/ui/vulkan")
include("src/xenia/vfs")
if not os.istarget("android") then
include("src/xenia/apu/sdl")
include("src/xenia/helper/sdl")
include("src/xenia/hid/sdl")
end
if os.istarget("windows") then
include("src/xenia/apu/xaudio2")
include("src/xenia/gpu/d3d12")
include("src/xenia/hid/winkey")
include("src/xenia/hid/xinput")
include("src/xenia/ui/d3d12")
end
================================================
FILE: src/xenia/app/discord/discord_presence.cc
================================================
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2020 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "discord_presence.h"
#include "third_party/discord-rpc/include/discord_rpc.h"
#include "xenia/base/string.h"
namespace xe {
namespace discord {
void HandleDiscordReady(const DiscordUser* request) {}
void HandleDiscordError(int errorCode, const char* message) {}
void HandleDiscordJoinGame(const char* joinSecret) {}
void HandleDiscordJoinRequest(const DiscordUser* request) {}
void HandleDiscordSpectateGame(const char* spectateSecret) {}
void DiscordPresence::Initialize() {
DiscordEventHandlers handlers = {};
handlers.ready = &HandleDiscordReady;
handlers.errored = &HandleDiscordError;
handlers.joinGame = &HandleDiscordJoinGame;
handlers.joinRequest = &HandleDiscordJoinRequest;
handlers.spectateGame = &HandleDiscordSpectateGame;
Discord_Initialize("606840046649081857", &handlers, 0, "");
}
void DiscordPresence::NotPlaying() {
DiscordRichPresence discordPresence = {};
discordPresence.state = "Idle";
discordPresence.details = "Standby";
discordPresence.largeImageKey = "app";
discordPresence.instance = 1;
Discord_UpdatePresence(&discordPresence);
}
void DiscordPresence::PlayingTitle(const std::string_view game_title) {
auto details = std::string(game_title);
DiscordRichPresence discordPresence = {};
discordPresence.state = "In Game";
discordPresence.details = details.c_str();
// TODO(gibbed): we don't have state icons yet.
// discordPresence.smallImageKey = "app";
// discordPresence.largeImageKey = "state_ingame";
discordPresence.largeImageKey = "app";
discordPresence.instance = 1;
Discord_UpdatePresence(&discordPresence);
}
void DiscordPresence::Shutdown() { Discord_Shutdown(); }
} // namespace discord
} // namespace xe
================================================
FILE: src/xenia/app/discord/discord_presence.h
================================================
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2020 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_DISCORD_DISCORD_PRESENCE_H_
#define XENIA_DISCORD_DISCORD_PRESENCE_H_
#include
namespace xe {
namespace discord {
class DiscordPresence {
public:
static void Initialize();
static void NotPlaying();
static void PlayingTitle(const std::string_view game_title);
static void Shutdown();
};
} // namespace discord
} // namespace xe
#endif // XENIA_DISCORD_DISCORD_PRESENCE_H_
================================================
FILE: src/xenia/app/discord/premake5.lua
================================================
project_root = "../../../.."
include(project_root.."/tools/build")
group("src")
project("xenia-app-discord")
uuid("d14c0885-22d2-40de-ab28-7b234ef2b949")
kind("StaticLib")
language("C++")
links({
"discord-rpc"
})
defines({
})
includedirs({
project_root.."/third_party/discord-rpc/src"
})
files({
"discord_presence.cc",
"discord_presence.h"
})
================================================
FILE: src/xenia/app/emulator_window.cc
================================================
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2022 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/app/emulator_window.h"
#include
#include
#include
#include
#include
#include
#include "third_party/fmt/include/fmt/format.h"
#include "third_party/imgui/imgui.h"
#include "xenia/base/assert.h"
#include "xenia/base/clock.h"
#include "xenia/base/cvar.h"
#include "xenia/base/debugging.h"
#include "xenia/base/logging.h"
#include "xenia/base/platform.h"
#include "xenia/base/profiling.h"
#include "xenia/base/system.h"
#include "xenia/base/threading.h"
#include "xenia/cpu/processor.h"
#include "xenia/emulator.h"
#include "xenia/gpu/command_processor.h"
#include "xenia/gpu/graphics_system.h"
#include "xenia/ui/file_picker.h"
#include "xenia/ui/graphics_provider.h"
#include "xenia/ui/imgui_dialog.h"
#include "xenia/ui/imgui_drawer.h"
#include "xenia/ui/immediate_drawer.h"
#include "xenia/ui/presenter.h"
#include "xenia/ui/ui_event.h"
#include "xenia/ui/virtual_key.h"
// Autogenerated by `xb premake`.
#include "build/version.h"
DECLARE_bool(debug);
DEFINE_bool(fullscreen, false, "Whether to launch the emulator in fullscreen.",
"Display");
DEFINE_string(
postprocess_antialiasing, "",
"Post-processing anti-aliasing effect to apply to the image output of the "
"game.\n"
"Using post-process anti-aliasing is heavily recommended when AMD "
"FidelityFX Contrast Adaptive Sharpening or Super Resolution 1.0 is "
"active.\n"
"Use: [none, fxaa, fxaa_extreme]\n"
" none (or any value not listed here):\n"
" Don't alter the original image.\n"
" fxaa:\n"
" NVIDIA Fast Approximate Anti-Aliasing 3.11, normal quality preset (12)."
"\n"
" fxaa_extreme:\n"
" NVIDIA Fast Approximate Anti-Aliasing 3.11, extreme quality preset "
"(39).",
"Display");
DEFINE_string(
postprocess_scaling_and_sharpening, "",
"Post-processing effect to use for resampling and/or sharpening of the "
"final display output.\n"
"Use: [bilinear, cas, fsr]\n"
" bilinear (or any value not listed here):\n"
" Original image at 1:1, simple bilinear stretching for resampling.\n"
" cas:\n"
" Use AMD FidelityFX Contrast Adaptive Sharpening (CAS) for sharpening "
"at scaling factors of up to 2x2, with additional bilinear stretching for "
"larger factors.\n"
" fsr:\n"
" Use AMD FidelityFX Super Resolution 1.0 (FSR) for highest-quality "
"upscaling, or AMD FidelityFX Contrast Adaptive Sharpening for sharpening "
"while not scaling or downsampling.\n"
" For scaling by factors of more than 2x2, multiple FSR passes are done.",
"Display");
DEFINE_double(
postprocess_ffx_cas_additional_sharpness,
xe::ui::Presenter::GuestOutputPaintConfig::kCasAdditionalSharpnessDefault,
"Additional sharpness for AMD FidelityFX Contrast Adaptive Sharpening "
"(CAS), from 0 to 1.\n"
"Higher is sharper.",
"Display");
DEFINE_uint32(
postprocess_ffx_fsr_max_upsampling_passes,
xe::ui::Presenter::GuestOutputPaintConfig::kFsrMaxUpscalingPassesMax,
"Maximum number of upsampling passes performed in AMD FidelityFX Super "
"Resolution 1.0 (FSR) before falling back to bilinear stretching after the "
"final pass.\n"
"Each pass upscales only to up to 2x2 the previous size. If the game "
"outputs a 1280x720 image, 1 pass will upscale it to up to 2560x1440 "
"(below 4K), after 2 passes it will be upscaled to a maximum of 5120x2880 "
"(including 3840x2160 for 4K), and so on.\n"
"This variable has no effect if the display resolution isn't very high, "
"but may be reduced on resolutions like 4K or 8K in case the performance "
"impact of multiple FSR upsampling passes is too high, or if softer edges "
"are desired.\n"
"The default value is the maximum internally supported by Xenia.",
"Display");
DEFINE_double(
postprocess_ffx_fsr_sharpness_reduction,
xe::ui::Presenter::GuestOutputPaintConfig::kFsrSharpnessReductionDefault,
"Sharpness reduction for AMD FidelityFX Super Resolution 1.0 (FSR), in "
"stops.\n"
"Lower is sharper.",
"Display");
// Dithering to 8bpc is enabled by default since the effect is minor, only
// effects what can't be shown normally by host displays, and nothing is changed
// by it for 8bpc source without resampling.
DEFINE_bool(
postprocess_dither, true,
"Dither the final image output from the internal precision to 8 bits per "
"channel so gradients are smoother.\n"
"On a 10bpc display, the lower 2 bits will still be kept, but noise will "
"be added to them - disabling may be recommended for 10bpc, but it "
"depends on the 10bpc displaying capabilities of the actual display used.",
"Display");
namespace xe {
namespace app {
using xe::ui::FileDropEvent;
using xe::ui::KeyEvent;
using xe::ui::MenuItem;
using xe::ui::UIEvent;
const std::string kBaseTitle = "Xenia";
EmulatorWindow::EmulatorWindow(Emulator* emulator,
ui::WindowedAppContext& app_context)
: emulator_(emulator),
app_context_(app_context),
window_listener_(*this),
window_(ui::Window::Create(app_context, kBaseTitle, 1280, 720)),
imgui_drawer_(
std::make_unique(window_.get(), kZOrderImGui)),
display_config_game_config_load_callback_(
new DisplayConfigGameConfigLoadCallback(*emulator, *this)) {
base_title_ = kBaseTitle +
#ifdef DEBUG
#if _NO_DEBUG_HEAP == 1
" DEBUG"
#else
" CHECKED"
#endif
#endif
" ("
#ifdef XE_BUILD_IS_PR
"PR#" XE_BUILD_PR_NUMBER " " XE_BUILD_PR_REPO
" " XE_BUILD_PR_BRANCH "@" XE_BUILD_PR_COMMIT_SHORT " against "
#endif
XE_BUILD_BRANCH "@" XE_BUILD_COMMIT_SHORT " on " XE_BUILD_DATE
")";
}
std::unique_ptr EmulatorWindow::Create(
Emulator* emulator, ui::WindowedAppContext& app_context) {
assert_true(app_context.IsInUIThread());
std::unique_ptr emulator_window(
new EmulatorWindow(emulator, app_context));
if (!emulator_window->Initialize()) {
return nullptr;
}
return emulator_window;
}
EmulatorWindow::~EmulatorWindow() {
// Notify the ImGui drawer that the immediate drawer is being destroyed.
ShutdownGraphicsSystemPresenterPainting();
}
ui::Presenter* EmulatorWindow::GetGraphicsSystemPresenter() const {
gpu::GraphicsSystem* graphics_system = emulator_->graphics_system();
return graphics_system ? graphics_system->presenter() : nullptr;
}
void EmulatorWindow::SetupGraphicsSystemPresenterPainting() {
ShutdownGraphicsSystemPresenterPainting();
if (!window_) {
return;
}
ui::Presenter* presenter = GetGraphicsSystemPresenter();
if (!presenter) {
return;
}
ApplyDisplayConfigForCvars();
window_->SetPresenter(presenter);
immediate_drawer_ =
emulator_->graphics_system()->provider()->CreateImmediateDrawer();
if (immediate_drawer_) {
immediate_drawer_->SetPresenter(presenter);
imgui_drawer_->SetPresenterAndImmediateDrawer(presenter,
immediate_drawer_.get());
Profiler::SetUserIO(kZOrderProfiler, window_.get(), presenter,
immediate_drawer_.get());
}
}
void EmulatorWindow::ShutdownGraphicsSystemPresenterPainting() {
Profiler::SetUserIO(kZOrderProfiler, window_.get(), nullptr, nullptr);
imgui_drawer_->SetPresenterAndImmediateDrawer(nullptr, nullptr);
immediate_drawer_.reset();
if (window_) {
window_->SetPresenter(nullptr);
}
}
void EmulatorWindow::OnEmulatorInitialized() {
emulator_initialized_ = true;
window_->SetMainMenuEnabled(true);
// When the user can see that the emulator isn't initializing anymore (the
// menu isn't disabled), enter fullscreen if requested.
if (cvars::fullscreen) {
SetFullscreen(true);
}
}
void EmulatorWindow::EmulatorWindowListener::OnClosing(ui::UIEvent& e) {
emulator_window_.app_context_.QuitFromUIThread();
}
void EmulatorWindow::EmulatorWindowListener::OnFileDrop(ui::FileDropEvent& e) {
emulator_window_.FileDrop(e.filename());
}
void EmulatorWindow::EmulatorWindowListener::OnKeyDown(ui::KeyEvent& e) {
emulator_window_.OnKeyDown(e);
}
void EmulatorWindow::DisplayConfigGameConfigLoadCallback::PostGameConfigLoad() {
emulator_window_.ApplyDisplayConfigForCvars();
}
void EmulatorWindow::DisplayConfigDialog::OnDraw(ImGuiIO& io) {
gpu::GraphicsSystem* graphics_system =
emulator_window_.emulator_->graphics_system();
if (!graphics_system) {
return;
}
// In the top-left corner so it's close to the menu bar from where it was
// opened.
// Origin Y coordinate 20 was taken from the Dear ImGui demo.
ImGui::SetNextWindowPos(ImVec2(20, 20), ImGuiCond_FirstUseEver);
ImGui::SetNextWindowSize(ImVec2(20, 20), ImGuiCond_FirstUseEver);
// Alpha from Dear ImGui tooltips (0.35 from the overlay provides too low
// visibility). Translucent so some effect of the changes can still be seen
// through it.
ImGui::SetNextWindowBgAlpha(0.6f);
bool dialog_open = true;
if (!ImGui::Begin("Post-processing", &dialog_open,
ImGuiWindowFlags_NoCollapse |
ImGuiWindowFlags_AlwaysAutoResize |
ImGuiWindowFlags_HorizontalScrollbar)) {
ImGui::End();
return;
}
// Even if the close button has been pressed, still paint everything not to
// have one frame with an empty window.
// Prevent user confusion which has been reported multiple times.
ImGui::TextUnformatted("All effects can be used on GPUs of any brand.");
ImGui::Spacing();
gpu::CommandProcessor* command_processor =
graphics_system->command_processor();
if (command_processor) {
if (ImGui::TreeNodeEx(
"Anti-aliasing",
ImGuiTreeNodeFlags_Framed | ImGuiTreeNodeFlags_DefaultOpen)) {
gpu::CommandProcessor::SwapPostEffect current_swap_post_effect =
command_processor->GetDesiredSwapPostEffect();
int new_swap_post_effect_index = int(current_swap_post_effect);
ImGui::RadioButton("None", &new_swap_post_effect_index,
int(gpu::CommandProcessor::SwapPostEffect::kNone));
ImGui::RadioButton(
"NVIDIA Fast Approximate Anti-Aliasing 3.11 (FXAA), normal quality",
&new_swap_post_effect_index,
int(gpu::CommandProcessor::SwapPostEffect::kFxaa));
ImGui::RadioButton(
"NVIDIA Fast Approximate Anti-Aliasing 3.11 (FXAA), extreme quality",
&new_swap_post_effect_index,
int(gpu::CommandProcessor::SwapPostEffect::kFxaaExtreme));
gpu::CommandProcessor::SwapPostEffect new_swap_post_effect =
gpu::CommandProcessor::SwapPostEffect(new_swap_post_effect_index);
if (current_swap_post_effect != new_swap_post_effect) {
command_processor->SetDesiredSwapPostEffect(new_swap_post_effect);
}
// Override the values in the cvars to save them to the config at exit if
// the user has set them to anything new.
if (GetSwapPostEffectForCvarValue(cvars::postprocess_antialiasing) !=
new_swap_post_effect) {
OVERRIDE_string(postprocess_antialiasing,
GetCvarValueForSwapPostEffect(new_swap_post_effect));
}
ImGui::TreePop();
}
}
ui::Presenter* presenter = graphics_system->presenter();
if (presenter) {
const ui::Presenter::GuestOutputPaintConfig& current_presenter_config =
presenter->GetGuestOutputPaintConfigFromUIThread();
ui::Presenter::GuestOutputPaintConfig new_presenter_config =
current_presenter_config;
if (ImGui::TreeNodeEx(
"Resampling and sharpening",
ImGuiTreeNodeFlags_Framed | ImGuiTreeNodeFlags_DefaultOpen)) {
// Filtering effect.
int new_effect_index = int(new_presenter_config.GetEffect());
ImGui::RadioButton(
"None / bilinear", &new_effect_index,
int(ui::Presenter::GuestOutputPaintConfig::Effect::kBilinear));
ImGui::RadioButton(
"AMD FidelityFX Contrast Adaptive Sharpening (CAS)",
&new_effect_index,
int(ui::Presenter::GuestOutputPaintConfig::Effect::kCas));
ImGui::RadioButton(
"AMD FidelityFX Super Resolution 1.0 (FSR)", &new_effect_index,
int(ui::Presenter::GuestOutputPaintConfig::Effect::kFsr));
new_presenter_config.SetEffect(
ui::Presenter::GuestOutputPaintConfig::Effect(new_effect_index));
// effect_description must be one complete, but short enough, sentence per
// line, as TextWrapped doesn't work correctly in auto-resizing windows
// (in the initial frames, the window becomes extremely tall, and widgets
// added after the wrapped text have no effect on the width of the text).
const char* effect_description = nullptr;
switch (new_presenter_config.GetEffect()) {
case ui::Presenter::GuestOutputPaintConfig::Effect::kBilinear:
effect_description =
"Simple bilinear filtering is done if resampling is needed.\n"
"Otherwise, only anti-aliasing is done if enabled, or displaying "
"as is.";
break;
case ui::Presenter::GuestOutputPaintConfig::Effect::kCas:
effect_description =
"Sharpening and resampling to up to 2x2 to improve the fidelity "
"of details.\n"
"For scaling by more than 2x2, bilinear stretching is done "
"afterwards.";
break;
case ui::Presenter::GuestOutputPaintConfig::Effect::kFsr:
effect_description =
"High-quality edge-preserving upscaling to arbitrary target "
"resolutions.\n"
"For scaling by more than 2x2, multiple upsampling passes are "
"done.\n"
"If not upscaling, Contrast Adaptive Sharpening (CAS) is used "
"instead.";
break;
}
if (effect_description) {
ImGui::TextUnformatted(effect_description);
}
if (new_presenter_config.GetEffect() ==
ui::Presenter::GuestOutputPaintConfig::Effect::kCas ||
new_presenter_config.GetEffect() ==
ui::Presenter::GuestOutputPaintConfig::Effect::kFsr) {
if (effect_description) {
ImGui::Spacing();
}
ImGui::TextUnformatted(
"FXAA is highly recommended when using CAS or FSR.");
ImGui::Spacing();
// 2 decimal places is more or less enough precision for the sharpness
// given the minor visual effect of small changes, the width of the
// slider, and readability convenience (2 decimal places is like an
// integer percentage). However, because Dear ImGui parses the string
// representation of the number and snaps the value to it internally,
// 2 decimal places actually offer less precision than the slider itself
// does. This is especially prominent in the low range of the non-linear
// FSR sharpness reduction slider. 3 decimal places are optimal in this
// case.
if (new_presenter_config.GetEffect() ==
ui::Presenter::GuestOutputPaintConfig::Effect::kFsr) {
float fsr_sharpness_reduction =
new_presenter_config.GetFsrSharpnessReduction();
ImGui::TextUnformatted(
"FSR sharpness reduction when upscaling (lower is sharper):");
const auto label =
fmt::format("{:.3f} stops", fsr_sharpness_reduction);
// Power 2.0 scaling as the reduction is in stops, used in exp2.
fsr_sharpness_reduction = sqrt(2.f * fsr_sharpness_reduction);
ImGui::SliderFloat(
"##FSRSharpnessReduction", &fsr_sharpness_reduction,
ui::Presenter::GuestOutputPaintConfig::kFsrSharpnessReductionMin,
ui::Presenter::GuestOutputPaintConfig::kFsrSharpnessReductionMax,
label.c_str(), ImGuiSliderFlags_NoInput);
fsr_sharpness_reduction =
.5f * fsr_sharpness_reduction * fsr_sharpness_reduction;
ImGui::SameLine();
if (ImGui::Button("Reset##ResetFSRSharpnessReduction")) {
fsr_sharpness_reduction = ui::Presenter::GuestOutputPaintConfig ::
kFsrSharpnessReductionDefault;
}
new_presenter_config.SetFsrSharpnessReduction(
fsr_sharpness_reduction);
}
float cas_additional_sharpness =
new_presenter_config.GetCasAdditionalSharpness();
ImGui::TextUnformatted(
new_presenter_config.GetEffect() ==
ui::Presenter::GuestOutputPaintConfig::Effect::kFsr
? "CAS additional sharpness when not upscaling (higher is "
"sharper):"
: "CAS additional sharpness (higher is sharper):");
ImGui::SliderFloat(
"##CASAdditionalSharpness", &cas_additional_sharpness,
ui::Presenter::GuestOutputPaintConfig::kCasAdditionalSharpnessMin,
ui::Presenter::GuestOutputPaintConfig::kCasAdditionalSharpnessMax,
"%.3f");
ImGui::SameLine();
if (ImGui::Button("Reset##ResetCASAdditionalSharpness")) {
cas_additional_sharpness = ui::Presenter::GuestOutputPaintConfig ::
kCasAdditionalSharpnessDefault;
}
new_presenter_config.SetCasAdditionalSharpness(
cas_additional_sharpness);
// There's no need to expose the setting for the maximum number of FSR
// EASU passes as it's largely meaningless if the user doesn't have a
// very high-resolution monitor compared to the original image size as
// most of the values of the slider will have no effect, and that's just
// very fine-grained performance control for a fixed-overhead pass only
// for huge screen resolutions.
}
ImGui::TreePop();
}
if (ImGui::TreeNodeEx("Dithering", ImGuiTreeNodeFlags_Framed |
ImGuiTreeNodeFlags_DefaultOpen)) {
bool dither = current_presenter_config.GetDither();
ImGui::Checkbox(
"Dither the final output to 8bpc to make gradients smoother",
&dither);
new_presenter_config.SetDither(dither);
ImGui::TreePop();
}
presenter->SetGuestOutputPaintConfigFromUIThread(new_presenter_config);
// Override the values in the cvars to save them to the config at exit if
// the user has set them to anything new.
ui::Presenter::GuestOutputPaintConfig cvars_presenter_config =
GetGuestOutputPaintConfigForCvars();
if (cvars_presenter_config.GetEffect() !=
new_presenter_config.GetEffect()) {
OVERRIDE_string(postprocess_scaling_and_sharpening,
GetCvarValueForGuestOutputPaintEffect(
new_presenter_config.GetEffect()));
}
if (cvars_presenter_config.GetCasAdditionalSharpness() !=
new_presenter_config.GetCasAdditionalSharpness()) {
OVERRIDE_double(postprocess_ffx_cas_additional_sharpness,
new_presenter_config.GetCasAdditionalSharpness());
}
if (cvars_presenter_config.GetFsrSharpnessReduction() !=
new_presenter_config.GetFsrSharpnessReduction()) {
OVERRIDE_double(postprocess_ffx_fsr_sharpness_reduction,
new_presenter_config.GetFsrSharpnessReduction());
}
if (cvars_presenter_config.GetDither() !=
new_presenter_config.GetDither()) {
OVERRIDE_bool(postprocess_dither, new_presenter_config.GetDither());
}
}
ImGui::End();
if (!dialog_open) {
emulator_window_.ToggleDisplayConfigDialog();
// `this` might have been destroyed by ToggleDisplayConfigDialog.
return;
}
}
bool EmulatorWindow::Initialize() {
window_->AddListener(&window_listener_);
window_->AddInputListener(&window_listener_, kZOrderEmulatorWindowInput);
// Main menu.
// FIXME: This code is really messy.
auto main_menu = MenuItem::Create(MenuItem::Type::kNormal);
auto file_menu = MenuItem::Create(MenuItem::Type::kPopup, "&File");
{
file_menu->AddChild(
MenuItem::Create(MenuItem::Type::kString, "&Open...", "Ctrl+O",
std::bind(&EmulatorWindow::FileOpen, this)));
#ifdef DEBUG
file_menu->AddChild(
MenuItem::Create(MenuItem::Type::kString, "Close",
std::bind(&EmulatorWindow::FileClose, this)));
#endif // #ifdef DEBUG
file_menu->AddChild(MenuItem::Create(MenuItem::Type::kSeparator));
file_menu->AddChild(MenuItem::Create(
MenuItem::Type::kString, "Show content directory...",
std::bind(&EmulatorWindow::ShowContentDirectory, this)));
file_menu->AddChild(MenuItem::Create(MenuItem::Type::kSeparator));
file_menu->AddChild(
MenuItem::Create(MenuItem::Type::kString, "E&xit", "Alt+F4",
[this]() { window_->RequestClose(); }));
}
main_menu->AddChild(std::move(file_menu));
// CPU menu.
auto cpu_menu = MenuItem::Create(MenuItem::Type::kPopup, "&CPU");
{
cpu_menu->AddChild(MenuItem::Create(
MenuItem::Type::kString, "&Reset Time Scalar", "Numpad *",
std::bind(&EmulatorWindow::CpuTimeScalarReset, this)));
cpu_menu->AddChild(MenuItem::Create(
MenuItem::Type::kString, "Time Scalar /= 2", "Numpad -",
std::bind(&EmulatorWindow::CpuTimeScalarSetHalf, this)));
cpu_menu->AddChild(MenuItem::Create(
MenuItem::Type::kString, "Time Scalar *= 2", "Numpad +",
std::bind(&EmulatorWindow::CpuTimeScalarSetDouble, this)));
}
cpu_menu->AddChild(MenuItem::Create(MenuItem::Type::kSeparator));
{
cpu_menu->AddChild(MenuItem::Create(MenuItem::Type::kString,
"Toggle Profiler &Display", "F3",
[]() { Profiler::ToggleDisplay(); }));
cpu_menu->AddChild(MenuItem::Create(MenuItem::Type::kString,
"&Pause/Resume Profiler", "`",
[]() { Profiler::TogglePause(); }));
}
cpu_menu->AddChild(MenuItem::Create(MenuItem::Type::kSeparator));
{
cpu_menu->AddChild(MenuItem::Create(
MenuItem::Type::kString, "&Break and Show Guest Debugger",
"Pause/Break", std::bind(&EmulatorWindow::CpuBreakIntoDebugger, this)));
cpu_menu->AddChild(MenuItem::Create(
MenuItem::Type::kString, "&Break into Host Debugger",
"Ctrl+Pause/Break",
std::bind(&EmulatorWindow::CpuBreakIntoHostDebugger, this)));
}
main_menu->AddChild(std::move(cpu_menu));
// GPU menu.
auto gpu_menu = MenuItem::Create(MenuItem::Type::kPopup, "&GPU");
{
gpu_menu->AddChild(
MenuItem::Create(MenuItem::Type::kString, "&Trace Frame", "F4",
std::bind(&EmulatorWindow::GpuTraceFrame, this)));
}
gpu_menu->AddChild(MenuItem::Create(MenuItem::Type::kSeparator));
{
gpu_menu->AddChild(
MenuItem::Create(MenuItem::Type::kString, "&Clear Runtime Caches", "F5",
std::bind(&EmulatorWindow::GpuClearCaches, this)));
}
main_menu->AddChild(std::move(gpu_menu));
// Display menu.
auto display_menu = MenuItem::Create(MenuItem::Type::kPopup, "&Display");
{
display_menu->AddChild(MenuItem::Create(
MenuItem::Type::kString, "&Post-processing settings", "F6",
std::bind(&EmulatorWindow::ToggleDisplayConfigDialog, this)));
}
display_menu->AddChild(MenuItem::Create(MenuItem::Type::kSeparator));
{
display_menu->AddChild(
MenuItem::Create(MenuItem::Type::kString, "&Fullscreen", "F11",
std::bind(&EmulatorWindow::ToggleFullscreen, this)));
}
main_menu->AddChild(std::move(display_menu));
// Help menu.
auto help_menu = MenuItem::Create(MenuItem::Type::kPopup, "&Help");
{
help_menu->AddChild(
MenuItem::Create(MenuItem::Type::kString, "FA&Q...", "F1",
std::bind(&EmulatorWindow::ShowFAQ, this)));
help_menu->AddChild(MenuItem::Create(MenuItem::Type::kSeparator));
help_menu->AddChild(
MenuItem::Create(MenuItem::Type::kString, "Game &compatibility...",
std::bind(&EmulatorWindow::ShowCompatibility, this)));
help_menu->AddChild(MenuItem::Create(MenuItem::Type::kSeparator));
help_menu->AddChild(MenuItem::Create(
MenuItem::Type::kString, "Build commit on GitHub...", "F2",
std::bind(&EmulatorWindow::ShowBuildCommit, this)));
help_menu->AddChild(MenuItem::Create(
MenuItem::Type::kString, "Recent changes on GitHub...", [this]() {
LaunchWebBrowser(
"https://github.com/xenia-project/xenia/compare/" XE_BUILD_COMMIT
"..." XE_BUILD_BRANCH);
}));
help_menu->AddChild(MenuItem::Create(MenuItem::Type::kSeparator));
help_menu->AddChild(MenuItem::Create(
MenuItem::Type::kString, "&About...",
[this]() { LaunchWebBrowser("https://xenia.jp/about/"); }));
}
main_menu->AddChild(std::move(help_menu));
window_->SetMainMenu(std::move(main_menu));
window_->SetMainMenuEnabled(false);
UpdateTitle();
if (!window_->Open()) {
XELOGE("Failed to open the platform window");
return false;
}
Profiler::SetUserIO(kZOrderProfiler, window_.get(), nullptr, nullptr);
return true;
}
const char* EmulatorWindow::GetCvarValueForSwapPostEffect(
gpu::CommandProcessor::SwapPostEffect effect) {
switch (effect) {
case gpu::CommandProcessor::SwapPostEffect::kFxaa:
return "fxaa";
case gpu::CommandProcessor::SwapPostEffect::kFxaaExtreme:
return "fxaa_extreme";
default:
return "";
}
}
gpu::CommandProcessor::SwapPostEffect
EmulatorWindow::GetSwapPostEffectForCvarValue(const std::string& cvar_value) {
if (cvar_value == GetCvarValueForSwapPostEffect(
gpu::CommandProcessor::SwapPostEffect::kFxaa)) {
return gpu::CommandProcessor::SwapPostEffect::kFxaa;
}
if (cvar_value == GetCvarValueForSwapPostEffect(
gpu::CommandProcessor::SwapPostEffect::kFxaaExtreme)) {
return gpu::CommandProcessor::SwapPostEffect::kFxaaExtreme;
}
return gpu::CommandProcessor::SwapPostEffect::kNone;
}
const char* EmulatorWindow::GetCvarValueForGuestOutputPaintEffect(
ui::Presenter::GuestOutputPaintConfig::Effect effect) {
switch (effect) {
case ui::Presenter::GuestOutputPaintConfig::Effect::kCas:
return "cas";
case ui::Presenter::GuestOutputPaintConfig::Effect::kFsr:
return "fsr";
default:
return "";
}
}
ui::Presenter::GuestOutputPaintConfig::Effect
EmulatorWindow::GetGuestOutputPaintEffectForCvarValue(
const std::string& cvar_value) {
if (cvar_value == GetCvarValueForGuestOutputPaintEffect(
ui::Presenter::GuestOutputPaintConfig::Effect::kCas)) {
return ui::Presenter::GuestOutputPaintConfig::Effect::kCas;
}
if (cvar_value == GetCvarValueForGuestOutputPaintEffect(
ui::Presenter::GuestOutputPaintConfig::Effect::kFsr)) {
return ui::Presenter::GuestOutputPaintConfig::Effect::kFsr;
}
return ui::Presenter::GuestOutputPaintConfig::Effect::kBilinear;
}
ui::Presenter::GuestOutputPaintConfig
EmulatorWindow::GetGuestOutputPaintConfigForCvars() {
ui::Presenter::GuestOutputPaintConfig paint_config;
paint_config.SetAllowOverscanCutoff(true);
paint_config.SetEffect(GetGuestOutputPaintEffectForCvarValue(
cvars::postprocess_scaling_and_sharpening));
paint_config.SetCasAdditionalSharpness(
float(cvars::postprocess_ffx_cas_additional_sharpness));
paint_config.SetFsrMaxUpsamplingPasses(
cvars::postprocess_ffx_fsr_max_upsampling_passes);
paint_config.SetFsrSharpnessReduction(
float(cvars::postprocess_ffx_fsr_sharpness_reduction));
paint_config.SetDither(cvars::postprocess_dither);
return paint_config;
}
void EmulatorWindow::ApplyDisplayConfigForCvars() {
gpu::GraphicsSystem* graphics_system = emulator_->graphics_system();
if (!graphics_system) {
return;
}
gpu::CommandProcessor* command_processor =
graphics_system->command_processor();
if (command_processor) {
command_processor->SetDesiredSwapPostEffect(
GetSwapPostEffectForCvarValue(cvars::postprocess_antialiasing));
}
ui::Presenter* presenter = graphics_system->presenter();
if (presenter) {
presenter->SetGuestOutputPaintConfigFromUIThread(
GetGuestOutputPaintConfigForCvars());
}
}
void EmulatorWindow::OnKeyDown(ui::KeyEvent& e) {
if (!emulator_initialized_) {
return;
}
switch (e.virtual_key()) {
case ui::VirtualKey::kO: {
if (!e.is_ctrl_pressed()) {
return;
}
FileOpen();
} break;
case ui::VirtualKey::kMultiply: {
CpuTimeScalarReset();
} break;
case ui::VirtualKey::kSubtract: {
CpuTimeScalarSetHalf();
} break;
case ui::VirtualKey::kAdd: {
CpuTimeScalarSetDouble();
} break;
case ui::VirtualKey::kF3: {
Profiler::ToggleDisplay();
} break;
case ui::VirtualKey::kF4: {
GpuTraceFrame();
} break;
case ui::VirtualKey::kF5: {
GpuClearCaches();
} break;
case ui::VirtualKey::kF6: {
ToggleDisplayConfigDialog();
} break;
case ui::VirtualKey::kF11: {
ToggleFullscreen();
} break;
case ui::VirtualKey::kEscape: {
// Allow users to escape fullscreen (but not enter it).
if (!window_->IsFullscreen()) {
return;
}
SetFullscreen(false);
} break;
#ifdef DEBUG
case ui::VirtualKey::kF7: {
// Save to file
// TODO: Choose path based on user input, or from options
// TODO: Spawn a new thread to do this.
emulator()->SaveToFile("test.sav");
} break;
case ui::VirtualKey::kF8: {
// Restore from file
// TODO: Choose path from user
// TODO: Spawn a new thread to do this.
emulator()->RestoreFromFile("test.sav");
} break;
#endif // #ifdef DEBUG
case ui::VirtualKey::kPause: {
CpuBreakIntoDebugger();
} break;
case ui::VirtualKey::kCancel: {
CpuBreakIntoHostDebugger();
} break;
case ui::VirtualKey::kF1: {
ShowFAQ();
} break;
case ui::VirtualKey::kF2: {
ShowBuildCommit();
} break;
default:
return;
}
e.set_handled(true);
}
void EmulatorWindow::FileDrop(const std::filesystem::path& filename) {
if (!emulator_initialized_) {
return;
}
auto result = emulator_->LaunchPath(filename);
if (XFAILED(result)) {
// TODO: Display a message box.
XELOGE("Failed to launch target: {:08X}", result);
}
}
void EmulatorWindow::FileOpen() {
std::filesystem::path path;
auto file_picker = xe::ui::FilePicker::Create();
file_picker->set_mode(ui::FilePicker::Mode::kOpen);
file_picker->set_type(ui::FilePicker::Type::kFile);
file_picker->set_multi_selection(false);
file_picker->set_title("Select Content Package");
file_picker->set_extensions({
{"Supported Files", "*.iso;*.xex;*.*"},
{"Disc Image (*.iso)", "*.iso"},
{"Xbox Executable (*.xex)", "*.xex"},
//{"Content Package (*.xcp)", "*.xcp" },
{"All Files (*.*)", "*.*"},
});
if (file_picker->Show(window_.get())) {
auto selected_files = file_picker->selected_files();
if (!selected_files.empty()) {
path = selected_files[0];
}
}
if (!path.empty()) {
// Normalize the path and make absolute.
auto abs_path = std::filesystem::absolute(path);
auto result = emulator_->LaunchPath(abs_path);
if (XFAILED(result)) {
// TODO: Display a message box.
XELOGE("Failed to launch target: {:08X}", result);
}
}
}
void EmulatorWindow::FileClose() {
if (emulator_->is_title_open()) {
emulator_->TerminateTitle();
}
}
void EmulatorWindow::ShowContentDirectory() {
std::filesystem::path target_path;
auto content_root = emulator_->content_root();
if (!emulator_->is_title_open() || !emulator_->kernel_state()) {
target_path = content_root;
} else {
// TODO(gibbed): expose this via ContentManager?
auto title_id =
fmt::format("{:08X}", emulator_->kernel_state()->title_id());
auto package_root = content_root / title_id;
target_path = package_root;
}
if (!std::filesystem::exists(target_path)) {
std::filesystem::create_directories(target_path);
}
LaunchFileExplorer(target_path);
}
void EmulatorWindow::CpuTimeScalarReset() {
Clock::set_guest_time_scalar(1.0);
UpdateTitle();
}
void EmulatorWindow::CpuTimeScalarSetHalf() {
Clock::set_guest_time_scalar(Clock::guest_time_scalar() / 2.0);
UpdateTitle();
}
void EmulatorWindow::CpuTimeScalarSetDouble() {
Clock::set_guest_time_scalar(Clock::guest_time_scalar() * 2.0);
UpdateTitle();
}
void EmulatorWindow::CpuBreakIntoDebugger() {
if (!cvars::debug) {
xe::ui::ImGuiDialog::ShowMessageBox(imgui_drawer_.get(), "Xenia Debugger",
"Xenia must be launched with the "
"--debug flag in order to enable "
"debugging.");
return;
}
auto processor = emulator()->processor();
if (processor->execution_state() == cpu::ExecutionState::kRunning) {
// Currently running, so interrupt (and show the debugger).
processor->Pause();
} else {
// Not running, so just bring the debugger into focus.
processor->ShowDebugger();
}
}
void EmulatorWindow::CpuBreakIntoHostDebugger() { xe::debugging::Break(); }
void EmulatorWindow::GpuTraceFrame() {
emulator()->graphics_system()->RequestFrameTrace();
}
void EmulatorWindow::GpuClearCaches() {
emulator()->graphics_system()->ClearCaches();
}
void EmulatorWindow::SetFullscreen(bool fullscreen) {
if (window_->IsFullscreen() == fullscreen) {
return;
}
window_->SetFullscreen(fullscreen);
window_->SetCursorVisibility(fullscreen
? ui::Window::CursorVisibility::kAutoHidden
: ui::Window::CursorVisibility::kVisible);
}
void EmulatorWindow::ToggleFullscreen() {
SetFullscreen(!window_->IsFullscreen());
}
void EmulatorWindow::ToggleDisplayConfigDialog() {
if (!display_config_dialog_) {
display_config_dialog_ = std::unique_ptr(
new DisplayConfigDialog(imgui_drawer_.get(), *this));
} else {
display_config_dialog_.reset();
}
}
void EmulatorWindow::ShowCompatibility() {
const std::string_view base_url =
"https://github.com/xenia-project/game-compatibility/issues";
std::string url;
// Avoid searching for a title ID of "00000000".
uint32_t title_id = emulator_->title_id();
if (!title_id) {
url = base_url;
} else {
url = fmt::format("{}?q=is%3Aissue+is%3Aopen+{:08X}", base_url, title_id);
}
LaunchWebBrowser(url);
}
void EmulatorWindow::ShowFAQ() {
LaunchWebBrowser("https://github.com/xenia-project/xenia/wiki/FAQ");
}
void EmulatorWindow::ShowBuildCommit() {
#ifdef XE_BUILD_IS_PR
LaunchWebBrowser(
"https://github.com/xenia-project/xenia/pull/" XE_BUILD_PR_NUMBER);
#else
LaunchWebBrowser(
"https://github.com/xenia-project/xenia/commit/" XE_BUILD_COMMIT);
#endif
}
void EmulatorWindow::UpdateTitle() {
xe::StringBuffer sb;
sb.Append(base_title_);
// Title information, if available
if (emulator()->is_title_open()) {
sb.AppendFormat(u8" | [{:08X}", emulator()->title_id());
auto title_version = emulator()->title_version();
if (!title_version.empty()) {
sb.Append(u8" v");
sb.Append(title_version);
}
sb.Append(u8"]");
auto title_name = emulator()->title_name();
if (!title_name.empty()) {
sb.Append(u8" ");
sb.Append(title_name);
}
}
// Graphics system name, if available
auto graphics_system = emulator()->graphics_system();
if (graphics_system) {
auto graphics_name = graphics_system->name();
if (!graphics_name.empty()) {
sb.Append(u8" <");
sb.Append(graphics_name);
sb.Append(u8">");
}
}
if (Clock::guest_time_scalar() != 1.0) {
sb.AppendFormat(u8" (@{:.2f}x)", Clock::guest_time_scalar());
}
if (initializing_shader_storage_) {
sb.Append(u8" (Preloading shaders\u2026)");
}
window_->SetTitle(sb.to_string_view());
}
void EmulatorWindow::SetInitializingShaderStorage(bool initializing) {
if (initializing_shader_storage_ == initializing) {
return;
}
initializing_shader_storage_ = initializing;
UpdateTitle();
}
} // namespace app
} // namespace xe
================================================
FILE: src/xenia/app/emulator_window.h
================================================
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2022 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_APP_EMULATOR_WINDOW_H_
#define XENIA_APP_EMULATOR_WINDOW_H_
#include
#include
#include "xenia/emulator.h"
#include "xenia/gpu/command_processor.h"
#include "xenia/ui/imgui_dialog.h"
#include "xenia/ui/imgui_drawer.h"
#include "xenia/ui/immediate_drawer.h"
#include "xenia/ui/menu_item.h"
#include "xenia/ui/presenter.h"
#include "xenia/ui/window.h"
#include "xenia/ui/window_listener.h"
#include "xenia/ui/windowed_app_context.h"
#include "xenia/xbox.h"
namespace xe {
namespace app {
class EmulatorWindow {
public:
enum : size_t {
// The UI is on top of the game and is open in special cases, so
// lowest-priority.
kZOrderHidInput,
kZOrderImGui,
kZOrderProfiler,
// Emulator window controls are expected to be always accessible by the
// user, so highest-priority.
kZOrderEmulatorWindowInput,
};
virtual ~EmulatorWindow();
static std::unique_ptr Create(
Emulator* emulator, ui::WindowedAppContext& app_context);
Emulator* emulator() const { return emulator_; }
ui::WindowedAppContext& app_context() const { return app_context_; }
ui::Window* window() const { return window_.get(); }
ui::ImGuiDrawer* imgui_drawer() const { return imgui_drawer_.get(); }
ui::Presenter* GetGraphicsSystemPresenter() const;
void SetupGraphicsSystemPresenterPainting();
void ShutdownGraphicsSystemPresenterPainting();
void OnEmulatorInitialized();
void UpdateTitle();
void SetFullscreen(bool fullscreen);
void ToggleFullscreen();
void SetInitializingShaderStorage(bool initializing);
private:
class EmulatorWindowListener final : public ui::WindowListener,
public ui::WindowInputListener {
public:
explicit EmulatorWindowListener(EmulatorWindow& emulator_window)
: emulator_window_(emulator_window) {}
void OnClosing(ui::UIEvent& e) override;
void OnFileDrop(ui::FileDropEvent& e) override;
void OnKeyDown(ui::KeyEvent& e) override;
private:
EmulatorWindow& emulator_window_;
};
class DisplayConfigGameConfigLoadCallback
: public Emulator::GameConfigLoadCallback {
public:
DisplayConfigGameConfigLoadCallback(Emulator& emulator,
EmulatorWindow& emulator_window)
: Emulator::GameConfigLoadCallback(emulator),
emulator_window_(emulator_window) {}
void PostGameConfigLoad() override;
private:
EmulatorWindow& emulator_window_;
};
class DisplayConfigDialog final : public ui::ImGuiDialog {
public:
DisplayConfigDialog(ui::ImGuiDrawer* imgui_drawer,
EmulatorWindow& emulator_window)
: ui::ImGuiDialog(imgui_drawer), emulator_window_(emulator_window) {}
protected:
void OnDraw(ImGuiIO& io) override;
private:
EmulatorWindow& emulator_window_;
};
explicit EmulatorWindow(Emulator* emulator,
ui::WindowedAppContext& app_context);
bool Initialize();
// For comparisons, use GetSwapPostEffectForCvarValue instead as the default
// fallback may be used for multiple values.
static const char* GetCvarValueForSwapPostEffect(
gpu::CommandProcessor::SwapPostEffect effect);
static gpu::CommandProcessor::SwapPostEffect GetSwapPostEffectForCvarValue(
const std::string& cvar_value);
// For comparisons, use GetGuestOutputPaintEffectForCvarValue instead as the
// default fallback may be used for multiple values.
static const char* GetCvarValueForGuestOutputPaintEffect(
ui::Presenter::GuestOutputPaintConfig::Effect effect);
static ui::Presenter::GuestOutputPaintConfig::Effect
GetGuestOutputPaintEffectForCvarValue(const std::string& cvar_value);
static ui::Presenter::GuestOutputPaintConfig
GetGuestOutputPaintConfigForCvars();
void ApplyDisplayConfigForCvars();
void OnKeyDown(ui::KeyEvent& e);
void FileDrop(const std::filesystem::path& filename);
void FileOpen();
void FileClose();
void ShowContentDirectory();
void CpuTimeScalarReset();
void CpuTimeScalarSetHalf();
void CpuTimeScalarSetDouble();
void CpuBreakIntoDebugger();
void CpuBreakIntoHostDebugger();
void GpuTraceFrame();
void GpuClearCaches();
void ToggleDisplayConfigDialog();
void ShowCompatibility();
void ShowFAQ();
void ShowBuildCommit();
Emulator* emulator_;
ui::WindowedAppContext& app_context_;
EmulatorWindowListener window_listener_;
std::unique_ptr window_;
std::unique_ptr imgui_drawer_;
std::unique_ptr
display_config_game_config_load_callback_;
// Creation may fail, in this case immediate drawer UI must not be drawn.
std::unique_ptr immediate_drawer_;
bool emulator_initialized_ = false;
std::string base_title_;
bool initializing_shader_storage_ = false;
std::unique_ptr display_config_dialog_;
};
} // namespace app
} // namespace xe
#endif // XENIA_APP_EMULATOR_WINDOW_H_
================================================
FILE: src/xenia/app/main_resources.rc
================================================
//{{NO_DEPENDENCIES}}
MAINICON ICON "..\\..\\..\\assets\\icon\\icon.ico"
================================================
FILE: src/xenia/app/premake5.lua
================================================
project_root = "../../.."
include(project_root.."/tools/build")
group("src")
project("xenia-app")
uuid("d7e98620-d007-4ad8-9dbd-b47c8853a17f")
language("C++")
links({
"xenia-apu",
"xenia-apu-nop",
"xenia-base",
"xenia-core",
"xenia-cpu",
"xenia-gpu",
"xenia-gpu-null",
"xenia-gpu-vulkan",
"xenia-hid",
"xenia-hid-nop",
"xenia-kernel",
"xenia-ui",
"xenia-ui-vulkan",
"xenia-vfs",
})
links({
"aes_128",
"capstone",
"fmt",
"dxbc",
"discord-rpc",
"glslang-spirv",
"imgui",
"libavcodec",
"libavutil",
"mspack",
"snappy",
"xxhash",
})
defines({
"XBYAK_NO_OP_NAMES",
"XBYAK_ENABLE_OMITTED_OPERAND",
})
local_platform_files()
files({
"../base/main_init_"..platform_suffix..".cc",
"../ui/windowed_app_main_"..platform_suffix..".cc",
})
resincludedirs({
project_root,
})
filter(SINGLE_LIBRARY_FILTER)
-- Unified library containing all apps as StaticLibs, not just the main
-- emulator windowed app.
kind("SharedLib")
links({
"xenia-gpu-vulkan-trace-viewer",
"xenia-hid-demo",
"xenia-ui-window-vulkan-demo",
})
filter(NOT_SINGLE_LIBRARY_FILTER)
kind("WindowedApp")
-- `targetname` is broken if building from Gradle, works only for toggling the
-- `lib` prefix, as Gradle uses LOCAL_MODULE_FILENAME, not a derivative of
-- LOCAL_MODULE, to specify the targets to build when executing ndk-build.
filter("platforms:not Android-*")
targetname("xenia")
filter("architecture:x86_64")
links({
"xenia-cpu-backend-x64",
})
-- TODO(Triang3l): The emulator itself on Android.
filter("platforms:not Android-*")
files({
"xenia_main.cc",
})
filter("platforms:Windows")
files({
"main_resources.rc",
})
filter({"architecture:x86_64", "files:../base/main_init_"..platform_suffix..".cc"})
vectorextensions("IA32") -- Disable AVX for main_init_win.cc so our AVX check doesn't use AVX instructions.
filter("platforms:not Android-*")
links({
"xenia-app-discord",
"xenia-apu-sdl",
-- TODO(Triang3l): CPU debugger on Android.
"xenia-debug-ui",
"xenia-helper-sdl",
"xenia-hid-sdl",
})
filter("platforms:Linux")
links({
"X11",
"xcb",
"X11-xcb",
"SDL2",
})
filter("platforms:Windows")
links({
"xenia-apu-xaudio2",
"xenia-gpu-d3d12",
"xenia-hid-winkey",
"xenia-hid-xinput",
"xenia-ui-d3d12",
})
filter({"platforms:Windows", SINGLE_LIBRARY_FILTER})
links({
"xenia-gpu-d3d12-trace-viewer",
"xenia-ui-window-d3d12-demo",
})
filter("platforms:Windows")
-- Only create the .user file if it doesn't already exist.
local user_file = project_root.."/build/xenia-app.vcxproj.user"
if not os.isfile(user_file) then
debugdir(project_root)
debugargs({
})
end
================================================
FILE: src/xenia/app/xenia_main.cc
================================================
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2022 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include
#include
#include
#include
#include
#include
#include
#include "xenia/app/discord/discord_presence.h"
#include "xenia/app/emulator_window.h"
#include "xenia/base/assert.h"
#include "xenia/base/cvar.h"
#include "xenia/base/debugging.h"
#include "xenia/base/logging.h"
#include "xenia/base/platform.h"
#include "xenia/base/profiling.h"
#include "xenia/base/threading.h"
#include "xenia/config.h"
#include "xenia/debug/ui/debug_window.h"
#include "xenia/emulator.h"
#include "xenia/ui/file_picker.h"
#include "xenia/ui/window.h"
#include "xenia/ui/window_listener.h"
#include "xenia/ui/windowed_app.h"
#include "xenia/ui/windowed_app_context.h"
#include "xenia/vfs/devices/host_path_device.h"
// Available audio systems:
#include "xenia/apu/nop/nop_audio_system.h"
#if !XE_PLATFORM_ANDROID
#include "xenia/apu/sdl/sdl_audio_system.h"
#endif // !XE_PLATFORM_ANDROID
#if XE_PLATFORM_WIN32
#include "xenia/apu/xaudio2/xaudio2_audio_system.h"
#endif // XE_PLATFORM_WIN32
// Available graphics systems:
#include "xenia/gpu/null/null_graphics_system.h"
#include "xenia/gpu/vulkan/vulkan_graphics_system.h"
#if XE_PLATFORM_WIN32
#include "xenia/gpu/d3d12/d3d12_graphics_system.h"
#endif // XE_PLATFORM_WIN32
// Available input drivers:
#include "xenia/hid/nop/nop_hid.h"
#if !XE_PLATFORM_ANDROID
#include "xenia/hid/sdl/sdl_hid.h"
#endif // !XE_PLATFORM_ANDROID
#if XE_PLATFORM_WIN32
#include "xenia/hid/winkey/winkey_hid.h"
#include "xenia/hid/xinput/xinput_hid.h"
#endif // XE_PLATFORM_WIN32
#include "third_party/fmt/include/fmt/format.h"
DEFINE_string(apu, "any", "Audio system. Use: [any, nop, sdl, xaudio2]", "APU");
DEFINE_string(gpu, "any", "Graphics system. Use: [any, d3d12, vulkan, null]",
"GPU");
DEFINE_string(hid, "any", "Input system. Use: [any, nop, sdl, winkey, xinput]",
"HID");
DEFINE_path(
storage_root, "",
"Root path for persistent internal data storage (config, etc.), or empty "
"to use the path preferred for the OS, such as the documents folder, or "
"the emulator executable directory if portable.txt is present in it.",
"Storage");
DEFINE_path(
content_root, "",
"Root path for guest content storage (saves, etc.), or empty to use the "
"content folder under the storage root.",
"Storage");
DEFINE_path(
cache_root, "",
"Root path for files used to speed up certain parts of the emulator or the "
"game. These files may be persistent, but they can be deleted without "
"major side effects such as progress loss. If empty, the cache folder "
"under the storage root, or, if available, the cache directory preferred "
"for the OS, will be used.",
"Storage");
DEFINE_bool(mount_scratch, false, "Enable scratch mount", "Storage");
DEFINE_bool(mount_cache, false, "Enable cache mount", "Storage");
DEFINE_transient_path(target, "",
"Specifies the target .xex or .iso to execute.",
"General");
DEFINE_transient_bool(portable, false,
"Specifies if Xenia should run in portable mode.",
"General");
DECLARE_bool(debug);
DEFINE_bool(discord, true, "Enable Discord rich presence", "General");
namespace xe {
namespace app {
class EmulatorApp final : public xe::ui::WindowedApp {
public:
static std::unique_ptr Create(
xe::ui::WindowedAppContext& app_context) {
return std::unique_ptr(new EmulatorApp(app_context));
}
~EmulatorApp();
bool OnInitialize() override;
protected:
void OnDestroy() override;
private:
template
class Factory {
private:
struct Creator {
std::string name;
std::function is_available;
std::function(Args...)> instantiate;
};
std::vector creators_;
public:
void Add(const std::string_view name, std::function is_available,
std::function(Args...)> instantiate) {
creators_.push_back({std::string(name), is_available, instantiate});
}
void Add(const std::string_view name,
std::function(Args...)> instantiate) {
auto always_available = []() { return true; };
Add(name, always_available, instantiate);
}
template
void Add(const std::string_view name) {
Add(name, DT::IsAvailable, [](Args... args) {
return std::make_unique
(std::forward(args)...);
});
}
std::unique_ptr Create(const std::string_view name, Args... args) {
if (!name.empty() && name != "any") {
auto it = std::find_if(
creators_.cbegin(), creators_.cend(),
[&name](const auto& f) { return name.compare(f.name) == 0; });
if (it != creators_.cend() && (*it).is_available()) {
return (*it).instantiate(std::forward(args)...);
}
return nullptr;
} else {
for (const auto& creator : creators_) {
if (!creator.is_available()) continue;
auto instance = creator.instantiate(std::forward(args)...);
if (!instance) continue;
return instance;
}
return nullptr;
}
}
std::vector> CreateAll(const std::string_view name,
Args... args) {
std::vector> instances;
if (!name.empty() && name != "any") {
auto it = std::find_if(
creators_.cbegin(), creators_.cend(),
[&name](const auto& f) { return name.compare(f.name) == 0; });
if (it != creators_.cend() && (*it).is_available()) {
auto instance = (*it).instantiate(std::forward(args)...);
if (instance) {
instances.emplace_back(std::move(instance));
}
}
} else {
for (const auto& creator : creators_) {
if (!creator.is_available()) continue;
auto instance = creator.instantiate(std::forward(args)...);
if (instance) {
instances.emplace_back(std::move(instance));
}
}
}
return instances;
}
};
class DebugWindowClosedListener final : public xe::ui::WindowListener {
public:
explicit DebugWindowClosedListener(EmulatorApp& emulator_app)
: emulator_app_(emulator_app) {}
void OnClosing(xe::ui::UIEvent& e) override;
private:
EmulatorApp& emulator_app_;
};
explicit EmulatorApp(xe::ui::WindowedAppContext& app_context);
static std::unique_ptr CreateAudioSystem(
cpu::Processor* processor);
static std::unique_ptr CreateGraphicsSystem();
static std::vector> CreateInputDrivers(
ui::Window* window);
void EmulatorThread();
void ShutdownEmulatorThreadFromUIThread();
DebugWindowClosedListener debug_window_closed_listener_;
std::unique_ptr emulator_;
std::unique_ptr emulator_window_;
// Created on demand, used by the emulator.
std::unique_ptr debug_window_;
// Refreshing the emulator - placed after its dependencies.
std::atomic emulator_thread_quit_requested_;
std::unique_ptr emulator_thread_event_;
std::thread emulator_thread_;
};
void EmulatorApp::DebugWindowClosedListener::OnClosing(xe::ui::UIEvent& e) {
EmulatorApp* emulator_app = &emulator_app_;
emulator_app->emulator_->processor()->set_debug_listener(nullptr);
emulator_app->debug_window_.reset();
}
EmulatorApp::EmulatorApp(xe::ui::WindowedAppContext& app_context)
: xe::ui::WindowedApp(app_context, "xenia", "[Path to .iso/.xex]"),
debug_window_closed_listener_(*this) {
AddPositionalOption("target");
}
EmulatorApp::~EmulatorApp() {
// Should be shut down from OnDestroy if OnInitialize has ever been done, but
// for the most safety as a running thread may be destroyed only after
// joining.
ShutdownEmulatorThreadFromUIThread();
}
std::unique_ptr EmulatorApp::CreateAudioSystem(
cpu::Processor* processor) {
Factory factory;
#if XE_PLATFORM_WIN32
factory.Add("xaudio2");
#endif // XE_PLATFORM_WIN32
#if !XE_PLATFORM_ANDROID
factory.Add("sdl");
#endif // !XE_PLATFORM_ANDROID
factory.Add("nop");
return factory.Create(cvars::apu, processor);
}
std::unique_ptr EmulatorApp::CreateGraphicsSystem() {
// While Vulkan is supported by a large variety of operating systems (Windows,
// GNU/Linux, Android, also via the MoltenVK translation layer on top of Metal
// on macOS and iOS), please don't remove platform-specific GPU backends from
// Xenia.
//
// Regardless of the operating system, having multiple options provides more
// stability to users. In case of driver issues, users may try switching
// between the available backends. For example, in June 2022, on Nvidia Ampere
// (RTX 30xx), Xenia had synchronization issues that resulted in flickering,
// most prominently in 4D5307E6, on Direct3D 12 - but the same issue was not
// reproducible in the Vulkan backend, however, it used ImageSampleExplicitLod
// with explicit gradients for cubemaps, which triggered a different driver
// bug on Nvidia (every 1 out of 2x2 pixels receiving junk).
//
// Specifically on Microsoft platforms, there are a few reasons why supporting
// Direct3D 12 is desirable rather than limiting Xenia to Vulkan only:
// - Wider hardware support for Direct3D 12 on x86 Windows desktops.
// Direct3D 12 requires the minimum of Nvidia Fermi, or, with a pre-2021
// driver version, Intel HD Graphics 4200. Vulkan, however, is supported
// only starting with Nvidia Kepler and a much more recent Intel UHD
// Graphics generation.
// - Wider hardware support on other kinds of Microsoft devices. The Xbox One
// and the Xbox Series X|S only support Direct3D as the GPU API in their UWP
// runtime, and only version 12 can be granted expanded resource access.
// Qualcomm, as of June 2022, also doesn't provide a Vulkan implementation
// for their Arm-based Windows devices, while Direct3D 12 is available.
// - Both older Intel GPUs and the Xbox One apparently, as well as earlier
// Windows 10 versions, also require Shader Model 5.1 DXBC shaders rather
// than Shader Model 6 DXIL ones, so a DXBC shader translator should be
// available in Xenia too, a DXIL one doesn't fully replace it.
// - As of June 2022, AMD also refuses to implement the
// VK_EXT_fragment_shader_interlock Vulkan extension in their drivers, as
// well as its OpenGL counterpart, which is heavily utilized for accurate
// support of Xenos render target formats that don't have PC equivalents
// (8_8_8_8_GAMMA, 2_10_10_10_FLOAT, 16_16 and 16_16_16_16 with -32 to 32
// range, D24FS8) with correct blending. Direct3D 12, however, requires
// support for similar functionality (rasterizer-ordered views) on the
// feature level 12_1, and the AMD driver implements it on Direct3D, as well
// as raster order groups in their Metal driver.
//
// Additionally, different host GPU APIs receive feature support at different
// paces. VK_EXT_fragment_shader_interlock first appeared in 2019, for
// instance, while Xenia had been taking advantage of rasterizer-ordered views
// on Direct3D 12 for over half a year at that point (they have existed in
// Direct3D 12 since the first version).
//
// MoltenVK on top Metal also has its flaws and limitations. Metal, for
// instance, as of June 2022, doesn't provide a switch for primitive restart,
// while Vulkan does - so MoltenVK is not completely transparent to Xenia,
// many of its issues that may be not very obvious (unlike when the Metal API
// is used directly) should be taken into account in Xenia. Also, as of June
// 2022, MoltenVK translates SPIR-V shaders into the C++-based Metal Shading
// Language rather than AIR directly, which likely massively increases
// pipeline object creation time - and Xenia translates shaders and creates
// pipelines when they're first actually used for a draw command by the game,
// thus it can't precompile anything that hasn't ever been encountered before
// there's already no time to waste.
//
// Very old hardware (Direct3D 10 level) is also not supported by most Vulkan
// drivers. However, in the future, Xenia may be ported to it using the
// Direct3D 11 API with the feature level 10_1 or 10_0. OpenGL, however, had
// been lagging behind Direct3D prior to versions 4.x, and didn't receive
// compute shaders until a 4.2 extension (while 4.2 already corresponds
// roughly to Direct3D 11 features) - and replacing Xenia compute shaders with
// transform feedback / stream output is not always trivial (in particular,
// will need to rely on GL_ARB_transform_feedback3 for skipping over memory
// locations that shouldn't be overwritten).
//
// For maintainability, as much implementation code as possible should be
// placed in `xe::gpu` and shared between the backends rather than duplicated
// between them.
const std::string gpu_implementation_name = cvars::gpu;
if (gpu_implementation_name == "null") {
return std::make_unique();
}
Factory factory;
#if XE_PLATFORM_WIN32
factory.Add("d3d12");
#endif // XE_PLATFORM_WIN32
factory.Add("vulkan");
std::unique_ptr gpu_implementation =
factory.Create(gpu_implementation_name);
if (!gpu_implementation) {
xe::FatalError(
"Unable to initialize the graphics subsystem.\n"
"\n"
#if XE_PLATFORM_ANDROID
"The GPU must support at least Vulkan 1.0 with the 'independentBlend' "
"feature.\n"
"\n"
#else
#if XE_PLATFORM_WIN32
"For Direct3D 12, at least Windows 10 is required, and the GPU must be "
"compatible with Direct3D 12 feature level 11_0.\n"
"\n"
#endif // XE_PLATFORM_WIN32
"For Vulkan, the Vulkan runtime must be installed, and the GPU must "
"support at least Vulkan 1.0. The Vulkan runtime can be downloaded at "
"https://vulkan.lunarg.com/sdk/home.\n"
"\n"
"Also, ensure that you have the latest driver installed for your GPU.\n"
"\n"
#endif // XE_PLATFORM_ANDROID
"See https://xenia.jp/faq/ for more information and the system "
"requirements.");
}
return gpu_implementation;
}
std::vector> EmulatorApp::CreateInputDrivers(
ui::Window* window) {
std::vector> drivers;
if (cvars::hid.compare("nop") == 0) {
drivers.emplace_back(
xe::hid::nop::Create(window, EmulatorWindow::kZOrderHidInput));
} else {
Factory factory;
#if XE_PLATFORM_WIN32
factory.Add("xinput", xe::hid::xinput::Create);
#endif // XE_PLATFORM_WIN32
#if !XE_PLATFORM_ANDROID
factory.Add("sdl", xe::hid::sdl::Create);
#endif // !XE_PLATFORM_ANDROID
#if XE_PLATFORM_WIN32
// WinKey input driver should always be the last input driver added!
factory.Add("winkey", xe::hid::winkey::Create);
#endif // XE_PLATFORM_WIN32
for (auto& driver : factory.CreateAll(cvars::hid, window,
EmulatorWindow::kZOrderHidInput)) {
if (XSUCCEEDED(driver->Setup())) {
drivers.emplace_back(std::move(driver));
}
}
if (drivers.empty()) {
// Fallback to nop if none created.
drivers.emplace_back(
xe::hid::nop::Create(window, EmulatorWindow::kZOrderHidInput));
}
}
return drivers;
}
bool EmulatorApp::OnInitialize() {
Profiler::Initialize();
Profiler::ThreadEnter("Main");
// Figure out where internal files and content should go.
std::filesystem::path storage_root = cvars::storage_root;
if (storage_root.empty()) {
storage_root = xe::filesystem::GetExecutableFolder();
if (!cvars::portable &&
!std::filesystem::exists(storage_root / "portable.txt")) {
storage_root = xe::filesystem::GetUserFolder();
#if defined(XE_PLATFORM_WIN32) || defined(XE_PLATFORM_GNU_LINUX)
storage_root = storage_root / "Xenia";
#else
// TODO(Triang3l): Point to the app's external storage "files" directory
// on Android.
#warning Unhandled platform for the data root.
storage_root = storage_root / "Xenia";
#endif
}
}
storage_root = std::filesystem::absolute(storage_root);
XELOGI("Storage root: {}", xe::path_to_utf8(storage_root));
config::SetupConfig(storage_root);
std::filesystem::path content_root = cvars::content_root;
if (content_root.empty()) {
content_root = storage_root / "content";
} else {
// If content root isn't an absolute path, then it should be relative to the
// storage root.
if (!content_root.is_absolute()) {
content_root = storage_root / content_root;
}
}
content_root = std::filesystem::absolute(content_root);
XELOGI("Content root: {}", xe::path_to_utf8(content_root));
std::filesystem::path cache_root = cvars::cache_root;
if (cache_root.empty()) {
cache_root = storage_root / "cache";
// TODO(Triang3l): Point to the app's external storage "cache" directory on
// Android.
} else {
// If content root isn't an absolute path, then it should be relative to the
// storage root.
if (!cache_root.is_absolute()) {
cache_root = storage_root / cache_root;
}
}
cache_root = std::filesystem::absolute(cache_root);
XELOGI("Cache root: {}", xe::path_to_utf8(cache_root));
if (cvars::discord) {
discord::DiscordPresence::Initialize();
discord::DiscordPresence::NotPlaying();
}
// Create the emulator but don't initialize so we can setup the window.
emulator_ =
std::make_unique("", storage_root, content_root, cache_root);
// Main emulator display window.
emulator_window_ = EmulatorWindow::Create(emulator_.get(), app_context());
if (!emulator_window_) {
XELOGE("Failed to create the main emulator window");
return false;
}
// Setup the emulator and run its loop in a separate thread.
emulator_thread_quit_requested_.store(false, std::memory_order_relaxed);
emulator_thread_event_ = xe::threading::Event::CreateAutoResetEvent(false);
assert_not_null(emulator_thread_event_);
emulator_thread_ = std::thread(&EmulatorApp::EmulatorThread, this);
return true;
}
void EmulatorApp::OnDestroy() {
ShutdownEmulatorThreadFromUIThread();
if (cvars::discord) {
discord::DiscordPresence::Shutdown();
}
Profiler::Dump();
// The profiler needs to shut down before the graphics context.
Profiler::Shutdown();
// Write all cvar overrides to the config.
config::SaveConfig();
// TODO(DrChat): Remove this code and do a proper exit.
XELOGI("Cheap-skate exit!");
std::quick_exit(EXIT_SUCCESS);
}
void EmulatorApp::EmulatorThread() {
assert_not_null(emulator_thread_event_);
xe::threading::set_name("Emulator");
Profiler::ThreadEnter("Emulator");
// Setup and initialize all subsystems. If we can't do something
// (unsupported system, memory issues, etc) this will fail early.
X_STATUS result = emulator_->Setup(
emulator_window_->window(), emulator_window_->imgui_drawer(), true,
CreateAudioSystem, CreateGraphicsSystem, CreateInputDrivers);
if (XFAILED(result)) {
XELOGE("Failed to setup emulator: {:08X}", result);
app_context().RequestDeferredQuit();
return;
}
app_context().CallInUIThread(
[this]() { emulator_window_->SetupGraphicsSystemPresenterPainting(); });
if (cvars::mount_scratch) {
auto scratch_device = std::make_unique(
"\\SCRATCH", "scratch", false);
if (!scratch_device->Initialize()) {
XELOGE("Unable to scan scratch path");
} else {
if (!emulator_->file_system()->RegisterDevice(
std::move(scratch_device))) {
XELOGE("Unable to register scratch path");
} else {
emulator_->file_system()->RegisterSymbolicLink("scratch:", "\\SCRATCH");
}
}
}
if (cvars::mount_cache) {
auto cache0_device =
std::make_unique("\\CACHE0", "cache0", false);
if (!cache0_device->Initialize()) {
XELOGE("Unable to scan cache0 path");
} else {
if (!emulator_->file_system()->RegisterDevice(std::move(cache0_device))) {
XELOGE("Unable to register cache0 path");
} else {
emulator_->file_system()->RegisterSymbolicLink("cache0:", "\\CACHE0");
}
}
auto cache1_device =
std::make_unique("\\CACHE1", "cache1", false);
if (!cache1_device->Initialize()) {
XELOGE("Unable to scan cache1 path");
} else {
if (!emulator_->file_system()->RegisterDevice(std::move(cache1_device))) {
XELOGE("Unable to register cache1 path");
} else {
emulator_->file_system()->RegisterSymbolicLink("cache1:", "\\CACHE1");
}
}
// Some (older?) games try accessing cache:\ too
// NOTE: this must be registered _after_ the cache0/cache1 devices, due to
// substring/start_with logic inside VirtualFileSystem::ResolvePath, else
// accesses to those devices will go here instead
auto cache_device =
std::make_unique("\\CACHE", "cache", false);
if (!cache_device->Initialize()) {
XELOGE("Unable to scan cache path");
} else {
if (!emulator_->file_system()->RegisterDevice(std::move(cache_device))) {
XELOGE("Unable to register cache path");
} else {
emulator_->file_system()->RegisterSymbolicLink("cache:", "\\CACHE");
}
}
}
// Set a debug handler.
// This will respond to debugging requests so we can open the debug UI.
if (cvars::debug) {
emulator_->processor()->set_debug_listener_request_handler(
[this](xe::cpu::Processor* processor) {
if (debug_window_) {
return debug_window_.get();
}
app_context().CallInUIThreadSynchronous([this]() {
debug_window_ = xe::debug::ui::DebugWindow::Create(emulator_.get(),
app_context());
debug_window_->window()->AddListener(
&debug_window_closed_listener_);
});
// If failed to enqueue the UI thread call, this will just be null.
return debug_window_.get();
});
}
emulator_->on_launch.AddListener([&](auto title_id, const auto& game_title) {
if (cvars::discord) {
discord::DiscordPresence::PlayingTitle(
game_title.empty() ? "Unknown Title" : std::string(game_title));
}
app_context().CallInUIThread([this]() { emulator_window_->UpdateTitle(); });
emulator_thread_event_->Set();
});
emulator_->on_shader_storage_initialization.AddListener(
[this](bool initializing) {
app_context().CallInUIThread([this, initializing]() {
emulator_window_->SetInitializingShaderStorage(initializing);
});
});
emulator_->on_terminate.AddListener([]() {
if (cvars::discord) {
discord::DiscordPresence::NotPlaying();
}
});
// Enable emulator input now that the emulator is properly loaded.
app_context().CallInUIThread(
[this]() { emulator_window_->OnEmulatorInitialized(); });
// Grab path from the flag or unnamed argument.
std::filesystem::path path;
if (!cvars::target.empty()) {
path = cvars::target;
}
if (!path.empty()) {
// Normalize the path and make absolute.
auto abs_path = std::filesystem::absolute(path);
result = emulator_->LaunchPath(abs_path);
if (XFAILED(result)) {
xe::FatalError(fmt::format("Failed to launch target: {:08X}", result));
app_context().RequestDeferredQuit();
return;
}
}
// Now, we're going to use this thread to drive events related to emulation.
while (!emulator_thread_quit_requested_.load(std::memory_order_relaxed)) {
xe::threading::Wait(emulator_thread_event_.get(), false);
while (true) {
emulator_->WaitUntilExit();
if (emulator_->TitleRequested()) {
emulator_->LaunchNextTitle();
} else {
break;
}
}
}
}
void EmulatorApp::ShutdownEmulatorThreadFromUIThread() {
// TODO(Triang3l): Proper shutdown of the emulator (relying on std::quick_exit
// for now) - currently WaitUntilExit loops forever otherwise (plus possibly
// lots of other things not shutting down correctly now). Some parts of the
// code call the regular std::exit, which seems to be calling destructors (at
// least on Linux), so the entire join is currently commented out.
#if 0
// Same thread as the one created it, to make sure there's zero possibility of
// a race with the creation of the emulator thread.
assert_true(app_context().IsInUIThread());
emulator_thread_quit_requested_.store(true, std::memory_order_relaxed);
if (!emulator_thread_.joinable()) {
return;
}
emulator_thread_event_->Set();
emulator_thread_.join();
#endif
}
} // namespace app
} // namespace xe
XE_DEFINE_WINDOWED_APP(xenia, xe::app::EmulatorApp::Create);
================================================
FILE: src/xenia/apu/apu_flags.cc
================================================
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2013 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/apu/apu_flags.h"
DEFINE_bool(mute, false, "Mutes all audio output.", "APU")
================================================
FILE: src/xenia/apu/apu_flags.h
================================================
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2013 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_APU_APU_FLAGS_H_
#define XENIA_APU_APU_FLAGS_H_
#include "xenia/base/cvar.h"
DECLARE_bool(mute)
#endif // XENIA_APU_APU_FLAGS_H_
================================================
FILE: src/xenia/apu/audio_driver.cc
================================================
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2013 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/apu/audio_driver.h"
namespace xe {
namespace apu {
AudioDriver::AudioDriver(Memory* memory) : memory_(memory) {}
AudioDriver::~AudioDriver() = default;
} // namespace apu
} // namespace xe
================================================
FILE: src/xenia/apu/audio_driver.h
================================================
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2013 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_APU_AUDIO_DRIVER_H_
#define XENIA_APU_AUDIO_DRIVER_H_
#include "xenia/memory.h"
#include "xenia/xbox.h"
namespace xe {
namespace apu {
class AudioDriver {
public:
explicit AudioDriver(Memory* memory);
virtual ~AudioDriver();
virtual void SubmitFrame(uint32_t samples_ptr) = 0;
protected:
inline uint8_t* TranslatePhysical(uint32_t guest_address) const {
return memory_->TranslatePhysical(guest_address);
}
Memory* memory_ = nullptr;
};
} // namespace apu
} // namespace xe
#endif // XENIA_APU_AUDIO_DRIVER_H_
================================================
FILE: src/xenia/apu/audio_system.cc
================================================
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2022 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/apu/audio_system.h"
#include "xenia/apu/apu_flags.h"
#include "xenia/apu/audio_driver.h"
#include "xenia/apu/xma_decoder.h"
#include "xenia/base/assert.h"
#include "xenia/base/byte_stream.h"
#include "xenia/base/logging.h"
#include "xenia/base/math.h"
#include "xenia/base/profiling.h"
#include "xenia/base/ring_buffer.h"
#include "xenia/base/string_buffer.h"
#include "xenia/base/threading.h"
#include "xenia/cpu/thread_state.h"
// As with normal Microsoft, there are like twelve different ways to access
// the audio APIs. Early games use XMA*() methods almost exclusively to touch
// decoders. Later games use XAudio*() and direct memory writes to the XMA
// structures (as opposed to the XMA* calls), meaning that we have to support
// both.
//
// For ease of implementation, most audio related processing is handled in
// AudioSystem, and the functions here call off to it.
// The XMA*() functions just manipulate the audio system in the guest context
// and let the normal AudioSystem handling take it, to prevent duplicate
// implementations. They can be found in xboxkrnl_audio_xma.cc
namespace xe {
namespace apu {
AudioSystem::AudioSystem(cpu::Processor* processor)
: memory_(processor->memory()),
processor_(processor),
worker_running_(false) {
std::memset(clients_, 0, sizeof(clients_));
for (size_t i = 0; i < kMaximumClientCount; ++i) {
client_semaphores_[i] =
xe::threading::Semaphore::Create(0, kMaximumQueuedFrames);
assert_not_null(client_semaphores_[i]);
wait_handles_[i] = client_semaphores_[i].get();
}
shutdown_event_ = xe::threading::Event::CreateAutoResetEvent(false);
assert_not_null(shutdown_event_);
wait_handles_[kMaximumClientCount] = shutdown_event_.get();
xma_decoder_ = std::make_unique(processor_);
resume_event_ = xe::threading::Event::CreateAutoResetEvent(false);
assert_not_null(resume_event_);
}
AudioSystem::~AudioSystem() {
if (xma_decoder_) {
xma_decoder_->Shutdown();
}
}
X_STATUS AudioSystem::Setup(kernel::KernelState* kernel_state) {
X_STATUS result = xma_decoder_->Setup(kernel_state);
if (result) {
return result;
}
worker_running_ = true;
worker_thread_ = kernel::object_ref(
new kernel::XHostThread(kernel_state, 128 * 1024, 0, [this]() {
WorkerThreadMain();
return 0;
}));
// As we run audio callbacks the debugger must be able to suspend us.
worker_thread_->set_can_debugger_suspend(true);
worker_thread_->set_name("Audio Worker");
worker_thread_->Create();
return X_STATUS_SUCCESS;
}
void AudioSystem::WorkerThreadMain() {
// Initialize driver and ringbuffer.
Initialize();
// Main run loop.
while (worker_running_) {
// These handles signify the number of submitted samples. Once we reach
// 64 samples, we wait until our audio backend releases a semaphore
// (signaling a sample has finished playing)
auto result =
xe::threading::WaitAny(wait_handles_, xe::countof(wait_handles_), true);
if (result.first == xe::threading::WaitResult::kFailed) {
// TODO: Assert?
continue;
}
if (result.first == threading::WaitResult::kSuccess &&
result.second == kMaximumClientCount) {
// Shutdown event signaled.
if (paused_) {
pause_fence_.Signal();
threading::Wait(resume_event_.get(), false);
}
continue;
}
// Number of clients pumped
bool pumped = false;
if (result.first == xe::threading::WaitResult::kSuccess) {
auto index = result.second;
auto global_lock = global_critical_region_.Acquire();
uint32_t client_callback = clients_[index].callback;
uint32_t client_callback_arg = clients_[index].wrapped_callback_arg;
global_lock.unlock();
if (client_callback) {
SCOPE_profile_cpu_i("apu", "xe::apu::AudioSystem->client_callback");
uint64_t args[] = {client_callback_arg};
processor_->Execute(worker_thread_->thread_state(), client_callback,
args, xe::countof(args));
}
pumped = true;
}
if (!worker_running_) {
break;
}
if (!pumped) {
SCOPE_profile_cpu_i("apu", "Sleep");
xe::threading::Sleep(std::chrono::milliseconds(500));
}
}
worker_running_ = false;
// TODO(benvanik): call module API to kill?
}
int AudioSystem::FindFreeClient() {
for (int i = 0; i < kMaximumClientCount; i++) {
auto& client = clients_[i];
if (!client.in_use) {
return i;
}
}
return -1;
}
void AudioSystem::Initialize() {}
void AudioSystem::Shutdown() {
worker_running_ = false;
shutdown_event_->Set();
if (worker_thread_) {
worker_thread_->Wait(0, 0, 0, nullptr);
worker_thread_.reset();
}
}
X_STATUS AudioSystem::RegisterClient(uint32_t callback, uint32_t callback_arg,
size_t* out_index) {
auto global_lock = global_critical_region_.Acquire();
auto index = FindFreeClient();
assert_true(index >= 0);
auto client_semaphore = client_semaphores_[index].get();
auto ret = client_semaphore->Release(kMaximumQueuedFrames, nullptr);
assert_true(ret);
AudioDriver* driver;
auto result = CreateDriver(index, client_semaphore, &driver);
if (XFAILED(result)) {
return result;
}
assert_not_null(driver);
uint32_t ptr = memory()->SystemHeapAlloc(0x4);
xe::store_and_swap(memory()->TranslateVirtual(ptr), callback_arg);
clients_[index] = {driver, callback, callback_arg, ptr, true};
if (out_index) {
*out_index = index;
}
return X_STATUS_SUCCESS;
}
void AudioSystem::SubmitFrame(size_t index, uint32_t samples_ptr) {
SCOPE_profile_cpu_f("apu");
auto global_lock = global_critical_region_.Acquire();
assert_true(index < kMaximumClientCount);
assert_true(clients_[index].driver != NULL);
(clients_[index].driver)->SubmitFrame(samples_ptr);
}
void AudioSystem::UnregisterClient(size_t index) {
SCOPE_profile_cpu_f("apu");
auto global_lock = global_critical_region_.Acquire();
assert_true(index < kMaximumClientCount);
DestroyDriver(clients_[index].driver);
memory()->SystemHeapFree(clients_[index].wrapped_callback_arg);
clients_[index] = {0};
// Drain the semaphore of its count.
auto client_semaphore = client_semaphores_[index].get();
xe::threading::WaitResult wait_result;
do {
wait_result = xe::threading::Wait(client_semaphore, false,
std::chrono::milliseconds(0));
} while (wait_result == xe::threading::WaitResult::kSuccess);
assert_true(wait_result == xe::threading::WaitResult::kTimeout);
}
bool AudioSystem::Save(ByteStream* stream) {
stream->Write(kAudioSaveSignature);
// Count the number of used clients first.
// Any gaps should be handled gracefully.
uint32_t used_clients = 0;
for (int i = 0; i < kMaximumClientCount; i++) {
if (clients_[i].in_use) {
used_clients++;
}
}
stream->Write(used_clients);
for (uint32_t i = 0; i < kMaximumClientCount; i++) {
auto& client = clients_[i];
if (!client.in_use) {
continue;
}
stream->Write(i);
stream->Write(client.callback);
stream->Write(client.callback_arg);
stream->Write(client.wrapped_callback_arg);
}
return true;
}
bool AudioSystem::Restore(ByteStream* stream) {
if (stream->Read() != kAudioSaveSignature) {
XELOGE("AudioSystem::Restore - Invalid magic value!");
return false;
}
uint32_t num_clients = stream->Read();
for (uint32_t i = 0; i < num_clients; i++) {
auto id = stream->Read();
assert_true(id < kMaximumClientCount);
auto& client = clients_[id];
// Reset the semaphore and recreate the driver ourselves.
if (client.driver) {
UnregisterClient(id);
}
client.callback = stream->Read();
client.callback_arg = stream->Read();
client.wrapped_callback_arg = stream->Read();
client.in_use = true;
auto client_semaphore = client_semaphores_[id].get();
auto ret = client_semaphore->Release(kMaximumQueuedFrames, nullptr);
assert_true(ret);
AudioDriver* driver = nullptr;
auto status = CreateDriver(id, client_semaphore, &driver);
if (XFAILED(status)) {
XELOGE(
"AudioSystem::Restore - Call to CreateDriver failed with status "
"{:08X}",
status);
return false;
}
assert_not_null(driver);
client.driver = driver;
}
return true;
}
void AudioSystem::Pause() {
if (paused_) {
return;
}
paused_ = true;
// Kind of a hack, but it works.
shutdown_event_->Set();
pause_fence_.Wait();
xma_decoder_->Pause();
}
void AudioSystem::Resume() {
if (!paused_) {
return;
}
paused_ = false;
resume_event_->Set();
xma_decoder_->Resume();
}
} // namespace apu
} // namespace xe
================================================
FILE: src/xenia/apu/audio_system.h
================================================
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2013 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_APU_AUDIO_SYSTEM_H_
#define XENIA_APU_AUDIO_SYSTEM_H_
#include
#include
#include "xenia/base/mutex.h"
#include "xenia/base/threading.h"
#include "xenia/cpu/processor.h"
#include "xenia/kernel/xthread.h"
#include "xenia/memory.h"
#include "xenia/xbox.h"
namespace xe {
namespace apu {
constexpr fourcc_t kAudioSaveSignature = make_fourcc("XAUD");
class AudioDriver;
class XmaDecoder;
class AudioSystem {
public:
virtual ~AudioSystem();
Memory* memory() const { return memory_; }
cpu::Processor* processor() const { return processor_; }
XmaDecoder* xma_decoder() const { return xma_decoder_.get(); }
virtual X_STATUS Setup(kernel::KernelState* kernel_state);
virtual void Shutdown();
X_STATUS RegisterClient(uint32_t callback, uint32_t callback_arg,
size_t* out_index);
void UnregisterClient(size_t index);
void SubmitFrame(size_t index, uint32_t samples_ptr);
bool Save(ByteStream* stream);
bool Restore(ByteStream* stream);
bool is_paused() const { return paused_; }
void Pause();
void Resume();
protected:
explicit AudioSystem(cpu::Processor* processor);
virtual void Initialize();
void WorkerThreadMain();
virtual X_STATUS CreateDriver(size_t index,
xe::threading::Semaphore* semaphore,
AudioDriver** out_driver) = 0;
virtual void DestroyDriver(AudioDriver* driver) = 0;
// TODO(gibbed): respect XAUDIO2_MAX_QUEUED_BUFFERS somehow (ie min(64,
// XAUDIO2_MAX_QUEUED_BUFFERS))
static const size_t kMaximumQueuedFrames = 64;
Memory* memory_ = nullptr;
cpu::Processor* processor_ = nullptr;
std::unique_ptr xma_decoder_;
std::atomic worker_running_ = {false};
kernel::object_ref worker_thread_;
xe::global_critical_region global_critical_region_;
static const size_t kMaximumClientCount = 8;
struct {
AudioDriver* driver;
uint32_t callback;
uint32_t callback_arg;
uint32_t wrapped_callback_arg;
bool in_use;
} clients_[kMaximumClientCount];
int FindFreeClient();
std::unique_ptr
client_semaphores_[kMaximumClientCount];
// Event is always there in case we have no clients.
std::unique_ptr shutdown_event_;
xe::threading::WaitHandle* wait_handles_[kMaximumClientCount + 1];
bool paused_ = false;
threading::Fence pause_fence_;
std::unique_ptr resume_event_;
};
} // namespace apu
} // namespace xe
#endif // XENIA_APU_AUDIO_SYSTEM_H_
================================================
FILE: src/xenia/apu/conversion.h
================================================
/**
******************************************************************************
* 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. *
******************************************************************************
*/
#ifndef XENIA_APU_CONVERSION_H_
#define XENIA_APU_CONVERSION_H_
#include
#include "xenia/base/byte_order.h"
#include "xenia/base/platform.h"
namespace xe {
namespace apu {
namespace conversion {
#if XE_ARCH_AMD64
inline void sequential_6_BE_to_interleaved_6_LE(float* output,
const float* input,
size_t ch_sample_count) {
const uint32_t* in = reinterpret_cast(input);
uint32_t* out = reinterpret_cast(output);
const __m128i byte_swap_shuffle =
_mm_set_epi8(12, 13, 14, 15, 8, 9, 10, 11, 4, 5, 6, 7, 0, 1, 2, 3);
for (size_t sample = 0; sample < ch_sample_count; sample++) {
__m128i sample0 = _mm_set_epi32(
in[3 * ch_sample_count + sample], in[2 * ch_sample_count + sample],
in[1 * ch_sample_count + sample], in[0 * ch_sample_count + sample]);
uint32_t sample1 = in[4 * ch_sample_count + sample];
uint32_t sample2 = in[5 * ch_sample_count + sample];
sample0 = _mm_shuffle_epi8(sample0, byte_swap_shuffle);
_mm_storeu_si128(reinterpret_cast<__m128i*>(&out[sample * 6]), sample0);
sample1 = xe::byte_swap(sample1);
out[sample * 6 + 4] = sample1;
sample2 = xe::byte_swap(sample2);
out[sample * 6 + 5] = sample2;
}
}
inline void sequential_6_BE_to_interleaved_2_LE(float* output,
const float* input,
size_t ch_sample_count) {
assert_true(ch_sample_count % 4 == 0);
const uint32_t* in = reinterpret_cast(input);
uint32_t* out = reinterpret_cast(output);
const __m128i byte_swap_shuffle =
_mm_set_epi8(12, 13, 14, 15, 8, 9, 10, 11, 4, 5, 6, 7, 0, 1, 2, 3);
const __m128 half = _mm_set1_ps(0.5f);
const __m128 two_fifths = _mm_set1_ps(1.0f / 2.5f);
// put center on left and right, discard low frequency
for (size_t sample = 0; sample < ch_sample_count; sample += 4) {
// load 4 samples from 6 channels each
__m128 fl = _mm_loadu_ps(&input[0 * ch_sample_count + sample]);
__m128 fr = _mm_loadu_ps(&input[1 * ch_sample_count + sample]);
__m128 fc = _mm_loadu_ps(&input[2 * ch_sample_count + sample]);
__m128 bl = _mm_loadu_ps(&input[4 * ch_sample_count + sample]);
__m128 br = _mm_loadu_ps(&input[5 * ch_sample_count + sample]);
// byte swap
fl = _mm_castsi128_ps(
_mm_shuffle_epi8(_mm_castps_si128(fl), byte_swap_shuffle));
fr = _mm_castsi128_ps(
_mm_shuffle_epi8(_mm_castps_si128(fr), byte_swap_shuffle));
fc = _mm_castsi128_ps(
_mm_shuffle_epi8(_mm_castps_si128(fc), byte_swap_shuffle));
bl = _mm_castsi128_ps(
_mm_shuffle_epi8(_mm_castps_si128(bl), byte_swap_shuffle));
br = _mm_castsi128_ps(
_mm_shuffle_epi8(_mm_castps_si128(br), byte_swap_shuffle));
__m128 center_halved = _mm_mul_ps(fc, half);
__m128 left = _mm_add_ps(_mm_add_ps(fl, bl), center_halved);
__m128 right = _mm_add_ps(_mm_add_ps(fr, br), center_halved);
left = _mm_mul_ps(left, two_fifths);
right = _mm_mul_ps(right, two_fifths);
_mm_storeu_ps(&output[sample * 2], _mm_unpacklo_ps(left, right));
_mm_storeu_ps(&output[(sample + 2) * 2], _mm_unpackhi_ps(left, right));
}
}
#else
inline void sequential_6_BE_to_interleaved_6_LE(float* output,
const float* input,
size_t ch_sample_count) {
for (size_t sample = 0; sample < ch_sample_count; sample++) {
for (size_t channel = 0; channel < 6; channel++) {
output[sample * 6 + channel] =
xe::byte_swap(input[channel * ch_sample_count + sample]);
}
}
}
inline void sequential_6_BE_to_interleaved_2_LE(float* output,
const float* input,
size_t ch_sample_count) {
// Default 5.1 channel mapping is fl, fr, fc, lf, bl, br
// https://docs.microsoft.com/en-us/windows/win32/xaudio2/xaudio2-default-channel-mapping
for (size_t sample = 0; sample < ch_sample_count; sample++) {
// put center on left and right, discard low frequency
float fl = xe::byte_swap(input[0 * ch_sample_count + sample]);
float fr = xe::byte_swap(input[1 * ch_sample_count + sample]);
float fc = xe::byte_swap(input[2 * ch_sample_count + sample]);
float br = xe::byte_swap(input[4 * ch_sample_count + sample]);
float bl = xe::byte_swap(input[5 * ch_sample_count + sample]);
float center_halved = fc * 0.5f;
output[sample * 2] = (fl + bl + center_halved) * (1.0f / 2.5f);
output[sample * 2 + 1] = (fr + br + center_halved) * (1.0f / 2.5f);
}
}
#endif
} // namespace conversion
} // namespace apu
} // namespace xe
#endif
================================================
FILE: src/xenia/apu/debug_visualizers.natvis
================================================
id={id_}, allocated={is_allocated_}, enabled={is_enabled_}
================================================
FILE: src/xenia/apu/nop/nop_apu_flags.cc
================================================
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2013 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/apu/nop/nop_apu_flags.h"
================================================
FILE: src/xenia/apu/nop/nop_apu_flags.h
================================================
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2013 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_APU_NOP_NOP_APU_FLAGS_H_
#define XENIA_APU_NOP_NOP_APU_FLAGS_H_
#endif // XENIA_APU_NOP_NOP_APU_FLAGS_H_
================================================
FILE: src/xenia/apu/nop/nop_audio_system.cc
================================================
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2013 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/apu/nop/nop_audio_system.h"
#include "xenia/apu/apu_flags.h"
namespace xe {
namespace apu {
namespace nop {
std::unique_ptr NopAudioSystem::Create(cpu::Processor* processor) {
return std::make_unique(processor);
}
NopAudioSystem::NopAudioSystem(cpu::Processor* processor)
: AudioSystem(processor) {}
NopAudioSystem::~NopAudioSystem() = default;
X_STATUS NopAudioSystem::CreateDriver(size_t index,
xe::threading::Semaphore* semaphore,
AudioDriver** out_driver) {
return X_STATUS_NOT_IMPLEMENTED;
}
void NopAudioSystem::DestroyDriver(AudioDriver* driver) { assert_always(); }
} // namespace nop
} // namespace apu
} // namespace xe
================================================
FILE: src/xenia/apu/nop/nop_audio_system.h
================================================
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2013 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_APU_NOP_NOP_AUDIO_SYSTEM_H_
#define XENIA_APU_NOP_NOP_AUDIO_SYSTEM_H_
#include "xenia/apu/audio_system.h"
namespace xe {
namespace apu {
namespace nop {
class NopAudioSystem : public AudioSystem {
public:
explicit NopAudioSystem(cpu::Processor* processor);
~NopAudioSystem() override;
static bool IsAvailable() { return true; }
static std::unique_ptr Create(cpu::Processor* processor);
X_STATUS CreateDriver(size_t index, xe::threading::Semaphore* semaphore,
AudioDriver** out_driver) override;
void DestroyDriver(AudioDriver* driver) override;
};
} // namespace nop
} // namespace apu
} // namespace xe
#endif // XENIA_APU_NOP_NOP_AUDIO_SYSTEM_H_
================================================
FILE: src/xenia/apu/nop/premake5.lua
================================================
project_root = "../../../.."
include(project_root.."/tools/build")
group("src")
project("xenia-apu-nop")
uuid("f37dbf3a-d200-4cc0-83f0-f801b1bdd862")
kind("StaticLib")
language("C++")
links({
"xenia-base",
"xenia-apu",
})
defines({
})
local_platform_files()
================================================
FILE: src/xenia/apu/premake5.lua
================================================
project_root = "../../.."
include(project_root.."/tools/build")
group("src")
project("xenia-apu")
uuid("f4df01f0-50e4-4c67-8f54-61660696cc79")
kind("StaticLib")
language("C++")
links({
"libavcodec",
"libavutil",
"xenia-base",
})
defines({
})
includedirs({
project_root.."/third_party/FFmpeg/",
})
local_platform_files()
================================================
FILE: src/xenia/apu/sdl/premake5.lua
================================================
project_root = "../../../.."
include(project_root.."/tools/build")
group("src")
project("xenia-apu-sdl")
uuid("153b4e8b-813a-40e6-9366-4b51abc73c45")
kind("StaticLib")
language("C++")
links({
"xenia-apu",
"xenia-base",
"xenia-helper-sdl",
"SDL2",
})
defines({
})
local_platform_files()
sdl2_include()
================================================
FILE: src/xenia/apu/sdl/sdl_audio_driver.cc
================================================
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2020 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/apu/sdl/sdl_audio_driver.h"
#include
#include
#include "xenia/apu/apu_flags.h"
#include "xenia/apu/conversion.h"
#include "xenia/base/assert.h"
#include "xenia/base/logging.h"
#include "xenia/base/profiling.h"
#include "xenia/helper/sdl/sdl_helper.h"
namespace xe {
namespace apu {
namespace sdl {
SDLAudioDriver::SDLAudioDriver(Memory* memory,
xe::threading::Semaphore* semaphore)
: AudioDriver(memory), semaphore_(semaphore) {}
SDLAudioDriver::~SDLAudioDriver() {
assert_true(frames_queued_.empty());
assert_true(frames_unused_.empty());
};
bool SDLAudioDriver::Initialize() {
SDL_version ver = {};
SDL_GetVersion(&ver);
if ((ver.major < 2) || (ver.major == 2 && ver.minor == 0 && ver.patch < 8)) {
XELOGW(
"SDL library version {}.{}.{} is outdated. "
"You may experience choppy audio.",
ver.major, ver.minor, ver.patch);
}
if (!xe::helper::sdl::SDLHelper::Prepare()) {
return false;
}
if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) {
return false;
}
sdl_initialized_ = true;
SDL_AudioSpec desired_spec = {};
SDL_AudioSpec obtained_spec;
desired_spec.freq = frame_frequency_;
desired_spec.format = AUDIO_F32;
desired_spec.channels = frame_channels_;
desired_spec.samples = channel_samples_;
desired_spec.callback = SDLCallback;
desired_spec.userdata = this;
// Allow the hardware to decide between 5.1 and stereo
int allowed_change = SDL_AUDIO_ALLOW_CHANNELS_CHANGE;
for (int i = 0; i < 2; i++) {
sdl_device_id_ = SDL_OpenAudioDevice(nullptr, 0, &desired_spec,
&obtained_spec, allowed_change);
if (sdl_device_id_ <= 0) {
XELOGE("SDL_OpenAudioDevice() failed.");
return false;
}
if (obtained_spec.channels == 2 || obtained_spec.channels == 6) {
break;
}
// If the system is 4 or 7.1, let SDL convert
allowed_change = 0;
SDL_CloseAudioDevice(sdl_device_id_);
sdl_device_id_ = -1;
}
if (sdl_device_id_ <= 0) {
XELOGE("Failed to get a compatible SDL Audio Device.");
return false;
}
sdl_device_channels_ = obtained_spec.channels;
SDL_PauseAudioDevice(sdl_device_id_, 0);
return true;
}
void SDLAudioDriver::SubmitFrame(uint32_t frame_ptr) {
const auto input_frame = memory_->TranslateVirtual(frame_ptr);
float* output_frame;
{
std::unique_lock guard(frames_mutex_);
if (frames_unused_.empty()) {
output_frame = new float[frame_samples_];
} else {
output_frame = frames_unused_.top();
frames_unused_.pop();
}
}
std::memcpy(output_frame, input_frame, frame_samples_ * sizeof(float));
{
std::unique_lock guard(frames_mutex_);
frames_queued_.push(output_frame);
}
}
void SDLAudioDriver::Shutdown() {
if (sdl_device_id_ > 0) {
SDL_CloseAudioDevice(sdl_device_id_);
sdl_device_id_ = -1;
}
if (sdl_initialized_) {
SDL_QuitSubSystem(SDL_INIT_AUDIO);
sdl_initialized_ = false;
}
std::unique_lock guard(frames_mutex_);
while (!frames_unused_.empty()) {
delete[] frames_unused_.top();
frames_unused_.pop();
};
while (!frames_queued_.empty()) {
delete[] frames_queued_.front();
frames_queued_.pop();
};
}
void SDLAudioDriver::SDLCallback(void* userdata, Uint8* stream, int len) {
SCOPE_profile_cpu_f("apu");
if (!userdata || !stream) {
XELOGE("SDLAudioDriver::sdl_callback called with nullptr.");
return;
}
const auto driver = static_cast(userdata);
assert_true(len ==
sizeof(float) * channel_samples_ * driver->sdl_device_channels_);
std::unique_lock guard(driver->frames_mutex_);
if (driver->frames_queued_.empty()) {
std::memset(stream, 0, len);
} else {
auto buffer = driver->frames_queued_.front();
driver->frames_queued_.pop();
if (cvars::mute) {
std::memset(stream, 0, len);
} else {
switch (driver->sdl_device_channels_) {
case 2:
conversion::sequential_6_BE_to_interleaved_2_LE(
reinterpret_cast(stream), buffer, channel_samples_);
break;
case 6:
conversion::sequential_6_BE_to_interleaved_6_LE(
reinterpret_cast(stream), buffer, channel_samples_);
break;
default:
assert_unhandled_case(driver->sdl_device_channels_);
break;
}
}
driver->frames_unused_.push(buffer);
auto ret = driver->semaphore_->Release(1, nullptr);
assert_true(ret);
}
};
} // namespace sdl
} // namespace apu
} // namespace xe
================================================
FILE: src/xenia/apu/sdl/sdl_audio_driver.h
================================================
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2020 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_APU_SDL_SDL_AUDIO_DRIVER_H_
#define XENIA_APU_SDL_SDL_AUDIO_DRIVER_H_
#include
#include
#include
#include "SDL.h"
#include "xenia/apu/audio_driver.h"
#include "xenia/base/threading.h"
namespace xe {
namespace apu {
namespace sdl {
class SDLAudioDriver : public AudioDriver {
public:
SDLAudioDriver(Memory* memory, xe::threading::Semaphore* semaphore);
~SDLAudioDriver() override;
bool Initialize();
void SubmitFrame(uint32_t frame_ptr) override;
void Shutdown();
protected:
static void SDLCallback(void* userdata, Uint8* stream, int len);
xe::threading::Semaphore* semaphore_ = nullptr;
SDL_AudioDeviceID sdl_device_id_ = -1;
bool sdl_initialized_ = false;
uint8_t sdl_device_channels_ = 0;
static const uint32_t frame_frequency_ = 48000;
static const uint32_t frame_channels_ = 6;
static const uint32_t channel_samples_ = 256;
static const uint32_t frame_samples_ = frame_channels_ * channel_samples_;
static const uint32_t frame_size_ = sizeof(float) * frame_samples_;
std::queue frames_queued_ = {};
std::stack frames_unused_ = {};
std::mutex frames_mutex_ = {};
};
} // namespace sdl
} // namespace apu
} // namespace xe
#endif // XENIA_APU_SDL_SDL_AUDIO_DRIVER_H_
================================================
FILE: src/xenia/apu/sdl/sdl_audio_system.cc
================================================
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2020 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/apu/sdl/sdl_audio_system.h"
#include "xenia/apu/apu_flags.h"
#include "xenia/apu/sdl/sdl_audio_driver.h"
namespace xe {
namespace apu {
namespace sdl {
std::unique_ptr SDLAudioSystem::Create(cpu::Processor* processor) {
return std::make_unique(processor);
}
SDLAudioSystem::SDLAudioSystem(cpu::Processor* processor)
: AudioSystem(processor) {}
SDLAudioSystem::~SDLAudioSystem() {}
void SDLAudioSystem::Initialize() { AudioSystem::Initialize(); }
X_STATUS SDLAudioSystem::CreateDriver(size_t index,
xe::threading::Semaphore* semaphore,
AudioDriver** out_driver) {
assert_not_null(out_driver);
auto driver = new SDLAudioDriver(memory_, semaphore);
if (!driver->Initialize()) {
driver->Shutdown();
return X_STATUS_UNSUCCESSFUL;
}
*out_driver = driver;
return X_STATUS_SUCCESS;
}
void SDLAudioSystem::DestroyDriver(AudioDriver* driver) {
assert_not_null(driver);
auto sdldriver = dynamic_cast(driver);
assert_not_null(sdldriver);
sdldriver->Shutdown();
delete sdldriver;
}
} // namespace sdl
} // namespace apu
} // namespace xe
================================================
FILE: src/xenia/apu/sdl/sdl_audio_system.h
================================================
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2020 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_APU_SDL_SDL_AUDIO_SYSTEM_H_
#define XENIA_APU_SDL_SDL_AUDIO_SYSTEM_H_
#include "xenia/apu/audio_system.h"
namespace xe {
namespace apu {
namespace sdl {
class SDLAudioSystem : public AudioSystem {
public:
explicit SDLAudioSystem(cpu::Processor* processor);
~SDLAudioSystem() override;
static bool IsAvailable() { return true; }
static std::unique_ptr Create(cpu::Processor* processor);
X_RESULT CreateDriver(size_t index, xe::threading::Semaphore* semaphore,
AudioDriver** out_driver) override;
void DestroyDriver(AudioDriver* driver) override;
protected:
void Initialize() override;
};
} // namespace sdl
} // namespace apu
} // namespace xe
#endif // XENIA_APU_SDL_SDL_AUDIO_SYSTEM_H_
================================================
FILE: src/xenia/apu/xaudio2/premake5.lua
================================================
project_root = "../../../.."
include(project_root.."/tools/build")
group("src")
project("xenia-apu-xaudio2")
uuid("7a54a497-24d9-4c0e-a013-8507a04231f9")
kind("StaticLib")
language("C++")
links({
"xenia-base",
"xenia-apu",
})
defines({
})
local_platform_files()
================================================
FILE: src/xenia/apu/xaudio2/xaudio2_api.h
================================================
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2019 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_APU_XAUDIO2_XAUDIO2_API_H_
#define XENIA_APU_XAUDIO2_XAUDIO2_API_H_
#include
#include "xenia/base/platform_win.h"
namespace xe {
namespace apu {
namespace xaudio2 {
namespace api {
// Parts of XAudio2.h needed for Xenia. The header in the Windows SDK (for 2.8+)
// or the DirectX SDK (for 2.7 and below) is for a single version that is chosen
// for the target OS version at compile time, and cannot be useful for different
// DLL versions.
//
// XAudio 2.8 is also not available on Windows 7, but including the 2.7 header
// is complicated because it has the same name and include guard as the 2.8 one,
// and also 2.7 is outdated - support already dropped in Microsoft Store apps,
// for instance.
//
// Copyright (c) Microsoft Corporation. All rights reserved.
class __declspec(uuid("5a508685-a254-4fba-9b82-9a24b00306af")) XAudio2_7;
interface __declspec(uuid("8bcf1f58-9fe7-4583-8ac6-e2adc465c8bb")) IXAudio2_7;
interface __declspec(uuid("60d8dac8-5aa1-4e8e-b597-2f5e2883d484")) IXAudio2_8;
#pragma pack(push, 1)
static constexpr UINT32 XE_XAUDIO2_MAX_QUEUED_BUFFERS = 64;
static constexpr float XE_XAUDIO2_MAX_FREQ_RATIO = 1024.0f;
static constexpr float XE_XAUDIO2_DEFAULT_FREQ_RATIO = 2.0f;
static constexpr UINT32 XE_XAUDIO2_COMMIT_NOW = 0;
static constexpr UINT32 XE_XAUDIO2_NO_LOOP_REGION = 0;
static constexpr UINT32 XE_XAUDIO2_DEFAULT_CHANNELS = 0;
static constexpr UINT32 XE_XAUDIO2_DEFAULT_SAMPLERATE = 0;
// 2.8+ only.
static constexpr UINT32 XE_XAUDIO2_VOICE_NOSAMPLESPLAYED = 0x0100;
interface IXAudio2_7;
interface IXAudio2_8;
interface IXAudio2Voice;
interface IXAudio2_7SourceVoice;
interface IXAudio2_8SourceVoice;
interface IXAudio2SubmixVoice;
interface IXAudio2_7MasteringVoice;
interface IXAudio2_8MasteringVoice;
interface IXAudio2EngineCallback;
interface IXAudio2VoiceCallback;
typedef UINT32 XAUDIO2_PROCESSOR;
static constexpr XAUDIO2_PROCESSOR XE_XAUDIO2_ANY_PROCESSOR = 0xFFFFFFFF;
static constexpr XAUDIO2_PROCESSOR XE_XAUDIO2_7_DEFAULT_PROCESSOR =
XE_XAUDIO2_ANY_PROCESSOR;
static constexpr XAUDIO2_PROCESSOR XE_XAUDIO2_8_DEFAULT_PROCESSOR = 0x00000001;
// 2.7 only.
struct XAUDIO2_DEVICE_DETAILS;
struct XAUDIO2_VOICE_DETAILS;
struct XAUDIO2_VOICE_SENDS;
struct XAUDIO2_EFFECT_CHAIN;
struct XAUDIO2_FILTER_PARAMETERS;
struct XAUDIO2_BUFFER {
UINT32 Flags;
UINT32 AudioBytes;
const BYTE* pAudioData;
UINT32 PlayBegin;
UINT32 PlayLength;
UINT32 LoopBegin;
UINT32 LoopLength;
UINT32 LoopCount;
void* pContext;
};
struct XAUDIO2_BUFFER_WMA;
struct XAUDIO2_VOICE_STATE {
void* pCurrentBufferContext;
UINT32 BuffersQueued;
UINT64 SamplesPlayed;
};
struct XAUDIO2_PERFORMANCE_DATA;
struct XAUDIO2_DEBUG_CONFIGURATION {
UINT32 TraceMask;
UINT32 BreakMask;
BOOL LogThreadID;
BOOL LogFileline;
BOOL LogFunctionName;
BOOL LogTiming;
};
static constexpr UINT32 XE_XAUDIO2_LOG_ERRORS = 0x0001;
static constexpr UINT32 XE_XAUDIO2_LOG_WARNINGS = 0x0002;
// clang-format off
// IXAudio2 2.7.
DECLARE_INTERFACE_(IXAudio2_7, IUnknown) {
STDMETHOD(QueryInterface)(REFIID riid, void** ppvInterface) = 0;
STDMETHOD_(ULONG, AddRef)() = 0;
STDMETHOD_(ULONG, Release)() = 0;
// 2.7 only.
STDMETHOD(GetDeviceCount)(UINT32* pCount) = 0;
// 2.7 only.
STDMETHOD(GetDeviceDetails)(UINT32 Index,
XAUDIO2_DEVICE_DETAILS* pDeviceDetails) = 0;
// 2.7 only.
STDMETHOD(Initialize)(
UINT32 Flags = 0,
XAUDIO2_PROCESSOR XAudio2Processor = XE_XAUDIO2_7_DEFAULT_PROCESSOR) = 0;
STDMETHOD(RegisterForCallbacks)(IXAudio2EngineCallback* pCallback) = 0;
STDMETHOD_(void, UnregisterForCallbacks)(
IXAudio2EngineCallback* pCallback) = 0;
STDMETHOD(CreateSourceVoice)(
IXAudio2_7SourceVoice** ppSourceVoice, const WAVEFORMATEX* pSourceFormat,
UINT32 Flags = 0, float MaxFrequencyRatio = XE_XAUDIO2_DEFAULT_FREQ_RATIO,
IXAudio2VoiceCallback* pCallback = nullptr,
const XAUDIO2_VOICE_SENDS* pSendList = nullptr,
const XAUDIO2_EFFECT_CHAIN* pEffectChain = nullptr) = 0;
STDMETHOD(CreateSubmixVoice)(
IXAudio2SubmixVoice** ppSubmixVoice, UINT32 InputChannels,
UINT32 InputSampleRate, UINT32 Flags = 0, UINT32 ProcessingStage = 0,
const XAUDIO2_VOICE_SENDS* pSendList = nullptr,
const XAUDIO2_EFFECT_CHAIN* pEffectChain = nullptr) = 0;
// 2.7: Device index instead of device ID, no stream category.
STDMETHOD(CreateMasteringVoice)(
IXAudio2_7MasteringVoice** ppMasteringVoice,
UINT32 InputChannels = XE_XAUDIO2_DEFAULT_CHANNELS,
UINT32 InputSampleRate = XE_XAUDIO2_DEFAULT_SAMPLERATE, UINT32 Flags = 0,
UINT32 DeviceIndex = 0,
const XAUDIO2_EFFECT_CHAIN* pEffectChain = nullptr) = 0;
STDMETHOD(StartEngine)() = 0;
STDMETHOD_(void, StopEngine)() = 0;
STDMETHOD(CommitChanges)(UINT32 OperationSet) = 0;
STDMETHOD_(void, GetPerformanceData)(XAUDIO2_PERFORMANCE_DATA* pPerfData) = 0;
STDMETHOD_(void, SetDebugConfiguration)(
const XAUDIO2_DEBUG_CONFIGURATION* pDebugConfiguration,
void* pReserved = nullptr) = 0;
};
// IXAudio2 2.8.
DECLARE_INTERFACE_(IXAudio2_8, IUnknown) {
STDMETHOD(QueryInterface)(REFIID riid, void** ppvInterface) = 0;
STDMETHOD_(ULONG, AddRef)() = 0;
STDMETHOD_(ULONG, Release)() = 0;
// 2.8: No GetDeviceCount, GetDeviceDetails and Initialize.
STDMETHOD(RegisterForCallbacks)(IXAudio2EngineCallback* pCallback) = 0;
STDMETHOD_(void, UnregisterForCallbacks)(
IXAudio2EngineCallback* pCallback) = 0;
STDMETHOD(CreateSourceVoice)(
IXAudio2_8SourceVoice** ppSourceVoice, const WAVEFORMATEX* pSourceFormat,
UINT32 Flags = 0, float MaxFrequencyRatio = XE_XAUDIO2_DEFAULT_FREQ_RATIO,
IXAudio2VoiceCallback* pCallback = nullptr,
const XAUDIO2_VOICE_SENDS* pSendList = nullptr,
const XAUDIO2_EFFECT_CHAIN* pEffectChain = nullptr) = 0;
STDMETHOD(CreateSubmixVoice)(
IXAudio2SubmixVoice** ppSubmixVoice, UINT32 InputChannels,
UINT32 InputSampleRate, UINT32 Flags = 0, UINT32 ProcessingStage = 0,
const XAUDIO2_VOICE_SENDS* pSendList = nullptr,
const XAUDIO2_EFFECT_CHAIN* pEffectChain = nullptr) = 0;
// 2.8: Device ID instead of device index, added stream category.
STDMETHOD(CreateMasteringVoice)(
IXAudio2_8MasteringVoice** ppMasteringVoice,
UINT32 InputChannels = XE_XAUDIO2_DEFAULT_CHANNELS,
UINT32 InputSampleRate = XE_XAUDIO2_DEFAULT_SAMPLERATE, UINT32 Flags = 0,
LPCWSTR szDeviceId = nullptr,
const XAUDIO2_EFFECT_CHAIN* pEffectChain = nullptr,
AUDIO_STREAM_CATEGORY StreamCategory = AudioCategory_GameEffects) = 0;
STDMETHOD(StartEngine)() = 0;
STDMETHOD_(void, StopEngine)() = 0;
STDMETHOD(CommitChanges)(UINT32 OperationSet) = 0;
STDMETHOD_(void, GetPerformanceData)(XAUDIO2_PERFORMANCE_DATA* pPerfData) = 0;
STDMETHOD_(void, SetDebugConfiguration)(
const XAUDIO2_DEBUG_CONFIGURATION* pDebugConfiguration,
void* pReserved = nullptr) = 0;
};
DECLARE_INTERFACE(IXAudio2Voice) {
#define XE_APU_XAUDIO2_API_DECLARE_IXAUDIO2VOICE_METHODS \
STDMETHOD_(void, GetVoiceDetails)(XAUDIO2_VOICE_DETAILS* pVoiceDetails) = 0; \
STDMETHOD(SetOutputVoices)(const XAUDIO2_VOICE_SENDS* pSendList) = 0; \
STDMETHOD(SetEffectChain)(const XAUDIO2_EFFECT_CHAIN* pEffectChain) = 0; \
STDMETHOD(EnableEffect)(UINT32 EffectIndex, \
UINT32 OperationSet = XE_XAUDIO2_COMMIT_NOW) = 0; \
STDMETHOD(DisableEffect)(UINT32 EffectIndex, \
UINT32 OperationSet = XE_XAUDIO2_COMMIT_NOW) = 0; \
STDMETHOD_(void, GetEffectState)(UINT32 EffectIndex, BOOL* pEnabled) = 0; \
STDMETHOD(SetEffectParameters)( \
UINT32 EffectIndex, const void* pParameters, UINT32 ParametersByteSize, \
UINT32 OperationSet = XE_XAUDIO2_COMMIT_NOW) = 0; \
STDMETHOD(GetEffectParameters)(UINT32 EffectIndex, void* pParameters, \
UINT32 ParametersByteSize) = 0; \
STDMETHOD(SetFilterParameters)( \
const XAUDIO2_FILTER_PARAMETERS* pParameters, \
UINT32 OperationSet = XE_XAUDIO2_COMMIT_NOW) = 0; \
STDMETHOD_(void, GetFilterParameters)( \
XAUDIO2_FILTER_PARAMETERS* pParameters) = 0; \
STDMETHOD(SetOutputFilterParameters)( \
IXAudio2Voice* pDestinationVoice, \
const XAUDIO2_FILTER_PARAMETERS* pParameters, \
UINT32 OperationSet = XE_XAUDIO2_COMMIT_NOW) = 0; \
STDMETHOD_(void, GetOutputFilterParameters)( \
IXAudio2Voice* pDestinationVoice, \
XAUDIO2_FILTER_PARAMETERS* pParameters) = 0; \
STDMETHOD(SetVolume)(float Volume, \
UINT32 OperationSet = XE_XAUDIO2_COMMIT_NOW) = 0; \
STDMETHOD_(void, GetVolume)(float* pVolume) = 0; \
STDMETHOD(SetChannelVolumes)( \
UINT32 Channels, const float* pVolumes, \
UINT32 OperationSet = XE_XAUDIO2_COMMIT_NOW) = 0; \
STDMETHOD_(void, GetChannelVolumes)(UINT32 Channels, float* pVolumes) = 0; \
STDMETHOD(SetOutputMatrix)(IXAudio2Voice* pDestinationVoice, \
UINT32 SourceChannels, \
UINT32 DestinationChannels, \
const float* pLevelMatrix, \
UINT32 OperationSet = XE_XAUDIO2_COMMIT_NOW) = 0; \
STDMETHOD_(void, GetOutputMatrix)(IXAudio2Voice* pDestinationVoice, \
UINT32 SourceChannels, \
UINT32 DestinationChannels, \
float* pLevelMatrix) = 0; \
STDMETHOD_(void, DestroyVoice)() = 0;
XE_APU_XAUDIO2_API_DECLARE_IXAUDIO2VOICE_METHODS
};
// IXAudio2SourceVoice 2.7.
DECLARE_INTERFACE_(IXAudio2_7SourceVoice, IXAudio2Voice) {
XE_APU_XAUDIO2_API_DECLARE_IXAUDIO2VOICE_METHODS
STDMETHOD(Start)(UINT32 Flags = 0,
UINT32 OperationSet = XE_XAUDIO2_COMMIT_NOW) = 0;
STDMETHOD(Stop)(UINT32 Flags = 0,
UINT32 OperationSet = XE_XAUDIO2_COMMIT_NOW) = 0;
STDMETHOD(SubmitSourceBuffer)(
const XAUDIO2_BUFFER* pBuffer,
const XAUDIO2_BUFFER_WMA* pBufferWMA = nullptr) = 0;
STDMETHOD(FlushSourceBuffers)() = 0;
STDMETHOD(Discontinuity)() = 0;
STDMETHOD(ExitLoop)(UINT32 OperationSet = XE_XAUDIO2_COMMIT_NOW) = 0;
// 2.7: No Flags.
STDMETHOD_(void, GetState)(XAUDIO2_VOICE_STATE* pVoiceState) = 0;
STDMETHOD(SetFrequencyRatio)(float Ratio,
UINT32 OperationSet = XE_XAUDIO2_COMMIT_NOW) = 0;
STDMETHOD_(void, GetFrequencyRatio)(float* pRatio) = 0;
STDMETHOD(SetSourceSampleRate)(UINT32 NewSourceSampleRate) = 0;
};
// IXAudio2SourceVoice 2.8.
DECLARE_INTERFACE_(IXAudio2_8SourceVoice, IXAudio2Voice) {
XE_APU_XAUDIO2_API_DECLARE_IXAUDIO2VOICE_METHODS
STDMETHOD(Start)(UINT32 Flags = 0,
UINT32 OperationSet = XE_XAUDIO2_COMMIT_NOW) = 0;
STDMETHOD(Stop)(UINT32 Flags = 0,
UINT32 OperationSet = XE_XAUDIO2_COMMIT_NOW) = 0;
STDMETHOD(SubmitSourceBuffer)(
const XAUDIO2_BUFFER* pBuffer,
const XAUDIO2_BUFFER_WMA* pBufferWMA = nullptr) = 0;
STDMETHOD(FlushSourceBuffers)() = 0;
STDMETHOD(Discontinuity)() = 0;
STDMETHOD(ExitLoop)(UINT32 OperationSet = XE_XAUDIO2_COMMIT_NOW) = 0;
// 2.8: Flags added.
STDMETHOD_(void, GetState)(XAUDIO2_VOICE_STATE* pVoiceState,
UINT32 Flags = 0) = 0;
STDMETHOD(SetFrequencyRatio)(float Ratio,
UINT32 OperationSet = XE_XAUDIO2_COMMIT_NOW) = 0;
STDMETHOD_(void, GetFrequencyRatio)(float* pRatio) = 0;
STDMETHOD(SetSourceSampleRate)(UINT32 NewSourceSampleRate) = 0;
};
// IXAudio2MasteringVoice 2.7.
DECLARE_INTERFACE_(IXAudio2_7MasteringVoice, IXAudio2Voice) {
XE_APU_XAUDIO2_API_DECLARE_IXAUDIO2VOICE_METHODS
// 2.7: No GetChannelMask.
};
// IXAudio2MasteringVoice 2.8.
DECLARE_INTERFACE_(IXAudio2_8MasteringVoice, IXAudio2Voice) {
XE_APU_XAUDIO2_API_DECLARE_IXAUDIO2VOICE_METHODS
// 2.8: GetChannelMask added.
STDMETHOD(GetChannelMask)(DWORD* pChannelmask) = 0;
};
DECLARE_INTERFACE(IXAudio2VoiceCallback) {
// Called just before this voice's processing pass begins.
STDMETHOD_(void, OnVoiceProcessingPassStart)(UINT32 BytesRequired) = 0;
STDMETHOD_(void, OnVoiceProcessingPassEnd)() = 0;
STDMETHOD_(void, OnStreamEnd)() = 0;
STDMETHOD_(void, OnBufferStart)(void* pBufferContext) = 0;
STDMETHOD_(void, OnBufferEnd)(void* pBufferContext) = 0;
STDMETHOD_(void, OnLoopEnd)(void* pBufferContext) = 0;
STDMETHOD_(void, OnVoiceError)(void* pBufferContext, HRESULT Error) = 0;
};
#pragma pack(pop)
} // namespace api
} // namespace xaudio2
} // namespace apu
} // namespace xe
#endif // XENIA_APU_XAUDIO2_XAUDIO2_API_H_
================================================
FILE: src/xenia/apu/xaudio2/xaudio2_apu_flags.cc
================================================
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2013 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/apu/xaudio2/xaudio2_apu_flags.h"
================================================
FILE: src/xenia/apu/xaudio2/xaudio2_apu_flags.h
================================================
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2013 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_APU_XAUDIO2_XAUDIO2_APU_FLAGS_H_
#define XENIA_APU_XAUDIO2_XAUDIO2_APU_FLAGS_H_
#endif // XENIA_APU_XAUDIO2_XAUDIO2_APU_FLAGS_H_
================================================
FILE: src/xenia/apu/xaudio2/xaudio2_audio_driver.cc
================================================
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2020 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/apu/xaudio2/xaudio2_audio_driver.h"
#include "xenia/apu/apu_flags.h"
#include "xenia/apu/conversion.h"
#include "xenia/apu/xaudio2/xaudio2_api.h"
#include "xenia/base/assert.h"
#include "xenia/base/clock.h"
#include "xenia/base/logging.h"
#include "xenia/base/platform_win.h"
#include "xenia/base/threading.h"
namespace xe {
namespace apu {
namespace xaudio2 {
class XAudio2AudioDriver::VoiceCallback : public api::IXAudio2VoiceCallback {
public:
explicit VoiceCallback(xe::threading::Semaphore* semaphore)
: semaphore_(semaphore) {}
~VoiceCallback() {}
void OnStreamEnd() {}
void OnVoiceProcessingPassEnd() {}
void OnVoiceProcessingPassStart(uint32_t samples_required) {}
void OnBufferEnd(void* context) {
auto ret = semaphore_->Release(1, nullptr);
assert_true(ret);
}
void OnBufferStart(void* context) {}
void OnLoopEnd(void* context) {}
void OnVoiceError(void* context, HRESULT result) {}
private:
xe::threading::Semaphore* semaphore_ = nullptr;
};
XAudio2AudioDriver::XAudio2AudioDriver(Memory* memory,
xe::threading::Semaphore* semaphore)
: AudioDriver(memory), semaphore_(semaphore) {}
XAudio2AudioDriver::~XAudio2AudioDriver() = default;
bool XAudio2AudioDriver::Initialize() {
voice_callback_ = new VoiceCallback(semaphore_);
// Load the XAudio2 DLL dynamically. Needed both for 2.7 and for
// differentiating between 2.8 and later versions. Windows 8.1 SDK references
// XAudio2_8.dll in xaudio2.lib, which is available on Windows 8.1, however,
// Windows 10 SDK references XAudio2_9.dll in it, which is only available in
// Windows 10, and XAudio2_8.dll is linked through a different .lib -
// xaudio2_8.lib, so easier not to link the .lib at all.
xaudio2_module_ = static_cast(LoadLibraryW(L"XAudio2_8.dll"));
if (xaudio2_module_) {
xaudio2_create_ = reinterpret_cast(
GetProcAddress(static_cast(xaudio2_module_), "XAudio2Create"));
if (xaudio2_create_) {
api_minor_version_ = 8;
} else {
XELOGE("XAudio2Create not found in XAudio2_8.dll");
FreeLibrary(static_cast(xaudio2_module_));
xaudio2_module_ = nullptr;
}
}
if (!xaudio2_module_) {
xaudio2_module_ = static_cast(LoadLibraryW(L"XAudio2_7.dll"));
if (xaudio2_module_) {
api_minor_version_ = 7;
} else {
XELOGE("Failed to load XAudio 2.8 or 2.7 library DLL");
return false;
}
}
// We need to be able to accept frames from any non-STA thread - primarily
// from any guest thread, so MTA needs to be used. The AudioDriver, however,
// may be initialized from the UI thread, which has the STA concurrency model,
// or from another thread regardless of its concurrency model. So, all
// management of the objects needs to be performed in MTA. Launch the
// lifecycle management thread, which will handle initialization and shutdown,
// and also provide a scope for implicit MTA in threads that have never
// initialized COM explicitly, which lasts until all threads that have
// initialized MTA explicitly have uninitialized it - the thread that holds
// the MTA scope needs to be running while other threads are able to submit
// frames as they might have not initialized MTA explicitly.
// https://devblogs.microsoft.com/oldnewthing/?p=4613
// This is needed for both XAudio 2.7 (created via explicit CoCreateInstance)
// and 2.8 (using XAudio2Create, but still requiring CoInitializeEx).
// https://docs.microsoft.com/en-us/windows/win32/xaudio2/how-to--initialize-xaudio2
assert_false(mta_thread_.joinable());
mta_thread_initialization_attempt_completed_ = false;
mta_thread_shutdown_requested_ = false;
mta_thread_ = std::thread(&XAudio2AudioDriver::MTAThread, this);
{
std::unique_lock mta_thread_initialization_completion_lock(
mta_thread_initialization_completion_mutex_);
while (true) {
if (mta_thread_initialization_attempt_completed_) {
break;
}
mta_thread_initialization_completion_cond_.wait(
mta_thread_initialization_completion_lock);
}
}
if (!mta_thread_initialization_completion_result_) {
mta_thread_.join();
return false;
}
return true;
}
template
bool XAudio2AudioDriver::InitializeObjects(Objects& objects) {
HRESULT hr;
api::XAUDIO2_DEBUG_CONFIGURATION config;
config.TraceMask = api::XE_XAUDIO2_LOG_ERRORS | api::XE_XAUDIO2_LOG_WARNINGS;
config.BreakMask = 0;
config.LogThreadID = FALSE;
config.LogTiming = TRUE;
config.LogFunctionName = TRUE;
config.LogFileline = TRUE;
objects.audio->SetDebugConfiguration(&config);
hr = objects.audio->CreateMasteringVoice(&objects.mastering_voice);
if (FAILED(hr)) {
XELOGE("IXAudio2::CreateMasteringVoice failed with 0x{:08X}", hr);
ShutdownObjects(objects);
return false;
}
WAVEFORMATIEEEFLOATEX waveformat;
waveformat.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
waveformat.Format.nChannels = frame_channels_;
waveformat.Format.nSamplesPerSec = 48000;
waveformat.Format.wBitsPerSample = 32;
waveformat.Format.nBlockAlign =
(waveformat.Format.nChannels * waveformat.Format.wBitsPerSample) / 8;
waveformat.Format.nAvgBytesPerSec =
waveformat.Format.nSamplesPerSec * waveformat.Format.nBlockAlign;
waveformat.Format.cbSize =
sizeof(WAVEFORMATIEEEFLOATEX) - sizeof(WAVEFORMATEX);
waveformat.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
waveformat.Samples.wValidBitsPerSample = waveformat.Format.wBitsPerSample;
static const DWORD kChannelMasks[] = {
0,
0,
SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT,
0,
0,
0,
SPEAKER_FRONT_LEFT | SPEAKER_FRONT_CENTER | SPEAKER_FRONT_RIGHT |
SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT,
0,
};
waveformat.dwChannelMask = kChannelMasks[waveformat.Format.nChannels];
hr = objects.audio->CreateSourceVoice(
&objects.pcm_voice, &waveformat.Format,
0, // api::XE_XAUDIO2_VOICE_NOSRC | api::XE_XAUDIO2_VOICE_NOPITCH,
api::XE_XAUDIO2_MAX_FREQ_RATIO, voice_callback_);
if (FAILED(hr)) {
XELOGE("IXAudio2::CreateSourceVoice failed with 0x{:08X}", hr);
ShutdownObjects(objects);
return false;
}
hr = objects.pcm_voice->Start();
if (FAILED(hr)) {
XELOGE("IXAudio2SourceVoice::Start failed with 0x{:08X}", hr);
ShutdownObjects(objects);
return false;
}
if (cvars::mute) {
objects.pcm_voice->SetVolume(0.0f);
}
return true;
}
void XAudio2AudioDriver::SubmitFrame(uint32_t frame_ptr) {
// Process samples! They are big-endian floats.
HRESULT hr;
api::XAUDIO2_VOICE_STATE state;
if (api_minor_version_ >= 8) {
objects_.api_2_8.pcm_voice->GetState(&state,
api::XE_XAUDIO2_VOICE_NOSAMPLESPLAYED);
} else {
objects_.api_2_7.pcm_voice->GetState(&state);
}
assert_true(state.BuffersQueued < frame_count_);
auto input_frame = memory_->TranslateVirtual(frame_ptr);
auto output_frame = reinterpret_cast(frames_[current_frame_]);
auto interleave_channels = frame_channels_;
// interleave the data
conversion::sequential_6_BE_to_interleaved_6_LE(output_frame, input_frame,
channel_samples_);
api::XAUDIO2_BUFFER buffer;
buffer.Flags = 0;
buffer.pAudioData = reinterpret_cast(output_frame);
buffer.AudioBytes = frame_size_;
buffer.PlayBegin = 0;
buffer.PlayLength = channel_samples_;
buffer.LoopBegin = api::XE_XAUDIO2_NO_LOOP_REGION;
buffer.LoopLength = 0;
buffer.LoopCount = 0;
buffer.pContext = 0;
if (api_minor_version_ >= 8) {
hr = objects_.api_2_8.pcm_voice->SubmitSourceBuffer(&buffer);
} else {
hr = objects_.api_2_7.pcm_voice->SubmitSourceBuffer(&buffer);
}
if (FAILED(hr)) {
XELOGE("SubmitSourceBuffer failed with 0x{:08X}", hr);
return;
}
current_frame_ = (current_frame_ + 1) % frame_count_;
// Update playback ratio to our time scalar.
// This will keep audio in sync with the game clock.
float frequency_ratio = static_cast(xe::Clock::guest_time_scalar());
if (api_minor_version_ >= 8) {
objects_.api_2_8.pcm_voice->SetFrequencyRatio(frequency_ratio);
} else {
objects_.api_2_7.pcm_voice->SetFrequencyRatio(frequency_ratio);
}
}
void XAudio2AudioDriver::Shutdown() {
// XAudio2 lifecycle is managed by the MTA thread.
if (mta_thread_.joinable()) {
{
std::unique_lock mta_thread_shutdown_request_lock(
mta_thread_shutdown_request_mutex_);
mta_thread_shutdown_requested_ = true;
}
mta_thread_shutdown_request_cond_.notify_all();
mta_thread_.join();
}
xaudio2_create_ = nullptr;
if (xaudio2_module_) {
FreeLibrary(static_cast(xaudio2_module_));
xaudio2_module_ = nullptr;
}
if (voice_callback_) {
delete voice_callback_;
voice_callback_ = nullptr;
}
}
template
void XAudio2AudioDriver::ShutdownObjects(Objects& objects) {
if (objects.pcm_voice) {
objects.pcm_voice->Stop();
objects.pcm_voice->DestroyVoice();
objects.pcm_voice = nullptr;
}
if (objects.mastering_voice) {
objects.mastering_voice->DestroyVoice();
objects.mastering_voice = nullptr;
}
if (objects.audio) {
objects.audio->StopEngine();
objects.audio->Release();
objects.audio = nullptr;
}
}
void XAudio2AudioDriver::MTAThread() {
xe::threading::set_name("XAudio2 MTA");
assert_false(mta_thread_initialization_attempt_completed_);
bool initialized = false;
// Initializing MTA COM in this thread, as well making other (guest) threads
// that don't explicitly call CoInitializeEx implicitly MTA for the period of
// time when they can interact with XAudio through the XAudio2AudioDriver,
// until the CoUninitialize (to be more precise, the CoUninitialize for the
// last remaining MTA thread, but we need implicit MTA for the audio here).
// https://devblogs.microsoft.com/oldnewthing/?p=4613
HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
bool com_initialized = SUCCEEDED(hr);
if (!com_initialized) {
XELOGE("XAudio2 MTA thread CoInitializeEx failed with 0x{:08X}", hr);
} else {
if (api_minor_version_ >= 8) {
hr = xaudio2_create_(&objects_.api_2_8.audio, 0, kProcessor);
if (FAILED(hr)) {
XELOGE("XAudio2Create failed with 0x{:08X}", hr);
} else {
initialized = InitializeObjects(objects_.api_2_8);
}
} else {
hr = CoCreateInstance(__uuidof(api::XAudio2_7), nullptr,
CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(&objects_.api_2_7.audio));
if (FAILED(hr)) {
XELOGE("CoCreateInstance for XAudio2 failed with 0x{:08X}", hr);
} else {
hr = objects_.api_2_7.audio->Initialize(0, kProcessor);
if (FAILED(hr)) {
XELOGE("IXAudio2::Initialize failed with 0x{:08X}", hr);
} else {
initialized = InitializeObjects(objects_.api_2_7);
}
}
}
}
// Notify the threads waiting for the initialization of the result.
mta_thread_initialization_completion_result_ = initialized;
{
std::unique_lock mta_thread_initialization_completion_lock(
mta_thread_initialization_completion_mutex_);
mta_thread_initialization_attempt_completed_ = true;
}
mta_thread_initialization_completion_cond_.notify_all();
if (initialized) {
// Initialized successfully, await a shutdown request while keeping an
// implicit COM MTA scope.
{
std::unique_lock mta_thread_shutdown_request_lock(
mta_thread_shutdown_request_mutex_);
while (true) {
if (mta_thread_shutdown_requested_) {
break;
}
mta_thread_shutdown_request_cond_.wait(
mta_thread_shutdown_request_lock);
}
}
if (api_minor_version_ >= 8) {
ShutdownObjects(objects_.api_2_8);
} else {
ShutdownObjects(objects_.api_2_7);
}
}
if (com_initialized) {
CoUninitialize();
}
}
} // namespace xaudio2
} // namespace apu
} // namespace xe
================================================
FILE: src/xenia/apu/xaudio2/xaudio2_audio_driver.h
================================================
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2013 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_APU_XAUDIO2_XAUDIO2_AUDIO_DRIVER_H_
#define XENIA_APU_XAUDIO2_XAUDIO2_AUDIO_DRIVER_H_
#include
#include
#include
#include "xenia/apu/audio_driver.h"
#include "xenia/apu/xaudio2/xaudio2_api.h"
#include "xenia/base/platform.h"
#include "xenia/base/threading.h"
struct IXAudio2;
struct IXAudio2MasteringVoice;
struct IXAudio2SourceVoice;
namespace xe {
namespace apu {
namespace xaudio2 {
class XAudio2AudioDriver : public AudioDriver {
public:
XAudio2AudioDriver(Memory* memory, xe::threading::Semaphore* semaphore);
~XAudio2AudioDriver() override;
bool Initialize();
// Must not be called from COM STA threads as MTA XAudio2 will be used. It's
// fine to call this from threads that have never initialized COM as
// initializing MTA for any thread implicitly initializes it for all threads
// not explicitly requesting STA (until COM is uninitialized all threads that
// have initialized MTA).
// https://devblogs.microsoft.com/oldnewthing/?p=4613
void SubmitFrame(uint32_t frame_ptr) override;
void Shutdown();
private:
// First CPU (2.8 default). XAUDIO2_ANY_PROCESSOR (2.7 default) steals too
// much time from other things. Ideally should process audio on what roughly
// represents thread 4 (5th) on the Xbox 360 (2.7 default on the console), or
// even beyond the 6 guest cores.
api::XAUDIO2_PROCESSOR kProcessor = 0x00000001;
// InitializeObjects and ShutdownObjects must be called only in the lifecycle
// management thread with COM MTA initialized.
template
bool InitializeObjects(Objects& objects);
template
void ShutdownObjects(Objects& objects);
void MTAThread();
void* xaudio2_module_ = nullptr;
// clang-format off
HRESULT (__stdcall* xaudio2_create_)(
api::IXAudio2_8** xaudio2_out, UINT32 flags,
api::XAUDIO2_PROCESSOR xaudio2_processor) = nullptr;
// clang-format on
uint32_t api_minor_version_ = 7;
bool mta_thread_initialization_completion_result_;
std::mutex mta_thread_initialization_completion_mutex_;
std::condition_variable mta_thread_initialization_completion_cond_;
bool mta_thread_initialization_attempt_completed_;
std::mutex mta_thread_shutdown_request_mutex_;
std::condition_variable mta_thread_shutdown_request_cond_;
bool mta_thread_shutdown_requested_;
std::thread mta_thread_;
union {
struct {
api::IXAudio2_7* audio;
api::IXAudio2_7MasteringVoice* mastering_voice;
api::IXAudio2_7SourceVoice* pcm_voice;
} api_2_7;
struct {
api::IXAudio2_8* audio;
api::IXAudio2_8MasteringVoice* mastering_voice;
api::IXAudio2_8SourceVoice* pcm_voice;
} api_2_8;
} objects_ = {};
xe::threading::Semaphore* semaphore_ = nullptr;
class VoiceCallback;
VoiceCallback* voice_callback_ = nullptr;
static const uint32_t frame_count_ = api::XE_XAUDIO2_MAX_QUEUED_BUFFERS;
static const uint32_t frame_channels_ = 6;
static const uint32_t channel_samples_ = 256;
static const uint32_t frame_samples_ = frame_channels_ * channel_samples_;
static const uint32_t frame_size_ = sizeof(float) * frame_samples_;
float frames_[frame_count_][frame_samples_];
uint32_t current_frame_ = 0;
};
} // namespace xaudio2
} // namespace apu
} // namespace xe
#endif // XENIA_APU_XAUDIO2_XAUDIO2_AUDIO_DRIVER_H_
================================================
FILE: src/xenia/apu/xaudio2/xaudio2_audio_system.cc
================================================
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2013 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/apu/xaudio2/xaudio2_audio_system.h"
#include "xenia/apu/apu_flags.h"
#include "xenia/apu/xaudio2/xaudio2_audio_driver.h"
namespace xe {
namespace apu {
namespace xaudio2 {
std::unique_ptr XAudio2AudioSystem::Create(
cpu::Processor* processor) {
return std::make_unique(processor);
}
XAudio2AudioSystem::XAudio2AudioSystem(cpu::Processor* processor)
: AudioSystem(processor) {}
XAudio2AudioSystem::~XAudio2AudioSystem() {}
void XAudio2AudioSystem::Initialize() { AudioSystem::Initialize(); }
X_STATUS XAudio2AudioSystem::CreateDriver(size_t index,
xe::threading::Semaphore* semaphore,
AudioDriver** out_driver) {
assert_not_null(out_driver);
auto driver = new XAudio2AudioDriver(memory_, semaphore);
if (!driver->Initialize()) {
driver->Shutdown();
return X_STATUS_UNSUCCESSFUL;
}
*out_driver = driver;
return X_STATUS_SUCCESS;
}
void XAudio2AudioSystem::DestroyDriver(AudioDriver* driver) {
assert_not_null(driver);
auto xdriver = static_cast(driver);
xdriver->Shutdown();
assert_not_null(xdriver);
delete xdriver;
}
} // namespace xaudio2
} // namespace apu
} // namespace xe
================================================
FILE: src/xenia/apu/xaudio2/xaudio2_audio_system.h
================================================
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2013 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_APU_XAUDIO2_XAUDIO2_AUDIO_SYSTEM_H_
#define XENIA_APU_XAUDIO2_XAUDIO2_AUDIO_SYSTEM_H_
#include "xenia/apu/audio_system.h"
namespace xe {
namespace apu {
namespace xaudio2 {
class XAudio2AudioSystem : public AudioSystem {
public:
explicit XAudio2AudioSystem(cpu::Processor* processor);
~XAudio2AudioSystem() override;
static bool IsAvailable() { return true; }
static std::unique_ptr Create(cpu::Processor* processor);
X_RESULT CreateDriver(size_t index, xe::threading::Semaphore* semaphore,
AudioDriver** out_driver) override;
void DestroyDriver(AudioDriver* driver) override;
protected:
void Initialize() override;
};
} // namespace xaudio2
} // namespace apu
} // namespace xe
#endif // XENIA_APU_XAUDIO2_XAUDIO2_AUDIO_SYSTEM_H_
================================================
FILE: src/xenia/apu/xma_context.cc
================================================
/**
******************************************************************************
* 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/apu/xma_context.h"
#include
#include
#include "xenia/apu/xma_decoder.h"
#include "xenia/apu/xma_helpers.h"
#include "xenia/base/bit_stream.h"
#include "xenia/base/logging.h"
#include "xenia/base/platform.h"
#include "xenia/base/profiling.h"
#include "xenia/base/ring_buffer.h"
extern "C" {
#if XE_COMPILER_MSVC
#pragma warning(push)
#pragma warning(disable : 4101 4244 5033)
#endif
#include "third_party/FFmpeg/libavcodec/avcodec.h"
#if XE_COMPILER_MSVC
#pragma warning(pop)
#endif
} // extern "C"
// Credits for most of this code goes to:
// https://github.com/koolkdev/libertyv/blob/master/libav_wrapper/xma2dec.c
namespace xe {
namespace apu {
XmaContext::XmaContext() = default;
XmaContext::~XmaContext() {
if (av_context_) {
if (avcodec_is_open(av_context_)) {
avcodec_close(av_context_);
}
av_free(av_context_);
}
if (av_frame_) {
av_frame_free(&av_frame_);
}
// if (current_frame_) {
// delete[] current_frame_;
// }
}
int XmaContext::Setup(uint32_t id, Memory* memory, uint32_t guest_ptr) {
id_ = id;
memory_ = memory;
guest_ptr_ = guest_ptr;
// Allocate ffmpeg stuff:
av_packet_ = av_packet_alloc();
assert_not_null(av_packet_);
// find the XMA2 audio decoder
av_codec_ = avcodec_find_decoder(AV_CODEC_ID_XMAFRAMES);
if (!av_codec_) {
XELOGE("XmaContext {}: Codec not found", id);
return 1;
}
av_context_ = avcodec_alloc_context3(av_codec_);
if (!av_context_) {
XELOGE("XmaContext {}: Couldn't allocate context", id);
return 1;
}
// Initialize these to 0. They'll actually be set later.
av_context_->channels = 0;
av_context_->sample_rate = 0;
av_frame_ = av_frame_alloc();
if (!av_frame_) {
XELOGE("XmaContext {}: Couldn't allocate frame", id);
return 1;
}
// FYI: We're purposely not opening the codec here. That is done later.
return 0;
}
bool XmaContext::Work() {
std::lock_guard lock(lock_);
if (!is_allocated() || !is_enabled()) {
return false;
}
set_is_enabled(false);
auto context_ptr = memory()->TranslateVirtual(guest_ptr());
XMA_CONTEXT_DATA data(context_ptr);
Decode(&data);
data.Store(context_ptr);
return true;
}
void XmaContext::Enable() {
std::lock_guard lock(lock_);
auto context_ptr = memory()->TranslateVirtual(guest_ptr());
XMA_CONTEXT_DATA data(context_ptr);
XELOGAPU("XmaContext: kicking context {} (buffer {} {}/{} bits)", id(),
data.current_buffer, data.input_buffer_read_offset,
(data.current_buffer == 0 ? data.input_buffer_0_packet_count
: data.input_buffer_1_packet_count) *
kBitsPerPacket);
data.Store(context_ptr);
set_is_enabled(true);
}
bool XmaContext::Block(bool poll) {
if (!lock_.try_lock()) {
if (poll) {
return false;
}
lock_.lock();
}
lock_.unlock();
return true;
}
void XmaContext::Clear() {
std::lock_guard lock(lock_);
XELOGAPU("XmaContext: reset context {}", id());
auto context_ptr = memory()->TranslateVirtual(guest_ptr());
XMA_CONTEXT_DATA data(context_ptr);
data.input_buffer_0_valid = 0;
data.input_buffer_1_valid = 0;
data.output_buffer_valid = 0;
data.output_buffer_read_offset = 0;
data.output_buffer_write_offset = 0;
data.Store(context_ptr);
}
void XmaContext::Disable() {
std::lock_guard lock(lock_);
XELOGAPU("XmaContext: disabling context {}", id());
set_is_enabled(false);
}
void XmaContext::Release() {
// Lock it in case the decoder thread is working on it now.
std::lock_guard lock(lock_);
assert_true(is_allocated_ == true);
set_is_allocated(false);
auto context_ptr = memory()->TranslateVirtual(guest_ptr());
std::memset(context_ptr, 0, sizeof(XMA_CONTEXT_DATA)); // Zero it.
}
void XmaContext::SwapInputBuffer(XMA_CONTEXT_DATA* data) {
// No more frames.
if (data->current_buffer == 0) {
data->input_buffer_0_valid = 0;
} else {
data->input_buffer_1_valid = 0;
}
data->current_buffer ^= 1;
data->input_buffer_read_offset = 0;
}
bool XmaContext::TrySetupNextLoop(XMA_CONTEXT_DATA* data,
bool ignore_input_buffer_offset) {
// Setup the input buffer offset if next loop exists.
// TODO(Pseudo-Kernel): Need to handle loop in the following cases.
// 1. loop_start == loop_end == 0
// 2. loop_start > loop_end && loop_count > 0
if (data->loop_count > 0 && data->loop_start < data->loop_end &&
(ignore_input_buffer_offset ||
data->input_buffer_read_offset >= data->loop_end)) {
// Loop back to the beginning.
data->input_buffer_read_offset = data->loop_start;
if (data->loop_count < 255) {
data->loop_count--;
}
return true;
}
return false;
}
/*
void XmaContext::NextPacket(
uint8_t* input_buffer,
uint32_t input_size,
uint32_t input_buffer_read_offset) {
*/
void XmaContext::NextPacket(XMA_CONTEXT_DATA* data) {
// auto packet_idx = GetFramePacketNumber(input_buffer, input_size,
// input_buffer_read_offset);
// packet_idx++;
// if (packet_idx++ >= input_size)
}
int XmaContext::GetSampleRate(int id) {
switch (id) {
case 0:
return 24000;
case 1:
return 32000;
case 2:
return 44100;
case 3:
return 48000;
}
assert_always();
return 0;
}
bool XmaContext::ValidFrameOffset(uint8_t* block, size_t size_bytes,
size_t frame_offset_bits) {
uint32_t packet_num =
GetFramePacketNumber(block, size_bytes, frame_offset_bits);
if (packet_num == -1) {
// Invalid packet number
return false;
}
uint8_t* packet = block + (packet_num * kBytesPerPacket);
size_t relative_offset_bits = frame_offset_bits % kBitsPerPacket;
uint32_t first_frame_offset = xma::GetPacketFrameOffset(packet);
if (first_frame_offset == -1 || first_frame_offset > kBitsPerPacket) {
// Packet only contains a partial frame, so no frames can start here.
return false;
}
BitStream stream(packet, kBitsPerPacket);
stream.SetOffset(first_frame_offset);
while (true) {
if (stream.offset_bits() == relative_offset_bits) {
return true;
}
if (stream.BitsRemaining() < 15) {
// Not enough room for another frame header.
return false;
}
uint64_t size = stream.Read(15);
if ((size - 15) > stream.BitsRemaining()) {
// Last frame.
return false;
} else if (size == 0x7FFF) {
// Invalid frame (and last of this packet)
return false;
}
stream.Advance(size - 16);
// Read the trailing bit to see if frames follow
if (stream.Read(1) == 0) {
break;
}
}
return false;
}
static void dump_raw(AVFrame* frame, int id) {
FILE* outfile = fopen(fmt::format("out{}.raw", id).c_str(), "ab");
if (!outfile) {
return;
}
size_t data_size = sizeof(float);
for (int i = 0; i < frame->nb_samples; i++) {
for (int ch = 0; ch < frame->channels; ch++) {
fwrite(frame->data[ch] + data_size * i, 1, data_size, outfile);
}
}
fclose(outfile);
}
void XmaContext::Decode(XMA_CONTEXT_DATA* data) {
SCOPE_profile_cpu_f("apu");
// What I see:
// XMA outputs 2 bytes per sample
// 512 samples per frame (128 per subframe)
// Max output size is data.output_buffer_block_count * 256
// This decoder is fed packets (max 4095 per buffer)
// Packets contain "some" frames
// 32bit header (big endian)
// Frames are the smallest thing the SPUs can decode.
// They can and usually will span packets.
// Sample rates (data.sample_rate):
// 0 - 24 kHz
// 1 - 32 kHz
// 2 - 44.1 kHz
// 3 - 48 kHz
// SPUs also support stereo decoding. (data.is_stereo)
// Check the output buffer - we cannot decode anything else if it's
// unavailable.
if (!data->output_buffer_valid) {
return;
}
// No available data.
if (!data->input_buffer_0_valid && !data->input_buffer_1_valid) {
data->output_buffer_valid = 0;
return;
}
// XAudio Loops
// loop_count:
// - XAUDIO2_MAX_LOOP_COUNT = 254
// - XAUDIO2_LOOP_INFINITE = 255
// loop_start/loop_end are bit offsets to a specific frame
// Translate pointers for future use.
// Sometimes the game will use rolling input buffers. If they do, we cannot
// assume they form a complete block! In addition, the buffers DO NOT have
// to be contiguous!
uint8_t* in0 = data->input_buffer_0_valid
? memory()->TranslatePhysical(data->input_buffer_0_ptr)
: nullptr;
uint8_t* in1 = data->input_buffer_1_valid
? memory()->TranslatePhysical(data->input_buffer_1_ptr)
: nullptr;
uint8_t* current_input_buffer = data->current_buffer ? in1 : in0;
XELOGAPU("Processing context {} (offset {}, buffer {}, ptr {:p})", id(),
data->input_buffer_read_offset, data->current_buffer,
current_input_buffer);
size_t input_buffer_0_size =
data->input_buffer_0_packet_count * kBytesPerPacket;
size_t input_buffer_1_size =
data->input_buffer_1_packet_count * kBytesPerPacket;
size_t input_total_size = input_buffer_0_size + input_buffer_1_size;
size_t current_input_size =
data->current_buffer ? input_buffer_1_size : input_buffer_0_size;
size_t current_input_packet_count = current_input_size / kBytesPerPacket;
// Output buffers are in raw PCM samples, 256 bytes per block.
// Output buffer is a ring buffer. We need to write from the write offset
// to the read offset.
uint8_t* output_buffer = memory()->TranslatePhysical(data->output_buffer_ptr);
uint32_t output_capacity =
data->output_buffer_block_count * kBytesPerSubframeChannel;
uint32_t output_read_offset =
data->output_buffer_read_offset * kBytesPerSubframeChannel;
uint32_t output_write_offset =
data->output_buffer_write_offset * kBytesPerSubframeChannel;
RingBuffer output_rb(output_buffer, output_capacity);
output_rb.set_read_offset(output_read_offset);
output_rb.set_write_offset(output_write_offset);
// We can only decode an entire frame and write it out at a time, so
// don't save any samples.
// TODO(JoelLinn): subframes when looping
size_t output_remaining_bytes = output_rb.write_count();
output_remaining_bytes -=
output_remaining_bytes % (kBytesPerFrameChannel << data->is_stereo);
// is_dirty_ = true; // TODO
// is_dirty_ = false; // TODO
assert_false(data->stop_when_done);
assert_false(data->interrupt_when_done);
static int total_samples = 0;
bool reuse_input_buffer = false;
// Decode until we can't write any more data.
while (output_remaining_bytes > 0) {
if (!data->input_buffer_0_valid && !data->input_buffer_1_valid) {
// Out of data.
break;
}
// Setup the input buffer if we are at loop_end.
// The input buffer must not be swapped out until all loops are processed.
reuse_input_buffer = TrySetupNextLoop(data, false);
// assert_true(packets_skip_ == 0);
// assert_true(split_frame_len_ == 0);
// assert_true(split_frame_len_partial_ == 0);
// Where are we in the buffer (in XMA jargon)
int packet_idx, frame_idx, frame_count;
uint8_t* packet;
bool frame_last_split;
BitStream stream(current_input_buffer, current_input_size * 8);
stream.SetOffset(data->input_buffer_read_offset);
// if we had a buffer swap try to skip packets first
if (packets_skip_ > 0) {
packet_idx =
GetFramePacketNumber(current_input_buffer, current_input_size,
data->input_buffer_read_offset);
while (packets_skip_ > 0) {
packets_skip_--;
packet_idx++;
if (packet_idx >= current_input_packet_count) {
if (!reuse_input_buffer) {
// Last packet. Try setup once more.
reuse_input_buffer = TrySetupNextLoop(data, true);
}
if (!reuse_input_buffer) {
SwapInputBuffer(data);
}
return;
}
}
// invalid frame pointer but needed for us
data->input_buffer_read_offset = packet_idx * kBitsPerPacket;
// continue;
}
if (split_frame_len_) {
// handle a frame that was split over two packages
packet_idx =
GetFramePacketNumber(current_input_buffer, current_input_size,
data->input_buffer_read_offset);
packet = current_input_buffer + packet_idx * kBytesPerPacket;
std::tie(frame_count, frame_last_split) = GetPacketFrameCount(packet);
frame_idx = -1;
stream =
BitStream(current_input_buffer, (packet_idx + 1) * kBitsPerPacket);
stream.SetOffset(packet_idx * kBitsPerPacket + 32);
if (split_frame_len_ > xma::kMaxFrameLength) {
// TODO write CopyPeekMethod
auto offset = stream.offset_bits();
stream.Copy(
xma_frame_.data() + 1 +
((split_frame_len_partial_ + split_frame_padding_start_) / 8),
15 - split_frame_len_partial_);
stream.SetOffset(offset);
BitStream slen(xma_frame_.data() + 1, 15 + split_frame_padding_start_);
slen.Advance(split_frame_padding_start_);
split_frame_len_ = static_cast(slen.Read(15));
}
if (frame_count > 0) {
assert_true(xma::GetPacketFrameOffset(packet) - 32 ==
split_frame_len_ - split_frame_len_partial_);
}
auto offset = stream.Copy(
xma_frame_.data() + 1 +
((split_frame_len_partial_ + split_frame_padding_start_) / 8),
split_frame_len_ - split_frame_len_partial_);
assert_true(offset ==
(split_frame_padding_start_ + split_frame_len_partial_) % 8);
} else {
if (data->input_buffer_read_offset % kBitsPerPacket == 0) {
// Invalid offset. Go ahead and set it.
int packet_number =
GetFramePacketNumber(current_input_buffer, current_input_size,
data->input_buffer_read_offset);
if (packet_number == -1) {
return;
}
auto offset =
xma::GetPacketFrameOffset(current_input_buffer +
kBytesPerPacket * packet_number) +
data->input_buffer_read_offset;
if (offset == -1) {
// No more frames.
SwapInputBuffer(data);
// TODO partial frames? end?
XELOGE("XmaContext {}: TODO partial frames? end?", id());
assert_always("TODO");
return;
} else {
data->input_buffer_read_offset = offset;
}
}
if (!ValidFrameOffset(current_input_buffer, current_input_size,
data->input_buffer_read_offset)) {
XELOGAPU("XmaContext {}: Invalid read offset {}!", id(),
data->input_buffer_read_offset);
SwapInputBuffer(data);
return;
}
// Where are we in the buffer (in XMA jargon)
std::tie(packet_idx, frame_idx) =
GetFrameNumber(current_input_buffer, current_input_size,
data->input_buffer_read_offset);
// TODO handle
assert_true(packet_idx >= 0);
assert_true(frame_idx >= 0);
packet = current_input_buffer + packet_idx * kBytesPerPacket;
// frames that belong to this packet
std::tie(frame_count, frame_last_split) = GetPacketFrameCount(packet);
assert_true(frame_count >= 0); // TODO end
PrepareDecoder(packet, data->sample_rate, bool(data->is_stereo));
// Current frame is split to next packet:
bool frame_is_split = frame_last_split && (frame_idx >= frame_count - 1);
stream =
BitStream(current_input_buffer, (packet_idx + 1) * kBitsPerPacket);
stream.SetOffset(data->input_buffer_read_offset);
// int frame_len;
// int frame_len_partial
split_frame_len_partial_ = static_cast(stream.BitsRemaining());
if (split_frame_len_partial_ >= 15) {
split_frame_len_ = static_cast(stream.Peek(15));
} else {
// assert_always();
split_frame_len_ = xma::kMaxFrameLength + 1;
}
assert_true(frame_is_split ==
(split_frame_len_ > split_frame_len_partial_));
// TODO fix bitstream copy
std::memset(xma_frame_.data(), 0, xma_frame_.size());
{
auto offset =
stream.Copy(xma_frame_.data() + 1,
std::min(split_frame_len_, split_frame_len_partial_));
assert_true(offset < 8);
split_frame_padding_start_ = static_cast(offset);
}
if (frame_is_split) {
// go to next xma packet of this stream
packets_skip_ = xma::GetPacketSkipCount(packet) + 1;
while (packets_skip_ > 0) {
packets_skip_--;
packet += kBytesPerPacket;
packet_idx++;
if (packet_idx >= current_input_packet_count) {
if (!reuse_input_buffer) {
// Last packet. Try setup once more.
reuse_input_buffer = TrySetupNextLoop(data, true);
}
if (!reuse_input_buffer) {
SwapInputBuffer(data);
}
return;
}
}
// TODO guest might read this:
data->input_buffer_read_offset = packet_idx * kBitsPerPacket;
continue;
}
}
av_packet_->data = xma_frame_.data();
av_packet_->size = static_cast(
1 + ((split_frame_padding_start_ + split_frame_len_) / 8) +
(((split_frame_padding_start_ + split_frame_len_) % 8) ? 1 : 0));
auto padding_end = av_packet_->size * 8 -
(8 + split_frame_padding_start_ + split_frame_len_);
assert_true(padding_end < 8);
xma_frame_[0] =
((split_frame_padding_start_ & 7) << 5) | ((padding_end & 7) << 2);
split_frame_len_ = 0;
split_frame_len_partial_ = 0;
split_frame_padding_start_ = 0;
auto ret = avcodec_send_packet(av_context_, av_packet_);
if (ret < 0) {
XELOGE("XmaContext {}: Error sending packet for decoding", id());
// TODO bail out
assert_always();
}
ret = avcodec_receive_frame(av_context_, av_frame_);
/*
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
// TODO AVERROR_EOF???
break;
else
*/
if (ret < 0) {
XELOGE("XmaContext {}: Error during decoding", id());
assert_always();
return; // TODO bail out
}
assert_true(ret == 0);
{
// copy over 1 frame
// update input buffer read offset
// assert(decoded_consumed_samples_ + kSamplesPerFrame <=
// current_frame_.size());
assert_true(av_context_->sample_fmt == AV_SAMPLE_FMT_FLTP);
// assert_true(frame_is_split == (frame_idx == -1));
// dump_raw(av_frame_, id());
ConvertFrame((const uint8_t**)av_frame_->data, bool(data->is_stereo),
raw_frame_.data());
// decoded_consumed_samples_ += kSamplesPerFrame;
auto byte_count = kBytesPerFrameChannel << data->is_stereo;
assert_true(output_remaining_bytes >= byte_count);
output_rb.Write(raw_frame_.data(), byte_count);
output_remaining_bytes -= byte_count;
data->output_buffer_write_offset = output_rb.write_offset() / 256;
total_samples += id_ == 0 ? kSamplesPerFrame : 0;
uint32_t offset = data->input_buffer_read_offset;
// if (offset % (kBytesPerSample * 8) == 0) {
// offset = xma::GetPacketFrameOffset(packet);
//}
offset = static_cast(
GetNextFrame(current_input_buffer, current_input_size, offset));
// assert_true((offset == 0) ==
// (frame_is_split || (frame_idx + 1 >= frame_count)));
if (frame_idx + 1 >= frame_count) {
// Skip to next packet (no split frame)
packets_skip_ = xma::GetPacketSkipCount(packet) + 1;
while (packets_skip_ > 0) {
packets_skip_--;
packet_idx++;
if (packet_idx >= current_input_packet_count) {
if (!reuse_input_buffer) {
// Last packet. Try setup once more.
reuse_input_buffer = TrySetupNextLoop(data, true);
}
if (!reuse_input_buffer) {
SwapInputBuffer(data);
}
return;
}
}
packet = current_input_buffer + packet_idx * kBytesPerPacket;
offset =
xma::GetPacketFrameOffset(packet) + packet_idx * kBitsPerPacket;
}
if (offset == 0 || frame_idx == -1) {
// Next packet but we already skipped to it
if (packet_idx >= current_input_packet_count) {
// Buffer is fully used
if (!reuse_input_buffer) {
// Last packet. Try setup once more.
reuse_input_buffer = TrySetupNextLoop(data, true);
}
if (!reuse_input_buffer) {
SwapInputBuffer(data);
}
break;
}
offset =
xma::GetPacketFrameOffset(packet) + packet_idx * kBitsPerPacket;
}
// TODO buffer bounds check
assert_true(data->input_buffer_read_offset < offset);
data->input_buffer_read_offset = offset;
}
}
// assert_true((split_frame_len_ != 0) == (data->input_buffer_read_offset ==
// 0));
// The game will kick us again with a new output buffer later.
// It's important that we only invalidate this if we actually wrote to it!!
if (output_rb.write_offset() == output_rb.read_offset()) {
data->output_buffer_valid = 0;
}
}
size_t XmaContext::GetNextFrame(uint8_t* block, size_t size,
size_t bit_offset) {
// offset = xma::GetPacketFrameOffset(packet);
// TODO meh
// auto next_packet = bit_offset - bit_offset % kBitsPerPacket +
// kBitsPerPacket;
auto packet_idx = GetFramePacketNumber(block, size, bit_offset);
BitStream stream(block, size * 8);
stream.SetOffset(bit_offset);
if (stream.BitsRemaining() < 15) {
return 0;
}
uint64_t len = stream.Read(15);
if ((len - 15) > stream.BitsRemaining()) {
assert_always("TODO");
// *bit_offset = next_packet;
// return false;
// return next_packet;
return 0;
} else if (len >= xma::kMaxFrameLength) {
assert_always("TODO");
// *bit_offset = next_packet;
// return false;
return 0;
// return next_packet;
}
stream.Advance(len - (15 + 1));
// Read the trailing bit to see if frames follow
if (stream.Read(1) == 0) {
return 0;
}
bit_offset += len;
if (packet_idx < GetFramePacketNumber(block, size, bit_offset)) {
return 0;
}
return bit_offset;
}
int XmaContext::GetFramePacketNumber(uint8_t* block, size_t size,
size_t bit_offset) {
size *= 8;
if (bit_offset >= size) {
// Not good :(
assert_always();
return -1;
}
size_t byte_offset = bit_offset >> 3;
size_t packet_number = byte_offset / kBytesPerPacket;
return (uint32_t)packet_number;
}
std::tuple XmaContext::GetFrameNumber(uint8_t* block, size_t size,
size_t bit_offset) {
auto packet_idx = GetFramePacketNumber(block, size, bit_offset);
if (packet_idx < 0 || (packet_idx + 1) * kBytesPerPacket > size) {
assert_always();
return {packet_idx, -2};
}
if (bit_offset == 0) {
return {packet_idx, -1};
}
uint8_t* packet = block + (packet_idx * kBytesPerPacket);
auto first_frame_offset = xma::GetPacketFrameOffset(packet);
BitStream stream(block, size * 8);
stream.SetOffset(packet_idx * kBitsPerPacket + first_frame_offset);
int frame_idx = 0;
while (true) {
if (stream.BitsRemaining() < 15) {
break;
}
if (stream.offset_bits() == bit_offset) {
break;
}
uint64_t size = stream.Read(15);
if ((size - 15) > stream.BitsRemaining()) {
// Last frame.
break;
} else if (size == 0x7FFF) {
// Invalid frame (and last of this packet)
break;
}
stream.Advance(size - (15 + 1));
// Read the trailing bit to see if frames follow
if (stream.Read(1) == 0) {
break;
}
frame_idx++;
}
return {packet_idx, frame_idx};
}
std::tuple XmaContext::GetPacketFrameCount(uint8_t* packet) {
auto first_frame_offset = xma::GetPacketFrameOffset(packet);
if (first_frame_offset > kBitsPerPacket - kBitsPerHeader) {
// frame offset is beyond packet end
return {0, false};
}
BitStream stream(packet, kBitsPerPacket);
stream.SetOffset(first_frame_offset);
int frame_count = 0;
while (true) {
frame_count++;
if (stream.BitsRemaining() < 15) {
return {frame_count, true};
}
uint64_t size = stream.Read(15);
if ((size - 15) > stream.BitsRemaining()) {
return {frame_count, true};
} else if (size == 0x7FFF) {
assert_always();
return {frame_count, true};
}
stream.Advance(size - (15 + 1));
if (stream.Read(1) == 0) {
return {frame_count, false};
}
}
}
int XmaContext::PrepareDecoder(uint8_t* packet, int sample_rate,
bool is_two_channel) {
// Sanity check: Packet metadata is always 1 for XMA2/0 for XMA
assert_true((packet[2] & 0x7) == 1 || (packet[2] & 0x7) == 0);
sample_rate = GetSampleRate(sample_rate);
// Re-initialize the context with new sample rate and channels.
uint32_t channels = is_two_channel ? 2 : 1;
if (av_context_->sample_rate != sample_rate ||
av_context_->channels != channels) {
// We have to reopen the codec so it'll realloc whatever data it needs.
// TODO(DrChat): Find a better way.
avcodec_close(av_context_);
av_context_->sample_rate = sample_rate;
av_context_->channels = channels;
if (avcodec_open2(av_context_, av_codec_, NULL) < 0) {
XELOGE("XmaContext: Failed to reopen FFmpeg context");
return -1;
}
return 1;
}
return 0;
}
void XmaContext::ConvertFrame(const uint8_t** samples, bool is_two_channel,
uint8_t* output_buffer) {
// Loop through every sample, convert and drop it into the output array.
// If more than one channel, we need to interleave the samples from each
// channel next to each other. Always saturate because FFmpeg output is
// not limited to [-1, 1] (for example 1.095 as seen in 5454082B).
constexpr float scale = (1 << 15) - 1;
auto out = reinterpret_cast(output_buffer);
// For testing of vectorized versions, stereo audio is common in 4D5307E6,
// since the first menu frame; the intro cutscene also has more than 2
// channels.
#if XE_ARCH_AMD64
static_assert(kSamplesPerFrame % 8 == 0);
const auto in_channel_0 = reinterpret_cast(samples[0]);
const __m128 scale_mm = _mm_set1_ps(scale);
if (is_two_channel) {
const auto in_channel_1 = reinterpret_cast(samples[1]);
const __m128i shufmask =
_mm_set_epi8(14, 15, 6, 7, 12, 13, 4, 5, 10, 11, 2, 3, 8, 9, 0, 1);
for (uint32_t i = 0; i < kSamplesPerFrame; i += 4) {
// Load 8 samples, 4 for each channel.
__m128 in_mm0 = _mm_loadu_ps(&in_channel_0[i]);
__m128 in_mm1 = _mm_loadu_ps(&in_channel_1[i]);
// Rescale.
in_mm0 = _mm_mul_ps(in_mm0, scale_mm);
in_mm1 = _mm_mul_ps(in_mm1, scale_mm);
// Cast to int32.
__m128i out_mm0 = _mm_cvtps_epi32(in_mm0);
__m128i out_mm1 = _mm_cvtps_epi32(in_mm1);
// Saturated cast and pack to int16.
__m128i out_mm = _mm_packs_epi32(out_mm0, out_mm1);
// Interleave channels and byte swap.
out_mm = _mm_shuffle_epi8(out_mm, shufmask);
// Store, as [out + i * 4] movdqu.
_mm_storeu_si128(reinterpret_cast<__m128i*>(&out[i * 2]), out_mm);
}
} else {
const __m128i shufmask =
_mm_set_epi8(14, 15, 12, 13, 10, 11, 8, 9, 6, 7, 4, 5, 2, 3, 0, 1);
for (uint32_t i = 0; i < kSamplesPerFrame; i += 8) {
// Load 8 samples, as [in_channel_0 + i * 4] and
// [in_channel_0 + i * 4 + 16] movups.
__m128 in_mm0 = _mm_loadu_ps(&in_channel_0[i]);
__m128 in_mm1 = _mm_loadu_ps(&in_channel_0[i + 4]);
// Rescale.
in_mm0 = _mm_mul_ps(in_mm0, scale_mm);
in_mm1 = _mm_mul_ps(in_mm1, scale_mm);
// Cast to int32.
__m128i out_mm0 = _mm_cvtps_epi32(in_mm0);
__m128i out_mm1 = _mm_cvtps_epi32(in_mm1);
// Saturated cast and pack to int16.
__m128i out_mm = _mm_packs_epi32(out_mm0, out_mm1);
// Byte swap.
out_mm = _mm_shuffle_epi8(out_mm, shufmask);
// Store, as [out + i * 2] movdqu.
_mm_storeu_si128(reinterpret_cast<__m128i*>(&out[i]), out_mm);
}
}
#else
uint32_t o = 0;
for (uint32_t i = 0; i < kSamplesPerFrame; i++) {
for (uint32_t j = 0; j <= uint32_t(is_two_channel); j++) {
// Select the appropriate array based on the current channel.
auto in = reinterpret_cast(samples[j]);
// Raw samples sometimes aren't within [-1, 1]
float scaled_sample = xe::clamp_float(in[i], -1.0f, 1.0f) * scale;
// Convert the sample and output it in big endian.
auto sample = static_cast(scaled_sample);
out[o++] = xe::byte_swap(sample);
}
}
#endif
}
} // namespace apu
} // namespace xe
================================================
FILE: src/xenia/apu/xma_context.h
================================================
/**
******************************************************************************
* 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. *
******************************************************************************
*/
#ifndef XENIA_APU_XMA_CONTEXT_H_
#define XENIA_APU_XMA_CONTEXT_H_
#include
#include
#include
#include
//#include
#include "xenia/memory.h"
#include "xenia/xbox.h"
// XMA audio format:
// From research, XMA appears to be based on WMA Pro with
// a few (very slight) modifications.
// XMA2 is fully backwards-compatible with XMA1.
// Helpful resources:
// https://github.com/koolkdev/libertyv/blob/master/libav_wrapper/xma2dec.c
// https://hcs64.com/mboard/forum.php?showthread=14818
// https://github.com/hrydgard/minidx9/blob/master/Include/xma2defs.h
// Forward declarations
struct AVCodec;
struct AVCodecParserContext;
struct AVCodecContext;
struct AVFrame;
struct AVPacket;
namespace xe {
namespace apu {
// This is stored in guest space in big-endian order.
// We load and swap the whole thing to splat here so that we can
// use bitfields.
// This could be important:
// https://www.fmod.org/questions/question/forum-15859
// Appears to be dumped in order (for the most part)
struct XMA_CONTEXT_DATA {
// DWORD 0
uint32_t input_buffer_0_packet_count : 12; // XMASetInputBuffer0, number of
// 2KB packets. Max 4095 packets.
// These packets form a block.
uint32_t loop_count : 8; // +12bit, XMASetLoopData NumLoops
uint32_t input_buffer_0_valid : 1; // +20bit, XMAIsInputBuffer0Valid
uint32_t input_buffer_1_valid : 1; // +21bit, XMAIsInputBuffer1Valid
uint32_t output_buffer_block_count : 5; // +22bit SizeWrite 256byte blocks
uint32_t output_buffer_write_offset : 5; // +27bit
// XMAGetOutputBufferWriteOffset
// AKA OffsetWrite
// DWORD 1
uint32_t input_buffer_1_packet_count : 12; // XMASetInputBuffer1, number of
// 2KB packets. Max 4095 packets.
// These packets form a block.
uint32_t loop_subframe_start : 2; // +12bit, XMASetLoopData
uint32_t loop_subframe_end : 3; // +14bit, XMASetLoopData
uint32_t loop_subframe_skip : 3; // +17bit, XMASetLoopData might be
// subframe_decode_count
uint32_t subframe_decode_count : 4; // +20bit
uint32_t subframe_skip_count : 3; // +24bit
uint32_t sample_rate : 2; // +27bit enum of sample rates
uint32_t is_stereo : 1; // +29bit
uint32_t unk_dword_1_c : 1; // +30bit
uint32_t output_buffer_valid : 1; // +31bit, XMAIsOutputBufferValid
// DWORD 2
uint32_t input_buffer_read_offset : 26; // XMAGetInputBufferReadOffset
uint32_t unk_dword_2 : 6; // ErrorStatus/ErrorSet (?)
// DWORD 3
uint32_t loop_start : 26; // XMASetLoopData LoopStartOffset
// frame offset in bits
uint32_t unk_dword_3 : 6; // ? ParserErrorStatus/ParserErrorSet(?)
// DWORD 4
uint32_t loop_end : 26; // XMASetLoopData LoopEndOffset
// frame offset in bits
uint32_t packet_metadata : 5; // XMAGetPacketMetadata
uint32_t current_buffer : 1; // ?
// DWORD 5
uint32_t input_buffer_0_ptr; // physical address
// DWORD 6
uint32_t input_buffer_1_ptr; // physical address
// DWORD 7
uint32_t output_buffer_ptr; // physical address
// DWORD 8
uint32_t work_buffer_ptr; // PtrOverlapAdd(?)
// DWORD 9
// +0bit, XMAGetOutputBufferReadOffset AKA WriteBufferOffsetRead
uint32_t output_buffer_read_offset : 5;
uint32_t : 25;
uint32_t stop_when_done : 1; // +30bit
uint32_t interrupt_when_done : 1; // +31bit
// DWORD 10-15
uint32_t unk_dwords_10_15[6]; // reserved?
explicit XMA_CONTEXT_DATA(const void* ptr) {
xe::copy_and_swap(reinterpret_cast(this),
reinterpret_cast(ptr),
sizeof(XMA_CONTEXT_DATA) / 4);
}
void Store(void* ptr) {
xe::copy_and_swap(reinterpret_cast(ptr),
reinterpret_cast(this),
sizeof(XMA_CONTEXT_DATA) / 4);
}
};
static_assert_size(XMA_CONTEXT_DATA, 64);
#pragma pack(push, 1)
// XMA2WAVEFORMATEX
struct Xma2ExtraData {
uint8_t raw[34];
};
static_assert_size(Xma2ExtraData, 34);
#pragma pack(pop)
class XmaContext {
public:
static const uint32_t kBytesPerPacket = 2048;
static const uint32_t kBitsPerPacket = kBytesPerPacket * 8;
static const uint32_t kBitsPerHeader = 33;
static const uint32_t kBytesPerSample = 2;
static const uint32_t kSamplesPerFrame = 512;
static const uint32_t kSamplesPerSubframe = 128;
static const uint32_t kBytesPerFrameChannel =
kSamplesPerFrame * kBytesPerSample;
static const uint32_t kBytesPerSubframeChannel =
kSamplesPerSubframe * kBytesPerSample;
// static const uint32_t kOutputBytesPerBlock = 256;
// static const uint32_t kOutputMaxSizeBytes = 31 * kOutputBytesPerBlock;
explicit XmaContext();
~XmaContext();
int Setup(uint32_t id, Memory* memory, uint32_t guest_ptr);
bool Work();
void Enable();
bool Block(bool poll);
void Clear();
void Disable();
void Release();
Memory* memory() const { return memory_; }
uint32_t id() { return id_; }
uint32_t guest_ptr() { return guest_ptr_; }
bool is_allocated() { return is_allocated_; }
bool is_enabled() { return is_enabled_; }
void set_is_allocated(bool is_allocated) { is_allocated_ = is_allocated; }
void set_is_enabled(bool is_enabled) { is_enabled_ = is_enabled; }
private:
static void SwapInputBuffer(XMA_CONTEXT_DATA* data);
static bool TrySetupNextLoop(XMA_CONTEXT_DATA* data,
bool ignore_input_buffer_offset);
static void NextPacket(XMA_CONTEXT_DATA* data);
static int GetSampleRate(int id);
// Get the offset of the next frame. Does not traverse packets.
static size_t GetNextFrame(uint8_t* block, size_t size, size_t bit_offset);
// Get the containing packet number of the frame pointed to by the offset.
static int GetFramePacketNumber(uint8_t* block, size_t size,
size_t bit_offset);
// Get the packet number and the index of the frame inside that packet
static std::tuple GetFrameNumber(uint8_t* block, size_t size,
size_t bit_offset);
// Get the number of frames contained in the packet (including truncated) and
// if the last frame is split.
static std::tuple GetPacketFrameCount(uint8_t* packet);
// Convert sample format and swap bytes
static void ConvertFrame(const uint8_t** samples, bool is_two_channel,
uint8_t* output_buffer);
bool ValidFrameOffset(uint8_t* block, size_t size_bytes,
size_t frame_offset_bits);
void Decode(XMA_CONTEXT_DATA* data);
int PrepareDecoder(uint8_t* packet, int sample_rate, bool is_two_channel);
Memory* memory_ = nullptr;
uint32_t id_ = 0;
uint32_t guest_ptr_ = 0;
std::mutex lock_;
bool is_allocated_ = false;
bool is_enabled_ = false;
// bool is_dirty_ = true;
// ffmpeg structures
AVPacket* av_packet_ = nullptr;
AVCodec* av_codec_ = nullptr;
AVCodecContext* av_context_ = nullptr;
AVFrame* av_frame_ = nullptr;
// uint32_t decoded_consumed_samples_ = 0; // TODO do this dynamically
// int decoded_idx_ = -1;
// bool partial_frame_saved_ = false;
// bool partial_frame_size_known_ = false;
// size_t partial_frame_total_size_bits_ = 0;
// size_t partial_frame_start_offset_bits_ = 0;
// size_t partial_frame_offset_bits_ = 0; // blah internal don't use this
// std::vector partial_frame_buffer_;
uint32_t packets_skip_ = 0;
// bool split_frame_pending_ = false;
uint32_t split_frame_len_ = 0;
uint32_t split_frame_len_partial_ = 0;
uint8_t split_frame_padding_start_ = 0;
// first byte contains bit offset information
std::array xma_frame_;
// uint8_t* current_frame_ = nullptr;
// conversion buffer for 2 channel frame
std::array raw_frame_;
// std::vector current_frame_ = std::vector(0);
};
} // namespace apu
} // namespace xe
#endif // XENIA_APU_XMA_CONTEXT_H_
================================================
FILE: src/xenia/apu/xma_decoder.cc
================================================
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2022 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/apu/xma_decoder.h"
#include "xenia/apu/xma_context.h"
#include "xenia/base/cvar.h"
#include "xenia/base/logging.h"
#include "xenia/base/math.h"
#include "xenia/base/profiling.h"
#include "xenia/base/ring_buffer.h"
#include "xenia/base/string_buffer.h"
#include "xenia/cpu/processor.h"
#include "xenia/cpu/thread_state.h"
#include "xenia/kernel/xthread.h"
extern "C" {
#include "third_party/FFmpeg/libavutil/log.h"
} // extern "C"
// As with normal Microsoft, there are like twelve different ways to access
// the audio APIs. Early games use XMA*() methods almost exclusively to touch
// decoders. Later games use XAudio*() and direct memory writes to the XMA
// structures (as opposed to the XMA* calls), meaning that we have to support
// both.
//
// The XMA*() functions just manipulate the audio system in the guest context
// and let the normal XmaDecoder handling take it, to prevent duplicate
// implementations. They can be found in xboxkrnl_audio_xma.cc
//
// XMA details:
// https://devel.nuclex.org/external/svn/directx/trunk/include/xma2defs.h
// https://github.com/gdawg/fsbext/blob/master/src/xma_header.h
//
// XAudio2 uses XMA under the covers, and seems to map with the same
// restrictions of frame/subframe/etc:
// https://msdn.microsoft.com/en-us/library/windows/desktop/microsoft.directx_sdk.xaudio2.xaudio2_buffer(v=vs.85).aspx
//
// XMA contexts are 64b in size and tight bitfields. They are in physical
// memory not usually available to games. Games will use MmMapIoSpace to get
// the 64b pointer in user memory so they can party on it. If the game doesn't
// do this, it's likely they are either passing the context to XAudio or
// using the XMA* functions.
DEFINE_bool(ffmpeg_verbose, false, "Verbose FFmpeg output (debug and above)",
"APU");
namespace xe {
namespace apu {
XmaDecoder::XmaDecoder(cpu::Processor* processor)
: memory_(processor->memory()), processor_(processor) {}
XmaDecoder::~XmaDecoder() = default;
void av_log_callback(void* avcl, int level, const char* fmt, va_list va) {
if (!cvars::ffmpeg_verbose && level > AV_LOG_WARNING) {
return;
}
char level_char = '?';
LogLevel log_level;
switch (level) {
case AV_LOG_ERROR: {
level_char = '!';
log_level = xe::LogLevel::Error;
break;
}
case AV_LOG_WARNING: {
level_char = 'w';
log_level = xe::LogLevel::Warning;
break;
}
case AV_LOG_INFO: {
level_char = 'i';
log_level = xe::LogLevel::Info;
break;
}
case AV_LOG_VERBOSE: {
level_char = 'v';
log_level = xe::LogLevel::Debug;
break;
}
case AV_LOG_DEBUG: {
level_char = 'd';
log_level = xe::LogLevel::Debug;
break;
}
default: {
level_char = '?';
log_level = xe::LogLevel::Debug;
break;
}
}
StringBuffer buff;
buff.AppendVarargs(fmt, va);
xe::logging::AppendLogLineFormat(log_level, level_char, "ffmpeg: {}",
buff.to_string_view());
}
X_STATUS XmaDecoder::Setup(kernel::KernelState* kernel_state) {
// Setup ffmpeg logging callback
av_log_set_callback(av_log_callback);
// Let the processor know we want register access callbacks.
memory_->AddVirtualMappedRange(
0x7FEA0000, 0xFFFF0000, 0x0000FFFF, this,
reinterpret_cast(MMIOReadRegisterThunk),
reinterpret_cast(MMIOWriteRegisterThunk));
// Setup XMA context data.
// The Xbox 360 kernel allocates the contexts with X_PAGE_NOCACHE |
// X_PAGE_READWRITE and writes MmGetPhysicalAddress for the address to the
// register.
context_data_first_ptr_ = memory()->SystemHeapAlloc(
sizeof(XMA_CONTEXT_DATA) * kContextCount, 256, kSystemHeapPhysical);
context_data_last_ptr_ =
context_data_first_ptr_ + (sizeof(XMA_CONTEXT_DATA) * kContextCount - 1);
register_file_[XmaRegister::ContextArrayAddress] =
memory()->GetPhysicalAddress(context_data_first_ptr_);
// Setup XMA contexts.
for (int i = 0; i < kContextCount; ++i) {
uint32_t guest_ptr = context_data_first_ptr_ + i * sizeof(XMA_CONTEXT_DATA);
XmaContext& context = contexts_[i];
if (context.Setup(i, memory(), guest_ptr)) {
assert_always();
}
}
register_file_[XmaRegister::NextContextIndex] = 1;
context_bitmap_.Resize(kContextCount);
worker_running_ = true;
work_event_ = xe::threading::Event::CreateAutoResetEvent(false);
assert_not_null(work_event_);
worker_thread_ = kernel::object_ref(
new kernel::XHostThread(kernel_state, 128 * 1024, 0, [this]() {
WorkerThreadMain();
return 0;
}));
worker_thread_->set_name("XMA Decoder");
worker_thread_->set_can_debugger_suspend(true);
worker_thread_->Create();
return X_STATUS_SUCCESS;
}
void XmaDecoder::WorkerThreadMain() {
uint32_t idle_loop_count = 0;
while (worker_running_) {
// Okay, let's loop through XMA contexts to find ones we need to decode!
bool did_work = false;
for (uint32_t n = 0; n < kContextCount; n++) {
XmaContext& context = contexts_[n];
did_work = context.Work() || did_work;
// TODO: Need thread safety to do this.
// Probably not too important though.
// registers_.current_context = n;
// registers_.next_context = (n + 1) % kContextCount;
}
if (paused_) {
pause_fence_.Signal();
resume_fence_.Wait();
}
if (!did_work) {
idle_loop_count++;
} else {
idle_loop_count = 0;
}
if (idle_loop_count > 500) {
// Idle for an extended period. Introduce a 20ms wait.
xe::threading::Wait(work_event_.get(), false,
std::chrono::milliseconds(20));
}
xe::threading::MaybeYield();
}
}
void XmaDecoder::Shutdown() {
worker_running_ = false;
if (work_event_) {
work_event_->Set();
}
if (paused_) {
Resume();
}
if (worker_thread_) {
// Wait for work thread.
xe::threading::Wait(worker_thread_->thread(), false);
worker_thread_.reset();
}
if (context_data_first_ptr_) {
memory()->SystemHeapFree(context_data_first_ptr_);
}
context_data_first_ptr_ = 0;
context_data_last_ptr_ = 0;
}
int XmaDecoder::GetContextId(uint32_t guest_ptr) {
static_assert_size(XMA_CONTEXT_DATA, 64);
if (guest_ptr < context_data_first_ptr_ ||
guest_ptr > context_data_last_ptr_) {
return -1;
}
assert_zero(guest_ptr & 0x3F);
return (guest_ptr - context_data_first_ptr_) >> 6;
}
uint32_t XmaDecoder::AllocateContext() {
size_t index = context_bitmap_.Acquire();
if (index == -1) {
// Out of contexts.
return 0;
}
XmaContext& context = contexts_[index];
assert_false(context.is_allocated());
context.set_is_allocated(true);
return context.guest_ptr();
}
void XmaDecoder::ReleaseContext(uint32_t guest_ptr) {
auto context_id = GetContextId(guest_ptr);
assert_true(context_id >= 0);
XmaContext& context = contexts_[context_id];
assert_true(context.is_allocated());
context.Release();
context_bitmap_.Release(context_id);
}
bool XmaDecoder::BlockOnContext(uint32_t guest_ptr, bool poll) {
auto context_id = GetContextId(guest_ptr);
assert_true(context_id >= 0);
XmaContext& context = contexts_[context_id];
return context.Block(poll);
}
uint32_t XmaDecoder::ReadRegister(uint32_t addr) {
auto r = (addr & 0xFFFF) / 4;
assert_true(r < XmaRegisterFile::kRegisterCount);
switch (r) {
case XmaRegister::ContextArrayAddress:
break;
case XmaRegister::CurrentContextIndex: {
// 0606h (1818h) is rotating context processing # set to hardware ID of
// context being processed.
// If bit 200h is set, the locking code will possibly collide on hardware
// IDs and error out, so we should never set it (I think?).
uint32_t& current_context_index =
register_file_[XmaRegister::CurrentContextIndex];
uint32_t& next_context_index =
register_file_[XmaRegister::NextContextIndex];
// To prevent games from seeing a stuck XMA context, return a rotating
// number.
current_context_index = next_context_index;
next_context_index = (next_context_index + 1) % kContextCount;
break;
}
default:
const auto register_info = register_file_.GetRegisterInfo(r);
if (register_info) {
XELOGW("XMA: Read from unhandled register ({:04X}, {})", r,
register_info->name);
} else {
XELOGW("XMA: Read from unknown register ({:04X})", r);
}
break;
}
return xe::byte_swap(register_file_[r]);
}
void XmaDecoder::WriteRegister(uint32_t addr, uint32_t value) {
SCOPE_profile_cpu_f("apu");
uint32_t r = (addr & 0xFFFF) / 4;
value = xe::byte_swap(value);
assert_true(r < XmaRegisterFile::kRegisterCount);
register_file_[r] = value;
if (r >= XmaRegister::Context0Kick && r <= XmaRegister::Context9Kick) {
// Context kick command.
// This will kick off the given hardware contexts.
// Basically, this kicks the SPU and says "hey, decode that audio!"
// XMAEnableContext
// The context ID is a bit in the range of the entire context array.
uint32_t base_context_id = (r - XmaRegister::Context0Kick) * 32;
for (int i = 0; value && i < 32; ++i, value >>= 1) {
if (value & 1) {
uint32_t context_id = base_context_id + i;
auto& context = contexts_[context_id];
context.Enable();
}
}
// Signal the decoder thread to start processing.
work_event_->Set();
} else if (r >= XmaRegister::Context0Lock && r <= XmaRegister::Context9Lock) {
// Context lock command.
// This requests a lock by flagging the context.
// XMADisableContext
uint32_t base_context_id = (r - XmaRegister::Context0Lock) * 32;
for (int i = 0; value && i < 32; ++i, value >>= 1) {
if (value & 1) {
uint32_t context_id = base_context_id + i;
auto& context = contexts_[context_id];
context.Disable();
}
}
// Signal the decoder thread to start processing.
// work_event_->Set();
} else if (r >= XmaRegister::Context0Clear &&
r <= XmaRegister::Context9Clear) {
// Context clear command.
// This will reset the given hardware contexts.
uint32_t base_context_id = (r - XmaRegister::Context0Clear) * 32;
for (int i = 0; value && i < 32; ++i, value >>= 1) {
if (value & 1) {
uint32_t context_id = base_context_id + i;
XmaContext& context = contexts_[context_id];
context.Clear();
}
}
} else {
// 0601h (1804h) is written to with 0x02000000 and 0x03000000 around a lock
// operation
switch (r) {
default: {
const auto register_info = register_file_.GetRegisterInfo(r);
if (register_info) {
XELOGW("XMA: Write to unhandled register ({:04X}, {}): {:08X}", r,
register_info->name, value);
} else {
XELOGW("XMA: Write to unknown register ({:04X}): {:08X}", r, value);
}
break;
}
#pragma warning(suppress : 4065)
}
}
}
void XmaDecoder::Pause() {
if (paused_) {
return;
}
paused_ = true;
pause_fence_.Wait();
}
void XmaDecoder::Resume() {
if (!paused_) {
return;
}
paused_ = false;
resume_fence_.Signal();
}
} // namespace apu
} // namespace xe
================================================
FILE: src/xenia/apu/xma_decoder.h
================================================
/**
******************************************************************************
* 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. *
******************************************************************************
*/
#ifndef XENIA_APU_XMA_DECODER_H_
#define XENIA_APU_XMA_DECODER_H_
#include
#include
#include
#include "xenia/apu/xma_context.h"
#include "xenia/apu/xma_register_file.h"
#include "xenia/base/bit_map.h"
#include "xenia/kernel/xthread.h"
#include "xenia/xbox.h"
namespace xe {
namespace apu {
struct XMA_CONTEXT_DATA;
class XmaDecoder {
public:
explicit XmaDecoder(cpu::Processor* processor);
~XmaDecoder();
Memory* memory() const { return memory_; }
cpu::Processor* processor() const { return processor_; }
X_STATUS Setup(kernel::KernelState* kernel_state);
void Shutdown();
uint32_t context_array_ptr() const {
return register_file_[XmaRegister::ContextArrayAddress];
}
uint32_t AllocateContext();
void ReleaseContext(uint32_t guest_ptr);
bool BlockOnContext(uint32_t guest_ptr, bool poll);
uint32_t ReadRegister(uint32_t addr);
void WriteRegister(uint32_t addr, uint32_t value);
bool is_paused() const { return paused_; }
void Pause();
void Resume();
protected:
int GetContextId(uint32_t guest_ptr);
private:
void WorkerThreadMain();
static uint32_t MMIOReadRegisterThunk(void* ppc_context, XmaDecoder* as,
uint32_t addr) {
return as->ReadRegister(addr);
}
static void MMIOWriteRegisterThunk(void* ppc_context, XmaDecoder* as,
uint32_t addr, uint32_t value) {
as->WriteRegister(addr, value);
}
protected:
Memory* memory_ = nullptr;
cpu::Processor* processor_ = nullptr;
std::atomic worker_running_ = {false};
kernel::object_ref worker_thread_;
std::unique_ptr work_event_ = nullptr;
bool paused_ = false;
xe::threading::Fence pause_fence_; // Signaled when worker paused.
xe::threading::Fence resume_fence_; // Signaled when resume requested.
XmaRegisterFile register_file_;
static const uint32_t kContextCount = 320;
XmaContext contexts_[kContextCount];
BitMap context_bitmap_;
uint32_t context_data_first_ptr_ = 0;
uint32_t context_data_last_ptr_ = 0;
};
} // namespace apu
} // namespace xe
#endif // XENIA_APU_XMA_DECODER_H_
================================================
FILE: src/xenia/apu/xma_helpers.h
================================================
/**
******************************************************************************
* 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. *
******************************************************************************
*/
// This file contains some functions used to help parse XMA data.
#ifndef XENIA_APU_XMA_HELPERS_H_
#define XENIA_APU_XMA_HELPERS_H_
#include
namespace xe {
namespace apu {
namespace xma {
static const uint32_t kMaxFrameLength = 0x7FFF;
// Get number of frames that /begin/ in this packet.
uint32_t GetPacketFrameCount(uint8_t* packet) {
return (uint8_t)(packet[0] >> 2);
}
// Get the first frame offset in bits
uint32_t GetPacketFrameOffset(uint8_t* packet) {
uint32_t val = (uint16_t)(((packet[0] & 0x3) << 13) | (packet[1] << 5) |
(packet[2] >> 3));
// if (val > kBitsPerPacket - kBitsPerHeader) {
// // There is no data in this packet
// return -1;
// } else {
return val + 32;
// }
}
uint32_t GetPacketMetadata(uint8_t* packet) {
return (uint8_t)(packet[2] & 0x7);
}
uint32_t GetPacketSkipCount(uint8_t* packet) { return (uint8_t)(packet[3]); }
} // namespace xma
} // namespace apu
} // namespace xe
#endif // XENIA_APU_XMA_HELPERS_H_
================================================
FILE: src/xenia/apu/xma_register_file.cc
================================================
/**
******************************************************************************
* 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/apu/xma_register_file.h"
#include
#include "xenia/base/math.h"
namespace xe {
namespace apu {
XmaRegisterFile::XmaRegisterFile() { std::memset(values, 0, sizeof(values)); }
const XmaRegisterInfo* XmaRegisterFile::GetRegisterInfo(uint32_t index) {
switch (index) {
#define XE_XMA_REGISTER(index, name) \
case index: { \
static const XmaRegisterInfo reg_info = { \
#name, \
}; \
return ®_info; \
}
#include "xenia/apu/xma_register_table.inc"
#undef XE_XMA_REGISTER
default:
return nullptr;
}
}
} // namespace apu
} // namespace xe
================================================
FILE: src/xenia/apu/xma_register_file.h
================================================
/**
******************************************************************************
* 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. *
******************************************************************************
*/
#ifndef XENIA_APU_XMA_REGISTER_FILE_H_
#define XENIA_APU_XMA_REGISTER_FILE_H_
#include
#include
namespace xe {
namespace apu {
struct XmaRegister {
#define XE_XMA_REGISTER(index, name) static const uint32_t name = index;
#include "xenia/apu/xma_register_table.inc"
#undef XE_XMA_REGISTER
};
struct XmaRegisterInfo {
const char* name;
};
class XmaRegisterFile {
public:
XmaRegisterFile();
static const XmaRegisterInfo* GetRegisterInfo(uint32_t index);
static const size_t kRegisterCount = (0xFFFF + 1) / 4;
uint32_t values[kRegisterCount];
uint32_t operator[](uint32_t reg) const { return values[reg]; }
uint32_t& operator[](uint32_t reg) { return values[reg]; }
};
} // namespace apu
} // namespace xe
#endif // XENIA_APU_XMA_REGISTER_FILE_H_
================================================
FILE: src/xenia/apu/xma_register_table.inc
================================================
/**
******************************************************************************
* 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. *
******************************************************************************
*/
// This is a partial file designed to be included by other files when
// constructing various tables.
#ifndef XE_XMA_REGISTER
#define XE_XMA_REGISTER(index, name)
#define __XE_XMA_REGISTER_UNSET
#endif
#ifndef XE_XMA_REGISTER_CONTEXT_GROUP
#define XE_XMA_REGISTER_CONTEXT_GROUP(index, suffix) \
XE_XMA_REGISTER(index + 0, Context0##suffix) \
XE_XMA_REGISTER(index + 1, Context1##suffix) \
XE_XMA_REGISTER(index + 2, Context2##suffix) \
XE_XMA_REGISTER(index + 3, Context3##suffix) \
XE_XMA_REGISTER(index + 4, Context4##suffix) \
XE_XMA_REGISTER(index + 5, Context5##suffix) \
XE_XMA_REGISTER(index + 6, Context6##suffix) \
XE_XMA_REGISTER(index + 7, Context7##suffix) \
XE_XMA_REGISTER(index + 8, Context8##suffix) \
XE_XMA_REGISTER(index + 9, Context9##suffix)
#endif
// 0x0000..0x001F : ???
// 0x0020..0x03FF : all 0xFFs?
// 0x0400..0x043F : ???
// 0x0440..0x047F : all 0xFFs?
// 0x0480..0x048B : ???
// 0x048C..0x04C0 : all 0xFFs?
// 0x04C1..0x04CB : ???
// 0x04CC..0x04FF : all 0xFFs?
// 0x0500..0x051F : ???
// 0x0520..0x057F : all 0xFFs?
// 0x0580..0x058F : ???
// 0x0590..0x05FF : all 0xFFs?
// XMA stuff is probably only 0x0600..0x06FF
//---------------------------------------------------------------------------//
XE_XMA_REGISTER(0x0600, ContextArrayAddress)
// 0x0601..0x0605 : ???
XE_XMA_REGISTER(0x0606, CurrentContextIndex)
XE_XMA_REGISTER(0x0607, NextContextIndex)
// 0x0608 : ???
// 0x0609..0x060F : zero?
XE_XMA_REGISTER_CONTEXT_GROUP(0x0610, Unknown610)
// 0x061A..0x061F : zero?
XE_XMA_REGISTER_CONTEXT_GROUP(0x0620, Unknown620)
// 0x062A..0x0641 : zero?
// 0x0642..0x0644 : ???
// 0x0645..0x064F : zero?
XE_XMA_REGISTER_CONTEXT_GROUP(0x0650, Kick)
// 0x065A..0x065F : zero?
XE_XMA_REGISTER_CONTEXT_GROUP(0x0660, Unknown660)
// 0x066A..0x0681 : zero?
// 0x0682..0x0684 : ???
// 0x0685..0x068F : zero?
XE_XMA_REGISTER_CONTEXT_GROUP(0x0690, Lock)
// 0x069A..0x069F : zero?
XE_XMA_REGISTER_CONTEXT_GROUP(0x06A0, Clear)
//---------------------------------------------------------------------------//
// 0x0700..0x07FF : all 0xFFs?
// 0x0800..0x17FF : ???
// 0x1800..0x2FFF : all 0xFFs?
// 0x3000..0x30FF : ???
// 0x3100..0x3FFF : all 0xFFs?
#ifdef __XE_XMA_REGISTER_UNSET
#undef __XE_XMA_REGISTER_UNSET
#undef XE_XMA_REGISTER
#endif
================================================
FILE: src/xenia/base/README.md
================================================
A lightweight cross-platform/compiler compatibility library.
This library presupposes C++11/14 support. As more compilers get C++14 it will
assume that.
Other parts of the project use this to avoid creating spaghetti linkage. Code
specific to the emulator should be kept out, as not all of the projects that
depend on this need it.
Where possible, C++11/14 STL should be used instead of adding any code in here,
and the code should be kept as small as possible (by reusing STL/etc). Third
party dependencies should be kept to a minimum.
Target compilers:
* MSVC++ 2013+
* Clang 3.4+
* GCC 4.8+.
Target platforms:
* Windows 8+ (`_win.cc` suffix)
* Mac OSX 10.9+ (`_mac.cc` suffix, falling back to `_posix.cc`)
* Linux ? (`_posix.cc` suffix)
Avoid the use of platform-specific #ifdefs and instead try to put all
platform-specific code in the appropriately suffixed cc files.
================================================
FILE: src/xenia/base/app_win32.manifest
================================================
True/PMPerMonitorV2,PerMonitor
================================================
FILE: src/xenia/base/arena.cc
================================================
/**
******************************************************************************
* 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/base/arena.h"
#include
#include
#include "xenia/base/assert.h"
#include "xenia/base/math.h"
namespace xe {
Arena::Arena(size_t chunk_size)
: chunk_size_(chunk_size), head_chunk_(nullptr), active_chunk_(nullptr) {}
Arena::~Arena() {
Reset();
Chunk* chunk = head_chunk_;
while (chunk) {
Chunk* next = chunk->next;
delete chunk;
chunk = next;
}
head_chunk_ = nullptr;
}
void Arena::Reset() {
active_chunk_ = head_chunk_;
if (active_chunk_) {
active_chunk_->offset = 0;
}
}
void Arena::DebugFill() {
auto chunk = head_chunk_;
while (chunk) {
std::memset(chunk->buffer, 0xCD, chunk->capacity);
chunk = chunk->next;
}
}
void* Arena::Alloc(size_t size, size_t align) {
assert_true(
align > 0 && xe::is_pow2(align) && align <= 16,
"align needs to be a power of 2 and not greater than Chunk alignment");
// for alignment
const auto get_padding = [this, align]() -> size_t {
const size_t mask = align - 1;
size_t deviation = active_chunk_->offset & mask;
return (align - deviation) & mask;
};
if (active_chunk_) {
if (active_chunk_->capacity - active_chunk_->offset <
size + get_padding() + 4_KiB) {
Chunk* next = active_chunk_->next;
if (!next) {
assert_true(size + get_padding() < chunk_size_,
"need to support larger chunks");
next = new Chunk(chunk_size_);
active_chunk_->next = next;
}
next->offset = 0;
active_chunk_ = next;
}
} else {
head_chunk_ = active_chunk_ = new Chunk(chunk_size_);
}
active_chunk_->offset += get_padding();
uint8_t* p = active_chunk_->buffer + active_chunk_->offset;
active_chunk_->offset += size;
assert_true((reinterpret_cast(p) & (align - 1)) == 0,
"alignment failed");
return p;
}
void Arena::Rewind(size_t size) { active_chunk_->offset -= size; }
size_t Arena::CalculateSize() {
size_t total_length = 0;
Chunk* chunk = head_chunk_;
while (chunk) {
total_length += chunk->offset;
if (chunk == active_chunk_) {
break;
}
chunk = chunk->next;
}
return total_length;
}
void* Arena::CloneContents() {
size_t total_length = CalculateSize();
auto result = reinterpret_cast(malloc(total_length));
auto p = result;
Chunk* chunk = head_chunk_;
while (chunk) {
std::memcpy(p, chunk->buffer, chunk->offset);
p += chunk->offset;
if (chunk == active_chunk_) {
break;
}
chunk = chunk->next;
}
return result;
}
void Arena::CloneContents(void* buffer, size_t buffer_length) {
uint8_t* p = reinterpret_cast(buffer);
Chunk* chunk = head_chunk_;
while (chunk) {
std::memcpy(p, chunk->buffer, chunk->offset);
p += chunk->offset;
if (chunk == active_chunk_) {
break;
}
chunk = chunk->next;
}
}
Arena::Chunk::Chunk(size_t chunk_size)
: next(nullptr), capacity(chunk_size), buffer(0), offset(0) {
buffer = reinterpret_cast(malloc(capacity));
assert_true((reinterpret_cast(buffer) & size_t(15)) == 0,
"16 byte alignment required");
}
Arena::Chunk::~Chunk() {
if (buffer) {
free(buffer);
}
}
} // namespace xe
================================================
FILE: src/xenia/base/arena.h
================================================
/**
******************************************************************************
* 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. *
******************************************************************************
*/
#ifndef XENIA_BASE_ARENA_H_
#define XENIA_BASE_ARENA_H_
#include
#include
#include
#include "xenia/base/literals.h"
namespace xe {
using namespace xe::literals;
class Arena {
public:
explicit Arena(size_t chunk_size = 4_MiB);
~Arena();
void Reset();
void DebugFill();
void* Alloc(size_t size, size_t align);
template
T* Alloc() {
return reinterpret_cast(Alloc(sizeof(T), alignof(T)));
}
// When rewinding aligned allocations, any padding that was applied during
// allocation will be leaked
void Rewind(size_t size);
void* CloneContents();
template
void CloneContents(std::vector* buffer) {
buffer->resize(CalculateSize() / sizeof(T));
CloneContents(buffer->data(), buffer->size() * sizeof(T));
}
private:
class Chunk {
public:
explicit Chunk(size_t chunk_size);
~Chunk();
Chunk* next;
size_t capacity;
uint8_t* buffer;
size_t offset;
};
size_t CalculateSize();
void CloneContents(void* buffer, size_t buffer_length);
size_t chunk_size_;
Chunk* head_chunk_;
Chunk* active_chunk_;
};
} // namespace xe
#endif // XENIA_BASE_ARENA_H_
================================================
FILE: src/xenia/base/assert.h
================================================
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2014 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_BASE_ASSERT_H_
#define XENIA_BASE_ASSERT_H_
#include
#include "xenia/base/platform.h"
namespace xe {
#define static_assert_size(type, size) \
static_assert(sizeof(type) == size, \
"bad definition for " #type ": must be " #size " bytes")
// We rely on assert being compiled out in NDEBUG.
#define xenia_assert assert
#define __XENIA_EXPAND(x) x
#define __XENIA_ARGC(...) \
__XENIA_EXPAND(__XENIA_ARGC_IMPL(__VA_ARGS__, 15, 14, 13, 12, 11, 10, 9, 8, \
7, 6, 5, 4, 3, 2, 1, 0))
#define __XENIA_ARGC_IMPL(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, \
x13, x14, x15, N, ...) \
N
#define __XENIA_MACRO_DISPATCH(func, ...) \
__XENIA_MACRO_DISPATCH_(func, __XENIA_ARGC(__VA_ARGS__))
#define __XENIA_MACRO_DISPATCH_(func, nargs) \
__XENIA_MACRO_DISPATCH__(func, nargs)
#define __XENIA_MACRO_DISPATCH__(func, nargs) func##nargs
#define assert_always(...) xenia_assert(false)
#define assert_true(...) \
__XENIA_MACRO_DISPATCH(assert_true, __VA_ARGS__)(__VA_ARGS__)
#define assert_true1(expr) xenia_assert(expr)
#define assert_true2(expr, message) xenia_assert((expr) || !message)
#define assert_false(...) \
__XENIA_MACRO_DISPATCH(assert_false, __VA_ARGS__)(__VA_ARGS__)
#define assert_false1(expr) xenia_assert(!(expr))
#define assert_false2(expr, message) xenia_assert(!(expr) || !message)
#define assert_zero(...) \
__XENIA_MACRO_DISPATCH(assert_zero, __VA_ARGS__)(__VA_ARGS__)
#define assert_zero1(expr) xenia_assert((expr) == 0)
#define assert_zero2(expr, message) xenia_assert((expr) == 0 || !message)
#define assert_not_zero(...) \
__XENIA_MACRO_DISPATCH(assert_not_zero, __VA_ARGS__)(__VA_ARGS__)
#define assert_not_zero1(expr) xenia_assert((expr) != 0)
#define assert_not_zero2(expr, message) xenia_assert((expr) != 0 || !message)
#define assert_null(...) \
__XENIA_MACRO_DISPATCH(assert_null, __VA_ARGS__)(__VA_ARGS__)
#define assert_null1(expr) xenia_assert((expr) == nullptr)
#define assert_null2(expr, message) xenia_assert((expr) == nullptr || !message)
#define assert_not_null(...) \
__XENIA_MACRO_DISPATCH(assert_not_null, __VA_ARGS__)(__VA_ARGS__)
#define assert_not_null1(expr) xenia_assert((expr) != nullptr)
#define assert_not_null2(expr, message) \
xenia_assert((expr) != nullptr || !message)
#define assert_unhandled_case(variable) \
assert_always("unhandled switch(" #variable ") case")
} // namespace xe
#endif // XENIA_BASE_ASSERT_H_
================================================
FILE: src/xenia/base/atomic.h
================================================
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2014 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_BASE_ATOMIC_H_
#define XENIA_BASE_ATOMIC_H_
#include
#include "xenia/base/platform.h"
namespace xe {
#if XE_PLATFORM_WIN32
inline int32_t atomic_inc(volatile int32_t* value) {
return _InterlockedIncrement(reinterpret_cast(value));
}
inline int32_t atomic_dec(volatile int32_t* value) {
return _InterlockedDecrement(reinterpret_cast(value));
}
inline int32_t atomic_exchange(int32_t new_value, volatile int32_t* value) {
return _InterlockedExchange(reinterpret_cast(value),
new_value);
}
inline int64_t atomic_exchange(int64_t new_value, volatile int64_t* value) {
return _InterlockedExchange64(reinterpret_cast(value),
new_value);
}
inline int32_t atomic_exchange_add(int32_t amount, volatile int32_t* value) {
return _InterlockedExchangeAdd(reinterpret_cast(value),
amount);
}
inline int64_t atomic_exchange_add(int64_t amount, volatile int64_t* value) {
return _InterlockedExchangeAdd64(reinterpret_cast(value),
amount);
}
inline bool atomic_cas(int32_t old_value, int32_t new_value,
volatile int32_t* value) {
return _InterlockedCompareExchange(reinterpret_cast(value),
new_value, old_value) == old_value;
}
inline bool atomic_cas(int64_t old_value, int64_t new_value,
volatile int64_t* value) {
return _InterlockedCompareExchange64(
reinterpret_cast(value), new_value,
old_value) == old_value;
}
#elif XE_PLATFORM_LINUX || XE_PLATFORM_MAC
inline int32_t atomic_inc(volatile int32_t* value) {
return __sync_add_and_fetch(value, 1);
}
inline int32_t atomic_dec(volatile int32_t* value) {
return __sync_sub_and_fetch(value, 1);
}
inline int32_t atomic_exchange(int32_t new_value, volatile int32_t* value) {
return __sync_val_compare_and_swap(value, *value, new_value);
}
inline int64_t atomic_exchange(int64_t new_value, volatile int64_t* value) {
return __sync_val_compare_and_swap(value, *value, new_value);
}
inline int32_t atomic_exchange_add(int32_t amount, volatile int32_t* value) {
return __sync_fetch_and_add(value, amount);
}
inline int64_t atomic_exchange_add(int64_t amount, volatile int64_t* value) {
return __sync_fetch_and_add(value, amount);
}
inline bool atomic_cas(int32_t old_value, int32_t new_value,
volatile int32_t* value) {
return __sync_bool_compare_and_swap(
reinterpret_cast(value), old_value, new_value);
}
inline bool atomic_cas(int64_t old_value, int64_t new_value,
volatile int64_t* value) {
return __sync_bool_compare_and_swap(
reinterpret_cast(value), old_value, new_value);
}
#else
#error No atomic primitives defined for this platform/cpu combination.
#endif // XE_PLATFORM
inline uint32_t atomic_inc(volatile uint32_t* value) {
return static_cast(
atomic_inc(reinterpret_cast(value)));
}
inline uint32_t atomic_dec(volatile uint32_t* value) {
return static_cast(
atomic_dec(reinterpret_cast(value)));
}
inline uint32_t atomic_exchange(uint32_t new_value, volatile uint32_t* value) {
return static_cast(
atomic_exchange(static_cast(new_value),
reinterpret_cast(value)));
}
inline uint64_t atomic_exchange(uint64_t new_value, volatile uint64_t* value) {
return static_cast(
atomic_exchange(static_cast(new_value),
reinterpret_cast(value)));
}
inline uint32_t atomic_exchange_add(uint32_t amount, volatile uint32_t* value) {
return static_cast(
atomic_exchange_add(static_cast(amount),
reinterpret_cast(value)));
}
inline uint64_t atomic_exchange_add(uint64_t amount, volatile uint64_t* value) {
return static_cast(
atomic_exchange_add(static_cast(amount),
reinterpret_cast(value)));
}
inline bool atomic_cas(uint32_t old_value, uint32_t new_value,
volatile uint32_t* value) {
return atomic_cas(static_cast(old_value),
static_cast(new_value),
reinterpret_cast(value));
}
inline bool atomic_cas(uint64_t old_value, uint64_t new_value,
volatile uint64_t* value) {
return atomic_cas(static_cast(old_value),
static_cast(new_value),
reinterpret_cast(value));
}
} // namespace xe
#endif // XENIA_BASE_ATOMIC_H_
================================================
FILE: src/xenia/base/bit_map.cc
================================================
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2015 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/base/bit_map.h"
#include "xenia/base/assert.h"
#include "xenia/base/atomic.h"
#include "xenia/base/math.h"
namespace xe {
BitMap::BitMap() = default;
BitMap::BitMap(size_t size_bits) { Resize(size_bits); }
BitMap::BitMap(uint64_t* data, size_t size_bits) {
assert_true(size_bits % kDataSizeBits == 0);
data_.resize(size_bits / kDataSizeBits);
std::memcpy(data_.data(), data, size_bits / kDataSizeBits);
}
size_t BitMap::Acquire() {
for (size_t i = 0; i < data_.size(); i++) {
uint64_t entry = 0;
uint64_t new_entry = 0;
int64_t acquired_idx = -1;
do {
entry = data_[i];
uint8_t index = lzcnt(entry);
if (index == kDataSizeBits) {
// None free.
acquired_idx = -1;
break;
}
// Entry has a free bit. Acquire it.
uint64_t bit = 1ull << (kDataSizeBits - index - 1);
new_entry = entry & ~bit;
assert_not_zero(entry & bit);
acquired_idx = index;
} while (!atomic_cas(entry, new_entry, &data_[i]));
if (acquired_idx != -1) {
// Acquired.
return (i * kDataSizeBits) + acquired_idx;
}
}
return -1;
}
void BitMap::Release(size_t index) {
auto slot = index / kDataSizeBits;
index -= slot * kDataSizeBits;
uint64_t bit = 1ull << (kDataSizeBits - index - 1);
uint64_t entry = 0;
uint64_t new_entry = 0;
do {
entry = data_[slot];
assert_zero(entry & bit);
new_entry = entry | bit;
} while (!atomic_cas(entry, new_entry, &data_[slot]));
}
void BitMap::Resize(size_t new_size_bits) {
auto old_size = data_.size();
assert_true(new_size_bits % kDataSizeBits == 0);
data_.resize(new_size_bits / kDataSizeBits);
// Initialize new entries.
if (data_.size() > old_size) {
for (size_t i = old_size; i < data_.size(); i++) {
data_[i] = -1;
}
}
}
void BitMap::Reset() {
for (size_t i = 0; i < data_.size(); i++) {
data_[i] = -1;
}
}
} // namespace xe
================================================
FILE: src/xenia/base/bit_map.h
================================================
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2015 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_BASE_BIT_MAP_H_
#define XENIA_BASE_BIT_MAP_H_
#include
#include
#include
namespace xe {
// Bit Map: Efficient lookup of free/used entries.
class BitMap {
public:
BitMap();
// Size is the number of entries, must be a multiple of 64.
BitMap(size_t size_bits);
// Data does not have to be aligned to a 4-byte boundary, but it is
// preferable.
// Size is the number of entries, must be a multiple of 64.
BitMap(uint64_t* data, size_t size_bits);
// (threadsafe) Acquires an entry and returns its index. Returns -1 if there
// are no more free entries.
size_t Acquire();
// (threadsafe) Releases an entry by an index.
void Release(size_t index);
// Resize the bitmap. Size is the number of entries, must be a multiple of 64.
void Resize(size_t new_size_bits);
// Sets all entries to free.
void Reset();
const std::vector data() const { return data_; }
std::vector& data() { return data_; }
private:
const static size_t kDataSize = 8;
const static size_t kDataSizeBits = kDataSize * 8;
std::vector data_;
};
} // namespace xe
#endif // XENIA_BASE_BIT_MAP_H_
================================================
FILE: src/xenia/base/bit_range.h
================================================
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2019 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_BASE_BIT_RANGE_H_
#define XENIA_BASE_BIT_RANGE_H_
#include
#include
#include
#include
#include
#include "xenia/base/math.h"
namespace xe {
namespace bit_range {
// Provided length is in bits since the first. Returns of the
// range in bits, with length == 0 if not found.
template
std::pair