Merge branch 'master' of https://github.com/xenia-project/xenia into canary_new

This commit is contained in:
Gliniak 2020-11-25 19:15:02 +01:00
commit b7ad547eef
132 changed files with 3074 additions and 1021 deletions

6
.gitmodules vendored
View File

@ -67,3 +67,9 @@
[submodule "third_party/premake-cmake"] [submodule "third_party/premake-cmake"]
path = third_party/premake-cmake path = third_party/premake-cmake
url = https://github.com/Enhex/premake-cmake.git url = https://github.com/Enhex/premake-cmake.git
[submodule "third_party/premake-androidmk"]
path = third_party/premake-androidmk
url = https://github.com/Triang3l/premake-androidmk.git
[submodule "third_party/date"]
path = third_party/date
url = https://github.com/HowardHinnant/date.git

View File

@ -69,8 +69,8 @@ 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) * Help work through [missing functionality/bugs in games](https://github.com/xenia-project/xenia/labels/compat)
* Add input drivers for [DualShock4 (PS4) controllers](https://github.com/xenia-project/xenia/issues/60) (or anything else) * Add input drivers for [DualShock4 (PS4) controllers](https://github.com/xenia-project/xenia/issues/60) (or anything else)
* Skilled with Linux? A strong contributor is needed to [help with porting](https://github.com/xenia-project/xenia/labels/cross%20platform)
<!-- <!--
* 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 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. something.
--> -->

View File

@ -0,0 +1,15 @@
*.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

View File

@ -0,0 +1 @@
/build

View File

@ -0,0 +1,79 @@
plugins {
id 'com.android.application'
}
android {
compileSdkVersion 30
buildToolsVersion "30.0.2"
ndkVersion '22.0.6917172 rc1'
defaultConfig {
applicationId "jp.xenia.emulator"
// 24 (7.0) - Vulkan.
minSdkVersion 24
targetSdkVersion 30
versionCode 1
versionName "Prototype"
externalNativeBuild {
ndkBuild {
arguments "NDK_APPLICATION_MK:=../../../build/xenia_Application.mk"
}
}
ndk {
abiFilters 'arm64-v8a'
}
}
buildTypes {
release {
externalNativeBuild {
ndkBuild {
arguments "PM5_CONFIG:=release_android"
}
}
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
debug {
applicationIdSuffix ".debug"
debuggable true
externalNativeBuild {
ndkBuild {
arguments "PM5_CONFIG:=debug_android"
}
}
}
checked {
applicationIdSuffix ".checked"
debuggable true
externalNativeBuild {
ndkBuild {
arguments "PM5_CONFIG:=checked_android"
}
}
}
}
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_Android.mk')
}
}
}

View File

@ -0,0 +1,21 @@
# 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

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="jp.xenia.emulator">
<uses-feature android:name="android.hardware.vulkan.level" android:version="0" android:required="true" />
<uses-feature android:name="android.hardware.vulkan.version" android:version="0x400000" android:required="true" />
<!-- Granted automatically - guest sockets -->
<uses-permission android:name="android.permission.INTERNET" />
<!-- Needs to be requested - loading games from outside the app data directory -->
<!-- WRITE_EXTERNAL_STORAGE is not required to write to the external app data directory since API 19 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@android:style/Theme.Material.Light">
<activity android:name="jp.xenia.emulator.DemoActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

View File

@ -0,0 +1,12 @@
package jp.xenia.emulator;
import android.app.Activity;
import android.os.Bundle;
public class DemoActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_demo);
}
}

View File

@ -0,0 +1,30 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="85.84757"
android:endY="92.4963"
android:startX="42.9492"
android:startY="49.59793"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>

View File

@ -0,0 +1,170 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
</vector>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="jp.xenia.emulator.DemoActivity">
</RelativeLayout>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -0,0 +1,3 @@
<resources>
<string name="app_name">Xenia</string>
</resources>

View File

@ -0,0 +1,24 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath "com.android.tools.build:gradle:4.1.1"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}

View File

@ -0,0 +1,19 @@
# 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

Binary file not shown.

View File

@ -0,0 +1,6 @@
#Sat Nov 21 20:44:11 MSK 2020
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip

172
android/android_studio_project/gradlew vendored Normal file
View File

@ -0,0 +1,172 @@
#!/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" "$@"

View File

@ -0,0 +1,84 @@
@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

View File

@ -0,0 +1,2 @@
include ':app'
rootProject.name = "Xenia"

View File

@ -119,6 +119,87 @@ Registers documented at [src/xenia/gpu/register_table.inc](../src/xenia/gpu/regi
PM4 commands documented at [src/xenia/gpu/xenos.h](../src/xenia/gpu/xenos.h#L521). 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 ### Shaders
* [LLVM R600 Tables](https://llvm.org/viewvc/llvm-project/llvm/trunk/lib/Target/AMDGPU/R600Instructions.td) * [LLVM R600 Tables](https://llvm.org/viewvc/llvm-project/llvm/trunk/lib/Target/AMDGPU/R600Instructions.td)

View File

@ -1,6 +1,9 @@
include("tools/build") include("tools/build")
require("third_party/premake-export-compile-commands/export-compile-commands") require("third_party/premake-export-compile-commands/export-compile-commands")
require("third_party/premake-cmake/cmake") require("third_party/premake-cmake/cmake")
-- gmake required for androidmk.
require("gmake")
require("third_party/premake-androidmk/androidmk")
location(build_root) location(build_root)
targetdir(build_bin) targetdir(build_bin)
@ -26,6 +29,8 @@ defines({
}) })
cppdialect("C++17") cppdialect("C++17")
exceptionhandling("On")
rtti("On")
symbols("On") symbols("On")
-- TODO(DrChat): Find a way to disable this on other architectures. -- TODO(DrChat): Find a way to disable this on other architectures.
@ -81,10 +86,15 @@ filter("configurations:Release")
}) })
optimize("Speed") optimize("Speed")
inlining("Auto") inlining("Auto")
floatingpoint("Fast")
flags({ flags({
"LinkTimeOptimization", "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") filter("platforms:Linux")
system("linux") system("linux")
toolset("clang") toolset("clang")
@ -138,6 +148,13 @@ filter({"platforms:Linux", "language:C++", "toolset:clang", "files:*.cc or *.cpp
"-stdlib=libstdc++", "-stdlib=libstdc++",
}) })
filter("platforms:Android")
system("android")
links({
"android",
"dl",
})
filter("platforms:Windows") filter("platforms:Windows")
system("windows") system("windows")
toolset("msc") toolset("msc")
@ -189,18 +206,24 @@ end
solution("xenia") solution("xenia")
uuid("931ef4b0-6170-4f7a-aaf2-0fece7632747") uuid("931ef4b0-6170-4f7a-aaf2-0fece7632747")
startproject("xenia-app") startproject("xenia-app")
architecture("x86_64") if os.istarget("android") then
if os.istarget("linux") then -- Not setting architecture as that's handled by ndk-build itself.
platforms({"Linux"}) platforms({"Android"})
elseif os.istarget("windows") then ndkstl("c++_static")
platforms({"Windows"}) else
-- 10.0.15063.0: ID3D12GraphicsCommandList1::SetSamplePositions. architecture("x86_64")
-- 10.0.19041.0: D3D12_HEAP_FLAG_CREATE_NOT_ZEROED. if os.istarget("linux") then
filter("action:vs2017") platforms({"Linux"})
systemversion("10.0.19041.0") elseif os.istarget("windows") then
filter("action:vs2019") platforms({"Windows"})
systemversion("10.0") -- 10.0.15063.0: ID3D12GraphicsCommandList1::SetSamplePositions.
filter({}) -- 10.0.19041.0: D3D12_HEAP_FLAG_CREATE_NOT_ZEROED.
filter("action:vs2017")
systemversion("10.0.19041.0")
filter("action:vs2019")
systemversion("10.0")
filter({})
end
end end
configurations({"Checked", "Debug", "Release"}) configurations({"Checked", "Debug", "Release"})
@ -215,18 +238,15 @@ solution("xenia")
include("third_party/imgui.lua") include("third_party/imgui.lua")
include("third_party/libav.lua") include("third_party/libav.lua")
include("third_party/mspack.lua") include("third_party/mspack.lua")
include("third_party/SDL2.lua")
include("third_party/snappy.lua") include("third_party/snappy.lua")
include("third_party/spirv-tools.lua") include("third_party/spirv-tools.lua")
include("third_party/volk.lua") include("third_party/volk.lua")
include("third_party/xxhash.lua") include("third_party/xxhash.lua")
include("src/xenia") include("src/xenia")
include("src/xenia/app")
include("src/xenia/app/discord") include("src/xenia/app/discord")
include("src/xenia/apu") include("src/xenia/apu")
include("src/xenia/apu/nop") include("src/xenia/apu/nop")
include("src/xenia/apu/sdl")
include("src/xenia/base") include("src/xenia/base")
include("src/xenia/cpu") include("src/xenia/cpu")
include("src/xenia/cpu/backend/x64") include("src/xenia/cpu/backend/x64")
@ -234,10 +254,8 @@ solution("xenia")
include("src/xenia/gpu") include("src/xenia/gpu")
include("src/xenia/gpu/null") include("src/xenia/gpu/null")
include("src/xenia/gpu/vulkan") include("src/xenia/gpu/vulkan")
include("src/xenia/helper/sdl")
include("src/xenia/hid") include("src/xenia/hid")
include("src/xenia/hid/nop") include("src/xenia/hid/nop")
include("src/xenia/hid/sdl")
include("src/xenia/kernel") include("src/xenia/kernel")
include("src/xenia/patcher") include("src/xenia/patcher")
include("src/xenia/ui") include("src/xenia/ui")
@ -245,6 +263,24 @@ solution("xenia")
include("src/xenia/ui/vulkan") include("src/xenia/ui/vulkan")
include("src/xenia/vfs") include("src/xenia/vfs")
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")
include("src/xenia/apu/sdl")
include("src/xenia/helper/sdl")
include("src/xenia/hid/sdl")
-- TODO(Triang3l): src/xenia/app has a dependency on xenia-helper-sdl, bring
-- it back later.
include("src/xenia/app")
end
if os.istarget("windows") then if os.istarget("windows") then
include("src/xenia/apu/xaudio2") include("src/xenia/apu/xaudio2")
include("src/xenia/gpu/d3d12") include("src/xenia/gpu/d3d12")

View File

@ -43,9 +43,10 @@ void DiscordPresence::NotPlaying() {
} }
void DiscordPresence::PlayingTitle(const std::string_view game_title) { void DiscordPresence::PlayingTitle(const std::string_view game_title) {
auto details = std::string(game_title);
DiscordRichPresence discordPresence = {}; DiscordRichPresence discordPresence = {};
discordPresence.state = "In Game"; discordPresence.state = "In Game";
discordPresence.details = std::string(game_title).c_str(); discordPresence.details = details.c_str();
// TODO(gibbed): we don't have state icons yet. // TODO(gibbed): we don't have state icons yet.
// discordPresence.smallImageKey = "app"; // discordPresence.smallImageKey = "app";
// discordPresence.largeImageKey = "state_ingame"; // discordPresence.largeImageKey = "state_ingame";

View File

@ -216,7 +216,7 @@ int xenia_main(const std::vector<std::string>& args) {
if (!cvars::portable && if (!cvars::portable &&
!std::filesystem::exists(storage_root / "portable.txt")) { !std::filesystem::exists(storage_root / "portable.txt")) {
storage_root = xe::filesystem::GetUserFolder(); storage_root = xe::filesystem::GetUserFolder();
#if defined(XE_PLATFORM_WIN32) || defined(XE_PLATFORM_LINUX) #if defined(XE_PLATFORM_WIN32) || defined(XE_PLATFORM_GNU_LINUX)
storage_root = storage_root / "Xenia"; storage_root = storage_root / "Xenia";
#else #else
#warning Unhandled platform for the data root. #warning Unhandled platform for the data root.

View File

@ -15,6 +15,10 @@
#include "xenia/base/assert.h" #include "xenia/base/assert.h"
#include "xenia/base/platform.h" #include "xenia/base/platform.h"
#if XE_PLATFORM_LINUX
#include <byteswap.h>
#endif
namespace xe { namespace xe {
#if XE_PLATFORM_WIN32 #if XE_PLATFORM_WIN32
@ -26,9 +30,9 @@ namespace xe {
#define XENIA_BASE_BYTE_SWAP_32 OSSwapInt32 #define XENIA_BASE_BYTE_SWAP_32 OSSwapInt32
#define XENIA_BASE_BYTE_SWAP_64 OSSwapInt64 #define XENIA_BASE_BYTE_SWAP_64 OSSwapInt64
#else #else
#define XENIA_BASE_BYTE_SWAP_16 __bswap_16 #define XENIA_BASE_BYTE_SWAP_16 bswap_16
#define XENIA_BASE_BYTE_SWAP_32 __bswap_32 #define XENIA_BASE_BYTE_SWAP_32 bswap_32
#define XENIA_BASE_BYTE_SWAP_64 __bswap_64 #define XENIA_BASE_BYTE_SWAP_64 bswap_64
#endif // XE_PLATFORM_WIN32 #endif // XE_PLATFORM_WIN32
inline int8_t byte_swap(int8_t value) { return value; } inline int8_t byte_swap(int8_t value) { return value; }

View File

@ -36,10 +36,8 @@
#include "third_party/fmt/include/fmt/format.h" #include "third_party/fmt/include/fmt/format.h"
DEFINE_path( DEFINE_path(log_file, "", "Logs are written to the given file", "Logging");
log_file, "", DEFINE_bool(log_to_stdout, true, "Write log output to stdout", "Logging");
"Logs are written to the given file (specify stdout for command line)",
"Logging");
DEFINE_bool(log_to_debugprint, false, "Dump the log to DebugPrint.", "Logging"); DEFINE_bool(log_to_debugprint, false, "Dump the log to DebugPrint.", "Logging");
DEFINE_bool(flush_log, true, "Flush log file after each log line batch.", DEFINE_bool(flush_log, true, "Flush log file after each log line batch.",
"Logging"); "Logging");
@ -66,31 +64,27 @@ struct LogLine {
thread_local char thread_log_buffer_[64 * 1024]; thread_local char thread_log_buffer_[64 * 1024];
void FileLogSink::Write(const char* buf, size_t size) {
if (file_) {
fwrite(buf, 1, size, file_);
}
}
void FileLogSink::Flush() {
if (file_) {
fflush(file_);
}
}
class Logger { class Logger {
public: public:
explicit Logger(const std::string_view app_name) explicit Logger(const std::string_view app_name)
: file_(nullptr), : wait_strategy_(),
running_(true),
wait_strategy_(),
claim_strategy_(kBlockCount, wait_strategy_), claim_strategy_(kBlockCount, wait_strategy_),
consumed_(wait_strategy_) { consumed_(wait_strategy_),
running_(true) {
claim_strategy_.add_claim_barrier(consumed_); claim_strategy_.add_claim_barrier(consumed_);
if (cvars::log_file.empty()) {
// Default to app name.
auto file_name = fmt::format("{}.log", app_name);
auto file_path = std::filesystem::path(file_name);
xe::filesystem::CreateParentFolder(file_path);
file_ = xe::filesystem::OpenFile(file_path, "wt");
} else {
if (cvars::log_file == "stdout") {
file_ = stdout;
} else {
xe::filesystem::CreateParentFolder(cvars::log_file);
file_ = xe::filesystem::OpenFile(cvars::log_file, "wt");
}
}
write_thread_ = write_thread_ =
xe::threading::Thread::Create({}, [this]() { WriteThread(); }); xe::threading::Thread::Create({}, [this]() { WriteThread(); });
write_thread_->set_name("Logging Writer"); write_thread_->set_name("Logging Writer");
@ -99,8 +93,10 @@ class Logger {
~Logger() { ~Logger() {
running_ = false; running_ = false;
xe::threading::Wait(write_thread_.get(), true); xe::threading::Wait(write_thread_.get(), true);
fflush(file_); }
fclose(file_);
void AddLogSink(std::unique_ptr<LogSink>&& sink) {
sinks_.push_back(std::move(sink));
} }
private: private:
@ -126,14 +122,14 @@ class Logger {
dp::multi_threaded_claim_strategy<dp::spin_wait_strategy> claim_strategy_; dp::multi_threaded_claim_strategy<dp::spin_wait_strategy> claim_strategy_;
dp::sequence_barrier<dp::spin_wait_strategy> consumed_; dp::sequence_barrier<dp::spin_wait_strategy> consumed_;
FILE* file_; std::vector<std::unique_ptr<LogSink>> sinks_;
std::atomic<bool> running_; std::atomic<bool> running_;
std::unique_ptr<xe::threading::Thread> write_thread_; std::unique_ptr<xe::threading::Thread> write_thread_;
void Write(const char* buf, size_t size) { void Write(const char* buf, size_t size) {
if (file_) { for (const auto& sink : sinks_) {
fwrite(buf, 1, size, file_); sink->Write(buf, size);
} }
if (cvars::log_to_debugprint) { if (cvars::log_to_debugprint) {
debugging::DebugPrint("{}", std::string_view(buf, size)); debugging::DebugPrint("{}", std::string_view(buf, size));
@ -246,7 +242,9 @@ class Logger {
desired_count = 1; desired_count = 1;
if (cvars::flush_log) { if (cvars::flush_log) {
fflush(file_); for (const auto& sink : sinks_) {
sink->Flush();
}
} }
idle_loops = 0; idle_loops = 0;
@ -291,6 +289,27 @@ class Logger {
void InitializeLogging(const std::string_view app_name) { void InitializeLogging(const std::string_view app_name) {
auto mem = memory::AlignedAlloc<Logger>(0x10); auto mem = memory::AlignedAlloc<Logger>(0x10);
logger_ = new (mem) Logger(app_name); logger_ = new (mem) Logger(app_name);
FILE* log_file = nullptr;
if (cvars::log_file.empty()) {
// Default to app name.
auto file_name = fmt::format("{}.log", app_name);
auto file_path = std::filesystem::path(file_name);
xe::filesystem::CreateParentFolder(file_path);
log_file = xe::filesystem::OpenFile(file_path, "wt");
} else {
xe::filesystem::CreateParentFolder(cvars::log_file);
log_file = xe::filesystem::OpenFile(cvars::log_file, "wt");
}
auto sink = std::make_unique<FileLogSink>(log_file);
logger_->AddLogSink(std::move(sink));
if (cvars::log_to_stdout) {
auto stdout_sink = std::make_unique<FileLogSink>(stdout);
logger_->AddLogSink(std::move(stdout_sink));
}
} }
void ShutdownLogging() { void ShutdownLogging() {

View File

@ -34,6 +34,31 @@ enum class LogLevel {
Trace, Trace,
}; };
class LogSink {
public:
virtual ~LogSink() = default;
virtual void Write(const char* buf, size_t size) = 0;
virtual void Flush() = 0;
};
class FileLogSink final : public LogSink {
public:
explicit FileLogSink(FILE* file) : file_(file) {}
virtual ~FileLogSink() {
if (file_) {
fflush(file_);
fclose(file_);
}
}
void Write(const char* buf, size_t size) override;
void Flush() override;
private:
FILE* file_;
};
// Initializes the logging system and any outputs requested. // Initializes the logging system and any outputs requested.
// Must be called on startup. // Must be called on startup.
void InitializeLogging(const std::string_view app_name); void InitializeLogging(const std::string_view app_name);

View File

@ -29,6 +29,8 @@
DEFINE_bool(win32_high_freq, true, DEFINE_bool(win32_high_freq, true,
"Requests high performance from the NT kernel", "Kernel"); "Requests high performance from the NT kernel", "Kernel");
DEFINE_bool(enable_console, false, "Open a console window with the main window",
"General");
namespace xe { namespace xe {
@ -37,27 +39,23 @@ bool has_console_attached_ = true;
bool has_console_attached() { return has_console_attached_; } bool has_console_attached() { return has_console_attached_; }
void AttachConsole() { void AttachConsole() {
bool has_console = ::AttachConsole(ATTACH_PARENT_PROCESS) == TRUE; if (!cvars::enable_console) {
if (!has_console) {
// We weren't launched from a console, so just return.
// We could alloc our own console, but meh:
// has_console = AllocConsole() == TRUE;
has_console_attached_ = false;
return; return;
} }
AllocConsole();
has_console_attached_ = true; has_console_attached_ = true;
auto std_handle = (intptr_t)GetStdHandle(STD_OUTPUT_HANDLE); auto std_handle = (intptr_t)GetStdHandle(STD_OUTPUT_HANDLE);
auto con_handle = _open_osfhandle(std_handle, _O_TEXT); auto con_handle = _open_osfhandle(std_handle, _O_TEXT);
auto fp = _fdopen(con_handle, "w"); auto fp = _fdopen(con_handle, "w");
*stdout = *fp; freopen_s(&fp, "CONOUT$", "w", stdout);
setvbuf(stdout, nullptr, _IONBF, 0);
std_handle = (intptr_t)GetStdHandle(STD_ERROR_HANDLE); std_handle = (intptr_t)GetStdHandle(STD_ERROR_HANDLE);
con_handle = _open_osfhandle(std_handle, _O_TEXT); con_handle = _open_osfhandle(std_handle, _O_TEXT);
fp = _fdopen(con_handle, "w"); fp = _fdopen(con_handle, "w");
*stderr = *fp; freopen_s(&fp, "CONOUT$", "w", stderr);
setvbuf(stderr, nullptr, _IONBF, 0);
} }
static void RequestHighPerformance() { static void RequestHighPerformance() {
@ -125,6 +123,10 @@ int Main() {
return 1; return 1;
} }
// Attach a console so we can write output to stdout. If the user hasn't
// redirected output themselves it'll pop up a window.
xe::AttachConsole();
// Setup COM on the main thread. // Setup COM on the main thread.
// NOTE: this may fail if COM has already been initialized - that's OK. // NOTE: this may fail if COM has already been initialized - that's OK.
#pragma warning(suppress : 6031) #pragma warning(suppress : 6031)
@ -163,10 +165,6 @@ int main(int argc_ignored, char** argv_ignored) { return xe::Main(); }
// Used in windowed apps; automatically picked based on subsystem. // Used in windowed apps; automatically picked based on subsystem.
int WINAPI wWinMain(HINSTANCE, HINSTANCE, LPWSTR command_line, int) { int WINAPI wWinMain(HINSTANCE, HINSTANCE, LPWSTR command_line, int) {
// Attach a console so we can write output to stdout. If the user hasn't
// redirected output themselves it'll pop up a window.
xe::AttachConsole();
// Run normal entry point. // Run normal entry point.
return xe::Main(); return xe::Main();
} }

View File

@ -22,6 +22,11 @@ namespace xe {
class Win32MappedMemory : public MappedMemory { class Win32MappedMemory : public MappedMemory {
public: public:
// CreateFile returns INVALID_HANDLE_VALUE in case of failure.
static constexpr HANDLE kFileHandleInvalid = INVALID_HANDLE_VALUE;
// CreateFileMapping returns nullptr in case of failure.
static constexpr HANDLE kMappingHandleInvalid = nullptr;
Win32MappedMemory(const std::filesystem::path& path, Mode mode) Win32MappedMemory(const std::filesystem::path& path, Mode mode)
: MappedMemory(path, mode) {} : MappedMemory(path, mode) {}
@ -29,10 +34,10 @@ class Win32MappedMemory : public MappedMemory {
if (data_) { if (data_) {
UnmapViewOfFile(data_); UnmapViewOfFile(data_);
} }
if (mapping_handle != INVALID_HANDLE_VALUE) { if (mapping_handle != kMappingHandleInvalid) {
CloseHandle(mapping_handle); CloseHandle(mapping_handle);
} }
if (file_handle != INVALID_HANDLE_VALUE) { if (file_handle != kFileHandleInvalid) {
CloseHandle(file_handle); CloseHandle(file_handle);
} }
} }
@ -42,11 +47,11 @@ class Win32MappedMemory : public MappedMemory {
UnmapViewOfFile(data_); UnmapViewOfFile(data_);
data_ = nullptr; data_ = nullptr;
} }
if (mapping_handle != INVALID_HANDLE_VALUE) { if (mapping_handle != kMappingHandleInvalid) {
CloseHandle(mapping_handle); CloseHandle(mapping_handle);
mapping_handle = INVALID_HANDLE_VALUE; mapping_handle = kMappingHandleInvalid;
} }
if (file_handle != INVALID_HANDLE_VALUE) { if (file_handle != kFileHandleInvalid) {
if (truncate_size) { if (truncate_size) {
LONG distance_high = truncate_size >> 32; LONG distance_high = truncate_size >> 32;
SetFilePointer(file_handle, truncate_size & 0xFFFFFFFF, &distance_high, SetFilePointer(file_handle, truncate_size & 0xFFFFFFFF, &distance_high,
@ -55,7 +60,7 @@ class Win32MappedMemory : public MappedMemory {
} }
CloseHandle(file_handle); CloseHandle(file_handle);
file_handle = INVALID_HANDLE_VALUE; file_handle = kFileHandleInvalid;
} }
} }
@ -65,8 +70,13 @@ class Win32MappedMemory : public MappedMemory {
size_t aligned_length = length + (offset - aligned_offset); size_t aligned_length = length + (offset - aligned_offset);
UnmapViewOfFile(data_); UnmapViewOfFile(data_);
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
data_ = MapViewOfFile(mapping_handle, view_access_, aligned_offset >> 32, data_ = MapViewOfFile(mapping_handle, view_access_, aligned_offset >> 32,
aligned_offset & 0xFFFFFFFF, aligned_length); aligned_offset & 0xFFFFFFFF, aligned_length);
#else
data_ = MapViewOfFileFromApp(mapping_handle, ULONG(view_access_),
ULONG64(aligned_offset), aligned_length);
#endif
if (!data_) { if (!data_) {
return false; return false;
} }
@ -83,8 +93,8 @@ class Win32MappedMemory : public MappedMemory {
return true; return true;
} }
HANDLE file_handle = INVALID_HANDLE_VALUE; HANDLE file_handle = kFileHandleInvalid;
HANDLE mapping_handle = INVALID_HANDLE_VALUE; HANDLE mapping_handle = kMappingHandleInvalid;
DWORD view_access_ = 0; DWORD view_access_ = 0;
}; };
@ -125,20 +135,32 @@ std::unique_ptr<MappedMemory> MappedMemory::Open(
mm->file_handle = CreateFile(path.c_str(), file_access, file_share, nullptr, mm->file_handle = CreateFile(path.c_str(), file_access, file_share, nullptr,
create_mode, FILE_ATTRIBUTE_NORMAL, nullptr); create_mode, FILE_ATTRIBUTE_NORMAL, nullptr);
if (mm->file_handle == INVALID_HANDLE_VALUE) { if (mm->file_handle == Win32MappedMemory::kFileHandleInvalid) {
return nullptr; return nullptr;
} }
mm->mapping_handle = CreateFileMapping(mm->file_handle, nullptr, #if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
mapping_protect, aligned_length >> 32, mm->mapping_handle = CreateFileMapping(
aligned_length & 0xFFFFFFFF, nullptr); mm->file_handle, nullptr, mapping_protect, DWORD(aligned_length >> 32),
if (mm->mapping_handle == INVALID_HANDLE_VALUE) { DWORD(aligned_length), nullptr);
#else
mm->mapping_handle =
CreateFileMappingFromApp(mm->file_handle, nullptr, ULONG(mapping_protect),
ULONG64(aligned_length), nullptr);
#endif
if (mm->mapping_handle == Win32MappedMemory::kMappingHandleInvalid) {
return nullptr; return nullptr;
} }
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
mm->data_ = reinterpret_cast<uint8_t*>(MapViewOfFile( mm->data_ = reinterpret_cast<uint8_t*>(MapViewOfFile(
mm->mapping_handle, view_access, static_cast<DWORD>(aligned_offset >> 32), mm->mapping_handle, view_access, DWORD(aligned_offset >> 32),
static_cast<DWORD>(aligned_offset & 0xFFFFFFFF), aligned_length)); DWORD(aligned_offset), aligned_length));
#else
mm->data_ = reinterpret_cast<uint8_t*>(
MapViewOfFileFromApp(mm->mapping_handle, ULONG(view_access),
ULONG64(aligned_offset), aligned_length));
#endif
if (!mm->data_) { if (!mm->data_) {
return nullptr; return nullptr;
} }
@ -203,8 +225,8 @@ class Win32ChunkedMappedMemoryWriter : public ChunkedMappedMemoryWriter {
class Chunk { class Chunk {
public: public:
explicit Chunk(size_t capacity) explicit Chunk(size_t capacity)
: file_handle_(0), : file_handle_(Win32MappedMemory::kFileHandleInvalid),
mapping_handle_(0), mapping_handle_(Win32MappedMemory::kMappingHandleInvalid),
data_(nullptr), data_(nullptr),
offset_(0), offset_(0),
capacity_(capacity), capacity_(capacity),
@ -214,10 +236,10 @@ class Win32ChunkedMappedMemoryWriter : public ChunkedMappedMemoryWriter {
if (data_) { if (data_) {
UnmapViewOfFile(data_); UnmapViewOfFile(data_);
} }
if (mapping_handle_) { if (mapping_handle_ != Win32MappedMemory::kMappingHandleInvalid) {
CloseHandle(mapping_handle_); CloseHandle(mapping_handle_);
} }
if (file_handle_) { if (file_handle_ != Win32MappedMemory::kFileHandleInvalid) {
CloseHandle(file_handle_); CloseHandle(file_handle_);
} }
} }
@ -231,14 +253,20 @@ class Win32ChunkedMappedMemoryWriter : public ChunkedMappedMemoryWriter {
file_handle_ = CreateFile(path.c_str(), file_access, file_share, nullptr, file_handle_ = CreateFile(path.c_str(), file_access, file_share, nullptr,
create_mode, FILE_ATTRIBUTE_NORMAL, nullptr); create_mode, FILE_ATTRIBUTE_NORMAL, nullptr);
if (!file_handle_) { if (file_handle_ == Win32MappedMemory::kFileHandleInvalid) {
return false; return false;
} }
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
mapping_handle_ = mapping_handle_ =
CreateFileMapping(file_handle_, nullptr, mapping_protect, 0, CreateFileMapping(file_handle_, nullptr, mapping_protect,
static_cast<DWORD>(capacity_), nullptr); DWORD(capacity_ >> 32), DWORD(capacity_), nullptr);
if (!mapping_handle_) { #else
mapping_handle_ = CreateFileMappingFromApp(file_handle_, nullptr,
ULONG(mapping_protect),
ULONG64(capacity_), nullptr);
#endif
if (mapping_handle_ == Win32MappedMemory::kMappingHandleInvalid) {
return false; return false;
} }
@ -247,10 +275,32 @@ class Win32ChunkedMappedMemoryWriter : public ChunkedMappedMemoryWriter {
if (low_address_space) { if (low_address_space) {
bool successful = false; bool successful = false;
data_ = reinterpret_cast<uint8_t*>(0x10000000); data_ = reinterpret_cast<uint8_t*>(0x10000000);
#if !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
HANDLE process = GetCurrentProcess();
#endif
for (int i = 0; i < 1000; ++i) { for (int i = 0; i < 1000; ++i) {
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
if (MapViewOfFileEx(mapping_handle_, view_access, 0, 0, capacity_, if (MapViewOfFileEx(mapping_handle_, view_access, 0, 0, capacity_,
data_)) { data_)) {
successful = true; successful = true;
}
#else
// VirtualAlloc2FromApp and MapViewOfFile3FromApp were added in
// 10.0.17134.0.
// https://docs.microsoft.com/en-us/uwp/win32-and-com/win32-apis
if (VirtualAlloc2FromApp(process, data_, capacity_,
MEM_RESERVE | MEM_RESERVE_PLACEHOLDER,
PAGE_NOACCESS, nullptr, 0)) {
if (MapViewOfFile3FromApp(mapping_handle_, process, data_, 0,
capacity_, MEM_REPLACE_PLACEHOLDER,
ULONG(mapping_protect), nullptr, 0)) {
successful = true;
} else {
VirtualFree(data_, capacity_, MEM_RELEASE);
}
}
#endif
if (successful) {
break; break;
} }
data_ += capacity_; data_ += capacity_;
@ -261,8 +311,13 @@ class Win32ChunkedMappedMemoryWriter : public ChunkedMappedMemoryWriter {
} }
} }
} else { } else {
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
data_ = reinterpret_cast<uint8_t*>( data_ = reinterpret_cast<uint8_t*>(
MapViewOfFile(mapping_handle_, view_access, 0, 0, capacity_)); MapViewOfFile(mapping_handle_, view_access, 0, 0, capacity_));
#else
data_ = reinterpret_cast<uint8_t*>(MapViewOfFileFromApp(
mapping_handle_, ULONG(view_access), 0, capacity_));
#endif
} }
if (!data_) { if (!data_) {
return false; return false;

View File

@ -8,11 +8,26 @@
*/ */
#include "xenia/base/memory.h" #include "xenia/base/memory.h"
#include "xenia/base/cvar.h"
#include "xenia/base/platform.h" #include "xenia/base/platform.h"
#include <algorithm> #include <algorithm>
DEFINE_bool(
writable_executable_memory, true,
"Allow mapping memory with both write and execute access, for simulating "
"behavior on platforms where that's not supported",
"Memory");
namespace xe { namespace xe {
namespace memory {
bool IsWritableExecutableMemoryPreferred() {
return IsWritableExecutableMemorySupported() &&
cvars::writable_executable_memory;
}
} // namespace memory
// TODO(benvanik): fancy AVX versions. // TODO(benvanik): fancy AVX versions.
// https://github.com/gnuradio/volk/blob/master/kernels/volk/volk_16u_byteswap.h // https://github.com/gnuradio/volk/blob/master/kernels/volk/volk_16u_byteswap.h

View File

@ -18,6 +18,7 @@
#include "xenia/base/assert.h" #include "xenia/base/assert.h"
#include "xenia/base/byte_order.h" #include "xenia/base/byte_order.h"
#include "xenia/base/platform.h"
namespace xe { namespace xe {
namespace memory { namespace memory {
@ -34,6 +35,7 @@ enum class PageAccess {
kNoAccess = 0, kNoAccess = 0,
kReadOnly = 1 << 0, kReadOnly = 1 << 0,
kReadWrite = kReadOnly | 1 << 1, kReadWrite = kReadOnly | 1 << 1,
kExecuteReadOnly = kReadOnly | 1 << 2,
kExecuteReadWrite = kReadWrite | 1 << 2, kExecuteReadWrite = kReadWrite | 1 << 2,
}; };
@ -48,6 +50,16 @@ enum class DeallocationType {
kDecommit = 1 << 1, kDecommit = 1 << 1,
}; };
// Whether the host allows the pages to be allocated or mapped with
// PageAccess::kExecuteReadWrite - if not, separate mappings backed by the same
// memory-mapped file must be used to write to executable pages.
bool IsWritableExecutableMemorySupported();
// Whether PageAccess::kExecuteReadWrite is a supported and preferred way of
// writing executable memory, useful for simulating how Xenia would work without
// writable executable memory on a system with it.
bool IsWritableExecutableMemoryPreferred();
// Allocates a block of memory at the given page-aligned base address. // Allocates a block of memory at the given page-aligned base address.
// Fails if the memory is not available. // Fails if the memory is not available.
// Specify nullptr for base_address to leave it up to the system. // Specify nullptr for base_address to leave it up to the system.
@ -96,12 +108,21 @@ void AlignedFree(T* ptr) {
#endif // XE_COMPILER_MSVC #endif // XE_COMPILER_MSVC
} }
#if XE_PLATFORM_WIN32
// HANDLE.
typedef void* FileMappingHandle; typedef void* FileMappingHandle;
constexpr FileMappingHandle kFileMappingHandleInvalid = nullptr;
#else
// File descriptor.
typedef int FileMappingHandle;
constexpr FileMappingHandle kFileMappingHandleInvalid = -1;
#endif
FileMappingHandle CreateFileMappingHandle(const std::filesystem::path& path, FileMappingHandle CreateFileMappingHandle(const std::filesystem::path& path,
size_t length, PageAccess access, size_t length, PageAccess access,
bool commit); bool commit);
void CloseFileMappingHandle(FileMappingHandle handle); void CloseFileMappingHandle(FileMappingHandle handle,
const std::filesystem::path& path);
void* MapFileView(FileMappingHandle handle, void* base_address, size_t length, void* MapFileView(FileMappingHandle handle, void* base_address, size_t length,
PageAccess access, size_t file_offset); PageAccess access, size_t file_offset);
bool UnmapFileView(FileMappingHandle handle, void* base_address, size_t length); bool UnmapFileView(FileMappingHandle handle, void* base_address, size_t length);

View File

@ -8,12 +8,23 @@
*/ */
#include "xenia/base/memory.h" #include "xenia/base/memory.h"
#include "xenia/base/string.h"
#include <fcntl.h> #include <fcntl.h>
#include <sys/mman.h> #include <sys/mman.h>
#include <unistd.h> #include <unistd.h>
#include "xenia/base/math.h"
#include "xenia/base/platform.h"
#include "xenia/base/string.h"
#if XE_PLATFORM_ANDROID
#include <linux/ashmem.h>
#include <string.h>
#include <sys/ioctl.h>
#include "xenia/base/platform_android.h"
#endif
namespace xe { namespace xe {
namespace memory { namespace memory {
@ -28,6 +39,8 @@ uint32_t ToPosixProtectFlags(PageAccess access) {
return PROT_READ; return PROT_READ;
case PageAccess::kReadWrite: case PageAccess::kReadWrite:
return PROT_READ | PROT_WRITE; return PROT_READ | PROT_WRITE;
case PageAccess::kExecuteReadOnly:
return PROT_READ | PROT_EXEC;
case PageAccess::kExecuteReadWrite: case PageAccess::kExecuteReadWrite:
return PROT_READ | PROT_WRITE | PROT_EXEC; return PROT_READ | PROT_WRITE | PROT_EXEC;
default: default:
@ -36,11 +49,19 @@ uint32_t ToPosixProtectFlags(PageAccess access) {
} }
} }
bool IsWritableExecutableMemorySupported() { return true; }
void* AllocFixed(void* base_address, size_t length, void* AllocFixed(void* base_address, size_t length,
AllocationType allocation_type, PageAccess access) { AllocationType allocation_type, PageAccess access) {
// mmap does not support reserve / commit, so ignore allocation_type. // mmap does not support reserve / commit, so ignore allocation_type.
uint32_t prot = ToPosixProtectFlags(access); uint32_t prot = ToPosixProtectFlags(access);
return mmap(base_address, length, prot, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); void* result = mmap(base_address, length, prot,
MAP_PRIVATE | MAP_FIXED | MAP_ANONYMOUS, -1, 0);
if (result == MAP_FAILED) {
return nullptr;
} else {
return result;
}
} }
bool DeallocFixed(void* base_address, size_t length, bool DeallocFixed(void* base_address, size_t length,
@ -64,12 +85,38 @@ bool QueryProtect(void* base_address, size_t& length, PageAccess& access_out) {
FileMappingHandle CreateFileMappingHandle(const std::filesystem::path& path, FileMappingHandle CreateFileMappingHandle(const std::filesystem::path& path,
size_t length, PageAccess access, size_t length, PageAccess access,
bool commit) { bool commit) {
#if XE_PLATFORM_ANDROID
if (xe::platform::android::api_level() >= 26) {
// TODO(Triang3l): Check if memfd can be used instead on API 30+.
int sharedmem_fd =
xe::platform::android::api_functions().api_26.ASharedMemory_create(
path.c_str(), length);
return sharedmem_fd >= 0 ? sharedmem_fd : kFileMappingHandleInvalid;
}
// Use /dev/ashmem on API versions below 26, which added ASharedMemory.
// /dev/ashmem was disabled on API 29 for apps targeting it.
// https://chromium.googlesource.com/chromium/src/+/master/third_party/ashmem/ashmem-dev.c
int ashmem_fd = open("/" ASHMEM_NAME_DEF, O_RDWR);
if (ashmem_fd < 0) {
return kFileMappingHandleInvalid;
}
char ashmem_name[ASHMEM_NAME_LEN];
strlcpy(ashmem_name, path.c_str(), xe::countof(ashmem_name));
if (ioctl(ashmem_fd, ASHMEM_SET_NAME, ashmem_name) < 0 ||
ioctl(ashmem_fd, ASHMEM_SET_SIZE, length) < 0) {
close(ashmem_fd);
return kFileMappingHandleInvalid;
}
return ashmem_fd;
#else
int oflag; int oflag;
switch (access) { switch (access) {
case PageAccess::kNoAccess: case PageAccess::kNoAccess:
oflag = 0; oflag = 0;
break; break;
case PageAccess::kReadOnly: case PageAccess::kReadOnly:
case PageAccess::kExecuteReadOnly:
oflag = O_RDONLY; oflag = O_RDONLY;
break; break;
case PageAccess::kReadWrite: case PageAccess::kReadWrite:
@ -78,27 +125,33 @@ FileMappingHandle CreateFileMappingHandle(const std::filesystem::path& path,
break; break;
default: default:
assert_always(); assert_always();
return nullptr; return kFileMappingHandleInvalid;
} }
oflag |= O_CREAT; oflag |= O_CREAT;
int ret = shm_open(path.c_str(), oflag, 0777); auto full_path = "/" / path;
if (ret > 0) { int ret = shm_open(full_path.c_str(), oflag, 0777);
ftruncate64(ret, length); if (ret < 0) {
return kFileMappingHandleInvalid;
} }
ftruncate64(ret, length);
return ret <= 0 ? nullptr : reinterpret_cast<FileMappingHandle>(ret); return ret;
#endif
} }
void CloseFileMappingHandle(FileMappingHandle handle) { void CloseFileMappingHandle(FileMappingHandle handle,
close((intptr_t)handle); const std::filesystem::path& path) {
close(handle);
#if !XE_PLATFORM_ANDROID
auto full_path = "/" / path;
shm_unlink(full_path.c_str());
#endif
} }
void* MapFileView(FileMappingHandle handle, void* base_address, size_t length, void* MapFileView(FileMappingHandle handle, void* base_address, size_t length,
PageAccess access, size_t file_offset) { PageAccess access, size_t file_offset) {
uint32_t prot = ToPosixProtectFlags(access); uint32_t prot = ToPosixProtectFlags(access);
return mmap64(base_address, length, prot, MAP_PRIVATE | MAP_ANONYMOUS, return mmap64(base_address, length, prot, MAP_PRIVATE | MAP_ANONYMOUS, handle,
reinterpret_cast<intptr_t>(handle), file_offset); file_offset);
} }
bool UnmapFileView(FileMappingHandle handle, void* base_address, bool UnmapFileView(FileMappingHandle handle, void* base_address,

View File

@ -42,6 +42,8 @@ DWORD ToWin32ProtectFlags(PageAccess access) {
return PAGE_READONLY; return PAGE_READONLY;
case PageAccess::kReadWrite: case PageAccess::kReadWrite:
return PAGE_READWRITE; return PAGE_READWRITE;
case PageAccess::kExecuteReadOnly:
return PAGE_EXECUTE_READ;
case PageAccess::kExecuteReadWrite: case PageAccess::kExecuteReadWrite:
return PAGE_EXECUTE_READWRITE; return PAGE_EXECUTE_READWRITE;
default: default:
@ -63,6 +65,8 @@ PageAccess ToXeniaProtectFlags(DWORD access) {
return PageAccess::kReadOnly; return PageAccess::kReadOnly;
case PAGE_READWRITE: case PAGE_READWRITE:
return PageAccess::kReadWrite; return PageAccess::kReadWrite;
case PAGE_EXECUTE_READ:
return PageAccess::kExecuteReadOnly;
case PAGE_EXECUTE_READWRITE: case PAGE_EXECUTE_READWRITE:
return PageAccess::kExecuteReadWrite; return PageAccess::kExecuteReadWrite;
default: default:
@ -70,6 +74,17 @@ PageAccess ToXeniaProtectFlags(DWORD access) {
} }
} }
bool IsWritableExecutableMemorySupported() {
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
return true;
#else
// To test FromApp functions on desktop, replace
// WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) with 0 in the #ifs and
// link to WindowsApp.lib.
return false;
#endif
}
void* AllocFixed(void* base_address, size_t length, void* AllocFixed(void* base_address, size_t length,
AllocationType allocation_type, PageAccess access) { AllocationType allocation_type, PageAccess access) {
DWORD alloc_type = 0; DWORD alloc_type = 0;
@ -88,7 +103,12 @@ void* AllocFixed(void* base_address, size_t length,
break; break;
} }
DWORD protect = ToWin32ProtectFlags(access); DWORD protect = ToWin32ProtectFlags(access);
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
return VirtualAlloc(base_address, length, alloc_type, protect); return VirtualAlloc(base_address, length, alloc_type, protect);
#else
return VirtualAllocFromApp(base_address, length, ULONG(alloc_type),
ULONG(protect));
#endif
} }
bool DeallocFixed(void* base_address, size_t length, bool DeallocFixed(void* base_address, size_t length,
@ -115,13 +135,19 @@ bool Protect(void* base_address, size_t length, PageAccess access,
*out_old_access = PageAccess::kNoAccess; *out_old_access = PageAccess::kNoAccess;
} }
DWORD new_protect = ToWin32ProtectFlags(access); DWORD new_protect = ToWin32ProtectFlags(access);
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
DWORD old_protect = 0; DWORD old_protect = 0;
BOOL result = VirtualProtect(base_address, length, new_protect, &old_protect); BOOL result = VirtualProtect(base_address, length, new_protect, &old_protect);
#else
ULONG old_protect = 0;
BOOL result = VirtualProtectFromApp(base_address, length, ULONG(new_protect),
&old_protect);
#endif
if (!result) { if (!result) {
return false; return false;
} }
if (out_old_access) { if (out_old_access) {
*out_old_access = ToXeniaProtectFlags(old_protect); *out_old_access = ToXeniaProtectFlags(DWORD(old_protect));
} }
return true; return true;
} }
@ -147,15 +173,25 @@ FileMappingHandle CreateFileMappingHandle(const std::filesystem::path& path,
bool commit) { bool commit) {
DWORD protect = DWORD protect =
ToWin32ProtectFlags(access) | (commit ? SEC_COMMIT : SEC_RESERVE); ToWin32ProtectFlags(access) | (commit ? SEC_COMMIT : SEC_RESERVE);
return CreateFileMappingW(INVALID_HANDLE_VALUE, NULL, protect, auto full_path = "Local" / path;
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
return CreateFileMappingW(INVALID_HANDLE_VALUE, nullptr, protect,
static_cast<DWORD>(length >> 32), static_cast<DWORD>(length >> 32),
static_cast<DWORD>(length), path.c_str()); static_cast<DWORD>(length), full_path.c_str());
#else
return CreateFileMappingFromApp(INVALID_HANDLE_VALUE, nullptr, ULONG(protect),
ULONG64(length), full_path.c_str());
#endif
} }
void CloseFileMappingHandle(FileMappingHandle handle) { CloseHandle(handle); } void CloseFileMappingHandle(FileMappingHandle handle,
const std::filesystem::path& path) {
CloseHandle(handle);
}
void* MapFileView(FileMappingHandle handle, void* base_address, size_t length, void* MapFileView(FileMappingHandle handle, void* base_address, size_t length,
PageAccess access, size_t file_offset) { PageAccess access, size_t file_offset) {
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
DWORD target_address_low = static_cast<DWORD>(file_offset); DWORD target_address_low = static_cast<DWORD>(file_offset);
DWORD target_address_high = static_cast<DWORD>(file_offset >> 32); DWORD target_address_high = static_cast<DWORD>(file_offset >> 32);
DWORD file_access = 0; DWORD file_access = 0;
@ -166,6 +202,9 @@ void* MapFileView(FileMappingHandle handle, void* base_address, size_t length,
case PageAccess::kReadWrite: case PageAccess::kReadWrite:
file_access = FILE_MAP_ALL_ACCESS; file_access = FILE_MAP_ALL_ACCESS;
break; break;
case PageAccess::kExecuteReadOnly:
file_access = FILE_MAP_READ | FILE_MAP_EXECUTE;
break;
case PageAccess::kExecuteReadWrite: case PageAccess::kExecuteReadWrite:
file_access = FILE_MAP_ALL_ACCESS | FILE_MAP_EXECUTE; file_access = FILE_MAP_ALL_ACCESS | FILE_MAP_EXECUTE;
break; break;
@ -176,6 +215,25 @@ void* MapFileView(FileMappingHandle handle, void* base_address, size_t length,
} }
return MapViewOfFileEx(handle, file_access, target_address_high, return MapViewOfFileEx(handle, file_access, target_address_high,
target_address_low, length, base_address); target_address_low, length, base_address);
#else
// VirtualAlloc2FromApp and MapViewOfFile3FromApp were added in 10.0.17134.0.
// https://docs.microsoft.com/en-us/uwp/win32-and-com/win32-apis
HANDLE process = GetCurrentProcess();
void* placeholder = VirtualAlloc2FromApp(
process, base_address, length, MEM_RESERVE | MEM_RESERVE_PLACEHOLDER,
PAGE_NOACCESS, nullptr, 0);
if (!placeholder) {
return nullptr;
}
void* mapping = MapViewOfFile3FromApp(
handle, process, placeholder, ULONG64(file_offset), length,
MEM_REPLACE_PLACEHOLDER, ULONG(ToWin32ProtectFlags(access)), nullptr, 0);
if (!mapping) {
VirtualFree(placeholder, length, MEM_RELEASE);
return nullptr;
}
return mapping;
#endif
} }
bool UnmapFileView(FileMappingHandle handle, void* base_address, bool UnmapFileView(FileMappingHandle handle, void* base_address,

View File

@ -31,8 +31,14 @@
#define XE_PLATFORM_MAC 1 #define XE_PLATFORM_MAC 1
#elif defined(WIN32) || defined(_WIN32) #elif defined(WIN32) || defined(_WIN32)
#define XE_PLATFORM_WIN32 1 #define XE_PLATFORM_WIN32 1
#else #elif defined(__ANDROID__)
#define XE_PLATFORM_ANDROID 1
#define XE_PLATFORM_LINUX 1 #define XE_PLATFORM_LINUX 1
#elif defined(__gnu_linux__)
#define XE_PLATFORM_GNU_LINUX 1
#define XE_PLATFORM_LINUX 1
#else
#error Unsupported target OS.
#endif #endif
#if defined(__clang__) #if defined(__clang__)
@ -51,8 +57,11 @@
#if defined(_M_AMD64) || defined(__amd64__) #if defined(_M_AMD64) || defined(__amd64__)
#define XE_ARCH_AMD64 1 #define XE_ARCH_AMD64 1
#elif defined(_M_IX86) #elif defined(_M_ARM64) || defined(__aarch64__)
#error "Xenia is not supported on 32-bit platforms." #define XE_ARCH_ARM64 1
#elif defined(_M_IX86) || defined(__i386__) || defined(_M_ARM) || \
defined(__arm__)
#error Xenia is not supported on 32-bit platforms.
#elif defined(_M_PPC) || defined(__powerpc__) #elif defined(_M_PPC) || defined(__powerpc__)
#define XE_ARCH_PPC 1 #define XE_ARCH_PPC 1
#endif #endif

View File

@ -0,0 +1,65 @@
/**
******************************************************************************
* 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/base/platform_android.h"
#include <android/configuration.h>
#include <dlfcn.h>
#include "xenia/base/assert.h"
namespace xe {
namespace platform {
namespace android {
static bool initialized = false;
static int32_t api_level_ = __ANDROID_API__;
static ApiFunctions api_functions_;
void Initialize(const ANativeActivity* activity) {
if (initialized) {
return;
}
AConfiguration* configuration = AConfiguration_new();
AConfiguration_fromAssetManager(configuration, activity->assetManager);
api_level_ = AConfiguration_getSdkVersion(configuration);
AConfiguration_delete(configuration);
if (api_level_ >= 26) {
// Leaked intentionally as these will be usable anywhere, already loaded
// into the address space as the application is linked against them.
// https://chromium.googlesource.com/chromium/src/+/master/third_party/ashmem/ashmem-dev.c#201
void* libandroid = dlopen("libandroid.so", RTLD_NOW);
assert_not_null(libandroid);
void* libc = dlopen("libc.so", RTLD_NOW);
assert_not_null(libc);
#define XE_PLATFORM_ANDROID_LOAD_API_FUNCTION(lib, name, api) \
api_functions_.api_##api.name = \
reinterpret_cast<decltype(api_functions_.api_##api.name)>( \
dlsym(lib, #name)); \
assert_not_null(api_functions_.api_##api.name);
XE_PLATFORM_ANDROID_LOAD_API_FUNCTION(libandroid, ASharedMemory_create, 26);
// pthreads are a part of Bionic libc on Android.
XE_PLATFORM_ANDROID_LOAD_API_FUNCTION(libc, pthread_getname_np, 26);
#undef XE_PLATFORM_ANDROID_LOAD_API_FUNCTION
}
initialized = true;
}
int32_t api_level() { return api_level_; }
const ApiFunctions& api_functions() { return api_functions_; }
} // namespace android
} // namespace platform
} // namespace xe

View File

@ -0,0 +1,48 @@
/**
******************************************************************************
* 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_BASE_PLATFORM_ANDROID_H_
#define XENIA_BASE_PLATFORM_ANDROID_H_
// NOTE: if you're including this file it means you are explicitly depending
// on Android-specific headers. This is bad for portability and should be
// avoided!
#include <android/native_activity.h>
#include <pthread.h>
#include <cstddef>
#include <cstdint>
namespace xe {
namespace platform {
namespace android {
// Must be called in onCreate of the first activity.
void Initialize(const ANativeActivity* activity);
// Returns the device API level - if not initialized, will return the minimum
// level supported by Xenia.
int32_t api_level();
// Android API functions added after the minimum supported API version.
struct ApiFunctions {
struct {
// libandroid
int (*ASharedMemory_create)(const char* name, size_t size);
// libc
int (*pthread_getname_np)(pthread_t pthread, char* buf, size_t n);
} api_26;
};
const ApiFunctions& api_functions();
} // namespace android
} // namespace platform
} // namespace xe
#endif // XENIA_BASE_PLATFORM_ANDROID_H_

View File

@ -15,7 +15,7 @@ namespace xe {
void LaunchWebBrowser(const std::string& url) { void LaunchWebBrowser(const std::string& url) {
auto temp = xe::to_utf16(url); auto temp = xe::to_utf16(url);
ShellExecuteW(nullptr, L"open", reinterpret_cast<LPCWSTR>(url.c_str()), ShellExecuteW(nullptr, L"open", reinterpret_cast<LPCWSTR>(temp.c_str()),
nullptr, nullptr, SW_SHOWNORMAL); nullptr, nullptr, SW_SHOWNORMAL);
} }

View File

@ -10,6 +10,9 @@
#include "xenia/base/memory.h" #include "xenia/base/memory.h"
#include "third_party/catch/include/catch.hpp" #include "third_party/catch/include/catch.hpp"
#include "third_party/fmt/include/fmt/format.h"
#include "xenia/base/clock.h"
namespace xe { namespace xe {
namespace base { namespace base {
@ -414,6 +417,58 @@ TEST_CASE("copy_and_swap_16_in_32_unaligned", "Copy and Swap") {
REQUIRE(true == true); REQUIRE(true == true);
} }
TEST_CASE("create_and_close_file_mapping", "Virtual Memory Mapping") {
auto path = fmt::format("xenia_test_{}", Clock::QueryHostTickCount());
auto memory = xe::memory::CreateFileMappingHandle(
path, 0x100, xe::memory::PageAccess::kReadWrite, true);
REQUIRE(memory != xe::memory::kFileMappingHandleInvalid);
xe::memory::CloseFileMappingHandle(memory, path);
}
TEST_CASE("map_view", "Virtual Memory Mapping") {
auto path = fmt::format("xenia_test_{}", Clock::QueryHostTickCount());
const size_t length = 0x100;
auto memory = xe::memory::CreateFileMappingHandle(
path, length, xe::memory::PageAccess::kReadWrite, true);
REQUIRE(memory != xe::memory::kFileMappingHandleInvalid);
uintptr_t address = 0x100000000;
auto view =
xe::memory::MapFileView(memory, reinterpret_cast<void*>(address), length,
xe::memory::PageAccess::kReadWrite, 0);
REQUIRE(reinterpret_cast<uintptr_t>(view) == address);
xe::memory::UnmapFileView(memory, reinterpret_cast<void*>(address), length);
xe::memory::CloseFileMappingHandle(memory, path);
}
TEST_CASE("read_write_view", "Virtual Memory Mapping") {
const size_t length = 0x100;
auto path = fmt::format("xenia_test_{}", Clock::QueryHostTickCount());
auto memory = xe::memory::CreateFileMappingHandle(
path, length, xe::memory::PageAccess::kReadWrite, true);
REQUIRE(memory != xe::memory::kFileMappingHandleInvalid);
uintptr_t address = 0x100000000;
auto view =
xe::memory::MapFileView(memory, reinterpret_cast<void*>(address), length,
xe::memory::PageAccess::kReadWrite, 0);
REQUIRE(reinterpret_cast<uintptr_t>(view) == address);
for (uint32_t i = 0; i < length; i += sizeof(uint8_t)) {
auto p_value = reinterpret_cast<uint8_t*>(address + i);
*p_value = i;
}
for (uint32_t i = 0; i < length; i += sizeof(uint8_t)) {
auto p_value = reinterpret_cast<uint8_t*>(address + i);
uint8_t value = *p_value;
REQUIRE(value == i);
}
xe::memory::UnmapFileView(memory, reinterpret_cast<void*>(address), length);
xe::memory::CloseFileMappingHandle(memory, path);
}
} // namespace test } // namespace test
} // namespace base } // namespace base
} // namespace xe } // namespace xe

View File

@ -174,8 +174,8 @@ TEST_CASE("HighResolutionTimer") {
{ {
const auto interval1 = 100ms; const auto interval1 = 100ms;
const auto interval2 = 200ms; const auto interval2 = 200ms;
std::atomic<uint64_t> counter1; std::atomic<uint64_t> counter1(0);
std::atomic<uint64_t> counter2; std::atomic<uint64_t> counter2(0);
auto start = std::chrono::steady_clock::now(); auto start = std::chrono::steady_clock::now();
auto cb1 = [&counter1] { ++counter1; }; auto cb1 = [&counter1] { ++counter1; };
auto cb2 = [&counter2] { ++counter2; }; auto cb2 = [&counter2] { ++counter2; };
@ -813,7 +813,7 @@ TEST_CASE("Create and Run Thread", "Thread") {
result = Wait(Thread::GetCurrentThread(), false, 50ms); result = Wait(Thread::GetCurrentThread(), false, 50ms);
REQUIRE(result == WaitResult::kTimeout); REQUIRE(result == WaitResult::kTimeout);
params.stack_size = 16 * 1024; params.stack_size = 16 * 1024 * 1024;
thread = Thread::Create(params, [] { thread = Thread::Create(params, [] {
while (true) { while (true) {
Thread::Exit(-1); Thread::Exit(-1);

View File

@ -95,9 +95,6 @@ void set_current_thread_id(uint32_t id);
// Sets the current thread name. // Sets the current thread name.
void set_name(const std::string_view name); void set_name(const std::string_view name);
// Sets the target thread name.
void set_name(std::thread::native_handle_type handle,
const std::string_view name);
// Yields the current thread to the scheduler. Maybe. // Yields the current thread to the scheduler. Maybe.
void MaybeYield(); void MaybeYield();

View File

@ -1,17 +0,0 @@
/**
******************************************************************************
* 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/threading.h"
#include <pthread.h>
#include <time.h>
namespace xe {
namespace threading {} // namespace threading
} // namespace xe

View File

@ -26,10 +26,6 @@ uint32_t current_thread_id() {
void set_name(const std::string& name) { pthread_setname_np(name.c_str()); } void set_name(const std::string& name) { pthread_setname_np(name.c_str()); }
void set_name(std::thread::native_handle_type handle, const std::string& name) {
// ?
}
void MaybeYield() { pthread_yield_np(); } void MaybeYield() { pthread_yield_np(); }
void Sleep(std::chrono::microseconds duration) { void Sleep(std::chrono::microseconds duration) {

View File

@ -11,6 +11,7 @@
#include "xenia/base/assert.h" #include "xenia/base/assert.h"
#include "xenia/base/logging.h" #include "xenia/base/logging.h"
#include "xenia/base/platform.h"
#include <pthread.h> #include <pthread.h>
#include <signal.h> #include <signal.h>
@ -19,9 +20,18 @@
#include <sys/time.h> #include <sys/time.h>
#include <sys/types.h> #include <sys/types.h>
#include <unistd.h> #include <unistd.h>
#include <array>
#include <cstring>
#include <ctime> #include <ctime>
#include <memory> #include <memory>
#if XE_PLATFORM_ANDROID
#include <sched.h>
#include "xenia/base/platform_android.h"
#include "xenia/base/string_util.h"
#endif
namespace xe { namespace xe {
namespace threading { namespace threading {
@ -43,6 +53,12 @@ enum class SignalType {
kTimer, kTimer,
kThreadSuspend, kThreadSuspend,
kThreadUserCallback, kThreadUserCallback,
#if XE_PLATFORM_ANDROID
// pthread_cancel is not available on Android, using a signal handler for
// simplified PTHREAD_CANCEL_ASYNCHRONOUS-like behavior - not disabling
// cancellation currently, so should be enough.
kThreadTerminate,
#endif
k_Count k_Count
}; };
@ -80,15 +96,12 @@ uint32_t current_thread_system_id() {
return static_cast<uint32_t>(syscall(SYS_gettid)); return static_cast<uint32_t>(syscall(SYS_gettid));
} }
void set_name(std::thread::native_handle_type handle,
const std::string_view name) {
pthread_setname_np(handle, std::string(name).c_str());
}
void set_name(const std::string_view name) { set_name(pthread_self(), name); }
void MaybeYield() { void MaybeYield() {
#if XE_PLATFORM_ANDROID
sched_yield();
#else
pthread_yield(); pthread_yield();
#endif
__sync_synchronize(); __sync_synchronize();
} }
@ -482,7 +495,11 @@ class PosixCondition<Thread> : public PosixConditionBase {
signaled_(false), signaled_(false),
exit_code_(0), exit_code_(0),
state_(State::kUninitialized), state_(State::kUninitialized),
suspend_count_(0) {} suspend_count_(0) {
#if XE_PLATFORM_ANDROID
android_pre_api_26_name_[0] = '\0';
#endif
}
bool Initialize(Thread::CreationParameters params, bool Initialize(Thread::CreationParameters params,
ThreadStartData* start_data) { ThreadStartData* start_data) {
start_data->create_suspended = params.create_suspended; start_data->create_suspended = params.create_suspended;
@ -505,6 +522,7 @@ class PosixCondition<Thread> : public PosixConditionBase {
} }
} }
if (pthread_create(&thread_, &attr, ThreadStartRoutine, start_data) != 0) { if (pthread_create(&thread_, &attr, ThreadStartRoutine, start_data) != 0) {
pthread_attr_destroy(&attr);
return false; return false;
} }
pthread_attr_destroy(&attr); pthread_attr_destroy(&attr);
@ -517,13 +535,24 @@ class PosixCondition<Thread> : public PosixConditionBase {
: thread_(thread), : thread_(thread),
signaled_(false), signaled_(false),
exit_code_(0), exit_code_(0),
state_(State::kRunning) {} state_(State::kRunning) {
#if XE_PLATFORM_ANDROID
android_pre_api_26_name_[0] = '\0';
#endif
}
virtual ~PosixCondition() { virtual ~PosixCondition() {
if (thread_ && !signaled_) { if (thread_ && !signaled_) {
#if XE_PLATFORM_ANDROID
if (pthread_kill(thread_,
GetSystemSignal(SignalType::kThreadTerminate)) != 0) {
assert_always();
}
#else
if (pthread_cancel(thread_) != 0) { if (pthread_cancel(thread_) != 0) {
assert_always(); assert_always();
} }
#endif
if (pthread_join(thread_, nullptr) != 0) { if (pthread_join(thread_, nullptr) != 0) {
assert_always(); assert_always();
} }
@ -537,8 +566,24 @@ class PosixCondition<Thread> : public PosixConditionBase {
auto result = std::array<char, 17>{'\0'}; auto result = std::array<char, 17>{'\0'};
std::unique_lock<std::mutex> lock(state_mutex_); std::unique_lock<std::mutex> lock(state_mutex_);
if (state_ != State::kUninitialized && state_ != State::kFinished) { if (state_ != State::kUninitialized && state_ != State::kFinished) {
if (pthread_getname_np(thread_, result.data(), result.size() - 1) != 0) #if XE_PLATFORM_ANDROID
// pthread_getname_np was added in API 26 - below that, store the name in
// this object, which may be only modified through Xenia threading, but
// should be enough in most cases.
if (xe::platform::android::api_level() >= 26) {
if (xe::platform::android::api_functions().api_26.pthread_getname_np(
thread_, result.data(), result.size() - 1) != 0) {
assert_always();
}
} else {
std::lock_guard<std::mutex> lock(android_pre_api_26_name_mutex_);
std::strcpy(result.data(), android_pre_api_26_name_);
}
#else
if (pthread_getname_np(thread_, result.data(), result.size() - 1) != 0) {
assert_always(); assert_always();
}
#endif
} }
return std::string(result.data()); return std::string(result.data());
} }
@ -547,18 +592,39 @@ class PosixCondition<Thread> : public PosixConditionBase {
WaitStarted(); WaitStarted();
std::unique_lock<std::mutex> lock(state_mutex_); std::unique_lock<std::mutex> lock(state_mutex_);
if (state_ != State::kUninitialized && state_ != State::kFinished) { if (state_ != State::kUninitialized && state_ != State::kFinished) {
threading::set_name(static_cast<std::thread::native_handle_type>(thread_), pthread_setname_np(thread_, std::string(name).c_str());
name); #if XE_PLATFORM_ANDROID
SetAndroidPreApi26Name(name);
#endif
} }
} }
#if XE_PLATFORM_ANDROID
void SetAndroidPreApi26Name(const std::string_view name) {
if (xe::platform::android::api_level() >= 26) {
return;
}
std::lock_guard<std::mutex> lock(android_pre_api_26_name_mutex_);
xe::string_util::copy_truncating(android_pre_api_26_name_, name,
xe::countof(android_pre_api_26_name_));
}
#endif
uint32_t system_id() const { return static_cast<uint32_t>(thread_); } uint32_t system_id() const { return static_cast<uint32_t>(thread_); }
uint64_t affinity_mask() { uint64_t affinity_mask() {
WaitStarted(); WaitStarted();
cpu_set_t cpu_set; cpu_set_t cpu_set;
if (pthread_getaffinity_np(thread_, sizeof(cpu_set_t), &cpu_set) != 0) #if XE_PLATFORM_ANDROID
if (sched_getaffinity(pthread_gettid_np(thread_), sizeof(cpu_set_t),
&cpu_set) != 0) {
assert_always(); assert_always();
}
#else
if (pthread_getaffinity_np(thread_, sizeof(cpu_set_t), &cpu_set) != 0) {
assert_always();
}
#endif
uint64_t result = 0; uint64_t result = 0;
auto cpu_count = std::min(CPU_SETSIZE, 64); auto cpu_count = std::min(CPU_SETSIZE, 64);
for (auto i = 0u; i < cpu_count; i++) { for (auto i = 0u; i < cpu_count; i++) {
@ -577,9 +643,16 @@ class PosixCondition<Thread> : public PosixConditionBase {
CPU_SET(i, &cpu_set); CPU_SET(i, &cpu_set);
} }
} }
#if XE_PLATFORM_ANDROID
if (sched_setaffinity(pthread_gettid_np(thread_), sizeof(cpu_set_t),
&cpu_set) != 0) {
assert_always();
}
#else
if (pthread_setaffinity_np(thread_, sizeof(cpu_set_t), &cpu_set) != 0) { if (pthread_setaffinity_np(thread_, sizeof(cpu_set_t), &cpu_set) != 0) {
assert_always(); assert_always();
} }
#endif
} }
int priority() { int priority() {
@ -608,8 +681,13 @@ class PosixCondition<Thread> : public PosixConditionBase {
user_callback_ = std::move(callback); user_callback_ = std::move(callback);
sigval value{}; sigval value{};
value.sival_ptr = this; value.sival_ptr = this;
#if XE_PLATFORM_ANDROID
sigqueue(pthread_gettid_np(thread_),
GetSystemSignal(SignalType::kThreadUserCallback), value);
#else
pthread_sigqueue(thread_, GetSystemSignal(SignalType::kThreadUserCallback), pthread_sigqueue(thread_, GetSystemSignal(SignalType::kThreadUserCallback),
value); value);
#endif
} }
void CallUserCallback() { void CallUserCallback() {
@ -665,7 +743,16 @@ class PosixCondition<Thread> : public PosixConditionBase {
signaled_ = true; signaled_ = true;
cond_.notify_all(); cond_.notify_all();
if (pthread_cancel(thread) != 0) assert_always(); #ifdef XE_PLATFORM_ANDROID
if (pthread_kill(thread, GetSystemSignal(SignalType::kThreadTerminate)) !=
0) {
assert_always();
}
#else
if (pthread_cancel(thread) != 0) {
assert_always();
}
#endif
} }
void WaitStarted() const { void WaitStarted() const {
@ -703,6 +790,12 @@ class PosixCondition<Thread> : public PosixConditionBase {
mutable std::mutex callback_mutex_; mutable std::mutex callback_mutex_;
mutable std::condition_variable state_signal_; mutable std::condition_variable state_signal_;
std::function<void()> user_callback_; std::function<void()> user_callback_;
#if XE_PLATFORM_ANDROID
// Name accessible via name() on Android before API 26 which added
// pthread_getname_np.
mutable std::mutex android_pre_api_26_name_mutex_;
char android_pre_api_26_name_[16];
#endif
}; };
class PosixWaitHandle { class PosixWaitHandle {
@ -722,7 +815,7 @@ class PosixConditionHandle : public T, public PosixWaitHandle {
PosixConditionHandle(uint32_t initial_count, uint32_t maximum_count); PosixConditionHandle(uint32_t initial_count, uint32_t maximum_count);
~PosixConditionHandle() override = default; ~PosixConditionHandle() override = default;
PosixConditionBase& condition() override { return handle_; } PosixCondition<T>& condition() override { return handle_; }
void* native_handle() const override { return handle_.native_handle(); } void* native_handle() const override { return handle_.native_handle(); }
protected: protected:
@ -941,9 +1034,11 @@ class PosixThread : public PosixConditionHandle<Thread> {
thread_local PosixThread* current_thread_ = nullptr; thread_local PosixThread* current_thread_ = nullptr;
void* PosixCondition<Thread>::ThreadStartRoutine(void* parameter) { void* PosixCondition<Thread>::ThreadStartRoutine(void* parameter) {
#if !XE_PLATFORM_ANDROID
if (pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, nullptr) != 0) { if (pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, nullptr) != 0) {
assert_always(); assert_always();
} }
#endif
threading::set_name(""); threading::set_name("");
auto start_data = static_cast<ThreadStartData*>(parameter); auto start_data = static_cast<ThreadStartData*>(parameter);
@ -990,6 +1085,9 @@ std::unique_ptr<Thread> Thread::Create(CreationParameters params,
std::function<void()> start_routine) { std::function<void()> start_routine) {
install_signal_handler(SignalType::kThreadSuspend); install_signal_handler(SignalType::kThreadSuspend);
install_signal_handler(SignalType::kThreadUserCallback); install_signal_handler(SignalType::kThreadUserCallback);
#if XE_PLATFORM_ANDROID
install_signal_handler(SignalType::kThreadTerminate);
#endif
auto thread = std::make_unique<PosixThread>(); auto thread = std::make_unique<PosixThread>();
if (!thread->Initialize(params, std::move(start_routine))) return nullptr; if (!thread->Initialize(params, std::move(start_routine))) return nullptr;
assert_not_null(thread); assert_not_null(thread);
@ -1006,7 +1104,10 @@ Thread* Thread::GetCurrentThread() {
pthread_t handle = pthread_self(); pthread_t handle = pthread_self();
current_thread_ = new PosixThread(handle); current_thread_ = new PosixThread(handle);
atexit([] { delete current_thread_; }); // TODO(bwrsandman): Disabling deleting thread_local current thread to prevent
// assert in destructor. Since this is thread local, the
// "memory leaking" is controlled.
// atexit([] { delete current_thread_; });
return current_thread_; return current_thread_;
} }
@ -1023,6 +1124,15 @@ void Thread::Exit(int exit_code) {
} }
} }
void set_name(const std::string_view name) {
pthread_setname_np(pthread_self(), std::string(name).c_str());
#if XE_PLATFORM_ANDROID
if (xe::platform::android::api_level() < 26 && current_thread_) {
current_thread_->condition().SetAndroidPreApi26Name(name);
}
#endif
}
static void signal_handler(int signal, siginfo_t* info, void* /*context*/) { static void signal_handler(int signal, siginfo_t* info, void* /*context*/) {
switch (GetSystemSignalType(signal)) { switch (GetSystemSignalType(signal)) {
case SignalType::kHighResolutionTimer: { case SignalType::kHighResolutionTimer: {
@ -1049,6 +1159,11 @@ static void signal_handler(int signal, siginfo_t* info, void* /*context*/) {
p_thread->CallUserCallback(); p_thread->CallUserCallback();
} }
} break; } break;
#if XE_PLATFORM_ANDROID
case SignalType::kThreadTerminate: {
pthread_exit(reinterpret_cast<void*>(-1));
} break;
#endif
default: default:
assert_always(); assert_always();
} }

View File

@ -57,7 +57,7 @@ void raise_thread_name_exception(HANDLE thread, const std::string& name) {
} }
} }
void set_name(HANDLE thread, const std::string_view name) { static void set_name(HANDLE thread, const std::string_view name) {
auto kernel = GetModuleHandleW(L"kernel32.dll"); auto kernel = GetModuleHandleW(L"kernel32.dll");
if (kernel) { if (kernel) {
auto func = auto func =

View File

@ -10,6 +10,8 @@
#ifndef XENIA_CPU_BACKEND_CODE_CACHE_H_ #ifndef XENIA_CPU_BACKEND_CODE_CACHE_H_
#define XENIA_CPU_BACKEND_CODE_CACHE_H_ #define XENIA_CPU_BACKEND_CODE_CACHE_H_
#include <cstddef>
#include <cstdint>
#include <string> #include <string>
#include "xenia/cpu/function.h" #include "xenia/cpu/function.h"
@ -24,8 +26,8 @@ class CodeCache {
virtual ~CodeCache() = default; virtual ~CodeCache() = default;
virtual const std::filesystem::path& file_name() const = 0; virtual const std::filesystem::path& file_name() const = 0;
virtual uint32_t base_address() const = 0; virtual uintptr_t execute_base_address() const = 0;
virtual uint32_t total_size() const = 0; virtual size_t total_size() const = 0;
// Finds a function based on the given host PC (that may be within a // Finds a function based on the given host PC (that may be within a
// function). // function).

View File

@ -40,11 +40,18 @@ X64CodeCache::~X64CodeCache() {
} }
// Unmap all views and close mapping. // Unmap all views and close mapping.
if (mapping_) { if (mapping_ != xe::memory::kFileMappingHandleInvalid) {
xe::memory::UnmapFileView(mapping_, generated_code_base_, if (generated_code_write_base_ &&
kGeneratedCodeSize); generated_code_write_base_ != generated_code_execute_base_) {
xe::memory::CloseFileMappingHandle(mapping_); xe::memory::UnmapFileView(mapping_, generated_code_write_base_,
mapping_ = nullptr; kGeneratedCodeSize);
}
if (generated_code_execute_base_) {
xe::memory::UnmapFileView(mapping_, generated_code_execute_base_,
kGeneratedCodeSize);
}
xe::memory::CloseFileMappingHandle(mapping_, file_name_);
mapping_ = xe::memory::kFileMappingHandleInvalid;
} }
} }
@ -63,28 +70,51 @@ bool X64CodeCache::Initialize() {
} }
// Create mmap file. This allows us to share the code cache with the debugger. // Create mmap file. This allows us to share the code cache with the debugger.
file_name_ = file_name_ = fmt::format("xenia_code_cache_{}", Clock::QueryHostTickCount());
fmt::format("Local\\xenia_code_cache_{}", Clock::QueryHostTickCount());
mapping_ = xe::memory::CreateFileMappingHandle( mapping_ = xe::memory::CreateFileMappingHandle(
file_name_, kGeneratedCodeSize, xe::memory::PageAccess::kExecuteReadWrite, file_name_, kGeneratedCodeSize, xe::memory::PageAccess::kExecuteReadWrite,
false); false);
if (!mapping_) { if (mapping_ == xe::memory::kFileMappingHandleInvalid) {
XELOGE("Unable to create code cache mmap"); XELOGE("Unable to create code cache mmap");
return false; return false;
} }
// Map generated code region into the file. Pages are committed as required. // Map generated code region into the file. Pages are committed as required.
generated_code_base_ = reinterpret_cast<uint8_t*>(xe::memory::MapFileView( if (xe::memory::IsWritableExecutableMemoryPreferred()) {
mapping_, reinterpret_cast<void*>(kGeneratedCodeBase), kGeneratedCodeSize, generated_code_execute_base_ =
xe::memory::PageAccess::kExecuteReadWrite, 0)); reinterpret_cast<uint8_t*>(xe::memory::MapFileView(
if (!generated_code_base_) { mapping_, reinterpret_cast<void*>(kGeneratedCodeExecuteBase),
XELOGE("Unable to allocate code cache generated code storage"); kGeneratedCodeSize, xe::memory::PageAccess::kExecuteReadWrite, 0));
XELOGE( generated_code_write_base_ = generated_code_execute_base_;
"This is likely because the {:X}-{:X} range is in use by some other " if (!generated_code_execute_base_ || !generated_code_write_base_) {
"system DLL", XELOGE("Unable to allocate code cache generated code storage");
static_cast<uint64_t>(kGeneratedCodeBase), XELOGE(
kGeneratedCodeBase + kGeneratedCodeSize); "This is likely because the {:X}-{:X} range is in use by some other "
return false; "system DLL",
uint64_t(kGeneratedCodeExecuteBase),
uint64_t(kGeneratedCodeExecuteBase + kGeneratedCodeSize));
return false;
}
} else {
generated_code_execute_base_ =
reinterpret_cast<uint8_t*>(xe::memory::MapFileView(
mapping_, reinterpret_cast<void*>(kGeneratedCodeExecuteBase),
kGeneratedCodeSize, xe::memory::PageAccess::kExecuteReadOnly, 0));
generated_code_write_base_ =
reinterpret_cast<uint8_t*>(xe::memory::MapFileView(
mapping_, reinterpret_cast<void*>(kGeneratedCodeWriteBase),
kGeneratedCodeSize, xe::memory::PageAccess::kReadWrite, 0));
if (!generated_code_execute_base_ || !generated_code_write_base_) {
XELOGE("Unable to allocate code cache generated code storage");
XELOGE(
"This is likely because the {:X}-{:X} and {:X}-{:X} ranges are in "
"use by some other system DLL",
uint64_t(kGeneratedCodeExecuteBase),
uint64_t(kGeneratedCodeExecuteBase + kGeneratedCodeSize),
uint64_t(kGeneratedCodeWriteBase),
uint64_t(kGeneratedCodeWriteBase + kGeneratedCodeSize));
return false;
}
} }
// Preallocate the function map to a large, reasonable size. // Preallocate the function map to a large, reasonable size.
@ -118,7 +148,7 @@ void X64CodeCache::CommitExecutableRange(uint32_t guest_low,
xe::memory::AllocFixed( xe::memory::AllocFixed(
indirection_table_base_ + (guest_low - kIndirectionTableBase), indirection_table_base_ + (guest_low - kIndirectionTableBase),
guest_high - guest_low, xe::memory::AllocationType::kCommit, guest_high - guest_low, xe::memory::AllocationType::kCommit,
xe::memory::PageAccess::kExecuteReadWrite); xe::memory::PageAccess::kReadWrite);
// Fill memory with the default value. // Fill memory with the default value.
uint32_t* p = reinterpret_cast<uint32_t*>(indirection_table_base_); uint32_t* p = reinterpret_cast<uint32_t*>(indirection_table_base_);
@ -127,21 +157,26 @@ void X64CodeCache::CommitExecutableRange(uint32_t guest_low,
} }
} }
void* X64CodeCache::PlaceHostCode(uint32_t guest_address, void* machine_code, void X64CodeCache::PlaceHostCode(uint32_t guest_address, void* machine_code,
const EmitFunctionInfo& func_info) { const EmitFunctionInfo& func_info,
void*& code_execute_address_out,
void*& code_write_address_out) {
// Same for now. We may use different pools or whatnot later on, like when // Same for now. We may use different pools or whatnot later on, like when
// we only want to place guest code in a serialized cache on disk. // we only want to place guest code in a serialized cache on disk.
return PlaceGuestCode(guest_address, machine_code, func_info, nullptr); PlaceGuestCode(guest_address, machine_code, func_info, nullptr,
code_execute_address_out, code_write_address_out);
} }
void* X64CodeCache::PlaceGuestCode(uint32_t guest_address, void* machine_code, void X64CodeCache::PlaceGuestCode(uint32_t guest_address, void* machine_code,
const EmitFunctionInfo& func_info, const EmitFunctionInfo& func_info,
GuestFunction* function_info) { GuestFunction* function_info,
void*& code_execute_address_out,
void*& code_write_address_out) {
// Hold a lock while we bump the pointers up. This is important as the // Hold a lock while we bump the pointers up. This is important as the
// unwind table requires entries AND code to be sorted in order. // unwind table requires entries AND code to be sorted in order.
size_t low_mark; size_t low_mark;
size_t high_mark; size_t high_mark;
uint8_t* code_address; uint8_t* code_execute_address;
UnwindReservation unwind_reservation; UnwindReservation unwind_reservation;
{ {
auto global_lock = global_critical_region_.Acquire(); auto global_lock = global_critical_region_.Acquire();
@ -150,26 +185,33 @@ void* X64CodeCache::PlaceGuestCode(uint32_t guest_address, void* machine_code,
// Reserve code. // Reserve code.
// Always move the code to land on 16b alignment. // Always move the code to land on 16b alignment.
code_address = generated_code_base_ + generated_code_offset_; code_execute_address =
generated_code_execute_base_ + generated_code_offset_;
code_execute_address_out = code_execute_address;
uint8_t* code_write_address =
generated_code_write_base_ + generated_code_offset_;
code_write_address_out = code_write_address;
generated_code_offset_ += xe::round_up(func_info.code_size.total, 16); generated_code_offset_ += xe::round_up(func_info.code_size.total, 16);
auto tail_address = generated_code_base_ + generated_code_offset_; auto tail_write_address =
generated_code_write_base_ + generated_code_offset_;
// Reserve unwind info. // Reserve unwind info.
// We go on the high size of the unwind info as we don't know how big we // We go on the high size of the unwind info as we don't know how big we
// need it, and a few extra bytes of padding isn't the worst thing. // need it, and a few extra bytes of padding isn't the worst thing.
unwind_reservation = unwind_reservation = RequestUnwindReservation(generated_code_write_base_ +
RequestUnwindReservation(generated_code_base_ + generated_code_offset_); generated_code_offset_);
generated_code_offset_ += xe::round_up(unwind_reservation.data_size, 16); generated_code_offset_ += xe::round_up(unwind_reservation.data_size, 16);
auto end_address = generated_code_base_ + generated_code_offset_; auto end_write_address =
generated_code_write_base_ + generated_code_offset_;
high_mark = generated_code_offset_; high_mark = generated_code_offset_;
// Store in map. It is maintained in sorted order of host PC dependent on // Store in map. It is maintained in sorted order of host PC dependent on
// us also being append-only. // us also being append-only.
generated_code_map_.emplace_back( generated_code_map_.emplace_back(
(uint64_t(code_address - generated_code_base_) << 32) | (uint64_t(code_execute_address - generated_code_execute_base_) << 32) |
generated_code_offset_, generated_code_offset_,
function_info); function_info);
@ -186,21 +228,30 @@ void* X64CodeCache::PlaceGuestCode(uint32_t guest_address, void* machine_code,
if (high_mark <= old_commit_mark) break; if (high_mark <= old_commit_mark) break;
new_commit_mark = old_commit_mark + 16 * 1024 * 1024; new_commit_mark = old_commit_mark + 16 * 1024 * 1024;
xe::memory::AllocFixed(generated_code_base_, new_commit_mark, if (generated_code_execute_base_ == generated_code_write_base_) {
xe::memory::AllocationType::kCommit, xe::memory::AllocFixed(generated_code_execute_base_, new_commit_mark,
xe::memory::PageAccess::kExecuteReadWrite); xe::memory::AllocationType::kCommit,
xe::memory::PageAccess::kExecuteReadWrite);
} else {
xe::memory::AllocFixed(generated_code_execute_base_, new_commit_mark,
xe::memory::AllocationType::kCommit,
xe::memory::PageAccess::kExecuteReadOnly);
xe::memory::AllocFixed(generated_code_write_base_, new_commit_mark,
xe::memory::AllocationType::kCommit,
xe::memory::PageAccess::kReadWrite);
}
} while (generated_code_commit_mark_.compare_exchange_weak( } while (generated_code_commit_mark_.compare_exchange_weak(
old_commit_mark, new_commit_mark)); old_commit_mark, new_commit_mark));
// Copy code. // Copy code.
std::memcpy(code_address, machine_code, func_info.code_size.total); std::memcpy(code_write_address, machine_code, func_info.code_size.total);
// Fill unused slots with 0xCC // Fill unused slots with 0xCC
std::memset(tail_address, 0xCC, std::memset(tail_write_address, 0xCC,
static_cast<size_t>(end_address - tail_address)); static_cast<size_t>(end_write_address - tail_write_address));
// Notify subclasses of placed code. // Notify subclasses of placed code.
PlaceCode(guest_address, machine_code, func_info, code_address, PlaceCode(guest_address, machine_code, func_info, code_execute_address,
unwind_reservation); unwind_reservation);
} }
@ -215,7 +266,7 @@ void* X64CodeCache::PlaceGuestCode(uint32_t guest_address, void* machine_code,
iJIT_Method_Load_V2 method = {0}; iJIT_Method_Load_V2 method = {0};
method.method_id = iJIT_GetNewMethodID(); method.method_id = iJIT_GetNewMethodID();
method.method_load_address = code_address; method.method_load_address = code_execute_address;
method.method_size = uint32_t(code_size); method.method_size = uint32_t(code_size);
method.method_name = const_cast<char*>(method_name.data()); method.method_name = const_cast<char*>(method_name.data());
method.module_name = function_info method.module_name = function_info
@ -231,10 +282,9 @@ void* X64CodeCache::PlaceGuestCode(uint32_t guest_address, void* machine_code,
if (guest_address && indirection_table_base_) { if (guest_address && indirection_table_base_) {
uint32_t* indirection_slot = reinterpret_cast<uint32_t*>( uint32_t* indirection_slot = reinterpret_cast<uint32_t*>(
indirection_table_base_ + (guest_address - kIndirectionTableBase)); indirection_table_base_ + (guest_address - kIndirectionTableBase));
*indirection_slot = uint32_t(reinterpret_cast<uint64_t>(code_address)); *indirection_slot =
uint32_t(reinterpret_cast<uint64_t>(code_execute_address));
} }
return code_address;
} }
uint32_t X64CodeCache::PlaceData(const void* data, size_t length) { uint32_t X64CodeCache::PlaceData(const void* data, size_t length) {
@ -246,7 +296,7 @@ uint32_t X64CodeCache::PlaceData(const void* data, size_t length) {
// Reserve code. // Reserve code.
// Always move the code to land on 16b alignment. // Always move the code to land on 16b alignment.
data_address = generated_code_base_ + generated_code_offset_; data_address = generated_code_write_base_ + generated_code_offset_;
generated_code_offset_ += xe::round_up(length, 16); generated_code_offset_ += xe::round_up(length, 16);
high_mark = generated_code_offset_; high_mark = generated_code_offset_;
@ -261,9 +311,18 @@ uint32_t X64CodeCache::PlaceData(const void* data, size_t length) {
if (high_mark <= old_commit_mark) break; if (high_mark <= old_commit_mark) break;
new_commit_mark = old_commit_mark + 16 * 1024 * 1024; new_commit_mark = old_commit_mark + 16 * 1024 * 1024;
xe::memory::AllocFixed(generated_code_base_, new_commit_mark, if (generated_code_execute_base_ == generated_code_write_base_) {
xe::memory::AllocationType::kCommit, xe::memory::AllocFixed(generated_code_execute_base_, new_commit_mark,
xe::memory::PageAccess::kExecuteReadWrite); xe::memory::AllocationType::kCommit,
xe::memory::PageAccess::kExecuteReadWrite);
} else {
xe::memory::AllocFixed(generated_code_execute_base_, new_commit_mark,
xe::memory::AllocationType::kCommit,
xe::memory::PageAccess::kExecuteReadOnly);
xe::memory::AllocFixed(generated_code_write_base_, new_commit_mark,
xe::memory::AllocationType::kCommit,
xe::memory::PageAccess::kReadWrite);
}
} while (generated_code_commit_mark_.compare_exchange_weak(old_commit_mark, } while (generated_code_commit_mark_.compare_exchange_weak(old_commit_mark,
new_commit_mark)); new_commit_mark));
@ -274,7 +333,7 @@ uint32_t X64CodeCache::PlaceData(const void* data, size_t length) {
} }
GuestFunction* X64CodeCache::LookupFunction(uint64_t host_pc) { GuestFunction* X64CodeCache::LookupFunction(uint64_t host_pc) {
uint32_t key = uint32_t(host_pc - kGeneratedCodeBase); uint32_t key = uint32_t(host_pc - kGeneratedCodeExecuteBase);
void* fn_entry = std::bsearch( void* fn_entry = std::bsearch(
&key, generated_code_map_.data(), generated_code_map_.size() + 1, &key, generated_code_map_.data(), generated_code_map_.size() + 1,
sizeof(std::pair<uint32_t, Function*>), sizeof(std::pair<uint32_t, Function*>),

View File

@ -11,6 +11,8 @@
#define XENIA_CPU_BACKEND_X64_X64_CODE_CACHE_H_ #define XENIA_CPU_BACKEND_X64_X64_CODE_CACHE_H_
#include <atomic> #include <atomic>
#include <cstddef>
#include <cstdint>
#include <memory> #include <memory>
#include <string> #include <string>
#include <utility> #include <utility>
@ -46,8 +48,10 @@ class X64CodeCache : public CodeCache {
virtual bool Initialize(); virtual bool Initialize();
const std::filesystem::path& file_name() const override { return file_name_; } const std::filesystem::path& file_name() const override { return file_name_; }
uint32_t base_address() const override { return kGeneratedCodeBase; } uintptr_t execute_base_address() const override {
uint32_t total_size() const override { return kGeneratedCodeSize; } return kGeneratedCodeExecuteBase;
}
size_t total_size() const override { return kGeneratedCodeSize; }
// TODO(benvanik): ELF serialization/etc // TODO(benvanik): ELF serialization/etc
// TODO(benvanik): keep track of code blocks // TODO(benvanik): keep track of code blocks
@ -59,11 +63,15 @@ class X64CodeCache : public CodeCache {
void CommitExecutableRange(uint32_t guest_low, uint32_t guest_high); void CommitExecutableRange(uint32_t guest_low, uint32_t guest_high);
void* PlaceHostCode(uint32_t guest_address, void* machine_code, void PlaceHostCode(uint32_t guest_address, void* machine_code,
const EmitFunctionInfo& func_info); const EmitFunctionInfo& func_info,
void* PlaceGuestCode(uint32_t guest_address, void* machine_code, void*& code_execute_address_out,
const EmitFunctionInfo& func_info, void*& code_write_address_out);
GuestFunction* function_info); void PlaceGuestCode(uint32_t guest_address, void* machine_code,
const EmitFunctionInfo& func_info,
GuestFunction* function_info,
void*& code_execute_address_out,
void*& code_write_address_out);
uint32_t PlaceData(const void* data, size_t length); uint32_t PlaceData(const void* data, size_t length);
GuestFunction* LookupFunction(uint64_t host_pc) override; GuestFunction* LookupFunction(uint64_t host_pc) override;
@ -71,13 +79,16 @@ class X64CodeCache : public CodeCache {
protected: protected:
// All executable code falls within 0x80000000 to 0x9FFFFFFF, so we can // All executable code falls within 0x80000000 to 0x9FFFFFFF, so we can
// only map enough for lookups within that range. // only map enough for lookups within that range.
static const uint64_t kIndirectionTableBase = 0x80000000; static const size_t kIndirectionTableSize = 0x1FFFFFFF;
static const uint64_t kIndirectionTableSize = 0x1FFFFFFF; static const uintptr_t kIndirectionTableBase = 0x80000000;
// The code range is 512MB, but we know the total code games will have is // The code range is 512MB, but we know the total code games will have is
// pretty small (dozens of mb at most) and our expansion is reasonablish // pretty small (dozens of mb at most) and our expansion is reasonablish
// so 256MB should be more than enough. // so 256MB should be more than enough.
static const uint64_t kGeneratedCodeBase = 0xA0000000; static const size_t kGeneratedCodeSize = 0x0FFFFFFF;
static const uint64_t kGeneratedCodeSize = 0x0FFFFFFF; static const uintptr_t kGeneratedCodeExecuteBase = 0xA0000000;
// Used for writing when PageAccess::kExecuteReadWrite is not supported.
static const uintptr_t kGeneratedCodeWriteBase =
kGeneratedCodeExecuteBase + kGeneratedCodeSize + 1;
// This is picked to be high enough to cover whatever we can reasonably // This is picked to be high enough to cover whatever we can reasonably
// expect. If we hit issues with this it probably means some corner case // expect. If we hit issues with this it probably means some corner case
@ -96,11 +107,13 @@ class X64CodeCache : public CodeCache {
return UnwindReservation(); return UnwindReservation();
} }
virtual void PlaceCode(uint32_t guest_address, void* machine_code, virtual void PlaceCode(uint32_t guest_address, void* machine_code,
const EmitFunctionInfo& func_info, void* code_address, const EmitFunctionInfo& func_info,
void* code_execute_address,
UnwindReservation unwind_reservation) {} UnwindReservation unwind_reservation) {}
std::filesystem::path file_name_; std::filesystem::path file_name_;
xe::memory::FileMappingHandle mapping_ = nullptr; xe::memory::FileMappingHandle mapping_ =
xe::memory::kFileMappingHandleInvalid;
// NOTE: the global critical region must be held when manipulating the offsets // NOTE: the global critical region must be held when manipulating the offsets
// or counts of anything, to keep the tables consistent and ordered. // or counts of anything, to keep the tables consistent and ordered.
@ -113,9 +126,13 @@ class X64CodeCache : public CodeCache {
// the generated code table that correspond to the PPC functions in guest // the generated code table that correspond to the PPC functions in guest
// space. // space.
uint8_t* indirection_table_base_ = nullptr; uint8_t* indirection_table_base_ = nullptr;
// Fixed at kGeneratedCodeBase and holding all generated code, growing as // Fixed at kGeneratedCodeExecuteBase and holding all generated code, growing
// needed. // as needed.
uint8_t* generated_code_base_ = nullptr; uint8_t* generated_code_execute_base_ = nullptr;
// View of the memory that backs generated_code_execute_base_ when
// PageAccess::kExecuteReadWrite is not supported, for writing the generated
// code. Equals to generated_code_execute_base_ when it's supported.
uint8_t* generated_code_write_base_ = nullptr;
// Current offset to empty space in generated code. // Current offset to empty space in generated code.
size_t generated_code_offset_ = 0; size_t generated_code_offset_ = 0;
// Current high water mark of COMMITTED code. // Current high water mark of COMMITTED code.

View File

@ -27,7 +27,7 @@ class PosixX64CodeCache : public X64CodeCache {
/* /*
UnwindReservation RequestUnwindReservation(uint8_t* entry_address) override; UnwindReservation RequestUnwindReservation(uint8_t* entry_address) override;
void PlaceCode(uint32_t guest_address, void* machine_code, size_t code_size, void PlaceCode(uint32_t guest_address, void* machine_code, size_t code_size,
size_t stack_size, void* code_address, size_t stack_size, void* code_execute_address,
UnwindReservation unwind_reservation) override; UnwindReservation unwind_reservation) override;
void InitializeUnwindEntry(uint8_t* unwind_entry_address, void InitializeUnwindEntry(uint8_t* unwind_entry_address,

View File

@ -107,11 +107,12 @@ class Win32X64CodeCache : public X64CodeCache {
private: private:
UnwindReservation RequestUnwindReservation(uint8_t* entry_address) override; UnwindReservation RequestUnwindReservation(uint8_t* entry_address) override;
void PlaceCode(uint32_t guest_address, void* machine_code, void PlaceCode(uint32_t guest_address, void* machine_code,
const EmitFunctionInfo& func_info, void* code_address, const EmitFunctionInfo& func_info, void* code_execute_address,
UnwindReservation unwind_reservation) override; UnwindReservation unwind_reservation) override;
void InitializeUnwindEntry(uint8_t* unwind_entry_address, void InitializeUnwindEntry(uint8_t* unwind_entry_address,
size_t unwind_table_slot, void* code_address, size_t unwind_table_slot,
void* code_execute_address,
const EmitFunctionInfo& func_info); const EmitFunctionInfo& func_info);
// Growable function table system handle. // Growable function table system handle.
@ -140,9 +141,9 @@ Win32X64CodeCache::~Win32X64CodeCache() {
delete_growable_table_(unwind_table_handle_); delete_growable_table_(unwind_table_handle_);
} }
} else { } else {
if (generated_code_base_) { if (generated_code_execute_base_) {
RtlDeleteFunctionTable(reinterpret_cast<PRUNTIME_FUNCTION>( RtlDeleteFunctionTable(reinterpret_cast<PRUNTIME_FUNCTION>(
reinterpret_cast<DWORD64>(generated_code_base_) | 0x3)); reinterpret_cast<DWORD64>(generated_code_execute_base_) | 0x3));
} }
} }
} }
@ -176,11 +177,12 @@ bool Win32X64CodeCache::Initialize() {
// Create table and register with the system. It's empty now, but we'll grow // Create table and register with the system. It's empty now, but we'll grow
// it as functions are added. // it as functions are added.
if (supports_growable_table_) { if (supports_growable_table_) {
if (add_growable_table_(&unwind_table_handle_, unwind_table_.data(), if (add_growable_table_(
unwind_table_count_, DWORD(unwind_table_.size()), &unwind_table_handle_, unwind_table_.data(), unwind_table_count_,
reinterpret_cast<ULONG_PTR>(generated_code_base_), DWORD(unwind_table_.size()),
reinterpret_cast<ULONG_PTR>(generated_code_base_ + reinterpret_cast<ULONG_PTR>(generated_code_execute_base_),
kGeneratedCodeSize))) { reinterpret_cast<ULONG_PTR>(generated_code_execute_base_ +
kGeneratedCodeSize))) {
XELOGE("Unable to create unwind function table"); XELOGE("Unable to create unwind function table");
return false; return false;
} }
@ -188,8 +190,9 @@ bool Win32X64CodeCache::Initialize() {
// Install a callback that the debugger will use to lookup unwind info on // Install a callback that the debugger will use to lookup unwind info on
// demand. // demand.
if (!RtlInstallFunctionTableCallback( if (!RtlInstallFunctionTableCallback(
reinterpret_cast<DWORD64>(generated_code_base_) | 0x3, reinterpret_cast<DWORD64>(generated_code_execute_base_) | 0x3,
reinterpret_cast<DWORD64>(generated_code_base_), kGeneratedCodeSize, reinterpret_cast<DWORD64>(generated_code_execute_base_),
kGeneratedCodeSize,
[](DWORD64 control_pc, PVOID context) { [](DWORD64 control_pc, PVOID context) {
auto code_cache = reinterpret_cast<Win32X64CodeCache*>(context); auto code_cache = reinterpret_cast<Win32X64CodeCache*>(context);
return reinterpret_cast<PRUNTIME_FUNCTION>( return reinterpret_cast<PRUNTIME_FUNCTION>(
@ -216,11 +219,12 @@ Win32X64CodeCache::RequestUnwindReservation(uint8_t* entry_address) {
void Win32X64CodeCache::PlaceCode(uint32_t guest_address, void* machine_code, void Win32X64CodeCache::PlaceCode(uint32_t guest_address, void* machine_code,
const EmitFunctionInfo& func_info, const EmitFunctionInfo& func_info,
void* code_address, void* code_execute_address,
UnwindReservation unwind_reservation) { UnwindReservation unwind_reservation) {
// Add unwind info. // Add unwind info.
InitializeUnwindEntry(unwind_reservation.entry_address, InitializeUnwindEntry(unwind_reservation.entry_address,
unwind_reservation.table_slot, code_address, func_info); unwind_reservation.table_slot, code_execute_address,
func_info);
if (supports_growable_table_) { if (supports_growable_table_) {
// Notify that the unwind table has grown. // Notify that the unwind table has grown.
@ -229,13 +233,15 @@ void Win32X64CodeCache::PlaceCode(uint32_t guest_address, void* machine_code,
} }
// This isn't needed on x64 (probably), but is convention. // This isn't needed on x64 (probably), but is convention.
FlushInstructionCache(GetCurrentProcess(), code_address, // On UWP, FlushInstructionCache available starting from 10.0.16299.0.
// https://docs.microsoft.com/en-us/uwp/win32-and-com/win32-apis
FlushInstructionCache(GetCurrentProcess(), code_execute_address,
func_info.code_size.total); func_info.code_size.total);
} }
void Win32X64CodeCache::InitializeUnwindEntry( void Win32X64CodeCache::InitializeUnwindEntry(
uint8_t* unwind_entry_address, size_t unwind_table_slot, void* code_address, uint8_t* unwind_entry_address, size_t unwind_table_slot,
const EmitFunctionInfo& func_info) { void* code_execute_address, const EmitFunctionInfo& func_info) {
auto unwind_info = reinterpret_cast<UNWIND_INFO*>(unwind_entry_address); auto unwind_info = reinterpret_cast<UNWIND_INFO*>(unwind_entry_address);
UNWIND_CODE* unwind_code = nullptr; UNWIND_CODE* unwind_code = nullptr;
@ -299,10 +305,12 @@ void Win32X64CodeCache::InitializeUnwindEntry(
// Add entry. // Add entry.
auto& fn_entry = unwind_table_[unwind_table_slot]; auto& fn_entry = unwind_table_[unwind_table_slot];
fn_entry.BeginAddress = fn_entry.BeginAddress =
(DWORD)(reinterpret_cast<uint8_t*>(code_address) - generated_code_base_); DWORD(reinterpret_cast<uint8_t*>(code_execute_address) -
generated_code_execute_base_);
fn_entry.EndAddress = fn_entry.EndAddress =
(DWORD)(fn_entry.BeginAddress + func_info.code_size.total); DWORD(fn_entry.BeginAddress + func_info.code_size.total);
fn_entry.UnwindData = (DWORD)(unwind_entry_address - generated_code_base_); fn_entry.UnwindData =
DWORD(unwind_entry_address - generated_code_execute_base_);
} }
void* Win32X64CodeCache::LookupUnwindInfo(uint64_t host_pc) { void* Win32X64CodeCache::LookupUnwindInfo(uint64_t host_pc) {
@ -310,8 +318,8 @@ void* Win32X64CodeCache::LookupUnwindInfo(uint64_t host_pc) {
&host_pc, unwind_table_.data(), unwind_table_count_, &host_pc, unwind_table_.data(), unwind_table_count_,
sizeof(RUNTIME_FUNCTION), sizeof(RUNTIME_FUNCTION),
[](const void* key_ptr, const void* element_ptr) { [](const void* key_ptr, const void* element_ptr) {
auto key = auto key = *reinterpret_cast<const uintptr_t*>(key_ptr) -
*reinterpret_cast<const uintptr_t*>(key_ptr) - kGeneratedCodeBase; kGeneratedCodeExecuteBase;
auto element = reinterpret_cast<const RUNTIME_FUNCTION*>(element_ptr); auto element = reinterpret_cast<const RUNTIME_FUNCTION*>(element_ptr);
if (key < element->BeginAddress) { if (key < element->BeginAddress) {
return -1; return -1;

View File

@ -125,20 +125,26 @@ void* X64Emitter::Emplace(const EmitFunctionInfo& func_info,
// top_ points to the Xbyak buffer, and since we are in AutoGrow mode // top_ points to the Xbyak buffer, and since we are in AutoGrow mode
// it has pending relocations. We copy the top_ to our buffer, swap the // it has pending relocations. We copy the top_ to our buffer, swap the
// pointer, relocate, then return the original scratch pointer for use. // pointer, relocate, then return the original scratch pointer for use.
// top_ is used by Xbyak's ready() as both write base pointer and the absolute
// address base, which would not work on platforms not supporting writable
// executable memory, but Xenia doesn't use absolute label addresses in the
// generated code.
uint8_t* old_address = top_; uint8_t* old_address = top_;
void* new_address; void* new_execute_address;
void* new_write_address;
assert_true(func_info.code_size.total == size_); assert_true(func_info.code_size.total == size_);
if (function) { if (function) {
new_address = code_cache_->PlaceGuestCode(function->address(), top_, code_cache_->PlaceGuestCode(function->address(), top_, func_info, function,
func_info, function); new_execute_address, new_write_address);
} else { } else {
new_address = code_cache_->PlaceHostCode(0, top_, func_info); code_cache_->PlaceHostCode(0, top_, func_info, new_execute_address,
new_write_address);
} }
top_ = reinterpret_cast<uint8_t*>(new_address); top_ = reinterpret_cast<uint8_t*>(new_write_address);
ready(); ready();
top_ = old_address; top_ = old_address;
reset(); reset();
return new_address; return new_execute_address;
} }
bool X64Emitter::Emit(HIRBuilder* builder, EmitFunctionInfo& func_info) { bool X64Emitter::Emit(HIRBuilder* builder, EmitFunctionInfo& func_info) {

View File

@ -177,6 +177,9 @@ class TestRunner {
public: public:
TestRunner() { TestRunner() {
memory_size_ = 64 * 1024 * 1024; memory_size_ = 64 * 1024 * 1024;
// FIXME(Triang3l): If this is ever compiled for a platform without
// xe::memory::IsWritableExecutableMemorySupported, two memory mappings must
// be used.
memory_ = memory::AllocFixed(nullptr, memory_size_, memory_ = memory::AllocFixed(nullptr, memory_size_,
memory::AllocationType::kReserveCommit, memory::AllocationType::kReserveCommit,
memory::PageAccess::kExecuteReadWrite); memory::PageAccess::kExecuteReadWrite);

View File

@ -9,6 +9,7 @@
#include "xenia/cpu/stack_walker.h" #include "xenia/cpu/stack_walker.h"
#include <cstdint>
#include <mutex> #include <mutex>
#include "xenia/base/logging.h" #include "xenia/base/logging.h"
@ -120,8 +121,8 @@ class Win32StackWalker : public StackWalker {
// They never change, so it's fine even if they are touched from multiple // They never change, so it's fine even if they are touched from multiple
// threads. // threads.
code_cache_ = code_cache; code_cache_ = code_cache;
code_cache_min_ = code_cache_->base_address(); code_cache_min_ = code_cache_->execute_base_address();
code_cache_max_ = code_cache_->base_address() + code_cache_->total_size(); code_cache_max_ = code_cache_min_ + code_cache_->total_size();
} }
bool Initialize() { bool Initialize() {
@ -297,13 +298,13 @@ class Win32StackWalker : public StackWalker {
std::mutex dbghelp_mutex_; std::mutex dbghelp_mutex_;
static xe::cpu::backend::CodeCache* code_cache_; static xe::cpu::backend::CodeCache* code_cache_;
static uint32_t code_cache_min_; static uintptr_t code_cache_min_;
static uint32_t code_cache_max_; static uintptr_t code_cache_max_;
}; };
xe::cpu::backend::CodeCache* Win32StackWalker::code_cache_ = nullptr; xe::cpu::backend::CodeCache* Win32StackWalker::code_cache_ = nullptr;
uint32_t Win32StackWalker::code_cache_min_ = 0; uintptr_t Win32StackWalker::code_cache_min_ = 0;
uint32_t Win32StackWalker::code_cache_max_ = 0; uintptr_t Win32StackWalker::code_cache_max_ = 0;
std::unique_ptr<StackWalker> StackWalker::Create( std::unique_ptr<StackWalker> StackWalker::Create(
backend::CodeCache* code_cache) { backend::CodeCache* code_cache) {

View File

@ -1454,7 +1454,7 @@ void DebugWindow::UpdateCache() {
// Fetch module listing. // Fetch module listing.
// We hold refs so that none are unloaded. // We hold refs so that none are unloaded.
cache_.modules = cache_.modules =
object_table->GetObjectsByType<XModule>(XObject::Type::kTypeModule); object_table->GetObjectsByType<XModule>(XObject::Type::Module);
cache_.thread_debug_infos = processor_->QueryThreadDebugInfos(); cache_.thread_debug_infos = processor_->QueryThreadDebugInfos();

View File

@ -394,7 +394,7 @@ void Emulator::Pause() {
auto lock = global_critical_region::AcquireDirect(); auto lock = global_critical_region::AcquireDirect();
auto threads = auto threads =
kernel_state()->object_table()->GetObjectsByType<kernel::XThread>( kernel_state()->object_table()->GetObjectsByType<kernel::XThread>(
kernel::XObject::kTypeThread); kernel::XObject::Type::Thread);
auto current_thread = kernel::XThread::IsInThread() auto current_thread = kernel::XThread::IsInThread()
? kernel::XThread::GetCurrentThread() ? kernel::XThread::GetCurrentThread()
: nullptr; : nullptr;
@ -424,7 +424,7 @@ void Emulator::Resume() {
auto threads = auto threads =
kernel_state()->object_table()->GetObjectsByType<kernel::XThread>( kernel_state()->object_table()->GetObjectsByType<kernel::XThread>(
kernel::XObject::kTypeThread); kernel::XObject::Type::Thread);
for (auto thread : threads) { for (auto thread : threads) {
if (!thread->can_debugger_suspend()) { if (!thread->can_debugger_suspend()) {
// Don't pause host threads. // Don't pause host threads.
@ -578,7 +578,7 @@ bool Emulator::ExceptionCallbackThunk(Exception* ex, void* data) {
bool Emulator::ExceptionCallback(Exception* ex) { bool Emulator::ExceptionCallback(Exception* ex) {
// Check to see if the exception occurred in guest code. // Check to see if the exception occurred in guest code.
auto code_cache = processor()->backend()->code_cache(); auto code_cache = processor()->backend()->code_cache();
auto code_base = code_cache->base_address(); auto code_base = code_cache->execute_base_address();
auto code_end = code_base + code_cache->total_size(); auto code_end = code_base + code_cache->total_size();
if (!processor()->is_debugger_attached() && debugging::IsDebuggerAttached()) { if (!processor()->is_debugger_attached() && debugging::IsDebuggerAttached()) {

View File

@ -1182,9 +1182,11 @@ bool CommandProcessor::ExecutePacketType3_DRAW_INDX(RingBuffer* reader,
// initiate fetch of index buffer and draw // initiate fetch of index buffer and draw
// if dword0 != 0, this is a conditional draw based on viz query. // if dword0 != 0, this is a conditional draw based on viz query.
// This ID matches the one issued in PM4_VIZ_QUERY // This ID matches the one issued in PM4_VIZ_QUERY
// ID = dword0 & 0x3F;
// use = dword0 & 0x40;
uint32_t dword0 = reader->ReadAndSwap<uint32_t>(); // viz query info uint32_t dword0 = reader->ReadAndSwap<uint32_t>(); // viz query info
// uint32_t viz_id = dword0 & 0x3F;
// when true, render conditionally based on query result
// uint32_t viz_use = dword0 & 0x100;
reg::VGT_DRAW_INITIATOR vgt_draw_initiator; reg::VGT_DRAW_INITIATOR vgt_draw_initiator;
vgt_draw_initiator.value = reader->ReadAndSwap<uint32_t>(); vgt_draw_initiator.value = reader->ReadAndSwap<uint32_t>();
WriteRegister(XE_GPU_REG_VGT_DRAW_INITIATOR, vgt_draw_initiator.value); WriteRegister(XE_GPU_REG_VGT_DRAW_INITIATOR, vgt_draw_initiator.value);
@ -1221,6 +1223,14 @@ bool CommandProcessor::ExecutePacketType3_DRAW_INDX(RingBuffer* reader,
} break; } break;
} }
auto viz_query = register_file_->Get<reg::PA_SC_VIZ_QUERY>();
if (viz_query.viz_query_ena && viz_query.kill_pix_post_hi_z) {
// TODO(Triang3l): Don't drop the draw call completely if the vertex shader
// has memexport.
// TODO(Triang3l || JoelLinn): Handle this properly in the render backends.
return true;
}
bool success = bool success =
IssueDraw(vgt_draw_initiator.prim_type, vgt_draw_initiator.num_indices, IssueDraw(vgt_draw_initiator.prim_type, vgt_draw_initiator.num_indices,
is_indexed ? &index_buffer_info : nullptr, is_indexed ? &index_buffer_info : nullptr,
@ -1254,6 +1264,14 @@ bool CommandProcessor::ExecutePacketType3_DRAW_INDX_2(RingBuffer* reader,
// TODO(Triang3l): VGT_IMMED_DATA. // TODO(Triang3l): VGT_IMMED_DATA.
reader->AdvanceRead((count - 1) * sizeof(uint32_t)); reader->AdvanceRead((count - 1) * sizeof(uint32_t));
auto viz_query = register_file_->Get<reg::PA_SC_VIZ_QUERY>();
if (viz_query.viz_query_ena && viz_query.kill_pix_post_hi_z) {
// TODO(Triang3l): Don't drop the draw call completely if the vertex shader
// has memexport.
// TODO(Triang3l || JoelLinn): Handle this properly in the render backends.
return true;
}
bool success = IssueDraw( bool success = IssueDraw(
vgt_draw_initiator.prim_type, vgt_draw_initiator.num_indices, nullptr, vgt_draw_initiator.prim_type, vgt_draw_initiator.num_indices, nullptr,
xenos::IsMajorModeExplicit(vgt_draw_initiator.major_mode, xenos::IsMajorModeExplicit(vgt_draw_initiator.major_mode,
@ -1450,15 +1468,26 @@ bool CommandProcessor::ExecutePacketType3_VIZ_QUERY(RingBuffer* reader,
uint32_t dword0 = reader->ReadAndSwap<uint32_t>(); uint32_t dword0 = reader->ReadAndSwap<uint32_t>();
uint32_t id = dword0 & 0x3F; uint32_t id = dword0 & 0x3F;
uint32_t end = dword0 & 0x80; uint32_t end = dword0 & 0x100;
if (!end) { if (!end) {
// begin a new viz query @ id // begin a new viz query @ id
// On hardware this clears the internal state of the scan converter (which
// is different to the register)
WriteRegister(XE_GPU_REG_VGT_EVENT_INITIATOR, VIZQUERY_START); WriteRegister(XE_GPU_REG_VGT_EVENT_INITIATOR, VIZQUERY_START);
XELOGGPU("Begin viz query ID {:02X}", id); XELOGGPU("Begin viz query ID {:02X}", id);
} else { } else {
// end the viz query // end the viz query
WriteRegister(XE_GPU_REG_VGT_EVENT_INITIATOR, VIZQUERY_END); WriteRegister(XE_GPU_REG_VGT_EVENT_INITIATOR, VIZQUERY_END);
XELOGGPU("End viz query ID {:02X}", id); XELOGGPU("End viz query ID {:02X}", id);
// The scan converter writes the internal result back to the register here.
// We just fake it and say it was visible in case it is read back.
if (id < 32) {
register_file_->values[XE_GPU_REG_PA_SC_VIZ_QUERY_STATUS_0].u32 |=
uint32_t(1) << id;
} else {
register_file_->values[XE_GPU_REG_PA_SC_VIZ_QUERY_STATUS_1].u32 |=
uint32_t(1) << (id - 32);
}
} }
return true; return true;

View File

@ -1876,14 +1876,14 @@ bool D3D12CommandProcessor::IssueDraw(xenos::PrimitiveType primitive_type,
!pixel_shader->memexport_stream_constants().empty(); !pixel_shader->memexport_stream_constants().empty();
bool memexport_used = memexport_used_vertex || memexport_used_pixel; bool memexport_used = memexport_used_vertex || memexport_used_pixel;
bool primitive_two_faced = bool primitive_polygonal =
xenos::IsPrimitiveTwoFaced(tessellated, primitive_type); xenos::IsPrimitivePolygonal(tessellated, primitive_type);
auto sq_program_cntl = regs.Get<reg::SQ_PROGRAM_CNTL>(); auto sq_program_cntl = regs.Get<reg::SQ_PROGRAM_CNTL>();
auto pa_su_sc_mode_cntl = regs.Get<reg::PA_SU_SC_MODE_CNTL>(); auto pa_su_sc_mode_cntl = regs.Get<reg::PA_SU_SC_MODE_CNTL>();
if (!memexport_used_vertex && if (!memexport_used_vertex &&
(sq_program_cntl.vs_export_mode == (sq_program_cntl.vs_export_mode ==
xenos::VertexShaderExportMode::kMultipass || xenos::VertexShaderExportMode::kMultipass ||
(primitive_two_faced && pa_su_sc_mode_cntl.cull_front && (primitive_polygonal && pa_su_sc_mode_cntl.cull_front &&
pa_su_sc_mode_cntl.cull_back))) { pa_su_sc_mode_cntl.cull_back))) {
// All faces are culled - can't be expressed in the pipeline. // All faces are culled - can't be expressed in the pipeline.
return true; return true;
@ -1996,15 +1996,45 @@ bool D3D12CommandProcessor::IssueDraw(xenos::PrimitiveType primitive_type,
current_external_pipeline_ = nullptr; current_external_pipeline_ = nullptr;
} }
// Get dynamic rasterizer state.
// Supersampling replacing multisampling due to difficulties of emulating
// EDRAM with multisampling with RTV/DSV (with ROV, there's MSAA), and also
// resolution scale.
uint32_t pixel_size_x, pixel_size_y;
if (edram_rov_used_) {
pixel_size_x = 1;
pixel_size_y = 1;
} else {
xenos::MsaaSamples msaa_samples =
regs.Get<reg::RB_SURFACE_INFO>().msaa_samples;
pixel_size_x = msaa_samples >= xenos::MsaaSamples::k4X ? 2 : 1;
pixel_size_y = msaa_samples >= xenos::MsaaSamples::k2X ? 2 : 1;
}
if (texture_cache_->IsResolutionScale2X()) {
pixel_size_x *= 2;
pixel_size_y *= 2;
}
draw_util::ViewportInfo viewport_info;
draw_util::GetHostViewportInfo(regs, float(pixel_size_x), float(pixel_size_y),
true, float(D3D12_VIEWPORT_BOUNDS_MAX),
float(D3D12_VIEWPORT_BOUNDS_MAX), false,
viewport_info);
draw_util::Scissor scissor;
draw_util::GetScissor(regs, scissor);
scissor.left *= pixel_size_x;
scissor.top *= pixel_size_y;
scissor.width *= pixel_size_x;
scissor.height *= pixel_size_y;
// Update viewport, scissor, blend factor and stencil reference. // Update viewport, scissor, blend factor and stencil reference.
UpdateFixedFunctionState(primitive_two_faced); UpdateFixedFunctionState(viewport_info, scissor, primitive_polygonal);
// Update system constants before uploading them. // Update system constants before uploading them.
UpdateSystemConstantValues( UpdateSystemConstantValues(
memexport_used, primitive_two_faced, line_loop_closing_index, memexport_used, primitive_polygonal, line_loop_closing_index,
indexed ? index_buffer_info->endianness : xenos::Endian::kNone, indexed ? index_buffer_info->endianness : xenos::Endian::kNone,
used_texture_mask, early_z, GetCurrentColorMask(pixel_shader), viewport_info, pixel_size_x, pixel_size_y, used_texture_mask, early_z,
pipeline_render_targets); GetCurrentColorMask(pixel_shader), pipeline_render_targets);
// Update constant buffers, descriptors and root parameters. // Update constant buffers, descriptors and root parameters.
if (!UpdateBindings(vertex_shader, pixel_shader, root_signature)) { if (!UpdateBindings(vertex_shader, pixel_shader, root_signature)) {
@ -2753,87 +2783,21 @@ void D3D12CommandProcessor::ClearCommandAllocatorCache() {
command_allocator_writable_last_ = nullptr; command_allocator_writable_last_ = nullptr;
} }
void D3D12CommandProcessor::UpdateFixedFunctionState(bool primitive_two_faced) { void D3D12CommandProcessor::UpdateFixedFunctionState(
const draw_util::ViewportInfo& viewport_info,
const draw_util::Scissor& scissor, bool primitive_polygonal) {
#if XE_UI_D3D12_FINE_GRAINED_DRAW_SCOPES #if XE_UI_D3D12_FINE_GRAINED_DRAW_SCOPES
SCOPE_profile_cpu_f("gpu"); SCOPE_profile_cpu_f("gpu");
#endif // XE_UI_D3D12_FINE_GRAINED_DRAW_SCOPES #endif // XE_UI_D3D12_FINE_GRAINED_DRAW_SCOPES
const RegisterFile& regs = *register_file_;
// Window parameters.
// http://ftp.tku.edu.tw/NetBSD/NetBSD-current/xsrc/external/mit/xf86-video-ati/dist/src/r600_reg_auto_r6xx.h
// See r200UpdateWindow:
// https://github.com/freedreno/mesa/blob/master/src/mesa/drivers/dri/r200/r200_state.c
auto pa_sc_window_offset = regs.Get<reg::PA_SC_WINDOW_OFFSET>();
// Supersampling replacing multisampling due to difficulties of emulating
// EDRAM with multisampling with RTV/DSV (with ROV, there's MSAA), and also
// resolution scale.
uint32_t pixel_size_x, pixel_size_y;
if (edram_rov_used_) {
pixel_size_x = 1;
pixel_size_y = 1;
} else {
xenos::MsaaSamples msaa_samples =
regs.Get<reg::RB_SURFACE_INFO>().msaa_samples;
pixel_size_x = msaa_samples >= xenos::MsaaSamples::k4X ? 2 : 1;
pixel_size_y = msaa_samples >= xenos::MsaaSamples::k2X ? 2 : 1;
}
if (texture_cache_->IsResolutionScale2X()) {
pixel_size_x *= 2;
pixel_size_y *= 2;
}
// Viewport. // Viewport.
// PA_CL_VTE_CNTL contains whether offsets and scales are enabled.
// http://www.x.org/docs/AMD/old/evergreen_3D_registers_v2.pdf
// In games, either all are enabled (for regular drawing) or none are (for
// rectangle lists usually).
//
// If scale/offset is enabled, the Xenos shader is writing (neglecting W
// division) position in the NDC (-1, -1, dx_clip_space_def - 1) -> (1, 1, 1)
// box. If it's not, the position is in screen space. Since we can only use
// the NDC in PC APIs, we use a viewport of the largest possible size, and
// divide the position by it in translated shaders.
auto pa_cl_vte_cntl = regs.Get<reg::PA_CL_VTE_CNTL>();
float viewport_scale_x =
pa_cl_vte_cntl.vport_x_scale_ena
? std::abs(regs[XE_GPU_REG_PA_CL_VPORT_XSCALE].f32)
: 4096.0f;
float viewport_scale_y =
pa_cl_vte_cntl.vport_y_scale_ena
? std::abs(regs[XE_GPU_REG_PA_CL_VPORT_YSCALE].f32)
: 4096.0f;
float viewport_scale_z = pa_cl_vte_cntl.vport_z_scale_ena
? regs[XE_GPU_REG_PA_CL_VPORT_ZSCALE].f32
: 1.0f;
float viewport_offset_x = pa_cl_vte_cntl.vport_x_offset_ena
? regs[XE_GPU_REG_PA_CL_VPORT_XOFFSET].f32
: std::abs(viewport_scale_x);
float viewport_offset_y = pa_cl_vte_cntl.vport_y_offset_ena
? regs[XE_GPU_REG_PA_CL_VPORT_YOFFSET].f32
: std::abs(viewport_scale_y);
float viewport_offset_z = pa_cl_vte_cntl.vport_z_offset_ena
? regs[XE_GPU_REG_PA_CL_VPORT_ZOFFSET].f32
: 0.0f;
if (regs.Get<reg::PA_SU_SC_MODE_CNTL>().vtx_window_offset_enable) {
viewport_offset_x += float(pa_sc_window_offset.window_x_offset);
viewport_offset_y += float(pa_sc_window_offset.window_y_offset);
}
D3D12_VIEWPORT viewport; D3D12_VIEWPORT viewport;
viewport.TopLeftX = viewport.TopLeftX = viewport_info.left;
(viewport_offset_x - viewport_scale_x) * float(pixel_size_x); viewport.TopLeftY = viewport_info.top;
viewport.TopLeftY = viewport.Width = viewport_info.width;
(viewport_offset_y - viewport_scale_y) * float(pixel_size_y); viewport.Height = viewport_info.height;
viewport.Width = viewport_scale_x * 2.0f * float(pixel_size_x); viewport.MinDepth = viewport_info.z_min;
viewport.Height = viewport_scale_y * 2.0f * float(pixel_size_y); viewport.MaxDepth = viewport_info.z_max;
viewport.MinDepth = viewport_offset_z;
viewport.MaxDepth = viewport_offset_z + viewport_scale_z;
if (viewport_scale_z < 0.0f) {
// MinDepth > MaxDepth doesn't work on Nvidia, emulating it in vertex
// shaders and when applying polygon offset.
std::swap(viewport.MinDepth, viewport.MaxDepth);
}
ff_viewport_update_needed_ |= ff_viewport_.TopLeftX != viewport.TopLeftX; ff_viewport_update_needed_ |= ff_viewport_.TopLeftX != viewport.TopLeftX;
ff_viewport_update_needed_ |= ff_viewport_.TopLeftY != viewport.TopLeftY; ff_viewport_update_needed_ |= ff_viewport_.TopLeftY != viewport.TopLeftY;
ff_viewport_update_needed_ |= ff_viewport_.Width != viewport.Width; ff_viewport_update_needed_ |= ff_viewport_.Width != viewport.Width;
@ -2847,13 +2811,11 @@ void D3D12CommandProcessor::UpdateFixedFunctionState(bool primitive_two_faced) {
} }
// Scissor. // Scissor.
draw_util::Scissor scissor;
draw_util::GetScissor(regs, scissor);
D3D12_RECT scissor_rect; D3D12_RECT scissor_rect;
scissor_rect.left = LONG(scissor.left * pixel_size_x); scissor_rect.left = LONG(scissor.left);
scissor_rect.top = LONG(scissor.top * pixel_size_y); scissor_rect.top = LONG(scissor.top);
scissor_rect.right = LONG((scissor.left + scissor.width) * pixel_size_x); scissor_rect.right = LONG(scissor.left + scissor.width);
scissor_rect.bottom = LONG((scissor.top + scissor.height) * pixel_size_y); scissor_rect.bottom = LONG(scissor.top + scissor.height);
ff_scissor_update_needed_ |= ff_scissor_.left != scissor_rect.left; ff_scissor_update_needed_ |= ff_scissor_.left != scissor_rect.left;
ff_scissor_update_needed_ |= ff_scissor_.top != scissor_rect.top; ff_scissor_update_needed_ |= ff_scissor_.top != scissor_rect.top;
ff_scissor_update_needed_ |= ff_scissor_.right != scissor_rect.right; ff_scissor_update_needed_ |= ff_scissor_.right != scissor_rect.right;
@ -2865,6 +2827,8 @@ void D3D12CommandProcessor::UpdateFixedFunctionState(bool primitive_two_faced) {
} }
if (!edram_rov_used_) { if (!edram_rov_used_) {
const RegisterFile& regs = *register_file_;
// Blend factor. // Blend factor.
ff_blend_factor_update_needed_ |= ff_blend_factor_update_needed_ |=
ff_blend_factor_[0] != regs[XE_GPU_REG_RB_BLEND_RED].f32; ff_blend_factor_[0] != regs[XE_GPU_REG_RB_BLEND_RED].f32;
@ -2887,7 +2851,7 @@ void D3D12CommandProcessor::UpdateFixedFunctionState(bool primitive_two_faced) {
// choose the back face one only if drawing only back faces. // choose the back face one only if drawing only back faces.
Register stencil_ref_mask_reg; Register stencil_ref_mask_reg;
auto pa_su_sc_mode_cntl = regs.Get<reg::PA_SU_SC_MODE_CNTL>(); auto pa_su_sc_mode_cntl = regs.Get<reg::PA_SU_SC_MODE_CNTL>();
if (primitive_two_faced && if (primitive_polygonal &&
regs.Get<reg::RB_DEPTHCONTROL>().backface_enable && regs.Get<reg::RB_DEPTHCONTROL>().backface_enable &&
pa_su_sc_mode_cntl.cull_front && !pa_su_sc_mode_cntl.cull_back) { pa_su_sc_mode_cntl.cull_front && !pa_su_sc_mode_cntl.cull_back) {
stencil_ref_mask_reg = XE_GPU_REG_RB_STENCILREFMASK_BF; stencil_ref_mask_reg = XE_GPU_REG_RB_STENCILREFMASK_BF;
@ -2906,9 +2870,11 @@ void D3D12CommandProcessor::UpdateFixedFunctionState(bool primitive_two_faced) {
} }
void D3D12CommandProcessor::UpdateSystemConstantValues( void D3D12CommandProcessor::UpdateSystemConstantValues(
bool shared_memory_is_uav, bool primitive_two_faced, bool shared_memory_is_uav, bool primitive_polygonal,
uint32_t line_loop_closing_index, xenos::Endian index_endian, uint32_t line_loop_closing_index, xenos::Endian index_endian,
uint32_t used_texture_mask, bool early_z, uint32_t color_mask, const draw_util::ViewportInfo& viewport_info, uint32_t pixel_size_x,
uint32_t pixel_size_y, uint32_t used_texture_mask, bool early_z,
uint32_t color_mask,
const RenderTargetCache::PipelineRenderTarget render_targets[4]) { const RenderTargetCache::PipelineRenderTarget render_targets[4]) {
#if XE_UI_D3D12_FINE_GRAINED_DRAW_SCOPES #if XE_UI_D3D12_FINE_GRAINED_DRAW_SCOPES
SCOPE_profile_cpu_f("gpu"); SCOPE_profile_cpu_f("gpu");
@ -2920,7 +2886,6 @@ void D3D12CommandProcessor::UpdateSystemConstantValues(
auto pa_su_point_minmax = regs.Get<reg::PA_SU_POINT_MINMAX>(); auto pa_su_point_minmax = regs.Get<reg::PA_SU_POINT_MINMAX>();
auto pa_su_point_size = regs.Get<reg::PA_SU_POINT_SIZE>(); auto pa_su_point_size = regs.Get<reg::PA_SU_POINT_SIZE>();
auto pa_su_sc_mode_cntl = regs.Get<reg::PA_SU_SC_MODE_CNTL>(); auto pa_su_sc_mode_cntl = regs.Get<reg::PA_SU_SC_MODE_CNTL>();
auto pa_su_vtx_cntl = regs.Get<reg::PA_SU_VTX_CNTL>();
float rb_alpha_ref = regs[XE_GPU_REG_RB_ALPHA_REF].f32; float rb_alpha_ref = regs[XE_GPU_REG_RB_ALPHA_REF].f32;
auto rb_colorcontrol = regs.Get<reg::RB_COLORCONTROL>(); auto rb_colorcontrol = regs.Get<reg::RB_COLORCONTROL>();
auto rb_depth_info = regs.Get<reg::RB_DEPTH_INFO>(); auto rb_depth_info = regs.Get<reg::RB_DEPTH_INFO>();
@ -2986,11 +2951,6 @@ void D3D12CommandProcessor::UpdateSystemConstantValues(
} }
} }
// Get viewport Z scale - needed for flags and ROV output.
float viewport_scale_z = pa_cl_vte_cntl.vport_z_scale_ena
? regs[XE_GPU_REG_PA_CL_VPORT_ZSCALE].f32
: 1.0f;
bool dirty = false; bool dirty = false;
// Flags. // Flags.
@ -3023,13 +2983,9 @@ void D3D12CommandProcessor::UpdateSystemConstantValues(
flags |= (pa_cl_clip_cntl.value & 0b111111) flags |= (pa_cl_clip_cntl.value & 0b111111)
<< DxbcShaderTranslator::kSysFlag_UserClipPlane0_Shift; << DxbcShaderTranslator::kSysFlag_UserClipPlane0_Shift;
} }
// Reversed depth. // Whether the primitive is polygonal and SV_IsFrontFace matters.
if (viewport_scale_z < 0.0f) { if (primitive_polygonal) {
flags |= DxbcShaderTranslator::kSysFlag_ReverseZ; flags |= DxbcShaderTranslator::kSysFlag_PrimitivePolygonal;
}
// Whether SV_IsFrontFace matters.
if (primitive_two_faced) {
flags |= DxbcShaderTranslator::kSysFlag_PrimitiveTwoFaced;
} }
// Primitive killing condition. // Primitive killing condition.
if (pa_cl_clip_cntl.vtx_kill_or) { if (pa_cl_clip_cntl.vtx_kill_or) {
@ -3122,81 +3078,24 @@ void D3D12CommandProcessor::UpdateSystemConstantValues(
} }
// Conversion to Direct3D 12 normalized device coordinates. // Conversion to Direct3D 12 normalized device coordinates.
// See viewport configuration in UpdateFixedFunctionState for explanations.
// X and Y scale/offset is to convert unnormalized coordinates generated by
// shaders (for rectangle list drawing, for instance) to the viewport of the
// largest possible render target size that is used to emulate unnormalized
// coordinates. Z scale/offset is to convert from OpenGL NDC to Direct3D NDC
// if needed. Also apply half-pixel offset to reproduce Direct3D 9
// rasterization rules - must be done before clipping, not through the
// viewport, for SSAA and resolution scale to work correctly.
float viewport_scale_x = regs[XE_GPU_REG_PA_CL_VPORT_XSCALE].f32;
float viewport_scale_y = regs[XE_GPU_REG_PA_CL_VPORT_YSCALE].f32;
// Kill all primitives if multipass or both faces are culled, but still need // Kill all primitives if multipass or both faces are culled, but still need
// to do memexport. // to do memexport.
if (sq_program_cntl.vs_export_mode == if (sq_program_cntl.vs_export_mode ==
xenos::VertexShaderExportMode::kMultipass || xenos::VertexShaderExportMode::kMultipass ||
(primitive_two_faced && pa_su_sc_mode_cntl.cull_front && (primitive_polygonal && pa_su_sc_mode_cntl.cull_front &&
pa_su_sc_mode_cntl.cull_back)) { pa_su_sc_mode_cntl.cull_back)) {
dirty |= !std::isnan(system_constants_.ndc_scale[0]);
dirty |= !std::isnan(system_constants_.ndc_scale[1]);
dirty |= !std::isnan(system_constants_.ndc_scale[2]);
dirty |= !std::isnan(system_constants_.ndc_offset[0]);
dirty |= !std::isnan(system_constants_.ndc_offset[1]);
dirty |= !std::isnan(system_constants_.ndc_offset[2]);
float nan_value = std::nanf(""); float nan_value = std::nanf("");
system_constants_.ndc_scale[0] = nan_value; for (uint32_t i = 0; i < 3; ++i) {
system_constants_.ndc_scale[1] = nan_value; dirty |= !std::isnan(system_constants_.ndc_scale[i]);
system_constants_.ndc_scale[2] = nan_value; system_constants_.ndc_scale[i] = nan_value;
system_constants_.ndc_offset[0] = nan_value; }
system_constants_.ndc_offset[1] = nan_value; } else {
system_constants_.ndc_offset[2] = nan_value; for (uint32_t i = 0; i < 3; ++i) {
} else { dirty |= system_constants_.ndc_scale[i] != viewport_info.ndc_scale[i];
// When VPORT_Z_SCALE_ENA is disabled, Z/W is directly what is expected to dirty |= system_constants_.ndc_offset[i] != viewport_info.ndc_offset[i];
// be written to the depth buffer, and for some reason DX_CLIP_SPACE_DEF system_constants_.ndc_scale[i] = viewport_info.ndc_scale[i];
// isn't set in this case in draws in games. system_constants_.ndc_offset[i] = viewport_info.ndc_offset[i];
bool gl_clip_space_def =
!pa_cl_clip_cntl.dx_clip_space_def && pa_cl_vte_cntl.vport_z_scale_ena;
float ndc_scale_x = pa_cl_vte_cntl.vport_x_scale_ena
? (viewport_scale_x >= 0.0f ? 1.0f : -1.0f)
: (1.0f / 4096.0f);
float ndc_scale_y = pa_cl_vte_cntl.vport_y_scale_ena
? (viewport_scale_y >= 0.0f ? -1.0f : 1.0f)
: (-1.0f / 4096.0f);
float ndc_scale_z = gl_clip_space_def ? 0.5f : 1.0f;
float ndc_offset_x = pa_cl_vte_cntl.vport_x_offset_ena ? 0.0f : -1.0f;
float ndc_offset_y = pa_cl_vte_cntl.vport_y_offset_ena ? 0.0f : 1.0f;
float ndc_offset_z = gl_clip_space_def ? 0.5f : 0.0f;
if (cvars::half_pixel_offset && !pa_su_vtx_cntl.pix_center) {
// Signs are hopefully correct here, tested in GTA IV on both clearing
// (without a viewport) and drawing things near the edges of the screen.
if (pa_cl_vte_cntl.vport_x_scale_ena) {
if (viewport_scale_x != 0.0f) {
ndc_offset_x += 0.5f / viewport_scale_x;
}
} else {
ndc_offset_x += 1.0f / xenos::kTexture2DCubeMaxWidthHeight;
}
if (pa_cl_vte_cntl.vport_y_scale_ena) {
if (viewport_scale_y != 0.0f) {
ndc_offset_y += 0.5f / viewport_scale_y;
}
} else {
ndc_offset_y -= 1.0f / xenos::kTexture2DCubeMaxWidthHeight;
}
} }
dirty |= system_constants_.ndc_scale[0] != ndc_scale_x;
dirty |= system_constants_.ndc_scale[1] != ndc_scale_y;
dirty |= system_constants_.ndc_scale[2] != ndc_scale_z;
dirty |= system_constants_.ndc_offset[0] != ndc_offset_x;
dirty |= system_constants_.ndc_offset[1] != ndc_offset_y;
dirty |= system_constants_.ndc_offset[2] != ndc_offset_z;
system_constants_.ndc_scale[0] = ndc_scale_x;
system_constants_.ndc_scale[1] = ndc_scale_y;
system_constants_.ndc_scale[2] = ndc_scale_z;
system_constants_.ndc_offset[0] = ndc_offset_x;
system_constants_.ndc_offset[1] = ndc_offset_y;
system_constants_.ndc_offset[2] = ndc_offset_z;
} }
// Point size. // Point size.
@ -3212,19 +3111,10 @@ void D3D12CommandProcessor::UpdateSystemConstantValues(
system_constants_.point_size[1] = point_size_y; system_constants_.point_size[1] = point_size_y;
system_constants_.point_size_min_max[0] = point_size_min; system_constants_.point_size_min_max[0] = point_size_min;
system_constants_.point_size_min_max[1] = point_size_max; system_constants_.point_size_min_max[1] = point_size_max;
float point_screen_to_ndc_x, point_screen_to_ndc_y; float point_screen_to_ndc_x =
if (pa_cl_vte_cntl.vport_x_scale_ena) { (0.5f * 2.0f * pixel_size_x) / viewport_info.width;
point_screen_to_ndc_x = float point_screen_to_ndc_y =
(viewport_scale_x != 0.0f) ? (0.5f / viewport_scale_x) : 0.0f; (0.5f * 2.0f * pixel_size_y) / viewport_info.height;
} else {
point_screen_to_ndc_x = 1.0f / xenos::kTexture2DCubeMaxWidthHeight;
}
if (pa_cl_vte_cntl.vport_y_scale_ena) {
point_screen_to_ndc_y =
(viewport_scale_y != 0.0f) ? (-0.5f / viewport_scale_y) : 0.0f;
} else {
point_screen_to_ndc_y = -1.0f / xenos::kTexture2DCubeMaxWidthHeight;
}
dirty |= system_constants_.point_screen_to_ndc[0] != point_screen_to_ndc_x; dirty |= system_constants_.point_screen_to_ndc[0] != point_screen_to_ndc_x;
dirty |= system_constants_.point_screen_to_ndc[1] != point_screen_to_ndc_y; dirty |= system_constants_.point_screen_to_ndc[1] != point_screen_to_ndc_y;
system_constants_.point_screen_to_ndc[0] = point_screen_to_ndc_x; system_constants_.point_screen_to_ndc[0] = point_screen_to_ndc_x;
@ -3374,27 +3264,18 @@ void D3D12CommandProcessor::UpdateSystemConstantValues(
dirty |= system_constants_.edram_depth_base_dwords != depth_base_dwords; dirty |= system_constants_.edram_depth_base_dwords != depth_base_dwords;
system_constants_.edram_depth_base_dwords = depth_base_dwords; system_constants_.edram_depth_base_dwords = depth_base_dwords;
// The Z range is reversed in the vertex shader if it's reverse - use the float depth_range_scale = viewport_info.z_max - viewport_info.z_min;
// absolute value of the scale.
float depth_range_scale = std::abs(viewport_scale_z);
dirty |= system_constants_.edram_depth_range_scale != depth_range_scale; dirty |= system_constants_.edram_depth_range_scale != depth_range_scale;
system_constants_.edram_depth_range_scale = depth_range_scale; system_constants_.edram_depth_range_scale = depth_range_scale;
float depth_range_offset = pa_cl_vte_cntl.vport_z_offset_ena dirty |= system_constants_.edram_depth_range_offset != viewport_info.z_min;
? regs[XE_GPU_REG_PA_CL_VPORT_ZOFFSET].f32 system_constants_.edram_depth_range_offset = viewport_info.z_min;
: 0.0f;
if (viewport_scale_z < 0.0f) {
// Similar to MinDepth in fixed-function viewport calculation.
depth_range_offset += viewport_scale_z;
}
dirty |= system_constants_.edram_depth_range_offset != depth_range_offset;
system_constants_.edram_depth_range_offset = depth_range_offset;
// For non-polygons, front polygon offset is used, and it's enabled if // For non-polygons, front polygon offset is used, and it's enabled if
// POLY_OFFSET_PARA_ENABLED is set, for polygons, separate front and back // POLY_OFFSET_PARA_ENABLED is set, for polygons, separate front and back
// are used. // are used.
float poly_offset_front_scale = 0.0f, poly_offset_front_offset = 0.0f; float poly_offset_front_scale = 0.0f, poly_offset_front_offset = 0.0f;
float poly_offset_back_scale = 0.0f, poly_offset_back_offset = 0.0f; float poly_offset_back_scale = 0.0f, poly_offset_back_offset = 0.0f;
if (primitive_two_faced) { if (primitive_polygonal) {
if (pa_su_sc_mode_cntl.poly_offset_front_enable) { if (pa_su_sc_mode_cntl.poly_offset_front_enable) {
poly_offset_front_scale = poly_offset_front_scale =
regs[XE_GPU_REG_PA_SU_POLY_OFFSET_FRONT_SCALE].f32; regs[XE_GPU_REG_PA_SU_POLY_OFFSET_FRONT_SCALE].f32;
@ -3457,7 +3338,7 @@ void D3D12CommandProcessor::UpdateSystemConstantValues(
system_constants_.edram_stencil_front_func_ops != stencil_func_ops; system_constants_.edram_stencil_front_func_ops != stencil_func_ops;
system_constants_.edram_stencil_front_func_ops = stencil_func_ops; system_constants_.edram_stencil_front_func_ops = stencil_func_ops;
if (primitive_two_faced && rb_depthcontrol.backface_enable) { if (primitive_polygonal && rb_depthcontrol.backface_enable) {
dirty |= system_constants_.edram_stencil_back_reference != dirty |= system_constants_.edram_stencil_back_reference !=
rb_stencilrefmask_bf.stencilref; rb_stencilrefmask_bf.stencilref;
system_constants_.edram_stencil_back_reference = system_constants_.edram_stencil_back_reference =

View File

@ -27,6 +27,7 @@
#include "xenia/gpu/d3d12/primitive_converter.h" #include "xenia/gpu/d3d12/primitive_converter.h"
#include "xenia/gpu/d3d12/render_target_cache.h" #include "xenia/gpu/d3d12/render_target_cache.h"
#include "xenia/gpu/d3d12/texture_cache.h" #include "xenia/gpu/d3d12/texture_cache.h"
#include "xenia/gpu/draw_util.h"
#include "xenia/gpu/dxbc_shader_translator.h" #include "xenia/gpu/dxbc_shader_translator.h"
#include "xenia/gpu/xenos.h" #include "xenia/gpu/xenos.h"
#include "xenia/kernel/kernel_state.h" #include "xenia/kernel/kernel_state.h"
@ -349,11 +350,15 @@ class D3D12CommandProcessor : public CommandProcessor {
D3D12_CPU_DESCRIPTOR_HANDLE& cpu_handle_out, D3D12_CPU_DESCRIPTOR_HANDLE& cpu_handle_out,
D3D12_GPU_DESCRIPTOR_HANDLE& gpu_handle_out); D3D12_GPU_DESCRIPTOR_HANDLE& gpu_handle_out);
void UpdateFixedFunctionState(bool primitive_two_faced); void UpdateFixedFunctionState(const draw_util::ViewportInfo& viewport_info,
const draw_util::Scissor& scissor,
bool primitive_polygonal);
void UpdateSystemConstantValues( void UpdateSystemConstantValues(
bool shared_memory_is_uav, bool primitive_two_faced, bool shared_memory_is_uav, bool primitive_polygonal,
uint32_t line_loop_closing_index, xenos::Endian index_endian, uint32_t line_loop_closing_index, xenos::Endian index_endian,
uint32_t used_texture_mask, bool early_z, uint32_t color_mask, const draw_util::ViewportInfo& viewport_info, uint32_t pixel_size_x,
uint32_t pixel_size_y, uint32_t used_texture_mask, bool early_z,
uint32_t color_mask,
const RenderTargetCache::PipelineRenderTarget render_targets[4]); const RenderTargetCache::PipelineRenderTarget render_targets[4]);
bool UpdateBindings(const D3D12Shader* vertex_shader, bool UpdateBindings(const D3D12Shader* vertex_shader,
const D3D12Shader* pixel_shader, const D3D12Shader* pixel_shader,

View File

@ -1285,7 +1285,7 @@ bool PipelineCache::GetCurrentStateDescription(
uint32_t(regs.Get<reg::VGT_HOS_CNTL>().tess_mode); uint32_t(regs.Get<reg::VGT_HOS_CNTL>().tess_mode);
} }
bool primitive_two_faced = xenos::IsPrimitiveTwoFaced( bool primitive_polygonal = xenos::IsPrimitivePolygonal(
host_vertex_shader_type != Shader::HostVertexShaderType::kVertex, host_vertex_shader_type != Shader::HostVertexShaderType::kVertex,
primitive_type); primitive_type);
@ -1305,16 +1305,11 @@ bool PipelineCache::GetCurrentStateDescription(
// Here we also assume that only one side is culled - if two sides are culled, // Here we also assume that only one side is culled - if two sides are culled,
// the D3D12 command processor will drop such draw early. // the D3D12 command processor will drop such draw early.
bool cull_front, cull_back; bool cull_front, cull_back;
if (primitive_two_faced) { float poly_offset = 0.0f, poly_offset_scale = 0.0f;
if (primitive_polygonal) {
description_out.front_counter_clockwise = pa_su_sc_mode_cntl.face == 0;
cull_front = pa_su_sc_mode_cntl.cull_front != 0; cull_front = pa_su_sc_mode_cntl.cull_front != 0;
cull_back = pa_su_sc_mode_cntl.cull_back != 0; cull_back = pa_su_sc_mode_cntl.cull_back != 0;
} else {
cull_front = false;
cull_back = false;
}
float poly_offset = 0.0f, poly_offset_scale = 0.0f;
if (primitive_two_faced) {
description_out.front_counter_clockwise = pa_su_sc_mode_cntl.face == 0;
if (cull_front) { if (cull_front) {
description_out.cull_mode = PipelineCullMode::kFront; description_out.cull_mode = PipelineCullMode::kFront;
} else if (cull_back) { } else if (cull_back) {
@ -1354,9 +1349,9 @@ bool PipelineCache::GetCurrentStateDescription(
description_out.fill_mode_wireframe = 0; description_out.fill_mode_wireframe = 0;
} }
} else { } else {
// Filled front faces only. // Filled front faces only, without culling.
// Use front depth bias if POLY_OFFSET_PARA_ENABLED cull_front = false;
// (POLY_OFFSET_FRONT_ENABLED is for two-sided primitives). cull_back = false;
if (!edram_rov_used_ && pa_su_sc_mode_cntl.poly_offset_para_enable) { if (!edram_rov_used_ && pa_su_sc_mode_cntl.poly_offset_para_enable) {
poly_offset = regs[XE_GPU_REG_PA_SU_POLY_OFFSET_FRONT_OFFSET].f32; poly_offset = regs[XE_GPU_REG_PA_SU_POLY_OFFSET_FRONT_OFFSET].f32;
poly_offset_scale = regs[XE_GPU_REG_PA_SU_POLY_OFFSET_FRONT_SCALE].f32; poly_offset_scale = regs[XE_GPU_REG_PA_SU_POLY_OFFSET_FRONT_SCALE].f32;
@ -1413,7 +1408,7 @@ bool PipelineCache::GetCurrentStateDescription(
if (rb_depthcontrol.stencil_enable) { if (rb_depthcontrol.stencil_enable) {
description_out.stencil_enable = 1; description_out.stencil_enable = 1;
bool stencil_backface_enable = bool stencil_backface_enable =
primitive_two_faced && rb_depthcontrol.backface_enable; primitive_polygonal && rb_depthcontrol.backface_enable;
// Per-face masks not supported by Direct3D 12, choose the back face // Per-face masks not supported by Direct3D 12, choose the back face
// ones only if drawing only back faces. // ones only if drawing only back faces.
Register stencil_ref_mask_reg; Register stencil_ref_mask_reg;

View File

@ -1,11 +1,11 @@
// generated from `xb buildhlsl` // generated from `xb buildhlsl`
// source: primitive_point_list.gs.hlsl // source: primitive_point_list.gs.hlsl
const uint8_t primitive_point_list_gs[] = { const uint8_t primitive_point_list_gs[] = {
0x44, 0x58, 0x42, 0x43, 0x6F, 0x7A, 0xE0, 0xA0, 0x82, 0xF0, 0x8E, 0x77, 0x44, 0x58, 0x42, 0x43, 0x16, 0x84, 0x10, 0x1C, 0xE9, 0xAD, 0x76, 0xF9,
0x2B, 0x62, 0x44, 0x00, 0xA3, 0x34, 0x47, 0x40, 0x01, 0x00, 0x00, 0x00, 0x92, 0xF2, 0xD5, 0x65, 0x7C, 0x8A, 0x5F, 0xC5, 0x01, 0x00, 0x00, 0x00,
0x0C, 0x1E, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0x20, 0x1E, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00,
0xD0, 0x0A, 0x00, 0x00, 0x28, 0x0D, 0x00, 0x00, 0xAC, 0x0F, 0x00, 0x00, 0xD0, 0x0A, 0x00, 0x00, 0x28, 0x0D, 0x00, 0x00, 0xAC, 0x0F, 0x00, 0x00,
0x70, 0x1D, 0x00, 0x00, 0x52, 0x44, 0x45, 0x46, 0x94, 0x0A, 0x00, 0x00, 0x84, 0x1D, 0x00, 0x00, 0x52, 0x44, 0x45, 0x46, 0x94, 0x0A, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x3C, 0x00, 0x00, 0x00, 0x01, 0x05, 0x53, 0x47, 0x00, 0x05, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x01, 0x05, 0x53, 0x47, 0x00, 0x05, 0x00, 0x00,
0x6A, 0x0A, 0x00, 0x00, 0x13, 0x13, 0x44, 0x25, 0x3C, 0x00, 0x00, 0x00, 0x6A, 0x0A, 0x00, 0x00, 0x13, 0x13, 0x44, 0x25, 0x3C, 0x00, 0x00, 0x00,
@ -335,8 +335,8 @@ const uint8_t primitive_point_list_gs[] = {
0x54, 0x45, 0x58, 0x43, 0x4F, 0x4F, 0x52, 0x44, 0x00, 0x53, 0x56, 0x5F, 0x54, 0x45, 0x58, 0x43, 0x4F, 0x4F, 0x52, 0x44, 0x00, 0x53, 0x56, 0x5F,
0x50, 0x6F, 0x73, 0x69, 0x74, 0x69, 0x6F, 0x6E, 0x00, 0x53, 0x56, 0x5F, 0x50, 0x6F, 0x73, 0x69, 0x74, 0x69, 0x6F, 0x6E, 0x00, 0x53, 0x56, 0x5F,
0x43, 0x6C, 0x69, 0x70, 0x44, 0x69, 0x73, 0x74, 0x61, 0x6E, 0x63, 0x65, 0x43, 0x6C, 0x69, 0x70, 0x44, 0x69, 0x73, 0x74, 0x61, 0x6E, 0x63, 0x65,
0x00, 0xAB, 0xAB, 0xAB, 0x53, 0x48, 0x45, 0x58, 0xBC, 0x0D, 0x00, 0x00, 0x00, 0xAB, 0xAB, 0xAB, 0x53, 0x48, 0x45, 0x58, 0xD0, 0x0D, 0x00, 0x00,
0x51, 0x00, 0x02, 0x00, 0x6F, 0x03, 0x00, 0x00, 0x6A, 0x08, 0x00, 0x01, 0x51, 0x00, 0x02, 0x00, 0x74, 0x03, 0x00, 0x00, 0x6A, 0x08, 0x00, 0x01,
0x59, 0x00, 0x00, 0x07, 0x46, 0x8E, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x00, 0x00, 0x07, 0x46, 0x8E, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x5F, 0x00, 0x00, 0x04, 0xF2, 0x10, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5F, 0x00, 0x00, 0x04, 0xF2, 0x10, 0x20, 0x00,
@ -369,7 +369,7 @@ const uint8_t primitive_point_list_gs[] = {
0x13, 0x00, 0x00, 0x00, 0x5F, 0x00, 0x00, 0x04, 0x32, 0x10, 0x20, 0x00, 0x13, 0x00, 0x00, 0x00, 0x5F, 0x00, 0x00, 0x04, 0x32, 0x10, 0x20, 0x00,
0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x5F, 0x00, 0x00, 0x04, 0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x5F, 0x00, 0x00, 0x04,
0x42, 0x10, 0x20, 0x00, 0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x42, 0x10, 0x20, 0x00, 0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
0x68, 0x00, 0x00, 0x02, 0x02, 0x00, 0x00, 0x00, 0x5D, 0x08, 0x00, 0x01, 0x68, 0x00, 0x00, 0x02, 0x03, 0x00, 0x00, 0x00, 0x5D, 0x08, 0x00, 0x01,
0x8F, 0x00, 0x00, 0x03, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8F, 0x00, 0x00, 0x03, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00,
0x5C, 0x28, 0x00, 0x01, 0x65, 0x00, 0x00, 0x03, 0xF2, 0x20, 0x10, 0x00, 0x5C, 0x28, 0x00, 0x01, 0x65, 0x00, 0x00, 0x03, 0xF2, 0x20, 0x10, 0x00,
0x00, 0x00, 0x00, 0x00, 0x65, 0x00, 0x00, 0x03, 0xF2, 0x20, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x65, 0x00, 0x00, 0x03, 0xF2, 0x20, 0x10, 0x00,
@ -426,113 +426,13 @@ const uint8_t primitive_point_list_gs[] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
0x38, 0x00, 0x18, 0x08, 0x32, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x18, 0x08, 0x32, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00,
0x46, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF6, 0x1F, 0x20, 0x00, 0x46, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF6, 0x1F, 0x20, 0x00,
0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x38, 0x00, 0x78, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x36, 0x00, 0x38, 0x06,
0xF2, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x46, 0x04, 0x10, 0x00, 0x72, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x06, 0x01, 0x10, 0x80,
0x00, 0x00, 0x00, 0x00, 0x02, 0x40, 0x00, 0x00, 0x00, 0x00, 0x80, 0xBF, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x00, 0x40, 0x05,
0x00, 0x00, 0x80, 0x3F, 0x00, 0x00, 0x80, 0x3F, 0x00, 0x00, 0x80, 0xBF, 0x82, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1A, 0x00, 0x10, 0x00,
0x00, 0x00, 0x78, 0x08, 0xF2, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x08, 0xF2, 0x00, 0x10, 0x00,
0x46, 0x0E, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x46, 0x14, 0x20, 0x00, 0x02, 0x00, 0x00, 0x00, 0xC6, 0x09, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06, 0x46, 0x14, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00,
0xF2, 0x20, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06,
0xF2, 0x20, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00,
0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06,
0xF2, 0x20, 0x10, 0x00, 0x02, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00,
0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06,
0xF2, 0x20, 0x10, 0x00, 0x03, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00,
0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06,
0xF2, 0x20, 0x10, 0x00, 0x04, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00,
0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06,
0xF2, 0x20, 0x10, 0x00, 0x05, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00,
0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06,
0xF2, 0x20, 0x10, 0x00, 0x06, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00,
0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06,
0xF2, 0x20, 0x10, 0x00, 0x07, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00,
0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06,
0xF2, 0x20, 0x10, 0x00, 0x08, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00,
0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06,
0xF2, 0x20, 0x10, 0x00, 0x09, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00,
0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06,
0xF2, 0x20, 0x10, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00,
0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06,
0xF2, 0x20, 0x10, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00,
0x00, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06,
0xF2, 0x20, 0x10, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00,
0x00, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06,
0xF2, 0x20, 0x10, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00,
0x00, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06,
0xF2, 0x20, 0x10, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00,
0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06,
0xF2, 0x20, 0x10, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00,
0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x08,
0x32, 0x20, 0x10, 0x00, 0x10, 0x00, 0x00, 0x00, 0x02, 0x40, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3F, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06, 0x42, 0x20, 0x10, 0x00,
0x10, 0x00, 0x00, 0x00, 0x2A, 0x10, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
0x10, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06, 0x32, 0x20, 0x10, 0x00,
0x11, 0x00, 0x00, 0x00, 0x46, 0x10, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
0x11, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x05, 0x32, 0x20, 0x10, 0x00,
0x12, 0x00, 0x00, 0x00, 0x46, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00,
0x36, 0x00, 0x00, 0x06, 0xC2, 0x20, 0x10, 0x00, 0x12, 0x00, 0x00, 0x00,
0xA6, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00,
0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00, 0x13, 0x00, 0x00, 0x00,
0x46, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00,
0x36, 0x00, 0x00, 0x06, 0x32, 0x20, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00,
0x46, 0x10, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
0x75, 0x00, 0x00, 0x03, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x60, 0x08, 0xC2, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00,
0x06, 0x04, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x14, 0x20, 0x00,
0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06,
0xF2, 0x20, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06,
0xF2, 0x20, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00,
0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06,
0xF2, 0x20, 0x10, 0x00, 0x02, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00,
0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06,
0xF2, 0x20, 0x10, 0x00, 0x03, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00,
0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06,
0xF2, 0x20, 0x10, 0x00, 0x04, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00,
0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06,
0xF2, 0x20, 0x10, 0x00, 0x05, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00,
0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06,
0xF2, 0x20, 0x10, 0x00, 0x06, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00,
0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06,
0xF2, 0x20, 0x10, 0x00, 0x07, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00,
0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06,
0xF2, 0x20, 0x10, 0x00, 0x08, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00,
0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06,
0xF2, 0x20, 0x10, 0x00, 0x09, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00,
0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06,
0xF2, 0x20, 0x10, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00,
0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06,
0xF2, 0x20, 0x10, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00,
0x00, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06,
0xF2, 0x20, 0x10, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00,
0x00, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06,
0xF2, 0x20, 0x10, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00,
0x00, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06,
0xF2, 0x20, 0x10, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00,
0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06,
0xF2, 0x20, 0x10, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00,
0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x08,
0x32, 0x20, 0x10, 0x00, 0x10, 0x00, 0x00, 0x00, 0x02, 0x40, 0x00, 0x00,
0x00, 0x00, 0x80, 0x3F, 0x00, 0x00, 0x80, 0x3F, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06, 0x42, 0x20, 0x10, 0x00,
0x10, 0x00, 0x00, 0x00, 0x2A, 0x10, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
0x10, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06, 0x32, 0x20, 0x10, 0x00,
0x11, 0x00, 0x00, 0x00, 0x46, 0x10, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
0x11, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x05, 0x32, 0x20, 0x10, 0x00,
0x12, 0x00, 0x00, 0x00, 0xE6, 0x0A, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00,
0x36, 0x00, 0x00, 0x06, 0xC2, 0x20, 0x10, 0x00, 0x12, 0x00, 0x00, 0x00,
0xA6, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00,
0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00, 0x13, 0x00, 0x00, 0x00,
0x46, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00,
0x36, 0x00, 0x00, 0x06, 0x32, 0x20, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00,
0x46, 0x10, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
0x75, 0x00, 0x00, 0x03, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x18, 0x09, 0x32, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00,
0x46, 0x00, 0x10, 0x80, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x46, 0x10, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00,
0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00,
0x46, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00,
@ -573,7 +473,7 @@ const uint8_t primitive_point_list_gs[] = {
0x32, 0x20, 0x10, 0x00, 0x11, 0x00, 0x00, 0x00, 0x46, 0x10, 0x20, 0x00, 0x32, 0x20, 0x10, 0x00, 0x11, 0x00, 0x00, 0x00, 0x46, 0x10, 0x20, 0x00,
0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x05,
0x32, 0x20, 0x10, 0x00, 0x12, 0x00, 0x00, 0x00, 0x46, 0x00, 0x10, 0x00, 0x32, 0x20, 0x10, 0x00, 0x12, 0x00, 0x00, 0x00, 0x46, 0x00, 0x10, 0x00,
0x00, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06, 0xC2, 0x20, 0x10, 0x00, 0x02, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06, 0xC2, 0x20, 0x10, 0x00,
0x12, 0x00, 0x00, 0x00, 0xA6, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0xA6, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
0x12, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00, 0x12, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00,
0x13, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
@ -613,6 +513,56 @@ const uint8_t primitive_point_list_gs[] = {
0x0E, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00,
0x0F, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
0x0F, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x08, 0x32, 0x20, 0x10, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x08, 0x32, 0x20, 0x10, 0x00,
0x10, 0x00, 0x00, 0x00, 0x02, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x80, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x36, 0x00, 0x00, 0x06, 0x42, 0x20, 0x10, 0x00, 0x10, 0x00, 0x00, 0x00,
0x2A, 0x10, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
0x36, 0x00, 0x00, 0x06, 0x32, 0x20, 0x10, 0x00, 0x11, 0x00, 0x00, 0x00,
0x46, 0x10, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00,
0x36, 0x00, 0x00, 0x05, 0x32, 0x20, 0x10, 0x00, 0x12, 0x00, 0x00, 0x00,
0xE6, 0x0A, 0x10, 0x00, 0x02, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06,
0xC2, 0x20, 0x10, 0x00, 0x12, 0x00, 0x00, 0x00, 0xA6, 0x1E, 0x20, 0x00,
0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06,
0xF2, 0x20, 0x10, 0x00, 0x13, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00,
0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06,
0x32, 0x20, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00, 0x46, 0x10, 0x20, 0x00,
0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x75, 0x00, 0x00, 0x03,
0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x08,
0xA2, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x04, 0x10, 0x00,
0x00, 0x00, 0x00, 0x00, 0x06, 0x14, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
0x12, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00,
0x00, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00,
0x01, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00,
0x02, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
0x02, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00,
0x03, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
0x03, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00,
0x04, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00,
0x05, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
0x05, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00,
0x06, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
0x06, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00,
0x07, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
0x07, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00,
0x08, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
0x08, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00,
0x09, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
0x09, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00,
0x0A, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
0x0A, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00,
0x0B, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
0x0B, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00,
0x0C, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
0x0C, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00,
0x0D, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
0x0D, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00,
0x0E, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
0x0E, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00,
0x0F, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
0x0F, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x08, 0x32, 0x20, 0x10, 0x00,
0x10, 0x00, 0x00, 0x00, 0x02, 0x40, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3F, 0x10, 0x00, 0x00, 0x00, 0x02, 0x40, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3F,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x36, 0x00, 0x00, 0x06, 0x42, 0x20, 0x10, 0x00, 0x10, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06, 0x42, 0x20, 0x10, 0x00, 0x10, 0x00, 0x00, 0x00,
@ -620,26 +570,78 @@ const uint8_t primitive_point_list_gs[] = {
0x36, 0x00, 0x00, 0x06, 0x32, 0x20, 0x10, 0x00, 0x11, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06, 0x32, 0x20, 0x10, 0x00, 0x11, 0x00, 0x00, 0x00,
0x46, 0x10, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x46, 0x10, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00,
0x36, 0x00, 0x00, 0x05, 0x32, 0x20, 0x10, 0x00, 0x12, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x05, 0x32, 0x20, 0x10, 0x00, 0x12, 0x00, 0x00, 0x00,
0xE6, 0x0A, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06, 0xD6, 0x05, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06,
0xC2, 0x20, 0x10, 0x00, 0x12, 0x00, 0x00, 0x00, 0xA6, 0x1E, 0x20, 0x00, 0xC2, 0x20, 0x10, 0x00, 0x12, 0x00, 0x00, 0x00, 0xA6, 0x1E, 0x20, 0x00,
0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06,
0xF2, 0x20, 0x10, 0x00, 0x13, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00, 0xF2, 0x20, 0x10, 0x00, 0x13, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00,
0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06,
0x32, 0x20, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00, 0x46, 0x10, 0x20, 0x00, 0x32, 0x20, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00, 0x46, 0x10, 0x20, 0x00,
0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x75, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x75, 0x00, 0x00, 0x03,
0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0x00, 0x00, 0x03, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x00, 0x20, 0x05,
0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x01, 0x42, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0x00, 0x10, 0x00,
0x53, 0x54, 0x41, 0x54, 0x94, 0x00, 0x00, 0x00, 0x74, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x08, 0x32, 0x00, 0x10, 0x00,
0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x86, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00,
0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x46, 0x10, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00,
0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00,
0x46, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00, 0x02, 0x00, 0x00, 0x00,
0x46, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00, 0x03, 0x00, 0x00, 0x00,
0x46, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00, 0x04, 0x00, 0x00, 0x00,
0x46, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00, 0x05, 0x00, 0x00, 0x00,
0x46, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00, 0x06, 0x00, 0x00, 0x00,
0x46, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00, 0x07, 0x00, 0x00, 0x00,
0x46, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00, 0x08, 0x00, 0x00, 0x00,
0x46, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00, 0x09, 0x00, 0x00, 0x00,
0x46, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00, 0x0A, 0x00, 0x00, 0x00,
0x46, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00,
0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00, 0x0B, 0x00, 0x00, 0x00,
0x46, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00,
0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00, 0x0C, 0x00, 0x00, 0x00,
0x46, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00,
0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00, 0x0D, 0x00, 0x00, 0x00,
0x46, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00,
0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00, 0x0E, 0x00, 0x00, 0x00,
0x46, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00,
0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00, 0x0F, 0x00, 0x00, 0x00,
0x46, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00,
0x36, 0x00, 0x00, 0x08, 0x32, 0x20, 0x10, 0x00, 0x10, 0x00, 0x00, 0x00,
0x02, 0x40, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3F, 0x00, 0x00, 0x80, 0x3F,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06,
0x42, 0x20, 0x10, 0x00, 0x10, 0x00, 0x00, 0x00, 0x2A, 0x10, 0x20, 0x00,
0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06,
0x32, 0x20, 0x10, 0x00, 0x11, 0x00, 0x00, 0x00, 0x46, 0x10, 0x20, 0x00,
0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x05,
0x32, 0x20, 0x10, 0x00, 0x12, 0x00, 0x00, 0x00, 0x46, 0x00, 0x10, 0x00,
0x00, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06, 0xC2, 0x20, 0x10, 0x00,
0x12, 0x00, 0x00, 0x00, 0xA6, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
0x12, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00,
0x13, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
0x13, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06, 0x32, 0x20, 0x10, 0x00,
0x14, 0x00, 0x00, 0x00, 0x46, 0x10, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
0x14, 0x00, 0x00, 0x00, 0x75, 0x00, 0x00, 0x03, 0x00, 0x00, 0x11, 0x00,
0x00, 0x00, 0x00, 0x00, 0x76, 0x00, 0x00, 0x03, 0x00, 0x00, 0x11, 0x00,
0x00, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x01, 0x53, 0x54, 0x41, 0x54,
0x94, 0x00, 0x00, 0x00, 0x76, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
}; };

View File

@ -130,7 +130,7 @@ dcl_input_siv v[1][18].xyzw, position
dcl_input v[1][19].xyzw dcl_input v[1][19].xyzw
dcl_input v[1][20].xy dcl_input v[1][20].xy
dcl_input v[1][20].z dcl_input v[1][20].z
dcl_temps 2 dcl_temps 3
dcl_inputprimitive point dcl_inputprimitive point
dcl_stream m0 dcl_stream m0
dcl_outputtopology trianglestrip dcl_outputtopology trianglestrip
@ -170,58 +170,9 @@ max [precise(xy)] r0.xy, r0.xyxx, CB0[0][2].xxxx
min [precise(xy)] r0.xy, r0.xyxx, CB0[0][2].yyyy min [precise(xy)] r0.xy, r0.xyxx, CB0[0][2].yyyy
mul [precise(xy)] r0.xy, r0.xyxx, CB0[0][2].zwzz mul [precise(xy)] r0.xy, r0.xyxx, CB0[0][2].zwzz
mul [precise(xy)] r0.xy, r0.xyxx, v[0][18].wwww mul [precise(xy)] r0.xy, r0.xyxx, v[0][18].wwww
mul [precise] r1.xyzw, r0.xyxy, l(-1.000000, 1.000000, 1.000000, -1.000000) mov [precise(xyz)] r1.xyz, -r0.xxyx
add [precise] r1.xyzw, r1.xyzw, v[0][18].xyxy mov [precise(w)] r1.w, r0.y
mov o0.xyzw, v[0][0].xyzw add [precise] r2.xyzw, r1.xwyz, v[0][18].xyxy
mov o1.xyzw, v[0][1].xyzw
mov o2.xyzw, v[0][2].xyzw
mov o3.xyzw, v[0][3].xyzw
mov o4.xyzw, v[0][4].xyzw
mov o5.xyzw, v[0][5].xyzw
mov o6.xyzw, v[0][6].xyzw
mov o7.xyzw, v[0][7].xyzw
mov o8.xyzw, v[0][8].xyzw
mov o9.xyzw, v[0][9].xyzw
mov o10.xyzw, v[0][10].xyzw
mov o11.xyzw, v[0][11].xyzw
mov o12.xyzw, v[0][12].xyzw
mov o13.xyzw, v[0][13].xyzw
mov o14.xyzw, v[0][14].xyzw
mov o15.xyzw, v[0][15].xyzw
mov o16.xy, l(0,1.000000,0,0)
mov o16.z, v[0][16].z
mov o17.xy, v[0][17].xyxx
mov o18.xy, r1.xyxx
mov o18.zw, v[0][18].zzzw
mov o19.xyzw, v[0][19].xyzw
mov o20.xy, v[0][20].xyxx
emit_stream m0
add [precise(zw)] r0.zw, r0.xxxy, v[0][18].xxxy
mov o0.xyzw, v[0][0].xyzw
mov o1.xyzw, v[0][1].xyzw
mov o2.xyzw, v[0][2].xyzw
mov o3.xyzw, v[0][3].xyzw
mov o4.xyzw, v[0][4].xyzw
mov o5.xyzw, v[0][5].xyzw
mov o6.xyzw, v[0][6].xyzw
mov o7.xyzw, v[0][7].xyzw
mov o8.xyzw, v[0][8].xyzw
mov o9.xyzw, v[0][9].xyzw
mov o10.xyzw, v[0][10].xyzw
mov o11.xyzw, v[0][11].xyzw
mov o12.xyzw, v[0][12].xyzw
mov o13.xyzw, v[0][13].xyzw
mov o14.xyzw, v[0][14].xyzw
mov o15.xyzw, v[0][15].xyzw
mov o16.xy, l(1.000000,1.000000,0,0)
mov o16.z, v[0][16].z
mov o17.xy, v[0][17].xyxx
mov o18.xy, r0.zwzz
mov o18.zw, v[0][18].zzzw
mov o19.xyzw, v[0][19].xyzw
mov o20.xy, v[0][20].xyxx
emit_stream m0
add [precise(xy)] r0.xy, -r0.xyxx, v[0][18].xyxx
mov o0.xyzw, v[0][0].xyzw mov o0.xyzw, v[0][0].xyzw
mov o1.xyzw, v[0][1].xyzw mov o1.xyzw, v[0][1].xyzw
mov o2.xyzw, v[0][2].xyzw mov o2.xyzw, v[0][2].xyzw
@ -241,7 +192,7 @@ mov o15.xyzw, v[0][15].xyzw
mov o16.xy, l(0,0,0,0) mov o16.xy, l(0,0,0,0)
mov o16.z, v[0][16].z mov o16.z, v[0][16].z
mov o17.xy, v[0][17].xyxx mov o17.xy, v[0][17].xyxx
mov o18.xy, r0.xyxx mov o18.xy, r2.xyxx
mov o18.zw, v[0][18].zzzw mov o18.zw, v[0][18].zzzw
mov o19.xyzw, v[0][19].xyzw mov o19.xyzw, v[0][19].xyzw
mov o20.xy, v[0][20].xyxx mov o20.xy, v[0][20].xyxx
@ -262,14 +213,65 @@ mov o12.xyzw, v[0][12].xyzw
mov o13.xyzw, v[0][13].xyzw mov o13.xyzw, v[0][13].xyzw
mov o14.xyzw, v[0][14].xyzw mov o14.xyzw, v[0][14].xyzw
mov o15.xyzw, v[0][15].xyzw mov o15.xyzw, v[0][15].xyzw
mov o16.xy, l(0,1.000000,0,0)
mov o16.z, v[0][16].z
mov o17.xy, v[0][17].xyxx
mov o18.xy, r2.zwzz
mov o18.zw, v[0][18].zzzw
mov o19.xyzw, v[0][19].xyzw
mov o20.xy, v[0][20].xyxx
emit_stream m0
add [precise(yw)] r0.yw, r0.xxxy, v[0][18].xxxy
mov o0.xyzw, v[0][0].xyzw
mov o1.xyzw, v[0][1].xyzw
mov o2.xyzw, v[0][2].xyzw
mov o3.xyzw, v[0][3].xyzw
mov o4.xyzw, v[0][4].xyzw
mov o5.xyzw, v[0][5].xyzw
mov o6.xyzw, v[0][6].xyzw
mov o7.xyzw, v[0][7].xyzw
mov o8.xyzw, v[0][8].xyzw
mov o9.xyzw, v[0][9].xyzw
mov o10.xyzw, v[0][10].xyzw
mov o11.xyzw, v[0][11].xyzw
mov o12.xyzw, v[0][12].xyzw
mov o13.xyzw, v[0][13].xyzw
mov o14.xyzw, v[0][14].xyzw
mov o15.xyzw, v[0][15].xyzw
mov o16.xy, l(1.000000,0,0,0) mov o16.xy, l(1.000000,0,0,0)
mov o16.z, v[0][16].z mov o16.z, v[0][16].z
mov o17.xy, v[0][17].xyxx mov o17.xy, v[0][17].xyxx
mov o18.xy, r1.zwzz mov o18.xy, r0.ywyy
mov o18.zw, v[0][18].zzzw
mov o19.xyzw, v[0][19].xyzw
mov o20.xy, v[0][20].xyxx
emit_stream m0
mov [precise(z)] r0.z, r1.z
add [precise(xy)] r0.xy, r0.xzxx, v[0][18].xyxx
mov o0.xyzw, v[0][0].xyzw
mov o1.xyzw, v[0][1].xyzw
mov o2.xyzw, v[0][2].xyzw
mov o3.xyzw, v[0][3].xyzw
mov o4.xyzw, v[0][4].xyzw
mov o5.xyzw, v[0][5].xyzw
mov o6.xyzw, v[0][6].xyzw
mov o7.xyzw, v[0][7].xyzw
mov o8.xyzw, v[0][8].xyzw
mov o9.xyzw, v[0][9].xyzw
mov o10.xyzw, v[0][10].xyzw
mov o11.xyzw, v[0][11].xyzw
mov o12.xyzw, v[0][12].xyzw
mov o13.xyzw, v[0][13].xyzw
mov o14.xyzw, v[0][14].xyzw
mov o15.xyzw, v[0][15].xyzw
mov o16.xy, l(1.000000,1.000000,0,0)
mov o16.z, v[0][16].z
mov o17.xy, v[0][17].xyxx
mov o18.xy, r0.xyxx
mov o18.zw, v[0][18].zzzw mov o18.zw, v[0][18].zzzw
mov o19.xyzw, v[0][19].xyzw mov o19.xyzw, v[0][19].xyzw
mov o20.xy, v[0][20].xyxx mov o20.xy, v[0][20].xyxx
emit_stream m0 emit_stream m0
cut_stream m0 cut_stream m0
ret ret
// Approximately 116 instruction slots used // Approximately 118 instruction slots used

View File

@ -26,19 +26,22 @@ void main(point XeVertexPreGS xe_in[1],
clamp(point_size, xe_point_size_min_max.xx, xe_point_size_min_max.yy) * clamp(point_size, xe_point_size_min_max.xx, xe_point_size_min_max.yy) *
xe_point_screen_to_ndc * xe_in[0].post_gs.position.w; xe_point_screen_to_ndc * xe_in[0].post_gs.position.w;
xe_out.point_params.xy = float2(0.0, 1.0);
xe_out.position.xy =
xe_in[0].post_gs.position.xy + float2(-1.0, 1.0) * point_size;
xe_stream.Append(xe_out);
xe_out.point_params.xy = float2(1.0, 1.0);
xe_out.position.xy = xe_in[0].post_gs.position.xy + point_size;
xe_stream.Append(xe_out);
xe_out.point_params.xy = float2(0.0, 0.0); xe_out.point_params.xy = float2(0.0, 0.0);
// TODO(Triang3l): On Vulkan, sign of Y needs to inverted because of
// upper-left origin.
// TODO(Triang3l): Investigate the true signs of point sprites.
xe_out.position.xy =
xe_in[0].post_gs.position.xy + float2(-point_size.x, point_size.y);
xe_stream.Append(xe_out);
xe_out.point_params.xy = float2(0.0, 1.0);
xe_out.position.xy = xe_in[0].post_gs.position.xy - point_size; xe_out.position.xy = xe_in[0].post_gs.position.xy - point_size;
xe_stream.Append(xe_out); xe_stream.Append(xe_out);
xe_out.point_params.xy = float2(1.0, 0.0); xe_out.point_params.xy = float2(1.0, 0.0);
xe_out.position.xy = xe_in[0].post_gs.position.xy + point_size;
xe_stream.Append(xe_out);
xe_out.point_params.xy = float2(1.0, 1.0);
xe_out.position.xy = xe_out.position.xy =
xe_in[0].post_gs.position.xy + float2(1.0, -1.0) * point_size; xe_in[0].post_gs.position.xy + float2(point_size.x, -point_size.y);
xe_stream.Append(xe_out); xe_stream.Append(xe_out);
xe_stream.RestartStrip(); xe_stream.RestartStrip();
} }

View File

@ -111,6 +111,179 @@ int32_t FloatToD3D11Fixed16p8(float f32) {
return result.s; return result.s;
} }
void GetHostViewportInfo(const RegisterFile& regs, float pixel_size_x,
float pixel_size_y, bool origin_bottom_left,
float x_max, float y_max, bool allow_reverse_z,
ViewportInfo& viewport_info_out) {
assert_true(pixel_size_x >= 1.0f);
assert_true(pixel_size_y >= 1.0f);
assert_true(x_max >= 1.0f);
assert_true(y_max >= 1.0f);
// PA_CL_VTE_CNTL contains whether offsets and scales are enabled.
// http://www.x.org/docs/AMD/old/evergreen_3D_registers_v2.pdf
// In games, either all are enabled (for regular drawing) or none are (for
// rectangle lists usually).
//
// If scale/offset is enabled, the Xenos shader is writing (neglecting W
// division) position in the NDC (-1, -1, dx_clip_space_def - 1) -> (1, 1, 1)
// box. If it's not, the position is in screen space. Since we can only use
// the NDC in PC APIs, we use a viewport of the largest possible size, and
// divide the position by it in translated shaders.
auto pa_cl_clip_cntl = regs.Get<reg::PA_CL_CLIP_CNTL>();
auto pa_cl_vte_cntl = regs.Get<reg::PA_CL_VTE_CNTL>();
auto pa_su_sc_mode_cntl = regs.Get<reg::PA_SU_SC_MODE_CNTL>();
auto pa_su_vtx_cntl = regs.Get<reg::PA_SU_VTX_CNTL>();
float viewport_left, viewport_top;
float viewport_width, viewport_height;
float ndc_scale_x, ndc_scale_y;
float ndc_offset_x, ndc_offset_y;
// To avoid zero size viewports, which would harm division and aren't allowed
// on Vulkan. Nothing will ever be covered by a viewport of this size - this
// is 2 orders of magnitude smaller than a .8 subpixel, and thus shouldn't
// have any effect on rounding, n and n + 1 / 1024 would be rounded to the
// same .8 fixed-point value, thus in fixed-point, the viewport would have
// zero size.
const float size_min = 1.0f / 1024.0f;
float viewport_offset_x = pa_cl_vte_cntl.vport_x_offset_ena
? regs[XE_GPU_REG_PA_CL_VPORT_XOFFSET].f32
: 0.0f;
float viewport_offset_y = pa_cl_vte_cntl.vport_y_offset_ena
? regs[XE_GPU_REG_PA_CL_VPORT_YOFFSET].f32
: 0.0f;
if (pa_su_sc_mode_cntl.vtx_window_offset_enable) {
auto pa_sc_window_offset = regs.Get<reg::PA_SC_WINDOW_OFFSET>();
viewport_offset_x += float(pa_sc_window_offset.window_x_offset);
viewport_offset_y += float(pa_sc_window_offset.window_y_offset);
}
if (pa_cl_vte_cntl.vport_x_scale_ena) {
float pa_cl_vport_xscale = regs[XE_GPU_REG_PA_CL_VPORT_XSCALE].f32;
float viewport_scale_x_abs = std::abs(pa_cl_vport_xscale) * pixel_size_x;
viewport_left = viewport_offset_x * pixel_size_x - viewport_scale_x_abs;
float viewport_right = viewport_left + viewport_scale_x_abs * 2.0f;
// Keep the viewport in the positive quarter-plane for simplicity of
// clamping to the maximum supported bounds.
float cutoff_left = std::fmax(-viewport_left, 0.0f);
float cutoff_right = std::fmax(viewport_right - x_max, 0.0f);
viewport_left = std::fmax(viewport_left, 0.0f);
viewport_right = std::fmin(viewport_right, x_max);
viewport_width = viewport_right - viewport_left;
if (viewport_width > size_min) {
ndc_scale_x =
(viewport_width + cutoff_left + cutoff_right) / viewport_width;
if (pa_cl_vport_xscale < 0.0f) {
ndc_scale_x = -ndc_scale_x;
}
ndc_offset_x =
((cutoff_right - cutoff_left) * (0.5f * 2.0f)) / viewport_width;
} else {
// Empty viewport, but don't pass 0 because that's against the Vulkan
// specification.
viewport_left = 0.0f;
viewport_width = size_min;
ndc_scale_x = 0.0f;
ndc_offset_x = 0.0f;
}
} else {
// Drawing without a viewport and without clipping to one - use a viewport
// covering the entire potential guest render target or the positive part of
// the host viewport area, whichever is smaller, and apply the offset, if
// enabled, via the shader.
viewport_left = 0.0f;
viewport_width = std::min(
float(xenos::kTexture2DCubeMaxWidthHeight) * pixel_size_x, x_max);
ndc_scale_x = (2.0f * pixel_size_x) / viewport_width;
ndc_offset_x = viewport_offset_x * ndc_scale_x - 1.0f;
}
if (pa_cl_vte_cntl.vport_y_scale_ena) {
float pa_cl_vport_yscale = regs[XE_GPU_REG_PA_CL_VPORT_YSCALE].f32;
float viewport_scale_y_abs = std::abs(pa_cl_vport_yscale) * pixel_size_y;
viewport_top = viewport_offset_y * pixel_size_y - viewport_scale_y_abs;
float viewport_bottom = viewport_top + viewport_scale_y_abs * 2.0f;
float cutoff_top = std::fmax(-viewport_top, 0.0f);
float cutoff_bottom = std::fmax(viewport_bottom - y_max, 0.0f);
viewport_top = std::fmax(viewport_top, 0.0f);
viewport_bottom = std::fmin(viewport_bottom, y_max);
viewport_height = viewport_bottom - viewport_top;
if (viewport_height > size_min) {
ndc_scale_y =
(viewport_height + cutoff_top + cutoff_bottom) / viewport_height;
if (pa_cl_vport_yscale < 0.0f) {
ndc_scale_y = -ndc_scale_y;
}
ndc_offset_y =
((cutoff_bottom - cutoff_top) * (0.5f * 2.0f)) / viewport_height;
} else {
// Empty viewport, but don't pass 0 because that's against the Vulkan
// specification.
viewport_top = 0.0f;
viewport_height = size_min;
ndc_scale_y = 0.0f;
ndc_offset_y = 0.0f;
}
} else {
viewport_height = std::min(
float(xenos::kTexture2DCubeMaxWidthHeight) * pixel_size_y, y_max);
ndc_scale_y = (2.0f * pixel_size_y) / viewport_height;
ndc_offset_y = viewport_offset_y * ndc_scale_y - 1.0f;
}
// Apply the vertex half-pixel offset via the shader (it must not affect
// clipping, otherwise with SSAA or resolution scale, samples in the left/top
// half will never be covered).
if (cvars::half_pixel_offset && !pa_su_vtx_cntl.pix_center) {
ndc_offset_x += (0.5f * 2.0f * pixel_size_x) / viewport_width;
ndc_offset_y += (0.5f * 2.0f * pixel_size_y) / viewport_height;
}
if (origin_bottom_left) {
ndc_scale_y = -ndc_scale_y;
ndc_offset_y = -ndc_offset_y;
}
float viewport_scale_z = pa_cl_vte_cntl.vport_z_scale_ena
? regs[XE_GPU_REG_PA_CL_VPORT_ZSCALE].f32
: 1.0f;
float viewport_offset_z = pa_cl_vte_cntl.vport_z_offset_ena
? regs[XE_GPU_REG_PA_CL_VPORT_ZOFFSET].f32
: 0.0f;
// Vulkan requires the depth bounds to be in the 0 to 1 range without
// VK_EXT_depth_range_unrestricted (which isn't used on the Xbox 360).
float viewport_z_min = std::min(std::fmax(viewport_offset_z, 0.0f), 1.0f);
float viewport_z_max =
std::min(std::fmax(viewport_offset_z + viewport_scale_z, 0.0f), 1.0f);
// When VPORT_Z_SCALE_ENA is disabled, Z/W is directly what is expected to be
// written to the depth buffer, and for some reason DX_CLIP_SPACE_DEF isn't
// set in this case in draws in games.
bool gl_clip_space_def =
!pa_cl_clip_cntl.dx_clip_space_def && pa_cl_vte_cntl.vport_z_scale_ena;
float ndc_scale_z = gl_clip_space_def ? 0.5f : 1.0f;
float ndc_offset_z = gl_clip_space_def ? 0.5f : 0.0f;
if (viewport_z_min > viewport_z_max && !allow_reverse_z) {
std::swap(viewport_z_min, viewport_z_max);
ndc_scale_z = -ndc_scale_z;
ndc_offset_z = 1.0f - ndc_offset_z;
}
viewport_info_out.left = viewport_left;
viewport_info_out.top = viewport_top;
viewport_info_out.width = viewport_width;
viewport_info_out.height = viewport_height;
viewport_info_out.z_min = viewport_z_min;
viewport_info_out.z_max = viewport_z_max;
viewport_info_out.ndc_scale[0] = ndc_scale_x;
viewport_info_out.ndc_scale[1] = ndc_scale_y;
viewport_info_out.ndc_scale[2] = ndc_scale_z;
viewport_info_out.ndc_offset[0] = ndc_offset_x;
viewport_info_out.ndc_offset[1] = ndc_offset_y;
viewport_info_out.ndc_offset[2] = ndc_offset_z;
}
void GetScissor(const RegisterFile& regs, Scissor& scissor_out) { void GetScissor(const RegisterFile& regs, Scissor& scissor_out) {
// FIXME(Triang3l): Screen scissor isn't applied here, but it seems to be // FIXME(Triang3l): Screen scissor isn't applied here, but it seems to be
// unused on Xbox 360 Direct3D 9. // unused on Xbox 360 Direct3D 9.

View File

@ -33,6 +33,28 @@ namespace draw_util {
// for use with the top-left rasterization rule later. // for use with the top-left rasterization rule later.
int32_t FloatToD3D11Fixed16p8(float f32); int32_t FloatToD3D11Fixed16p8(float f32);
struct ViewportInfo {
// The returned viewport will always be in the positive quarter-plane for
// simplicity of clamping to the maximum size supported by the host, negative
// offset will be applied via ndc_offset.
float left;
float top;
float width;
float height;
float z_min;
float z_max;
float ndc_scale[3];
float ndc_offset[3];
};
// Converts the guest viewport (or fakes one if drawing without a viewport) to
// a viewport, plus values to multiply-add the returned position by, usable on
// host graphics APIs such as Direct3D 11+ and Vulkan, also forcing it to the
// Direct3D clip space with 0...W Z rather than -W...W.
void GetHostViewportInfo(const RegisterFile& regs, float pixel_size_x,
float pixel_size_y, bool origin_bottom_left,
float x_max, float y_max, bool allow_reverse_z,
ViewportInfo& viewport_info_out);
struct Scissor { struct Scissor {
uint32_t left; uint32_t left;
uint32_t top; uint32_t top;

View File

@ -770,7 +770,7 @@ void DxbcShaderTranslator::StartPixelShader() {
uint32_t(CbufferRegister::kSystemConstants), uint32_t(CbufferRegister::kSystemConstants),
kSysConst_Flags_Vec) kSysConst_Flags_Vec)
.Select(kSysConst_Flags_Comp), .Select(kSysConst_Flags_Comp),
DxbcSrc::LU(kSysFlag_PrimitiveTwoFaced)); DxbcSrc::LU(kSysFlag_PrimitivePolygonal));
DxbcOpIf(true, DxbcSrc::R(param_gen_temp, DxbcSrc::kZZZZ)); DxbcOpIf(true, DxbcSrc::R(param_gen_temp, DxbcSrc::kZZZZ));
{ {
// Negate modifier flips the sign bit even for 0 - set it to minus for // Negate modifier flips the sign bit even for 0 - set it to minus for
@ -1044,10 +1044,9 @@ void DxbcShaderTranslator::CompleteVertexOrDomainShader() {
DxbcOpEndIf(); DxbcOpEndIf();
} }
// Apply scale for drawing without a viewport, and also remap from OpenGL // Apply scale for guest to host viewport and clip space conversion. Also, if
// Z clip space to Direct3D if needed. Also, if the vertex shader is // the vertex shader is multipass, the NDC scale constant can be used to set
// multipass, the NDC scale constant can be used to set position to NaN to // position to NaN to kill all primitives.
// kill all primitives.
system_constants_used_ |= 1ull << kSysConst_NDCScale_Index; system_constants_used_ |= 1ull << kSysConst_NDCScale_Index;
DxbcOpMul(DxbcDest::R(system_temp_position_, 0b0111), DxbcOpMul(DxbcDest::R(system_temp_position_, 0b0111),
DxbcSrc::R(system_temp_position_), DxbcSrc::R(system_temp_position_),
@ -1056,16 +1055,7 @@ void DxbcShaderTranslator::CompleteVertexOrDomainShader() {
kSysConst_NDCScale_Vec, kSysConst_NDCScale_Vec,
kSysConst_NDCScale_Comp * 0b010101 + 0b100100)); kSysConst_NDCScale_Comp * 0b010101 + 0b100100));
// Reverse Z (Z = W - Z) if the viewport depth is inverted. // Apply offset (multiplied by W) used for the same purposes.
DxbcOpAnd(temp_x_dest, flags_src, DxbcSrc::LU(kSysFlag_ReverseZ));
DxbcOpIf(true, temp_x_src);
DxbcOpAdd(DxbcDest::R(system_temp_position_, 0b0100),
DxbcSrc::R(system_temp_position_, DxbcSrc::kWWWW),
-DxbcSrc::R(system_temp_position_, DxbcSrc::kZZZZ));
DxbcOpEndIf();
// Apply offset (multiplied by W) for drawing without a viewport and for half
// pixel offset.
system_constants_used_ |= 1ull << kSysConst_NDCOffset_Index; system_constants_used_ |= 1ull << kSysConst_NDCOffset_Index;
DxbcOpMAd(DxbcDest::R(system_temp_position_, 0b0111), DxbcOpMAd(DxbcDest::R(system_temp_position_, 0b0111),
DxbcSrc::CB(cbuffer_index_system_constants_, DxbcSrc::CB(cbuffer_index_system_constants_,

View File

@ -123,9 +123,8 @@ class DxbcShaderTranslator : public ShaderTranslator {
kSysFlag_UserClipPlane3_Shift, kSysFlag_UserClipPlane3_Shift,
kSysFlag_UserClipPlane4_Shift, kSysFlag_UserClipPlane4_Shift,
kSysFlag_UserClipPlane5_Shift, kSysFlag_UserClipPlane5_Shift,
kSysFlag_ReverseZ_Shift,
kSysFlag_KillIfAnyVertexKilled_Shift, kSysFlag_KillIfAnyVertexKilled_Shift,
kSysFlag_PrimitiveTwoFaced_Shift, kSysFlag_PrimitivePolygonal_Shift,
kSysFlag_AlphaPassIfLess_Shift, kSysFlag_AlphaPassIfLess_Shift,
kSysFlag_AlphaPassIfEqual_Shift, kSysFlag_AlphaPassIfEqual_Shift,
kSysFlag_AlphaPassIfGreater_Shift, kSysFlag_AlphaPassIfGreater_Shift,
@ -165,9 +164,8 @@ class DxbcShaderTranslator : public ShaderTranslator {
kSysFlag_UserClipPlane3 = 1u << kSysFlag_UserClipPlane3_Shift, kSysFlag_UserClipPlane3 = 1u << kSysFlag_UserClipPlane3_Shift,
kSysFlag_UserClipPlane4 = 1u << kSysFlag_UserClipPlane4_Shift, kSysFlag_UserClipPlane4 = 1u << kSysFlag_UserClipPlane4_Shift,
kSysFlag_UserClipPlane5 = 1u << kSysFlag_UserClipPlane5_Shift, kSysFlag_UserClipPlane5 = 1u << kSysFlag_UserClipPlane5_Shift,
kSysFlag_ReverseZ = 1u << kSysFlag_ReverseZ_Shift,
kSysFlag_KillIfAnyVertexKilled = 1u << kSysFlag_KillIfAnyVertexKilled_Shift, kSysFlag_KillIfAnyVertexKilled = 1u << kSysFlag_KillIfAnyVertexKilled_Shift,
kSysFlag_PrimitiveTwoFaced = 1u << kSysFlag_PrimitiveTwoFaced_Shift, kSysFlag_PrimitivePolygonal = 1u << kSysFlag_PrimitivePolygonal_Shift,
kSysFlag_AlphaPassIfLess = 1u << kSysFlag_AlphaPassIfLess_Shift, kSysFlag_AlphaPassIfLess = 1u << kSysFlag_AlphaPassIfLess_Shift,
kSysFlag_AlphaPassIfEqual = 1u << kSysFlag_AlphaPassIfEqual_Shift, kSysFlag_AlphaPassIfEqual = 1u << kSysFlag_AlphaPassIfEqual_Shift,
kSysFlag_AlphaPassIfGreater = 1u << kSysFlag_AlphaPassIfGreater_Shift, kSysFlag_AlphaPassIfGreater = 1u << kSysFlag_AlphaPassIfGreater_Shift,
@ -220,8 +218,7 @@ class DxbcShaderTranslator : public ShaderTranslator {
float point_size[2]; float point_size[2];
float point_size_min_max[2]; float point_size_min_max[2];
// Inverse scale of the host viewport (but not supersampled), with signs // Screen point size * 2 (but not supersampled) -> size in NDC.
// pre-applied.
float point_screen_to_ndc[2]; float point_screen_to_ndc[2];
float user_clip_planes[6][4]; float user_clip_planes[6][4];

View File

@ -15,9 +15,25 @@
//#define XE_GPU_REGISTER(index, type, name) //#define XE_GPU_REGISTER(index, type, name)
XE_GPU_REGISTER(0x0048, kDword, BIF_PERFCOUNTER0_SELECT)
XE_GPU_REGISTER(0x0049, kDword, BIF_PERFCOUNTER0_HI)
XE_GPU_REGISTER(0x004A, kDword, BIF_PERFCOUNTER0_LOW)
XE_GPU_REGISTER(0x01DD, kDword, SCRATCH_ADDR) XE_GPU_REGISTER(0x01DD, kDword, SCRATCH_ADDR)
XE_GPU_REGISTER(0x01DC, kDword, SCRATCH_UMSK) XE_GPU_REGISTER(0x01DC, kDword, SCRATCH_UMSK)
XE_GPU_REGISTER(0x01E6, kDword, CP_PERFCOUNTER0_SELECT)
XE_GPU_REGISTER(0x01E7, kDword, CP_PERFCOUNTER0_LOW)
XE_GPU_REGISTER(0x01E8, kDword, CP_PERFCOUNTER0_HI)
XE_GPU_REGISTER(0x01F5, kDword, CP_PERFMON_CNTL)
XE_GPU_REGISTER(0x0395, kDword, RBBM_PERFCOUNTER0_SELECT)
XE_GPU_REGISTER(0x0396, kDword, RBBM_PERFCOUNTER1_SELECT)
XE_GPU_REGISTER(0x0397, kDword, RBBM_PERFCOUNTER0_LOW)
XE_GPU_REGISTER(0x0398, kDword, RBBM_PERFCOUNTER0_HI)
XE_GPU_REGISTER(0x0399, kDword, RBBM_PERFCOUNTER1_LOW)
XE_GPU_REGISTER(0x039A, kDword, RBBM_PERFCOUNTER1_HI)
XE_GPU_REGISTER(0x045E, kDword, CALLBACK_ACK) XE_GPU_REGISTER(0x045E, kDword, CALLBACK_ACK)
XE_GPU_REGISTER(0x0578, kDword, SCRATCH_REG0) // interrupt sync XE_GPU_REGISTER(0x0578, kDword, SCRATCH_REG0) // interrupt sync
@ -31,33 +47,226 @@ XE_GPU_REGISTER(0x057F, kDword, SCRATCH_REG7)
XE_GPU_REGISTER(0x05C8, kDword, WAIT_UNTIL) XE_GPU_REGISTER(0x05C8, kDword, WAIT_UNTIL)
XE_GPU_REGISTER(0x0815, kDword, MC0_PERFCOUNTER0_SELECT)
XE_GPU_REGISTER(0x0816, kDword, MC0_PERFCOUNTER0_HI)
XE_GPU_REGISTER(0x0817, kDword, MC0_PERFCOUNTER0_LOW)
XE_GPU_REGISTER(0x0855, kDword, MC1_PERFCOUNTER0_SELECT)
XE_GPU_REGISTER(0x0856, kDword, MC1_PERFCOUNTER0_HI)
XE_GPU_REGISTER(0x0857, kDword, MC1_PERFCOUNTER0_LOW)
XE_GPU_REGISTER(0x0A02, kDword, UNKNOWN_0A02) XE_GPU_REGISTER(0x0A02, kDword, UNKNOWN_0A02)
XE_GPU_REGISTER(0x0A03, kDword, UNKNOWN_0A03) XE_GPU_REGISTER(0x0A03, kDword, UNKNOWN_0A03)
XE_GPU_REGISTER(0x0A04, kDword, UNKNOWN_0A04) XE_GPU_REGISTER(0x0A04, kDword, UNKNOWN_0A04)
XE_GPU_REGISTER(0x0A05, kDword, UNKNOWN_0A05) XE_GPU_REGISTER(0x0A05, kDword, UNKNOWN_0A05)
XE_GPU_REGISTER(0x0A18, kDword, MH_PERFCOUNTER0_SELECT)
XE_GPU_REGISTER(0x0A19, kDword, MH_PERFCOUNTER0_HI)
XE_GPU_REGISTER(0x0A1A, kDword, MH_PERFCOUNTER0_LOW)
XE_GPU_REGISTER(0x0A1B, kDword, MH_PERFCOUNTER1_SELECT)
XE_GPU_REGISTER(0x0A1C, kDword, MH_PERFCOUNTER1_HI)
XE_GPU_REGISTER(0x0A1D, kDword, MH_PERFCOUNTER1_LOW)
XE_GPU_REGISTER(0x0A1E, kDword, MH_PERFCOUNTER2_SELECT)
XE_GPU_REGISTER(0x0A1F, kDword, MH_PERFCOUNTER2_HI)
XE_GPU_REGISTER(0x0A20, kDword, MH_PERFCOUNTER2_LOW)
XE_GPU_REGISTER(0x0A2F, kDword, COHER_SIZE_HOST) XE_GPU_REGISTER(0x0A2F, kDword, COHER_SIZE_HOST)
XE_GPU_REGISTER(0x0A30, kDword, COHER_BASE_HOST) XE_GPU_REGISTER(0x0A30, kDword, COHER_BASE_HOST)
XE_GPU_REGISTER(0x0A31, kDword, COHER_STATUS_HOST) XE_GPU_REGISTER(0x0A31, kDword, COHER_STATUS_HOST)
// Status flags of viz queries, doesn't seem to be read back by d3d
// queries 0x00 to 0x1f (be), bit set when visible
XE_GPU_REGISTER(0x0C44, kDword, PA_SC_VIZ_QUERY_STATUS_0)
// queries 0x20 to 0x3f (be)
XE_GPU_REGISTER(0x0C45, kDword, PA_SC_VIZ_QUERY_STATUS_1)
XE_GPU_REGISTER(0x0C48, kDword, VGT_PERFCOUNTER0_SELECT)
XE_GPU_REGISTER(0x0C49, kDword, VGT_PERFCOUNTER1_SELECT)
XE_GPU_REGISTER(0x0C4A, kDword, VGT_PERFCOUNTER2_SELECT)
XE_GPU_REGISTER(0x0C4B, kDword, VGT_PERFCOUNTER3_SELECT)
XE_GPU_REGISTER(0x0C4C, kDword, VGT_PERFCOUNTER0_LOW)
XE_GPU_REGISTER(0x0C4D, kDword, VGT_PERFCOUNTER0_HI)
XE_GPU_REGISTER(0x0C4E, kDword, VGT_PERFCOUNTER1_LOW)
XE_GPU_REGISTER(0x0C4F, kDword, VGT_PERFCOUNTER1_HI)
XE_GPU_REGISTER(0x0C50, kDword, VGT_PERFCOUNTER2_LOW)
XE_GPU_REGISTER(0x0C51, kDword, VGT_PERFCOUNTER2_HI)
XE_GPU_REGISTER(0x0C52, kDword, VGT_PERFCOUNTER3_LOW)
XE_GPU_REGISTER(0x0C53, kDword, VGT_PERFCOUNTER3_HI)
XE_GPU_REGISTER(0x0C85, kDword, PA_CL_ENHANCE)
XE_GPU_REGISTER(0x0C88, kDword, PA_SU_PERFCOUNTER0_SELECT)
XE_GPU_REGISTER(0x0C89, kDword, PA_SU_PERFCOUNTER1_SELECT)
XE_GPU_REGISTER(0x0C8A, kDword, PA_SU_PERFCOUNTER2_SELECT)
XE_GPU_REGISTER(0x0C8B, kDword, PA_SU_PERFCOUNTER3_SELECT)
XE_GPU_REGISTER(0x0C8C, kDword, PA_SU_PERFCOUNTER0_LOW)
XE_GPU_REGISTER(0x0C8D, kDword, PA_SU_PERFCOUNTER0_HI)
XE_GPU_REGISTER(0x0C8E, kDword, PA_SU_PERFCOUNTER1_LOW)
XE_GPU_REGISTER(0x0C8F, kDword, PA_SU_PERFCOUNTER1_HI)
XE_GPU_REGISTER(0x0C90, kDword, PA_SU_PERFCOUNTER2_LOW)
XE_GPU_REGISTER(0x0C91, kDword, PA_SU_PERFCOUNTER2_HI)
XE_GPU_REGISTER(0x0C92, kDword, PA_SU_PERFCOUNTER3_LOW)
XE_GPU_REGISTER(0x0C93, kDword, PA_SU_PERFCOUNTER3_HI)
XE_GPU_REGISTER(0x0C98, kDword, PA_SC_PERFCOUNTER0_SELECT)
XE_GPU_REGISTER(0x0C99, kDword, PA_SC_PERFCOUNTER1_SELECT)
XE_GPU_REGISTER(0x0C9A, kDword, PA_SC_PERFCOUNTER2_SELECT)
XE_GPU_REGISTER(0x0C9B, kDword, PA_SC_PERFCOUNTER3_SELECT)
XE_GPU_REGISTER(0x0C9C, kDword, PA_SC_PERFCOUNTER0_LOW)
XE_GPU_REGISTER(0x0C9D, kDword, PA_SC_PERFCOUNTER0_HI)
XE_GPU_REGISTER(0x0C9E, kDword, PA_SC_PERFCOUNTER1_LOW)
XE_GPU_REGISTER(0x0C9F, kDword, PA_SC_PERFCOUNTER1_HI)
XE_GPU_REGISTER(0x0CA0, kDword, PA_SC_PERFCOUNTER2_LOW)
XE_GPU_REGISTER(0x0CA1, kDword, PA_SC_PERFCOUNTER2_HI)
XE_GPU_REGISTER(0x0CA2, kDword, PA_SC_PERFCOUNTER3_LOW)
XE_GPU_REGISTER(0x0CA3, kDword, PA_SC_PERFCOUNTER3_HI)
XE_GPU_REGISTER(0x0D00, kDword, SQ_GPR_MANAGEMENT) XE_GPU_REGISTER(0x0D00, kDword, SQ_GPR_MANAGEMENT)
XE_GPU_REGISTER(0x0D01, kDword, SQ_FLOW_CONTROL) XE_GPU_REGISTER(0x0D01, kDword, SQ_FLOW_CONTROL)
XE_GPU_REGISTER(0x0D02, kDword, SQ_INST_STORE_MANAGMENT) XE_GPU_REGISTER(0x0D02, kDword, SQ_INST_STORE_MANAGMENT)
XE_GPU_REGISTER(0x0D04, kDword, SQ_EO_RT) XE_GPU_REGISTER(0x0D04, kDword, SQ_EO_RT)
XE_GPU_REGISTER(0x0C85, kDword, PA_CL_ENHANCE) XE_GPU_REGISTER(0x0DC8, kDword, SQ_PERFCOUNTER0_SELECT)
XE_GPU_REGISTER(0x0DC9, kDword, SQ_PERFCOUNTER1_SELECT)
XE_GPU_REGISTER(0x0DCA, kDword, SQ_PERFCOUNTER2_SELECT)
XE_GPU_REGISTER(0x0DCB, kDword, SQ_PERFCOUNTER3_SELECT)
XE_GPU_REGISTER(0x0DCC, kDword, SQ_PERFCOUNTER0_LOW)
XE_GPU_REGISTER(0x0DCD, kDword, SQ_PERFCOUNTER0_HI)
XE_GPU_REGISTER(0x0DCE, kDword, SQ_PERFCOUNTER1_LOW)
XE_GPU_REGISTER(0x0DCF, kDword, SQ_PERFCOUNTER1_HI)
XE_GPU_REGISTER(0x0DD0, kDword, SQ_PERFCOUNTER2_LOW)
XE_GPU_REGISTER(0x0DD1, kDword, SQ_PERFCOUNTER2_HI)
XE_GPU_REGISTER(0x0DD2, kDword, SQ_PERFCOUNTER3_LOW)
XE_GPU_REGISTER(0x0DD3, kDword, SQ_PERFCOUNTER3_HI)
XE_GPU_REGISTER(0x0DD4, kDword, SX_PERFCOUNTER0_SELECT)
XE_GPU_REGISTER(0x0DD8, kDword, SX_PERFCOUNTER0_LOW)
XE_GPU_REGISTER(0x0DD9, kDword, SX_PERFCOUNTER0_HI)
// Set with WAIT_UNTIL = WAIT_3D_IDLECLEAN // Set with WAIT_UNTIL = WAIT_3D_IDLECLEAN
XE_GPU_REGISTER(0x0E00, kDword, UNKNOWN_0E00) XE_GPU_REGISTER(0x0E00, kDword, UNKNOWN_0E00)
XE_GPU_REGISTER(0x0E05, kDword, TCR_PERFCOUNTER0_SELECT)
XE_GPU_REGISTER(0x0E06, kDword, TCR_PERFCOUNTER0_HI)
XE_GPU_REGISTER(0x0E07, kDword, TCR_PERFCOUNTER0_LOW)
XE_GPU_REGISTER(0x0E08, kDword, TCR_PERFCOUNTER1_SELECT)
XE_GPU_REGISTER(0x0E09, kDword, TCR_PERFCOUNTER1_HI)
XE_GPU_REGISTER(0x0E0A, kDword, TCR_PERFCOUNTER1_LOW)
XE_GPU_REGISTER(0x0E1F, kDword, TP0_PERFCOUNTER0_SELECT)
XE_GPU_REGISTER(0x0E20, kDword, TP0_PERFCOUNTER0_HI)
XE_GPU_REGISTER(0x0E21, kDword, TP0_PERFCOUNTER0_LOW)
XE_GPU_REGISTER(0x0E22, kDword, TP0_PERFCOUNTER1_SELECT)
XE_GPU_REGISTER(0x0E23, kDword, TP0_PERFCOUNTER1_HI)
XE_GPU_REGISTER(0x0E24, kDword, TP0_PERFCOUNTER1_LOW)
XE_GPU_REGISTER(0x0E28, kDword, TP1_PERFCOUNTER0_SELECT)
XE_GPU_REGISTER(0x0E29, kDword, TP1_PERFCOUNTER0_HI)
XE_GPU_REGISTER(0x0E2A, kDword, TP1_PERFCOUNTER0_LOW)
XE_GPU_REGISTER(0x0E2B, kDword, TP1_PERFCOUNTER1_SELECT)
XE_GPU_REGISTER(0x0E2C, kDword, TP1_PERFCOUNTER1_HI)
XE_GPU_REGISTER(0x0E2D, kDword, TP1_PERFCOUNTER1_LOW)
XE_GPU_REGISTER(0x0E31, kDword, TP2_PERFCOUNTER0_SELECT)
XE_GPU_REGISTER(0x0E32, kDword, TP2_PERFCOUNTER0_HI)
XE_GPU_REGISTER(0x0E33, kDword, TP2_PERFCOUNTER0_LOW)
XE_GPU_REGISTER(0x0E34, kDword, TP2_PERFCOUNTER1_SELECT)
XE_GPU_REGISTER(0x0E35, kDword, TP2_PERFCOUNTER1_HI)
XE_GPU_REGISTER(0x0E36, kDword, TP2_PERFCOUNTER1_LOW)
XE_GPU_REGISTER(0x0E3A, kDword, TP3_PERFCOUNTER0_SELECT)
XE_GPU_REGISTER(0x0E3B, kDword, TP3_PERFCOUNTER0_HI)
XE_GPU_REGISTER(0x0E3C, kDword, TP3_PERFCOUNTER0_LOW)
XE_GPU_REGISTER(0x0E3D, kDword, TP3_PERFCOUNTER1_SELECT)
XE_GPU_REGISTER(0x0E3E, kDword, TP3_PERFCOUNTER1_HI)
XE_GPU_REGISTER(0x0E3F, kDword, TP3_PERFCOUNTER1_LOW)
// Set with WAIT_UNTIL = WAIT_3D_IDLECLEAN
XE_GPU_REGISTER(0x0E40, kDword, UNKNOWN_0E40) XE_GPU_REGISTER(0x0E40, kDword, UNKNOWN_0E40)
// Set during GPU initialization by D3D
XE_GPU_REGISTER(0x0E42, kDword, UNKNOWN_0E42) XE_GPU_REGISTER(0x0E42, kDword, UNKNOWN_0E42)
XE_GPU_REGISTER(0x0E48, kDword, VC_PERFCOUNTER0_SELECT)
XE_GPU_REGISTER(0x0E49, kDword, VC_PERFCOUNTER0_HI)
XE_GPU_REGISTER(0x0E4A, kDword, VC_PERFCOUNTER0_LOW)
XE_GPU_REGISTER(0x0E4B, kDword, VC_PERFCOUNTER1_SELECT)
XE_GPU_REGISTER(0x0E4C, kDword, VC_PERFCOUNTER1_HI)
XE_GPU_REGISTER(0x0E4D, kDword, VC_PERFCOUNTER1_LOW)
XE_GPU_REGISTER(0x0E4E, kDword, VC_PERFCOUNTER2_SELECT)
XE_GPU_REGISTER(0x0E4F, kDword, VC_PERFCOUNTER2_HI)
XE_GPU_REGISTER(0x0E50, kDword, VC_PERFCOUNTER2_LOW)
XE_GPU_REGISTER(0x0E51, kDword, VC_PERFCOUNTER3_SELECT)
XE_GPU_REGISTER(0x0E52, kDword, VC_PERFCOUNTER3_HI)
XE_GPU_REGISTER(0x0E53, kDword, VC_PERFCOUNTER3_LOW)
XE_GPU_REGISTER(0x0E54, kDword, TCM_PERFCOUNTER0_SELECT)
XE_GPU_REGISTER(0x0E55, kDword, TCM_PERFCOUNTER0_HI)
XE_GPU_REGISTER(0x0E56, kDword, TCM_PERFCOUNTER0_LOW)
XE_GPU_REGISTER(0x0E57, kDword, TCM_PERFCOUNTER1_SELECT)
XE_GPU_REGISTER(0x0E58, kDword, TCM_PERFCOUNTER1_HI)
XE_GPU_REGISTER(0x0E59, kDword, TCM_PERFCOUNTER1_LOW)
XE_GPU_REGISTER(0x0E5A, kDword, TCF_PERFCOUNTER0_SELECT)
XE_GPU_REGISTER(0x0E5B, kDword, TCF_PERFCOUNTER0_HI)
XE_GPU_REGISTER(0x0E5C, kDword, TCF_PERFCOUNTER0_LOW)
XE_GPU_REGISTER(0x0E5D, kDword, TCF_PERFCOUNTER1_SELECT)
XE_GPU_REGISTER(0x0E5E, kDword, TCF_PERFCOUNTER1_HI)
XE_GPU_REGISTER(0x0E5F, kDword, TCF_PERFCOUNTER1_LOW)
XE_GPU_REGISTER(0x0E60, kDword, TCF_PERFCOUNTER2_SELECT)
XE_GPU_REGISTER(0x0E61, kDword, TCF_PERFCOUNTER2_HI)
XE_GPU_REGISTER(0x0E62, kDword, TCF_PERFCOUNTER2_LOW)
XE_GPU_REGISTER(0x0E63, kDword, TCF_PERFCOUNTER3_SELECT)
XE_GPU_REGISTER(0x0E64, kDword, TCF_PERFCOUNTER3_HI)
XE_GPU_REGISTER(0x0E65, kDword, TCF_PERFCOUNTER3_LOW)
XE_GPU_REGISTER(0x0E66, kDword, TCF_PERFCOUNTER4_SELECT)
XE_GPU_REGISTER(0x0E67, kDword, TCF_PERFCOUNTER4_HI)
XE_GPU_REGISTER(0x0E68, kDword, TCF_PERFCOUNTER4_LOW)
XE_GPU_REGISTER(0x0E69, kDword, TCF_PERFCOUNTER5_SELECT)
XE_GPU_REGISTER(0x0E6A, kDword, TCF_PERFCOUNTER5_HI)
XE_GPU_REGISTER(0x0E6B, kDword, TCF_PERFCOUNTER5_LOW)
XE_GPU_REGISTER(0x0E6C, kDword, TCF_PERFCOUNTER6_SELECT)
XE_GPU_REGISTER(0x0E6D, kDword, TCF_PERFCOUNTER6_HI)
XE_GPU_REGISTER(0x0E6E, kDword, TCF_PERFCOUNTER6_LOW)
XE_GPU_REGISTER(0x0E6F, kDword, TCF_PERFCOUNTER7_SELECT)
XE_GPU_REGISTER(0x0E70, kDword, TCF_PERFCOUNTER7_HI)
XE_GPU_REGISTER(0x0E71, kDword, TCF_PERFCOUNTER7_LOW)
XE_GPU_REGISTER(0x0E72, kDword, TCF_PERFCOUNTER8_SELECT)
XE_GPU_REGISTER(0x0E73, kDword, TCF_PERFCOUNTER8_HI)
XE_GPU_REGISTER(0x0E74, kDword, TCF_PERFCOUNTER8_LOW)
XE_GPU_REGISTER(0x0E75, kDword, TCF_PERFCOUNTER9_SELECT)
XE_GPU_REGISTER(0x0E76, kDword, TCF_PERFCOUNTER9_HI)
XE_GPU_REGISTER(0x0E77, kDword, TCF_PERFCOUNTER9_LOW)
XE_GPU_REGISTER(0x0E78, kDword, TCF_PERFCOUNTER10_SELECT)
XE_GPU_REGISTER(0x0E79, kDword, TCF_PERFCOUNTER10_HI)
XE_GPU_REGISTER(0x0E7A, kDword, TCF_PERFCOUNTER10_LOW)
XE_GPU_REGISTER(0x0E7B, kDword, TCF_PERFCOUNTER11_SELECT)
XE_GPU_REGISTER(0x0E7C, kDword, TCF_PERFCOUNTER11_HI)
XE_GPU_REGISTER(0x0E7D, kDword, TCF_PERFCOUNTER11_LOW)
XE_GPU_REGISTER(0x0F01, kDword, RB_BC_CONTROL) XE_GPU_REGISTER(0x0F01, kDword, RB_BC_CONTROL)
XE_GPU_REGISTER(0x0F02, kDword, RB_EDRAM_INFO) XE_GPU_REGISTER(0x0F02, kDword, RB_EDRAM_INFO)
// D1*, LUT, and AVIVO registers taken from libxenon and https://www.x.org/docs/AMD/old/RRG-216M56-03oOEM.pdf XE_GPU_REGISTER(0x0F04, kDword, BC_PERFCOUNTER0_SELECT)
XE_GPU_REGISTER(0x0F05, kDword, BC_PERFCOUNTER1_SELECT)
XE_GPU_REGISTER(0x0F06, kDword, BC_PERFCOUNTER2_SELECT)
XE_GPU_REGISTER(0x0F07, kDword, BC_PERFCOUNTER3_SELECT)
XE_GPU_REGISTER(0x0F08, kDword, BC_PERFCOUNTER0_LOW)
XE_GPU_REGISTER(0x0F09, kDword, BC_PERFCOUNTER0_HI)
XE_GPU_REGISTER(0x0F0A, kDword, BC_PERFCOUNTER1_LOW)
XE_GPU_REGISTER(0x0F0B, kDword, BC_PERFCOUNTER1_HI)
XE_GPU_REGISTER(0x0F0C, kDword, BC_PERFCOUNTER2_LOW)
XE_GPU_REGISTER(0x0F0D, kDword, BC_PERFCOUNTER2_HI)
XE_GPU_REGISTER(0x0F0E, kDword, BC_PERFCOUNTER3_LOW)
XE_GPU_REGISTER(0x0F0F, kDword, BC_PERFCOUNTER3_HI)
XE_GPU_REGISTER(0x1004, kDword, HZ_PERFCOUNTER0_SELECT)
XE_GPU_REGISTER(0x1005, kDword, HZ_PERFCOUNTER0_HI)
XE_GPU_REGISTER(0x1006, kDword, HZ_PERFCOUNTER0_LOW)
XE_GPU_REGISTER(0x1007, kDword, HZ_PERFCOUNTER1_SELECT)
XE_GPU_REGISTER(0x1008, kDword, HZ_PERFCOUNTER1_HI)
XE_GPU_REGISTER(0x1009, kDword, HZ_PERFCOUNTER1_LOW)
// D1*, LUT, and AVIVO registers taken from libxenon and
// https://www.x.org/docs/AMD/old/RRG-216M56-03oOEM.pdf
XE_GPU_REGISTER(0x1838, kDword, D1MODE_MASTER_UPDATE_LOCK) XE_GPU_REGISTER(0x1838, kDword, D1MODE_MASTER_UPDATE_LOCK)
XE_GPU_REGISTER(0x1841, kDword, D1GRPH_CONTROL) XE_GPU_REGISTER(0x1841, kDword, D1GRPH_CONTROL)

View File

@ -289,12 +289,16 @@ union PA_SC_MPASS_PS_CNTL {
static constexpr Register register_index = XE_GPU_REG_PA_SC_MPASS_PS_CNTL; static constexpr Register register_index = XE_GPU_REG_PA_SC_MPASS_PS_CNTL;
}; };
// Scanline converter viz query // Scanline converter viz query, used by D3D for gpu side conditional rendering
union PA_SC_VIZ_QUERY { union PA_SC_VIZ_QUERY {
struct { struct {
uint32_t viz_query_ena : 1; // +0 // the visibility of draws should be evaluated
uint32_t viz_query_id : 6; // +1 uint32_t viz_query_ena : 1; // +0
uint32_t kill_pix_post_early_z : 1; // +7 uint32_t viz_query_id : 6; // +1
// discard geometry after test (but use for testing)
uint32_t kill_pix_post_hi_z : 1; // +7
// not used with d3d
uint32_t kill_pix_detail_mask : 1; // +8
}; };
uint32_t value; uint32_t value;
static constexpr Register register_index = XE_GPU_REG_PA_SC_VIZ_QUERY; static constexpr Register register_index = XE_GPU_REG_PA_SC_VIZ_QUERY;

View File

@ -64,7 +64,15 @@ enum class PrimitiveType : uint32_t {
kQuadPatch = 0x12, kQuadPatch = 0x12,
}; };
inline bool IsPrimitiveTwoFaced(bool tessellated, PrimitiveType type) { // Polygonal primitive types (not including points and lines) are rasterized as
// triangles, have front and back faces, and also support face culling and fill
// modes (polymode_front_ptype, polymode_back_ptype). Other primitive types are
// always "front" (but don't support front face and back face culling, according
// to OpenGL and Vulkan specifications - even if glCullFace is
// GL_FRONT_AND_BACK, points and lines are still drawn), and may in some cases
// use the "para" registers instead of "front" or "back" (for "parallelogram" -
// like poly_offset_para_enable).
constexpr bool IsPrimitivePolygonal(bool tessellated, PrimitiveType type) {
if (tessellated && (type == PrimitiveType::kTrianglePatch || if (tessellated && (type == PrimitiveType::kTrianglePatch ||
type == PrimitiveType::kQuadPatch)) { type == PrimitiveType::kQuadPatch)) {
return true; return true;
@ -81,6 +89,10 @@ inline bool IsPrimitiveTwoFaced(bool tessellated, PrimitiveType type) {
default: default:
break; break;
} }
// TODO(Triang3l): Investigate how kRectangleList should be treated - possibly
// actually drawn as two polygons on the console, however, the current
// geometry shader doesn't care about the winding order - allowing backface
// culling for rectangles currently breaks Gears of War 2.
return false; return false;
} }

View File

@ -782,13 +782,13 @@ bool KernelState::Save(ByteStream* stream) {
for (auto object : objects) { for (auto object : objects) {
auto prev_offset = stream->offset(); auto prev_offset = stream->offset();
if (object->is_host_object() || object->type() == XObject::kTypeThread) { if (object->is_host_object() || object->type() == XObject::Type::Thread) {
// Don't save host objects or save XThreads again // Don't save host objects or save XThreads again
num_objects--; num_objects--;
continue; continue;
} }
stream->Write<uint32_t>(object->type()); stream->Write<uint32_t>(static_cast<uint32_t>(object->type()));
if (!object->Save(stream)) { if (!object->Save(stream)) {
XELOGD("Did not save object of type {}", object->type()); XELOGD("Did not save object of type {}", object->type());
assert_always(); assert_always();
@ -823,7 +823,7 @@ bool KernelState::Restore(ByteStream* stream) {
uint32_t num_threads = stream->Read<uint32_t>(); uint32_t num_threads = stream->Read<uint32_t>();
XELOGD("Loading {} threads...", num_threads); XELOGD("Loading {} threads...", num_threads);
for (uint32_t i = 0; i < num_threads; i++) { for (uint32_t i = 0; i < num_threads; i++) {
auto thread = XObject::Restore(this, XObject::kTypeThread, stream); auto thread = XObject::Restore(this, XObject::Type::Thread, stream);
if (!thread) { if (!thread) {
// Can't continue the restore or we risk misalignment. // Can't continue the restore or we risk misalignment.
assert_always(); assert_always();

View File

@ -51,7 +51,7 @@ class ObjectTable {
object_ref<T> LookupObject(X_HANDLE handle) { object_ref<T> LookupObject(X_HANDLE handle) {
auto object = LookupObject(handle, false); auto object = LookupObject(handle, false);
if (object) { if (object) {
assert_true(object->type() == T::kType); assert_true(object->type() == T::kObjectType);
} }
auto result = object_ref<T>(reinterpret_cast<T*>(object)); auto result = object_ref<T>(reinterpret_cast<T*>(object));
return result; return result;
@ -72,7 +72,7 @@ class ObjectTable {
std::vector<object_ref<T>> GetObjectsByType() { std::vector<object_ref<T>> GetObjectsByType() {
std::vector<object_ref<T>> results; std::vector<object_ref<T>> results;
GetObjectsByType( GetObjectsByType(
T::kType, T::kObjectType,
reinterpret_cast<std::vector<object_ref<XObject>>*>(&results)); reinterpret_cast<std::vector<object_ref<XObject>>*>(&results));
return results; return results;
} }

View File

@ -148,7 +148,7 @@ class Param {
protected: protected:
Param() : ordinal_(-1) {} Param() : ordinal_(-1) {}
explicit Param(Init& init) : ordinal_(--init.ordinal) {} explicit Param(Init& init) : ordinal_(init.ordinal++) {}
template <typename V> template <typename V>
void LoadValue(Init& init, V* out_value) { void LoadValue(Init& init, V* out_value) {
@ -519,10 +519,13 @@ xe::cpu::Export* RegisterExport(R (*fn)(Ps&...), const char* name,
++export_entry->function_data.call_count; ++export_entry->function_data.call_count;
Param::Init init = { Param::Init init = {
ppc_context, ppc_context,
sizeof...(Ps),
0, 0,
}; };
auto params = std::make_tuple<Ps...>(Ps(init)...); // Using braces initializer instead of make_tuple because braces
// enforce execution order across compilers.
// The make_tuple order is undefined per the C++ standard and
// cause inconsitencies between msvc and clang.
std::tuple<Ps...> params = {Ps(init)...};
if (export_entry->tags & xe::cpu::ExportTag::kLog && if (export_entry->tags & xe::cpu::ExportTag::kLog &&
(!(export_entry->tags & xe::cpu::ExportTag::kHighFrequency) || (!(export_entry->tags & xe::cpu::ExportTag::kHighFrequency) ||
cvars::log_high_frequency_kernel_calls)) { cvars::log_high_frequency_kernel_calls)) {
@ -554,9 +557,13 @@ xe::cpu::Export* RegisterExport(void (*fn)(Ps&...), const char* name,
++export_entry->function_data.call_count; ++export_entry->function_data.call_count;
Param::Init init = { Param::Init init = {
ppc_context, ppc_context,
sizeof...(Ps), 0,
}; };
auto params = std::make_tuple<Ps...>(Ps(init)...); // Using braces initializer instead of make_tuple because braces
// enforce execution order across compilers.
// The make_tuple order is undefined per the C++ standard and
// cause inconsitencies between msvc and clang.
std::tuple<Ps...> params = {Ps(init)...};
if (export_entry->tags & xe::cpu::ExportTag::kLog && if (export_entry->tags & xe::cpu::ExportTag::kLog &&
(!(export_entry->tags & xe::cpu::ExportTag::kHighFrequency) || (!(export_entry->tags & xe::cpu::ExportTag::kHighFrequency) ||
cvars::log_high_frequency_kernel_calls)) { cvars::log_high_frequency_kernel_calls)) {

View File

@ -314,6 +314,7 @@ class UserProfile {
uint64_t xuid() const { return account_.xuid_online; } uint64_t xuid() const { return account_.xuid_online; }
std::string name() const { return to_utf8(account_.GetGamertagString()); } std::string name() const { return to_utf8(account_.GetGamertagString()); }
uint32_t signin_state() const { return 1; } uint32_t signin_state() const { return 1; }
uint32_t type() const { return 2; /* online profile? */ }
void AddSetting(std::unique_ptr<Setting> setting); void AddSetting(std::unique_ptr<Setting> setting);
Setting* GetSetting(uint32_t setting_id); Setting* GetSetting(uint32_t setting_id);

View File

@ -39,15 +39,6 @@ struct DeviceInfo {
char16_t name[28]; char16_t name[28];
}; };
typedef struct {
xe::be<uint32_t> device_id;
xe::be<uint32_t> device_type;
xe::be<uint64_t> total_bytes;
xe::be<uint64_t> free_bytes;
xe::be<uint16_t> name[28];
} X_CONTENT_DEVICE_DATA;
//static_assert_size(X_CONTENT_DEVICE_DATA, 0x50);
// TODO(gibbed): real information. // TODO(gibbed): real information.
// //
// Until we expose real information about a HDD device, we // Until we expose real information about a HDD device, we
@ -57,15 +48,12 @@ typedef struct {
// they incorrectly only look at the lower 32-bits of free_bytes, // they incorrectly only look at the lower 32-bits of free_bytes,
// when it is a 64-bit value. Which means any size above ~4GB // when it is a 64-bit value. Which means any size above ~4GB
// will not be recognized properly. // will not be recognized properly.
//
// Rapala fishing only detected the hdd if the size was
// raised to 20GB (the smallest xbox hdd)
#define ONE_GB (1024ull * 1024ull * 1024ull) #define ONE_GB (1024ull * 1024ull * 1024ull)
static const DeviceInfo dummy_device_info_ = { static const DeviceInfo dummy_device_info_ = {
0x00000001, // first device seems to be storage 0x00000001, // id
1, // hdd type 1, // 1=HDD
20ull * ONE_GB, // 20GB 20ull * ONE_GB, // 20GB
12ull * ONE_GB, // 12GB, so it looks a little used. 3ull * ONE_GB, // 3GB, so it looks a little used.
u"Dummy HDD", u"Dummy HDD",
}; };
#undef ONE_GB #undef ONE_GB
@ -132,6 +120,15 @@ dword_result_t XamContentGetDeviceState(dword_t device_id,
} }
DECLARE_XAM_EXPORT1(XamContentGetDeviceState, kContent, kStub); DECLARE_XAM_EXPORT1(XamContentGetDeviceState, kContent, kStub);
typedef struct {
xe::be<uint32_t> device_id;
xe::be<uint32_t> device_type;
xe::be<uint64_t> total_bytes;
xe::be<uint64_t> free_bytes;
xe::be<uint16_t> name[28];
} X_CONTENT_DEVICE_DATA;
static_assert_size(X_CONTENT_DEVICE_DATA, 0x50);
dword_result_t XamContentGetDeviceData( dword_result_t XamContentGetDeviceData(
dword_t device_id, pointer_t<X_CONTENT_DEVICE_DATA> device_data) { dword_t device_id, pointer_t<X_CONTENT_DEVICE_DATA> device_data) {
if ((device_id & 0x0000000F) != dummy_device_info_.device_id) { if ((device_id & 0x0000000F) != dummy_device_info_.device_id) {
@ -144,10 +141,10 @@ dword_result_t XamContentGetDeviceData(
device_data.Zero(); device_data.Zero();
const auto& device_info = dummy_device_info_; const auto& device_info = dummy_device_info_;
xe::store_and_swap(&device_data->device_id, device_info.device_id); device_data->device_id = device_info.device_id;
xe::store_and_swap(&device_data->device_type, device_info.device_type); device_data->device_type = device_info.device_type;
xe::store(&device_data->total_bytes, device_info.total_bytes); device_data->total_bytes = device_info.total_bytes;
xe::store(&device_data->free_bytes, device_info.free_bytes); device_data->free_bytes = device_info.free_bytes;
xe::store_and_swap<std::u16string>(&device_data->name[0], device_info.name); xe::store_and_swap<std::u16string>(&device_data->name[0], device_info.name);
return X_ERROR_SUCCESS; return X_ERROR_SUCCESS;
} }

View File

@ -102,25 +102,25 @@ DECLARE_XAM_EXPORT1(XamProfileEnumerate, kUserProfiles, kImplemented);
X_HRESULT_result_t XamUserGetXUID(dword_t user_index, dword_t type_mask, X_HRESULT_result_t XamUserGetXUID(dword_t user_index, dword_t type_mask,
lpqword_t xuid_ptr) { lpqword_t xuid_ptr) {
assert_true(type_mask == 1 || type_mask == 2 || type_mask == 3 ||
type_mask == 4 || type_mask == 7);
if (!xuid_ptr) { if (!xuid_ptr) {
return X_E_INVALIDARG; return X_E_INVALIDARG;
} }
assert_true(type_mask == 1 || type_mask == 2 || type_mask == 3 ||
type_mask == 4 || type_mask == 7);
uint32_t result = X_E_NO_SUCH_USER; uint32_t result = X_E_NO_SUCH_USER;
uint64_t xuid = 0; uint64_t xuid = 0;
if (user_index < 4) { if (user_index < 4) {
if (user_index == 0) { if (user_index == 0) {
const auto& user_profile = kernel_state()->user_profile(); const auto& user_profile = kernel_state()->user_profile();
if (type_mask & (2 | 4)) { auto type = user_profile->type() & type_mask;
if (type & (2 | 4)) {
// maybe online profile?
xuid = user_profile->xuid(); xuid = user_profile->xuid();
result = X_E_SUCCESS; result = X_E_SUCCESS;
} else if (type_mask & 1) { } else if (type & 1) {
// maybe offline profile?
xuid = user_profile->xuid(); xuid = user_profile->xuid();
result = X_E_SUCCESS; result = X_E_SUCCESS;
} else {
result = X_E_INVALIDARG;
} }
} }
} else { } else {
@ -178,24 +178,44 @@ DECLARE_XAM_EXPORT1(XamUserGetSigninInfo, kUserProfiles, kImplemented);
dword_result_t XamUserGetName(dword_t user_index, lpstring_t buffer, dword_result_t XamUserGetName(dword_t user_index, lpstring_t buffer,
dword_t buffer_len) { dword_t buffer_len) {
if (user_index) { if (user_index >= 4) {
return X_ERROR_NO_SUCH_USER; return X_E_INVALIDARG;
} }
if (!buffer_len) { if (user_index) {
return X_ERROR_SUCCESS; return X_E_NO_SUCH_USER;
} }
const auto& user_profile = kernel_state()->user_profile(); const auto& user_profile = kernel_state()->user_profile();
const auto& user_name = user_profile->name(); const auto& user_name = user_profile->name();
// Real XAM will only copy a maximum of 15 characters out.
xe::string_util::copy_truncating(buffer, user_name, xe::string_util::copy_truncating(buffer, user_name,
std::min(buffer_len.value(), uint32_t(15))); std::min(buffer_len.value(), uint32_t(16)));
return X_ERROR_SUCCESS; return X_E_SUCCESS;
} }
DECLARE_XAM_EXPORT1(XamUserGetName, kUserProfiles, kImplemented); DECLARE_XAM_EXPORT1(XamUserGetName, kUserProfiles, kImplemented);
dword_result_t XamUserGetGamerTag(dword_t user_index, lpu16string_t buffer,
dword_t buffer_len) {
if (user_index >= 4) {
return X_E_INVALIDARG;
}
if (user_index) {
return X_E_NO_SUCH_USER;
}
if (!buffer || buffer_len < 16) {
return X_E_INVALIDARG;
}
const auto& user_profile = kernel_state()->user_profile();
auto user_name = xe::to_utf16(user_profile->name());
xe::string_util::copy_and_swap_truncating(
buffer, user_name, std::min(buffer_len.value(), uint32_t(16)));
return X_E_SUCCESS;
}
DECLARE_XAM_EXPORT1(XamUserGetGamerTag, kUserProfiles, kImplemented);
typedef struct { typedef struct {
xe::be<uint32_t> setting_count; xe::be<uint32_t> setting_count;
xe::be<uint32_t> settings_ptr; xe::be<uint32_t> settings_ptr;
@ -481,8 +501,15 @@ DECLARE_XAM_EXPORT1(XamUserWriteProfileSettings, kUserProfiles, kImplemented);
dword_result_t XamUserCheckPrivilege(dword_t user_index, dword_t mask, dword_result_t XamUserCheckPrivilege(dword_t user_index, dword_t mask,
lpdword_t out_value) { lpdword_t out_value) {
if (user_index) { // checking all users?
return X_ERROR_NO_SUCH_USER; if (user_index != 0xFF) {
if (user_index >= 4) {
return X_ERROR_INVALID_PARAMETER;
}
if (user_index) {
return X_ERROR_NO_SUCH_USER;
}
} }
// If we deny everything, games should hopefully not try to do stuff. // If we deny everything, games should hopefully not try to do stuff.
@ -539,6 +566,17 @@ DECLARE_XAM_EXPORT1(XamUserContentRestrictionCheckAccess, kUserProfiles, kStub);
dword_result_t XamUserIsOnlineEnabled(dword_t user_index) { return 1; } dword_result_t XamUserIsOnlineEnabled(dword_t user_index) { return 1; }
DECLARE_XAM_EXPORT1(XamUserIsOnlineEnabled, kUserProfiles, kStub); DECLARE_XAM_EXPORT1(XamUserIsOnlineEnabled, kUserProfiles, kStub);
dword_result_t XamUserGetMembershipTier(dword_t user_index) {
if (user_index >= 4) {
return X_ERROR_INVALID_PARAMETER;
}
if (user_index) {
return X_ERROR_NO_SUCH_USER;
}
return 6 /* 6 appears to be Gold */;
}
DECLARE_XAM_EXPORT1(XamUserGetMembershipTier, kUserProfiles, kStub);
dword_result_t XamUserAreUsersFriends(dword_t user_index, dword_t unk1, dword_result_t XamUserAreUsersFriends(dword_t user_index, dword_t unk1,
dword_t unk2, lpdword_t out_value, dword_t unk2, lpdword_t out_value,
dword_t overlapped_ptr) { dword_t overlapped_ptr) {

View File

@ -47,8 +47,15 @@ void HandleSetThreadName(pointer_t<X_EXCEPTION_RECORD> record) {
return; return;
} }
auto name = // Shadowrun (and its demo) has a bug where it ends up passing freed memory
kernel_memory()->TranslateVirtual<const char*>(thread_info->name_ptr); // for the name, so at the point of SetThreadName it's filled with junk.
// TODO(gibbed): cvar for thread name encoding for conversion, some games use
// SJIS and there's no way to automatically know this.
auto name = std::string(
kernel_memory()->TranslateVirtual<const char*>(thread_info->name_ptr));
std::replace_if(
name.begin(), name.end(), [](auto c) { return c < 32 || c > 127; }, '?');
object_ref<XThread> thread; object_ref<XThread> thread;
if (thread_info->thread_id == -1) { if (thread_info->thread_id == -1) {

View File

@ -1,4 +1,4 @@
/** /**
****************************************************************************** ******************************************************************************
* Xenia : Xbox 360 Emulator Research Project * * Xenia : Xbox 360 Emulator Research Project *
****************************************************************************** ******************************************************************************
@ -29,10 +29,23 @@ namespace xboxkrnl {
static bool IsValidPath(const std::string_view s, bool is_pattern) { static bool IsValidPath(const std::string_view s, bool is_pattern) {
// TODO(gibbed): validate path components individually // TODO(gibbed): validate path components individually
bool got_asterisk = false;
for (const auto& c : s) { for (const auto& c : s) {
if (c <= 31 || c >= 127) { if (c <= 31 || c >= 127) {
return false; return false;
} }
if (got_asterisk) {
// * must be followed by a . (*.)
//
// Viva Piñata: Party Animals (4D530819) has a bug in its game code where
// it attempts to FindFirstFile() with filters of "Game:\\*_X3.rkv",
// "Game:\\m*_X3.rkv", and "Game:\\w*_X3.rkv" and will infinite loop if
// the path filter is allowed.
if (c != '.') {
return false;
}
got_asterisk = false;
}
switch (c) { switch (c) {
case '"': case '"':
// case '*': // case '*':
@ -47,12 +60,20 @@ static bool IsValidPath(const std::string_view s, bool is_pattern) {
case '|': { case '|': {
return false; return false;
} }
case '*': case '*': {
// Pattern-specific (for NtQueryDirectoryFile)
if (!is_pattern) {
return false;
}
got_asterisk = true;
break;
}
case '?': { case '?': {
// Pattern-specific (for NtQueryDirectoryFile) // Pattern-specific (for NtQueryDirectoryFile)
if (!is_pattern) { if (!is_pattern) {
return false; return false;
} }
break;
} }
default: { default: {
break; break;
@ -98,7 +119,7 @@ dword_result_t NtCreateFile(lpdword_t handle_out, dword_t desired_access,
auto root_file = kernel_state()->object_table()->LookupObject<XFile>( auto root_file = kernel_state()->object_table()->LookupObject<XFile>(
object_attrs->root_directory); object_attrs->root_directory);
assert_not_null(root_file); assert_not_null(root_file);
assert_true(root_file->type() == XObject::Type::kTypeFile); assert_true(root_file->type() == XObject::Type::File);
root_entry = root_file->entry(); root_entry = root_file->entry();
} }
@ -368,7 +389,7 @@ dword_result_t NtQueryFullAttributesFile(
root_file = kernel_state()->object_table()->LookupObject<XFile>( root_file = kernel_state()->object_table()->LookupObject<XFile>(
obj_attribs->root_directory); obj_attribs->root_directory);
assert_not_null(root_file); assert_not_null(root_file);
assert_true(root_file->type() == XObject::Type::kTypeFile); assert_true(root_file->type() == XObject::Type::File);
assert_always(); assert_always();
} }
@ -415,7 +436,7 @@ dword_result_t NtQueryDirectoryFile(
// Enforce that the path is ASCII. // Enforce that the path is ASCII.
if (!IsValidPath(name, true)) { if (!IsValidPath(name, true)) {
return X_STATUS_OBJECT_NAME_INVALID; return X_STATUS_INVALID_PARAMETER;
} }
if (file) { if (file) {

View File

@ -418,6 +418,11 @@ DECLARE_XBOXKRNL_EXPORT2(MmQueryAddressProtect, kMemory, kImplemented,
void MmSetAddressProtect(lpvoid_t base_address, dword_t region_size, void MmSetAddressProtect(lpvoid_t base_address, dword_t region_size,
dword_t protect_bits) { dword_t protect_bits) {
if (!protect_bits) {
XELOGE("MmSetAddressProtect: Failed due to incorrect protect_bits");
return;
}
uint32_t protect = FromXdkProtectFlags(protect_bits); uint32_t protect = FromXdkProtectFlags(protect_bits);
auto heap = kernel_memory()->LookupHeap(base_address); auto heap = kernel_memory()->LookupHeap(base_address);
heap->Protect(base_address.guest_address(), region_size, protect); heap->Protect(base_address.guest_address(), region_size, protect);

View File

@ -157,12 +157,15 @@ XboxkrnlModule::XboxkrnlModule(Emulator* emulator, KernelState* kernel_state)
// disassembly. // disassembly.
// aomega08 says the value is 0x02000817, bit 27: debug mode on. // aomega08 says the value is 0x02000817, bit 27: debug mode on.
// When that is set, though, allocs crash in weird ways. // When that is set, though, allocs crash in weird ways.
//
// From kernel dissasembly, after storage is initialized
// XboxHardwareInfo flags is set with flag 5 (0x20).
uint32_t pXboxHardwareInfo = memory_->SystemHeapAlloc(16); uint32_t pXboxHardwareInfo = memory_->SystemHeapAlloc(16);
auto lpXboxHardwareInfo = memory_->TranslateVirtual(pXboxHardwareInfo); auto lpXboxHardwareInfo = memory_->TranslateVirtual(pXboxHardwareInfo);
export_resolver_->SetVariableMapping( export_resolver_->SetVariableMapping(
"xboxkrnl.exe", ordinals::XboxHardwareInfo, pXboxHardwareInfo); "xboxkrnl.exe", ordinals::XboxHardwareInfo, pXboxHardwareInfo);
xe::store_and_swap<uint32_t>(lpXboxHardwareInfo + 0, 0x20); // flags //test xe::store_and_swap<uint32_t>(lpXboxHardwareInfo + 0, 0x20); // flags
xe::store_and_swap<uint8_t>(lpXboxHardwareInfo + 4, 0x06); // cpu count xe::store_and_swap<uint8_t>(lpXboxHardwareInfo + 4, 0x06); // cpu count
// Remaining 11b are zeroes? // Remaining 11b are zeroes?
// ExConsoleGameRegion, probably same values as keyvault region uses? // ExConsoleGameRegion, probably same values as keyvault region uses?

View File

@ -78,22 +78,21 @@ DECLARE_XBOXKRNL_EXPORT1(ObLookupThreadByThreadId, kNone, kImplemented);
dword_result_t ObReferenceObjectByHandle(dword_t handle, dword_result_t ObReferenceObjectByHandle(dword_t handle,
dword_t object_type_ptr, dword_t object_type_ptr,
lpdword_t out_object_ptr) { lpdword_t out_object_ptr) {
const static std::unordered_map<XObject::Type, uint32_t> obj_type_match = { // These values come from how Xenia handles uninitialized kernel data exports.
{XObject::kTypeEvent, 0xD00EBEEF}, // D###BEEF where ### is the ordinal.
{XObject::kTypeSemaphore, 0xD017BEEF}, const static std::unordered_map<XObject::Type, uint32_t> object_types = {
{XObject::kTypeThread, 0xD01BBEEF}}; {XObject::Type::Event, 0xD00EBEEF},
{XObject::Type::Semaphore, 0xD017BEEF},
{XObject::Type::Thread, 0xD01BBEEF}};
auto object = kernel_state()->object_table()->LookupObject<XObject>(handle); auto object = kernel_state()->object_table()->LookupObject<XObject>(handle);
if (!object) { if (!object) {
return X_STATUS_INVALID_HANDLE; return X_STATUS_INVALID_HANDLE;
} }
uint32_t native_ptr = object->guest_object(); uint32_t native_ptr = object->guest_object();
auto obj_type = obj_type_match.find(object->type()); auto object_type = object_types.find(object->type());
if (object_type != object_types.end()) {
if (obj_type != obj_type_match.end()) { if (object_type_ptr && object_type_ptr != object_type->second) {
if (object_type_ptr && object_type_ptr != obj_type->second) {
return X_STATUS_OBJECT_TYPE_MISMATCH; return X_STATUS_OBJECT_TYPE_MISMATCH;
} }
} else { } else {

View File

@ -21,16 +21,10 @@
#include "xenia/kernel/util/shim_utils.h" #include "xenia/kernel/util/shim_utils.h"
#include "xenia/kernel/xboxkrnl/xboxkrnl_private.h" #include "xenia/kernel/xboxkrnl/xboxkrnl_private.h"
#include "xenia/kernel/xboxkrnl/xboxkrnl_threading.h" #include "xenia/kernel/xboxkrnl/xboxkrnl_threading.h"
#include "xenia/kernel/xclock.h"
#include "xenia/kernel/xevent.h" #include "xenia/kernel/xevent.h"
#include "xenia/kernel/xthread.h" #include "xenia/kernel/xthread.h"
#if XE_PLATFORM_WIN32
#include "xenia/base/platform_win.h"
#define timegm _mkgmtime
#endif
static constexpr uint64_t MAX_TIME64_T = 0x793406FFF;
namespace xe { namespace xe {
namespace kernel { namespace kernel {
namespace xboxkrnl { namespace xboxkrnl {
@ -509,52 +503,51 @@ struct X_TIME_FIELDS {
xe::be<uint16_t> milliseconds; xe::be<uint16_t> milliseconds;
xe::be<uint16_t> weekday; xe::be<uint16_t> weekday;
}; };
static_assert(sizeof(X_TIME_FIELDS) == 16, "Must be LARGEINTEGER"); static_assert_size(X_TIME_FIELDS, 16);
// https://support.microsoft.com/en-us/kb/167296 // https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-rtltimetotimefields
void RtlTimeToTimeFields(lpqword_t time_ptr, void RtlTimeToTimeFields(lpqword_t time_ptr,
pointer_t<X_TIME_FIELDS> time_fields_ptr) { pointer_t<X_TIME_FIELDS> time_fields_ptr) {
int64_t time_ms = time_ptr.value() / 10000 - 11644473600000LL; auto tp = XClock::to_sys(XClock::from_file_time(time_ptr.value()));
time_t timet = time_ms / 1000; auto dp = date::floor<date::days>(tp);
auto year_month_day = date::year_month_day{dp};
// https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/localtime-s-localtime32-s-localtime64-s?view=vs-2019 auto weekday = date::weekday{dp};
// __time64_t structure auto time = date::hh_mm_ss{date::floor<std::chrono::milliseconds>(tp - dp)};
// allows dates to be expressed up through 23:59:59, January 18, 3001 time_fields_ptr->year = static_cast<int>(year_month_day.year());
if (uint64_t(timet) > MAX_TIME64_T) { time_fields_ptr->month = static_cast<unsigned>(year_month_day.month());
return; time_fields_ptr->day = static_cast<unsigned>(year_month_day.day());
} time_fields_ptr->weekday = weekday.c_encoding();
time_fields_ptr->hour = time.hours().count();
struct tm* tm = gmtime(&timet); time_fields_ptr->minute = time.minutes().count();
time_fields_ptr->second = static_cast<uint16_t>(time.seconds().count());
time_fields_ptr->year = tm->tm_year + 1900; time_fields_ptr->milliseconds =
time_fields_ptr->month = tm->tm_mon + 1; static_cast<uint16_t>(time.subseconds().count());
time_fields_ptr->day = tm->tm_mday;
time_fields_ptr->hour = tm->tm_hour;
time_fields_ptr->minute = tm->tm_min;
time_fields_ptr->second = tm->tm_sec;
time_fields_ptr->milliseconds = time_ms % 1000;
time_fields_ptr->weekday = tm->tm_wday;
} }
DECLARE_XBOXKRNL_EXPORT1(RtlTimeToTimeFields, kNone, kImplemented); DECLARE_XBOXKRNL_EXPORT1(RtlTimeToTimeFields, kNone, kImplemented);
// https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-rtltimefieldstotime
dword_result_t RtlTimeFieldsToTime(pointer_t<X_TIME_FIELDS> time_fields_ptr, dword_result_t RtlTimeFieldsToTime(pointer_t<X_TIME_FIELDS> time_fields_ptr,
lpqword_t time_ptr) { lpqword_t time_ptr) {
struct tm tm; if (time_fields_ptr->year < 1601 || time_fields_ptr->month < 1 ||
tm.tm_year = time_fields_ptr->year - 1900; time_fields_ptr->month > 11 || time_fields_ptr->day < 1 ||
tm.tm_mon = time_fields_ptr->month - 1; time_fields_ptr->hour > 23 || time_fields_ptr->minute > 59 ||
tm.tm_mday = time_fields_ptr->day; time_fields_ptr->second > 59 || time_fields_ptr->milliseconds > 999) {
tm.tm_hour = time_fields_ptr->hour;
tm.tm_min = time_fields_ptr->minute;
tm.tm_sec = time_fields_ptr->second;
tm.tm_isdst = 0;
time_t timet = timegm(&tm);
if (timet == -1) {
// set last error = ERROR_INVALID_PARAMETER
return 0; return 0;
} }
uint64_t time = auto year = date::year{time_fields_ptr->year};
((timet + 11644473600LL) * 1000 + time_fields_ptr->milliseconds) * 10000; auto month = date::month{time_fields_ptr->month};
*time_ptr = time; auto day = date::day{time_fields_ptr->day};
auto year_month_day = date::year_month_day{year, month, day};
if (!year_month_day.ok()) {
return 0;
}
auto dp = static_cast<date::sys_days>(year_month_day);
std::chrono::system_clock::time_point time = dp;
time += std::chrono::hours{time_fields_ptr->hour};
time += std::chrono::minutes{time_fields_ptr->minute};
time += std::chrono::seconds{time_fields_ptr->second};
time += std::chrono::milliseconds{time_fields_ptr->milliseconds};
*time_ptr = XClock::to_file_time(XClock::from_sys(time));
return 1; return 1;
} }
DECLARE_XBOXKRNL_EXPORT1(RtlTimeFieldsToTime, kNone, kImplemented); DECLARE_XBOXKRNL_EXPORT1(RtlTimeFieldsToTime, kNone, kImplemented);

View File

@ -434,7 +434,7 @@ dword_result_t NtCreateEvent(lpdword_t handle_ptr,
auto existing_object = auto existing_object =
LookupNamedObject<XEvent>(kernel_state(), obj_attributes_ptr); LookupNamedObject<XEvent>(kernel_state(), obj_attributes_ptr);
if (existing_object) { if (existing_object) {
if (existing_object->type() == XObject::kTypeEvent) { if (existing_object->type() == XObject::Type::Event) {
if (handle_ptr) { if (handle_ptr) {
existing_object->RetainHandle(); existing_object->RetainHandle();
*handle_ptr = existing_object->handle(); *handle_ptr = existing_object->handle();
@ -561,7 +561,7 @@ dword_result_t NtCreateSemaphore(lpdword_t handle_ptr,
auto existing_object = auto existing_object =
LookupNamedObject<XSemaphore>(kernel_state(), obj_attributes_ptr); LookupNamedObject<XSemaphore>(kernel_state(), obj_attributes_ptr);
if (existing_object) { if (existing_object) {
if (existing_object->type() == XObject::kTypeSemaphore) { if (existing_object->type() == XObject::Type::Semaphore) {
if (handle_ptr) { if (handle_ptr) {
existing_object->RetainHandle(); existing_object->RetainHandle();
*handle_ptr = existing_object->handle(); *handle_ptr = existing_object->handle();
@ -615,7 +615,7 @@ dword_result_t NtCreateMutant(lpdword_t handle_out,
auto existing_object = LookupNamedObject<XMutant>( auto existing_object = LookupNamedObject<XMutant>(
kernel_state(), obj_attributes.guest_address()); kernel_state(), obj_attributes.guest_address());
if (existing_object) { if (existing_object) {
if (existing_object->type() == XObject::kTypeMutant) { if (existing_object->type() == XObject::Type::Mutant) {
if (handle_out) { if (handle_out) {
existing_object->RetainHandle(); existing_object->RetainHandle();
*handle_out = existing_object->handle(); *handle_out = existing_object->handle();
@ -676,7 +676,7 @@ dword_result_t NtCreateTimer(lpdword_t handle_ptr, lpvoid_t obj_attributes_ptr,
auto existing_object = auto existing_object =
LookupNamedObject<XTimer>(kernel_state(), obj_attributes_ptr); LookupNamedObject<XTimer>(kernel_state(), obj_attributes_ptr);
if (existing_object) { if (existing_object) {
if (existing_object->type() == XObject::kTypeTimer) { if (existing_object->type() == XObject::Type::Timer) {
if (handle_ptr) { if (handle_ptr) {
existing_object->RetainHandle(); existing_object->RetainHandle();
*handle_ptr = existing_object->handle(); *handle_ptr = existing_object->handle();

78
src/xenia/kernel/xclock.h Normal file
View File

@ -0,0 +1,78 @@
/**
******************************************************************************
* 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_KERNEL_XCLOCK_H_
#define XENIA_KERNEL_XCLOCK_H_
#include <chrono>
#include "xenia/base/clock.h"
#include "third_party/date/include/date/date.h"
namespace xe {
namespace kernel {
struct XClock {
using rep = int64_t;
using period = std::ratio_multiply<std::ratio<100>, std::nano>;
using duration = std::chrono::duration<rep, period>;
using time_point = std::chrono::time_point<XClock>;
static constexpr bool is_steady = false;
static time_point now() noexcept {
return from_file_time(Clock::QueryGuestSystemTime());
}
static uint64_t to_file_time(time_point const& tp) noexcept {
return static_cast<uint64_t>(tp.time_since_epoch().count());
}
static time_point from_file_time(uint64_t const& tp) noexcept {
return time_point{duration{tp}};
}
static std::chrono::system_clock::time_point to_sys(time_point const& tp) {
// TODO(gibbed): verify behavior under Linux
using sys_duration = std::chrono::system_clock::duration;
using sys_time = std::chrono::system_clock::time_point;
auto dp = tp;
dp += system_clock_delta();
auto cdp = std::chrono::time_point_cast<sys_duration>(dp);
return sys_time{cdp.time_since_epoch()};
}
static time_point from_sys(std::chrono::system_clock::time_point const& tp) {
// TODO(gibbed): verify behavior under Linux
auto ctp = std::chrono::time_point_cast<duration>(tp);
auto dp = time_point{ctp.time_since_epoch()};
dp -= system_clock_delta();
return dp;
}
private:
// The delta between std::chrono::system_clock (Jan 1 1970) and Xenon file
// time (Jan 1 1601), in seconds. In the spec std::chrono::system_clock's
// epoch is undefined, but C++20 cements it as Jan 1 1970.
static constexpr std::chrono::seconds system_clock_delta() {
auto filetime_epoch = date::year{1601} / date::month{1} / date::day{1};
auto system_clock_epoch = date::year{1970} / date::month{1} / date::day{1};
std::chrono::system_clock::time_point fp{
static_cast<date::sys_days>(filetime_epoch)};
std::chrono::system_clock::time_point sp{
static_cast<date::sys_days>(system_clock_epoch)};
return std::chrono::floor<std::chrono::seconds>(fp.time_since_epoch() -
sp.time_since_epoch());
}
};
} // namespace kernel
} // namespace xe
#endif // XENIA_KERNEL_XCLOCK_H_

View File

@ -14,7 +14,7 @@ namespace kernel {
XEnumerator::XEnumerator(KernelState* kernel_state, size_t items_per_enumerate, XEnumerator::XEnumerator(KernelState* kernel_state, size_t items_per_enumerate,
size_t item_size) size_t item_size)
: XObject(kernel_state, kType), : XObject(kernel_state, kObjectType),
items_per_enumerate_(items_per_enumerate), items_per_enumerate_(items_per_enumerate),
item_size_(item_size) {} item_size_(item_size) {}

View File

@ -21,7 +21,7 @@ namespace kernel {
class XEnumerator : public XObject { class XEnumerator : public XObject {
public: public:
static const Type kType = kTypeEnumerator; static const XObject::Type kObjectType = XObject::Type::Enumerator;
XEnumerator(KernelState* kernel_state, size_t items_per_enumerate, XEnumerator(KernelState* kernel_state, size_t items_per_enumerate,
size_t item_size); size_t item_size);

View File

@ -15,7 +15,8 @@
namespace xe { namespace xe {
namespace kernel { namespace kernel {
XEvent::XEvent(KernelState* kernel_state) : XObject(kernel_state, kType) {} XEvent::XEvent(KernelState* kernel_state)
: XObject(kernel_state, kObjectType) {}
XEvent::~XEvent() = default; XEvent::~XEvent() = default;

View File

@ -25,7 +25,7 @@ static_assert_size(X_KEVENT, 0x10);
class XEvent : public XObject { class XEvent : public XObject {
public: public:
static const Type kType = kTypeEvent; static const XObject::Type kObjectType = XObject::Type::Event;
explicit XEvent(KernelState* kernel_state); explicit XEvent(KernelState* kernel_state);
~XEvent() override; ~XEvent() override;

View File

@ -22,11 +22,13 @@ namespace xe {
namespace kernel { namespace kernel {
XFile::XFile(KernelState* kernel_state, vfs::File* file, bool synchronous) XFile::XFile(KernelState* kernel_state, vfs::File* file, bool synchronous)
: XObject(kernel_state, kType), file_(file), is_synchronous_(synchronous) { : XObject(kernel_state, kObjectType),
file_(file),
is_synchronous_(synchronous) {
async_event_ = threading::Event::CreateAutoResetEvent(false); async_event_ = threading::Event::CreateAutoResetEvent(false);
} }
XFile::XFile() : XObject(kType) { XFile::XFile() : XObject(kObjectType) {
async_event_ = threading::Event::CreateAutoResetEvent(false); async_event_ = threading::Event::CreateAutoResetEvent(false);
} }

Some files were not shown because too many files have changed in this diff Show More