Merge branch 'master' of https://github.com/xenia-project/xenia into canary_new
|
@ -67,3 +67,9 @@
|
|||
[submodule "third_party/premake-cmake"]
|
||||
path = third_party/premake-cmake
|
||||
url = https://github.com/Enhex/premake-cmake.git
|
||||
[submodule "third_party/premake-androidmk"]
|
||||
path = third_party/premake-androidmk
|
||||
url = https://github.com/Triang3l/premake-androidmk.git
|
||||
[submodule "third_party/date"]
|
||||
path = third_party/date
|
||||
url = https://github.com/HowardHinnant/date.git
|
||||
|
|
|
@ -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)
|
||||
* 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
|
||||
something.
|
||||
-->
|
||||
|
|
|
@ -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
|
|
@ -0,0 +1 @@
|
|||
/build
|
|
@ -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')
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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>
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
After Width: | Height: | Size: 3.5 KiB |
After Width: | Height: | Size: 5.2 KiB |
After Width: | Height: | Size: 2.6 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 4.8 KiB |
After Width: | Height: | Size: 7.3 KiB |
After Width: | Height: | Size: 7.7 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 16 KiB |
|
@ -0,0 +1,3 @@
|
|||
<resources>
|
||||
<string name="app_name">Xenia</string>
|
||||
</resources>
|
|
@ -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
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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" "$@"
|
|
@ -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
|
|
@ -0,0 +1,2 @@
|
|||
include ':app'
|
||||
rootProject.name = "Xenia"
|
81
docs/gpu.md
|
@ -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).
|
||||
|
||||
#### Performance Counters that may be read back by D3D
|
||||
|
||||
They are 64-bit values and have a high and low 32-bit register as well as a `SELECT` register each:
|
||||
|
||||
- CP_PERFCOUNTER0
|
||||
|
||||
- RBBM_PERFCOUNTER0
|
||||
- RBBM_PERFCOUNTER1
|
||||
|
||||
- SQ_PERFCOUNTER0
|
||||
- SQ_PERFCOUNTER1
|
||||
- SQ_PERFCOUNTER2
|
||||
- SQ_PERFCOUNTER3
|
||||
|
||||
- VGT_PERFCOUNTER0
|
||||
- VGT_PERFCOUNTER1
|
||||
- VGT_PERFCOUNTER2
|
||||
- VGT_PERFCOUNTER3
|
||||
|
||||
- VC_PERFCOUNTER0
|
||||
- VC_PERFCOUNTER1
|
||||
- VC_PERFCOUNTER2
|
||||
- VC_PERFCOUNTER3
|
||||
|
||||
- PA_SU_PERFCOUNTER0
|
||||
- PA_SU_PERFCOUNTER1
|
||||
- PA_SU_PERFCOUNTER2
|
||||
- PA_SU_PERFCOUNTER3
|
||||
|
||||
- PA_SC_PERFCOUNTER0
|
||||
- PA_SC_PERFCOUNTER1
|
||||
- PA_SC_PERFCOUNTER2
|
||||
- PA_SC_PERFCOUNTER3
|
||||
|
||||
- HZ_PERFCOUNTER0
|
||||
- HZ_PERFCOUNTER1
|
||||
|
||||
- TCR_PERFCOUNTER0
|
||||
- TCR_PERFCOUNTER1
|
||||
|
||||
- TCM_PERFCOUNTER0
|
||||
- TCM_PERFCOUNTER1
|
||||
|
||||
- TCF_PERFCOUNTER0
|
||||
- TCF_PERFCOUNTER1
|
||||
- TCF_PERFCOUNTER2
|
||||
- TCF_PERFCOUNTER3
|
||||
- TCF_PERFCOUNTER4
|
||||
- TCF_PERFCOUNTER5
|
||||
- TCF_PERFCOUNTER6
|
||||
- TCF_PERFCOUNTER7
|
||||
- TCF_PERFCOUNTER8
|
||||
- TCF_PERFCOUNTER9
|
||||
- TCF_PERFCOUNTER10
|
||||
- TCF_PERFCOUNTER11
|
||||
|
||||
- TP0_PERFCOUNTER0
|
||||
- TP0_PERFCOUNTER1
|
||||
- TP1_PERFCOUNTER0
|
||||
- TP1_PERFCOUNTER1
|
||||
- TP2_PERFCOUNTER0
|
||||
- TP2_PERFCOUNTER1
|
||||
- TP3_PERFCOUNTER0
|
||||
- TP3_PERFCOUNTER1
|
||||
|
||||
- SX_PERFCOUNTER0
|
||||
|
||||
- BC_PERFCOUNTER0
|
||||
- BC_PERFCOUNTER1
|
||||
- BC_PERFCOUNTER2
|
||||
- BC_PERFCOUNTER3
|
||||
|
||||
- MC0_PERFCOUNTER0
|
||||
- MC1_PERFCOUNTER0
|
||||
|
||||
- MH_PERFCOUNTER0
|
||||
- MH_PERFCOUNTER1
|
||||
- MH_PERFCOUNTER2
|
||||
|
||||
- BIF_PERFCOUNTER0
|
||||
|
||||
### Shaders
|
||||
|
||||
* [LLVM R600 Tables](https://llvm.org/viewvc/llvm-project/llvm/trunk/lib/Target/AMDGPU/R600Instructions.td)
|
||||
|
|
72
premake5.lua
|
@ -1,6 +1,9 @@
|
|||
include("tools/build")
|
||||
require("third_party/premake-export-compile-commands/export-compile-commands")
|
||||
require("third_party/premake-cmake/cmake")
|
||||
-- gmake required for androidmk.
|
||||
require("gmake")
|
||||
require("third_party/premake-androidmk/androidmk")
|
||||
|
||||
location(build_root)
|
||||
targetdir(build_bin)
|
||||
|
@ -26,6 +29,8 @@ defines({
|
|||
})
|
||||
|
||||
cppdialect("C++17")
|
||||
exceptionhandling("On")
|
||||
rtti("On")
|
||||
symbols("On")
|
||||
|
||||
-- TODO(DrChat): Find a way to disable this on other architectures.
|
||||
|
@ -81,10 +86,15 @@ filter("configurations:Release")
|
|||
})
|
||||
optimize("Speed")
|
||||
inlining("Auto")
|
||||
floatingpoint("Fast")
|
||||
flags({
|
||||
"LinkTimeOptimization",
|
||||
})
|
||||
-- Not using floatingpoint("Fast") - NaN checks are used in some places
|
||||
-- (though rarely), overall preferable to avoid any functional differences
|
||||
-- between debug and release builds, and to have calculations involved in GPU
|
||||
-- (especially anything that may affect vertex position invariance) and CPU
|
||||
-- (such as constant propagation) emulation as predictable as possible,
|
||||
-- including handling of specials since games make assumptions about them.
|
||||
filter("platforms:Linux")
|
||||
system("linux")
|
||||
toolset("clang")
|
||||
|
@ -138,6 +148,13 @@ filter({"platforms:Linux", "language:C++", "toolset:clang", "files:*.cc or *.cpp
|
|||
"-stdlib=libstdc++",
|
||||
})
|
||||
|
||||
filter("platforms:Android")
|
||||
system("android")
|
||||
links({
|
||||
"android",
|
||||
"dl",
|
||||
})
|
||||
|
||||
filter("platforms:Windows")
|
||||
system("windows")
|
||||
toolset("msc")
|
||||
|
@ -189,18 +206,24 @@ end
|
|||
solution("xenia")
|
||||
uuid("931ef4b0-6170-4f7a-aaf2-0fece7632747")
|
||||
startproject("xenia-app")
|
||||
architecture("x86_64")
|
||||
if os.istarget("linux") then
|
||||
platforms({"Linux"})
|
||||
elseif os.istarget("windows") then
|
||||
platforms({"Windows"})
|
||||
-- 10.0.15063.0: ID3D12GraphicsCommandList1::SetSamplePositions.
|
||||
-- 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({})
|
||||
if os.istarget("android") then
|
||||
-- Not setting architecture as that's handled by ndk-build itself.
|
||||
platforms({"Android"})
|
||||
ndkstl("c++_static")
|
||||
else
|
||||
architecture("x86_64")
|
||||
if os.istarget("linux") then
|
||||
platforms({"Linux"})
|
||||
elseif os.istarget("windows") then
|
||||
platforms({"Windows"})
|
||||
-- 10.0.15063.0: ID3D12GraphicsCommandList1::SetSamplePositions.
|
||||
-- 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
|
||||
configurations({"Checked", "Debug", "Release"})
|
||||
|
||||
|
@ -215,18 +238,15 @@ solution("xenia")
|
|||
include("third_party/imgui.lua")
|
||||
include("third_party/libav.lua")
|
||||
include("third_party/mspack.lua")
|
||||
include("third_party/SDL2.lua")
|
||||
include("third_party/snappy.lua")
|
||||
include("third_party/spirv-tools.lua")
|
||||
include("third_party/volk.lua")
|
||||
include("third_party/xxhash.lua")
|
||||
|
||||
include("src/xenia")
|
||||
include("src/xenia/app")
|
||||
include("src/xenia/app/discord")
|
||||
include("src/xenia/apu")
|
||||
include("src/xenia/apu/nop")
|
||||
include("src/xenia/apu/sdl")
|
||||
include("src/xenia/base")
|
||||
include("src/xenia/cpu")
|
||||
include("src/xenia/cpu/backend/x64")
|
||||
|
@ -234,10 +254,8 @@ solution("xenia")
|
|||
include("src/xenia/gpu")
|
||||
include("src/xenia/gpu/null")
|
||||
include("src/xenia/gpu/vulkan")
|
||||
include("src/xenia/helper/sdl")
|
||||
include("src/xenia/hid")
|
||||
include("src/xenia/hid/nop")
|
||||
include("src/xenia/hid/sdl")
|
||||
include("src/xenia/kernel")
|
||||
include("src/xenia/patcher")
|
||||
include("src/xenia/ui")
|
||||
|
@ -245,6 +263,24 @@ solution("xenia")
|
|||
include("src/xenia/ui/vulkan")
|
||||
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
|
||||
include("src/xenia/apu/xaudio2")
|
||||
include("src/xenia/gpu/d3d12")
|
||||
|
|
|
@ -43,9 +43,10 @@ void DiscordPresence::NotPlaying() {
|
|||
}
|
||||
|
||||
void DiscordPresence::PlayingTitle(const std::string_view game_title) {
|
||||
auto details = std::string(game_title);
|
||||
DiscordRichPresence discordPresence = {};
|
||||
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.
|
||||
// discordPresence.smallImageKey = "app";
|
||||
// discordPresence.largeImageKey = "state_ingame";
|
||||
|
|
|
@ -216,7 +216,7 @@ int xenia_main(const std::vector<std::string>& args) {
|
|||
if (!cvars::portable &&
|
||||
!std::filesystem::exists(storage_root / "portable.txt")) {
|
||||
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";
|
||||
#else
|
||||
#warning Unhandled platform for the data root.
|
||||
|
|
|
@ -15,6 +15,10 @@
|
|||
#include "xenia/base/assert.h"
|
||||
#include "xenia/base/platform.h"
|
||||
|
||||
#if XE_PLATFORM_LINUX
|
||||
#include <byteswap.h>
|
||||
#endif
|
||||
|
||||
namespace xe {
|
||||
|
||||
#if XE_PLATFORM_WIN32
|
||||
|
@ -26,9 +30,9 @@ namespace xe {
|
|||
#define XENIA_BASE_BYTE_SWAP_32 OSSwapInt32
|
||||
#define XENIA_BASE_BYTE_SWAP_64 OSSwapInt64
|
||||
#else
|
||||
#define XENIA_BASE_BYTE_SWAP_16 __bswap_16
|
||||
#define XENIA_BASE_BYTE_SWAP_32 __bswap_32
|
||||
#define XENIA_BASE_BYTE_SWAP_64 __bswap_64
|
||||
#define XENIA_BASE_BYTE_SWAP_16 bswap_16
|
||||
#define XENIA_BASE_BYTE_SWAP_32 bswap_32
|
||||
#define XENIA_BASE_BYTE_SWAP_64 bswap_64
|
||||
#endif // XE_PLATFORM_WIN32
|
||||
|
||||
inline int8_t byte_swap(int8_t value) { return value; }
|
||||
|
|
|
@ -36,10 +36,8 @@
|
|||
|
||||
#include "third_party/fmt/include/fmt/format.h"
|
||||
|
||||
DEFINE_path(
|
||||
log_file, "",
|
||||
"Logs are written to the given file (specify stdout for command line)",
|
||||
"Logging");
|
||||
DEFINE_path(log_file, "", "Logs are written to the given file", "Logging");
|
||||
DEFINE_bool(log_to_stdout, true, "Write log output to stdout", "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.",
|
||||
"Logging");
|
||||
|
@ -66,31 +64,27 @@ struct LogLine {
|
|||
|
||||
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 {
|
||||
public:
|
||||
explicit Logger(const std::string_view app_name)
|
||||
: file_(nullptr),
|
||||
running_(true),
|
||||
wait_strategy_(),
|
||||
: wait_strategy_(),
|
||||
claim_strategy_(kBlockCount, wait_strategy_),
|
||||
consumed_(wait_strategy_) {
|
||||
consumed_(wait_strategy_),
|
||||
running_(true) {
|
||||
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_ =
|
||||
xe::threading::Thread::Create({}, [this]() { WriteThread(); });
|
||||
write_thread_->set_name("Logging Writer");
|
||||
|
@ -99,8 +93,10 @@ class Logger {
|
|||
~Logger() {
|
||||
running_ = false;
|
||||
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:
|
||||
|
@ -126,14 +122,14 @@ class Logger {
|
|||
dp::multi_threaded_claim_strategy<dp::spin_wait_strategy> claim_strategy_;
|
||||
dp::sequence_barrier<dp::spin_wait_strategy> consumed_;
|
||||
|
||||
FILE* file_;
|
||||
std::vector<std::unique_ptr<LogSink>> sinks_;
|
||||
|
||||
std::atomic<bool> running_;
|
||||
std::unique_ptr<xe::threading::Thread> write_thread_;
|
||||
|
||||
void Write(const char* buf, size_t size) {
|
||||
if (file_) {
|
||||
fwrite(buf, 1, size, file_);
|
||||
for (const auto& sink : sinks_) {
|
||||
sink->Write(buf, size);
|
||||
}
|
||||
if (cvars::log_to_debugprint) {
|
||||
debugging::DebugPrint("{}", std::string_view(buf, size));
|
||||
|
@ -246,7 +242,9 @@ class Logger {
|
|||
desired_count = 1;
|
||||
|
||||
if (cvars::flush_log) {
|
||||
fflush(file_);
|
||||
for (const auto& sink : sinks_) {
|
||||
sink->Flush();
|
||||
}
|
||||
}
|
||||
|
||||
idle_loops = 0;
|
||||
|
@ -291,6 +289,27 @@ class Logger {
|
|||
void InitializeLogging(const std::string_view app_name) {
|
||||
auto mem = memory::AlignedAlloc<Logger>(0x10);
|
||||
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() {
|
||||
|
|
|
@ -34,6 +34,31 @@ enum class LogLevel {
|
|||
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.
|
||||
// Must be called on startup.
|
||||
void InitializeLogging(const std::string_view app_name);
|
||||
|
|
|
@ -29,6 +29,8 @@
|
|||
|
||||
DEFINE_bool(win32_high_freq, true,
|
||||
"Requests high performance from the NT kernel", "Kernel");
|
||||
DEFINE_bool(enable_console, false, "Open a console window with the main window",
|
||||
"General");
|
||||
|
||||
namespace xe {
|
||||
|
||||
|
@ -37,27 +39,23 @@ bool has_console_attached_ = true;
|
|||
bool has_console_attached() { return has_console_attached_; }
|
||||
|
||||
void AttachConsole() {
|
||||
bool has_console = ::AttachConsole(ATTACH_PARENT_PROCESS) == TRUE;
|
||||
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;
|
||||
if (!cvars::enable_console) {
|
||||
return;
|
||||
}
|
||||
|
||||
AllocConsole();
|
||||
|
||||
has_console_attached_ = true;
|
||||
|
||||
auto std_handle = (intptr_t)GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
auto con_handle = _open_osfhandle(std_handle, _O_TEXT);
|
||||
auto fp = _fdopen(con_handle, "w");
|
||||
*stdout = *fp;
|
||||
setvbuf(stdout, nullptr, _IONBF, 0);
|
||||
freopen_s(&fp, "CONOUT$", "w", stdout);
|
||||
|
||||
std_handle = (intptr_t)GetStdHandle(STD_ERROR_HANDLE);
|
||||
con_handle = _open_osfhandle(std_handle, _O_TEXT);
|
||||
fp = _fdopen(con_handle, "w");
|
||||
*stderr = *fp;
|
||||
setvbuf(stderr, nullptr, _IONBF, 0);
|
||||
freopen_s(&fp, "CONOUT$", "w", stderr);
|
||||
}
|
||||
|
||||
static void RequestHighPerformance() {
|
||||
|
@ -125,6 +123,10 @@ int Main() {
|
|||
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.
|
||||
// NOTE: this may fail if COM has already been initialized - that's OK.
|
||||
#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.
|
||||
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.
|
||||
return xe::Main();
|
||||
}
|
||||
|
|
|
@ -22,6 +22,11 @@ namespace xe {
|
|||
|
||||
class Win32MappedMemory : public MappedMemory {
|
||||
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)
|
||||
: MappedMemory(path, mode) {}
|
||||
|
||||
|
@ -29,10 +34,10 @@ class Win32MappedMemory : public MappedMemory {
|
|||
if (data_) {
|
||||
UnmapViewOfFile(data_);
|
||||
}
|
||||
if (mapping_handle != INVALID_HANDLE_VALUE) {
|
||||
if (mapping_handle != kMappingHandleInvalid) {
|
||||
CloseHandle(mapping_handle);
|
||||
}
|
||||
if (file_handle != INVALID_HANDLE_VALUE) {
|
||||
if (file_handle != kFileHandleInvalid) {
|
||||
CloseHandle(file_handle);
|
||||
}
|
||||
}
|
||||
|
@ -42,11 +47,11 @@ class Win32MappedMemory : public MappedMemory {
|
|||
UnmapViewOfFile(data_);
|
||||
data_ = nullptr;
|
||||
}
|
||||
if (mapping_handle != INVALID_HANDLE_VALUE) {
|
||||
if (mapping_handle != kMappingHandleInvalid) {
|
||||
CloseHandle(mapping_handle);
|
||||
mapping_handle = INVALID_HANDLE_VALUE;
|
||||
mapping_handle = kMappingHandleInvalid;
|
||||
}
|
||||
if (file_handle != INVALID_HANDLE_VALUE) {
|
||||
if (file_handle != kFileHandleInvalid) {
|
||||
if (truncate_size) {
|
||||
LONG distance_high = truncate_size >> 32;
|
||||
SetFilePointer(file_handle, truncate_size & 0xFFFFFFFF, &distance_high,
|
||||
|
@ -55,7 +60,7 @@ class Win32MappedMemory : public MappedMemory {
|
|||
}
|
||||
|
||||
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);
|
||||
|
||||
UnmapViewOfFile(data_);
|
||||
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
|
||||
data_ = MapViewOfFile(mapping_handle, view_access_, aligned_offset >> 32,
|
||||
aligned_offset & 0xFFFFFFFF, aligned_length);
|
||||
#else
|
||||
data_ = MapViewOfFileFromApp(mapping_handle, ULONG(view_access_),
|
||||
ULONG64(aligned_offset), aligned_length);
|
||||
#endif
|
||||
if (!data_) {
|
||||
return false;
|
||||
}
|
||||
|
@ -83,8 +93,8 @@ class Win32MappedMemory : public MappedMemory {
|
|||
return true;
|
||||
}
|
||||
|
||||
HANDLE file_handle = INVALID_HANDLE_VALUE;
|
||||
HANDLE mapping_handle = INVALID_HANDLE_VALUE;
|
||||
HANDLE file_handle = kFileHandleInvalid;
|
||||
HANDLE mapping_handle = kMappingHandleInvalid;
|
||||
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,
|
||||
create_mode, FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||
if (mm->file_handle == INVALID_HANDLE_VALUE) {
|
||||
if (mm->file_handle == Win32MappedMemory::kFileHandleInvalid) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
mm->mapping_handle = CreateFileMapping(mm->file_handle, nullptr,
|
||||
mapping_protect, aligned_length >> 32,
|
||||
aligned_length & 0xFFFFFFFF, nullptr);
|
||||
if (mm->mapping_handle == INVALID_HANDLE_VALUE) {
|
||||
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
|
||||
mm->mapping_handle = CreateFileMapping(
|
||||
mm->file_handle, nullptr, mapping_protect, DWORD(aligned_length >> 32),
|
||||
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;
|
||||
}
|
||||
|
||||
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
|
||||
mm->data_ = reinterpret_cast<uint8_t*>(MapViewOfFile(
|
||||
mm->mapping_handle, view_access, static_cast<DWORD>(aligned_offset >> 32),
|
||||
static_cast<DWORD>(aligned_offset & 0xFFFFFFFF), aligned_length));
|
||||
mm->mapping_handle, view_access, DWORD(aligned_offset >> 32),
|
||||
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_) {
|
||||
return nullptr;
|
||||
}
|
||||
|
@ -203,8 +225,8 @@ class Win32ChunkedMappedMemoryWriter : public ChunkedMappedMemoryWriter {
|
|||
class Chunk {
|
||||
public:
|
||||
explicit Chunk(size_t capacity)
|
||||
: file_handle_(0),
|
||||
mapping_handle_(0),
|
||||
: file_handle_(Win32MappedMemory::kFileHandleInvalid),
|
||||
mapping_handle_(Win32MappedMemory::kMappingHandleInvalid),
|
||||
data_(nullptr),
|
||||
offset_(0),
|
||||
capacity_(capacity),
|
||||
|
@ -214,10 +236,10 @@ class Win32ChunkedMappedMemoryWriter : public ChunkedMappedMemoryWriter {
|
|||
if (data_) {
|
||||
UnmapViewOfFile(data_);
|
||||
}
|
||||
if (mapping_handle_) {
|
||||
if (mapping_handle_ != Win32MappedMemory::kMappingHandleInvalid) {
|
||||
CloseHandle(mapping_handle_);
|
||||
}
|
||||
if (file_handle_) {
|
||||
if (file_handle_ != Win32MappedMemory::kFileHandleInvalid) {
|
||||
CloseHandle(file_handle_);
|
||||
}
|
||||
}
|
||||
|
@ -231,14 +253,20 @@ class Win32ChunkedMappedMemoryWriter : public ChunkedMappedMemoryWriter {
|
|||
|
||||
file_handle_ = CreateFile(path.c_str(), file_access, file_share, nullptr,
|
||||
create_mode, FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||
if (!file_handle_) {
|
||||
if (file_handle_ == Win32MappedMemory::kFileHandleInvalid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
|
||||
mapping_handle_ =
|
||||
CreateFileMapping(file_handle_, nullptr, mapping_protect, 0,
|
||||
static_cast<DWORD>(capacity_), nullptr);
|
||||
if (!mapping_handle_) {
|
||||
CreateFileMapping(file_handle_, nullptr, mapping_protect,
|
||||
DWORD(capacity_ >> 32), DWORD(capacity_), nullptr);
|
||||
#else
|
||||
mapping_handle_ = CreateFileMappingFromApp(file_handle_, nullptr,
|
||||
ULONG(mapping_protect),
|
||||
ULONG64(capacity_), nullptr);
|
||||
#endif
|
||||
if (mapping_handle_ == Win32MappedMemory::kMappingHandleInvalid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -247,10 +275,32 @@ class Win32ChunkedMappedMemoryWriter : public ChunkedMappedMemoryWriter {
|
|||
if (low_address_space) {
|
||||
bool successful = false;
|
||||
data_ = reinterpret_cast<uint8_t*>(0x10000000);
|
||||
#if !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
|
||||
HANDLE process = GetCurrentProcess();
|
||||
#endif
|
||||
for (int i = 0; i < 1000; ++i) {
|
||||
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
|
||||
if (MapViewOfFileEx(mapping_handle_, view_access, 0, 0, capacity_,
|
||||
data_)) {
|
||||
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;
|
||||
}
|
||||
data_ += capacity_;
|
||||
|
@ -261,8 +311,13 @@ class Win32ChunkedMappedMemoryWriter : public ChunkedMappedMemoryWriter {
|
|||
}
|
||||
}
|
||||
} else {
|
||||
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
|
||||
data_ = reinterpret_cast<uint8_t*>(
|
||||
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_) {
|
||||
return false;
|
||||
|
|
|
@ -8,11 +8,26 @@
|
|||
*/
|
||||
|
||||
#include "xenia/base/memory.h"
|
||||
#include "xenia/base/cvar.h"
|
||||
#include "xenia/base/platform.h"
|
||||
|
||||
#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 memory {
|
||||
|
||||
bool IsWritableExecutableMemoryPreferred() {
|
||||
return IsWritableExecutableMemorySupported() &&
|
||||
cvars::writable_executable_memory;
|
||||
}
|
||||
|
||||
} // namespace memory
|
||||
|
||||
// TODO(benvanik): fancy AVX versions.
|
||||
// https://github.com/gnuradio/volk/blob/master/kernels/volk/volk_16u_byteswap.h
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
|
||||
#include "xenia/base/assert.h"
|
||||
#include "xenia/base/byte_order.h"
|
||||
#include "xenia/base/platform.h"
|
||||
|
||||
namespace xe {
|
||||
namespace memory {
|
||||
|
@ -34,6 +35,7 @@ enum class PageAccess {
|
|||
kNoAccess = 0,
|
||||
kReadOnly = 1 << 0,
|
||||
kReadWrite = kReadOnly | 1 << 1,
|
||||
kExecuteReadOnly = kReadOnly | 1 << 2,
|
||||
kExecuteReadWrite = kReadWrite | 1 << 2,
|
||||
};
|
||||
|
||||
|
@ -48,6 +50,16 @@ enum class DeallocationType {
|
|||
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.
|
||||
// Fails if the memory is not available.
|
||||
// Specify nullptr for base_address to leave it up to the system.
|
||||
|
@ -96,12 +108,21 @@ void AlignedFree(T* ptr) {
|
|||
#endif // XE_COMPILER_MSVC
|
||||
}
|
||||
|
||||
#if XE_PLATFORM_WIN32
|
||||
// HANDLE.
|
||||
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,
|
||||
size_t length, PageAccess access,
|
||||
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,
|
||||
PageAccess access, size_t file_offset);
|
||||
bool UnmapFileView(FileMappingHandle handle, void* base_address, size_t length);
|
||||
|
|
|
@ -8,12 +8,23 @@
|
|||
*/
|
||||
|
||||
#include "xenia/base/memory.h"
|
||||
#include "xenia/base/string.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <sys/mman.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 memory {
|
||||
|
||||
|
@ -28,6 +39,8 @@ uint32_t ToPosixProtectFlags(PageAccess access) {
|
|||
return PROT_READ;
|
||||
case PageAccess::kReadWrite:
|
||||
return PROT_READ | PROT_WRITE;
|
||||
case PageAccess::kExecuteReadOnly:
|
||||
return PROT_READ | PROT_EXEC;
|
||||
case PageAccess::kExecuteReadWrite:
|
||||
return PROT_READ | PROT_WRITE | PROT_EXEC;
|
||||
default:
|
||||
|
@ -36,11 +49,19 @@ uint32_t ToPosixProtectFlags(PageAccess access) {
|
|||
}
|
||||
}
|
||||
|
||||
bool IsWritableExecutableMemorySupported() { return true; }
|
||||
|
||||
void* AllocFixed(void* base_address, size_t length,
|
||||
AllocationType allocation_type, PageAccess access) {
|
||||
// mmap does not support reserve / commit, so ignore allocation_type.
|
||||
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,
|
||||
|
@ -64,12 +85,38 @@ bool QueryProtect(void* base_address, size_t& length, PageAccess& access_out) {
|
|||
FileMappingHandle CreateFileMappingHandle(const std::filesystem::path& path,
|
||||
size_t length, PageAccess access,
|
||||
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;
|
||||
switch (access) {
|
||||
case PageAccess::kNoAccess:
|
||||
oflag = 0;
|
||||
break;
|
||||
case PageAccess::kReadOnly:
|
||||
case PageAccess::kExecuteReadOnly:
|
||||
oflag = O_RDONLY;
|
||||
break;
|
||||
case PageAccess::kReadWrite:
|
||||
|
@ -78,27 +125,33 @@ FileMappingHandle CreateFileMappingHandle(const std::filesystem::path& path,
|
|||
break;
|
||||
default:
|
||||
assert_always();
|
||||
return nullptr;
|
||||
return kFileMappingHandleInvalid;
|
||||
}
|
||||
|
||||
oflag |= O_CREAT;
|
||||
int ret = shm_open(path.c_str(), oflag, 0777);
|
||||
if (ret > 0) {
|
||||
ftruncate64(ret, length);
|
||||
auto full_path = "/" / path;
|
||||
int ret = shm_open(full_path.c_str(), oflag, 0777);
|
||||
if (ret < 0) {
|
||||
return kFileMappingHandleInvalid;
|
||||
}
|
||||
|
||||
return ret <= 0 ? nullptr : reinterpret_cast<FileMappingHandle>(ret);
|
||||
ftruncate64(ret, length);
|
||||
return ret;
|
||||
#endif
|
||||
}
|
||||
|
||||
void CloseFileMappingHandle(FileMappingHandle handle) {
|
||||
close((intptr_t)handle);
|
||||
void CloseFileMappingHandle(FileMappingHandle 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,
|
||||
PageAccess access, size_t file_offset) {
|
||||
uint32_t prot = ToPosixProtectFlags(access);
|
||||
return mmap64(base_address, length, prot, MAP_PRIVATE | MAP_ANONYMOUS,
|
||||
reinterpret_cast<intptr_t>(handle), file_offset);
|
||||
return mmap64(base_address, length, prot, MAP_PRIVATE | MAP_ANONYMOUS, handle,
|
||||
file_offset);
|
||||
}
|
||||
|
||||
bool UnmapFileView(FileMappingHandle handle, void* base_address,
|
||||
|
|
|
@ -42,6 +42,8 @@ DWORD ToWin32ProtectFlags(PageAccess access) {
|
|||
return PAGE_READONLY;
|
||||
case PageAccess::kReadWrite:
|
||||
return PAGE_READWRITE;
|
||||
case PageAccess::kExecuteReadOnly:
|
||||
return PAGE_EXECUTE_READ;
|
||||
case PageAccess::kExecuteReadWrite:
|
||||
return PAGE_EXECUTE_READWRITE;
|
||||
default:
|
||||
|
@ -63,6 +65,8 @@ PageAccess ToXeniaProtectFlags(DWORD access) {
|
|||
return PageAccess::kReadOnly;
|
||||
case PAGE_READWRITE:
|
||||
return PageAccess::kReadWrite;
|
||||
case PAGE_EXECUTE_READ:
|
||||
return PageAccess::kExecuteReadOnly;
|
||||
case PAGE_EXECUTE_READWRITE:
|
||||
return PageAccess::kExecuteReadWrite;
|
||||
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,
|
||||
AllocationType allocation_type, PageAccess access) {
|
||||
DWORD alloc_type = 0;
|
||||
|
@ -88,7 +103,12 @@ void* AllocFixed(void* base_address, size_t length,
|
|||
break;
|
||||
}
|
||||
DWORD protect = ToWin32ProtectFlags(access);
|
||||
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
|
||||
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,
|
||||
|
@ -115,13 +135,19 @@ bool Protect(void* base_address, size_t length, PageAccess access,
|
|||
*out_old_access = PageAccess::kNoAccess;
|
||||
}
|
||||
DWORD new_protect = ToWin32ProtectFlags(access);
|
||||
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
|
||||
DWORD old_protect = 0;
|
||||
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) {
|
||||
return false;
|
||||
}
|
||||
if (out_old_access) {
|
||||
*out_old_access = ToXeniaProtectFlags(old_protect);
|
||||
*out_old_access = ToXeniaProtectFlags(DWORD(old_protect));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -147,15 +173,25 @@ FileMappingHandle CreateFileMappingHandle(const std::filesystem::path& path,
|
|||
bool commit) {
|
||||
DWORD protect =
|
||||
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), 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,
|
||||
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_high = static_cast<DWORD>(file_offset >> 32);
|
||||
DWORD file_access = 0;
|
||||
|
@ -166,6 +202,9 @@ void* MapFileView(FileMappingHandle handle, void* base_address, size_t length,
|
|||
case PageAccess::kReadWrite:
|
||||
file_access = FILE_MAP_ALL_ACCESS;
|
||||
break;
|
||||
case PageAccess::kExecuteReadOnly:
|
||||
file_access = FILE_MAP_READ | FILE_MAP_EXECUTE;
|
||||
break;
|
||||
case PageAccess::kExecuteReadWrite:
|
||||
file_access = FILE_MAP_ALL_ACCESS | FILE_MAP_EXECUTE;
|
||||
break;
|
||||
|
@ -176,6 +215,25 @@ void* MapFileView(FileMappingHandle handle, void* base_address, size_t length,
|
|||
}
|
||||
return MapViewOfFileEx(handle, file_access, target_address_high,
|
||||
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,
|
||||
|
|
|
@ -31,8 +31,14 @@
|
|||
#define XE_PLATFORM_MAC 1
|
||||
#elif defined(WIN32) || defined(_WIN32)
|
||||
#define XE_PLATFORM_WIN32 1
|
||||
#else
|
||||
#elif defined(__ANDROID__)
|
||||
#define XE_PLATFORM_ANDROID 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
|
||||
|
||||
#if defined(__clang__)
|
||||
|
@ -51,8 +57,11 @@
|
|||
|
||||
#if defined(_M_AMD64) || defined(__amd64__)
|
||||
#define XE_ARCH_AMD64 1
|
||||
#elif defined(_M_IX86)
|
||||
#error "Xenia is not supported on 32-bit platforms."
|
||||
#elif defined(_M_ARM64) || defined(__aarch64__)
|
||||
#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__)
|
||||
#define XE_ARCH_PPC 1
|
||||
#endif
|
||||
|
|
|
@ -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
|
|
@ -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_
|
|
@ -15,7 +15,7 @@ namespace xe {
|
|||
|
||||
void LaunchWebBrowser(const std::string& 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);
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,9 @@
|
|||
#include "xenia/base/memory.h"
|
||||
|
||||
#include "third_party/catch/include/catch.hpp"
|
||||
#include "third_party/fmt/include/fmt/format.h"
|
||||
|
||||
#include "xenia/base/clock.h"
|
||||
|
||||
namespace xe {
|
||||
namespace base {
|
||||
|
@ -414,6 +417,58 @@ TEST_CASE("copy_and_swap_16_in_32_unaligned", "Copy and Swap") {
|
|||
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 base
|
||||
} // namespace xe
|
||||
|
|
|
@ -174,8 +174,8 @@ TEST_CASE("HighResolutionTimer") {
|
|||
{
|
||||
const auto interval1 = 100ms;
|
||||
const auto interval2 = 200ms;
|
||||
std::atomic<uint64_t> counter1;
|
||||
std::atomic<uint64_t> counter2;
|
||||
std::atomic<uint64_t> counter1(0);
|
||||
std::atomic<uint64_t> counter2(0);
|
||||
auto start = std::chrono::steady_clock::now();
|
||||
auto cb1 = [&counter1] { ++counter1; };
|
||||
auto cb2 = [&counter2] { ++counter2; };
|
||||
|
@ -813,7 +813,7 @@ TEST_CASE("Create and Run Thread", "Thread") {
|
|||
result = Wait(Thread::GetCurrentThread(), false, 50ms);
|
||||
REQUIRE(result == WaitResult::kTimeout);
|
||||
|
||||
params.stack_size = 16 * 1024;
|
||||
params.stack_size = 16 * 1024 * 1024;
|
||||
thread = Thread::Create(params, [] {
|
||||
while (true) {
|
||||
Thread::Exit(-1);
|
||||
|
|
|
@ -95,9 +95,6 @@ void set_current_thread_id(uint32_t id);
|
|||
|
||||
// Sets the current thread 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.
|
||||
void MaybeYield();
|
||||
|
|
|
@ -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
|
|
@ -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(std::thread::native_handle_type handle, const std::string& name) {
|
||||
// ?
|
||||
}
|
||||
|
||||
void MaybeYield() { pthread_yield_np(); }
|
||||
|
||||
void Sleep(std::chrono::microseconds duration) {
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
#include "xenia/base/assert.h"
|
||||
#include "xenia/base/logging.h"
|
||||
#include "xenia/base/platform.h"
|
||||
|
||||
#include <pthread.h>
|
||||
#include <signal.h>
|
||||
|
@ -19,9 +20,18 @@
|
|||
#include <sys/time.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include <ctime>
|
||||
#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 threading {
|
||||
|
||||
|
@ -43,6 +53,12 @@ enum class SignalType {
|
|||
kTimer,
|
||||
kThreadSuspend,
|
||||
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
|
||||
};
|
||||
|
||||
|
@ -80,15 +96,12 @@ uint32_t current_thread_system_id() {
|
|||
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() {
|
||||
#if XE_PLATFORM_ANDROID
|
||||
sched_yield();
|
||||
#else
|
||||
pthread_yield();
|
||||
#endif
|
||||
__sync_synchronize();
|
||||
}
|
||||
|
||||
|
@ -482,7 +495,11 @@ class PosixCondition<Thread> : public PosixConditionBase {
|
|||
signaled_(false),
|
||||
exit_code_(0),
|
||||
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,
|
||||
ThreadStartData* start_data) {
|
||||
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) {
|
||||
pthread_attr_destroy(&attr);
|
||||
return false;
|
||||
}
|
||||
pthread_attr_destroy(&attr);
|
||||
|
@ -517,13 +535,24 @@ class PosixCondition<Thread> : public PosixConditionBase {
|
|||
: thread_(thread),
|
||||
signaled_(false),
|
||||
exit_code_(0),
|
||||
state_(State::kRunning) {}
|
||||
state_(State::kRunning) {
|
||||
#if XE_PLATFORM_ANDROID
|
||||
android_pre_api_26_name_[0] = '\0';
|
||||
#endif
|
||||
}
|
||||
|
||||
virtual ~PosixCondition() {
|
||||
if (thread_ && !signaled_) {
|
||||
#if XE_PLATFORM_ANDROID
|
||||
if (pthread_kill(thread_,
|
||||
GetSystemSignal(SignalType::kThreadTerminate)) != 0) {
|
||||
assert_always();
|
||||
}
|
||||
#else
|
||||
if (pthread_cancel(thread_) != 0) {
|
||||
assert_always();
|
||||
}
|
||||
#endif
|
||||
if (pthread_join(thread_, nullptr) != 0) {
|
||||
assert_always();
|
||||
}
|
||||
|
@ -537,8 +566,24 @@ class PosixCondition<Thread> : public PosixConditionBase {
|
|||
auto result = std::array<char, 17>{'\0'};
|
||||
std::unique_lock<std::mutex> lock(state_mutex_);
|
||||
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();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
return std::string(result.data());
|
||||
}
|
||||
|
@ -547,18 +592,39 @@ class PosixCondition<Thread> : public PosixConditionBase {
|
|||
WaitStarted();
|
||||
std::unique_lock<std::mutex> lock(state_mutex_);
|
||||
if (state_ != State::kUninitialized && state_ != State::kFinished) {
|
||||
threading::set_name(static_cast<std::thread::native_handle_type>(thread_),
|
||||
name);
|
||||
pthread_setname_np(thread_, std::string(name).c_str());
|
||||
#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_); }
|
||||
|
||||
uint64_t affinity_mask() {
|
||||
WaitStarted();
|
||||
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();
|
||||
}
|
||||
#else
|
||||
if (pthread_getaffinity_np(thread_, sizeof(cpu_set_t), &cpu_set) != 0) {
|
||||
assert_always();
|
||||
}
|
||||
#endif
|
||||
uint64_t result = 0;
|
||||
auto cpu_count = std::min(CPU_SETSIZE, 64);
|
||||
for (auto i = 0u; i < cpu_count; i++) {
|
||||
|
@ -577,9 +643,16 @@ class PosixCondition<Thread> : public PosixConditionBase {
|
|||
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) {
|
||||
assert_always();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
int priority() {
|
||||
|
@ -608,8 +681,13 @@ class PosixCondition<Thread> : public PosixConditionBase {
|
|||
user_callback_ = std::move(callback);
|
||||
sigval value{};
|
||||
value.sival_ptr = this;
|
||||
#if XE_PLATFORM_ANDROID
|
||||
sigqueue(pthread_gettid_np(thread_),
|
||||
GetSystemSignal(SignalType::kThreadUserCallback), value);
|
||||
#else
|
||||
pthread_sigqueue(thread_, GetSystemSignal(SignalType::kThreadUserCallback),
|
||||
value);
|
||||
#endif
|
||||
}
|
||||
|
||||
void CallUserCallback() {
|
||||
|
@ -665,7 +743,16 @@ class PosixCondition<Thread> : public PosixConditionBase {
|
|||
signaled_ = true;
|
||||
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 {
|
||||
|
@ -703,6 +790,12 @@ class PosixCondition<Thread> : public PosixConditionBase {
|
|||
mutable std::mutex callback_mutex_;
|
||||
mutable std::condition_variable state_signal_;
|
||||
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 {
|
||||
|
@ -722,7 +815,7 @@ class PosixConditionHandle : public T, public PosixWaitHandle {
|
|||
PosixConditionHandle(uint32_t initial_count, uint32_t maximum_count);
|
||||
~PosixConditionHandle() override = default;
|
||||
|
||||
PosixConditionBase& condition() override { return handle_; }
|
||||
PosixCondition<T>& condition() override { return handle_; }
|
||||
void* native_handle() const override { return handle_.native_handle(); }
|
||||
|
||||
protected:
|
||||
|
@ -941,9 +1034,11 @@ class PosixThread : public PosixConditionHandle<Thread> {
|
|||
thread_local PosixThread* current_thread_ = nullptr;
|
||||
|
||||
void* PosixCondition<Thread>::ThreadStartRoutine(void* parameter) {
|
||||
#if !XE_PLATFORM_ANDROID
|
||||
if (pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, nullptr) != 0) {
|
||||
assert_always();
|
||||
}
|
||||
#endif
|
||||
threading::set_name("");
|
||||
|
||||
auto start_data = static_cast<ThreadStartData*>(parameter);
|
||||
|
@ -990,6 +1085,9 @@ std::unique_ptr<Thread> Thread::Create(CreationParameters params,
|
|||
std::function<void()> start_routine) {
|
||||
install_signal_handler(SignalType::kThreadSuspend);
|
||||
install_signal_handler(SignalType::kThreadUserCallback);
|
||||
#if XE_PLATFORM_ANDROID
|
||||
install_signal_handler(SignalType::kThreadTerminate);
|
||||
#endif
|
||||
auto thread = std::make_unique<PosixThread>();
|
||||
if (!thread->Initialize(params, std::move(start_routine))) return nullptr;
|
||||
assert_not_null(thread);
|
||||
|
@ -1006,7 +1104,10 @@ Thread* Thread::GetCurrentThread() {
|
|||
pthread_t handle = pthread_self();
|
||||
|
||||
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_;
|
||||
}
|
||||
|
@ -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*/) {
|
||||
switch (GetSystemSignalType(signal)) {
|
||||
case SignalType::kHighResolutionTimer: {
|
||||
|
@ -1049,6 +1159,11 @@ static void signal_handler(int signal, siginfo_t* info, void* /*context*/) {
|
|||
p_thread->CallUserCallback();
|
||||
}
|
||||
} break;
|
||||
#if XE_PLATFORM_ANDROID
|
||||
case SignalType::kThreadTerminate: {
|
||||
pthread_exit(reinterpret_cast<void*>(-1));
|
||||
} break;
|
||||
#endif
|
||||
default:
|
||||
assert_always();
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
if (kernel) {
|
||||
auto func =
|
||||
|
|
|
@ -10,6 +10,8 @@
|
|||
#ifndef XENIA_CPU_BACKEND_CODE_CACHE_H_
|
||||
#define XENIA_CPU_BACKEND_CODE_CACHE_H_
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
#include "xenia/cpu/function.h"
|
||||
|
@ -24,8 +26,8 @@ class CodeCache {
|
|||
virtual ~CodeCache() = default;
|
||||
|
||||
virtual const std::filesystem::path& file_name() const = 0;
|
||||
virtual uint32_t base_address() const = 0;
|
||||
virtual uint32_t total_size() const = 0;
|
||||
virtual uintptr_t execute_base_address() const = 0;
|
||||
virtual size_t total_size() const = 0;
|
||||
|
||||
// Finds a function based on the given host PC (that may be within a
|
||||
// function).
|
||||
|
|
|
@ -40,11 +40,18 @@ X64CodeCache::~X64CodeCache() {
|
|||
}
|
||||
|
||||
// Unmap all views and close mapping.
|
||||
if (mapping_) {
|
||||
xe::memory::UnmapFileView(mapping_, generated_code_base_,
|
||||
kGeneratedCodeSize);
|
||||
xe::memory::CloseFileMappingHandle(mapping_);
|
||||
mapping_ = nullptr;
|
||||
if (mapping_ != xe::memory::kFileMappingHandleInvalid) {
|
||||
if (generated_code_write_base_ &&
|
||||
generated_code_write_base_ != generated_code_execute_base_) {
|
||||
xe::memory::UnmapFileView(mapping_, generated_code_write_base_,
|
||||
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.
|
||||
file_name_ =
|
||||
fmt::format("Local\\xenia_code_cache_{}", Clock::QueryHostTickCount());
|
||||
file_name_ = fmt::format("xenia_code_cache_{}", Clock::QueryHostTickCount());
|
||||
mapping_ = xe::memory::CreateFileMappingHandle(
|
||||
file_name_, kGeneratedCodeSize, xe::memory::PageAccess::kExecuteReadWrite,
|
||||
false);
|
||||
if (!mapping_) {
|
||||
if (mapping_ == xe::memory::kFileMappingHandleInvalid) {
|
||||
XELOGE("Unable to create code cache mmap");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Map generated code region into the file. Pages are committed as required.
|
||||
generated_code_base_ = reinterpret_cast<uint8_t*>(xe::memory::MapFileView(
|
||||
mapping_, reinterpret_cast<void*>(kGeneratedCodeBase), kGeneratedCodeSize,
|
||||
xe::memory::PageAccess::kExecuteReadWrite, 0));
|
||||
if (!generated_code_base_) {
|
||||
XELOGE("Unable to allocate code cache generated code storage");
|
||||
XELOGE(
|
||||
"This is likely because the {:X}-{:X} range is in use by some other "
|
||||
"system DLL",
|
||||
static_cast<uint64_t>(kGeneratedCodeBase),
|
||||
kGeneratedCodeBase + kGeneratedCodeSize);
|
||||
return false;
|
||||
if (xe::memory::IsWritableExecutableMemoryPreferred()) {
|
||||
generated_code_execute_base_ =
|
||||
reinterpret_cast<uint8_t*>(xe::memory::MapFileView(
|
||||
mapping_, reinterpret_cast<void*>(kGeneratedCodeExecuteBase),
|
||||
kGeneratedCodeSize, xe::memory::PageAccess::kExecuteReadWrite, 0));
|
||||
generated_code_write_base_ = generated_code_execute_base_;
|
||||
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} range is in use by some other "
|
||||
"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.
|
||||
|
@ -118,7 +148,7 @@ void X64CodeCache::CommitExecutableRange(uint32_t guest_low,
|
|||
xe::memory::AllocFixed(
|
||||
indirection_table_base_ + (guest_low - kIndirectionTableBase),
|
||||
guest_high - guest_low, xe::memory::AllocationType::kCommit,
|
||||
xe::memory::PageAccess::kExecuteReadWrite);
|
||||
xe::memory::PageAccess::kReadWrite);
|
||||
|
||||
// Fill memory with the default value.
|
||||
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,
|
||||
const EmitFunctionInfo& func_info) {
|
||||
void X64CodeCache::PlaceHostCode(uint32_t guest_address, void* machine_code,
|
||||
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
|
||||
// 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,
|
||||
const EmitFunctionInfo& func_info,
|
||||
GuestFunction* function_info) {
|
||||
void X64CodeCache::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) {
|
||||
// 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.
|
||||
size_t low_mark;
|
||||
size_t high_mark;
|
||||
uint8_t* code_address;
|
||||
uint8_t* code_execute_address;
|
||||
UnwindReservation unwind_reservation;
|
||||
{
|
||||
auto global_lock = global_critical_region_.Acquire();
|
||||
|
@ -150,26 +185,33 @@ void* X64CodeCache::PlaceGuestCode(uint32_t guest_address, void* machine_code,
|
|||
|
||||
// Reserve code.
|
||||
// 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);
|
||||
|
||||
auto tail_address = generated_code_base_ + generated_code_offset_;
|
||||
auto tail_write_address =
|
||||
generated_code_write_base_ + generated_code_offset_;
|
||||
|
||||
// Reserve unwind info.
|
||||
// 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.
|
||||
unwind_reservation =
|
||||
RequestUnwindReservation(generated_code_base_ + generated_code_offset_);
|
||||
unwind_reservation = RequestUnwindReservation(generated_code_write_base_ +
|
||||
generated_code_offset_);
|
||||
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_;
|
||||
|
||||
// Store in map. It is maintained in sorted order of host PC dependent on
|
||||
// us also being append-only.
|
||||
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_,
|
||||
function_info);
|
||||
|
||||
|
@ -186,21 +228,30 @@ void* X64CodeCache::PlaceGuestCode(uint32_t guest_address, void* machine_code,
|
|||
if (high_mark <= old_commit_mark) break;
|
||||
|
||||
new_commit_mark = old_commit_mark + 16 * 1024 * 1024;
|
||||
xe::memory::AllocFixed(generated_code_base_, new_commit_mark,
|
||||
xe::memory::AllocationType::kCommit,
|
||||
xe::memory::PageAccess::kExecuteReadWrite);
|
||||
if (generated_code_execute_base_ == generated_code_write_base_) {
|
||||
xe::memory::AllocFixed(generated_code_execute_base_, new_commit_mark,
|
||||
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, new_commit_mark));
|
||||
|
||||
// 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
|
||||
std::memset(tail_address, 0xCC,
|
||||
static_cast<size_t>(end_address - tail_address));
|
||||
std::memset(tail_write_address, 0xCC,
|
||||
static_cast<size_t>(end_write_address - tail_write_address));
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
|
@ -215,7 +266,7 @@ void* X64CodeCache::PlaceGuestCode(uint32_t guest_address, void* machine_code,
|
|||
|
||||
iJIT_Method_Load_V2 method = {0};
|
||||
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_name = const_cast<char*>(method_name.data());
|
||||
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_) {
|
||||
uint32_t* indirection_slot = reinterpret_cast<uint32_t*>(
|
||||
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) {
|
||||
|
@ -246,7 +296,7 @@ uint32_t X64CodeCache::PlaceData(const void* data, size_t length) {
|
|||
|
||||
// Reserve code.
|
||||
// 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);
|
||||
|
||||
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;
|
||||
|
||||
new_commit_mark = old_commit_mark + 16 * 1024 * 1024;
|
||||
xe::memory::AllocFixed(generated_code_base_, new_commit_mark,
|
||||
xe::memory::AllocationType::kCommit,
|
||||
xe::memory::PageAccess::kExecuteReadWrite);
|
||||
if (generated_code_execute_base_ == generated_code_write_base_) {
|
||||
xe::memory::AllocFixed(generated_code_execute_base_, new_commit_mark,
|
||||
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,
|
||||
new_commit_mark));
|
||||
|
||||
|
@ -274,7 +333,7 @@ uint32_t X64CodeCache::PlaceData(const void* data, size_t length) {
|
|||
}
|
||||
|
||||
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(
|
||||
&key, generated_code_map_.data(), generated_code_map_.size() + 1,
|
||||
sizeof(std::pair<uint32_t, Function*>),
|
||||
|
|
|
@ -11,6 +11,8 @@
|
|||
#define XENIA_CPU_BACKEND_X64_X64_CODE_CACHE_H_
|
||||
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
@ -46,8 +48,10 @@ class X64CodeCache : public CodeCache {
|
|||
virtual bool Initialize();
|
||||
|
||||
const std::filesystem::path& file_name() const override { return file_name_; }
|
||||
uint32_t base_address() const override { return kGeneratedCodeBase; }
|
||||
uint32_t total_size() const override { return kGeneratedCodeSize; }
|
||||
uintptr_t execute_base_address() const override {
|
||||
return kGeneratedCodeExecuteBase;
|
||||
}
|
||||
size_t total_size() const override { return kGeneratedCodeSize; }
|
||||
|
||||
// TODO(benvanik): ELF serialization/etc
|
||||
// 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* PlaceHostCode(uint32_t guest_address, void* machine_code,
|
||||
const EmitFunctionInfo& func_info);
|
||||
void* PlaceGuestCode(uint32_t guest_address, void* machine_code,
|
||||
const EmitFunctionInfo& func_info,
|
||||
GuestFunction* function_info);
|
||||
void PlaceHostCode(uint32_t guest_address, void* machine_code,
|
||||
const EmitFunctionInfo& func_info,
|
||||
void*& code_execute_address_out,
|
||||
void*& code_write_address_out);
|
||||
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);
|
||||
|
||||
GuestFunction* LookupFunction(uint64_t host_pc) override;
|
||||
|
@ -71,13 +79,16 @@ class X64CodeCache : public CodeCache {
|
|||
protected:
|
||||
// All executable code falls within 0x80000000 to 0x9FFFFFFF, so we can
|
||||
// only map enough for lookups within that range.
|
||||
static const uint64_t kIndirectionTableBase = 0x80000000;
|
||||
static const uint64_t kIndirectionTableSize = 0x1FFFFFFF;
|
||||
static const size_t kIndirectionTableSize = 0x1FFFFFFF;
|
||||
static const uintptr_t kIndirectionTableBase = 0x80000000;
|
||||
// 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
|
||||
// so 256MB should be more than enough.
|
||||
static const uint64_t kGeneratedCodeBase = 0xA0000000;
|
||||
static const uint64_t kGeneratedCodeSize = 0x0FFFFFFF;
|
||||
static const size_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
|
||||
// expect. If we hit issues with this it probably means some corner case
|
||||
|
@ -96,11 +107,13 @@ class X64CodeCache : public CodeCache {
|
|||
return UnwindReservation();
|
||||
}
|
||||
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) {}
|
||||
|
||||
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
|
||||
// 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
|
||||
// space.
|
||||
uint8_t* indirection_table_base_ = nullptr;
|
||||
// Fixed at kGeneratedCodeBase and holding all generated code, growing as
|
||||
// needed.
|
||||
uint8_t* generated_code_base_ = nullptr;
|
||||
// Fixed at kGeneratedCodeExecuteBase and holding all generated code, growing
|
||||
// as needed.
|
||||
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.
|
||||
size_t generated_code_offset_ = 0;
|
||||
// Current high water mark of COMMITTED code.
|
||||
|
|
|
@ -27,7 +27,7 @@ class PosixX64CodeCache : public X64CodeCache {
|
|||
/*
|
||||
UnwindReservation RequestUnwindReservation(uint8_t* entry_address) override;
|
||||
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;
|
||||
|
||||
void InitializeUnwindEntry(uint8_t* unwind_entry_address,
|
||||
|
|
|
@ -107,11 +107,12 @@ class Win32X64CodeCache : public X64CodeCache {
|
|||
private:
|
||||
UnwindReservation RequestUnwindReservation(uint8_t* entry_address) override;
|
||||
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;
|
||||
|
||||
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);
|
||||
|
||||
// Growable function table system handle.
|
||||
|
@ -140,9 +141,9 @@ Win32X64CodeCache::~Win32X64CodeCache() {
|
|||
delete_growable_table_(unwind_table_handle_);
|
||||
}
|
||||
} else {
|
||||
if (generated_code_base_) {
|
||||
if (generated_code_execute_base_) {
|
||||
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
|
||||
// it as functions are added.
|
||||
if (supports_growable_table_) {
|
||||
if (add_growable_table_(&unwind_table_handle_, unwind_table_.data(),
|
||||
unwind_table_count_, DWORD(unwind_table_.size()),
|
||||
reinterpret_cast<ULONG_PTR>(generated_code_base_),
|
||||
reinterpret_cast<ULONG_PTR>(generated_code_base_ +
|
||||
kGeneratedCodeSize))) {
|
||||
if (add_growable_table_(
|
||||
&unwind_table_handle_, unwind_table_.data(), unwind_table_count_,
|
||||
DWORD(unwind_table_.size()),
|
||||
reinterpret_cast<ULONG_PTR>(generated_code_execute_base_),
|
||||
reinterpret_cast<ULONG_PTR>(generated_code_execute_base_ +
|
||||
kGeneratedCodeSize))) {
|
||||
XELOGE("Unable to create unwind function table");
|
||||
return false;
|
||||
}
|
||||
|
@ -188,8 +190,9 @@ bool Win32X64CodeCache::Initialize() {
|
|||
// Install a callback that the debugger will use to lookup unwind info on
|
||||
// demand.
|
||||
if (!RtlInstallFunctionTableCallback(
|
||||
reinterpret_cast<DWORD64>(generated_code_base_) | 0x3,
|
||||
reinterpret_cast<DWORD64>(generated_code_base_), kGeneratedCodeSize,
|
||||
reinterpret_cast<DWORD64>(generated_code_execute_base_) | 0x3,
|
||||
reinterpret_cast<DWORD64>(generated_code_execute_base_),
|
||||
kGeneratedCodeSize,
|
||||
[](DWORD64 control_pc, PVOID context) {
|
||||
auto code_cache = reinterpret_cast<Win32X64CodeCache*>(context);
|
||||
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,
|
||||
const EmitFunctionInfo& func_info,
|
||||
void* code_address,
|
||||
void* code_execute_address,
|
||||
UnwindReservation unwind_reservation) {
|
||||
// Add unwind info.
|
||||
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_) {
|
||||
// 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.
|
||||
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);
|
||||
}
|
||||
|
||||
void Win32X64CodeCache::InitializeUnwindEntry(
|
||||
uint8_t* unwind_entry_address, size_t unwind_table_slot, void* code_address,
|
||||
const EmitFunctionInfo& func_info) {
|
||||
uint8_t* unwind_entry_address, size_t unwind_table_slot,
|
||||
void* code_execute_address, const EmitFunctionInfo& func_info) {
|
||||
auto unwind_info = reinterpret_cast<UNWIND_INFO*>(unwind_entry_address);
|
||||
UNWIND_CODE* unwind_code = nullptr;
|
||||
|
||||
|
@ -299,10 +305,12 @@ void Win32X64CodeCache::InitializeUnwindEntry(
|
|||
// Add entry.
|
||||
auto& fn_entry = unwind_table_[unwind_table_slot];
|
||||
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 =
|
||||
(DWORD)(fn_entry.BeginAddress + func_info.code_size.total);
|
||||
fn_entry.UnwindData = (DWORD)(unwind_entry_address - generated_code_base_);
|
||||
DWORD(fn_entry.BeginAddress + func_info.code_size.total);
|
||||
fn_entry.UnwindData =
|
||||
DWORD(unwind_entry_address - generated_code_execute_base_);
|
||||
}
|
||||
|
||||
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_,
|
||||
sizeof(RUNTIME_FUNCTION),
|
||||
[](const void* key_ptr, const void* element_ptr) {
|
||||
auto key =
|
||||
*reinterpret_cast<const uintptr_t*>(key_ptr) - kGeneratedCodeBase;
|
||||
auto key = *reinterpret_cast<const uintptr_t*>(key_ptr) -
|
||||
kGeneratedCodeExecuteBase;
|
||||
auto element = reinterpret_cast<const RUNTIME_FUNCTION*>(element_ptr);
|
||||
if (key < element->BeginAddress) {
|
||||
return -1;
|
||||
|
|
|
@ -125,20 +125,26 @@ void* X64Emitter::Emplace(const EmitFunctionInfo& func_info,
|
|||
// 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
|
||||
// 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_;
|
||||
void* new_address;
|
||||
void* new_execute_address;
|
||||
void* new_write_address;
|
||||
assert_true(func_info.code_size.total == size_);
|
||||
if (function) {
|
||||
new_address = code_cache_->PlaceGuestCode(function->address(), top_,
|
||||
func_info, function);
|
||||
code_cache_->PlaceGuestCode(function->address(), top_, func_info, function,
|
||||
new_execute_address, new_write_address);
|
||||
} 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();
|
||||
top_ = old_address;
|
||||
reset();
|
||||
return new_address;
|
||||
return new_execute_address;
|
||||
}
|
||||
|
||||
bool X64Emitter::Emit(HIRBuilder* builder, EmitFunctionInfo& func_info) {
|
||||
|
|
|
@ -177,6 +177,9 @@ class TestRunner {
|
|||
public:
|
||||
TestRunner() {
|
||||
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::AllocationType::kReserveCommit,
|
||||
memory::PageAccess::kExecuteReadWrite);
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
#include "xenia/cpu/stack_walker.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <mutex>
|
||||
|
||||
#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
|
||||
// threads.
|
||||
code_cache_ = code_cache;
|
||||
code_cache_min_ = code_cache_->base_address();
|
||||
code_cache_max_ = code_cache_->base_address() + code_cache_->total_size();
|
||||
code_cache_min_ = code_cache_->execute_base_address();
|
||||
code_cache_max_ = code_cache_min_ + code_cache_->total_size();
|
||||
}
|
||||
|
||||
bool Initialize() {
|
||||
|
@ -297,13 +298,13 @@ class Win32StackWalker : public StackWalker {
|
|||
std::mutex dbghelp_mutex_;
|
||||
|
||||
static xe::cpu::backend::CodeCache* code_cache_;
|
||||
static uint32_t code_cache_min_;
|
||||
static uint32_t code_cache_max_;
|
||||
static uintptr_t code_cache_min_;
|
||||
static uintptr_t code_cache_max_;
|
||||
};
|
||||
|
||||
xe::cpu::backend::CodeCache* Win32StackWalker::code_cache_ = nullptr;
|
||||
uint32_t Win32StackWalker::code_cache_min_ = 0;
|
||||
uint32_t Win32StackWalker::code_cache_max_ = 0;
|
||||
uintptr_t Win32StackWalker::code_cache_min_ = 0;
|
||||
uintptr_t Win32StackWalker::code_cache_max_ = 0;
|
||||
|
||||
std::unique_ptr<StackWalker> StackWalker::Create(
|
||||
backend::CodeCache* code_cache) {
|
||||
|
|
|
@ -1454,7 +1454,7 @@ void DebugWindow::UpdateCache() {
|
|||
// Fetch module listing.
|
||||
// We hold refs so that none are unloaded.
|
||||
cache_.modules =
|
||||
object_table->GetObjectsByType<XModule>(XObject::Type::kTypeModule);
|
||||
object_table->GetObjectsByType<XModule>(XObject::Type::Module);
|
||||
|
||||
cache_.thread_debug_infos = processor_->QueryThreadDebugInfos();
|
||||
|
||||
|
|
|
@ -394,7 +394,7 @@ void Emulator::Pause() {
|
|||
auto lock = global_critical_region::AcquireDirect();
|
||||
auto threads =
|
||||
kernel_state()->object_table()->GetObjectsByType<kernel::XThread>(
|
||||
kernel::XObject::kTypeThread);
|
||||
kernel::XObject::Type::Thread);
|
||||
auto current_thread = kernel::XThread::IsInThread()
|
||||
? kernel::XThread::GetCurrentThread()
|
||||
: nullptr;
|
||||
|
@ -424,7 +424,7 @@ void Emulator::Resume() {
|
|||
|
||||
auto threads =
|
||||
kernel_state()->object_table()->GetObjectsByType<kernel::XThread>(
|
||||
kernel::XObject::kTypeThread);
|
||||
kernel::XObject::Type::Thread);
|
||||
for (auto thread : threads) {
|
||||
if (!thread->can_debugger_suspend()) {
|
||||
// Don't pause host threads.
|
||||
|
@ -578,7 +578,7 @@ bool Emulator::ExceptionCallbackThunk(Exception* ex, void* data) {
|
|||
bool Emulator::ExceptionCallback(Exception* ex) {
|
||||
// Check to see if the exception occurred in guest code.
|
||||
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();
|
||||
|
||||
if (!processor()->is_debugger_attached() && debugging::IsDebuggerAttached()) {
|
||||
|
|
|
@ -1182,9 +1182,11 @@ bool CommandProcessor::ExecutePacketType3_DRAW_INDX(RingBuffer* reader,
|
|||
// initiate fetch of index buffer and draw
|
||||
// if dword0 != 0, this is a conditional draw based on 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 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;
|
||||
vgt_draw_initiator.value = reader->ReadAndSwap<uint32_t>();
|
||||
WriteRegister(XE_GPU_REG_VGT_DRAW_INITIATOR, vgt_draw_initiator.value);
|
||||
|
@ -1221,6 +1223,14 @@ bool CommandProcessor::ExecutePacketType3_DRAW_INDX(RingBuffer* reader,
|
|||
} 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 =
|
||||
IssueDraw(vgt_draw_initiator.prim_type, vgt_draw_initiator.num_indices,
|
||||
is_indexed ? &index_buffer_info : nullptr,
|
||||
|
@ -1254,6 +1264,14 @@ bool CommandProcessor::ExecutePacketType3_DRAW_INDX_2(RingBuffer* reader,
|
|||
// TODO(Triang3l): VGT_IMMED_DATA.
|
||||
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(
|
||||
vgt_draw_initiator.prim_type, vgt_draw_initiator.num_indices, nullptr,
|
||||
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 id = dword0 & 0x3F;
|
||||
uint32_t end = dword0 & 0x80;
|
||||
uint32_t end = dword0 & 0x100;
|
||||
if (!end) {
|
||||
// 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);
|
||||
XELOGGPU("Begin viz query ID {:02X}", id);
|
||||
} else {
|
||||
// end the viz query
|
||||
WriteRegister(XE_GPU_REG_VGT_EVENT_INITIATOR, VIZQUERY_END);
|
||||
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;
|
||||
|
|
|
@ -1876,14 +1876,14 @@ bool D3D12CommandProcessor::IssueDraw(xenos::PrimitiveType primitive_type,
|
|||
!pixel_shader->memexport_stream_constants().empty();
|
||||
bool memexport_used = memexport_used_vertex || memexport_used_pixel;
|
||||
|
||||
bool primitive_two_faced =
|
||||
xenos::IsPrimitiveTwoFaced(tessellated, primitive_type);
|
||||
bool primitive_polygonal =
|
||||
xenos::IsPrimitivePolygonal(tessellated, primitive_type);
|
||||
auto sq_program_cntl = regs.Get<reg::SQ_PROGRAM_CNTL>();
|
||||
auto pa_su_sc_mode_cntl = regs.Get<reg::PA_SU_SC_MODE_CNTL>();
|
||||
if (!memexport_used_vertex &&
|
||||
(sq_program_cntl.vs_export_mode ==
|
||||
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))) {
|
||||
// All faces are culled - can't be expressed in the pipeline.
|
||||
return true;
|
||||
|
@ -1996,15 +1996,45 @@ bool D3D12CommandProcessor::IssueDraw(xenos::PrimitiveType primitive_type,
|
|||
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.
|
||||
UpdateFixedFunctionState(primitive_two_faced);
|
||||
UpdateFixedFunctionState(viewport_info, scissor, primitive_polygonal);
|
||||
|
||||
// Update system constants before uploading them.
|
||||
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,
|
||||
used_texture_mask, early_z, GetCurrentColorMask(pixel_shader),
|
||||
pipeline_render_targets);
|
||||
viewport_info, pixel_size_x, pixel_size_y, used_texture_mask, early_z,
|
||||
GetCurrentColorMask(pixel_shader), pipeline_render_targets);
|
||||
|
||||
// Update constant buffers, descriptors and root parameters.
|
||||
if (!UpdateBindings(vertex_shader, pixel_shader, root_signature)) {
|
||||
|
@ -2753,87 +2783,21 @@ void D3D12CommandProcessor::ClearCommandAllocatorCache() {
|
|||
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
|
||||
SCOPE_profile_cpu_f("gpu");
|
||||
#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.
|
||||
// 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;
|
||||
viewport.TopLeftX =
|
||||
(viewport_offset_x - viewport_scale_x) * float(pixel_size_x);
|
||||
viewport.TopLeftY =
|
||||
(viewport_offset_y - viewport_scale_y) * float(pixel_size_y);
|
||||
viewport.Width = viewport_scale_x * 2.0f * float(pixel_size_x);
|
||||
viewport.Height = viewport_scale_y * 2.0f * float(pixel_size_y);
|
||||
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);
|
||||
}
|
||||
viewport.TopLeftX = viewport_info.left;
|
||||
viewport.TopLeftY = viewport_info.top;
|
||||
viewport.Width = viewport_info.width;
|
||||
viewport.Height = viewport_info.height;
|
||||
viewport.MinDepth = viewport_info.z_min;
|
||||
viewport.MaxDepth = viewport_info.z_max;
|
||||
ff_viewport_update_needed_ |= ff_viewport_.TopLeftX != viewport.TopLeftX;
|
||||
ff_viewport_update_needed_ |= ff_viewport_.TopLeftY != viewport.TopLeftY;
|
||||
ff_viewport_update_needed_ |= ff_viewport_.Width != viewport.Width;
|
||||
|
@ -2847,13 +2811,11 @@ void D3D12CommandProcessor::UpdateFixedFunctionState(bool primitive_two_faced) {
|
|||
}
|
||||
|
||||
// Scissor.
|
||||
draw_util::Scissor scissor;
|
||||
draw_util::GetScissor(regs, scissor);
|
||||
D3D12_RECT scissor_rect;
|
||||
scissor_rect.left = LONG(scissor.left * pixel_size_x);
|
||||
scissor_rect.top = LONG(scissor.top * pixel_size_y);
|
||||
scissor_rect.right = LONG((scissor.left + scissor.width) * pixel_size_x);
|
||||
scissor_rect.bottom = LONG((scissor.top + scissor.height) * pixel_size_y);
|
||||
scissor_rect.left = LONG(scissor.left);
|
||||
scissor_rect.top = LONG(scissor.top);
|
||||
scissor_rect.right = LONG(scissor.left + scissor.width);
|
||||
scissor_rect.bottom = LONG(scissor.top + scissor.height);
|
||||
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_.right != scissor_rect.right;
|
||||
|
@ -2865,6 +2827,8 @@ void D3D12CommandProcessor::UpdateFixedFunctionState(bool primitive_two_faced) {
|
|||
}
|
||||
|
||||
if (!edram_rov_used_) {
|
||||
const RegisterFile& regs = *register_file_;
|
||||
|
||||
// Blend factor.
|
||||
ff_blend_factor_update_needed_ |=
|
||||
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.
|
||||
Register stencil_ref_mask_reg;
|
||||
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 &&
|
||||
pa_su_sc_mode_cntl.cull_front && !pa_su_sc_mode_cntl.cull_back) {
|
||||
stencil_ref_mask_reg = XE_GPU_REG_RB_STENCILREFMASK_BF;
|
||||
|
@ -2906,9 +2870,11 @@ void D3D12CommandProcessor::UpdateFixedFunctionState(bool primitive_two_faced) {
|
|||
}
|
||||
|
||||
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 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]) {
|
||||
#if XE_UI_D3D12_FINE_GRAINED_DRAW_SCOPES
|
||||
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_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_vtx_cntl = regs.Get<reg::PA_SU_VTX_CNTL>();
|
||||
float rb_alpha_ref = regs[XE_GPU_REG_RB_ALPHA_REF].f32;
|
||||
auto rb_colorcontrol = regs.Get<reg::RB_COLORCONTROL>();
|
||||
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;
|
||||
|
||||
// Flags.
|
||||
|
@ -3023,13 +2983,9 @@ void D3D12CommandProcessor::UpdateSystemConstantValues(
|
|||
flags |= (pa_cl_clip_cntl.value & 0b111111)
|
||||
<< DxbcShaderTranslator::kSysFlag_UserClipPlane0_Shift;
|
||||
}
|
||||
// Reversed depth.
|
||||
if (viewport_scale_z < 0.0f) {
|
||||
flags |= DxbcShaderTranslator::kSysFlag_ReverseZ;
|
||||
}
|
||||
// Whether SV_IsFrontFace matters.
|
||||
if (primitive_two_faced) {
|
||||
flags |= DxbcShaderTranslator::kSysFlag_PrimitiveTwoFaced;
|
||||
// Whether the primitive is polygonal and SV_IsFrontFace matters.
|
||||
if (primitive_polygonal) {
|
||||
flags |= DxbcShaderTranslator::kSysFlag_PrimitivePolygonal;
|
||||
}
|
||||
// Primitive killing condition.
|
||||
if (pa_cl_clip_cntl.vtx_kill_or) {
|
||||
|
@ -3122,81 +3078,24 @@ void D3D12CommandProcessor::UpdateSystemConstantValues(
|
|||
}
|
||||
|
||||
// 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
|
||||
// to do memexport.
|
||||
if (sq_program_cntl.vs_export_mode ==
|
||||
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)) {
|
||||
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("");
|
||||
system_constants_.ndc_scale[0] = nan_value;
|
||||
system_constants_.ndc_scale[1] = nan_value;
|
||||
system_constants_.ndc_scale[2] = nan_value;
|
||||
system_constants_.ndc_offset[0] = nan_value;
|
||||
system_constants_.ndc_offset[1] = nan_value;
|
||||
system_constants_.ndc_offset[2] = nan_value;
|
||||
} else {
|
||||
// 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_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;
|
||||
}
|
||||
for (uint32_t i = 0; i < 3; ++i) {
|
||||
dirty |= !std::isnan(system_constants_.ndc_scale[i]);
|
||||
system_constants_.ndc_scale[i] = nan_value;
|
||||
}
|
||||
} else {
|
||||
for (uint32_t i = 0; i < 3; ++i) {
|
||||
dirty |= system_constants_.ndc_scale[i] != viewport_info.ndc_scale[i];
|
||||
dirty |= system_constants_.ndc_offset[i] != viewport_info.ndc_offset[i];
|
||||
system_constants_.ndc_scale[i] = viewport_info.ndc_scale[i];
|
||||
system_constants_.ndc_offset[i] = viewport_info.ndc_offset[i];
|
||||
}
|
||||
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.
|
||||
|
@ -3212,19 +3111,10 @@ void D3D12CommandProcessor::UpdateSystemConstantValues(
|
|||
system_constants_.point_size[1] = point_size_y;
|
||||
system_constants_.point_size_min_max[0] = point_size_min;
|
||||
system_constants_.point_size_min_max[1] = point_size_max;
|
||||
float point_screen_to_ndc_x, point_screen_to_ndc_y;
|
||||
if (pa_cl_vte_cntl.vport_x_scale_ena) {
|
||||
point_screen_to_ndc_x =
|
||||
(viewport_scale_x != 0.0f) ? (0.5f / viewport_scale_x) : 0.0f;
|
||||
} 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;
|
||||
}
|
||||
float point_screen_to_ndc_x =
|
||||
(0.5f * 2.0f * pixel_size_x) / viewport_info.width;
|
||||
float point_screen_to_ndc_y =
|
||||
(0.5f * 2.0f * pixel_size_y) / viewport_info.height;
|
||||
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;
|
||||
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;
|
||||
system_constants_.edram_depth_base_dwords = depth_base_dwords;
|
||||
|
||||
// The Z range is reversed in the vertex shader if it's reverse - use the
|
||||
// absolute value of the scale.
|
||||
float depth_range_scale = std::abs(viewport_scale_z);
|
||||
float depth_range_scale = viewport_info.z_max - viewport_info.z_min;
|
||||
dirty |= 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
|
||||
? regs[XE_GPU_REG_PA_CL_VPORT_ZOFFSET].f32
|
||||
: 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;
|
||||
dirty |= system_constants_.edram_depth_range_offset != viewport_info.z_min;
|
||||
system_constants_.edram_depth_range_offset = viewport_info.z_min;
|
||||
|
||||
// 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
|
||||
// are used.
|
||||
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;
|
||||
if (primitive_two_faced) {
|
||||
if (primitive_polygonal) {
|
||||
if (pa_su_sc_mode_cntl.poly_offset_front_enable) {
|
||||
poly_offset_front_scale =
|
||||
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;
|
||||
|
||||
if (primitive_two_faced && rb_depthcontrol.backface_enable) {
|
||||
if (primitive_polygonal && rb_depthcontrol.backface_enable) {
|
||||
dirty |= system_constants_.edram_stencil_back_reference !=
|
||||
rb_stencilrefmask_bf.stencilref;
|
||||
system_constants_.edram_stencil_back_reference =
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
#include "xenia/gpu/d3d12/primitive_converter.h"
|
||||
#include "xenia/gpu/d3d12/render_target_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/xenos.h"
|
||||
#include "xenia/kernel/kernel_state.h"
|
||||
|
@ -349,11 +350,15 @@ class D3D12CommandProcessor : public CommandProcessor {
|
|||
D3D12_CPU_DESCRIPTOR_HANDLE& cpu_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(
|
||||
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 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]);
|
||||
bool UpdateBindings(const D3D12Shader* vertex_shader,
|
||||
const D3D12Shader* pixel_shader,
|
||||
|
|
|
@ -1285,7 +1285,7 @@ bool PipelineCache::GetCurrentStateDescription(
|
|||
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,
|
||||
primitive_type);
|
||||
|
||||
|
@ -1305,16 +1305,11 @@ bool PipelineCache::GetCurrentStateDescription(
|
|||
// Here we also assume that only one side is culled - if two sides are culled,
|
||||
// the D3D12 command processor will drop such draw early.
|
||||
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_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) {
|
||||
description_out.cull_mode = PipelineCullMode::kFront;
|
||||
} else if (cull_back) {
|
||||
|
@ -1354,9 +1349,9 @@ bool PipelineCache::GetCurrentStateDescription(
|
|||
description_out.fill_mode_wireframe = 0;
|
||||
}
|
||||
} else {
|
||||
// Filled front faces only.
|
||||
// Use front depth bias if POLY_OFFSET_PARA_ENABLED
|
||||
// (POLY_OFFSET_FRONT_ENABLED is for two-sided primitives).
|
||||
// Filled front faces only, without culling.
|
||||
cull_front = false;
|
||||
cull_back = false;
|
||||
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_scale = regs[XE_GPU_REG_PA_SU_POLY_OFFSET_FRONT_SCALE].f32;
|
||||
|
@ -1413,7 +1408,7 @@ bool PipelineCache::GetCurrentStateDescription(
|
|||
if (rb_depthcontrol.stencil_enable) {
|
||||
description_out.stencil_enable = 1;
|
||||
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
|
||||
// ones only if drawing only back faces.
|
||||
Register stencil_ref_mask_reg;
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
// generated from `xb buildhlsl`
|
||||
// source: primitive_point_list.gs.hlsl
|
||||
const uint8_t primitive_point_list_gs[] = {
|
||||
0x44, 0x58, 0x42, 0x43, 0x6F, 0x7A, 0xE0, 0xA0, 0x82, 0xF0, 0x8E, 0x77,
|
||||
0x2B, 0x62, 0x44, 0x00, 0xA3, 0x34, 0x47, 0x40, 0x01, 0x00, 0x00, 0x00,
|
||||
0x0C, 0x1E, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00,
|
||||
0x44, 0x58, 0x42, 0x43, 0x16, 0x84, 0x10, 0x1C, 0xE9, 0xAD, 0x76, 0xF9,
|
||||
0x92, 0xF2, 0xD5, 0x65, 0x7C, 0x8A, 0x5F, 0xC5, 0x01, 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,
|
||||
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,
|
||||
0x3C, 0x00, 0x00, 0x00, 0x01, 0x05, 0x53, 0x47, 0x00, 0x05, 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,
|
||||
0x50, 0x6F, 0x73, 0x69, 0x74, 0x69, 0x6F, 0x6E, 0x00, 0x53, 0x56, 0x5F,
|
||||
0x43, 0x6C, 0x69, 0x70, 0x44, 0x69, 0x73, 0x74, 0x61, 0x6E, 0x63, 0x65,
|
||||
0x00, 0xAB, 0xAB, 0xAB, 0x53, 0x48, 0x45, 0x58, 0xBC, 0x0D, 0x00, 0x00,
|
||||
0x51, 0x00, 0x02, 0x00, 0x6F, 0x03, 0x00, 0x00, 0x6A, 0x08, 0x00, 0x01,
|
||||
0x00, 0xAB, 0xAB, 0xAB, 0x53, 0x48, 0x45, 0x58, 0xD0, 0x0D, 0x00, 0x00,
|
||||
0x51, 0x00, 0x02, 0x00, 0x74, 0x03, 0x00, 0x00, 0x6A, 0x08, 0x00, 0x01,
|
||||
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, 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,
|
||||
0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x5F, 0x00, 0x00, 0x04,
|
||||
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,
|
||||
0x5C, 0x28, 0x00, 0x01, 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,
|
||||
0x38, 0x00, 0x18, 0x08, 0x32, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 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,
|
||||
0xF2, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x46, 0x04, 0x10, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x02, 0x40, 0x00, 0x00, 0x00, 0x00, 0x80, 0xBF,
|
||||
0x00, 0x00, 0x80, 0x3F, 0x00, 0x00, 0x80, 0x3F, 0x00, 0x00, 0x80, 0xBF,
|
||||
0x00, 0x00, 0x78, 0x08, 0xF2, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00,
|
||||
0x46, 0x0E, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x46, 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, 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,
|
||||
0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x36, 0x00, 0x38, 0x06,
|
||||
0x72, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x06, 0x01, 0x10, 0x80,
|
||||
0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x00, 0x40, 0x05,
|
||||
0x82, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1A, 0x00, 0x10, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x08, 0xF2, 0x00, 0x10, 0x00,
|
||||
0x02, 0x00, 0x00, 0x00, 0xC6, 0x09, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00,
|
||||
0x46, 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,
|
||||
|
@ -573,7 +473,7 @@ const uint8_t primitive_point_list_gs[] = {
|
|||
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,
|
||||
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,
|
||||
|
@ -613,6 +513,56 @@ const uint8_t primitive_point_list_gs[] = {
|
|||
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,
|
||||
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,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 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,
|
||||
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, 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,
|
||||
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, 0x74, 0x00, 0x00, 0x00,
|
||||
0x02, 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, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x00, 0x20, 0x05,
|
||||
0x42, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0x00, 0x10, 0x00,
|
||||
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x08, 0x32, 0x00, 0x10, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x86, 0x00, 0x10, 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,
|
||||
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, 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, 0x01, 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, 0x02, 0x00, 0x00, 0x00,
|
||||
0x01, 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,
|
||||
};
|
||||
|
|
|
@ -130,7 +130,7 @@ dcl_input_siv v[1][18].xyzw, position
|
|||
dcl_input v[1][19].xyzw
|
||||
dcl_input v[1][20].xy
|
||||
dcl_input v[1][20].z
|
||||
dcl_temps 2
|
||||
dcl_temps 3
|
||||
dcl_inputprimitive point
|
||||
dcl_stream m0
|
||||
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
|
||||
mul [precise(xy)] r0.xy, r0.xyxx, CB0[0][2].zwzz
|
||||
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)
|
||||
add [precise] r1.xyzw, r1.xyzw, v[0][18].xyxy
|
||||
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(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 [precise(xyz)] r1.xyz, -r0.xxyx
|
||||
mov [precise(w)] r1.w, r0.y
|
||||
add [precise] r2.xyzw, r1.xwyz, v[0][18].xyxy
|
||||
mov o0.xyzw, v[0][0].xyzw
|
||||
mov o1.xyzw, v[0][1].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.z, v[0][16].z
|
||||
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 o19.xyzw, v[0][19].xyzw
|
||||
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 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, 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.z, v[0][16].z
|
||||
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 o19.xyzw, v[0][19].xyzw
|
||||
mov o20.xy, v[0][20].xyxx
|
||||
emit_stream m0
|
||||
cut_stream m0
|
||||
ret
|
||||
// Approximately 116 instruction slots used
|
||||
// Approximately 118 instruction slots used
|
||||
|
|
|
@ -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) *
|
||||
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);
|
||||
// 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_stream.Append(xe_out);
|
||||
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_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.RestartStrip();
|
||||
}
|
||||
|
|
|
@ -111,6 +111,179 @@ int32_t FloatToD3D11Fixed16p8(float f32) {
|
|||
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) {
|
||||
// FIXME(Triang3l): Screen scissor isn't applied here, but it seems to be
|
||||
// unused on Xbox 360 Direct3D 9.
|
||||
|
|
|
@ -33,6 +33,28 @@ namespace draw_util {
|
|||
// for use with the top-left rasterization rule later.
|
||||
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 {
|
||||
uint32_t left;
|
||||
uint32_t top;
|
||||
|
|
|
@ -770,7 +770,7 @@ void DxbcShaderTranslator::StartPixelShader() {
|
|||
uint32_t(CbufferRegister::kSystemConstants),
|
||||
kSysConst_Flags_Vec)
|
||||
.Select(kSysConst_Flags_Comp),
|
||||
DxbcSrc::LU(kSysFlag_PrimitiveTwoFaced));
|
||||
DxbcSrc::LU(kSysFlag_PrimitivePolygonal));
|
||||
DxbcOpIf(true, DxbcSrc::R(param_gen_temp, DxbcSrc::kZZZZ));
|
||||
{
|
||||
// Negate modifier flips the sign bit even for 0 - set it to minus for
|
||||
|
@ -1044,10 +1044,9 @@ void DxbcShaderTranslator::CompleteVertexOrDomainShader() {
|
|||
DxbcOpEndIf();
|
||||
}
|
||||
|
||||
// Apply scale for drawing without a viewport, and also remap from OpenGL
|
||||
// Z clip space to Direct3D if needed. Also, if the vertex shader is
|
||||
// multipass, the NDC scale constant can be used to set position to NaN to
|
||||
// kill all primitives.
|
||||
// Apply scale for guest to host viewport and clip space conversion. Also, if
|
||||
// the vertex shader is multipass, the NDC scale constant can be used to set
|
||||
// position to NaN to kill all primitives.
|
||||
system_constants_used_ |= 1ull << kSysConst_NDCScale_Index;
|
||||
DxbcOpMul(DxbcDest::R(system_temp_position_, 0b0111),
|
||||
DxbcSrc::R(system_temp_position_),
|
||||
|
@ -1056,16 +1055,7 @@ void DxbcShaderTranslator::CompleteVertexOrDomainShader() {
|
|||
kSysConst_NDCScale_Vec,
|
||||
kSysConst_NDCScale_Comp * 0b010101 + 0b100100));
|
||||
|
||||
// Reverse Z (Z = W - Z) if the viewport depth is inverted.
|
||||
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.
|
||||
// Apply offset (multiplied by W) used for the same purposes.
|
||||
system_constants_used_ |= 1ull << kSysConst_NDCOffset_Index;
|
||||
DxbcOpMAd(DxbcDest::R(system_temp_position_, 0b0111),
|
||||
DxbcSrc::CB(cbuffer_index_system_constants_,
|
||||
|
|
|
@ -123,9 +123,8 @@ class DxbcShaderTranslator : public ShaderTranslator {
|
|||
kSysFlag_UserClipPlane3_Shift,
|
||||
kSysFlag_UserClipPlane4_Shift,
|
||||
kSysFlag_UserClipPlane5_Shift,
|
||||
kSysFlag_ReverseZ_Shift,
|
||||
kSysFlag_KillIfAnyVertexKilled_Shift,
|
||||
kSysFlag_PrimitiveTwoFaced_Shift,
|
||||
kSysFlag_PrimitivePolygonal_Shift,
|
||||
kSysFlag_AlphaPassIfLess_Shift,
|
||||
kSysFlag_AlphaPassIfEqual_Shift,
|
||||
kSysFlag_AlphaPassIfGreater_Shift,
|
||||
|
@ -165,9 +164,8 @@ class DxbcShaderTranslator : public ShaderTranslator {
|
|||
kSysFlag_UserClipPlane3 = 1u << kSysFlag_UserClipPlane3_Shift,
|
||||
kSysFlag_UserClipPlane4 = 1u << kSysFlag_UserClipPlane4_Shift,
|
||||
kSysFlag_UserClipPlane5 = 1u << kSysFlag_UserClipPlane5_Shift,
|
||||
kSysFlag_ReverseZ = 1u << kSysFlag_ReverseZ_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_AlphaPassIfEqual = 1u << kSysFlag_AlphaPassIfEqual_Shift,
|
||||
kSysFlag_AlphaPassIfGreater = 1u << kSysFlag_AlphaPassIfGreater_Shift,
|
||||
|
@ -220,8 +218,7 @@ class DxbcShaderTranslator : public ShaderTranslator {
|
|||
float point_size[2];
|
||||
|
||||
float point_size_min_max[2];
|
||||
// Inverse scale of the host viewport (but not supersampled), with signs
|
||||
// pre-applied.
|
||||
// Screen point size * 2 (but not supersampled) -> size in NDC.
|
||||
float point_screen_to_ndc[2];
|
||||
|
||||
float user_clip_planes[6][4];
|
||||
|
|
|
@ -15,9 +15,25 @@
|
|||
|
||||
//#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(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(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(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(0x0A03, kDword, UNKNOWN_0A03)
|
||||
XE_GPU_REGISTER(0x0A04, kDword, UNKNOWN_0A04)
|
||||
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(0x0A30, kDword, COHER_BASE_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(0x0D01, kDword, SQ_FLOW_CONTROL)
|
||||
XE_GPU_REGISTER(0x0D02, kDword, SQ_INST_STORE_MANAGMENT)
|
||||
|
||||
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
|
||||
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)
|
||||
|
||||
// Set during GPU initialization by D3D
|
||||
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(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(0x1841, kDword, D1GRPH_CONTROL)
|
||||
|
|
|
@ -289,12 +289,16 @@ union 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 {
|
||||
struct {
|
||||
uint32_t viz_query_ena : 1; // +0
|
||||
uint32_t viz_query_id : 6; // +1
|
||||
uint32_t kill_pix_post_early_z : 1; // +7
|
||||
// the visibility of draws should be evaluated
|
||||
uint32_t viz_query_ena : 1; // +0
|
||||
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;
|
||||
static constexpr Register register_index = XE_GPU_REG_PA_SC_VIZ_QUERY;
|
||||
|
|
|
@ -64,7 +64,15 @@ enum class PrimitiveType : uint32_t {
|
|||
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 ||
|
||||
type == PrimitiveType::kQuadPatch)) {
|
||||
return true;
|
||||
|
@ -81,6 +89,10 @@ inline bool IsPrimitiveTwoFaced(bool tessellated, PrimitiveType type) {
|
|||
default:
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -782,13 +782,13 @@ bool KernelState::Save(ByteStream* stream) {
|
|||
for (auto object : objects) {
|
||||
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
|
||||
num_objects--;
|
||||
continue;
|
||||
}
|
||||
|
||||
stream->Write<uint32_t>(object->type());
|
||||
stream->Write<uint32_t>(static_cast<uint32_t>(object->type()));
|
||||
if (!object->Save(stream)) {
|
||||
XELOGD("Did not save object of type {}", object->type());
|
||||
assert_always();
|
||||
|
@ -823,7 +823,7 @@ bool KernelState::Restore(ByteStream* stream) {
|
|||
uint32_t num_threads = stream->Read<uint32_t>();
|
||||
XELOGD("Loading {} threads...", num_threads);
|
||||
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) {
|
||||
// Can't continue the restore or we risk misalignment.
|
||||
assert_always();
|
||||
|
|
|
@ -51,7 +51,7 @@ class ObjectTable {
|
|||
object_ref<T> LookupObject(X_HANDLE handle) {
|
||||
auto object = LookupObject(handle, false);
|
||||
if (object) {
|
||||
assert_true(object->type() == T::kType);
|
||||
assert_true(object->type() == T::kObjectType);
|
||||
}
|
||||
auto result = object_ref<T>(reinterpret_cast<T*>(object));
|
||||
return result;
|
||||
|
@ -72,7 +72,7 @@ class ObjectTable {
|
|||
std::vector<object_ref<T>> GetObjectsByType() {
|
||||
std::vector<object_ref<T>> results;
|
||||
GetObjectsByType(
|
||||
T::kType,
|
||||
T::kObjectType,
|
||||
reinterpret_cast<std::vector<object_ref<XObject>>*>(&results));
|
||||
return results;
|
||||
}
|
||||
|
|
|
@ -148,7 +148,7 @@ class Param {
|
|||
|
||||
protected:
|
||||
Param() : ordinal_(-1) {}
|
||||
explicit Param(Init& init) : ordinal_(--init.ordinal) {}
|
||||
explicit Param(Init& init) : ordinal_(init.ordinal++) {}
|
||||
|
||||
template <typename V>
|
||||
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;
|
||||
Param::Init init = {
|
||||
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 &&
|
||||
(!(export_entry->tags & xe::cpu::ExportTag::kHighFrequency) ||
|
||||
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;
|
||||
Param::Init init = {
|
||||
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 &&
|
||||
(!(export_entry->tags & xe::cpu::ExportTag::kHighFrequency) ||
|
||||
cvars::log_high_frequency_kernel_calls)) {
|
||||
|
|
|
@ -314,6 +314,7 @@ class UserProfile {
|
|||
uint64_t xuid() const { return account_.xuid_online; }
|
||||
std::string name() const { return to_utf8(account_.GetGamertagString()); }
|
||||
uint32_t signin_state() const { return 1; }
|
||||
uint32_t type() const { return 2; /* online profile? */ }
|
||||
|
||||
void AddSetting(std::unique_ptr<Setting> setting);
|
||||
Setting* GetSetting(uint32_t setting_id);
|
||||
|
|
|
@ -39,15 +39,6 @@ struct DeviceInfo {
|
|||
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.
|
||||
//
|
||||
// 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,
|
||||
// when it is a 64-bit value. Which means any size above ~4GB
|
||||
// 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)
|
||||
static const DeviceInfo dummy_device_info_ = {
|
||||
0x00000001, // first device seems to be storage
|
||||
1, // hdd type
|
||||
20ull * ONE_GB, // 20GB
|
||||
12ull * ONE_GB, // 12GB, so it looks a little used.
|
||||
0x00000001, // id
|
||||
1, // 1=HDD
|
||||
20ull * ONE_GB, // 20GB
|
||||
3ull * ONE_GB, // 3GB, so it looks a little used.
|
||||
u"Dummy HDD",
|
||||
};
|
||||
#undef ONE_GB
|
||||
|
@ -132,6 +120,15 @@ dword_result_t XamContentGetDeviceState(dword_t device_id,
|
|||
}
|
||||
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_t device_id, pointer_t<X_CONTENT_DEVICE_DATA> device_data) {
|
||||
if ((device_id & 0x0000000F) != dummy_device_info_.device_id) {
|
||||
|
@ -144,10 +141,10 @@ dword_result_t XamContentGetDeviceData(
|
|||
|
||||
device_data.Zero();
|
||||
const auto& device_info = dummy_device_info_;
|
||||
xe::store_and_swap(&device_data->device_id, device_info.device_id);
|
||||
xe::store_and_swap(&device_data->device_type, device_info.device_type);
|
||||
xe::store(&device_data->total_bytes, device_info.total_bytes);
|
||||
xe::store(&device_data->free_bytes, device_info.free_bytes);
|
||||
device_data->device_id = device_info.device_id;
|
||||
device_data->device_type = device_info.device_type;
|
||||
device_data->total_bytes = device_info.total_bytes;
|
||||
device_data->free_bytes = device_info.free_bytes;
|
||||
xe::store_and_swap<std::u16string>(&device_data->name[0], device_info.name);
|
||||
return X_ERROR_SUCCESS;
|
||||
}
|
||||
|
|
|
@ -102,25 +102,25 @@ DECLARE_XAM_EXPORT1(XamProfileEnumerate, kUserProfiles, kImplemented);
|
|||
|
||||
X_HRESULT_result_t XamUserGetXUID(dword_t user_index, dword_t type_mask,
|
||||
lpqword_t xuid_ptr) {
|
||||
assert_true(type_mask == 1 || type_mask == 2 || type_mask == 3 ||
|
||||
type_mask == 4 || type_mask == 7);
|
||||
if (!xuid_ptr) {
|
||||
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;
|
||||
uint64_t xuid = 0;
|
||||
if (user_index < 4) {
|
||||
if (user_index == 0) {
|
||||
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();
|
||||
result = X_E_SUCCESS;
|
||||
} else if (type_mask & 1) {
|
||||
} else if (type & 1) {
|
||||
// maybe offline profile?
|
||||
xuid = user_profile->xuid();
|
||||
result = X_E_SUCCESS;
|
||||
} else {
|
||||
result = X_E_INVALIDARG;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -178,24 +178,44 @@ DECLARE_XAM_EXPORT1(XamUserGetSigninInfo, kUserProfiles, kImplemented);
|
|||
|
||||
dword_result_t XamUserGetName(dword_t user_index, lpstring_t buffer,
|
||||
dword_t buffer_len) {
|
||||
if (user_index) {
|
||||
return X_ERROR_NO_SUCH_USER;
|
||||
if (user_index >= 4) {
|
||||
return X_E_INVALIDARG;
|
||||
}
|
||||
|
||||
if (!buffer_len) {
|
||||
return X_ERROR_SUCCESS;
|
||||
if (user_index) {
|
||||
return X_E_NO_SUCH_USER;
|
||||
}
|
||||
|
||||
const auto& user_profile = kernel_state()->user_profile();
|
||||
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,
|
||||
std::min(buffer_len.value(), uint32_t(15)));
|
||||
return X_ERROR_SUCCESS;
|
||||
std::min(buffer_len.value(), uint32_t(16)));
|
||||
return X_E_SUCCESS;
|
||||
}
|
||||
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 {
|
||||
xe::be<uint32_t> setting_count;
|
||||
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,
|
||||
lpdword_t out_value) {
|
||||
if (user_index) {
|
||||
return X_ERROR_NO_SUCH_USER;
|
||||
// checking all users?
|
||||
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.
|
||||
|
@ -539,6 +566,17 @@ DECLARE_XAM_EXPORT1(XamUserContentRestrictionCheckAccess, kUserProfiles, kStub);
|
|||
dword_result_t XamUserIsOnlineEnabled(dword_t user_index) { return 1; }
|
||||
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_t unk2, lpdword_t out_value,
|
||||
dword_t overlapped_ptr) {
|
||||
|
|
|
@ -47,8 +47,15 @@ void HandleSetThreadName(pointer_t<X_EXCEPTION_RECORD> record) {
|
|||
return;
|
||||
}
|
||||
|
||||
auto name =
|
||||
kernel_memory()->TranslateVirtual<const char*>(thread_info->name_ptr);
|
||||
// Shadowrun (and its demo) has a bug where it ends up passing freed memory
|
||||
// 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;
|
||||
if (thread_info->thread_id == -1) {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/**
|
||||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
|
@ -29,10 +29,23 @@ namespace xboxkrnl {
|
|||
|
||||
static bool IsValidPath(const std::string_view s, bool is_pattern) {
|
||||
// TODO(gibbed): validate path components individually
|
||||
bool got_asterisk = false;
|
||||
for (const auto& c : s) {
|
||||
if (c <= 31 || c >= 127) {
|
||||
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) {
|
||||
case '"':
|
||||
// case '*':
|
||||
|
@ -47,12 +60,20 @@ static bool IsValidPath(const std::string_view s, bool is_pattern) {
|
|||
case '|': {
|
||||
return false;
|
||||
}
|
||||
case '*':
|
||||
case '*': {
|
||||
// Pattern-specific (for NtQueryDirectoryFile)
|
||||
if (!is_pattern) {
|
||||
return false;
|
||||
}
|
||||
got_asterisk = true;
|
||||
break;
|
||||
}
|
||||
case '?': {
|
||||
// Pattern-specific (for NtQueryDirectoryFile)
|
||||
if (!is_pattern) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
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>(
|
||||
object_attrs->root_directory);
|
||||
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();
|
||||
}
|
||||
|
@ -368,7 +389,7 @@ dword_result_t NtQueryFullAttributesFile(
|
|||
root_file = kernel_state()->object_table()->LookupObject<XFile>(
|
||||
obj_attribs->root_directory);
|
||||
assert_not_null(root_file);
|
||||
assert_true(root_file->type() == XObject::Type::kTypeFile);
|
||||
assert_true(root_file->type() == XObject::Type::File);
|
||||
assert_always();
|
||||
}
|
||||
|
||||
|
@ -415,7 +436,7 @@ dword_result_t NtQueryDirectoryFile(
|
|||
|
||||
// Enforce that the path is ASCII.
|
||||
if (!IsValidPath(name, true)) {
|
||||
return X_STATUS_OBJECT_NAME_INVALID;
|
||||
return X_STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
if (file) {
|
||||
|
|
|
@ -418,6 +418,11 @@ DECLARE_XBOXKRNL_EXPORT2(MmQueryAddressProtect, kMemory, kImplemented,
|
|||
|
||||
void MmSetAddressProtect(lpvoid_t base_address, dword_t region_size,
|
||||
dword_t protect_bits) {
|
||||
if (!protect_bits) {
|
||||
XELOGE("MmSetAddressProtect: Failed due to incorrect protect_bits");
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t protect = FromXdkProtectFlags(protect_bits);
|
||||
auto heap = kernel_memory()->LookupHeap(base_address);
|
||||
heap->Protect(base_address.guest_address(), region_size, protect);
|
||||
|
|
|
@ -157,12 +157,15 @@ XboxkrnlModule::XboxkrnlModule(Emulator* emulator, KernelState* kernel_state)
|
|||
// disassembly.
|
||||
// aomega08 says the value is 0x02000817, bit 27: debug mode on.
|
||||
// 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);
|
||||
auto lpXboxHardwareInfo = memory_->TranslateVirtual(pXboxHardwareInfo);
|
||||
export_resolver_->SetVariableMapping(
|
||||
"xboxkrnl.exe", ordinals::XboxHardwareInfo, pXboxHardwareInfo);
|
||||
xe::store_and_swap<uint32_t>(lpXboxHardwareInfo + 0, 0x20); // flags //test
|
||||
xe::store_and_swap<uint8_t>(lpXboxHardwareInfo + 4, 0x06); // cpu count
|
||||
xe::store_and_swap<uint32_t>(lpXboxHardwareInfo + 0, 0x20); // flags
|
||||
xe::store_and_swap<uint8_t>(lpXboxHardwareInfo + 4, 0x06); // cpu count
|
||||
// Remaining 11b are zeroes?
|
||||
|
||||
// ExConsoleGameRegion, probably same values as keyvault region uses?
|
||||
|
|
|
@ -78,22 +78,21 @@ DECLARE_XBOXKRNL_EXPORT1(ObLookupThreadByThreadId, kNone, kImplemented);
|
|||
dword_result_t ObReferenceObjectByHandle(dword_t handle,
|
||||
dword_t object_type_ptr,
|
||||
lpdword_t out_object_ptr) {
|
||||
const static std::unordered_map<XObject::Type, uint32_t> obj_type_match = {
|
||||
{XObject::kTypeEvent, 0xD00EBEEF},
|
||||
{XObject::kTypeSemaphore, 0xD017BEEF},
|
||||
{XObject::kTypeThread, 0xD01BBEEF}};
|
||||
|
||||
// These values come from how Xenia handles uninitialized kernel data exports.
|
||||
// D###BEEF where ### is the ordinal.
|
||||
const static std::unordered_map<XObject::Type, uint32_t> object_types = {
|
||||
{XObject::Type::Event, 0xD00EBEEF},
|
||||
{XObject::Type::Semaphore, 0xD017BEEF},
|
||||
{XObject::Type::Thread, 0xD01BBEEF}};
|
||||
auto object = kernel_state()->object_table()->LookupObject<XObject>(handle);
|
||||
|
||||
if (!object) {
|
||||
return X_STATUS_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
uint32_t native_ptr = object->guest_object();
|
||||
auto obj_type = obj_type_match.find(object->type());
|
||||
|
||||
if (obj_type != obj_type_match.end()) {
|
||||
if (object_type_ptr && object_type_ptr != obj_type->second) {
|
||||
auto object_type = object_types.find(object->type());
|
||||
if (object_type != object_types.end()) {
|
||||
if (object_type_ptr && object_type_ptr != object_type->second) {
|
||||
return X_STATUS_OBJECT_TYPE_MISMATCH;
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -21,16 +21,10 @@
|
|||
#include "xenia/kernel/util/shim_utils.h"
|
||||
#include "xenia/kernel/xboxkrnl/xboxkrnl_private.h"
|
||||
#include "xenia/kernel/xboxkrnl/xboxkrnl_threading.h"
|
||||
#include "xenia/kernel/xclock.h"
|
||||
#include "xenia/kernel/xevent.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 kernel {
|
||||
namespace xboxkrnl {
|
||||
|
@ -509,52 +503,51 @@ struct X_TIME_FIELDS {
|
|||
xe::be<uint16_t> milliseconds;
|
||||
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,
|
||||
pointer_t<X_TIME_FIELDS> time_fields_ptr) {
|
||||
int64_t time_ms = time_ptr.value() / 10000 - 11644473600000LL;
|
||||
time_t timet = time_ms / 1000;
|
||||
|
||||
// https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/localtime-s-localtime32-s-localtime64-s?view=vs-2019
|
||||
// __time64_t structure
|
||||
// allows dates to be expressed up through 23:59:59, January 18, 3001
|
||||
if (uint64_t(timet) > MAX_TIME64_T) {
|
||||
return;
|
||||
}
|
||||
|
||||
struct tm* tm = gmtime(&timet);
|
||||
|
||||
time_fields_ptr->year = tm->tm_year + 1900;
|
||||
time_fields_ptr->month = tm->tm_mon + 1;
|
||||
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;
|
||||
auto tp = XClock::to_sys(XClock::from_file_time(time_ptr.value()));
|
||||
auto dp = date::floor<date::days>(tp);
|
||||
auto year_month_day = date::year_month_day{dp};
|
||||
auto weekday = date::weekday{dp};
|
||||
auto time = date::hh_mm_ss{date::floor<std::chrono::milliseconds>(tp - dp)};
|
||||
time_fields_ptr->year = static_cast<int>(year_month_day.year());
|
||||
time_fields_ptr->month = static_cast<unsigned>(year_month_day.month());
|
||||
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();
|
||||
time_fields_ptr->minute = time.minutes().count();
|
||||
time_fields_ptr->second = static_cast<uint16_t>(time.seconds().count());
|
||||
time_fields_ptr->milliseconds =
|
||||
static_cast<uint16_t>(time.subseconds().count());
|
||||
}
|
||||
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,
|
||||
lpqword_t time_ptr) {
|
||||
struct tm tm;
|
||||
tm.tm_year = time_fields_ptr->year - 1900;
|
||||
tm.tm_mon = time_fields_ptr->month - 1;
|
||||
tm.tm_mday = time_fields_ptr->day;
|
||||
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
|
||||
if (time_fields_ptr->year < 1601 || time_fields_ptr->month < 1 ||
|
||||
time_fields_ptr->month > 11 || time_fields_ptr->day < 1 ||
|
||||
time_fields_ptr->hour > 23 || time_fields_ptr->minute > 59 ||
|
||||
time_fields_ptr->second > 59 || time_fields_ptr->milliseconds > 999) {
|
||||
return 0;
|
||||
}
|
||||
uint64_t time =
|
||||
((timet + 11644473600LL) * 1000 + time_fields_ptr->milliseconds) * 10000;
|
||||
*time_ptr = time;
|
||||
auto year = date::year{time_fields_ptr->year};
|
||||
auto month = date::month{time_fields_ptr->month};
|
||||
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;
|
||||
}
|
||||
DECLARE_XBOXKRNL_EXPORT1(RtlTimeFieldsToTime, kNone, kImplemented);
|
||||
|
|
|
@ -434,7 +434,7 @@ dword_result_t NtCreateEvent(lpdword_t handle_ptr,
|
|||
auto existing_object =
|
||||
LookupNamedObject<XEvent>(kernel_state(), obj_attributes_ptr);
|
||||
if (existing_object) {
|
||||
if (existing_object->type() == XObject::kTypeEvent) {
|
||||
if (existing_object->type() == XObject::Type::Event) {
|
||||
if (handle_ptr) {
|
||||
existing_object->RetainHandle();
|
||||
*handle_ptr = existing_object->handle();
|
||||
|
@ -561,7 +561,7 @@ dword_result_t NtCreateSemaphore(lpdword_t handle_ptr,
|
|||
auto existing_object =
|
||||
LookupNamedObject<XSemaphore>(kernel_state(), obj_attributes_ptr);
|
||||
if (existing_object) {
|
||||
if (existing_object->type() == XObject::kTypeSemaphore) {
|
||||
if (existing_object->type() == XObject::Type::Semaphore) {
|
||||
if (handle_ptr) {
|
||||
existing_object->RetainHandle();
|
||||
*handle_ptr = existing_object->handle();
|
||||
|
@ -615,7 +615,7 @@ dword_result_t NtCreateMutant(lpdword_t handle_out,
|
|||
auto existing_object = LookupNamedObject<XMutant>(
|
||||
kernel_state(), obj_attributes.guest_address());
|
||||
if (existing_object) {
|
||||
if (existing_object->type() == XObject::kTypeMutant) {
|
||||
if (existing_object->type() == XObject::Type::Mutant) {
|
||||
if (handle_out) {
|
||||
existing_object->RetainHandle();
|
||||
*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 =
|
||||
LookupNamedObject<XTimer>(kernel_state(), obj_attributes_ptr);
|
||||
if (existing_object) {
|
||||
if (existing_object->type() == XObject::kTypeTimer) {
|
||||
if (existing_object->type() == XObject::Type::Timer) {
|
||||
if (handle_ptr) {
|
||||
existing_object->RetainHandle();
|
||||
*handle_ptr = existing_object->handle();
|
||||
|
|
|
@ -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_
|
|
@ -14,7 +14,7 @@ namespace kernel {
|
|||
|
||||
XEnumerator::XEnumerator(KernelState* kernel_state, size_t items_per_enumerate,
|
||||
size_t item_size)
|
||||
: XObject(kernel_state, kType),
|
||||
: XObject(kernel_state, kObjectType),
|
||||
items_per_enumerate_(items_per_enumerate),
|
||||
item_size_(item_size) {}
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ namespace kernel {
|
|||
|
||||
class XEnumerator : public XObject {
|
||||
public:
|
||||
static const Type kType = kTypeEnumerator;
|
||||
static const XObject::Type kObjectType = XObject::Type::Enumerator;
|
||||
|
||||
XEnumerator(KernelState* kernel_state, size_t items_per_enumerate,
|
||||
size_t item_size);
|
||||
|
|
|
@ -15,7 +15,8 @@
|
|||
namespace xe {
|
||||
namespace kernel {
|
||||
|
||||
XEvent::XEvent(KernelState* kernel_state) : XObject(kernel_state, kType) {}
|
||||
XEvent::XEvent(KernelState* kernel_state)
|
||||
: XObject(kernel_state, kObjectType) {}
|
||||
|
||||
XEvent::~XEvent() = default;
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ static_assert_size(X_KEVENT, 0x10);
|
|||
|
||||
class XEvent : public XObject {
|
||||
public:
|
||||
static const Type kType = kTypeEvent;
|
||||
static const XObject::Type kObjectType = XObject::Type::Event;
|
||||
|
||||
explicit XEvent(KernelState* kernel_state);
|
||||
~XEvent() override;
|
||||
|
|
|
@ -22,11 +22,13 @@ namespace xe {
|
|||
namespace kernel {
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
XFile::XFile() : XObject(kType) {
|
||||
XFile::XFile() : XObject(kObjectType) {
|
||||
async_event_ = threading::Event::CreateAutoResetEvent(false);
|
||||
}
|
||||
|
||||
|
|