Merge branch 'master' into vulkan

This commit is contained in:
Triang3l 2022-02-03 21:12:10 +03:00
commit 922efb13ce
501 changed files with 56183 additions and 10196 deletions

View File

@ -8,14 +8,17 @@ skip_tags: true
skip_commits:
files:
- .drone.yml
- .drone.star
- .github/**
- .travis.yml
- android/**
- docs/**
- src/**/*_posix.*
- src/**/*_linux.*
- src/**/*_gnulinux.*
- src/**/*_x11.*
- src/**/*_gtk.*
- src/**/*_android.*
- src/**/*_mac.*
- LICENSE
- README.md
@ -27,23 +30,28 @@ pull_requests:
os: Visual Studio 2019
init:
- git config --global core.autocrlf input
- ps: |
If (-Not $env:APPVEYOR_PULL_REQUEST_NUMBER) {
$env:is_not_pr = "true"
}
If (-Not $env:APPVEYOR_REPO_COMMIT_MESSAGE_EXTENDED) {
$env:APPVEYOR_REPO_COMMIT_MESSAGE_EXTENDED = " "
}
install:
- cmd: vcpkg integrate remove
- cmd: xb setup
- |
vcpkg integrate remove
xb setup
platform: Windows
configuration:
- Release
- Checked
configuration: [Release, Checked]
build_script:
- cmd: xb build --config=%CONFIGURATION% --target=src\xenia-app --target=tests\xenia-base-tests --target=tests\xenia-cpu-ppc-tests --target=src\xenia-vfs-dump
- xb build --config=%CONFIGURATION% --target=src\xenia-app --target=tests\xenia-base-tests --target=tests\xenia-cpu-ppc-tests --target=src\xenia-vfs-dump
after_build:
- cmd: |
- |
IF NOT "%CONFIGURATION%"=="Checked" SET "ARCHIVE_SUFFIX=%APPVEYOR_REPO_BRANCH%"
IF NOT "%CONFIGURATION%"=="Checked" SET "ARCHIVE_SWITCHES=--"
IF "%CONFIGURATION%"=="Checked" SET "ARCHIVE_SUFFIX=%APPVEYOR_REPO_BRANCH%_FOR-DEVS-ONLY"
@ -52,10 +60,10 @@ after_build:
7z a xenia-vfs-dump_%ARCHIVE_SUFFIX%.zip %ARCHIVE_SWITCHES% LICENSE "%APPVEYOR_BUILD_FOLDER%\build\bin\%PLATFORM%\%CONFIGURATION%\xenia-vfs-dump.exe" "%APPVEYOR_BUILD_FOLDER%\build\bin\%PLATFORM%\%CONFIGURATION%\xenia-vfs-dump.pdb"
before_test:
- cmd: xb gentests
- xb gentests
test_script:
- cmd: xb test --config=%CONFIGURATION% --no_build
- xb test --config=%CONFIGURATION% --no_build
artifacts:
- path: '*.zip'
@ -73,3 +81,22 @@ deploy:
configuration: release
appveyor_repo_tag: true
is_not_pr: true
- provider: GitHub
name: xenia-master
repository: xenia-project/release-builds-windows
auth_token:
secure: /8he47z1WnPN7LcCTe5T5KMxxX0SmqFj9QMpeWEa3aZ64kMsfupOT/jKakqTM8af
tag: v$(appveyor_build_version)
release: v$(appveyor_build_version)
description: |
Windows release build for https://github.com/xenia-project/xenia/commit/$(APPVEYOR_REPO_COMMIT).
$(APPVEYOR_REPO_COMMIT_MESSAGE)
$(APPVEYOR_REPO_COMMIT_MESSAGE_EXTENDED)
draft: false
prerelease: false
on:
branch: master
configuration: release
is_not_pr: true

451
.drone.star Normal file
View File

@ -0,0 +1,451 @@
def main(ctx):
return [
pipeline_lint(),
pipeline_linux_desktop('x86_64-linux-clang', image_linux_x86_64(), 'amd64', 'clang', True),
pipeline_linux_desktop('x86_64-linux-gcc', image_linux_x86_64(), 'amd64', 'gcc', False), # GCC release linking is really slow
pipeline_android('x86_64-android', image_linux_x86_64(), 'amd64', 'Android-x86_64'),
pipeline_android('aarch64-android', image_linux_x86_64(), 'amd64', 'Android-ARM64'),
]
def image_linux_x86_64():
return 'xeniaproject/buildenv:2022-01-01'
def volume_build(toolchain, path='/drone/src/build'):
return {
'name': 'build-' + toolchain,
'path': path,
}
def command_cc(cc):
# set CC, CXX, ...
return 'export $(cat /{}.env | sed \'s/#.*//g\' | xargs)'.format(cc)
def command_ndk_build(platform, configuration, target):
return '$ANDROID_NDK_ROOT/build/ndk-build NDK_PROJECT_PATH:=./bin/{configuration} NDK_APPLICATION_MK:=./xenia.Application.mk PREMAKE_ANDROIDNDK_PLATFORMS:={platform} PREMAKE_ANDROIDNDK_CONFIGURATIONS:={configuration} -j$(nproc) {target}'.format(platform=platform, configuration=configuration, target=target)
def targets_android(platform):
targets = [
'aes_128',
'capstone',
'dxbc',
'discord-rpc',
'cxxopts',
'cpptoml',
'avcodec',
'avutil',
'fmt',
'glslang-spirv',
'imgui',
'mspack',
'snappy',
'spirv-tools',
'xxhash',
# 'xenia-core',
# 'xenia-app-discord',
# 'xenia-apu',
# 'xenia-apu-nop',
'xenia-base',
'xenia-base-tests',
# 'xenia-cpu',
# 'xenia-cpu-tests',
# 'xenia-cpu-ppc-tests',
# 'xenia-cpu-backend-x64',
# 'xenia-debug-ui',
# 'xenia-gpu',
# 'xenia-gpu-shader-compiler',
# 'xenia-gpu-null',
# 'xenia-gpu-vulkan',
# 'xenia-gpu-vulkan-trace-viewer',
# 'xenia-gpu-vulkan-trace-dump',
'xenia-hid',
# 'xenia-hid-demo',
'xenia-hid-nop',
# 'xenia-kernel',
'xenia-ui',
'xenia-ui-spirv',
# 'xenia-ui-vulkan',
# 'xenia-ui-window-vulkan-demo',
'xenia-vfs',
'xenia-vfs-dump',
]
if platform == 'Android-x86_64':
targets.extend([
'xenia-core',
'xenia-apu',
'xenia-apu-nop',
'xenia-cpu',
'xenia-cpu-tests',
'xenia-cpu-ppc-tests',
'xenia-cpu-backend-x64',
'xenia-debug-ui',
'xenia-gpu',
'xenia-gpu-null',
'xenia-gpu-vulkan',
'xenia-gpu-shader-compiler',
'xenia-kernel',
])
return targets
# Run lint in a separate pipeline so that it will try building even if lint fails
def pipeline_lint():
return {
'kind': 'pipeline',
'type': 'docker',
'name': 'lint',
'steps': [
{
'name': 'lint',
'image': image_linux_x86_64(),
'commands': [
'clang-format --version',
'./xenia-build lint --all',
],
},
],
}
def pipeline_linux_desktop(name, image, arch, cc, build_release_all):
return {
'kind': 'pipeline',
'type': 'docker',
'name': name,
'platform': {
'os': 'linux',
'arch': arch,
},
# These volumes will be mounted at the build directory, allowing to
# run different premake toolchains from the same source tree
'volumes': [
{
'name': 'build-premake',
'temp': {},
},
{
'name': 'build-cmake',
'temp': {},
},
],
'steps': [
#
# Setup the source tree
#
{
'name': 'clone-submodules',
'image': image,
'commands': [
'pwd',
# May miss recursive submodules (but faster than xb setup)
'git submodule update --init --depth 1 -j $(nproc)',
],
},
#
# Setup the two build systems
#
# Native premake Makefiles for production
{
'name': 'toolchain-premake',
'image': image,
'volumes': [volume_build('premake')],
'commands': [
command_cc(cc),
'$CXX --version',
'python3 --version',
'./xenia-build premake --cc={}'.format(cc),
],
'depends_on': ['clone-submodules'],
},
# Development toolchain
{
'name': 'toolchain-cmake',
'image': image,
'volumes': [volume_build('cmake')],
'commands': [
command_cc(cc),
'''
./xenia-build premake --cc={} --devenv=cmake
cd build
for c in Debug Release
do
mkdir cmake-$c
cd cmake-$c
cmake -DCMAKE_BUILD_TYPE=$c ..
cd ..
done
'''.format(cc),
],
# Premake itself needs to be build first:
'depends_on': ['toolchain-premake'],
},
#
# Building
#
{
'name': 'build-premake-debug-tests',
'image': image,
'volumes': [volume_build('premake')],
'commands': [
command_cc(cc),
'./xenia-build build --no_premake -j$(nproc) --config=Debug --target=xenia-base-tests',
],
'depends_on': ['toolchain-premake'],
},
{
'name': 'build-premake-debug-all',
'image': image,
'volumes': [volume_build('premake')],
'commands': [
command_cc(cc),
'./xenia-build build --no_premake -j$(nproc) --config=Debug',
],
'depends_on': ['build-premake-debug-tests'],
},
{
'name': 'build-premake-release-tests',
'image': image,
'volumes': [volume_build('premake')],
'commands': [
command_cc(cc),
'./xenia-build build --no_premake -j$(nproc) --config=Release --target=xenia-base-tests',
],
'depends_on': ['toolchain-premake'],
},
] + ([
{
'name': 'build-premake-release-all',
'image': image,
'volumes': [volume_build('premake')],
'commands': [
command_cc(cc),
'./xenia-build build --no_premake -j$(nproc) --config=Release',
],
'depends_on': ['build-premake-release-tests'],
},
] if build_release_all else []) + [
{
'name': 'build-cmake-debug-all',
'image': image,
'volumes': [volume_build('cmake')],
'commands': [
command_cc(cc),
'cd build/cmake-Debug',
'cmake --build . -j$(nproc)',
],
'depends_on': ['toolchain-cmake'],
},
{
'name': 'build-cmake-release-tests',
'image': image,
'volumes': [volume_build('cmake')],
'commands': [
command_cc(cc),
'cd build/cmake-Release',
'cmake --build . -j$(nproc) --target xenia-base-tests',
],
'depends_on': ['toolchain-cmake'],
},
] + ([
{
'name': 'build-cmake-release-all',
'image': image,
'volumes': [volume_build('cmake')],
'commands': [
command_cc(cc),
'cd build/cmake-Release',
'cmake --build . -j$(nproc)',
],
'depends_on': ['build-cmake-release-tests'],
},
] if build_release_all else []) + [
#
# Tests
#
{
'name': 'test-premake-debug-valgrind',
'image': image,
'volumes': [volume_build('premake')],
'commands': [
'valgrind --error-exitcode=99 ./build/bin/Linux/Debug/xenia-base-tests',
],
'depends_on': ['build-premake-debug-tests'],
},
{
'name': 'test-premake-release',
'image': image,
'volumes': [volume_build('premake')],
'commands': [
'./build/bin/Linux/Release/xenia-base-tests',
],
'depends_on': ['build-premake-release-tests'],
},
{
'name': 'test-cmake-release',
'image': image,
'volumes': [volume_build('cmake')],
'commands': [
'./build/bin/Linux/Release/xenia-base-tests',
],
'depends_on': ['build-cmake-release-tests'],
},
#
# Stat
#
{
'name': 'stat',
'image': image,
'volumes': [
volume_build('premake', '/build-premake'),
volume_build('cmake', '/build-cmake'),
],
'commands': [
'''
header() {
SEP='============================================================'
echo
echo $SEP
echo $@
echo $SEP
}
for v in premake cmake
do
for c in Debug Release
do
header $v $c
p=/build-$v/bin/Linux/$c
ls -la $p
sha256sum $p/*
done
done
'''
],
'depends_on': [
'build-premake-debug-all',
'build-cmake-debug-all',
] + ([
'build-premake-release-all',
'build-cmake-release-all',
] if build_release_all else [
'build-premake-release-tests',
'build-cmake-release-tests',
]),
},
],
}
def pipeline_android(name, image, arch, platform):
return {
'kind': 'pipeline',
'type': 'docker',
'name': name,
'platform': {
'os': 'linux',
'arch': arch,
},
'steps': [
#
# Setup the source tree
#
{
'name': 'clone-submodules',
'image': image,
'commands': [
'pwd',
# May miss recursive submodules (but faster than xb setup)
'git submodule update --init --depth 1 -j $(nproc)',
],
},
#
# Build premake and generate NDK makefiles
#
# NDK Makefiles
{
'name': 'toolchain',
'image': image,
'commands': [
'c++ --version',
'python3 --version',
'./xenia-build premake --target_os android',
],
'depends_on': ['clone-submodules'],
},
#
# Building
#
{
'name': 'build-debug',
'image': image,
'commands': [
'cd build',
command_ndk_build(platform, 'Debug', ' '.join(targets_android(platform))),
],
'depends_on': ['toolchain'],
},
{
'name': 'build-release',
'image': image,
'commands': [
'cd build',
command_ndk_build(platform, 'Release', ' '.join(targets_android(platform))),
],
'depends_on': ['toolchain'],
},
#
# Stat
#
{
'name': 'stat',
'image': image,
'commands': [
'''
header() {
SEP='============================================================'
echo
echo $SEP
echo $@
echo $SEP
}
for c in Debug Release
do
header $c
p=build/bin/$c/obj/local/*
ls -la $p
sha256sum $p/* || true
done
'''
],
'depends_on': [
'build-debug',
'build-release',
],
},
],
}

View File

@ -1,214 +0,0 @@
---
kind: pipeline
type: docker
name: lint
# Run this in a separate pipeline so that it will build even if this fails
steps:
- name: lint
image: xeniaproject/buildenv:2021-06-21
commands:
- clang-format --version
- ./xenia-build lint --all
---
kind: pipeline
type: docker
name: x86_64-linux
platform:
os: linux
arch: amd64
# Some expressions in this file are duplicates. Scripting support is
# available using jsonnet but increases complexity
# https://docs.drone.io/pipeline/scripting/jsonnet/
# These volumes will be mounted at the build directory, allowing to
# run different premake toolchains from the same source tree
volumes:
- name: build-premake
temp: {}
- name: build-cmake
temp: {}
steps:
#
# Setup the source tree
#
- name: clone-submodules
image: xeniaproject/buildenv:2021-06-21
commands:
- pwd
# May miss recursive submodules (but faster than xb setup)
- git submodule update --init --depth 1 -j $(nproc)
#
# Setup the two build systems
#
# Native premake Makefiles for production
- name: toolchain-premake
image: xeniaproject/buildenv:2021-06-21
volumes:
- name: build-premake
path: /drone/src/build
commands:
- $CXX --version
- $AR --version
- python3 --version
- ./xenia-build premake
depends_on:
- clone-submodules
# Development toolchain
- name: toolchain-cmake
image: xeniaproject/buildenv:2021-06-21
volumes:
- name: build-cmake
path: /drone/src/build
commands:
- |
./xenia-build premake --devenv=cmake
cd build
for c in Debug Release
do
mkdir cmake-$c
cd cmake-$c
cmake -DCMAKE_BUILD_TYPE=$c ..
cd ..
done
depends_on:
# Premake itself needs to be build first:
- toolchain-premake
#
# Building
#
- name: build-premake-debug-all
image: xeniaproject/buildenv:2021-06-21
volumes:
- name: build-premake
path: /drone/src/build
commands:
- ./xenia-build build --no_premake -j$(nproc) --config=Debug
depends_on:
- toolchain-premake
- name: build-premake-release-tests
image: xeniaproject/buildenv:2021-06-21
volumes:
- name: build-premake
path: /drone/src/build
commands:
- ./xenia-build build --no_premake -j$(nproc) --config=Release --target=xenia-base-tests
depends_on:
- toolchain-premake
- name: build-premake-release-all
image: xeniaproject/buildenv:2021-06-21
volumes:
- name: build-premake
path: /drone/src/build
commands:
- ./xenia-build build --no_premake -j$(nproc) --config=Release
depends_on:
- build-premake-release-tests
- name: build-cmake-debug-all
image: xeniaproject/buildenv:2021-06-21
volumes:
- name: build-cmake
path: /drone/src/build
commands:
- cd build/cmake-Debug
- cmake --build . -j$(nproc)
depends_on:
- toolchain-cmake
- name: build-cmake-release-tests
image: xeniaproject/buildenv:2021-06-21
volumes:
- name: build-cmake
path: /drone/src/build
commands:
- cd build/cmake-Release
- cmake --build . -j$(nproc) --target xenia-base-tests
depends_on:
- toolchain-cmake
- name: build-cmake-release-all
image: xeniaproject/buildenv:2021-06-21
volumes:
- name: build-cmake
path: /drone/src/build
commands:
- cd build/cmake-Release
- cmake --build . -j$(nproc)
depends_on:
- build-cmake-release-tests
#
# Tests
#
- name: test-premake
image: xeniaproject/buildenv:2021-06-21
volumes:
- name: build-premake
path: /drone/src/build
commands:
- ./build/bin/Linux/Release/xenia-base-tests
depends_on:
- build-premake-release-tests
- name: test-cmake
image: xeniaproject/buildenv:2021-06-21
volumes:
- name: build-cmake
path: /drone/src/build
commands:
- ./build/bin/Linux/Release/xenia-base-tests
depends_on:
- build-cmake-release-tests
#
# Stat
#
- name: stat
image: xeniaproject/buildenv:2021-06-21
volumes:
- name: build-premake
path: /build-premake
- name: build-cmake
path: /build-cmake
commands:
- |
header() {
SEP='============================================================'
echo
echo $SEP
echo $@
echo $SEP
}
for v in premake cmake
do
for c in Debug Release
do
header $v $c
p=/build-$v/bin/Linux/$c
ls -la $p
sha256sum $p/*
done
done
depends_on:
- build-premake-debug-all
- build-premake-release-all
- build-cmake-debug-all
- build-cmake-release-all

2
.github/FUNDING.yml vendored
View File

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

View File

@ -1,32 +0,0 @@
---
name: Bug report
about: Template for bug reports.
title: ''
labels: ''
assignees: ''
---
<!--
# THIS IS NOT A SUPPORT FORUM! For support, first read the wiki:
# https://github.com/xenia-project/xenia/wiki
#
# If your question wasn't answered there or you need help, proceed here:
# Xenia Discord (#help) - https://discord.gg/Q9mxZf9
# /r/xenia (questions thread) - https://www.reddit.com/r/xenia/
#
# DO NOT CREATE ISSUES ABOUT SPECIFIC GAMES IN THIS REPOSITORY!
# a game specific issue would be e.g. "Game X crashes after you hit a character a certain way"
# A Xenia issue would be e.g. "Kernel export NtDoSomething does nothing"
# For specific games, visit https://github.com/xenia-project/game-compatibility#game-compatibility
#
# Try to create a very concise title that's straight to the point
-->
[//]: # (Describe what's going wrong:)
[//]: # (Describe what should happen:)
[//]: # (If applicable, provide a callstack here - esp. for crashes)
[//]: # (If applicable, upload a logfile and link it here)

54
.github/ISSUE_TEMPLATE/bug_report.yaml vendored Normal file
View File

@ -0,0 +1,54 @@
name: Bug report
description: Template for bug reports.
title: 'Bug: '
body:
- type: markdown
attributes:
value: |
Try to create a very concise title that's straight to the point.
**THIS IS NOT A SUPPORT FORUM!** For support, first read the wiki: https://github.com/xenia-project/xenia/wiki
If your question wasn't answered there or you need help, proceed to #help in the Discord server: https://discord.gg/Q9mxZf9
DO NOT CREATE ISSUES ABOUT SPECIFIC GAMES IN THIS REPOSITORY!
A game specific issue would be e.g. "Game X crashes after you hit a character a certain way"
A Xenia issue would be e.g. "Kernel export NtDoSomething does nothing"
For specific games, visit https://github.com/xenia-project/game-compatibility#game-compatibility
- type: checkboxes
id: validation
attributes:
label: Validation
options:
- label: I've read the [FAQ](https://github.com/xenia-project/xenia/wiki/FAQ).
required: true
- label: The Xenia build used is from the master branch (not MLBS/AlexVS/Canary/pull requests, etc.)
required: true
- label: This issue isn't for tech support (help with Xenia).
required: true
- label: If this issue occurs in a specific game, I've done analysis to locate the faulty subsystem of the emulator and a potential reason in it.
required: true
- label: I've checked if this issue hasn't already been reported.
required: true
- label: 'My device meets the minimum requirements: https://github.com/xenia-project/xenia/wiki/Quickstart#system-requirements'
required: true
- label: '(If building) I have read the building doc: https://github.com/xenia-project/xenia/blob/master/docs/building.md'
- type: textarea
id: problem
attributes:
label: Describe what's going wrong
validations:
required: true
- type: textarea
id: what-should-happen
attributes:
label: Describe what should happen
validations:
required: true
- type: textarea
id: callstack
attributes:
label: If applicable, provide a callstack here, especially for crashes
- type: textarea
id: logfile
attributes:
label: If applicable, upload a logfile and link it here

6
.gitmodules vendored
View File

@ -67,6 +67,12 @@
[submodule "third_party/premake-androidndk"]
path = third_party/premake-androidndk
url = https://github.com/Triang3l/premake-androidndk.git
[submodule "third_party/FidelityFX-CAS"]
path = third_party/FidelityFX-CAS
url = https://github.com/GPUOpen-Effects/FidelityFX-CAS.git
[submodule "third_party/FidelityFX-FSR"]
path = third_party/FidelityFX-FSR
url = https://github.com/GPUOpen-Effects/FidelityFX-FSR.git
[submodule "third_party/glslang"]
path = third_party/glslang
url = https://github.com/KhronosGroup/glslang.git

View File

@ -21,9 +21,9 @@ Discussing illegal activities will get you banned.
## Status
Buildbot | Status
-------- | ------
[Windows](https://ci.appveyor.com/project/benvanik/xenia/branch/master) | [![Build status](https://ci.appveyor.com/api/projects/status/ftqiy86kdfawyx3a/branch/master?svg=true)](https://ci.appveyor.com/project/benvanik/xenia/branch/master)
Buildbot | Status | Releases
-------- | ------ | --------
[Windows](https://ci.appveyor.com/project/benvanik/xenia/branch/master) | [![Build status](https://ci.appveyor.com/api/projects/status/ftqiy86kdfawyx3a/branch/master?svg=true)](https://ci.appveyor.com/project/benvanik/xenia/branch/master) | [Latest](https://github.com/xenia-project/release-builds-windows/releases/latest) ◦ [All](https://github.com/xenia-project/release-builds-windows/releases)
[Linux](https://cloud.drone.io/xenia-project/xenia) | [![Build status](https://cloud.drone.io/api/badges/xenia-project/xenia/status.svg)](https://cloud.drone.io/xenia-project/xenia)
Quite a few real games run. Quite a few don't.
@ -61,7 +61,7 @@ Fixes and optimizations are always welcome (please!), but in addition to
that there are some major work areas still untouched:
* Help work through [missing functionality/bugs in games](https://github.com/xenia-project/xenia/labels/compat)
* Add input drivers for [third-party controllers](https://github.com/xenia-project/xenia/issues/1333)
* Reduce the size of Xenia's [huge log files](https://github.com/xenia-project/xenia/issues/1526)
* Skilled with Linux? A strong contributor is needed to [help with porting](https://github.com/xenia-project/xenia/labels/platform-linux)
See more projects [good for contributors](https://github.com/xenia-project/xenia/labels/good%20first%20issue). It's a good idea to ask on Discord and check the issues page before beginning work on

View File

@ -4,7 +4,6 @@ plugins {
android {
compileSdkVersion 30
buildToolsVersion '30.0.2'
ndkVersion '23.0.7599858'
defaultConfig {
@ -82,4 +81,8 @@ android {
path file('../../../build/xenia.wks.Android.mk')
}
}
}
dependencies {
implementation 'org.jetbrains:annotations:15.0'
}

View File

@ -29,7 +29,9 @@
android:supportsRtl="true"
android:theme="@android:style/Theme.Material.Light">
<activity android:name="jp.xenia.emulator.WindowDemoActivity">
<activity
android:name="jp.xenia.emulator.WindowDemoActivity"
android:label="@string/activity_label_window_demo">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />

View File

@ -0,0 +1,21 @@
package jp.xenia;
/**
* Base class for all unchecked exceptions thrown by the Xenia project components.
*/
public class XeniaRuntimeException extends RuntimeException {
public XeniaRuntimeException() {
}
public XeniaRuntimeException(final String name) {
super(name);
}
public XeniaRuntimeException(final String name, final Throwable cause) {
super(name, cause);
}
public XeniaRuntimeException(final Exception cause) {
super(cause);
}
}

View File

@ -1,8 +1,18 @@
package jp.xenia.emulator;
import android.os.Bundle;
public class WindowDemoActivity extends WindowedAppActivity {
@Override
protected String getWindowedAppIdentifier() {
return "xenia_ui_window_vulkan_demo";
}
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_window_demo);
setWindowSurfaceView(findViewById(R.id.window_demo_surface_view));
}
}

View File

@ -0,0 +1,42 @@
package jp.xenia.emulator;
import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.SurfaceView;
public class WindowSurfaceView extends SurfaceView {
public WindowSurfaceView(final Context context) {
super(context);
// Native drawing is invoked from onDraw.
setWillNotDraw(false);
}
public WindowSurfaceView(final Context context, final AttributeSet attrs) {
super(context, attrs);
setWillNotDraw(false);
}
public WindowSurfaceView(
final Context context, final AttributeSet attrs, final int defStyleAttr) {
super(context, attrs, defStyleAttr);
setWillNotDraw(false);
}
public WindowSurfaceView(
final Context context, final AttributeSet attrs, final int defStyleAttr,
final int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
setWillNotDraw(false);
}
@Override
protected void onDraw(final Canvas canvas) {
final Context context = getContext();
if (!(context instanceof WindowedAppActivity)) {
return;
}
final WindowedAppActivity activity = (WindowedAppActivity) context;
activity.onWindowSurfaceDraw(false);
}
}

View File

@ -3,43 +3,158 @@ package jp.xenia.emulator;
import android.app.Activity;
import android.content.res.AssetManager;
import android.os.Bundle;
import android.util.Log;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.View;
import org.jetbrains.annotations.Nullable;
import jp.xenia.XeniaRuntimeException;
public abstract class WindowedAppActivity extends Activity {
private static final String TAG = "WindowedAppActivity";
static {
// TODO(Triang3l): Move all demos to libxenia.so.
System.loadLibrary("xenia-ui-window-vulkan-demo");
}
private long mAppContext;
private final WindowSurfaceOnLayoutChangeListener mWindowSurfaceOnLayoutChangeListener =
new WindowSurfaceOnLayoutChangeListener();
private final WindowSurfaceHolderCallback mWindowSurfaceHolderCallback =
new WindowSurfaceHolderCallback();
private native long initializeWindowedAppOnCreateNative(
// May be 0 while destroying (mainly while the superclass is).
private long mAppContext = 0;
@Nullable
private WindowSurfaceView mWindowSurfaceView = null;
private native long initializeWindowedAppOnCreate(
String windowedAppIdentifier, AssetManager assetManager);
private native void onDestroyNative(long appContext);
private native void onWindowSurfaceLayoutChange(
long appContext, int left, int top, int right, int bottom);
private native void onWindowSurfaceChanged(long appContext, Surface windowSurface);
private native void paintWindow(long appContext, boolean forcePaint);
protected abstract String getWindowedAppIdentifier();
protected void setWindowSurfaceView(@Nullable final WindowSurfaceView windowSurfaceView) {
if (mWindowSurfaceView == windowSurfaceView) {
return;
}
// Detach from the old surface.
if (mWindowSurfaceView != null) {
mWindowSurfaceView.getHolder().removeCallback(mWindowSurfaceHolderCallback);
mWindowSurfaceView.removeOnLayoutChangeListener(mWindowSurfaceOnLayoutChangeListener);
mWindowSurfaceView = null;
if (mAppContext != 0) {
onWindowSurfaceChanged(mAppContext, null);
}
}
if (windowSurfaceView == null) {
return;
}
mWindowSurfaceView = windowSurfaceView;
// The native window code assumes that, when the surface exists, it covers the entire
// window.
// FIXME(Triang3l): This doesn't work if the layout has already been performed.
mWindowSurfaceView.addOnLayoutChangeListener(mWindowSurfaceOnLayoutChangeListener);
final SurfaceHolder windowSurfaceHolder = mWindowSurfaceView.getHolder();
windowSurfaceHolder.addCallback(mWindowSurfaceHolderCallback);
// If setting after the creation of the surface.
if (mAppContext != 0) {
final Surface windowSurface = windowSurfaceHolder.getSurface();
if (windowSurface != null) {
onWindowSurfaceChanged(mAppContext, windowSurface);
}
}
}
public void onWindowSurfaceDraw(final boolean forcePaint) {
if (mAppContext == 0) {
return;
}
paintWindow(mAppContext, forcePaint);
}
// Used from the native WindowedAppContext. May be called from non-UI threads.
protected void postInvalidateWindowSurface() {
if (mWindowSurfaceView == null) {
return;
}
mWindowSurfaceView.postInvalidate();
}
@Override
protected void onCreate(Bundle savedInstanceState) {
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mAppContext = initializeWindowedAppOnCreateNative(getWindowedAppIdentifier(), getAssets());
final String windowedAppIdentifier = getWindowedAppIdentifier();
mAppContext = initializeWindowedAppOnCreate(windowedAppIdentifier, getAssets());
if (mAppContext == 0) {
Log.e(TAG, "Error initializing the windowed app");
finish();
return;
throw new XeniaRuntimeException(
"Error initializing the windowed app " + windowedAppIdentifier);
}
}
@Override
protected void onDestroy() {
setWindowSurfaceView(null);
if (mAppContext != 0) {
onDestroyNative(mAppContext);
}
mAppContext = 0;
super.onDestroy();
}
private class WindowSurfaceOnLayoutChangeListener implements View.OnLayoutChangeListener {
@Override
public void onLayoutChange(
final View v, final int left, final int top, final int right, final int bottom,
final int oldLeft, final int oldTop, final int oldRight, final int oldBottom) {
if (mAppContext != 0) {
onWindowSurfaceLayoutChange(mAppContext, left, top, right, bottom);
}
}
}
private class WindowSurfaceHolderCallback implements SurfaceHolder.Callback2 {
@Override
public void surfaceCreated(final SurfaceHolder holder) {
if (mAppContext == 0) {
return;
}
onWindowSurfaceChanged(mAppContext, holder.getSurface());
}
@Override
public void surfaceChanged(
final SurfaceHolder holder, final int format, final int width, final int height) {
if (mAppContext == 0) {
return;
}
onWindowSurfaceChanged(mAppContext, holder.getSurface());
}
@Override
public void surfaceDestroyed(final SurfaceHolder holder) {
if (mAppContext == 0) {
return;
}
onWindowSurfaceChanged(mAppContext, null);
}
@Override
public void surfaceRedrawNeeded(final SurfaceHolder holder) {
onWindowSurfaceDraw(true);
}
}
}

View File

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

View File

@ -1,3 +1,4 @@
<resources>
<string name="app_name">Xenia</string>
<string name="activity_label_window_demo">Xenia Window Demo</string>
</resources>

View File

@ -5,7 +5,7 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.0.3'
classpath 'com.android.tools.build:gradle:7.1.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files

View File

@ -2,13 +2,20 @@
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2020 Ben Vanik. All rights reserved. *
* Copyright 2022 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/app/emulator_window.h"
#include <filesystem>
#include <functional>
#include <memory>
#include <mutex>
#include <string>
#include <utility>
#include "third_party/fmt/include/fmt/format.h"
#include "third_party/imgui/imgui.h"
#include "xenia/base/assert.h"
@ -20,11 +27,17 @@
#include "xenia/base/profiling.h"
#include "xenia/base/system.h"
#include "xenia/base/threading.h"
#include "xenia/cpu/processor.h"
#include "xenia/emulator.h"
#include "xenia/gpu/command_processor.h"
#include "xenia/gpu/graphics_system.h"
#include "xenia/ui/file_picker.h"
#include "xenia/ui/graphics_provider.h"
#include "xenia/ui/imgui_dialog.h"
#include "xenia/ui/imgui_drawer.h"
#include "xenia/ui/immediate_drawer.h"
#include "xenia/ui/presenter.h"
#include "xenia/ui/ui_event.h"
#include "xenia/ui/virtual_key.h"
// Autogenerated by `xb premake`.
@ -32,13 +45,91 @@
DECLARE_bool(debug);
DEFINE_bool(fullscreen, false, "Whether to launch the emulator in fullscreen.",
"Display");
DEFINE_string(
postprocess_antialiasing, "",
"Post-processing anti-aliasing effect to apply to the image output of the "
"game.\n"
"Using post-process anti-aliasing is heavily recommended when AMD "
"FidelityFX Contrast Adaptive Sharpening or Super Resolution 1.0 is "
"active.\n"
"Use: [none, fxaa, fxaa_extreme]\n"
" none (or any value not listed here):\n"
" Don't alter the original image.\n"
" fxaa:\n"
" NVIDIA Fast Approximate Anti-Aliasing 3.11, normal quality preset (12)."
"\n"
" fxaa_extreme:\n"
" NVIDIA Fast Approximate Anti-Aliasing 3.11, extreme quality preset "
"(39).",
"Display");
DEFINE_string(
postprocess_scaling_and_sharpening, "",
"Post-processing effect to use for resampling and/or sharpening of the "
"final display output.\n"
"Use: [bilinear, cas, fsr]\n"
" bilinear (or any value not listed here):\n"
" Original image at 1:1, simple bilinear stretching for resampling.\n"
" cas:\n"
" Use AMD FidelityFX Contrast Adaptive Sharpening (CAS) for sharpening "
"at scaling factors of up to 2x2, with additional bilinear stretching for "
"larger factors.\n"
" fsr:\n"
" Use AMD FidelityFX Super Resolution 1.0 (FSR) for highest-quality "
"upscaling, or AMD FidelityFX Contrast Adaptive Sharpening for sharpening "
"while not scaling or downsampling.\n"
" For scaling by factors of more than 2x2, multiple FSR passes are done.",
"Display");
DEFINE_double(
postprocess_ffx_cas_additional_sharpness,
xe::ui::Presenter::GuestOutputPaintConfig::kCasAdditionalSharpnessDefault,
"Additional sharpness for AMD FidelityFX Contrast Adaptive Sharpening "
"(CAS), from 0 to 1.\n"
"Higher is sharper.",
"Display");
DEFINE_uint32(
postprocess_ffx_fsr_max_upsampling_passes,
xe::ui::Presenter::GuestOutputPaintConfig::kFsrMaxUpscalingPassesMax,
"Maximum number of upsampling passes performed in AMD FidelityFX Super "
"Resolution 1.0 (FSR) before falling back to bilinear stretching after the "
"final pass.\n"
"Each pass upscales only to up to 2x2 the previous size. If the game "
"outputs a 1280x720 image, 1 pass will upscale it to up to 2560x1440 "
"(below 4K), after 2 passes it will be upscaled to a maximum of 5120x2880 "
"(including 3840x2160 for 4K), and so on.\n"
"This variable has no effect if the display resolution isn't very high, "
"but may be reduced on resolutions like 4K or 8K in case the performance "
"impact of multiple FSR upsampling passes is too high, or if softer edges "
"are desired.\n"
"The default value is the maximum internally supported by Xenia.",
"Display");
DEFINE_double(
postprocess_ffx_fsr_sharpness_reduction,
xe::ui::Presenter::GuestOutputPaintConfig::kFsrSharpnessReductionDefault,
"Sharpness reduction for AMD FidelityFX Super Resolution 1.0 (FSR), in "
"stops.\n"
"Lower is sharper.",
"Display");
// Dithering to 8bpc is enabled by default since the effect is minor, only
// effects what can't be shown normally by host displays, and nothing is changed
// by it for 8bpc source without resampling.
DEFINE_bool(
postprocess_dither, true,
"Dither the final image output from the internal precision to 8 bits per "
"channel so gradients are smoother.\n"
"On a 10bpc display, the lower 2 bits will still be kept, but noise will "
"be added to them - disabling may be recommended for 10bpc, but it "
"depends on the 10bpc displaying capabilities of the actual display used.",
"Display");
namespace xe {
namespace app {
using xe::ui::FileDropEvent;
using xe::ui::KeyEvent;
using xe::ui::MenuItem;
using xe::ui::MouseEvent;
using xe::ui::UIEvent;
const std::string kBaseTitle = "Xenia";
@ -47,7 +138,12 @@ EmulatorWindow::EmulatorWindow(Emulator* emulator,
ui::WindowedAppContext& app_context)
: emulator_(emulator),
app_context_(app_context),
window_(ui::Window::Create(app_context, kBaseTitle)) {
window_listener_(*this),
window_(ui::Window::Create(app_context, kBaseTitle, 1280, 720)),
imgui_drawer_(
std::make_unique<ui::ImGuiDrawer>(window_.get(), kZOrderImGui)),
display_config_game_config_load_callback_(
new DisplayConfigGameConfigLoadCallback(*emulator, *this)) {
base_title_ = kBaseTitle +
#ifdef DEBUG
#if _NO_DEBUG_HEAP == 1
@ -76,107 +172,331 @@ std::unique_ptr<EmulatorWindow> EmulatorWindow::Create(
return emulator_window;
}
bool EmulatorWindow::Initialize() {
if (!window_->Initialize()) {
XELOGE("Failed to initialize platform window");
return false;
EmulatorWindow::~EmulatorWindow() {
// Notify the ImGui drawer that the immediate drawer is being destroyed.
ShutdownGraphicsSystemPresenterPainting();
}
ui::Presenter* EmulatorWindow::GetGraphicsSystemPresenter() const {
gpu::GraphicsSystem* graphics_system = emulator_->graphics_system();
return graphics_system ? graphics_system->presenter() : nullptr;
}
void EmulatorWindow::SetupGraphicsSystemPresenterPainting() {
ShutdownGraphicsSystemPresenterPainting();
if (!window_) {
return;
}
UpdateTitle();
ui::Presenter* presenter = GetGraphicsSystemPresenter();
if (!presenter) {
return;
}
window_->on_closed.AddListener(
[this](UIEvent* e) { app_context_.QuitFromUIThread(); });
ApplyDisplayConfigForCvars();
window_->on_file_drop.AddListener(
[this](FileDropEvent* e) { FileDrop(e->filename()); });
window_->SetPresenter(presenter);
window_->on_key_down.AddListener([this](KeyEvent* e) {
bool handled = true;
switch (e->virtual_key()) {
case ui::VirtualKey::kO: {
if (e->is_ctrl_pressed()) {
FileOpen();
}
} break;
case ui::VirtualKey::kMultiply: {
CpuTimeScalarReset();
} break;
case ui::VirtualKey::kSubtract: {
CpuTimeScalarSetHalf();
} break;
case ui::VirtualKey::kAdd: {
CpuTimeScalarSetDouble();
} break;
immediate_drawer_ =
emulator_->graphics_system()->provider()->CreateImmediateDrawer();
if (immediate_drawer_) {
immediate_drawer_->SetPresenter(presenter);
imgui_drawer_->SetPresenterAndImmediateDrawer(presenter,
immediate_drawer_.get());
Profiler::SetUserIO(kZOrderProfiler, window_.get(), presenter,
immediate_drawer_.get());
}
}
case ui::VirtualKey::kF3: {
Profiler::ToggleDisplay();
} break;
void EmulatorWindow::ShutdownGraphicsSystemPresenterPainting() {
Profiler::SetUserIO(kZOrderProfiler, window_.get(), nullptr, nullptr);
imgui_drawer_->SetPresenterAndImmediateDrawer(nullptr, nullptr);
immediate_drawer_.reset();
if (window_) {
window_->SetPresenter(nullptr);
}
}
case ui::VirtualKey::kF4: {
GpuTraceFrame();
} break;
case ui::VirtualKey::kF5: {
GpuClearCaches();
} break;
case ui::VirtualKey::kF7: {
// Save to file
// TODO: Choose path based on user input, or from options
// TODO: Spawn a new thread to do this.
emulator()->SaveToFile("test.sav");
} break;
case ui::VirtualKey::kF8: {
// Restore from file
// TODO: Choose path from user
// TODO: Spawn a new thread to do this.
emulator()->RestoreFromFile("test.sav");
} break;
case ui::VirtualKey::kF11: {
ToggleFullscreen();
} break;
case ui::VirtualKey::kEscape: {
// Allow users to escape fullscreen (but not enter it).
if (window_->is_fullscreen()) {
window_->ToggleFullscreen(false);
} else {
handled = false;
}
} break;
void EmulatorWindow::OnEmulatorInitialized() {
emulator_initialized_ = true;
window_->SetMainMenuEnabled(true);
// When the user can see that the emulator isn't initializing anymore (the
// menu isn't disabled), enter fullscreen if requested.
if (cvars::fullscreen) {
window_->SetFullscreen(true);
}
}
case ui::VirtualKey::kPause: {
CpuBreakIntoDebugger();
} break;
case ui::VirtualKey::kCancel: {
CpuBreakIntoHostDebugger();
} break;
void EmulatorWindow::EmulatorWindowListener::OnClosing(ui::UIEvent& e) {
emulator_window_.app_context_.QuitFromUIThread();
}
case ui::VirtualKey::kF1: {
ShowHelpWebsite();
} break;
void EmulatorWindow::EmulatorWindowListener::OnFileDrop(ui::FileDropEvent& e) {
emulator_window_.FileDrop(e.filename());
}
case ui::VirtualKey::kF2: {
ShowCommitID();
} break;
void EmulatorWindow::EmulatorWindowListener::OnKeyDown(ui::KeyEvent& e) {
emulator_window_.OnKeyDown(e);
}
default: {
handled = false;
} break;
}
e->set_handled(handled);
});
void EmulatorWindow::DisplayConfigGameConfigLoadCallback::PostGameConfigLoad() {
emulator_window_.ApplyDisplayConfigForCvars();
}
window_->on_mouse_move.AddListener([this](MouseEvent* e) {
if (window_->is_fullscreen() && (e->dx() > 2 || e->dy() > 2)) {
if (!window_->is_cursor_visible()) {
window_->set_cursor_visible(true);
void EmulatorWindow::DisplayConfigDialog::OnDraw(ImGuiIO& io) {
gpu::GraphicsSystem* graphics_system =
emulator_window_.emulator_->graphics_system();
if (!graphics_system) {
return;
}
// In the top-left corner so it's close to the menu bar from where it was
// opened.
// Origin Y coordinate 20 was taken from the Dear ImGui demo.
ImGui::SetNextWindowPos(ImVec2(20, 20), ImGuiCond_FirstUseEver);
ImGui::SetNextWindowSize(ImVec2(20, 20), ImGuiCond_FirstUseEver);
// Alpha from Dear ImGui tooltips (0.35 from the overlay provides too low
// visibility). Translucent so some effect of the changes can still be seen
// through it.
ImGui::SetNextWindowBgAlpha(0.6f);
bool dialog_open = true;
if (!ImGui::Begin("Post-processing", &dialog_open,
ImGuiWindowFlags_NoCollapse |
ImGuiWindowFlags_AlwaysAutoResize |
ImGuiWindowFlags_HorizontalScrollbar)) {
ImGui::End();
return;
}
// Even if the close button has been pressed, still paint everything not to
// have one frame with an empty window.
// Prevent user confusion which has been reported multiple times.
ImGui::TextUnformatted("All effects can be used on GPUs of any brand.");
ImGui::Spacing();
gpu::CommandProcessor* command_processor =
graphics_system->command_processor();
if (command_processor) {
if (ImGui::TreeNodeEx(
"Anti-aliasing",
ImGuiTreeNodeFlags_Framed | ImGuiTreeNodeFlags_DefaultOpen)) {
gpu::CommandProcessor::SwapPostEffect current_swap_post_effect =
command_processor->GetDesiredSwapPostEffect();
int new_swap_post_effect_index = int(current_swap_post_effect);
ImGui::RadioButton("None", &new_swap_post_effect_index,
int(gpu::CommandProcessor::SwapPostEffect::kNone));
ImGui::RadioButton(
"NVIDIA Fast Approximate Anti-Aliasing 3.11 (FXAA), normal quality",
&new_swap_post_effect_index,
int(gpu::CommandProcessor::SwapPostEffect::kFxaa));
ImGui::RadioButton(
"NVIDIA Fast Approximate Anti-Aliasing 3.11 (FXAA), extreme quality",
&new_swap_post_effect_index,
int(gpu::CommandProcessor::SwapPostEffect::kFxaaExtreme));
gpu::CommandProcessor::SwapPostEffect new_swap_post_effect =
gpu::CommandProcessor::SwapPostEffect(new_swap_post_effect_index);
if (current_swap_post_effect != new_swap_post_effect) {
command_processor->SetDesiredSwapPostEffect(new_swap_post_effect);
}
cursor_hide_time_ = Clock::QueryHostSystemTime() + 30000000;
// Override the values in the cvars to save them to the config at exit if
// the user has set them to anything new.
if (GetSwapPostEffectForCvarValue(cvars::postprocess_antialiasing) !=
new_swap_post_effect) {
OVERRIDE_string(postprocess_antialiasing,
GetCvarValueForSwapPostEffect(new_swap_post_effect));
}
ImGui::TreePop();
}
}
ui::Presenter* presenter = graphics_system->presenter();
if (presenter) {
const ui::Presenter::GuestOutputPaintConfig& current_presenter_config =
presenter->GetGuestOutputPaintConfigFromUIThread();
ui::Presenter::GuestOutputPaintConfig new_presenter_config =
current_presenter_config;
if (ImGui::TreeNodeEx(
"Resampling and sharpening",
ImGuiTreeNodeFlags_Framed | ImGuiTreeNodeFlags_DefaultOpen)) {
// Filtering effect.
int new_effect_index = int(new_presenter_config.GetEffect());
ImGui::RadioButton(
"None / bilinear", &new_effect_index,
int(ui::Presenter::GuestOutputPaintConfig::Effect::kBilinear));
ImGui::RadioButton(
"AMD FidelityFX Contrast Adaptive Sharpening (CAS)",
&new_effect_index,
int(ui::Presenter::GuestOutputPaintConfig::Effect::kCas));
ImGui::RadioButton(
"AMD FidelityFX Super Resolution 1.0 (FSR)", &new_effect_index,
int(ui::Presenter::GuestOutputPaintConfig::Effect::kFsr));
new_presenter_config.SetEffect(
ui::Presenter::GuestOutputPaintConfig::Effect(new_effect_index));
// effect_description must be one complete, but short enough, sentence per
// line, as TextWrapped doesn't work correctly in auto-resizing windows
// (in the initial frames, the window becomes extremely tall, and widgets
// added after the wrapped text have no effect on the width of the text).
const char* effect_description = nullptr;
switch (new_presenter_config.GetEffect()) {
case ui::Presenter::GuestOutputPaintConfig::Effect::kBilinear:
effect_description =
"Simple bilinear filtering is done if resampling is needed.\n"
"Otherwise, only anti-aliasing is done if enabled, or displaying "
"as is.";
break;
case ui::Presenter::GuestOutputPaintConfig::Effect::kCas:
effect_description =
"Sharpening and resampling to up to 2x2 to improve the fidelity "
"of details.\n"
"For scaling by more than 2x2, bilinear stretching is done "
"afterwards.";
break;
case ui::Presenter::GuestOutputPaintConfig::Effect::kFsr:
effect_description =
"High-quality edge-preserving upscaling to arbitrary target "
"resolutions.\n"
"For scaling by more than 2x2, multiple upsampling passes are "
"done.\n"
"If not upscaling, Contrast Adaptive Sharpening (CAS) is used "
"instead.";
break;
}
if (effect_description) {
ImGui::TextUnformatted(effect_description);
}
if (new_presenter_config.GetEffect() ==
ui::Presenter::GuestOutputPaintConfig::Effect::kCas ||
new_presenter_config.GetEffect() ==
ui::Presenter::GuestOutputPaintConfig::Effect::kFsr) {
if (effect_description) {
ImGui::Spacing();
}
ImGui::TextUnformatted(
"FXAA is highly recommended when using CAS or FSR.");
ImGui::Spacing();
// 2 decimal places is more or less enough precision for the sharpness
// given the minor visual effect of small changes, the width of the
// slider, and readability convenience (2 decimal places is like an
// integer percentage). However, because Dear ImGui parses the string
// representation of the number and snaps the value to it internally,
// 2 decimal places actually offer less precision than the slider itself
// does. This is especially prominent in the low range of the non-linear
// FSR sharpness reduction slider. 3 decimal places are optimal in this
// case.
if (new_presenter_config.GetEffect() ==
ui::Presenter::GuestOutputPaintConfig::Effect::kFsr) {
float fsr_sharpness_reduction =
new_presenter_config.GetFsrSharpnessReduction();
ImGui::TextUnformatted(
"FSR sharpness reduction when upscaling (lower is sharper):");
// Power 2.0 as the reduction is in stops, used in exp2.
ImGui::SliderFloat(
"##FSRSharpnessReduction", &fsr_sharpness_reduction,
ui::Presenter::GuestOutputPaintConfig::kFsrSharpnessReductionMin,
ui::Presenter::GuestOutputPaintConfig::kFsrSharpnessReductionMax,
"%.3f stops", 2.0f);
ImGui::SameLine();
if (ImGui::Button("Reset##ResetFSRSharpnessReduction")) {
fsr_sharpness_reduction = ui::Presenter::GuestOutputPaintConfig ::
kFsrSharpnessReductionDefault;
}
new_presenter_config.SetFsrSharpnessReduction(
fsr_sharpness_reduction);
}
float cas_additional_sharpness =
new_presenter_config.GetCasAdditionalSharpness();
ImGui::TextUnformatted(
new_presenter_config.GetEffect() ==
ui::Presenter::GuestOutputPaintConfig::Effect::kFsr
? "CAS additional sharpness when not upscaling (higher is "
"sharper):"
: "CAS additional sharpness (higher is sharper):");
ImGui::SliderFloat(
"##CASAdditionalSharpness", &cas_additional_sharpness,
ui::Presenter::GuestOutputPaintConfig::kCasAdditionalSharpnessMin,
ui::Presenter::GuestOutputPaintConfig::kCasAdditionalSharpnessMax,
"%.3f");
ImGui::SameLine();
if (ImGui::Button("Reset##ResetCASAdditionalSharpness")) {
cas_additional_sharpness = ui::Presenter::GuestOutputPaintConfig ::
kCasAdditionalSharpnessDefault;
}
new_presenter_config.SetCasAdditionalSharpness(
cas_additional_sharpness);
// There's no need to expose the setting for the maximum number of FSR
// EASU passes as it's largely meaningless if the user doesn't have a
// very high-resolution monitor compared to the original image size as
// most of the values of the slider will have no effect, and that's just
// very fine-grained performance control for a fixed-overhead pass only
// for huge screen resolutions.
}
ImGui::TreePop();
}
e->set_handled(false);
});
if (ImGui::TreeNodeEx("Dithering", ImGuiTreeNodeFlags_Framed |
ImGuiTreeNodeFlags_DefaultOpen)) {
bool dither = current_presenter_config.GetDither();
ImGui::Checkbox(
"Dither the final output to 8bpc to make gradients smoother",
&dither);
new_presenter_config.SetDither(dither);
window_->on_paint.AddListener([this](UIEvent* e) { CheckHideCursor(); });
ImGui::TreePop();
}
presenter->SetGuestOutputPaintConfigFromUIThread(new_presenter_config);
// Override the values in the cvars to save them to the config at exit if
// the user has set them to anything new.
ui::Presenter::GuestOutputPaintConfig cvars_presenter_config =
GetGuestOutputPaintConfigForCvars();
if (cvars_presenter_config.GetEffect() !=
new_presenter_config.GetEffect()) {
OVERRIDE_string(postprocess_scaling_and_sharpening,
GetCvarValueForGuestOutputPaintEffect(
new_presenter_config.GetEffect()));
}
if (cvars_presenter_config.GetCasAdditionalSharpness() !=
new_presenter_config.GetCasAdditionalSharpness()) {
OVERRIDE_double(postprocess_ffx_cas_additional_sharpness,
new_presenter_config.GetCasAdditionalSharpness());
}
if (cvars_presenter_config.GetFsrSharpnessReduction() !=
new_presenter_config.GetFsrSharpnessReduction()) {
OVERRIDE_double(postprocess_ffx_fsr_sharpness_reduction,
new_presenter_config.GetFsrSharpnessReduction());
}
if (cvars_presenter_config.GetDither() !=
new_presenter_config.GetDither()) {
OVERRIDE_bool(postprocess_dither, new_presenter_config.GetDither());
}
}
ImGui::End();
if (!dialog_open) {
emulator_window_.ToggleDisplayConfigDialog();
// `this` might have been destroyed by ToggleDisplayConfigDialog.
return;
}
}
bool EmulatorWindow::Initialize() {
window_->AddListener(&window_listener_);
window_->AddInputListener(&window_listener_, kZOrderEmulatorWindowInput);
// Main menu.
// FIXME: This code is really messy.
@ -186,17 +506,19 @@ bool EmulatorWindow::Initialize() {
file_menu->AddChild(
MenuItem::Create(MenuItem::Type::kString, "&Open...", "Ctrl+O",
std::bind(&EmulatorWindow::FileOpen, this)));
#ifdef DEBUG
file_menu->AddChild(
MenuItem::Create(MenuItem::Type::kString, "Close",
std::bind(&EmulatorWindow::FileClose, this)));
#endif // #ifdef DEBUG
file_menu->AddChild(MenuItem::Create(MenuItem::Type::kSeparator));
file_menu->AddChild(MenuItem::Create(
MenuItem::Type::kString, "Show content directory...",
std::bind(&EmulatorWindow::ShowContentDirectory, this)));
file_menu->AddChild(MenuItem::Create(MenuItem::Type::kSeparator));
file_menu->AddChild(MenuItem::Create(MenuItem::Type::kString, "E&xit",
"Alt+F4",
[this]() { window_->Close(); }));
file_menu->AddChild(
MenuItem::Create(MenuItem::Type::kString, "E&xit", "Alt+F4",
[this]() { window_->RequestClose(); }));
}
main_menu->AddChild(std::move(file_menu));
@ -249,21 +571,35 @@ bool EmulatorWindow::Initialize() {
}
main_menu->AddChild(std::move(gpu_menu));
// Window menu.
auto window_menu = MenuItem::Create(MenuItem::Type::kPopup, "&Window");
// Display menu.
auto display_menu = MenuItem::Create(MenuItem::Type::kPopup, "&Display");
{
window_menu->AddChild(
display_menu->AddChild(MenuItem::Create(
MenuItem::Type::kString, "&Post-processing settings", "F6",
std::bind(&EmulatorWindow::ToggleDisplayConfigDialog, this)));
}
display_menu->AddChild(MenuItem::Create(MenuItem::Type::kSeparator));
{
display_menu->AddChild(
MenuItem::Create(MenuItem::Type::kString, "&Fullscreen", "F11",
std::bind(&EmulatorWindow::ToggleFullscreen, this)));
}
main_menu->AddChild(std::move(window_menu));
main_menu->AddChild(std::move(display_menu));
// Help menu.
auto help_menu = MenuItem::Create(MenuItem::Type::kPopup, "&Help");
{
help_menu->AddChild(
MenuItem::Create(MenuItem::Type::kString, "Build commit on GitHub...",
"F2", std::bind(&EmulatorWindow::ShowCommitID, this)));
MenuItem::Create(MenuItem::Type::kString, "FA&Q...", "F1",
std::bind(&EmulatorWindow::ShowFAQ, this)));
help_menu->AddChild(MenuItem::Create(MenuItem::Type::kSeparator));
help_menu->AddChild(
MenuItem::Create(MenuItem::Type::kString, "Game &compatibility...",
std::bind(&EmulatorWindow::ShowCompatibility, this)));
help_menu->AddChild(MenuItem::Create(MenuItem::Type::kSeparator));
help_menu->AddChild(MenuItem::Create(
MenuItem::Type::kString, "Build commit on GitHub...", "F2",
std::bind(&EmulatorWindow::ShowBuildCommit, this)));
help_menu->AddChild(MenuItem::Create(
MenuItem::Type::kString, "Recent changes on GitHub...", [this]() {
LaunchWebBrowser(
@ -271,25 +607,202 @@ bool EmulatorWindow::Initialize() {
"..." XE_BUILD_BRANCH);
}));
help_menu->AddChild(MenuItem::Create(MenuItem::Type::kSeparator));
help_menu->AddChild(
MenuItem::Create(MenuItem::Type::kString, "&Website...", "F1",
std::bind(&EmulatorWindow::ShowHelpWebsite, this)));
help_menu->AddChild(MenuItem::Create(
MenuItem::Type::kString, "&About...",
[this]() { LaunchWebBrowser("https://xenia.jp/about/"); }));
}
main_menu->AddChild(std::move(help_menu));
window_->set_main_menu(std::move(main_menu));
window_->SetMainMenu(std::move(main_menu));
window_->Resize(1280, 720);
window_->SetMainMenuEnabled(false);
window_->DisableMainMenu();
UpdateTitle();
if (!window_->Open()) {
XELOGE("Failed to open the platform window");
return false;
}
Profiler::SetUserIO(kZOrderProfiler, window_.get(), nullptr, nullptr);
return true;
}
const char* EmulatorWindow::GetCvarValueForSwapPostEffect(
gpu::CommandProcessor::SwapPostEffect effect) {
switch (effect) {
case gpu::CommandProcessor::SwapPostEffect::kFxaa:
return "fxaa";
case gpu::CommandProcessor::SwapPostEffect::kFxaaExtreme:
return "fxaa_extreme";
default:
return "";
}
}
gpu::CommandProcessor::SwapPostEffect
EmulatorWindow::GetSwapPostEffectForCvarValue(const std::string& cvar_value) {
if (cvar_value == GetCvarValueForSwapPostEffect(
gpu::CommandProcessor::SwapPostEffect::kFxaa)) {
return gpu::CommandProcessor::SwapPostEffect::kFxaa;
}
if (cvar_value == GetCvarValueForSwapPostEffect(
gpu::CommandProcessor::SwapPostEffect::kFxaaExtreme)) {
return gpu::CommandProcessor::SwapPostEffect::kFxaaExtreme;
}
return gpu::CommandProcessor::SwapPostEffect::kNone;
}
const char* EmulatorWindow::GetCvarValueForGuestOutputPaintEffect(
ui::Presenter::GuestOutputPaintConfig::Effect effect) {
switch (effect) {
case ui::Presenter::GuestOutputPaintConfig::Effect::kCas:
return "cas";
case ui::Presenter::GuestOutputPaintConfig::Effect::kFsr:
return "fsr";
default:
return "";
}
}
ui::Presenter::GuestOutputPaintConfig::Effect
EmulatorWindow::GetGuestOutputPaintEffectForCvarValue(
const std::string& cvar_value) {
if (cvar_value == GetCvarValueForGuestOutputPaintEffect(
ui::Presenter::GuestOutputPaintConfig::Effect::kCas)) {
return ui::Presenter::GuestOutputPaintConfig::Effect::kCas;
}
if (cvar_value == GetCvarValueForGuestOutputPaintEffect(
ui::Presenter::GuestOutputPaintConfig::Effect::kFsr)) {
return ui::Presenter::GuestOutputPaintConfig::Effect::kFsr;
}
return ui::Presenter::GuestOutputPaintConfig::Effect::kBilinear;
}
ui::Presenter::GuestOutputPaintConfig
EmulatorWindow::GetGuestOutputPaintConfigForCvars() {
ui::Presenter::GuestOutputPaintConfig paint_config;
paint_config.SetEffect(GetGuestOutputPaintEffectForCvarValue(
cvars::postprocess_scaling_and_sharpening));
paint_config.SetCasAdditionalSharpness(
float(cvars::postprocess_ffx_cas_additional_sharpness));
paint_config.SetFsrMaxUpsamplingPasses(
cvars::postprocess_ffx_fsr_max_upsampling_passes);
paint_config.SetFsrSharpnessReduction(
float(cvars::postprocess_ffx_fsr_sharpness_reduction));
paint_config.SetDither(cvars::postprocess_dither);
return paint_config;
}
void EmulatorWindow::ApplyDisplayConfigForCvars() {
gpu::GraphicsSystem* graphics_system = emulator_->graphics_system();
if (!graphics_system) {
return;
}
gpu::CommandProcessor* command_processor =
graphics_system->command_processor();
if (command_processor) {
command_processor->SetDesiredSwapPostEffect(
GetSwapPostEffectForCvarValue(cvars::postprocess_antialiasing));
}
ui::Presenter* presenter = graphics_system->presenter();
if (presenter) {
presenter->SetGuestOutputPaintConfigFromUIThread(
GetGuestOutputPaintConfigForCvars());
}
}
void EmulatorWindow::OnKeyDown(ui::KeyEvent& e) {
if (!emulator_initialized_) {
return;
}
switch (e.virtual_key()) {
case ui::VirtualKey::kO: {
if (!e.is_ctrl_pressed()) {
return;
}
FileOpen();
} break;
case ui::VirtualKey::kMultiply: {
CpuTimeScalarReset();
} break;
case ui::VirtualKey::kSubtract: {
CpuTimeScalarSetHalf();
} break;
case ui::VirtualKey::kAdd: {
CpuTimeScalarSetDouble();
} break;
case ui::VirtualKey::kF3: {
Profiler::ToggleDisplay();
} break;
case ui::VirtualKey::kF4: {
GpuTraceFrame();
} break;
case ui::VirtualKey::kF5: {
GpuClearCaches();
} break;
case ui::VirtualKey::kF6: {
ToggleDisplayConfigDialog();
} break;
case ui::VirtualKey::kF11: {
ToggleFullscreen();
} break;
case ui::VirtualKey::kEscape: {
// Allow users to escape fullscreen (but not enter it).
if (!window_->IsFullscreen()) {
return;
}
SetFullscreen(false);
} break;
#ifdef DEBUG
case ui::VirtualKey::kF7: {
// Save to file
// TODO: Choose path based on user input, or from options
// TODO: Spawn a new thread to do this.
emulator()->SaveToFile("test.sav");
} break;
case ui::VirtualKey::kF8: {
// Restore from file
// TODO: Choose path from user
// TODO: Spawn a new thread to do this.
emulator()->RestoreFromFile("test.sav");
} break;
#endif // #ifdef DEBUG
case ui::VirtualKey::kPause: {
CpuBreakIntoDebugger();
} break;
case ui::VirtualKey::kCancel: {
CpuBreakIntoHostDebugger();
} break;
case ui::VirtualKey::kF1: {
ShowFAQ();
} break;
case ui::VirtualKey::kF2: {
ShowBuildCommit();
} break;
default:
return;
}
e.set_handled(true);
}
void EmulatorWindow::FileDrop(const std::filesystem::path& filename) {
if (!emulator_initialized_) {
return;
}
auto result = emulator_->LaunchPath(filename);
if (XFAILED(result)) {
// TODO: Display a message box.
@ -312,7 +825,7 @@ void EmulatorWindow::FileOpen() {
//{"Content Package (*.xcp)", "*.xcp" },
{"All Files (*.*)", "*.*"},
});
if (file_picker->Show(window_->native_handle())) {
if (file_picker->Show(window_.get())) {
auto selected_files = file_picker->selected_files();
if (!selected_files.empty()) {
path = selected_files[0];
@ -357,17 +870,6 @@ void EmulatorWindow::ShowContentDirectory() {
LaunchFileExplorer(target_path);
}
void EmulatorWindow::CheckHideCursor() {
if (!window_->is_fullscreen()) {
// Only hide when fullscreen.
return;
}
if (Clock::QueryHostSystemTime() > cursor_hide_time_) {
window_->set_cursor_visible(false);
}
}
void EmulatorWindow::CpuTimeScalarReset() {
Clock::set_guest_time_scalar(1.0);
UpdateTitle();
@ -385,7 +887,7 @@ void EmulatorWindow::CpuTimeScalarSetDouble() {
void EmulatorWindow::CpuBreakIntoDebugger() {
if (!cvars::debug) {
xe::ui::ImGuiDialog::ShowMessageBox(window_.get(), "Xenia Debugger",
xe::ui::ImGuiDialog::ShowMessageBox(imgui_drawer_.get(), "Xenia Debugger",
"Xenia must be launched with the "
"--debug flag in order to enable "
"debugging.");
@ -411,19 +913,48 @@ void EmulatorWindow::GpuClearCaches() {
emulator()->graphics_system()->ClearCaches();
}
void EmulatorWindow::ToggleFullscreen() {
window_->ToggleFullscreen(!window_->is_fullscreen());
void EmulatorWindow::SetFullscreen(bool fullscreen) {
if (window_->IsFullscreen() == fullscreen) {
return;
}
window_->SetFullscreen(fullscreen);
window_->SetCursorVisibility(fullscreen
? ui::Window::CursorVisibility::kAutoHidden
: ui::Window::CursorVisibility::kVisible);
}
// Hide the cursor after a second if we're going fullscreen
cursor_hide_time_ = Clock::QueryHostSystemTime() + 30000000;
if (!window_->is_fullscreen()) {
window_->set_cursor_visible(true);
void EmulatorWindow::ToggleFullscreen() {
SetFullscreen(!window_->IsFullscreen());
}
void EmulatorWindow::ToggleDisplayConfigDialog() {
if (!display_config_dialog_) {
display_config_dialog_ = std::unique_ptr<DisplayConfigDialog>(
new DisplayConfigDialog(imgui_drawer_.get(), *this));
} else {
display_config_dialog_.reset();
}
}
void EmulatorWindow::ShowHelpWebsite() { LaunchWebBrowser("https://xenia.jp"); }
void EmulatorWindow::ShowCompatibility() {
const std::string_view base_url =
"https://github.com/xenia-project/game-compatibility/issues";
std::string url;
// Avoid searching for a title ID of "00000000".
uint32_t title_id = emulator_->title_id();
if (!title_id) {
url = base_url;
} else {
url = fmt::format("{}?q=is%3Aissue+is%3Aopen+{:08X}", base_url, title_id);
}
LaunchWebBrowser(url);
}
void EmulatorWindow::ShowCommitID() {
void EmulatorWindow::ShowFAQ() {
LaunchWebBrowser("https://github.com/xenia-project/xenia/wiki/FAQ");
}
void EmulatorWindow::ShowBuildCommit() {
#ifdef XE_BUILD_IS_PR
LaunchWebBrowser(
"https://github.com/xenia-project/xenia/pull/" XE_BUILD_PR_NUMBER);
@ -473,7 +1004,7 @@ void EmulatorWindow::UpdateTitle() {
sb.Append(u8" (Preloading shaders\u2026)");
}
window_->set_title(sb.to_string_view());
window_->SetTitle(sb.to_string_view());
}
void EmulatorWindow::SetInitializingShaderStorage(bool initializing) {

View File

@ -2,7 +2,7 @@
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2020 Ben Vanik. All rights reserved. *
* Copyright 2022 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
@ -13,42 +13,124 @@
#include <memory>
#include <string>
#include "xenia/emulator.h"
#include "xenia/gpu/command_processor.h"
#include "xenia/ui/imgui_dialog.h"
#include "xenia/ui/imgui_drawer.h"
#include "xenia/ui/immediate_drawer.h"
#include "xenia/ui/menu_item.h"
#include "xenia/ui/presenter.h"
#include "xenia/ui/window.h"
#include "xenia/ui/window_listener.h"
#include "xenia/ui/windowed_app_context.h"
#include "xenia/xbox.h"
namespace xe {
class Emulator;
} // namespace xe
namespace xe {
namespace app {
class EmulatorWindow {
public:
enum : size_t {
// The UI is on top of the game and is open in special cases, so
// lowest-priority.
kZOrderHidInput,
kZOrderImGui,
kZOrderProfiler,
// Emulator window controls are expected to be always accessible by the
// user, so highest-priority.
kZOrderEmulatorWindowInput,
};
virtual ~EmulatorWindow();
static std::unique_ptr<EmulatorWindow> Create(
Emulator* emulator, ui::WindowedAppContext& app_context);
Emulator* emulator() const { return emulator_; }
ui::WindowedAppContext& app_context() const { return app_context_; }
ui::Window* window() const { return window_.get(); }
ui::ImGuiDrawer* imgui_drawer() const { return imgui_drawer_.get(); }
ui::Presenter* GetGraphicsSystemPresenter() const;
void SetupGraphicsSystemPresenterPainting();
void ShutdownGraphicsSystemPresenterPainting();
void OnEmulatorInitialized();
void UpdateTitle();
void SetFullscreen(bool fullscreen);
void ToggleFullscreen();
void SetInitializingShaderStorage(bool initializing);
private:
class EmulatorWindowListener final : public ui::WindowListener,
public ui::WindowInputListener {
public:
explicit EmulatorWindowListener(EmulatorWindow& emulator_window)
: emulator_window_(emulator_window) {}
void OnClosing(ui::UIEvent& e) override;
void OnFileDrop(ui::FileDropEvent& e) override;
void OnKeyDown(ui::KeyEvent& e) override;
private:
EmulatorWindow& emulator_window_;
};
class DisplayConfigGameConfigLoadCallback
: public Emulator::GameConfigLoadCallback {
public:
DisplayConfigGameConfigLoadCallback(Emulator& emulator,
EmulatorWindow& emulator_window)
: Emulator::GameConfigLoadCallback(emulator),
emulator_window_(emulator_window) {}
void PostGameConfigLoad() override;
private:
EmulatorWindow& emulator_window_;
};
class DisplayConfigDialog final : public ui::ImGuiDialog {
public:
DisplayConfigDialog(ui::ImGuiDrawer* imgui_drawer,
EmulatorWindow& emulator_window)
: ui::ImGuiDialog(imgui_drawer), emulator_window_(emulator_window) {}
protected:
void OnDraw(ImGuiIO& io) override;
private:
EmulatorWindow& emulator_window_;
};
explicit EmulatorWindow(Emulator* emulator,
ui::WindowedAppContext& app_context);
bool Initialize();
// For comparisons, use GetSwapPostEffectForCvarValue instead as the default
// fallback may be used for multiple values.
static const char* GetCvarValueForSwapPostEffect(
gpu::CommandProcessor::SwapPostEffect effect);
static gpu::CommandProcessor::SwapPostEffect GetSwapPostEffectForCvarValue(
const std::string& cvar_value);
// For comparisons, use GetGuestOutputPaintEffectForCvarValue instead as the
// default fallback may be used for multiple values.
static const char* GetCvarValueForGuestOutputPaintEffect(
ui::Presenter::GuestOutputPaintConfig::Effect effect);
static ui::Presenter::GuestOutputPaintConfig::Effect
GetGuestOutputPaintEffectForCvarValue(const std::string& cvar_value);
static ui::Presenter::GuestOutputPaintConfig
GetGuestOutputPaintConfigForCvars();
void ApplyDisplayConfigForCvars();
void OnKeyDown(ui::KeyEvent& e);
void FileDrop(const std::filesystem::path& filename);
void FileOpen();
void FileClose();
void ShowContentDirectory();
void CheckHideCursor();
void CpuTimeScalarReset();
void CpuTimeScalarSetHalf();
void CpuTimeScalarSetDouble();
@ -56,15 +138,27 @@ class EmulatorWindow {
void CpuBreakIntoHostDebugger();
void GpuTraceFrame();
void GpuClearCaches();
void ShowHelpWebsite();
void ShowCommitID();
void ToggleDisplayConfigDialog();
void ShowCompatibility();
void ShowFAQ();
void ShowBuildCommit();
Emulator* emulator_;
ui::WindowedAppContext& app_context_;
EmulatorWindowListener window_listener_;
std::unique_ptr<ui::Window> window_;
std::unique_ptr<ui::ImGuiDrawer> imgui_drawer_;
std::unique_ptr<DisplayConfigGameConfigLoadCallback>
display_config_game_config_load_callback_;
// Creation may fail, in this case immediate drawer UI must not be drawn.
std::unique_ptr<ui::ImmediateDrawer> immediate_drawer_;
bool emulator_initialized_ = false;
std::string base_title_;
uint64_t cursor_hide_time_ = 0;
bool initializing_shader_storage_ = false;
std::unique_ptr<DisplayConfigDialog> display_config_dialog_;
};
} // namespace app

View File

@ -2,7 +2,7 @@
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2020 Ben Vanik. All rights reserved. *
* Copyright 2022 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
@ -28,6 +28,7 @@
#include "xenia/emulator.h"
#include "xenia/ui/file_picker.h"
#include "xenia/ui/window.h"
#include "xenia/ui/window_listener.h"
#include "xenia/ui/windowed_app.h"
#include "xenia/ui/windowed_app_context.h"
#include "xenia/vfs/devices/host_path_device.h"
@ -62,8 +63,6 @@ DEFINE_string(gpu, "any", "Graphics system. Use: [any, d3d12, vulkan, null]",
DEFINE_string(hid, "any", "Input system. Use: [any, nop, sdl, winkey, xinput]",
"HID");
DEFINE_bool(fullscreen, false, "Toggles fullscreen", "GPU");
DEFINE_path(
storage_root, "",
"Root path for persistent internal data storage (config, etc.), or empty "
@ -192,6 +191,17 @@ class EmulatorApp final : public xe::ui::WindowedApp {
}
};
class DebugWindowClosedListener final : public xe::ui::WindowListener {
public:
explicit DebugWindowClosedListener(EmulatorApp& emulator_app)
: emulator_app_(emulator_app) {}
void OnClosing(xe::ui::UIEvent& e) override;
private:
EmulatorApp& emulator_app_;
};
explicit EmulatorApp(xe::ui::WindowedAppContext& app_context);
static std::unique_ptr<apu::AudioSystem> CreateAudioSystem(
@ -203,6 +213,8 @@ class EmulatorApp final : public xe::ui::WindowedApp {
void EmulatorThread();
void ShutdownEmulatorThreadFromUIThread();
DebugWindowClosedListener debug_window_closed_listener_;
std::unique_ptr<Emulator> emulator_;
std::unique_ptr<EmulatorWindow> emulator_window_;
@ -215,8 +227,15 @@ class EmulatorApp final : public xe::ui::WindowedApp {
std::thread emulator_thread_;
};
void EmulatorApp::DebugWindowClosedListener::OnClosing(xe::ui::UIEvent& e) {
EmulatorApp* emulator_app = &emulator_app_;
emulator_app->emulator_->processor()->set_debug_listener(nullptr);
emulator_app->debug_window_.reset();
}
EmulatorApp::EmulatorApp(xe::ui::WindowedAppContext& app_context)
: xe::ui::WindowedApp(app_context, "xenia", "[Path to .iso/.xex]") {
: xe::ui::WindowedApp(app_context, "xenia", "[Path to .iso/.xex]"),
debug_window_closed_listener_(*this) {
AddPositionalOption("target");
}
@ -253,9 +272,10 @@ std::vector<std::unique_ptr<hid::InputDriver>> EmulatorApp::CreateInputDrivers(
ui::Window* window) {
std::vector<std::unique_ptr<hid::InputDriver>> drivers;
if (cvars::hid.compare("nop") == 0) {
drivers.emplace_back(xe::hid::nop::Create(window));
drivers.emplace_back(
xe::hid::nop::Create(window, EmulatorWindow::kZOrderHidInput));
} else {
Factory<hid::InputDriver, ui::Window*> factory;
Factory<hid::InputDriver, ui::Window*, size_t> factory;
#if XE_PLATFORM_WIN32
factory.Add("xinput", xe::hid::xinput::Create);
#endif // XE_PLATFORM_WIN32
@ -264,14 +284,16 @@ std::vector<std::unique_ptr<hid::InputDriver>> EmulatorApp::CreateInputDrivers(
// WinKey input driver should always be the last input driver added!
factory.Add("winkey", xe::hid::winkey::Create);
#endif // XE_PLATFORM_WIN32
for (auto& driver : factory.CreateAll(cvars::hid, window)) {
for (auto& driver : factory.CreateAll(cvars::hid, window,
EmulatorWindow::kZOrderHidInput)) {
if (XSUCCEEDED(driver->Setup())) {
drivers.emplace_back(std::move(driver));
}
}
if (drivers.empty()) {
// Fallback to nop if none created.
drivers.emplace_back(xe::hid::nop::Create(window));
drivers.emplace_back(
xe::hid::nop::Create(window, EmulatorWindow::kZOrderHidInput));
}
}
return drivers;
@ -366,6 +388,9 @@ void EmulatorApp::OnDestroy() {
// The profiler needs to shut down before the graphics context.
Profiler::Shutdown();
// Write all cvar overrides to the config.
config::SaveConfig();
// TODO(DrChat): Remove this code and do a proper exit.
XELOGI("Cheap-skate exit!");
std::quick_exit(EXIT_SUCCESS);
@ -379,15 +404,18 @@ void EmulatorApp::EmulatorThread() {
// Setup and initialize all subsystems. If we can't do something
// (unsupported system, memory issues, etc) this will fail early.
X_STATUS result =
emulator_->Setup(emulator_window_->window(), CreateAudioSystem,
CreateGraphicsSystem, CreateInputDrivers);
X_STATUS result = emulator_->Setup(
emulator_window_->window(), emulator_window_->imgui_drawer(),
CreateAudioSystem, CreateGraphicsSystem, CreateInputDrivers);
if (XFAILED(result)) {
XELOGE("Failed to setup emulator: {:08X}", result);
app_context().RequestDeferredQuit();
return;
}
app_context().CallInUIThread(
[this]() { emulator_window_->SetupGraphicsSystemPresenterPainting(); });
if (cvars::mount_scratch) {
auto scratch_device = std::make_unique<xe::vfs::HostPathDevice>(
"\\SCRATCH", "scratch", false);
@ -456,12 +484,8 @@ void EmulatorApp::EmulatorThread() {
app_context().CallInUIThreadSynchronous([this]() {
debug_window_ = xe::debug::ui::DebugWindow::Create(emulator_.get(),
app_context());
debug_window_->window()->on_closed.AddListener(
[this](xe::ui::UIEvent* e) {
emulator_->processor()->set_debug_listener(nullptr);
app_context().CallInUIThread(
[this]() { debug_window_.reset(); });
});
debug_window_->window()->AddListener(
&debug_window_closed_listener_);
});
// If failed to enqueue the UI thread call, this will just be null.
return debug_window_.get();
@ -490,9 +514,9 @@ void EmulatorApp::EmulatorThread() {
}
});
// Enable the main menu now that the emulator is properly loaded
// Enable emulator input now that the emulator is properly loaded.
app_context().CallInUIThread(
[this]() { emulator_window_->window()->EnableMainMenu(); });
[this]() { emulator_window_->OnEmulatorInitialized(); });
// Grab path from the flag or unnamed argument.
std::filesystem::path path;
@ -500,12 +524,6 @@ void EmulatorApp::EmulatorThread() {
path = cvars::target;
}
// Toggles fullscreen
if (cvars::fullscreen) {
app_context().CallInUIThread(
[this]() { emulator_window_->ToggleFullscreen(); });
}
if (!path.empty()) {
// Normalize the path and make absolute.
auto abs_path = std::filesystem::absolute(path);

View File

@ -177,6 +177,25 @@ void XmaContext::SwapInputBuffer(XMA_CONTEXT_DATA* data) {
data->input_buffer_read_offset = 0;
}
bool XmaContext::TrySetupNextLoop(XMA_CONTEXT_DATA* data,
bool ignore_input_buffer_offset) {
// Setup the input buffer offset if next loop exists.
// TODO(Pseudo-Kernel): Need to handle loop in the following cases.
// 1. loop_start == loop_end == 0
// 2. loop_start > loop_end && loop_count > 0
if (data->loop_count > 0 && data->loop_start < data->loop_end &&
(ignore_input_buffer_offset ||
data->input_buffer_read_offset >= data->loop_end)) {
// Loop back to the beginning.
data->input_buffer_read_offset = data->loop_start;
if (data->loop_count < 255) {
data->loop_count--;
}
return true;
}
return false;
}
/*
void XmaContext::NextPacket(
uint8_t* input_buffer,
@ -364,6 +383,7 @@ void XmaContext::Decode(XMA_CONTEXT_DATA* data) {
assert_false(data->stop_when_done);
assert_false(data->interrupt_when_done);
static int total_samples = 0;
bool reuse_input_buffer = false;
// Decode until we can't write any more data.
while (output_remaining_bytes > 0) {
if (!data->input_buffer_0_valid && !data->input_buffer_1_valid) {
@ -371,6 +391,10 @@ void XmaContext::Decode(XMA_CONTEXT_DATA* data) {
break;
}
// Setup the input buffer if we are at loop_end.
// The input buffer must not be swapped out until all loops are processed.
reuse_input_buffer = TrySetupNextLoop(data, false);
// assert_true(packets_skip_ == 0);
// assert_true(split_frame_len_ == 0);
// assert_true(split_frame_len_partial_ == 0);
@ -392,7 +416,13 @@ void XmaContext::Decode(XMA_CONTEXT_DATA* data) {
packets_skip_--;
packet_idx++;
if (packet_idx >= current_input_packet_count) {
SwapInputBuffer(data);
if (!reuse_input_buffer) {
// Last packet. Try setup once more.
reuse_input_buffer = TrySetupNextLoop(data, true);
}
if (!reuse_input_buffer) {
SwapInputBuffer(data);
}
return;
}
}
@ -524,7 +554,13 @@ void XmaContext::Decode(XMA_CONTEXT_DATA* data) {
packet += kBytesPerPacket;
packet_idx++;
if (packet_idx >= current_input_packet_count) {
SwapInputBuffer(data);
if (!reuse_input_buffer) {
// Last packet. Try setup once more.
reuse_input_buffer = TrySetupNextLoop(data, true);
}
if (!reuse_input_buffer) {
SwapInputBuffer(data);
}
return;
}
}
@ -606,7 +642,13 @@ void XmaContext::Decode(XMA_CONTEXT_DATA* data) {
packets_skip_--;
packet_idx++;
if (packet_idx >= current_input_packet_count) {
SwapInputBuffer(data);
if (!reuse_input_buffer) {
// Last packet. Try setup once more.
reuse_input_buffer = TrySetupNextLoop(data, true);
}
if (!reuse_input_buffer) {
SwapInputBuffer(data);
}
return;
}
}
@ -618,7 +660,13 @@ void XmaContext::Decode(XMA_CONTEXT_DATA* data) {
// Next packet but we already skipped to it
if (packet_idx >= current_input_packet_count) {
// Buffer is fully used
SwapInputBuffer(data);
if (!reuse_input_buffer) {
// Last packet. Try setup once more.
reuse_input_buffer = TrySetupNextLoop(data, true);
}
if (!reuse_input_buffer) {
SwapInputBuffer(data);
}
break;
}
offset =

View File

@ -171,6 +171,8 @@ class XmaContext {
private:
static void SwapInputBuffer(XMA_CONTEXT_DATA* data);
static bool TrySetupNextLoop(XMA_CONTEXT_DATA* data,
bool ignore_input_buffer_offset);
static void NextPacket(XMA_CONTEXT_DATA* data);
static int GetSampleRate(int id);
// Get the offset of the next frame. Does not traverse packets.

View File

@ -60,7 +60,7 @@ void* Arena::Alloc(size_t size, size_t align) {
if (active_chunk_) {
if (active_chunk_->capacity - active_chunk_->offset <
size + get_padding() + 4096) {
size + get_padding() + 4_KiB) {
Chunk* next = active_chunk_->next;
if (!next) {
assert_true(size + get_padding() < chunk_size_,

View File

@ -14,11 +14,15 @@
#include <cstdint>
#include <vector>
#include "xenia/base/literals.h"
namespace xe {
using namespace xe::literals;
class Arena {
public:
explicit Arena(size_t chunk_size = 4 * 1024 * 1024);
explicit Arena(size_t chunk_size = 4_MiB);
~Arena();
void Reset();

View File

@ -24,7 +24,7 @@ extern "C" int main(int argc, char** argv) {
// Initialize Android globals, including logging. Needs parsed cvars.
// TODO(Triang3l): Obtain the actual API level.
xe::InitializeAndroidAppFromMainThread(__ANDROID_API__);
xe::InitializeAndroidAppFromMainThread(__ANDROID_API__, nullptr, nullptr);
std::vector<std::string> args;
for (int n = 0; n < argc; n++) {

View File

@ -13,7 +13,8 @@
#include "xenia/base/console_app_main.h"
#include "xenia/base/main_win.h"
int main(int argc_ignored, char** argv_ignored) {
// A wide character entry point is required for functions like _get_wpgmptr.
int wmain(int argc_ignored, wchar_t** argv_ignored) {
xe::ConsoleAppEntryInfo entry_info = xe::GetConsoleAppEntryInfo();
std::vector<std::string> args;

View File

@ -27,7 +27,7 @@ static bool has_shell_environment_variable() {
size_t size = 0;
// Check if SHELL exists
// If it doesn't, then we are in a Windows Terminal
auto error = getenv_s(&size, nullptr, 0, "SHELL");
auto error = _wgetenv_s(&size, nullptr, 0, L"SHELL");
if (error) {
return false;
}

View File

@ -2,7 +2,7 @@
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2020 Ben Vanik. All rights reserved. *
* Copyright 2022 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
@ -86,6 +86,10 @@ class ConfigVar : public CommandVar<T>, virtual public IConfigVar {
void LoadGameConfigValue(std::shared_ptr<cpptoml::base> result) override;
void SetConfigValue(T val);
void SetGameConfigValue(T val);
// Changes the actual value used to the one specified, and also makes it the
// one that will be stored when the global config is written next time. After
// overriding, however, the next game config loaded may still change it.
void OverrideConfigValue(T val);
private:
std::string category_;
@ -260,6 +264,16 @@ void ConfigVar<T>::SetGameConfigValue(T val) {
UpdateValue();
}
template <class T>
void ConfigVar<T>::OverrideConfigValue(T val) {
config_value_ = std::make_unique<T>(val);
// The user explicitly changes the value at runtime and wants it to take
// effect immediately. Drop everything with a higher priority. The next game
// config load, however, may still change it.
game_config_value_.reset();
this->commandline_value_.reset();
UpdateValue();
}
template <class T>
void ConfigVar<T>::ResetConfigValueToDefault() {
SetConfigValue(this->default_value_);
}
@ -373,6 +387,28 @@ ICommandVar* define_cmdvar(const char* name, T* default_value,
extern type name; \
}
#define ACCESS_CVar(name) (*cv::cv_##name)
// dynamic_cast is needed because of virtual inheritance.
#define OVERRIDE_CVar(name, type, value) \
dynamic_cast<cvar::ConfigVar<type>*>(&ACCESS_CVar(name)) \
->OverrideConfigValue(value);
#define OVERRIDE_bool(name, value) OVERRIDE_CVar(name, bool, value)
#define OVERRIDE_int32(name, value) OVERRIDE_CVar(name, int32_t, value)
#define OVERRIDE_uint32(name, value) OVERRIDE_CVar(name, uint32_t, value)
#define OVERRIDE_uint64(name, value) OVERRIDE_CVar(name, uint64_t, value)
#define OVERRIDE_double(name, value) OVERRIDE_CVar(name, double, value)
#define OVERRIDE_string(name, value) OVERRIDE_CVar(name, std::string, value)
#define OVERRIDE_path(name, value) \
OVERRIDE_CVar(name, std::filesystem::path, value)
// Interface for changing the default value of a variable with auto-upgrading of
// users' configs (to distinguish between a leftover old default and an explicit
// override), without having to rename the variable.

View File

@ -2,7 +2,7 @@
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2020 Ben Vanik. All rights reserved. *
* Copyright 2022 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
@ -41,8 +41,8 @@ std::filesystem::path GetUserFolder();
// attempting to create it.
bool CreateParentFolder(const std::filesystem::path& path);
// Creates an empty file at the given path.
bool CreateFile(const std::filesystem::path& path);
// Creates an empty file at the given path, overwriting if it exists.
bool CreateEmptyFile(const std::filesystem::path& path);
// Opens the file at the given path with the specified mode.
// This behaves like fopen and the returned handle can be used with stdio.

View File

@ -2,7 +2,7 @@
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2020 Ben Vanik. All rights reserved. *
* Copyright 2022 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
@ -122,7 +122,7 @@ static uint64_t convertUnixtimeToWinFiletime(time_t unixtime) {
return filetime;
}
bool CreateFile(const std::filesystem::path& path) {
bool CreateEmptyFile(const std::filesystem::path& path) {
int file = creat(path.c_str(), 0774);
if (file >= 0) {
close(file);

View File

@ -2,7 +2,7 @@
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2020 Ben Vanik. All rights reserved. *
* Copyright 2022 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
@ -60,7 +60,7 @@ std::filesystem::path GetUserFolder() {
return result;
}
bool CreateFile(const std::filesystem::path& path) {
bool CreateEmptyFile(const std::filesystem::path& path) {
auto handle = CreateFileW(path.c_str(), 0, 0, nullptr, CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL, nullptr);
if (handle == INVALID_HANDLE_VALUE) {

39
src/xenia/base/literals.h Normal file
View File

@ -0,0 +1,39 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2021 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_BASE_LITERALS_H_
#define XENIA_BASE_LITERALS_H_
#include <cstdint>
namespace xe::literals {
constexpr size_t operator""_KiB(unsigned long long int x) {
return 1024ULL * x;
}
constexpr size_t operator""_MiB(unsigned long long int x) {
return 1024_KiB * x;
}
constexpr size_t operator""_GiB(unsigned long long int x) {
return 1024_MiB * x;
}
constexpr size_t operator""_TiB(unsigned long long int x) {
return 1024_GiB * x;
}
constexpr size_t operator""_PiB(unsigned long long int x) {
return 1024_TiB * x;
}
} // namespace xe::literals
#endif // XENIA_BASE_LITERALS_H_

View File

@ -11,6 +11,7 @@
#include <algorithm>
#include <atomic>
#include <cstdlib>
#include <cstring>
#include <mutex>
#include <vector>
@ -25,6 +26,7 @@
#include "xenia/base/cvar.h"
#include "xenia/base/debugging.h"
#include "xenia/base/filesystem.h"
#include "xenia/base/literals.h"
#include "xenia/base/math.h"
#include "xenia/base/memory.h"
#include "xenia/base/platform.h"
@ -59,6 +61,7 @@ DEFINE_int32(
"Logging");
namespace dp = disruptorplus;
using namespace xe::literals;
namespace xe {
@ -74,7 +77,7 @@ struct LogLine {
char prefix_char;
};
thread_local char thread_log_buffer_[64 * 1024];
thread_local char thread_log_buffer_[64_KiB];
FileLogSink::~FileLogSink() {
if (file_) {
@ -234,7 +237,7 @@ class Logger {
}
private:
static const size_t kBufferSize = 8 * 1024 * 1024;
static const size_t kBufferSize = 8_MiB;
uint8_t buffer_[kBufferSize];
static const size_t kBlockSize = 256;
@ -498,7 +501,13 @@ void FatalError(const std::string_view str) {
}
ShutdownLogging();
std::exit(1);
#if XE_PLATFORM_ANDROID
// Throw an error that can be reported to the developers via the store.
std::abort();
#else
std::exit(EXIT_FAILURE);
#endif // XE_PLATFORM_ANDROID
}
} // namespace xe

View File

@ -9,11 +9,15 @@
#include "xenia/base/main_android.h"
#include <android/log.h>
#include <pthread.h>
#include <cstddef>
#include <cstdlib>
#include "xenia/base/assert.h"
#include "xenia/base/logging.h"
#include "xenia/base/memory.h"
#include "xenia/base/system.h"
#include "xenia/base/threading.h"
namespace xe {
@ -22,7 +26,25 @@ static size_t android_initializations_ = 0;
static int32_t android_api_level_ = __ANDROID_API__;
void InitializeAndroidAppFromMainThread(int32_t api_level) {
static JNIEnv* android_main_thread_jni_env_ = nullptr;
static JavaVM* android_java_vm_ = nullptr;
static pthread_key_t android_thread_jni_env_key_;
static jobject android_application_context_ = nullptr;
static void AndroidThreadJNIEnvDestructor(void* jni_env_pointer) {
// The JNIEnv pointer for the main thread is taken externally, the lifetime of
// the attachment is not managed by the key.
JNIEnv* jni_env = static_cast<JNIEnv*>(jni_env_pointer);
if (jni_env && jni_env != android_main_thread_jni_env_) {
android_java_vm_->DetachCurrentThread();
}
// Multiple iterations of destructor invocations can be done - clear.
pthread_setspecific(android_thread_jni_env_key_, nullptr);
}
void InitializeAndroidAppFromMainThread(int32_t api_level,
JNIEnv* main_thread_jni_env,
jobject application_context) {
if (android_initializations_++) {
// Already initialized for another component in the process.
return;
@ -32,6 +54,45 @@ void InitializeAndroidAppFromMainThread(int32_t api_level) {
// subsystem initialization itself.
android_api_level_ = api_level;
android_main_thread_jni_env_ = main_thread_jni_env;
if (main_thread_jni_env) {
// In a Java VM, not just in a process that runs an executable - set up
// the attachment of threads to the Java VM.
if (main_thread_jni_env->GetJavaVM(&android_java_vm_) < 0) {
// Logging has not been initialized yet.
__android_log_write(
ANDROID_LOG_ERROR, "InitializeAndroidAppFromMainThread",
"Failed to get the Java VM from the JNI environment of the main "
"thread");
std::abort();
}
if (pthread_key_create(&android_thread_jni_env_key_,
AndroidThreadJNIEnvDestructor)) {
__android_log_write(
ANDROID_LOG_ERROR, "InitializeAndroidAppFromMainThread",
"Failed to create the thread-specific JNI environment key");
std::abort();
}
if (pthread_setspecific(android_thread_jni_env_key_, main_thread_jni_env)) {
__android_log_write(
ANDROID_LOG_ERROR, "InitializeAndroidAppFromMainThread",
"Failed to set the thread-specific JNI environment pointer for the "
"main thread");
std::abort();
}
if (application_context) {
android_application_context_ =
main_thread_jni_env->NewGlobalRef(application_context);
if (!android_application_context_) {
__android_log_write(
ANDROID_LOG_ERROR, "InitializeAndroidAppFromMainThread",
"Failed to create a global reference to the application context "
"object");
std::abort();
}
}
}
// Logging uses threading.
xe::threading::AndroidInitialize();
@ -40,6 +101,15 @@ void InitializeAndroidAppFromMainThread(int32_t api_level) {
xe::InitializeLogging("xenia");
xe::memory::AndroidInitialize();
if (android_application_context_) {
if (!xe::InitializeAndroidSystemForApplicationContext()) {
__android_log_write(ANDROID_LOG_ERROR,
"InitializeAndroidAppFromMainThread",
"Failed to initialize system UI interaction");
std::abort();
}
}
}
void ShutdownAndroidAppFromMainThread() {
@ -52,15 +122,36 @@ void ShutdownAndroidAppFromMainThread() {
return;
}
xe::ShutdownAndroidSystem();
xe::memory::AndroidShutdown();
xe::ShutdownLogging();
xe::threading::AndroidShutdown();
if (android_application_context_) {
android_main_thread_jni_env_->DeleteGlobalRef(android_application_context_);
android_application_context_ = nullptr;
}
if (android_java_vm_) {
android_java_vm_ = nullptr;
pthread_key_delete(android_thread_jni_env_key_);
}
android_main_thread_jni_env_ = nullptr;
android_api_level_ = __ANDROID_API__;
}
int32_t GetAndroidApiLevel() { return android_api_level_; }
JNIEnv* GetAndroidThreadJniEnv() {
if (!android_java_vm_) {
return nullptr;
}
return static_cast<JNIEnv*>(pthread_getspecific(android_thread_jni_env_key_));
}
jobject GetAndroidApplicationContext() { return android_application_context_; }
} // namespace xe

View File

@ -2,7 +2,7 @@
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2021 Ben Vanik. All rights reserved. *
* Copyright 2022 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
@ -10,6 +10,7 @@
#ifndef XENIA_BASE_MAIN_ANDROID_H_
#define XENIA_BASE_MAIN_ANDROID_H_
#include <jni.h>
#include <cstdint>
#include "xenia/base/platform.h"
@ -27,14 +28,33 @@ namespace xe {
// counting internally.
//
// In standalone console apps built with $(BUILD_EXECUTABLE), these functions
// must be called in `main`.
void InitializeAndroidAppFromMainThread(int32_t api_level);
// must be called in `main`, with a null main thread JNI environment.
void InitializeAndroidAppFromMainThread(int32_t api_level,
JNIEnv* main_thread_jni_env,
jobject application_context);
void ShutdownAndroidAppFromMainThread();
// May be the minimum supported level if the initialization was done without a
// configuration.
int32_t GetAndroidApiLevel();
// Useful notes about JNI usage on Android within Xenia:
// - All static libraries defining JNI native functions must be linked to shared
// libraries via LOCAL_WHOLE_STATIC_LIBRARIES.
// - If method or field IDs are cached, a global reference to the class needs to
// be held - it prevents the class from being unloaded by the class loaders
// (in a way that would make the IDs invalid when it's reloaded).
// - GetStringUTFChars (UTF-8) returns null-terminated strings, GetStringChars
// (UTF-16) does not.
// May return null if not in a Java VM process, or in case of a failure to
// attach on a non-main thread.
JNIEnv* GetAndroidThreadJniEnv();
// Returns the global reference if in an application context, or null otherwise.
// This is the application context, not the activity one, because multiple
// activities may be running in one process.
jobject GetAndroidApplicationContext();
} // namespace xe
#endif // XENIA_BASE_MAIN_ANDROID_H_

View File

@ -2,7 +2,7 @@
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2021 Ben Vanik. All rights reserved. *
* Copyright 2022 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
@ -19,37 +19,64 @@
// Autogenerated by `xb premake`.
#include "build/version.h"
// For RequestHighPerformance.
// For RequestWin32MMCSS.
#include <dwmapi.h>
// For RequestWin32HighResolutionTimer.
#include <winternl.h>
DEFINE_bool(win32_high_freq, true,
"Requests high performance from the NT kernel", "Kernel");
DEFINE_bool(win32_high_resolution_timer, true,
"Requests high-resolution timer from the NT kernel", "Win32");
DEFINE_bool(
win32_mmcss, true,
"Opt in the Multimedia Class Scheduler Service (MMCSS) scheduling for "
"prioritized access to CPU resources",
"Win32");
namespace xe {
static void RequestHighPerformance() {
#if XE_PLATFORM_WIN32
NTSTATUS(*NtQueryTimerResolution)
(OUT PULONG MinimumResolution, OUT PULONG MaximumResolution,
OUT PULONG CurrentResolution);
static void RequestWin32HighResolutionTimer() {
HMODULE ntdll_module = GetModuleHandleW(L"ntdll.dll");
if (!ntdll_module) {
return;
}
NTSTATUS(*NtSetTimerResolution)
(IN ULONG DesiredResolution, IN BOOLEAN SetResolution,
OUT PULONG CurrentResolution);
NtQueryTimerResolution = (decltype(NtQueryTimerResolution))GetProcAddress(
GetModuleHandleW(L"ntdll.dll"), "NtQueryTimerResolution");
NtSetTimerResolution = (decltype(NtSetTimerResolution))GetProcAddress(
GetModuleHandleW(L"ntdll.dll"), "NtSetTimerResolution");
if (!NtQueryTimerResolution || !NtSetTimerResolution) {
// clang-format off
NTSTATUS (NTAPI* nt_query_timer_resolution)(OUT PULONG MinimumResolution,
OUT PULONG MaximumResolution,
OUT PULONG CurrentResolution);
NTSTATUS (NTAPI* nt_set_timer_resolution)(IN ULONG DesiredResolution,
IN BOOLEAN SetResolution,
OUT PULONG CurrentResolution);
// clang-format on
nt_query_timer_resolution =
reinterpret_cast<decltype(nt_query_timer_resolution)>(
GetProcAddress(ntdll_module, "NtQueryTimerResolution"));
nt_set_timer_resolution = reinterpret_cast<decltype(nt_set_timer_resolution)>(
GetProcAddress(ntdll_module, "NtSetTimerResolution"));
if (!nt_query_timer_resolution || !nt_set_timer_resolution) {
return;
}
ULONG minimum_resolution, maximum_resolution, current_resolution;
NtQueryTimerResolution(&minimum_resolution, &maximum_resolution,
&current_resolution);
NtSetTimerResolution(maximum_resolution, TRUE, &current_resolution);
#endif
nt_query_timer_resolution(&minimum_resolution, &maximum_resolution,
&current_resolution);
nt_set_timer_resolution(maximum_resolution, TRUE, &current_resolution);
}
static void RequestWin32MMCSS() {
HMODULE dwmapi_module = LoadLibraryW(L"dwmapi.dll");
if (!dwmapi_module) {
return;
}
// clang-format off
HRESULT (STDAPICALLTYPE* dwm_enable_mmcss)(BOOL fEnableMMCSS);
// clang-format on
dwm_enable_mmcss = reinterpret_cast<decltype(dwm_enable_mmcss)>(
GetProcAddress(dwmapi_module, "DwmEnableMMCSS"));
if (dwm_enable_mmcss) {
dwm_enable_mmcss(TRUE);
}
FreeLibrary(dwmapi_module);
}
bool ParseWin32LaunchArguments(
@ -103,9 +130,12 @@ int InitializeWin32App(const std::string_view app_name) {
#endif
XE_BUILD_BRANCH "@" XE_BUILD_COMMIT_SHORT " on " XE_BUILD_DATE);
// Request high performance timing.
if (cvars::win32_high_freq) {
RequestHighPerformance();
// Request high-performance timing and scheduling.
if (cvars::win32_high_resolution_timer) {
RequestWin32HighResolutionTimer();
}
if (cvars::win32_mmcss) {
RequestWin32MMCSS();
}
return 0;

View File

@ -2,7 +2,7 @@
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2014 Ben Vanik. All rights reserved. *
* Copyright 2022 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
@ -11,6 +11,10 @@
#include "xenia/base/cvar.h"
#include "xenia/base/platform.h"
#if XE_ARCH_ARM64
#include <arm_neon.h>
#endif
#include <algorithm>
DEFINE_bool(
@ -184,8 +188,8 @@ void copy_and_swap_64_unaligned(void* dest_ptr, const void* src_ptr,
void copy_and_swap_16_in_32_aligned(void* dest_ptr, const void* src_ptr,
size_t count) {
auto dest = reinterpret_cast<uint64_t*>(dest_ptr);
auto src = reinterpret_cast<const uint64_t*>(src_ptr);
auto dest = reinterpret_cast<uint32_t*>(dest_ptr);
auto src = reinterpret_cast<const uint32_t*>(src_ptr);
size_t i;
for (i = 0; i + 4 <= count; i += 4) {
__m128i input = _mm_load_si128(reinterpret_cast<const __m128i*>(&src[i]));
@ -201,8 +205,8 @@ void copy_and_swap_16_in_32_aligned(void* dest_ptr, const void* src_ptr,
void copy_and_swap_16_in_32_unaligned(void* dest_ptr, const void* src_ptr,
size_t count) {
auto dest = reinterpret_cast<uint64_t*>(dest_ptr);
auto src = reinterpret_cast<const uint64_t*>(src_ptr);
auto dest = reinterpret_cast<uint32_t*>(dest_ptr);
auto src = reinterpret_cast<const uint32_t*>(src_ptr);
size_t i;
for (i = 0; i + 4 <= count; i += 4) {
__m128i input = _mm_loadu_si128(reinterpret_cast<const __m128i*>(&src[i]));
@ -215,7 +219,133 @@ void copy_and_swap_16_in_32_unaligned(void* dest_ptr, const void* src_ptr,
dest[i] = (src[i] >> 16) | (src[i] << 16);
}
}
#elif XE_ARCH_ARM64
// Although NEON offers vector rev instructions (like vrev32q_u8), they are
// slower in benchmarks. Also, using uint8x16xN_t wasn't any faster in the
// benchmarks, hence we use just use one SIMD register to minimize residual
// processing.
void copy_and_swap_16_aligned(void* dst_ptr, const void* src_ptr,
size_t count) {
copy_and_swap_16_unaligned(dst_ptr, src_ptr, count);
}
void copy_and_swap_16_unaligned(void* dst_ptr, const void* src_ptr,
size_t count) {
auto dst = reinterpret_cast<uint8_t*>(dst_ptr);
auto src = reinterpret_cast<const uint8_t*>(src_ptr);
const uint8x16_t tbl_idx =
vcombine_u8(vcreate_u8(UINT64_C(0x0607040502030001)),
vcreate_u8(UINT64_C(0x0E0F0C0D0A0B0809)));
while (count >= 8) {
uint8x16_t data = vld1q_u8(src);
data = vqtbl1q_u8(data, tbl_idx);
vst1q_u8(dst, data);
count -= 8;
// These pointer increments will be combined with the load/stores (ldr/str)
// into single instructions (at least by clang)
dst += 16;
src += 16;
}
while (count > 0) {
store_and_swap<uint16_t>(dst, load<uint16_t>(src));
count--;
dst += 2;
src += 2;
}
}
void copy_and_swap_32_aligned(void* dst, const void* src, size_t count) {
copy_and_swap_32_unaligned(dst, src, count);
}
void copy_and_swap_32_unaligned(void* dst_ptr, const void* src_ptr,
size_t count) {
auto dst = reinterpret_cast<uint8_t*>(dst_ptr);
auto src = reinterpret_cast<const uint8_t*>(src_ptr);
const uint8x16_t tbl_idx =
vcombine_u8(vcreate_u8(UINT64_C(0x405060700010203)),
vcreate_u8(UINT64_C(0x0C0D0E0F08090A0B)));
while (count >= 4) {
uint8x16_t data = vld1q_u8(src);
data = vqtbl1q_u8(data, tbl_idx);
vst1q_u8(dst, data);
count -= 4;
dst += 16;
src += 16;
}
while (count > 0) {
store_and_swap<uint32_t>(dst, load<uint32_t>(src));
count--;
dst += 4;
src += 4;
}
}
void copy_and_swap_64_aligned(void* dst, const void* src, size_t count) {
copy_and_swap_64_unaligned(dst, src, count);
}
void copy_and_swap_64_unaligned(void* dst_ptr, const void* src_ptr,
size_t count) {
auto dst = reinterpret_cast<uint8_t*>(dst_ptr);
auto src = reinterpret_cast<const uint8_t*>(src_ptr);
const uint8x16_t tbl_idx =
vcombine_u8(vcreate_u8(UINT64_C(0x0001020304050607)),
vcreate_u8(UINT64_C(0x08090A0B0C0D0E0F)));
while (count >= 2) {
uint8x16_t data = vld1q_u8(src);
data = vqtbl1q_u8(data, tbl_idx);
vst1q_u8(dst, data);
count -= 2;
dst += 16;
src += 16;
}
while (count > 0) {
store_and_swap<uint64_t>(dst, load<uint64_t>(src));
count--;
dst += 8;
src += 8;
}
}
void copy_and_swap_16_in_32_aligned(void* dst, const void* src, size_t count) {
return copy_and_swap_16_in_32_unaligned(dst, src, count);
}
void copy_and_swap_16_in_32_unaligned(void* dst_ptr, const void* src_ptr,
size_t count) {
auto dst = reinterpret_cast<uint16_t*>(dst_ptr);
auto src = reinterpret_cast<const uint16_t*>(src_ptr);
while (count > 0) {
uint16_t word0 = *src++;
uint16_t word1 = *src++;
*dst++ = word1;
*dst++ = word0;
count--;
}
}
#else
// Generic routines.
void copy_and_swap_16_aligned(void* dest, const void* src, size_t count) {
return copy_and_swap_16_unaligned(dest, src, count);
@ -260,14 +390,20 @@ void copy_and_swap_16_in_32_aligned(void* dest, const void* src, size_t count) {
return copy_and_swap_16_in_32_unaligned(dest, src, count);
}
void copy_and_swap_16_in_32_unaligned(void* dest_ptr, const void* src_ptr,
void copy_and_swap_16_in_32_unaligned(void* dst_ptr, const void* src_ptr,
size_t count) {
auto dest = reinterpret_cast<uint64_t*>(dest_ptr);
auto src = reinterpret_cast<const uint64_t*>(src_ptr);
for (size_t i = 0; i < count; ++i) {
dest[i] = (src[i] >> 16) | (src[i] << 16);
auto dst = reinterpret_cast<uint16_t*>(dst_ptr);
auto src = reinterpret_cast<const uint16_t*>(src_ptr);
while (count > 0) {
uint16_t word0 = *src++;
uint16_t word1 = *src++;
*dst++ = word1;
*dst++ = word0;
count--;
}
}
#endif
} // namespace xe

View File

@ -2,17 +2,20 @@
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2014 Ben Vanik. All rights reserved. *
* Copyright 2022 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include <algorithm>
#include <string>
// NOTE: this must be included before microprofile as macro expansion needs
// XELOGI.
#include "xenia/base/logging.h"
#include "third_party/fmt/include/fmt/printf.h"
// NOTE: microprofile must be setup first, before profiling.h is included.
#define MICROPROFILE_ENABLED 1
#define MICROPROFILEUI_ENABLED 1
@ -21,7 +24,11 @@
#define MICROPROFILE_PER_THREAD_BUFFER_SIZE (1024 * 1024 * 10)
#define MICROPROFILE_USE_THREAD_NAME_CALLBACK 1
#define MICROPROFILE_WEBSERVER_MAXFRAMES 3
#define MICROPROFILE_PRINTF XELOGI
#define MICROPROFILE_PRINTF(...) \
do { \
auto xenia_profiler_formatted = fmt::sprintf(__VA_ARGS__); \
XELOGI("{}", xenia_profiler_formatted); \
} while (false);
#define MICROPROFILE_WEBSERVER 0
#define MICROPROFILE_DEBUG 0
#define MICROPROFILE_MAX_THREADS 128
@ -30,6 +37,7 @@
#include "xenia/base/assert.h"
#include "xenia/base/cvar.h"
#include "xenia/base/profiling.h"
#include "xenia/ui/ui_event.h"
#include "xenia/ui/virtual_key.h"
#include "xenia/ui/window.h"
@ -38,21 +46,27 @@
#endif // XE_OPTION_PROFILING
#if XE_OPTION_PROFILING_UI
#undef DrawText
#include "xenia/ui/microprofile_drawer.h"
#endif // XE_OPTION_PROFILING_UI
DEFINE_bool(profiler_dpi_scaling, false,
"Apply window DPI scaling to the profiler.", "UI");
DEFINE_bool(show_profiler, false, "Show profiling UI by default.", "UI");
namespace xe {
#if XE_OPTION_PROFILING_UI
ui::Window* Profiler::window_ = nullptr;
std::unique_ptr<ui::MicroprofileDrawer> Profiler::drawer_ = nullptr;
#endif // XE_OPTION_PROFILING_UI
#if XE_OPTION_PROFILING
Profiler::ProfilerWindowInputListener Profiler::input_listener_;
size_t Profiler::z_order_ = 0;
ui::Window* Profiler::window_ = nullptr;
#if XE_OPTION_PROFILING_UI
Profiler::ProfilerUIDrawer Profiler::ui_drawer_;
ui::Presenter* Profiler::presenter_ = nullptr;
std::unique_ptr<ui::MicroprofileDrawer> Profiler::drawer_;
bool Profiler::dpi_scaling_ = false;
#endif // XE_OPTION_PROFILING_UI
bool Profiler::is_enabled() { return true; }
bool Profiler::is_visible() { return is_enabled() && MicroProfileIsDrawing(); }
@ -73,6 +87,7 @@ void Profiler::Initialize() {
g_MicroProfile.nActiveBars |= 0x1 | 0x2;
#if XE_OPTION_PROFILING_UI
dpi_scaling_ = cvars::profiler_dpi_scaling;
MicroProfileInitUI();
g_MicroProfileUI.bShowSpikes = true;
g_MicroProfileUI.nOpacityBackground = 0x40u << 24;
@ -96,7 +111,7 @@ void Profiler::Dump() {
}
void Profiler::Shutdown() {
drawer_.reset();
SetUserIO(0, nullptr, nullptr, nullptr);
window_ = nullptr;
MicroProfileShutdown();
}
@ -113,144 +128,208 @@ void Profiler::ThreadEnter(const char* name) {
void Profiler::ThreadExit() { MicroProfileOnThreadExit(); }
bool Profiler::OnKeyDown(ui::VirtualKey virtual_key) {
void Profiler::ProfilerWindowInputListener::OnKeyDown(ui::KeyEvent& e) {
// https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx
switch (virtual_key) {
bool handled = true;
switch (e.virtual_key()) {
case ui::VirtualKey::kOem3: // `
MicroProfileTogglePause();
return true;
break;
#if XE_OPTION_PROFILING_UI
case ui::VirtualKey::kTab:
MicroProfileToggleDisplayMode();
return true;
ToggleDisplay();
break;
case ui::VirtualKey::k1:
MicroProfileModKey(1);
return true;
break;
#endif // XE_OPTION_PROFILING_UI
default:
handled = false;
break;
}
return false;
if (handled) {
e.set_handled(true);
}
PostInputEvent();
}
bool Profiler::OnKeyUp(ui::VirtualKey virtual_key) {
switch (virtual_key) {
void Profiler::ProfilerWindowInputListener::OnKeyUp(ui::KeyEvent& e) {
bool handled = true;
switch (e.virtual_key()) {
#if XE_OPTION_PROFILING_UI
case ui::VirtualKey::k1:
MicroProfileModKey(0);
return true;
break;
#endif // XE_OPTION_PROFILING_UI
default:
handled = false;
break;
}
return false;
if (handled) {
e.set_handled(true);
}
PostInputEvent();
}
#if XE_OPTION_PROFILING_UI
void Profiler::OnMouseDown(bool left_button, bool right_button) {
MicroProfileMouseButton(left_button, right_button);
void Profiler::ProfilerWindowInputListener::OnMouseDown(ui::MouseEvent& e) {
Profiler::SetMousePosition(e.x(), e.y(), 0);
MicroProfileMouseButton(e.button() == ui::MouseEvent::Button::kLeft,
e.button() == ui::MouseEvent::Button::kRight);
e.set_handled(true);
PostInputEvent();
}
void Profiler::OnMouseUp() { MicroProfileMouseButton(0, 0); }
void Profiler::OnMouseMove(int x, int y) { MicroProfileMousePosition(x, y, 0); }
void Profiler::OnMouseWheel(int x, int y, int dy) {
MicroProfileMousePosition(x, y, dy);
void Profiler::ProfilerWindowInputListener::OnMouseUp(ui::MouseEvent& e) {
Profiler::SetMousePosition(e.x(), e.y(), 0);
MicroProfileMouseButton(0, 0);
e.set_handled(true);
PostInputEvent();
}
void Profiler::ToggleDisplay() { MicroProfileToggleDisplayMode(); }
void Profiler::ProfilerWindowInputListener::OnMouseMove(ui::MouseEvent& e) {
Profiler::SetMousePosition(e.x(), e.y(), 0);
e.set_handled(true);
PostInputEvent();
}
void Profiler::ProfilerWindowInputListener::OnMouseWheel(ui::MouseEvent& e) {
Profiler::SetMousePosition(e.x(), e.y(), e.scroll_y());
e.set_handled(true);
PostInputEvent();
}
void Profiler::TogglePause() { MicroProfileTogglePause(); }
#else
void Profiler::OnMouseDown(bool left_button, bool right_button) {}
void Profiler::OnMouseUp() {}
void Profiler::OnMouseMove(int x, int y) {}
void Profiler::OnMouseWheel(int x, int y, int dy) {}
void Profiler::ToggleDisplay() {}
void Profiler::TogglePause() {}
#endif // XE_OPTION_PROFILING_UI
void Profiler::set_window(ui::Window* window) {
assert_null(window_);
void Profiler::ToggleDisplay() {
bool was_visible = is_visible();
MicroProfileToggleDisplayMode();
if (is_visible() != was_visible) {
if (window_) {
if (was_visible) {
window_->RemoveInputListener(&input_listener_);
} else {
window_->AddInputListener(&input_listener_, z_order_);
}
}
#if XE_OPTION_PROFILING_UI
if (presenter_) {
if (was_visible) {
presenter_->RemoveUIDrawerFromUIThread(&ui_drawer_);
} else {
presenter_->AddUIDrawerFromUIThread(&ui_drawer_, z_order_);
}
}
#endif // XE_OPTION_PROFILING_UI
}
}
void Profiler::SetUserIO(size_t z_order, ui::Window* window,
ui::Presenter* presenter,
ui::ImmediateDrawer* immediate_drawer) {
#if XE_OPTION_PROFILING_UI
if (presenter_ && is_visible()) {
presenter_->RemoveUIDrawerFromUIThread(&ui_drawer_);
}
drawer_.reset();
presenter_ = nullptr;
#endif // XE_OPTION_PROFILING_UI
if (window_) {
if (is_visible()) {
window_->RemoveInputListener(&input_listener_);
}
window_ = nullptr;
}
if (!window) {
return;
}
z_order_ = z_order;
window_ = window;
drawer_ = std::make_unique<ui::MicroprofileDrawer>(window);
window_->on_painted.AddListener([](ui::UIEvent* e) { Profiler::Present(); });
#if XE_OPTION_PROFILING_UI
if (presenter && immediate_drawer) {
presenter_ = presenter;
drawer_ = std::make_unique<ui::MicroprofileDrawer>(immediate_drawer);
}
#endif // XE_OPTION_PROFILING_UI
// Pass through mouse events.
window_->on_mouse_down.AddListener([](ui::MouseEvent* e) {
if (Profiler::is_visible()) {
Profiler::OnMouseDown(e->button() == ui::MouseEvent::Button::kLeft,
e->button() == ui::MouseEvent::Button::kRight);
e->set_handled(true);
window_->Invalidate();
if (is_visible()) {
window_->AddInputListener(&input_listener_, z_order_);
#if XE_OPTION_PROFILING_UI
if (presenter_) {
presenter_->AddUIDrawerFromUIThread(&ui_drawer_, z_order_);
}
});
window_->on_mouse_up.AddListener([](ui::MouseEvent* e) {
if (Profiler::is_visible()) {
Profiler::OnMouseUp();
e->set_handled(true);
window_->Invalidate();
}
});
window_->on_mouse_move.AddListener([](ui::MouseEvent* e) {
if (Profiler::is_visible()) {
Profiler::OnMouseMove(e->x(), e->y());
e->set_handled(true);
window_->Invalidate();
}
});
window_->on_mouse_wheel.AddListener([](ui::MouseEvent* e) {
if (Profiler::is_visible()) {
Profiler::OnMouseWheel(e->x(), e->y(), -e->dy());
e->set_handled(true);
window_->Invalidate();
}
});
// Watch for toggle/mode keys and such.
window_->on_key_down.AddListener([](ui::KeyEvent* e) {
if (Profiler::is_visible()) {
Profiler::OnKeyDown(e->virtual_key());
e->set_handled(true);
window_->Invalidate();
}
});
window_->on_key_up.AddListener([](ui::KeyEvent* e) {
if (Profiler::is_visible()) {
Profiler::OnKeyUp(e->virtual_key());
e->set_handled(true);
window_->Invalidate();
}
});
#endif // XE_OPTION_PROFILING_UI
}
}
void Profiler::Flip() {
MicroProfileFlip();
// This can be called from non-UI threads, so not trying to access the drawer
// to trigger redraw here as it's owned and managed exclusively by the UI
// thread. Relying on continuous painting currently.
}
void Profiler::Present() {
SCOPE_profile_cpu_f("internal");
#if XE_OPTION_PROFILING_UI
if (!window_ || !drawer_) {
void Profiler::ProfilerUIDrawer::Draw(ui::UIDrawContext& ui_draw_context) {
if (!window_ || !presenter_ || !drawer_) {
return;
}
drawer_->Begin();
MicroProfileDraw(window_->scaled_width(), window_->scaled_height());
SCOPE_profile_cpu_f("internal");
uint32_t coordinate_space_width = dpi_scaling_
? window_->GetActualLogicalWidth()
: window_->GetActualPhysicalWidth();
uint32_t coordinate_space_height = dpi_scaling_
? window_->GetActualLogicalHeight()
: window_->GetActualPhysicalHeight();
drawer_->Begin(ui_draw_context, coordinate_space_width,
coordinate_space_height);
MicroProfileDraw(coordinate_space_width, coordinate_space_height);
drawer_->End();
#endif // XE_OPTION_PROFILING_UI
// Continuous repaint.
if (is_visible()) {
presenter_->RequestUIPaintFromUIThread();
}
}
#endif // XE_OPTION_PROFILING_UI
void Profiler::Flip() { MicroProfileFlip(); }
#if XE_OPTION_PROFILING_UI
void Profiler::SetMousePosition(int32_t x, int32_t y, int32_t wheel_delta) {
if (!window_) {
return;
}
if (dpi_scaling_) {
x = window_->PositionToLogical(x);
y = window_->PositionToLogical(y);
}
MicroProfileMousePosition(uint32_t(std::max(int32_t(0), x)),
uint32_t(std::max(int32_t(0), y)), wheel_delta);
}
#endif // XE_OPTION_PROFILING_UI
void Profiler::PostInputEvent() {
// The profiler can be hidden from within the profiler (Mode > Off).
if (!is_visible()) {
window_->RemoveInputListener(&input_listener_);
#if XE_OPTION_PROFILING_UI
if (presenter_) {
presenter_->RemoveUIDrawerFromUIThread(&ui_drawer_);
}
#endif // XE_OPTION_PROFILING_UI
return;
}
// Relying on continuous painting currently, no need to request drawing.
}
#else
@ -262,16 +341,11 @@ void Profiler::Shutdown() {}
uint32_t Profiler::GetColor(const char* str) { return 0; }
void Profiler::ThreadEnter(const char* name) {}
void Profiler::ThreadExit() {}
bool Profiler::OnKeyDown(ui::VirtualKey virtual_key) { return false; }
bool Profiler::OnKeyUp(ui::VirtualKey virtual_key) { return false; }
void Profiler::OnMouseDown(bool left_button, bool right_button) {}
void Profiler::OnMouseUp() {}
void Profiler::OnMouseMove(int x, int y) {}
void Profiler::OnMouseWheel(int x, int y, int dy) {}
void Profiler::ToggleDisplay() {}
void Profiler::TogglePause() {}
void Profiler::set_window(ui::Window* window) {}
void Profiler::Present() {}
void Profiler::SetUserIO(size_t z_order, ui::Window* window,
ui::Presenter* presenter,
ui::ImmediateDrawer* immediate_drawer) {}
void Profiler::Flip() {}
#endif // XE_OPTION_PROFILING
@ -310,7 +384,7 @@ void MicroProfileDrawText(int nX, int nY, uint32_t nColor, const char* pText,
if (!drawer) {
return;
}
drawer->DrawText(nX, nY, nColor, pText, nLen);
drawer->DrawTextString(nX, nY, nColor, pText, nLen);
}
#endif // XE_OPTION_PROFILING_UI

View File

@ -2,7 +2,7 @@
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2014 Ben Vanik. All rights reserved. *
* Copyright 2022 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
@ -10,10 +10,15 @@
#ifndef XENIA_BASE_PROFILING_H_
#define XENIA_BASE_PROFILING_H_
#include <cstddef>
#include <cstdint>
#include <memory>
#include "xenia/base/platform.h"
#include "xenia/base/string.h"
#include "xenia/ui/ui_drawer.h"
#include "xenia/ui/virtual_key.h"
#include "xenia/ui/window_listener.h"
#if XE_PLATFORM_WIN32
#define XE_OPTION_PROFILING 1
@ -30,7 +35,9 @@
namespace xe {
namespace ui {
class ImmediateDrawer;
class MicroprofileDrawer;
class Presenter;
class Window;
} // namespace ui
} // namespace xe
@ -172,27 +179,65 @@ class Profiler {
// Deactivates the calling thread for profiling.
static void ThreadExit();
static bool OnKeyDown(ui::VirtualKey virtual_key);
static bool OnKeyUp(ui::VirtualKey virtual_key);
static void OnMouseDown(bool left_button, bool right_button);
static void OnMouseUp();
static void OnMouseMove(int x, int y);
static void OnMouseWheel(int x, int y, int dy);
static void ToggleDisplay();
static void TogglePause();
// Initializes input and drawing with the given display.
static void set_window(ui::Window* window);
// Gets the current display, if any.
static ui::MicroprofileDrawer* drawer() { return drawer_.get(); }
// Initializes input for the given window and drawing for the given presenter
// and immediate drawer.
static void SetUserIO(size_t z_order, ui::Window* window,
ui::Presenter* presenter,
ui::ImmediateDrawer* immediate_drawer);
// Gets the current drawer, if any.
static ui::MicroprofileDrawer* drawer() {
#if XE_OPTION_PROFILING_UI
return drawer_.get();
#else
return nullptr;
#endif
}
// Presents the profiler to the bound display, if any.
static void Present();
static void Present(ui::UIDrawContext& ui_draw_context);
// Starts a new frame on the profiler
static void Flip();
private:
#if XE_OPTION_PROFILING
class ProfilerWindowInputListener final : public ui::WindowInputListener {
public:
void OnKeyDown(ui::KeyEvent& e) override;
void OnKeyUp(ui::KeyEvent& e) override;
#if XE_OPTION_PROFILING_UI
void OnMouseDown(ui::MouseEvent& e) override;
void OnMouseMove(ui::MouseEvent& e) override;
void OnMouseUp(ui::MouseEvent& e) override;
void OnMouseWheel(ui::MouseEvent& e) override;
#endif // XE_OPTION_PROFILING_UI
};
// For now, no need for OnDpiChanged in a WindowListener because redrawing is
// done continuously.
#if XE_OPTION_PROFILING_UI
class ProfilerUIDrawer final : public ui::UIDrawer {
public:
void Draw(ui::UIDrawContext& context) override;
};
#endif // XE_OPTION_PROFILING_UI
#if XE_OPTION_PROFILING_UI
static void SetMousePosition(int32_t x, int32_t y, int32_t wheel_delta);
#endif // XE_OPTION_PROFILING_UI
static void PostInputEvent();
static ProfilerWindowInputListener input_listener_;
static size_t z_order_;
static ui::Window* window_;
#if XE_OPTION_PROFILING_UI
static ProfilerUIDrawer ui_drawer_;
static ui::Presenter* presenter_;
static std::unique_ptr<ui::MicroprofileDrawer> drawer_;
static bool dpi_scaling_;
#endif // XE_OPTION_PROFILING_UI
#endif // XE_OPTION_PROFILING
};
} // namespace xe

View File

@ -13,12 +13,15 @@
#include <cstdarg>
#include "xenia/base/assert.h"
#include "xenia/base/literals.h"
#include "xenia/base/math.h"
namespace xe {
using namespace xe::literals;
StringBuffer::StringBuffer(size_t initial_capacity) {
buffer_capacity_ = std::max(initial_capacity, static_cast<size_t>(16 * 1024));
buffer_capacity_ = std::max(initial_capacity, static_cast<size_t>(16_KiB));
buffer_ = reinterpret_cast<char*>(std::malloc(buffer_capacity_));
assert_not_null(buffer_);
buffer_[0] = 0;
@ -40,7 +43,7 @@ void StringBuffer::Grow(size_t additional_length) {
}
size_t old_capacity = buffer_capacity_;
size_t new_capacity =
std::max(xe::round_up(buffer_offset_ + additional_length, 16 * 1024),
std::max(xe::round_up(buffer_offset_ + additional_length, 16_KiB),
old_capacity * 2);
auto new_buffer = std::realloc(buffer_, new_capacity);
assert_not_null(new_buffer);

View File

@ -134,7 +134,7 @@ inline std::string to_hex_string(double value) {
}
inline std::string to_hex_string(const vec128_t& value) {
return fmt::format("[{:08X} {:08X} {:08X} {:08X} {:08X}]", value.u32[0],
return fmt::format("[{:08X} {:08X} {:08X} {:08X}]", value.u32[0],
value.u32[1], value.u32[2], value.u32[3]);
}

View File

@ -13,10 +13,17 @@
#include <filesystem>
#include <string_view>
#include "xenia/base/platform.h"
#include "xenia/base/string.h"
namespace xe {
#if XE_PLATFORM_ANDROID
bool InitializeAndroidSystemForApplicationContext();
void ShutdownAndroidSystem();
#endif
// The URL must include the protocol.
void LaunchWebBrowser(const std::string_view url);
void LaunchFileExplorer(const std::filesystem::path& path);

View File

@ -0,0 +1,297 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2022 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include <jni.h>
#include <cstring>
#include <string>
#include "xenia/base/assert.h"
#include "xenia/base/logging.h"
#include "xenia/base/main_android.h"
#include "xenia/base/system.h"
namespace xe {
// To store method and field IDs persistently, global references to the classes
// are required to prevent the classes from being unloaded and reloaded,
// potentially changing the IDs.
static jclass android_system_application_context_class_ = nullptr;
static jmethodID android_system_application_context_start_activity_ = nullptr;
static jclass android_system_uri_class_ = nullptr;
static jmethodID android_system_uri_parse_ = nullptr;
static jclass android_system_intent_class_ = nullptr;
static jmethodID android_system_intent_init_action_uri_ = nullptr;
static jmethodID android_system_intent_add_flags_ = nullptr;
static jobject android_system_intent_action_view_ = nullptr;
static jint android_system_intent_flag_activity_new_task_;
static bool android_system_initialized_ = false;
bool InitializeAndroidSystemForApplicationContext() {
assert_false(android_system_initialized_);
JNIEnv* jni_env = GetAndroidThreadJniEnv();
if (!jni_env) {
return false;
}
jobject application_context = xe::GetAndroidApplicationContext();
if (!application_context) {
return false;
}
// Application context.
{
{
jclass application_context_class_local_ref =
jni_env->GetObjectClass(application_context);
if (!application_context_class_local_ref) {
XELOGE(
"InitializeAndroidSystemForApplicationContext: Failed to get the "
"class of the application context");
ShutdownAndroidSystem();
return false;
}
android_system_application_context_class_ =
reinterpret_cast<jclass>(jni_env->NewGlobalRef(
reinterpret_cast<jobject>(application_context_class_local_ref)));
jni_env->DeleteLocalRef(application_context_class_local_ref);
}
if (!android_system_application_context_class_) {
XELOGE(
"InitializeAndroidSystemForApplicationContext: Failed to create a "
"global reference to the class of the application context");
ShutdownAndroidSystem();
return false;
}
bool application_context_ids_obtained = true;
application_context_ids_obtained &=
(android_system_application_context_start_activity_ =
jni_env->GetMethodID(android_system_application_context_class_,
"startActivity",
"(Landroid/content/Intent;)V")) != nullptr;
if (!application_context_ids_obtained) {
XELOGE(
"InitializeAndroidSystemForApplicationContext: Failed to get the "
"application context class IDs");
ShutdownAndroidSystem();
return false;
}
}
// URI.
{
{
jclass uri_class_local_ref = jni_env->FindClass("android/net/Uri");
if (!uri_class_local_ref) {
XELOGE(
"InitializeAndroidSystemForApplicationContext: Failed to find the "
"URI class");
ShutdownAndroidSystem();
return false;
}
android_system_uri_class_ =
reinterpret_cast<jclass>(jni_env->NewGlobalRef(
reinterpret_cast<jobject>(uri_class_local_ref)));
jni_env->DeleteLocalRef(uri_class_local_ref);
}
if (!android_system_uri_class_) {
XELOGE(
"InitializeAndroidSystemForApplicationContext: Failed to create a "
"global reference to the URI class");
ShutdownAndroidSystem();
return false;
}
bool uri_ids_obtained = true;
uri_ids_obtained &=
(android_system_uri_parse_ = jni_env->GetStaticMethodID(
android_system_uri_class_, "parse",
"(Ljava/lang/String;)Landroid/net/Uri;")) != nullptr;
if (!uri_ids_obtained) {
XELOGE(
"InitializeAndroidSystemForApplicationContext: Failed to get the URI "
"class IDs");
ShutdownAndroidSystem();
return false;
}
}
// Intent.
{
{
jclass intent_class_local_ref =
jni_env->FindClass("android/content/Intent");
if (!intent_class_local_ref) {
XELOGE(
"InitializeAndroidSystemForApplicationContext: Failed to find the "
"intent class");
ShutdownAndroidSystem();
return false;
}
android_system_intent_class_ =
reinterpret_cast<jclass>(jni_env->NewGlobalRef(
reinterpret_cast<jobject>(intent_class_local_ref)));
jni_env->DeleteLocalRef(intent_class_local_ref);
}
if (!android_system_intent_class_) {
XELOGE(
"InitializeAndroidSystemForApplicationContext: Failed to create a "
"global reference to the intent class");
ShutdownAndroidSystem();
return false;
}
bool intent_ids_obtained = true;
jfieldID intent_action_view_id;
intent_ids_obtained &= (intent_action_view_id = jni_env->GetStaticFieldID(
android_system_intent_class_, "ACTION_VIEW",
"Ljava/lang/String;")) != nullptr;
jfieldID intent_flag_activity_new_task_id;
intent_ids_obtained &=
(intent_flag_activity_new_task_id = jni_env->GetStaticFieldID(
android_system_intent_class_, "FLAG_ACTIVITY_NEW_TASK", "I")) !=
nullptr;
intent_ids_obtained &=
(android_system_intent_init_action_uri_ = jni_env->GetMethodID(
android_system_intent_class_, "<init>",
"(Ljava/lang/String;Landroid/net/Uri;)V")) != nullptr;
intent_ids_obtained &=
(android_system_intent_add_flags_ =
jni_env->GetMethodID(android_system_intent_class_, "addFlags",
"(I)Landroid/content/Intent;")) != nullptr;
if (!intent_ids_obtained) {
XELOGE(
"InitializeAndroidSystemForApplicationContext: Failed to get the "
"intent class IDs");
ShutdownAndroidSystem();
return false;
}
{
jobject intent_action_view_local_ref = jni_env->GetStaticObjectField(
android_system_intent_class_, intent_action_view_id);
if (!intent_action_view_local_ref) {
XELOGE(
"InitializeAndroidSystemForApplicationContext: Failed to get the "
"intent view action string");
ShutdownAndroidSystem();
return false;
}
android_system_intent_action_view_ =
jni_env->NewGlobalRef(intent_action_view_local_ref);
jni_env->DeleteLocalRef(intent_action_view_local_ref);
if (!android_system_intent_action_view_) {
XELOGE(
"InitializeAndroidSystemForApplicationContext: Failed to create a "
"global reference to the intent view action string");
ShutdownAndroidSystem();
return false;
}
}
android_system_intent_flag_activity_new_task_ = jni_env->GetStaticIntField(
android_system_intent_class_, intent_flag_activity_new_task_id);
}
android_system_initialized_ = true;
return true;
}
void ShutdownAndroidSystem() {
// May be called from InitializeAndroidSystemForApplicationContext as well.
android_system_initialized_ = false;
android_system_intent_add_flags_ = nullptr;
android_system_intent_init_action_uri_ = nullptr;
android_system_uri_parse_ = nullptr;
android_system_application_context_start_activity_ = nullptr;
JNIEnv* jni_env = GetAndroidThreadJniEnv();
if (jni_env) {
if (android_system_intent_action_view_) {
jni_env->DeleteGlobalRef(android_system_intent_action_view_);
}
if (android_system_intent_class_) {
jni_env->DeleteGlobalRef(android_system_intent_class_);
}
if (android_system_uri_class_) {
jni_env->DeleteGlobalRef(android_system_uri_class_);
}
if (android_system_application_context_class_) {
jni_env->DeleteGlobalRef(android_system_application_context_class_);
}
}
android_system_intent_action_view_ = nullptr;
android_system_intent_class_ = nullptr;
android_system_uri_class_ = nullptr;
android_system_application_context_class_ = nullptr;
}
void LaunchWebBrowser(const std::string_view url) {
if (!android_system_initialized_) {
return;
}
JNIEnv* jni_env = GetAndroidThreadJniEnv();
if (!jni_env) {
return;
}
jobject application_context = GetAndroidApplicationContext();
if (!application_context) {
return;
}
jstring uri_string = jni_env->NewStringUTF(std::string(url).c_str());
if (!uri_string) {
XELOGE("LaunchWebBrowser: Failed to create the URI string");
return;
}
jobject uri = jni_env->CallStaticObjectMethod(
android_system_uri_class_, android_system_uri_parse_, uri_string);
jni_env->DeleteLocalRef(uri_string);
if (!uri) {
XELOGE("LaunchWebBrowser: Failed to parse the URI");
return;
}
jobject intent = jni_env->NewObject(android_system_intent_class_,
android_system_intent_init_action_uri_,
android_system_intent_action_view_, uri);
jni_env->DeleteLocalRef(uri);
if (!intent) {
XELOGE("LaunchWebBrowser: Failed to create the intent");
return;
}
// Start a new task - the user may want to be able to switch between the
// emulator and the newly opened web browser, without having to quit the web
// browser to return to the emulator. Also, since the application context, not
// the activity, is used, the new task flag is required.
{
jobject intent_add_flags_result_local_ref = jni_env->CallObjectMethod(
intent, android_system_intent_add_flags_,
android_system_intent_flag_activity_new_task_);
if (intent_add_flags_result_local_ref) {
jni_env->DeleteLocalRef(intent_add_flags_result_local_ref);
}
}
jni_env->CallVoidMethod(application_context,
android_system_application_context_start_activity_,
intent);
jni_env->DeleteLocalRef(intent);
}
void LaunchFileExplorer(const std::filesystem::path& path) { assert_always(); }
void ShowSimpleMessageBox(SimpleMessageBoxType type, std::string_view message) {
// TODO(Triang3l): Likely not needed much at all. ShowSimpleMessageBox is a
// concept pretty unfriendly to platforms like Android because it's blocking,
// and because it can be called from threads other than the UI thread. In the
// normal execution flow, dialogs should preferably be asynchronous, and used
// only in the UI thread. However, non-blocking messages may be good for error
// reporting - investigate the usage of Toasts with respect to threads, and
// aborting the process immediately after showing a Toast. For a Toast, the
// Java VM for the calling thread is needed.
}
} // namespace xe

View File

@ -2,7 +2,7 @@
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2021 Ben Vanik. All rights reserved. *
* Copyright 2022 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
@ -14,6 +14,8 @@
#include "xenia/base/clock.h"
#include <array>
namespace xe {
namespace base {
namespace test {
@ -113,6 +115,24 @@ TEST_CASE("copy_and_swap_16_unaligned", "[copy_and_swap]") {
REQUIRE(c[2] == 0xAB89);
REQUIRE(c[3] == 0xEFCD);
{
constexpr size_t count = 100;
std::array<uint8_t, count * 2> src{};
std::array<uint8_t, count * 2> dst{};
for (size_t i = 0; i < src.size(); ++i) {
src[i] = static_cast<uint8_t>(i) + 1; // no zero in array
}
copy_and_swap_16_unaligned(dst.data(), src.data(), count);
for (size_t i = 0; i < src.size(); i += 2) {
// Check src is untouched
REQUIRE(static_cast<size_t>(src[i + 0]) == i + 1);
REQUIRE(static_cast<size_t>(src[i + 1]) == i + 2);
// Check swapped bytes
REQUIRE(static_cast<size_t>(dst[i]) == static_cast<size_t>(src[i + 1]));
REQUIRE(static_cast<size_t>(dst[i + 1]) == static_cast<size_t>(src[i]));
}
}
uint64_t e;
copy_and_swap_16_unaligned(&e, d, 4);
REQUIRE(e == 0xEFCDAB8967452301);
@ -221,6 +241,32 @@ TEST_CASE("copy_and_swap_32_unaligned", "[copy_and_swap]") {
REQUIRE(c[2] == 0xEDEE87E8);
REQUIRE(c[3] == 0x994151D8);
{
constexpr size_t count = 17;
std::array<uint8_t, count * 4> src{};
std::array<uint8_t, count * 4> dst{};
for (size_t i = 0; i < src.size(); ++i) {
src[i] = static_cast<uint8_t>(i) + 1; // no zero in array
}
copy_and_swap_32_unaligned(dst.data(), src.data(), count);
for (size_t i = 0; i < src.size(); i += 4) {
// Check src is untouched
REQUIRE(static_cast<size_t>(src[i + 0]) == i + 1);
REQUIRE(static_cast<size_t>(src[i + 1]) == i + 2);
REQUIRE(static_cast<size_t>(src[i + 2]) == i + 3);
REQUIRE(static_cast<size_t>(src[i + 3]) == i + 4);
// Check swapped bytes
REQUIRE(static_cast<size_t>(dst[i + 0]) ==
static_cast<size_t>(src[i + 3]));
REQUIRE(static_cast<size_t>(dst[i + 1]) ==
static_cast<size_t>(src[i + 2]));
REQUIRE(static_cast<size_t>(dst[i + 2]) ==
static_cast<size_t>(src[i + 1]));
REQUIRE(static_cast<size_t>(dst[i + 3]) ==
static_cast<size_t>(src[i + 0]));
}
}
uint64_t e;
copy_and_swap_32_unaligned(&e, d, 2);
REQUIRE(e == 0xEFCDAB8967452301);
@ -408,13 +454,56 @@ TEST_CASE("copy_and_swap_64_unaligned", "[copy_and_swap]") {
}
TEST_CASE("copy_and_swap_16_in_32_aligned", "[copy_and_swap]") {
// TODO(bwrsandman): test once properly understood.
REQUIRE(true == true);
constexpr size_t count = 17;
alignas(16) std::array<uint8_t, count * 4> src{};
alignas(16) std::array<uint8_t, count * 4> dst{};
// Check alignment (if this fails, adjust allocation)
REQUIRE((reinterpret_cast<uintptr_t>(src.data()) & 0xF) == 0);
REQUIRE((reinterpret_cast<uintptr_t>(dst.data()) & 0xF) == 0);
for (size_t i = 0; i < src.size(); ++i) {
src[i] = static_cast<uint8_t>(i) + 1; // no zero in array
}
copy_and_swap_16_in_32_aligned(dst.data(), src.data(), count);
for (size_t i = 0; i < src.size(); i += 4) {
// Check src is untouched
REQUIRE(static_cast<size_t>(src[i + 0]) == i + 1);
REQUIRE(static_cast<size_t>(src[i + 1]) == i + 2);
REQUIRE(static_cast<size_t>(src[i + 2]) == i + 3);
REQUIRE(static_cast<size_t>(src[i + 3]) == i + 4);
// Check swapped bytes
REQUIRE(static_cast<size_t>(dst[i + 0]) == static_cast<size_t>(src[i + 2]));
REQUIRE(static_cast<size_t>(dst[i + 1]) == static_cast<size_t>(src[i + 3]));
REQUIRE(static_cast<size_t>(dst[i + 2]) == static_cast<size_t>(src[i + 0]));
REQUIRE(static_cast<size_t>(dst[i + 3]) == static_cast<size_t>(src[i + 1]));
}
}
TEST_CASE("copy_and_swap_16_in_32_unaligned", "[copy_and_swap]") {
// TODO(bwrsandman): test once properly understood.
REQUIRE(true == true);
constexpr size_t count = 17;
std::array<uint8_t, count * 4> src{};
std::array<uint8_t, count * 4> dst{};
for (size_t i = 0; i < src.size(); ++i) {
src[i] = static_cast<uint8_t>(i) + 1; // no zero in array
}
copy_and_swap_16_in_32_unaligned(dst.data(), src.data(), count);
for (size_t i = 0; i < src.size(); i += 4) {
// Check src is untouched
REQUIRE(static_cast<size_t>(src[i + 0]) == i + 1);
REQUIRE(static_cast<size_t>(src[i + 1]) == i + 2);
REQUIRE(static_cast<size_t>(src[i + 2]) == i + 3);
REQUIRE(static_cast<size_t>(src[i + 3]) == i + 4);
// Check swapped bytes
REQUIRE(static_cast<size_t>(dst[i + 0]) == static_cast<size_t>(src[i + 2]));
REQUIRE(static_cast<size_t>(dst[i + 1]) == static_cast<size_t>(src[i + 3]));
REQUIRE(static_cast<size_t>(dst[i + 2]) == static_cast<size_t>(src[i + 0]));
REQUIRE(static_cast<size_t>(dst[i + 3]) == static_cast<size_t>(src[i + 1]));
}
}
TEST_CASE("create_and_close_file_mapping", "Virtual Memory Mapping") {

View File

@ -824,7 +824,7 @@ TEST_CASE("Create and Run Thread", "[thread]") {
}
SECTION("16kb stack size") {
params.stack_size = 16 * 1024 * 1024;
params.stack_size = 16_MiB;
thread = Thread::Create(params, [] {
Thread::Exit(-1);
FAIL("Function must not return");

View File

@ -25,11 +25,14 @@
#include <vector>
#include "xenia/base/assert.h"
#include "xenia/base/literals.h"
#include "xenia/base/platform.h"
namespace xe {
namespace threading {
using namespace xe::literals;
#if XE_PLATFORM_ANDROID
void AndroidInitialize();
void AndroidShutdown();
@ -368,7 +371,7 @@ struct ThreadPriority {
class Thread : public WaitHandle {
public:
struct CreationParameters {
size_t stack_size = 4 * 1024 * 1024;
size_t stack_size = 4_MiB;
bool create_suspended = false;
int32_t initial_priority = 0;
};

View File

@ -14,6 +14,7 @@
#include "xenia/base/platform.h"
#include <pthread.h>
#include <sched.h>
#include <signal.h>
#include <sys/eventfd.h>
#include <sys/syscall.h>
@ -28,7 +29,6 @@
#if XE_PLATFORM_ANDROID
#include <dlfcn.h>
#include <sched.h>
#include "xenia/base/main_android.h"
#include "xenia/base/string_util.h"
@ -128,11 +128,7 @@ uint32_t current_thread_system_id() {
}
void MaybeYield() {
#if XE_PLATFORM_ANDROID
sched_yield();
#else
pthread_yield();
#endif
__sync_synchronize();
}

View File

@ -8,11 +8,13 @@
*/
#include <cstddef>
#include <ostream>
#include <string>
#include "third_party/fmt/include/fmt/format.h"
#include "xenia/base/math.h"
#include "xenia/base/platform.h"
#include "xenia/base/string_util.h"
#include "xenia/base/vec128.h"
namespace xe {
@ -21,4 +23,9 @@ std::string to_string(const vec128_t& value) {
return fmt::format("({}, {}, {}, {})", value.x, value.y, value.z, value.w);
}
std::ostream& operator<<(std::ostream& os, const vec128_t& value) {
os << string_util::to_hex_string(value);
return os;
}
} // namespace xe

View File

@ -257,6 +257,8 @@ static inline vec128_t vec128b(uint8_t x0, uint8_t x1, uint8_t x2, uint8_t x3,
std::string to_string(const vec128_t& value);
std::ostream& operator<<(std::ostream& os, const vec128_t& value);
} // namespace xe
#endif // XENIA_BASE_VEC128_H_

View File

@ -105,6 +105,10 @@ void ReadGameConfig(const std::filesystem::path& file_path) {
}
void SaveConfig() {
if (config_path.empty()) {
return;
}
// All cvar defaults have been updated on loading - store the current date.
auto defaults_date_cvar =
dynamic_cast<cvar::ConfigVar<uint32_t>*>(cv::cv_defaults_date);

View File

@ -15,6 +15,7 @@
namespace config {
void SetupConfig(const std::filesystem::path& config_folder);
void LoadGameConfig(const std::string_view title_id);
void SaveConfig();
} // namespace config
#endif // XENIA_CONFIG_H_

View File

@ -26,10 +26,24 @@
#include "xenia/cpu/processor.h"
#include "xenia/cpu/stack_walker.h"
DEFINE_bool(
use_haswell_instructions, true,
"Uses the AVX2/FMA/etc instructions on Haswell processors when available.",
"CPU");
DEFINE_int32(x64_extension_mask, -1,
"Allow the detection and utilization of specific instruction set "
"features.\n"
" 0 = x86_64 + AVX1\n"
" 1 = AVX2\n"
" 2 = FMA\n"
" 4 = LZCNT\n"
" 8 = BMI1\n"
" 16 = BMI2\n"
" 32 = F16C\n"
" 64 = Movbe\n"
" 128 = GFNI\n"
" 256 = AVX512F\n"
" 512 = AVX512VL\n"
" 1024 = AVX512BW\n"
" 2048 = AVX512DQ\n"
" -1 = Detect and utilize all possible processor features\n",
"x64");
namespace xe {
namespace cpu {
@ -84,7 +98,7 @@ bool X64Backend::Initialize(Processor* processor) {
}
// Need movbe to do advanced LOAD/STORE tricks.
if (cvars::use_haswell_instructions) {
if (cvars::x64_extension_mask & kX64EmitMovbe) {
machine_info_.supports_extended_load_store =
cpu.has(Xbyak::util::Cpu::tMOVBE);
} else {

View File

@ -15,7 +15,7 @@
#include "xenia/base/cvar.h"
#include "xenia/cpu/backend/backend.h"
DECLARE_bool(use_haswell_instructions);
DECLARE_int32(x64_extension_mask);
namespace xe {
class Exception;

View File

@ -20,6 +20,7 @@
#include "third_party/fmt/include/fmt/format.h"
#include "xenia/base/assert.h"
#include "xenia/base/clock.h"
#include "xenia/base/literals.h"
#include "xenia/base/logging.h"
#include "xenia/base/math.h"
#include "xenia/base/memory.h"
@ -31,6 +32,8 @@ namespace cpu {
namespace backend {
namespace x64 {
using namespace xe::literals;
X64CodeCache::X64CodeCache() = default;
X64CodeCache::~X64CodeCache() {
@ -227,7 +230,7 @@ void X64CodeCache::PlaceGuestCode(uint32_t guest_address, void* machine_code,
old_commit_mark = generated_code_commit_mark_;
if (high_mark <= old_commit_mark) break;
new_commit_mark = old_commit_mark + 16 * 1024 * 1024;
new_commit_mark = old_commit_mark + 16_MiB;
if (generated_code_execute_base_ == generated_code_write_base_) {
xe::memory::AllocFixed(generated_code_execute_base_, new_commit_mark,
xe::memory::AllocationType::kCommit,
@ -310,7 +313,7 @@ uint32_t X64CodeCache::PlaceData(const void* data, size_t length) {
old_commit_mark = generated_code_commit_mark_;
if (high_mark <= old_commit_mark) break;
new_commit_mark = old_commit_mark + 16 * 1024 * 1024;
new_commit_mark = old_commit_mark + 16_MiB;
if (generated_code_execute_base_ == generated_code_write_base_) {
xe::memory::AllocFixed(generated_code_execute_base_, new_commit_mark,
xe::memory::AllocationType::kCommit,

View File

@ -18,6 +18,7 @@
#include "xenia/base/assert.h"
#include "xenia/base/atomic.h"
#include "xenia/base/debugging.h"
#include "xenia/base/literals.h"
#include "xenia/base/logging.h"
#include "xenia/base/math.h"
#include "xenia/base/memory.h"
@ -50,8 +51,9 @@ namespace x64 {
using xe::cpu::hir::HIRBuilder;
using xe::cpu::hir::Instr;
using namespace xe::literals;
static const size_t kMaxCodeSize = 1 * 1024 * 1024;
static const size_t kMaxCodeSize = 1_MiB;
static const size_t kStashOffset = 32;
// static const size_t kStashOffsetHigh = 32 + 32;
@ -72,21 +74,31 @@ X64Emitter::X64Emitter(X64Backend* backend, XbyakAllocator* allocator)
backend_(backend),
code_cache_(backend->code_cache()),
allocator_(allocator) {
if (cvars::use_haswell_instructions) {
feature_flags_ |= cpu_.has(Xbyak::util::Cpu::tAVX2) ? kX64EmitAVX2 : 0;
feature_flags_ |= cpu_.has(Xbyak::util::Cpu::tFMA) ? kX64EmitFMA : 0;
feature_flags_ |= cpu_.has(Xbyak::util::Cpu::tLZCNT) ? kX64EmitLZCNT : 0;
feature_flags_ |= cpu_.has(Xbyak::util::Cpu::tBMI2) ? kX64EmitBMI2 : 0;
feature_flags_ |= cpu_.has(Xbyak::util::Cpu::tF16C) ? kX64EmitF16C : 0;
feature_flags_ |= cpu_.has(Xbyak::util::Cpu::tMOVBE) ? kX64EmitMovbe : 0;
}
if (!cpu_.has(Xbyak::util::Cpu::tAVX)) {
xe::FatalError(
"Your CPU does not support AVX, which is required by Xenia. See the "
"FAQ for system requirements at https://xenia.jp");
return;
}
#define TEST_EMIT_FEATURE(emit, ext) \
if ((cvars::x64_extension_mask & emit) == emit) { \
feature_flags_ |= (cpu_.has(ext) ? emit : 0); \
}
TEST_EMIT_FEATURE(kX64EmitAVX2, Xbyak::util::Cpu::tAVX2);
TEST_EMIT_FEATURE(kX64EmitFMA, Xbyak::util::Cpu::tFMA);
TEST_EMIT_FEATURE(kX64EmitLZCNT, Xbyak::util::Cpu::tLZCNT);
TEST_EMIT_FEATURE(kX64EmitBMI1, Xbyak::util::Cpu::tBMI1);
TEST_EMIT_FEATURE(kX64EmitF16C, Xbyak::util::Cpu::tF16C);
TEST_EMIT_FEATURE(kX64EmitMovbe, Xbyak::util::Cpu::tMOVBE);
TEST_EMIT_FEATURE(kX64EmitGFNI, Xbyak::util::Cpu::tGFNI);
TEST_EMIT_FEATURE(kX64EmitAVX512F, Xbyak::util::Cpu::tAVX512F);
TEST_EMIT_FEATURE(kX64EmitAVX512VL, Xbyak::util::Cpu::tAVX512VL);
TEST_EMIT_FEATURE(kX64EmitAVX512BW, Xbyak::util::Cpu::tAVX512BW);
TEST_EMIT_FEATURE(kX64EmitAVX512DQ, Xbyak::util::Cpu::tAVX512DQ);
#undef TEST_EMIT_FEATURE
}
X64Emitter::~X64Emitter() = default;

View File

@ -125,12 +125,23 @@ class XbyakAllocator : public Xbyak::Allocator {
};
enum X64EmitterFeatureFlags {
kX64EmitAVX2 = 1 << 1,
kX64EmitFMA = 1 << 2,
kX64EmitLZCNT = 1 << 3,
kX64EmitAVX2 = 1 << 0,
kX64EmitFMA = 1 << 1,
kX64EmitLZCNT = 1 << 2,
kX64EmitBMI1 = 1 << 3,
kX64EmitBMI2 = 1 << 4,
kX64EmitF16C = 1 << 5,
kX64EmitMovbe = 1 << 6,
kX64EmitGFNI = 1 << 7,
kX64EmitAVX512F = 1 << 8,
kX64EmitAVX512VL = 1 << 9,
kX64EmitAVX512BW = 1 << 10,
kX64EmitAVX512DQ = 1 << 11,
kX64EmitAVX512Ortho = kX64EmitAVX512F | kX64EmitAVX512VL,
kX64EmitAVX512Ortho64 = kX64EmitAVX512Ortho | kX64EmitAVX512DQ
};
class X64Emitter : public Xbyak::CodeGenerator {
@ -221,7 +232,7 @@ class X64Emitter : public Xbyak::CodeGenerator {
Xbyak::Address StashConstantXmm(int index, const vec128_t& v);
bool IsFeatureEnabled(uint32_t feature_flag) const {
return (feature_flags_ & feature_flag) != 0;
return (feature_flags_ & feature_flag) == feature_flag;
}
FunctionDebugInfo* debug_info() const { return debug_info_; }

View File

@ -731,6 +731,25 @@ struct VECTOR_SHL_V128
static void EmitInt8(X64Emitter& e, const EmitArgType& i) {
// TODO(benvanik): native version (with shift magic).
if (i.src2.is_constant) {
if (e.IsFeatureEnabled(kX64EmitGFNI)) {
const auto& shamt = i.src2.constant();
bool all_same = true;
for (size_t n = 0; n < 16 - n; ++n) {
if (shamt.u8[n] != shamt.u8[n + 1]) {
all_same = false;
break;
}
}
if (all_same) {
// Every count is the same, so we can use gf2p8affineqb.
const uint8_t shift_amount = shamt.u8[0] & 0b111;
const uint64_t shift_matrix =
UINT64_C(0x0102040810204080) >> (shift_amount * 8);
e.vgf2p8affineqb(i.dest, i.src1,
e.StashConstantXmm(0, vec128q(shift_matrix)), 0);
return;
}
}
e.lea(e.GetNativeParam(1), e.StashConstantXmm(1, i.src2.constant()));
} else {
e.lea(e.GetNativeParam(1), e.StashXmm(1, i.src2));
@ -920,6 +939,25 @@ struct VECTOR_SHR_V128
static void EmitInt8(X64Emitter& e, const EmitArgType& i) {
// TODO(benvanik): native version (with shift magic).
if (i.src2.is_constant) {
if (e.IsFeatureEnabled(kX64EmitGFNI)) {
const auto& shamt = i.src2.constant();
bool all_same = true;
for (size_t n = 0; n < 16 - n; ++n) {
if (shamt.u8[n] != shamt.u8[n + 1]) {
all_same = false;
break;
}
}
if (all_same) {
// Every count is the same, so we can use gf2p8affineqb.
const uint8_t shift_amount = shamt.u8[0] & 0b111;
const uint64_t shift_matrix = UINT64_C(0x0102040810204080)
<< (shift_amount * 8);
e.vgf2p8affineqb(i.dest, i.src1,
e.StashConstantXmm(0, vec128q(shift_matrix)), 0);
return;
}
}
e.lea(e.GetNativeParam(1), e.StashConstantXmm(1, i.src2.constant()));
} else {
e.lea(e.GetNativeParam(1), e.StashXmm(1, i.src2));
@ -1084,6 +1122,27 @@ struct VECTOR_SHA_V128
static void EmitInt8(X64Emitter& e, const EmitArgType& i) {
// TODO(benvanik): native version (with shift magic).
if (i.src2.is_constant) {
if (e.IsFeatureEnabled(kX64EmitGFNI)) {
const auto& shamt = i.src2.constant();
bool all_same = true;
for (size_t n = 0; n < 16 - n; ++n) {
if (shamt.u8[n] != shamt.u8[n + 1]) {
all_same = false;
break;
}
}
if (all_same) {
// Every count is the same, so we can use gf2p8affineqb.
const uint8_t shift_amount = shamt.u8[0] & 0b111;
const uint64_t shift_matrix =
(UINT64_C(0x0102040810204080) << (shift_amount * 8)) |
(UINT64_C(0x8080808080808080) >> (64 - shift_amount * 8));
;
e.vgf2p8affineqb(i.dest, i.src1,
e.StashConstantXmm(0, vec128q(shift_matrix)), 0);
return;
}
}
e.lea(e.GetNativeParam(1), e.StashConstantXmm(1, i.src2.constant()));
} else {
e.lea(e.GetNativeParam(1), e.StashXmm(1, i.src2));

View File

@ -2627,6 +2627,115 @@ struct AND_V128 : Sequence<AND_V128, I<OPCODE_AND, V128Op, V128Op, V128Op>> {
};
EMITTER_OPCODE_TABLE(OPCODE_AND, AND_I8, AND_I16, AND_I32, AND_I64, AND_V128);
// ============================================================================
// OPCODE_AND_NOT
// ============================================================================
template <typename SEQ, typename REG, typename ARGS>
void EmitAndNotXX(X64Emitter& e, const ARGS& i) {
if (i.src1.is_constant) {
if (i.src2.is_constant) {
// Both constants.
e.mov(i.dest, i.src1.constant() & ~i.src2.constant());
} else {
// src1 constant.
// `and` instruction only supports up to 32-bit immediate constants
// 64-bit constants will need a temp register
if (i.dest.reg().getBit() == 64) {
auto temp = GetTempReg<typename decltype(i.src1)::reg_type>(e);
e.mov(temp, i.src1.constant());
if (e.IsFeatureEnabled(kX64EmitBMI1)) {
if (i.dest.reg().getBit() == 64) {
e.andn(i.dest.reg().cvt64(), i.src2.reg().cvt64(), temp.cvt64());
} else {
e.andn(i.dest.reg().cvt32(), i.src2.reg().cvt32(), temp.cvt32());
}
} else {
e.mov(i.dest, i.src2);
e.not_(i.dest);
e.and_(i.dest, temp);
}
} else {
e.mov(i.dest, i.src2);
e.not_(i.dest);
e.and_(i.dest, uint32_t(i.src1.constant()));
}
}
} else if (i.src2.is_constant) {
// src2 constant.
if (i.dest == i.src1) {
auto temp = GetTempReg<typename decltype(i.src2)::reg_type>(e);
e.mov(temp, ~i.src2.constant());
e.and_(i.dest, temp);
} else {
e.mov(i.dest, i.src1);
auto temp = GetTempReg<typename decltype(i.src2)::reg_type>(e);
e.mov(temp, ~i.src2.constant());
e.and_(i.dest, temp);
}
} else {
// neither are constant
if (e.IsFeatureEnabled(kX64EmitBMI1)) {
if (i.dest.reg().getBit() == 64) {
e.andn(i.dest.reg().cvt64(), i.src2.reg().cvt64(),
i.src1.reg().cvt64());
} else {
e.andn(i.dest.reg().cvt32(), i.src2.reg().cvt32(),
i.src1.reg().cvt32());
}
} else {
if (i.dest == i.src2) {
e.not_(i.dest);
e.and_(i.dest, i.src1);
} else if (i.dest == i.src1) {
auto temp = GetTempReg<typename decltype(i.dest)::reg_type>(e);
e.mov(temp, i.src2);
e.not_(temp);
e.and_(i.dest, temp);
} else {
e.mov(i.dest, i.src2);
e.not_(i.dest);
e.and_(i.dest, i.src1);
}
}
}
}
struct AND_NOT_I8 : Sequence<AND_NOT_I8, I<OPCODE_AND_NOT, I8Op, I8Op, I8Op>> {
static void Emit(X64Emitter& e, const EmitArgType& i) {
EmitAndNotXX<AND_NOT_I8, Reg8>(e, i);
}
};
struct AND_NOT_I16
: Sequence<AND_NOT_I16, I<OPCODE_AND_NOT, I16Op, I16Op, I16Op>> {
static void Emit(X64Emitter& e, const EmitArgType& i) {
EmitAndNotXX<AND_NOT_I16, Reg16>(e, i);
}
};
struct AND_NOT_I32
: Sequence<AND_NOT_I32, I<OPCODE_AND_NOT, I32Op, I32Op, I32Op>> {
static void Emit(X64Emitter& e, const EmitArgType& i) {
EmitAndNotXX<AND_NOT_I32, Reg32>(e, i);
}
};
struct AND_NOT_I64
: Sequence<AND_NOT_I64, I<OPCODE_AND_NOT, I64Op, I64Op, I64Op>> {
static void Emit(X64Emitter& e, const EmitArgType& i) {
EmitAndNotXX<AND_NOT_I64, Reg64>(e, i);
}
};
struct AND_NOT_V128
: Sequence<AND_NOT_V128, I<OPCODE_AND_NOT, V128Op, V128Op, V128Op>> {
static void Emit(X64Emitter& e, const EmitArgType& i) {
EmitCommutativeBinaryXmmOp(e, i,
[](X64Emitter& e, Xmm dest, Xmm src1, Xmm src2) {
e.vpandn(dest, src2, src1);
});
}
};
EMITTER_OPCODE_TABLE(OPCODE_AND_NOT, AND_NOT_I8, AND_NOT_I16, AND_NOT_I32,
AND_NOT_I64, AND_NOT_V128);
// ============================================================================
// OPCODE_OR
// ============================================================================

View File

@ -38,6 +38,13 @@ DEFINE_bool(
DEFINE_bool(validate_hir, false,
"Perform validation checks on the HIR during compilation.", "CPU");
DEFINE_uint64(
pvr, 0x710700,
"Processor version and revision number.\nBits 0 to 15 are the version "
"number.\nBits 16 to 31 are the revision number.\nNote: Some XEXs (such as "
"mfgbootlauncher.xex) may check for a value that's less than 0x710700.",
"CPU");
// Breakpoints:
DEFINE_uint64(break_on_instruction, 0,
"int3 before the given guest address is executed.", "CPU");

View File

@ -26,6 +26,9 @@ DECLARE_bool(disable_global_lock);
DECLARE_bool(validate_hir);
DECLARE_uint64(pvr);
// Breakpoints:
DECLARE_uint64(break_on_instruction);
DECLARE_int32(break_condition_gpr);
DECLARE_uint64(break_condition_value);

View File

@ -1759,6 +1759,26 @@ Value* HIRBuilder::And(Value* value1, Value* value2) {
return i->dest;
}
Value* HIRBuilder::AndNot(Value* value1, Value* value2) {
ASSERT_NON_FLOAT_TYPE(value1);
ASSERT_NON_FLOAT_TYPE(value2);
ASSERT_TYPES_EQUAL(value1, value2);
if (value1 == value2) {
return LoadZero(value1->type);
} else if (value1->IsConstantZero()) {
return value1;
} else if (value2->IsConstantZero()) {
return value1;
}
Instr* i = AppendInstr(OPCODE_AND_NOT_info, 0, AllocValue(value1->type));
i->set_src1(value1);
i->set_src2(value2);
i->src3.value = NULL;
return i->dest;
}
Value* HIRBuilder::Or(Value* value1, Value* value2) {
ASSERT_NON_FLOAT_TYPE(value1);
ASSERT_NON_FLOAT_TYPE(value2);

View File

@ -224,6 +224,7 @@ class HIRBuilder {
Value* DotProduct4(Value* value1, Value* value2);
Value* And(Value* value1, Value* value2);
Value* AndNot(Value* value1, Value* value2);
Value* Or(Value* value1, Value* value2);
Value* Xor(Value* value1, Value* value2);
Value* Not(Value* value);

View File

@ -255,6 +255,7 @@ enum Opcode {
OPCODE_DOT_PRODUCT_3,
OPCODE_DOT_PRODUCT_4,
OPCODE_AND,
OPCODE_AND_NOT,
OPCODE_OR,
OPCODE_XOR,
OPCODE_NOT,

View File

@ -524,6 +524,12 @@ DEFINE_OPCODE(
OPCODE_SIG_V_V_V,
OPCODE_FLAG_COMMUNATIVE)
DEFINE_OPCODE(
OPCODE_AND_NOT,
"and_not",
OPCODE_SIG_V_V_V,
0)
DEFINE_OPCODE(
OPCODE_OR,
"or",

View File

@ -286,7 +286,7 @@ int InstrEmit_stvlx_(PPCHIRBuilder& f, const InstrData& i, uint32_t vd,
// mask = FFFF... >> eb
Value* mask = f.Permute(f.LoadVectorShr(eb), f.LoadZeroVec128(),
f.Not(f.LoadZeroVec128()), INT8_TYPE);
Value* v = f.Or(f.And(old_value, f.Not(mask)), f.And(new_value, mask));
Value* v = f.Or(f.AndNot(old_value, mask), f.And(new_value, mask));
// ea &= ~0xF (handled above)
f.Store(ea, f.ByteSwap(v));
return 0;
@ -328,7 +328,7 @@ int InstrEmit_stvrx_(PPCHIRBuilder& f, const InstrData& i, uint32_t vd,
// mask = ~FFFF... >> eb
Value* mask = f.Permute(f.LoadVectorShr(eb), f.Not(f.LoadZeroVec128()),
f.LoadZeroVec128(), INT8_TYPE);
Value* v = f.Or(f.And(old_value, f.Not(mask)), f.And(new_value, mask));
Value* v = f.Or(f.AndNot(old_value, mask), f.And(new_value, mask));
// ea &= ~0xF (handled above)
f.Store(ea, f.ByteSwap(v));
f.MarkLabel(skip_label);
@ -459,7 +459,7 @@ int InstrEmit_vand128(PPCHIRBuilder& f, const InstrData& i) {
int InstrEmit_vandc_(PPCHIRBuilder& f, uint32_t vd, uint32_t va, uint32_t vb) {
// VD <- (VA) & ¬(VB)
Value* v = f.And(f.LoadVR(va), f.Not(f.LoadVR(vb)));
Value* v = f.AndNot(f.LoadVR(va), f.LoadVR(vb));
f.StoreVR(vd, v);
return 0;
}

View File

@ -657,7 +657,7 @@ int InstrEmit_andx(PPCHIRBuilder& f, const InstrData& i) {
int InstrEmit_andcx(PPCHIRBuilder& f, const InstrData& i) {
// RA <- (RS) & ¬(RB)
Value* ra = f.And(f.LoadGPR(i.X.RT), f.Not(f.LoadGPR(i.X.RB)));
Value* ra = f.AndNot(f.LoadGPR(i.X.RT), f.LoadGPR(i.X.RB));
f.StoreGPR(i.X.RA, ra);
if (i.X.Rc) {
f.UpdateCR(0, ra);

View File

@ -620,6 +620,16 @@ int InstrEmit_mfspr(PPCHIRBuilder& f, const InstrData& i) {
// TBU
v = f.Shr(f.LoadClock(), 32);
break;
case 287:
// [ Processor Version Register (PVR) ]
// PVR is a 32 bit, read-only register within the supervisor level.
// Bits 0 to 15 are the version number.
// Bits 16 to 31 are the revision number.
// Known Values: 0x710600?, 0x710700, 0x710800 (Corona?);
// Note: Some XEXs (such as mfgbootlauncher.xex) may check for a value
// that's less than 0x710700.
v = f.LoadConstantUint64(cvars::pvr);
break;
default:
XEINSTRNOTIMPLEMENTED();
return 1;

View File

@ -10,6 +10,7 @@
#include "xenia/base/console_app_main.h"
#include "xenia/base/cvar.h"
#include "xenia/base/filesystem.h"
#include "xenia/base/literals.h"
#include "xenia/base/logging.h"
#include "xenia/base/math.h"
#include "xenia/base/platform.h"
@ -36,6 +37,7 @@ namespace cpu {
namespace test {
using xe::cpu::ppc::PPCContext;
using namespace xe::literals;
typedef std::vector<std::pair<std::string, std::string>> AnnotationList;
@ -177,7 +179,7 @@ class TestSuite {
class TestRunner {
public:
TestRunner() : memory_size_(64 * 1024 * 1024) {
TestRunner() : memory_size_(64_MiB) {
memory_.reset(new Memory());
memory_->Initialize();
}
@ -420,8 +422,7 @@ bool RunTests(const std::string_view test_name) {
int failed_count = 0;
int passed_count = 0;
XELOGI("Haswell instruction usage {}.",
cvars::use_haswell_instructions ? "enabled" : "disabled");
XELOGI("Instruction feature mask {}.", cvars::x64_extension_mask);
auto test_path_root = cvars::test_path;
std::vector<std::filesystem::path> test_files;

View File

@ -16,6 +16,7 @@
#include "xenia/base/cvar.h"
#include "xenia/base/debugging.h"
#include "xenia/base/exception_handler.h"
#include "xenia/base/literals.h"
#include "xenia/base/logging.h"
#include "xenia/base/memory.h"
#include "xenia/base/profiling.h"
@ -57,6 +58,8 @@ namespace cpu {
using xe::cpu::ppc::PPCOpcode;
using xe::kernel::XThread;
using namespace xe::literals;
class BuiltinModule : public Module {
public:
explicit BuiltinModule(Processor* processor)
@ -142,8 +145,8 @@ bool Processor::Setup(std::unique_ptr<backend::Backend> backend) {
// Open the trace data path, if requested.
functions_trace_path_ = cvars::trace_function_data_path;
if (!functions_trace_path_.empty()) {
functions_trace_file_ = ChunkedMappedMemoryWriter::Open(
functions_trace_path_, 32 * 1024 * 1024, true);
functions_trace_file_ =
ChunkedMappedMemoryWriter::Open(functions_trace_path_, 32_MiB, true);
}
return true;

View File

@ -58,6 +58,28 @@ TEST_CASE("VECTOR_SHA_I8_CONSTANT", "[instr]") {
});
}
// This targets the "all_same" optimization of the Int8 specialization of
// VECTOR_SHA_V128
TEST_CASE("VECTOR_SHA_I8_SAME_CONSTANT", "[instr]") {
TestFunction test([](HIRBuilder& b) {
StoreVR(
b, 3,
b.VectorSha(LoadVR(b, 4), b.LoadConstantVec128(vec128b(5)), INT8_TYPE));
b.Return();
});
test.Run(
[](PPCContext* ctx) {
ctx->v[4] = vec128b(0x7E, 0x7E, 0x7E, 0x7F, 0x80, 0xFF, 0x01, 0x12,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
},
[](PPCContext* ctx) {
auto result = ctx->v[3];
REQUIRE(result == vec128b(0x03, 0x03, 0x03, 0x03, 0xfc, 0xff, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00));
});
}
TEST_CASE("VECTOR_SHA_I16", "[instr]") {
TestFunction test([](HIRBuilder& b) {
StoreVR(b, 3, b.VectorSha(LoadVR(b, 4), LoadVR(b, 5), INT16_TYPE));

View File

@ -58,6 +58,28 @@ TEST_CASE("VECTOR_SHL_I8_CONSTANT", "[instr]") {
});
}
// This targets the "all_same" optimization of the Int8 specialization of
// VECTOR_SHL_V128
TEST_CASE("VECTOR_SHL_I8_SAME_CONSTANT", "[instr]") {
TestFunction test([](HIRBuilder& b) {
StoreVR(
b, 3,
b.VectorShl(LoadVR(b, 4), b.LoadConstantVec128(vec128b(5)), INT8_TYPE));
b.Return();
});
test.Run(
[](PPCContext* ctx) {
ctx->v[4] = vec128b(0x7E, 0x7E, 0x7E, 0x7F, 0x80, 0xFF, 0x01, 0x12,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
},
[](PPCContext* ctx) {
auto result = ctx->v[3];
REQUIRE(result == vec128b(0xC0, 0xC0, 0xC0, 0xE0, 0x00, 0xE0, 0x20,
0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00));
});
}
TEST_CASE("VECTOR_SHL_I16", "[instr]") {
TestFunction test([](HIRBuilder& b) {
StoreVR(b, 3, b.VectorShl(LoadVR(b, 4), LoadVR(b, 5), INT16_TYPE));

View File

@ -58,6 +58,28 @@ TEST_CASE("VECTOR_SHR_I8_CONSTANT", "[instr]") {
});
}
// This targets the "all_same" optimization of the Int8 specialization of
// VECTOR_SHR_V128
TEST_CASE("VECTOR_SHR_I8_SAME_CONSTANT", "[instr]") {
TestFunction test([](HIRBuilder& b) {
StoreVR(
b, 3,
b.VectorShr(LoadVR(b, 4), b.LoadConstantVec128(vec128b(3)), INT8_TYPE));
b.Return();
});
test.Run(
[](PPCContext* ctx) {
ctx->v[4] = vec128b(0x7E, 0x7E, 0x7E, 0x7F, 0x80, 0xFF, 0x01, 0x12,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
},
[](PPCContext* ctx) {
auto result = ctx->v[3];
REQUIRE(result == vec128b(0x0F, 0x0F, 0x0F, 0x0F, 0x10, 0x1F, 0x00,
0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00));
});
}
TEST_CASE("VECTOR_SHR_I16", "[instr]") {
TestFunction test([](HIRBuilder& b) {
StoreVR(b, 3, b.VectorShr(LoadVR(b, 4), LoadVR(b, 5), INT16_TYPE));

View File

@ -2,7 +2,7 @@
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2020 Ben Vanik. All rights reserved. *
* Copyright 2022 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
@ -32,6 +32,8 @@
#include "xenia/kernel/xthread.h"
#include "xenia/ui/graphics_provider.h"
#include "xenia/ui/imgui_drawer.h"
#include "xenia/ui/immediate_drawer.h"
#include "xenia/ui/presenter.h"
#include "xenia/ui/windowed_app_context.h"
DEFINE_bool(imgui_debug, false, "Show ImGui debugging tools.", "UI");
@ -49,14 +51,18 @@ using xe::ui::MenuItem;
using xe::ui::MouseEvent;
using xe::ui::UIEvent;
const std::string kBaseTitle = "Xenia Debugger";
void DebugWindow::DebugDialog::OnDraw(ImGuiIO& io) {
debug_window_.DrawFrame(io);
}
static const std::string kBaseTitle = "Xenia Debugger";
DebugWindow::DebugWindow(Emulator* emulator,
xe::ui::WindowedAppContext& app_context)
: emulator_(emulator),
processor_(emulator->processor()),
app_context_(app_context),
window_(xe::ui::Window::Create(app_context_, kBaseTitle)) {
window_(xe::ui::Window::Create(app_context_, kBaseTitle, 1500, 1000)) {
if (cs_open(CS_ARCH_X86, CS_MODE_64, &capstone_handle_) != CS_ERR_OK) {
assert_always("Failed to initialize capstone");
}
@ -86,44 +92,57 @@ std::unique_ptr<DebugWindow> DebugWindow::Create(
}
bool DebugWindow::Initialize() {
if (!window_->Initialize()) {
XELOGE("Failed to initialize platform window");
return false;
}
// Main menu.
auto main_menu = MenuItem::Create(MenuItem::Type::kNormal);
auto file_menu = MenuItem::Create(MenuItem::Type::kPopup, "&File");
{
file_menu->AddChild(MenuItem::Create(MenuItem::Type::kString, "&Close",
"Alt+F4",
[this]() { window_->Close(); }));
file_menu->AddChild(
MenuItem::Create(MenuItem::Type::kString, "&Close", "Alt+F4",
[this]() { window_->RequestClose(); }));
}
main_menu->AddChild(std::move(file_menu));
window_->set_main_menu(std::move(main_menu));
window_->SetMainMenu(std::move(main_menu));
window_->Resize(1500, 1000);
// Open the window once it's configured.
if (!window_->Open()) {
XELOGE("Failed to open the platform window for the debugger");
return false;
}
// Create the graphics context used for drawing.
auto provider = emulator_->display_window()->context()->provider();
window_->set_context(provider->CreateHostContext(window_.get()));
// Setup drawing to the window.
// Enable imgui input.
window_->set_imgui_input_enabled(true);
xe::ui::GraphicsProvider& graphics_provider =
*emulator_->graphics_system()->provider();
window_->on_painting.AddListener([this](UIEvent* e) { DrawFrame(); });
presenter_ = graphics_provider.CreatePresenter();
if (!presenter_) {
XELOGE("Failed to initialize the presenter for the debugger");
return false;
}
immediate_drawer_ = graphics_provider.CreateImmediateDrawer();
if (!immediate_drawer_) {
XELOGE("Failed to initialize the immediate drawer for the debugger");
return false;
}
immediate_drawer_->SetPresenter(presenter_.get());
imgui_drawer_ = std::make_unique<xe::ui::ImGuiDrawer>(window_.get(), 0);
imgui_drawer_->SetPresenterAndImmediateDrawer(presenter_.get(),
immediate_drawer_.get());
debug_dialog_ =
std::unique_ptr<DebugDialog>(new DebugDialog(imgui_drawer_.get(), *this));
// Update the cache before the first frame.
UpdateCache();
window_->Invalidate();
// Begin drawing.
window_->SetPresenter(presenter_.get());
return true;
}
void DebugWindow::DrawFrame() {
xe::ui::GraphicsContextLock lock(window_->context());
auto& io = window_->imgui_drawer()->GetIO();
void DebugWindow::DrawFrame(ImGuiIO& io) {
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(-1, 0));
ImGui::Begin("main_window", nullptr,
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize |
@ -242,9 +261,6 @@ void DebugWindow::DrawFrame() {
ImGui::ShowDemoWindow();
ImGui::ShowMetricsWindow();
}
// Continuous paint.
window_->Invalidate();
}
void DebugWindow::DrawToolbar() {
@ -1443,7 +1459,7 @@ void DebugWindow::UpdateCache() {
title += " (stepping)";
break;
}
window_->set_title(title);
window_->SetTitle(title);
});
cache_.is_running =
@ -1573,7 +1589,7 @@ void DebugWindow::OnBreakpointHit(Breakpoint* breakpoint,
}
void DebugWindow::Focus() const {
app_context_.CallInUIThread([this]() { window_->set_focus(true); });
app_context_.CallInUIThread([this]() { window_->Focus(); });
}
} // namespace ui

View File

@ -2,7 +2,7 @@
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2015 Ben Vanik. All rights reserved. *
* Copyright 2022 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
@ -18,7 +18,11 @@
#include "xenia/cpu/debug_listener.h"
#include "xenia/cpu/processor.h"
#include "xenia/emulator.h"
#include "xenia/ui/imgui_dialog.h"
#include "xenia/ui/imgui_drawer.h"
#include "xenia/ui/immediate_drawer.h"
#include "xenia/ui/menu_item.h"
#include "xenia/ui/presenter.h"
#include "xenia/ui/window.h"
#include "xenia/ui/windowed_app_context.h"
#include "xenia/xbox.h"
@ -48,11 +52,24 @@ class DebugWindow : public cpu::DebugListener {
cpu::ThreadDebugInfo* thread_info) override;
private:
class DebugDialog final : public xe::ui::ImGuiDialog {
public:
explicit DebugDialog(xe::ui::ImGuiDrawer* imgui_drawer,
DebugWindow& debug_window)
: xe::ui::ImGuiDialog(imgui_drawer), debug_window_(debug_window) {}
protected:
void OnDraw(ImGuiIO& io) override;
private:
DebugWindow& debug_window_;
};
explicit DebugWindow(Emulator* emulator,
xe::ui::WindowedAppContext& app_context);
bool Initialize();
void DrawFrame();
void DrawFrame(ImGuiIO& io);
void DrawToolbar();
void DrawFunctionsPane();
void DrawSourcePane();
@ -93,7 +110,10 @@ class DebugWindow : public cpu::DebugListener {
cpu::Processor* processor_ = nullptr;
xe::ui::WindowedAppContext& app_context_;
std::unique_ptr<xe::ui::Window> window_;
uint64_t last_draw_tick_count_ = 0;
std::unique_ptr<xe::ui::Presenter> presenter_;
std::unique_ptr<xe::ui::ImmediateDrawer> immediate_drawer_;
std::unique_ptr<xe::ui::ImGuiDrawer> imgui_drawer_;
std::unique_ptr<DebugDialog> debug_dialog_;
uintptr_t capstone_handle_ = 0;

View File

@ -9,6 +9,7 @@
#include "xenia/emulator.h"
#include <algorithm>
#include <cinttypes>
#include "config.h"
@ -20,9 +21,9 @@
#include "xenia/base/cvar.h"
#include "xenia/base/debugging.h"
#include "xenia/base/exception_handler.h"
#include "xenia/base/literals.h"
#include "xenia/base/logging.h"
#include "xenia/base/mapped_memory.h"
#include "xenia/base/profiling.h"
#include "xenia/base/string.h"
#include "xenia/cpu/backend/code_cache.h"
#include "xenia/cpu/backend/x64/x64_backend.h"
@ -40,6 +41,7 @@
#include "xenia/kernel/xboxkrnl/xboxkrnl_module.h"
#include "xenia/memory.h"
#include "xenia/ui/imgui_dialog.h"
#include "xenia/ui/imgui_drawer.h"
#include "xenia/ui/window.h"
#include "xenia/ui/windowed_app_context.h"
#include "xenia/vfs/devices/disc_image_device.h"
@ -60,6 +62,17 @@ DEFINE_string(
namespace xe {
using namespace xe::literals;
Emulator::GameConfigLoadCallback::GameConfigLoadCallback(Emulator& emulator)
: emulator_(emulator) {
emulator_.AddGameConfigLoadCallback(this);
}
Emulator::GameConfigLoadCallback::~GameConfigLoadCallback() {
emulator_.RemoveGameConfigLoadCallback(this);
}
Emulator::Emulator(const std::filesystem::path& command_line,
const std::filesystem::path& storage_root,
const std::filesystem::path& content_root,
@ -113,7 +126,7 @@ Emulator::~Emulator() {
}
X_STATUS Emulator::Setup(
ui::Window* display_window,
ui::Window* display_window, ui::ImGuiDrawer* imgui_drawer,
std::function<std::unique_ptr<apu::AudioSystem>(cpu::Processor*)>
audio_system_factory,
std::function<std::unique_ptr<gpu::GraphicsSystem>()>
@ -123,6 +136,7 @@ X_STATUS Emulator::Setup(
X_STATUS result = X_STATUS_UNSUCCESSFUL;
display_window_ = display_window;
imgui_drawer_ = imgui_drawer;
// Initialize clock.
// 360 uses a 50MHz clock.
@ -209,8 +223,10 @@ X_STATUS Emulator::Setup(
kernel_state_ = std::make_unique<xe::kernel::KernelState>(this);
// Setup the core components.
result = graphics_system_->Setup(processor_.get(), kernel_state_.get(),
display_window_);
result = graphics_system_->Setup(
processor_.get(), kernel_state_.get(),
display_window_ ? &display_window_->app_context() : nullptr,
display_window_ != nullptr);
if (result) {
return result;
}
@ -233,14 +249,6 @@ X_STATUS Emulator::Setup(
// Initialize emulator fallback exception handling last.
ExceptionHandler::Install(Emulator::ExceptionCallbackThunk, this);
if (display_window_) {
// Finish initializing the display.
display_window_->app_context().CallInUIThreadSynchronous([this]() {
xe::ui::GraphicsContextLock context_lock(display_window_->context());
Profiler::set_window(display_window_);
});
}
return result;
}
@ -414,9 +422,8 @@ void Emulator::Resume() {
bool Emulator::SaveToFile(const std::filesystem::path& path) {
Pause();
filesystem::CreateFile(path);
auto map = MappedMemory::Open(path, MappedMemory::Mode::kReadWrite, 0,
1024ull * 1024ull * 1024ull * 2ull);
filesystem::CreateEmptyFile(path);
auto map = MappedMemory::Open(path, MappedMemory::Mode::kReadWrite, 0, 2_GiB);
if (!map) {
return false;
}
@ -585,14 +592,16 @@ bool Emulator::ExceptionCallback(Exception* ex) {
}
// Display a dialog telling the user the guest has crashed.
display_window()->app_context().CallInUIThreadSynchronous([this]() {
xe::ui::ImGuiDialog::ShowMessageBox(
display_window(), "Uh-oh!",
"The guest has crashed.\n\n"
""
"Xenia has now paused itself.\n"
"A crash dump has been written into the log.");
});
if (display_window_ && imgui_drawer_) {
display_window_->app_context().CallInUIThreadSynchronous([this]() {
xe::ui::ImGuiDialog::ShowMessageBox(
imgui_drawer_, "Uh-oh!",
"The guest has crashed.\n\n"
""
"Xenia has now paused itself.\n"
"A crash dump has been written into the log.");
});
}
// Now suspend ourself (we should be a guest thread).
current_thread->Suspend(nullptr);
@ -619,6 +628,41 @@ void Emulator::WaitUntilExit() {
on_exit();
}
void Emulator::AddGameConfigLoadCallback(GameConfigLoadCallback* callback) {
assert_not_null(callback);
// Game config load callbacks handling is entirely in the UI thread.
assert_true(!display_window_ ||
display_window_->app_context().IsInUIThread());
// Check if already added.
if (std::find(game_config_load_callbacks_.cbegin(),
game_config_load_callbacks_.cend(),
callback) != game_config_load_callbacks_.cend()) {
return;
}
game_config_load_callbacks_.push_back(callback);
}
void Emulator::RemoveGameConfigLoadCallback(GameConfigLoadCallback* callback) {
assert_not_null(callback);
// Game config load callbacks handling is entirely in the UI thread.
assert_true(!display_window_ ||
display_window_->app_context().IsInUIThread());
auto it = std::find(game_config_load_callbacks_.cbegin(),
game_config_load_callbacks_.cend(), callback);
if (it == game_config_load_callbacks_.cend()) {
return;
}
if (game_config_load_callback_loop_next_index_ != SIZE_MAX) {
// Actualize the next callback index after the erasure from the vector.
size_t existing_index =
size_t(std::distance(game_config_load_callbacks_.cbegin(), it));
if (game_config_load_callback_loop_next_index_ > existing_index) {
--game_config_load_callback_loop_next_index_;
}
}
game_config_load_callbacks_.erase(it);
}
std::string Emulator::FindLaunchModule() {
std::string path("game:\\");
@ -675,6 +719,10 @@ static std::string format_version(xex2_version version) {
X_STATUS Emulator::CompleteLaunch(const std::filesystem::path& path,
const std::string_view module_path) {
// Making changes to the UI (setting the icon) and executing game config load
// callbacks which expect to be called from the UI thread.
assert_true(display_window_->app_context().IsInUIThread());
// Setup NullDevices for raw HDD partition accesses
// Cache/STFC code baked into games tries reading/writing to these
// By using a NullDevice that just returns success to all IO requests it
@ -727,7 +775,19 @@ X_STATUS Emulator::CompleteLaunch(const std::filesystem::path& path,
// Try and load the resource database (xex only).
if (module->title_id()) {
auto title_id = fmt::format("{:08X}", module->title_id());
// Load the per-game configuration file and make sure updates are handled by
// the callbacks.
config::LoadGameConfig(title_id);
assert_true(game_config_load_callback_loop_next_index_ == SIZE_MAX);
game_config_load_callback_loop_next_index_ = 0;
while (game_config_load_callback_loop_next_index_ <
game_config_load_callbacks_.size()) {
game_config_load_callbacks_[game_config_load_callback_loop_next_index_++]
->PostGameConfigLoad();
}
game_config_load_callback_loop_next_index_ = SIZE_MAX;
uint32_t resource_data = 0;
uint32_t resource_size = 0;
if (XSUCCEEDED(module->GetSection(title_id.c_str(), &resource_data,

View File

@ -10,9 +10,12 @@
#ifndef XENIA_EMULATOR_H_
#define XENIA_EMULATOR_H_
#include <cstddef>
#include <cstdint>
#include <functional>
#include <optional>
#include <string>
#include <vector>
#include "xenia/base/delegate.h"
#include "xenia/base/exception_handler.h"
@ -38,6 +41,7 @@ class InputDriver;
class InputSystem;
} // namespace hid
namespace ui {
class ImGuiDrawer;
class Window;
} // namespace ui
} // namespace xe
@ -50,6 +54,37 @@ constexpr fourcc_t kEmulatorSaveSignature = make_fourcc("XSAV");
// This is responsible for initializing and managing all the various subsystems.
class Emulator {
public:
// This is the class for the top-level callbacks. They may be called in an
// undefined order, so among them there must be no dependencies on each other,
// especially hierarchical ones. If hierarchical handling is needed, for
// instance, if a specific implementation of a subsystem needs to handle
// changes, but the entire implementation must be reloaded, the implementation
// in this example _must not_ register / unregister its own callback - rather,
// the proper ordering and hierarchy should be constructed in a single
// callback (in this example, for the whole subsystem).
//
// All callbacks must be created and destroyed in the UI thread only (or the
// thread that takes its place in the architecture of the specific app if
// there's no UI), as they are invoked in the UI thread.
class GameConfigLoadCallback {
public:
GameConfigLoadCallback(Emulator& emulator);
GameConfigLoadCallback(const GameConfigLoadCallback& callback) = delete;
GameConfigLoadCallback& operator=(const GameConfigLoadCallback& callback) =
delete;
virtual ~GameConfigLoadCallback();
// The callback is invoked in the UI thread (or the thread that takes its
// place in the architecture of the specific app if there's no UI).
virtual void PostGameConfigLoad() = 0;
protected:
Emulator& emulator() const { return emulator_; }
private:
Emulator& emulator_;
};
explicit Emulator(const std::filesystem::path& command_line,
const std::filesystem::path& storage_root,
const std::filesystem::path& content_root,
@ -82,9 +117,13 @@ class Emulator {
// Are we currently running a title?
bool is_title_open() const { return title_id_.has_value(); }
// Window used for displaying graphical output.
// Window used for displaying graphical output. Can be null.
ui::Window* display_window() const { return display_window_; }
// ImGui drawer for various kinds of dialogs requested by the guest. Can be
// null.
ui::ImGuiDrawer* imgui_drawer() const { return imgui_drawer_; }
// Guest memory system modelling the RAM (both virtual and physical) of the
// system.
Memory* memory() const { return memory_.get(); }
@ -121,7 +160,7 @@ class Emulator {
// Once this function returns a game can be launched using one of the Launch
// functions.
X_STATUS Setup(
ui::Window* display_window,
ui::Window* display_window, ui::ImGuiDrawer* imgui_drawer,
std::function<std::unique_ptr<apu::AudioSystem>(cpu::Processor*)>
audio_system_factory,
std::function<std::unique_ptr<gpu::GraphicsSystem>()>
@ -170,6 +209,9 @@ class Emulator {
static bool ExceptionCallbackThunk(Exception* ex, void* data);
bool ExceptionCallback(Exception* ex);
void AddGameConfigLoadCallback(GameConfigLoadCallback* callback);
void RemoveGameConfigLoadCallback(GameConfigLoadCallback* callback);
std::string FindLaunchModule();
X_STATUS CompleteLaunch(const std::filesystem::path& path,
@ -183,7 +225,8 @@ class Emulator {
std::string title_name_;
std::string title_version_;
ui::Window* display_window_;
ui::Window* display_window_ = nullptr;
ui::ImGuiDrawer* imgui_drawer_ = nullptr;
std::unique_ptr<Memory> memory_;
@ -196,6 +239,16 @@ class Emulator {
std::unique_ptr<vfs::VirtualFileSystem> file_system_;
std::unique_ptr<kernel::KernelState> kernel_state_;
// Accessible only from the thread that invokes those callbacks (the UI thread
// if the UI is available).
std::vector<GameConfigLoadCallback*> game_config_load_callbacks_;
// Using an index, not an iterator, because after the erasure, the adjustment
// must be done for the vector element indices that would be in the iterator
// range that would be invalidated.
// SIZE_MAX if not currently in the game config load callback loop.
size_t game_config_load_callback_loop_next_index_ = SIZE_MAX;
kernel::object_ref<kernel::XThread> main_thread_;
std::optional<uint32_t> title_id_; // Currently running title ID

View File

@ -2,7 +2,7 @@
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2020 Ben Vanik. All rights reserved. *
* Copyright 2022 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
@ -45,15 +45,12 @@ CommandProcessor::CommandProcessor(GraphicsSystem* graphics_system,
CommandProcessor::~CommandProcessor() = default;
bool CommandProcessor::Initialize(
std::unique_ptr<xe::ui::GraphicsContext> context) {
context_ = std::move(context);
bool CommandProcessor::Initialize() {
// Initialize the gamma ramps to their default (linear) values - taken from
// what games set when starting.
for (uint32_t i = 0; i < 256; ++i) {
uint32_t value = i * 1023 / 255;
gamma_ramp_.normal[i].value = value | (value << 10) | (value << 20);
gamma_ramp_.table[i].value = value | (value << 10) | (value << 20);
}
for (uint32_t i = 0; i < 128; ++i) {
uint32_t value = (i * 65535 / 127) & ~63;
@ -64,7 +61,7 @@ bool CommandProcessor::Initialize(
gamma_ramp_.pwl[i].values[j].value = value;
}
}
dirty_gamma_ramp_normal_ = true;
dirty_gamma_ramp_table_ = true;
dirty_gamma_ramp_pwl_ = true;
worker_running_ = true;
@ -140,8 +137,18 @@ void CommandProcessor::CallInThread(std::function<void()> fn) {
void CommandProcessor::ClearCaches() {}
void CommandProcessor::SetDesiredSwapPostEffect(
SwapPostEffect swap_post_effect) {
if (swap_post_effect_desired_ == swap_post_effect) {
return;
}
swap_post_effect_desired_ = swap_post_effect;
CallInThread([this, swap_post_effect]() {
swap_post_effect_actual_ = swap_post_effect;
});
}
void CommandProcessor::WorkerThreadMain() {
context_->MakeCurrent();
if (!SetupContext()) {
xe::FatalError("Unable to setup command processor internal state");
return;
@ -212,9 +219,6 @@ void CommandProcessor::Pause() {
threading::Thread::GetCurrentThread()->Suspend();
});
// HACK - Prevents a hang in IssueSwap()
swap_state_.pending = false;
fence.Wait();
}
@ -255,7 +259,7 @@ bool CommandProcessor::Restore(ByteStream* stream) {
bool CommandProcessor::SetupContext() { return true; }
void CommandProcessor::ShutdownContext() { context_.reset(); }
void CommandProcessor::ShutdownContext() {}
void CommandProcessor::InitializeRingBuffer(uint32_t ptr, uint32_t size_log2) {
read_ptr_index_ = 0;
@ -326,14 +330,17 @@ void CommandProcessor::UpdateGammaRampValue(GammaRampType type,
if (mask_lo) {
switch (type) {
case GammaRampType::kNormal:
case GammaRampType::kTable:
assert_true(regs->values[XE_GPU_REG_DC_LUT_RW_MODE].u32 == 0);
gamma_ramp_.normal[index].value = value;
dirty_gamma_ramp_normal_ = true;
gamma_ramp_.table[index].value = value;
dirty_gamma_ramp_table_ = true;
break;
case GammaRampType::kPWL:
assert_true(regs->values[XE_GPU_REG_DC_LUT_RW_MODE].u32 == 1);
gamma_ramp_.pwl[index].values[gamma_ramp_rw_subindex_].value = value;
// The lower 6 bits are hardwired to 0.
// https://developer.amd.com/wordpress/media/2012/10/RRG-216M56-03oOEM.pdf
gamma_ramp_.pwl[index].values[gamma_ramp_rw_subindex_].value =
value & ~(uint32_t(63) | (uint32_t(63) << 16));
gamma_ramp_rw_subindex_ = (gamma_ramp_rw_subindex_ + 1) % 3;
dirty_gamma_ramp_pwl_ = true;
break;
@ -385,51 +392,6 @@ void CommandProcessor::PrepareForWait() { trace_writer_.Flush(); }
void CommandProcessor::ReturnFromWait() {}
void CommandProcessor::IssueSwap(uint32_t frontbuffer_ptr,
uint32_t frontbuffer_width,
uint32_t frontbuffer_height) {
SCOPE_profile_cpu_f("gpu");
if (!swap_request_handler_) {
return;
}
// If there was a swap pending we drop it on the floor.
// This prevents the display from pulling the backbuffer out from under us.
// If we skip a lot then we may need to buffer more, but as the display
// thread should be fairly idle that shouldn't happen.
if (!cvars::vsync) {
std::lock_guard<std::mutex> lock(swap_state_.mutex);
if (swap_state_.pending) {
swap_state_.pending = false;
// TODO(benvanik): frame skip counter.
XELOGW("Skipped frame!");
}
} else {
// Spin until no more pending swap.
while (worker_running_) {
{
std::lock_guard<std::mutex> lock(swap_state_.mutex);
if (!swap_state_.pending) {
break;
}
}
xe::threading::MaybeYield();
}
}
PerformSwap(frontbuffer_ptr, frontbuffer_width, frontbuffer_height);
{
// Set pending so that the display will swap the next time it can.
std::lock_guard<std::mutex> lock(swap_state_.mutex);
swap_state_.pending = true;
}
// Notify the display a swap is pending so that our changes are picked up.
// It does the actual front/back buffer swap.
swap_request_handler_();
}
uint32_t CommandProcessor::ExecutePrimaryBuffer(uint32_t read_index,
uint32_t write_index) {
SCOPE_profile_cpu_f("gpu");
@ -440,7 +402,7 @@ uint32_t CommandProcessor::ExecutePrimaryBuffer(uint32_t read_index,
uint32_t title_id = kernel_state_->GetExecutableModule()
? kernel_state_->GetExecutableModule()->title_id()
: 0;
auto file_name = fmt::format("{:8X}_stream.xtr", title_id);
auto file_name = fmt::format("{:08X}_stream.xtr", title_id);
auto path = trace_stream_path_ / file_name;
trace_writer_.Open(path, title_id);
InitializeTrace();
@ -767,7 +729,7 @@ bool CommandProcessor::ExecutePacketType3(RingBuffer* reader, uint32_t packet) {
} else if (trace_state_ == TraceState::kSingleFrame) {
// New trace request - we only start tracing at the beginning of a frame.
uint32_t title_id = kernel_state_->GetExecutableModule()->title_id();
auto file_name = fmt::format("{:8X}_{}.xtr", title_id, counter_ - 1);
auto file_name = fmt::format("{:08X}_{}.xtr", title_id, counter_ - 1);
auto path = trace_frame_path_ / file_name;
trace_writer_.Open(path, title_id);
InitializeTrace();
@ -837,7 +799,7 @@ bool CommandProcessor::ExecutePacketType3_XE_SWAP(RingBuffer* reader,
uint32_t frontbuffer_height = reader->ReadAndSwap<uint32_t>();
reader->AdvanceRead((count - 4) * sizeof(uint32_t));
if (swap_mode_ == SwapMode::kNormal) {
if (!ignore_swap_) {
IssueSwap(frontbuffer_ptr, frontbuffer_width, frontbuffer_height);
}

View File

@ -2,7 +2,7 @@
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2020 Ben Vanik. All rights reserved. *
* Copyright 2022 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
@ -26,7 +26,7 @@
#include "xenia/gpu/xenos.h"
#include "xenia/kernel/xthread.h"
#include "xenia/memory.h"
#include "xenia/ui/graphics_context.h"
#include "xenia/ui/presenter.h"
namespace xe {
@ -60,12 +60,20 @@ enum class SwapMode {
enum class GammaRampType {
kUnknown = 0,
kNormal,
kTable,
kPWL,
};
struct GammaRamp {
struct NormalEntry {
// A lot of gamma ramp (DC_LUT) documentation:
// https://developer.amd.com/wordpress/media/2012/10/RRG-216M56-03oOEM.pdf
// The ramps entries are BGR, not RGB.
// For the 256-entry table (used by Direct3D 9 for a 8bpc front buffer),
// 535107D4 has in-game settings allowing separate configuration.
// The component order of the PWL table is untested, however, it's likely BGR
// too, since DC_LUTA/B registers have values for blue first, and for red
// last.
struct TableEntry {
union {
uint32_t value;
struct {
@ -81,6 +89,15 @@ struct GammaRamp {
union {
uint32_t value;
struct {
// The lower 6 bits are always zero (these are 10-bit in the upper bits
// thus, not fully 16-bit).
// See DC_LUTA/B_CONTROL for information about the way they should be
// interpreted (`output = base + (multiplier * delta) / 2^increment`,
// where the increment is the value specified in DC_LUTA/B_CONTROL for
// the specific color channel, the base is 7 bits of the front buffer
// value above `increment` bits, the multiplier is the lower `increment`
// bits of it; the increment is nonzero, otherwise the 256-entry table
// should be used instead).
uint16_t base;
uint16_t delta;
};
@ -91,19 +108,25 @@ struct GammaRamp {
union {
PWLValue values[3];
struct {
PWLValue r;
PWLValue g;
PWLValue b;
PWLValue g;
PWLValue r;
};
};
};
NormalEntry normal[256];
TableEntry table[256];
PWLEntry pwl[128];
};
class CommandProcessor {
public:
enum class SwapPostEffect {
kNone,
kFxaa,
kFxaaExtreme,
};
CommandProcessor(GraphicsSystem* graphics_system,
kernel::KernelState* kernel_state);
virtual ~CommandProcessor();
@ -114,21 +137,26 @@ class CommandProcessor {
Shader* active_vertex_shader() const { return active_vertex_shader_; }
Shader* active_pixel_shader() const { return active_pixel_shader_; }
virtual bool Initialize(std::unique_ptr<xe::ui::GraphicsContext> context);
virtual bool Initialize();
virtual void Shutdown();
void CallInThread(std::function<void()> fn);
virtual void ClearCaches();
SwapState& swap_state() { return swap_state_; }
void set_swap_mode(SwapMode swap_mode) { swap_mode_ = swap_mode; }
void IssueSwap(uint32_t frontbuffer_ptr, uint32_t frontbuffer_width,
uint32_t frontbuffer_height);
void set_swap_request_handler(std::function<void()> fn) {
swap_request_handler_ = fn;
void SetIgnoreSwap(bool ignore_swap) { ignore_swap_ = ignore_swap; }
// "Desired" is for the external thread managing the post-processing effect.
SwapPostEffect GetDesiredSwapPostEffect() const {
return swap_post_effect_desired_;
}
void SetDesiredSwapPostEffect(SwapPostEffect swap_post_effect);
// Implementations must not make assumptions that the front buffer will
// necessarily be a resolve destination - it may be a texture generated by any
// means like written to by the CPU or loaded from a file (the disclaimer
// screen right in the beginning of 4D530AA4 is not a resolved render target,
// for instance).
virtual void IssueSwap(uint32_t frontbuffer_ptr, uint32_t frontbuffer_width,
uint32_t frontbuffer_height) = 0;
// May be called not only from the command processor thread when the command
// processor is paused, and the termination of this function may be explicitly
@ -179,9 +207,6 @@ class CommandProcessor {
virtual void PrepareForWait();
virtual void ReturnFromWait();
virtual void PerformSwap(uint32_t frontbuffer_ptr, uint32_t frontbuffer_width,
uint32_t frontbuffer_height) = 0;
uint32_t ExecutePrimaryBuffer(uint32_t start_index, uint32_t end_index);
virtual void OnPrimaryBufferEnd() {}
void ExecuteIndirectBuffer(uint32_t ptr, uint32_t length);
@ -254,6 +279,14 @@ class CommandProcessor {
bool major_mode_explicit) = 0;
virtual bool IssueCopy() = 0;
// "Actual" is for the command processor thread, to be read by the
// implementations.
SwapPostEffect GetActualSwapPostEffect() const {
return swap_post_effect_actual_;
}
// TODO(Triang3l): Write the gamma ramp (including the display controller
// write pointers) in the common code.
virtual void InitializeTrace() = 0;
Memory* memory_ = nullptr;
@ -274,10 +307,8 @@ class CommandProcessor {
std::atomic<bool> worker_running_;
kernel::object_ref<kernel::XHostThread> worker_thread_;
std::unique_ptr<xe::ui::GraphicsContext> context_;
SwapMode swap_mode_ = SwapMode::kNormal;
SwapState swap_state_;
std::function<void()> swap_request_handler_;
bool ignore_swap_ = false;
std::queue<std::function<void()>> pending_fns_;
// MicroEngine binary from PM4_ME_INIT
@ -305,8 +336,13 @@ class CommandProcessor {
GammaRamp gamma_ramp_ = {};
int gamma_ramp_rw_subindex_ = 0;
bool dirty_gamma_ramp_normal_ = true;
bool dirty_gamma_ramp_table_ = true;
bool dirty_gamma_ramp_pwl_ = true;
// By default (such as for tools), post-processing is disabled.
// "Desired" is for the external thread managing the post-processing effect.
SwapPostEffect swap_post_effect_desired_ = SwapPostEffect::kNone;
SwapPostEffect swap_post_effect_actual_ = SwapPostEffect::kNone;
};
} // namespace gpu

File diff suppressed because it is too large Load Diff

View File

@ -2,7 +2,7 @@
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2020 Ben Vanik. All rights reserved. *
* Copyright 2022 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
@ -32,8 +32,8 @@
#include "xenia/gpu/dxbc_shader_translator.h"
#include "xenia/gpu/xenos.h"
#include "xenia/kernel/kernel_state.h"
#include "xenia/ui/d3d12/d3d12_context.h"
#include "xenia/ui/d3d12/d3d12_descriptor_heap_pool.h"
#include "xenia/ui/d3d12/d3d12_provider.h"
#include "xenia/ui/d3d12/d3d12_upload_buffer_pool.h"
#include "xenia/ui/d3d12/d3d12_util.h"
@ -58,8 +58,9 @@ class D3D12CommandProcessor : public CommandProcessor {
void RestoreEdramSnapshot(const void* snapshot) override;
ui::d3d12::D3D12Context& GetD3D12Context() const {
return static_cast<ui::d3d12::D3D12Context&>(*context_);
ui::d3d12::D3D12Provider& GetD3D12Provider() const {
return *static_cast<ui::d3d12::D3D12Provider*>(
graphics_system_->provider());
}
// Returns the deferred drawing command list for the currently open
@ -153,7 +154,7 @@ class D3D12CommandProcessor : public CommandProcessor {
kEdramR32G32UintUAV,
kEdramR32G32B32A32UintUAV,
kGammaRampNormalSRV,
kGammaRampTableSRV,
kGammaRampPWLSRV,
// Beyond this point, SRVs are accessible to shaders through an unbounded
@ -210,16 +211,14 @@ class D3D12CommandProcessor : public CommandProcessor {
// Returns the text to display in the GPU backend name in the window title.
std::string GetWindowTitleText() const;
std::unique_ptr<xe::ui::RawImage> Capture();
protected:
bool SetupContext() override;
void ShutdownContext() override;
void WriteRegister(uint32_t index, uint32_t value) override;
void PerformSwap(uint32_t frontbuffer_ptr, uint32_t frontbuffer_width,
uint32_t frontbuffer_height) override;
void IssueSwap(uint32_t frontbuffer_ptr, uint32_t frontbuffer_width,
uint32_t frontbuffer_height) override;
void OnPrimaryBufferEnd() override;
@ -321,8 +320,9 @@ class D3D12CommandProcessor : public CommandProcessor {
void CheckSubmissionFence(uint64_t await_submission);
// If is_guest_command is true, a new full frame - with full cleanup of
// resources and, if needed, starting capturing - is opened if pending (as
// opposed to simply resuming after mid-frame synchronization).
void BeginSubmission(bool is_guest_command);
// opposed to simply resuming after mid-frame synchronization). Returns
// whether a submission is open currently and the device is not removed.
bool BeginSubmission(bool is_guest_command);
// If is_swap is true, a full frame is closed - with, if needed, cache
// clearing and stopping capturing. Returns whether the submission was done
// successfully, if it has failed, leaves it open.
@ -380,6 +380,8 @@ class D3D12CommandProcessor : public CommandProcessor {
void WriteGammaRampSRV(bool is_pwl, D3D12_CPU_DESCRIPTOR_HANDLE handle) const;
bool device_removed_ = false;
bool cache_clear_requested_ = false;
HANDLE fence_completion_event_ = nullptr;
@ -497,42 +499,73 @@ class D3D12CommandProcessor : public CommandProcessor {
std::unique_ptr<TextureCache> texture_cache_;
// Mip 0 contains the normal gamma ramp (256 entries), mip 1 contains the PWL
// ramp (128 entries). DXGI_FORMAT_R10G10B10A2_UNORM 1D.
ID3D12Resource* gamma_ramp_texture_ = nullptr;
D3D12_RESOURCE_STATES gamma_ramp_texture_state_;
// Bytes 0x0...0x3FF - 256-entry R10G10B10X2 gamma ramp (red and blue must be
// read as swapped - 535107D4 has settings allowing separate configuration).
// Bytes 0x400...0x9FF - 128-entry PWL R16G16 gamma ramp (R - base, G - delta,
// low 6 bits of each are zero, 3 elements per entry).
// https://www.x.org/docs/AMD/old/42590_m76_rrg_1.01o.pdf
Microsoft::WRL::ComPtr<ID3D12Resource> gamma_ramp_buffer_;
D3D12_RESOURCE_STATES gamma_ramp_buffer_state_;
// Upload buffer for an image that is the same as gamma_ramp_, but with
// kQueueFrames array layers.
ID3D12Resource* gamma_ramp_upload_ = nullptr;
uint8_t* gamma_ramp_upload_mapping_ = nullptr;
D3D12_PLACED_SUBRESOURCE_FOOTPRINT gamma_ramp_footprints_[kQueueFrames * 2];
Microsoft::WRL::ComPtr<ID3D12Resource> gamma_ramp_upload_buffer_;
uint8_t* gamma_ramp_upload_buffer_mapping_ = nullptr;
static constexpr uint32_t kSwapTextureWidth = 1280;
static constexpr uint32_t kSwapTextureHeight = 720;
std::pair<uint32_t, uint32_t> GetSwapTextureSize() const {
return std::make_pair(
kSwapTextureWidth * texture_cache_->GetDrawResolutionScaleX(),
kSwapTextureHeight * texture_cache_->GetDrawResolutionScaleY());
}
std::pair<uint32_t, uint32_t> GetSwapScreenSize() const {
uint32_t resolution_scale =
std::max(texture_cache_->GetDrawResolutionScaleX(),
texture_cache_->GetDrawResolutionScaleY());
return std::make_pair(kSwapTextureWidth * resolution_scale,
kSwapTextureHeight * resolution_scale);
}
ID3D12Resource* swap_texture_ = nullptr;
D3D12_PLACED_SUBRESOURCE_FOOTPRINT swap_texture_copy_footprint_;
UINT64 swap_texture_copy_size_;
ID3D12DescriptorHeap* swap_texture_rtv_descriptor_heap_ = nullptr;
D3D12_CPU_DESCRIPTOR_HANDLE swap_texture_rtv_;
ID3D12DescriptorHeap* swap_texture_srv_descriptor_heap_ = nullptr;
struct ApplyGammaConstants {
uint32_t size[2];
};
enum class ApplyGammaRootParameter : UINT {
kConstants,
kDestination,
kSource,
kRamp,
kCount,
};
Microsoft::WRL::ComPtr<ID3D12RootSignature> apply_gamma_root_signature_;
Microsoft::WRL::ComPtr<ID3D12PipelineState> apply_gamma_table_pipeline_;
Microsoft::WRL::ComPtr<ID3D12PipelineState>
apply_gamma_table_fxaa_luma_pipeline_;
Microsoft::WRL::ComPtr<ID3D12PipelineState> apply_gamma_pwl_pipeline_;
Microsoft::WRL::ComPtr<ID3D12PipelineState>
apply_gamma_pwl_fxaa_luma_pipeline_;
struct FxaaConstants {
uint32_t size[2];
float size_inv[2];
};
enum class FxaaRootParameter : UINT {
kConstants,
kDestination,
kSource,
kCount,
};
Microsoft::WRL::ComPtr<ID3D12RootSignature> fxaa_root_signature_;
Microsoft::WRL::ComPtr<ID3D12PipelineState> fxaa_pipeline_;
Microsoft::WRL::ComPtr<ID3D12PipelineState> fxaa_extreme_pipeline_;
// PWL gamma ramp can result in values with more precision than 10bpc. Though
// those sub-10bpc bits don't have any noticeable visual effect, so normally
// R10G10B10A2_UNORM is enough. But what's the most important is that for the
// original FXAA shader, the luma needs to be written to the alpha channel.
// For simplicity (to avoid modifying the FXAA shader and adding more texture
// fetches into it), and for the highest quality (preserving all 13 bits that
// may be generated by applying the PWL gamma ramp with an increment of 2^3,
// and also leaving some space for the result of applying fractional weights
// to calculate the luma), using R16G16B16A16_UNORM instead of
// R10G10B10X2_UNORM with a separate alpha texture.
static constexpr DXGI_FORMAT kFxaaSourceTextureFormat =
DXGI_FORMAT_R16G16B16A16_UNORM;
// Kept in NON_PIXEL_SHADER_RESOURCE state.
Microsoft::WRL::ComPtr<ID3D12Resource> fxaa_source_texture_;
uint64_t fxaa_source_texture_submission_ = 0;
// Unsubmitted barrier batch.
std::vector<D3D12_RESOURCE_BARRIER> barriers_;
// <Resource, submission where requested>, sorted by the submission number.
std::deque<std::pair<ID3D12Resource*, uint64_t>> buffers_for_deletion_;
std::deque<std::pair<uint64_t, ID3D12Resource*>> resources_for_deletion_;
static constexpr uint32_t kScratchBufferSizeIncrement = 16 * 1024 * 1024;
ID3D12Resource* scratch_buffer_ = nullptr;

View File

@ -22,13 +22,6 @@ namespace xe {
namespace gpu {
namespace d3d12 {
// Generated with `xb buildshaders`.
namespace shaders {
#include "xenia/gpu/shaders/bytecode/d3d12_5_1/fullscreen_tc_vs.h"
#include "xenia/gpu/shaders/bytecode/d3d12_5_1/stretch_gamma_ps.h"
#include "xenia/gpu/shaders/bytecode/d3d12_5_1/stretch_ps.h"
} // namespace shaders
D3D12GraphicsSystem::D3D12GraphicsSystem() {}
D3D12GraphicsSystem::~D3D12GraphicsSystem() {}
@ -48,198 +41,11 @@ std::string D3D12GraphicsSystem::name() const {
X_STATUS D3D12GraphicsSystem::Setup(cpu::Processor* processor,
kernel::KernelState* kernel_state,
ui::Window* target_window) {
ui::WindowedAppContext* app_context,
bool is_surface_required) {
provider_ = xe::ui::d3d12::D3D12Provider::Create();
auto d3d12_provider = static_cast<xe::ui::d3d12::D3D12Provider*>(provider());
auto device = d3d12_provider->GetDevice();
auto result = GraphicsSystem::Setup(processor, kernel_state, target_window);
if (result != X_STATUS_SUCCESS) {
return result;
}
if (target_window) {
display_context_ = reinterpret_cast<xe::ui::d3d12::D3D12Context*>(
target_window->context());
}
// Create the stretch pipeline root signature, with 1 parameter (source
// texture) for raw stretch and 3 parameters (source texture, gamma ramp LUT,
// inverse of the size of the gamma ramp LUT) for gamma-correcting stretch.
// Raw.
D3D12_ROOT_PARAMETER stretch_root_parameters[3];
stretch_root_parameters[0].ParameterType =
D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE;
stretch_root_parameters[0].DescriptorTable.NumDescriptorRanges = 1;
D3D12_DESCRIPTOR_RANGE stretch_root_texture_range;
stretch_root_texture_range.RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SRV;
stretch_root_texture_range.NumDescriptors = 1;
stretch_root_texture_range.BaseShaderRegister = 0;
stretch_root_texture_range.RegisterSpace = 0;
stretch_root_texture_range.OffsetInDescriptorsFromTableStart = 0;
stretch_root_parameters[0].DescriptorTable.pDescriptorRanges =
&stretch_root_texture_range;
stretch_root_parameters[0].ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL;
D3D12_STATIC_SAMPLER_DESC stretch_sampler_desc;
stretch_sampler_desc.Filter = D3D12_FILTER_MIN_MAG_MIP_LINEAR;
stretch_sampler_desc.AddressU = D3D12_TEXTURE_ADDRESS_MODE_CLAMP;
stretch_sampler_desc.AddressV = D3D12_TEXTURE_ADDRESS_MODE_CLAMP;
stretch_sampler_desc.AddressW = D3D12_TEXTURE_ADDRESS_MODE_CLAMP;
stretch_sampler_desc.MipLODBias = 0.0f;
stretch_sampler_desc.MaxAnisotropy = 1;
stretch_sampler_desc.ComparisonFunc = D3D12_COMPARISON_FUNC_NEVER;
stretch_sampler_desc.BorderColor = D3D12_STATIC_BORDER_COLOR_OPAQUE_BLACK;
stretch_sampler_desc.MinLOD = 0.0f;
stretch_sampler_desc.MaxLOD = 0.0f;
stretch_sampler_desc.ShaderRegister = 0;
stretch_sampler_desc.RegisterSpace = 0;
stretch_sampler_desc.ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL;
D3D12_ROOT_SIGNATURE_DESC stretch_root_desc;
stretch_root_desc.NumParameters = 1;
stretch_root_desc.pParameters = stretch_root_parameters;
stretch_root_desc.NumStaticSamplers = 1;
stretch_root_desc.pStaticSamplers = &stretch_sampler_desc;
stretch_root_desc.Flags =
D3D12_ROOT_SIGNATURE_FLAG_DENY_VERTEX_SHADER_ROOT_ACCESS;
stretch_root_signature_ =
ui::d3d12::util::CreateRootSignature(*d3d12_provider, stretch_root_desc);
if (stretch_root_signature_ == nullptr) {
XELOGE("Failed to create the front buffer stretch root signature");
return X_STATUS_UNSUCCESSFUL;
}
// Gamma.
stretch_root_parameters[1].ParameterType =
D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE;
stretch_root_parameters[1].DescriptorTable.NumDescriptorRanges = 1;
D3D12_DESCRIPTOR_RANGE stretch_root_gamma_ramp_range;
stretch_root_gamma_ramp_range.RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SRV;
stretch_root_gamma_ramp_range.NumDescriptors = 1;
stretch_root_gamma_ramp_range.BaseShaderRegister = 1;
stretch_root_gamma_ramp_range.RegisterSpace = 0;
stretch_root_gamma_ramp_range.OffsetInDescriptorsFromTableStart = 0;
stretch_root_parameters[1].DescriptorTable.pDescriptorRanges =
&stretch_root_gamma_ramp_range;
stretch_root_parameters[1].ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL;
stretch_root_parameters[2].ParameterType =
D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS;
stretch_root_parameters[2].Constants.ShaderRegister = 0;
stretch_root_parameters[2].Constants.RegisterSpace = 0;
stretch_root_parameters[2].Constants.Num32BitValues = 1;
stretch_root_parameters[2].ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL;
stretch_root_desc.NumParameters = 3;
stretch_root_desc.pParameters = stretch_root_parameters;
stretch_gamma_root_signature_ =
ui::d3d12::util::CreateRootSignature(*d3d12_provider, stretch_root_desc);
if (stretch_gamma_root_signature_ == nullptr) {
XELOGE(
"Failed to create the gamma-correcting front buffer stretch root "
"signature");
stretch_root_signature_->Release();
stretch_root_signature_ = nullptr;
return X_STATUS_UNSUCCESSFUL;
}
// Create the stretch pipelines.
D3D12_GRAPHICS_PIPELINE_STATE_DESC stretch_pipeline_desc = {};
stretch_pipeline_desc.pRootSignature = stretch_root_signature_;
stretch_pipeline_desc.VS.pShaderBytecode = shaders::fullscreen_tc_vs;
stretch_pipeline_desc.VS.BytecodeLength = sizeof(shaders::fullscreen_tc_vs);
stretch_pipeline_desc.PS.pShaderBytecode = shaders::stretch_ps;
stretch_pipeline_desc.PS.BytecodeLength = sizeof(shaders::stretch_ps);
// The shader will set alpha to 1, don't use output-merger to preserve it.
stretch_pipeline_desc.BlendState.RenderTarget[0].RenderTargetWriteMask =
D3D12_COLOR_WRITE_ENABLE_ALL;
stretch_pipeline_desc.SampleMask = UINT_MAX;
stretch_pipeline_desc.RasterizerState.FillMode = D3D12_FILL_MODE_SOLID;
stretch_pipeline_desc.RasterizerState.CullMode = D3D12_CULL_MODE_NONE;
stretch_pipeline_desc.RasterizerState.DepthClipEnable = TRUE;
stretch_pipeline_desc.PrimitiveTopologyType =
D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
stretch_pipeline_desc.NumRenderTargets = 1;
stretch_pipeline_desc.RTVFormats[0] =
ui::d3d12::D3D12Context::kSwapChainFormat;
stretch_pipeline_desc.SampleDesc.Count = 1;
if (FAILED(device->CreateGraphicsPipelineState(
&stretch_pipeline_desc, IID_PPV_ARGS(&stretch_pipeline_)))) {
XELOGE("Failed to create the front buffer stretch pipeline");
stretch_gamma_root_signature_->Release();
stretch_gamma_root_signature_ = nullptr;
stretch_root_signature_->Release();
stretch_root_signature_ = nullptr;
return X_STATUS_UNSUCCESSFUL;
}
stretch_pipeline_desc.pRootSignature = stretch_gamma_root_signature_;
stretch_pipeline_desc.PS.pShaderBytecode = shaders::stretch_gamma_ps;
stretch_pipeline_desc.PS.BytecodeLength = sizeof(shaders::stretch_gamma_ps);
if (FAILED(device->CreateGraphicsPipelineState(
&stretch_pipeline_desc, IID_PPV_ARGS(&stretch_gamma_pipeline_)))) {
XELOGE(
"Failed to create the gamma-correcting front buffer stretch pipeline");
stretch_pipeline_->Release();
stretch_pipeline_ = nullptr;
stretch_gamma_root_signature_->Release();
stretch_gamma_root_signature_ = nullptr;
stretch_root_signature_->Release();
stretch_root_signature_ = nullptr;
return X_STATUS_UNSUCCESSFUL;
}
return X_STATUS_SUCCESS;
}
void D3D12GraphicsSystem::Shutdown() {
ui::d3d12::util::ReleaseAndNull(stretch_gamma_pipeline_);
ui::d3d12::util::ReleaseAndNull(stretch_pipeline_);
ui::d3d12::util::ReleaseAndNull(stretch_gamma_root_signature_);
ui::d3d12::util::ReleaseAndNull(stretch_root_signature_);
GraphicsSystem::Shutdown();
}
std::unique_ptr<xe::ui::RawImage> D3D12GraphicsSystem::Capture() {
auto d3d12_command_processor =
static_cast<D3D12CommandProcessor*>(command_processor());
if (!d3d12_command_processor) {
return nullptr;
}
return d3d12_command_processor->Capture();
}
void D3D12GraphicsSystem::StretchTextureToFrontBuffer(
D3D12_GPU_DESCRIPTOR_HANDLE handle,
D3D12_GPU_DESCRIPTOR_HANDLE* gamma_ramp_handle, float gamma_ramp_inv_size,
ID3D12GraphicsCommandList* command_list) {
if (gamma_ramp_handle != nullptr) {
command_list->SetPipelineState(stretch_gamma_pipeline_);
command_list->SetGraphicsRootSignature(stretch_gamma_root_signature_);
command_list->SetGraphicsRootDescriptorTable(1, *gamma_ramp_handle);
command_list->SetGraphicsRoot32BitConstants(2, 1, &gamma_ramp_inv_size, 0);
} else {
command_list->SetPipelineState(stretch_pipeline_);
command_list->SetGraphicsRootSignature(stretch_root_signature_);
}
command_list->SetGraphicsRootDescriptorTable(0, handle);
command_list->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
command_list->DrawInstanced(3, 1, 0, 0);
}
void D3D12GraphicsSystem::StretchTextureToFrontBuffer(
D3D12_GPU_DESCRIPTOR_HANDLE handle,
D3D12_GPU_DESCRIPTOR_HANDLE* gamma_ramp_handle, float gamma_ramp_inv_size,
DeferredCommandList& command_list) {
if (gamma_ramp_handle != nullptr) {
command_list.D3DSetPipelineState(stretch_gamma_pipeline_);
command_list.D3DSetGraphicsRootSignature(stretch_gamma_root_signature_);
command_list.D3DSetGraphicsRootDescriptorTable(1, *gamma_ramp_handle);
command_list.D3DSetGraphicsRoot32BitConstants(2, 1, &gamma_ramp_inv_size,
0);
} else {
command_list.D3DSetPipelineState(stretch_pipeline_);
command_list.D3DSetGraphicsRootSignature(stretch_root_signature_);
}
command_list.D3DSetGraphicsRootDescriptorTable(0, handle);
command_list.D3DIASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
command_list.D3DDrawInstanced(3, 1, 0, 0);
return GraphicsSystem::Setup(processor, kernel_state, app_context,
is_surface_required);
}
std::unique_ptr<CommandProcessor>
@ -248,69 +54,6 @@ D3D12GraphicsSystem::CreateCommandProcessor() {
new D3D12CommandProcessor(this, kernel_state_));
}
void D3D12GraphicsSystem::Swap(xe::ui::UIEvent* e) {
if (display_context_->WasLost()) {
// We're crashing. Cheese it.
return;
}
if (!command_processor_) {
return;
}
auto& swap_state = command_processor_->swap_state();
ID3D12DescriptorHeap* swap_srv_heap;
{
std::lock_guard<std::mutex> lock(swap_state.mutex);
swap_state.pending = false;
swap_srv_heap = reinterpret_cast<ID3D12DescriptorHeap*>(
swap_state.front_buffer_texture);
}
if (swap_srv_heap == nullptr) {
// Not ready yet.
return;
}
uint32_t window_width, window_height;
display_context_->GetSwapChainSize(window_width, window_height);
int32_t target_x, target_y;
uint32_t target_width, target_height;
draw_util::GetPresentArea(swap_state.width, swap_state.height, window_width,
window_height, target_x, target_y, target_width,
target_height);
// For safety.
target_x = clamp(target_x, int32_t(D3D12_VIEWPORT_BOUNDS_MIN),
int32_t(D3D12_VIEWPORT_BOUNDS_MAX));
target_y = clamp(target_y, int32_t(D3D12_VIEWPORT_BOUNDS_MIN),
int32_t(D3D12_VIEWPORT_BOUNDS_MAX));
target_width = std::min(
target_width, uint32_t(int32_t(D3D12_VIEWPORT_BOUNDS_MAX) - target_x));
target_height = std::min(
target_height, uint32_t(int32_t(D3D12_VIEWPORT_BOUNDS_MAX) - target_y));
auto command_list = display_context_->GetSwapCommandList();
// Assuming the window has already been cleared to the needed letterbox color.
D3D12_VIEWPORT viewport;
viewport.TopLeftX = float(target_x);
viewport.TopLeftY = float(target_y);
viewport.Width = float(target_width);
viewport.Height = float(target_height);
viewport.MinDepth = 0.0f;
viewport.MaxDepth = 0.0f;
command_list->RSSetViewports(1, &viewport);
D3D12_RECT scissor;
scissor.left = 0;
scissor.top = 0;
scissor.right = window_width;
scissor.bottom = window_height;
command_list->RSSetScissorRects(1, &scissor);
command_list->SetDescriptorHeaps(1, &swap_srv_heap);
StretchTextureToFrontBuffer(
swap_srv_heap->GetGPUDescriptorHandleForHeapStart(), nullptr, 0.0f,
command_list);
}
} // namespace d3d12
} // namespace gpu
} // namespace xe

View File

@ -15,7 +15,6 @@
#include "xenia/gpu/command_processor.h"
#include "xenia/gpu/d3d12/deferred_command_list.h"
#include "xenia/gpu/graphics_system.h"
#include "xenia/ui/d3d12/d3d12_context.h"
namespace xe {
namespace gpu {
@ -31,37 +30,11 @@ class D3D12GraphicsSystem : public GraphicsSystem {
std::string name() const override;
X_STATUS Setup(cpu::Processor* processor, kernel::KernelState* kernel_state,
ui::Window* target_window) override;
void Shutdown() override;
std::unique_ptr<xe::ui::RawImage> Capture() override;
// Draws a texture covering the entire viewport to the render target currently
// bound on the specified command list (in D3D12Context::kSwapChainFormat).
// This changes the current pipeline, graphics root signature and primitive
// topology. The gamma ramp texture must be 1D if present at all, for linear
// space, pass nullptr as the gamma ramp.
void StretchTextureToFrontBuffer(
D3D12_GPU_DESCRIPTOR_HANDLE handle,
D3D12_GPU_DESCRIPTOR_HANDLE* gamma_ramp_handle, float gamma_ramp_inv_size,
ID3D12GraphicsCommandList* command_list);
void StretchTextureToFrontBuffer(
D3D12_GPU_DESCRIPTOR_HANDLE handle,
D3D12_GPU_DESCRIPTOR_HANDLE* gamma_ramp_handle, float gamma_ramp_inv_size,
DeferredCommandList& command_list);
ui::WindowedAppContext* app_context,
bool is_surface_required) override;
protected:
std::unique_ptr<CommandProcessor> CreateCommandProcessor() override;
void Swap(xe::ui::UIEvent* e) override;
private:
ui::d3d12::D3D12Context* display_context_ = nullptr;
ID3D12RootSignature* stretch_root_signature_ = nullptr;
ID3D12RootSignature* stretch_gamma_root_signature_ = nullptr;
ID3D12PipelineState* stretch_pipeline_ = nullptr;
ID3D12PipelineState* stretch_gamma_pipeline_ = nullptr;
};
} // namespace d3d12

View File

@ -33,7 +33,7 @@ bool D3D12PrimitiveProcessor::Initialize() {
return false;
}
frame_index_buffer_pool_ = std::make_unique<ui::d3d12::D3D12UploadBufferPool>(
command_processor_.GetD3D12Context().GetD3D12Provider(),
command_processor_.GetD3D12Provider(),
std::max(size_t(kMinRequiredConvertedIndexBufferSize),
ui::GraphicsUploadBufferPool::kDefaultPageSize));
return true;
@ -90,7 +90,7 @@ bool D3D12PrimitiveProcessor::InitializeBuiltin16BitIndexBuffer(
assert_null(builtin_index_buffer_upload_);
const ui::d3d12::D3D12Provider& provider =
command_processor_.GetD3D12Context().GetD3D12Provider();
command_processor_.GetD3D12Provider();
ID3D12Device* device = provider.GetDevice();
D3D12_RESOURCE_DESC resource_desc;

View File

@ -215,7 +215,7 @@ D3D12RenderTargetCache::~D3D12RenderTargetCache() { Shutdown(true); }
bool D3D12RenderTargetCache::Initialize() {
const ui::d3d12::D3D12Provider& provider =
command_processor_.GetD3D12Context().GetD3D12Provider();
command_processor_.GetD3D12Provider();
ID3D12Device* device = provider.GetDevice();
if (cvars::render_target_path_d3d12 == "rtv") {
@ -1298,7 +1298,7 @@ bool D3D12RenderTargetCache::Update(bool is_rasterization_done,
void D3D12RenderTargetCache::WriteEdramRawSRVDescriptor(
D3D12_CPU_DESCRIPTOR_HANDLE handle) {
const ui::d3d12::D3D12Provider& provider =
command_processor_.GetD3D12Context().GetD3D12Provider();
command_processor_.GetD3D12Provider();
ID3D12Device* device = provider.GetDevice();
device->CopyDescriptorsSimple(
1, handle,
@ -1311,7 +1311,7 @@ void D3D12RenderTargetCache::WriteEdramRawSRVDescriptor(
void D3D12RenderTargetCache::WriteEdramRawUAVDescriptor(
D3D12_CPU_DESCRIPTOR_HANDLE handle) {
const ui::d3d12::D3D12Provider& provider =
command_processor_.GetD3D12Context().GetD3D12Provider();
command_processor_.GetD3D12Provider();
ID3D12Device* device = provider.GetDevice();
device->CopyDescriptorsSimple(
1, handle,
@ -1339,7 +1339,7 @@ void D3D12RenderTargetCache::WriteEdramUintPow2SRVDescriptor(
return;
}
const ui::d3d12::D3D12Provider& provider =
command_processor_.GetD3D12Context().GetD3D12Provider();
command_processor_.GetD3D12Provider();
ID3D12Device* device = provider.GetDevice();
device->CopyDescriptorsSimple(
1, handle,
@ -1366,7 +1366,7 @@ void D3D12RenderTargetCache::WriteEdramUintPow2UAVDescriptor(
return;
}
const ui::d3d12::D3D12Provider& provider =
command_processor_.GetD3D12Context().GetD3D12Provider();
command_processor_.GetD3D12Provider();
ID3D12Device* device = provider.GetDevice();
device->CopyDescriptorsSimple(
1, handle,
@ -1668,8 +1668,9 @@ bool D3D12RenderTargetCache::InitializeTraceSubmitDownloads() {
ui::d3d12::util::FillBufferResourceDesc(edram_snapshot_download_buffer_desc,
xenos::kEdramSizeBytes,
D3D12_RESOURCE_FLAG_NONE);
auto& provider = command_processor_.GetD3D12Context().GetD3D12Provider();
auto device = provider.GetDevice();
const ui::d3d12::D3D12Provider& provider =
command_processor_.GetD3D12Provider();
ID3D12Device* device = provider.GetDevice();
if (FAILED(device->CreateCommittedResource(
&ui::d3d12::util::kHeapPropertiesReadback,
provider.GetHeapFlagCreateNotZeroed(),
@ -1721,7 +1722,8 @@ void D3D12RenderTargetCache::RestoreEdramSnapshot(const void* snapshot) {
// Create the buffer - will be used for copying to either a 32-bit 1280x2048
// render target or the EDRAM buffer.
auto& provider = command_processor_.GetD3D12Context().GetD3D12Provider();
const ui::d3d12::D3D12Provider& provider =
command_processor_.GetD3D12Provider();
if (!edram_snapshot_restore_pool_) {
edram_snapshot_restore_pool_ =
std::make_unique<ui::d3d12::D3D12UploadBufferPool>(
@ -1966,8 +1968,7 @@ DXGI_FORMAT D3D12RenderTargetCache::GetDepthSRVStencilDXGIFormat(
RenderTargetCache::RenderTarget* D3D12RenderTargetCache::CreateRenderTarget(
RenderTargetKey key) {
ID3D12Device* device =
command_processor_.GetD3D12Context().GetD3D12Provider().GetDevice();
ID3D12Device* device = command_processor_.GetD3D12Provider().GetDevice();
D3D12_RESOURCE_DESC resource_desc;
resource_desc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
@ -4345,8 +4346,7 @@ D3D12RenderTargetCache::GetOrCreateTransferPipelines(TransferShaderKey key) {
// ***************************************************************************
ID3D12PipelineState* const* pipelines;
ID3D12Device* device =
command_processor_.GetD3D12Context().GetD3D12Provider().GetDevice();
ID3D12Device* device = command_processor_.GetD3D12Provider().GetDevice();
D3D12_INPUT_ELEMENT_DESC pipeline_input_element_desc;
pipeline_input_element_desc.SemanticName = "POSITION";
pipeline_input_element_desc.SemanticIndex = 0;
@ -4516,7 +4516,7 @@ void D3D12RenderTargetCache::PerformTransfersAndResolveClears(
assert_true(GetPath() == Path::kHostRenderTargets);
const ui::d3d12::D3D12Provider& provider =
command_processor_.GetD3D12Context().GetD3D12Provider();
command_processor_.GetD3D12Provider();
ID3D12Device* device = provider.GetDevice();
uint64_t current_submission = command_processor_.GetCurrentSubmission();
DeferredCommandList& command_list =
@ -6476,8 +6476,8 @@ ID3D12PipelineState* D3D12RenderTargetCache::GetOrCreateDumpPipeline(
// Pipeline
// ***************************************************************************
ID3D12PipelineState* pipeline = ui::d3d12::util::CreateComputePipeline(
command_processor_.GetD3D12Context().GetD3D12Provider().GetDevice(),
built_shader_.data(), built_shader_size_bytes,
command_processor_.GetD3D12Provider().GetDevice(), built_shader_.data(),
built_shader_size_bytes,
key.is_depth ? dump_root_signature_depth_ : dump_root_signature_color_);
const char* format_name =
key.is_depth
@ -6561,7 +6561,7 @@ void D3D12RenderTargetCache::DumpRenderTargets(uint32_t dump_base,
// 32bpp and 64bpp.
size_t edram_uav_indices[2] = {SIZE_MAX, SIZE_MAX};
const ui::d3d12::D3D12Provider& provider =
command_processor_.GetD3D12Context().GetD3D12Provider();
command_processor_.GetD3D12Provider();
if (!bindless_resources_used_) {
if (any_sources_32bpp_64bpp[0]) {
edram_uav_indices[0] = current_temporary_descriptors_cpu_.size();

View File

@ -43,7 +43,7 @@ bool D3D12SharedMemory::Initialize() {
InitializeCommon();
const ui::d3d12::D3D12Provider& provider =
command_processor_.GetD3D12Context().GetD3D12Provider();
command_processor_.GetD3D12Provider();
ID3D12Device* device = provider.GetDevice();
D3D12_RESOURCE_DESC buffer_desc;
@ -215,8 +215,9 @@ void D3D12SharedMemory::CommitUAVWritesAndTransitionBuffer(
void D3D12SharedMemory::WriteRawSRVDescriptor(
D3D12_CPU_DESCRIPTOR_HANDLE handle) {
auto& provider = command_processor_.GetD3D12Context().GetD3D12Provider();
auto device = provider.GetDevice();
const ui::d3d12::D3D12Provider& provider =
command_processor_.GetD3D12Provider();
ID3D12Device* device = provider.GetDevice();
device->CopyDescriptorsSimple(
1, handle,
provider.OffsetViewDescriptor(buffer_descriptor_heap_start_,
@ -226,8 +227,9 @@ void D3D12SharedMemory::WriteRawSRVDescriptor(
void D3D12SharedMemory::WriteRawUAVDescriptor(
D3D12_CPU_DESCRIPTOR_HANDLE handle) {
auto& provider = command_processor_.GetD3D12Context().GetD3D12Provider();
auto device = provider.GetDevice();
const ui::d3d12::D3D12Provider& provider =
command_processor_.GetD3D12Provider();
ID3D12Device* device = provider.GetDevice();
device->CopyDescriptorsSimple(
1, handle,
provider.OffsetViewDescriptor(buffer_descriptor_heap_start_,
@ -252,8 +254,9 @@ void D3D12SharedMemory::WriteUintPow2SRVDescriptor(
assert_unhandled_case(element_size_bytes_pow2);
return;
}
auto& provider = command_processor_.GetD3D12Context().GetD3D12Provider();
auto device = provider.GetDevice();
const ui::d3d12::D3D12Provider& provider =
command_processor_.GetD3D12Provider();
ID3D12Device* device = provider.GetDevice();
device->CopyDescriptorsSimple(
1, handle,
provider.OffsetViewDescriptor(buffer_descriptor_heap_start_,
@ -278,8 +281,9 @@ void D3D12SharedMemory::WriteUintPow2UAVDescriptor(
assert_unhandled_case(element_size_bytes_pow2);
return;
}
auto& provider = command_processor_.GetD3D12Context().GetD3D12Provider();
auto device = provider.GetDevice();
const ui::d3d12::D3D12Provider& provider =
command_processor_.GetD3D12Provider();
ID3D12Device* device = provider.GetDevice();
device->CopyDescriptorsSimple(
1, handle,
provider.OffsetViewDescriptor(buffer_descriptor_heap_start_,
@ -298,8 +302,9 @@ bool D3D12SharedMemory::InitializeTraceSubmitDownloads() {
ui::d3d12::util::FillBufferResourceDesc(
download_buffer_desc, download_page_count << page_size_log2(),
D3D12_RESOURCE_FLAG_NONE);
auto& provider = command_processor_.GetD3D12Context().GetD3D12Provider();
auto device = provider.GetDevice();
const ui::d3d12::D3D12Provider& provider =
command_processor_.GetD3D12Provider();
ID3D12Device* device = provider.GetDevice();
if (FAILED(device->CreateCommittedResource(
&ui::d3d12::util::kHeapPropertiesReadback,
provider.GetHeapFlagCreateNotZeroed(), &download_buffer_desc,
@ -365,7 +370,7 @@ bool D3D12SharedMemory::AllocateSparseHostGpuMemoryRange(
<< host_gpu_memory_sparse_granularity_log2();
const ui::d3d12::D3D12Provider& provider =
command_processor_.GetD3D12Context().GetD3D12Provider();
command_processor_.GetD3D12Provider();
ID3D12Device* device = provider.GetDevice();
ID3D12CommandQueue* direct_queue = provider.GetDirectQueue();

View File

@ -16,6 +16,7 @@
#include <vector>
#include "xenia/base/assert.h"
#include "xenia/base/literals.h"
#include "xenia/base/math.h"
#include "xenia/ui/d3d12/d3d12_api.h"
@ -23,12 +24,14 @@ namespace xe {
namespace gpu {
namespace d3d12 {
using namespace xe::literals;
class D3D12CommandProcessor;
class DeferredCommandList {
public:
DeferredCommandList(const D3D12CommandProcessor& command_processor,
size_t initial_size_bytes = 1024 * 1024);
size_t initial_size_bytes = 1_MiB);
void Reset();
void Execute(ID3D12GraphicsCommandList* command_list,

View File

@ -87,7 +87,8 @@ PipelineCache::PipelineCache(D3D12CommandProcessor& command_processor,
register_file_(register_file),
render_target_cache_(render_target_cache),
bindless_resources_used_(bindless_resources_used) {
auto& provider = command_processor_.GetD3D12Context().GetD3D12Provider();
const ui::d3d12::D3D12Provider& provider =
command_processor_.GetD3D12Provider();
bool edram_rov_used = render_target_cache.GetPath() ==
RenderTargetCache::Path::kPixelShaderInterlock;
@ -109,7 +110,8 @@ PipelineCache::PipelineCache(D3D12CommandProcessor& command_processor,
PipelineCache::~PipelineCache() { Shutdown(); }
bool PipelineCache::Initialize() {
auto& provider = command_processor_.GetD3D12Context().GetD3D12Provider();
const ui::d3d12::D3D12Provider& provider =
command_processor_.GetD3D12Provider();
// Initialize the command processor thread DXIL objects.
dxbc_converter_ = nullptr;
@ -414,7 +416,8 @@ void PipelineCache::InitializeShaderStorage(
std::mutex shaders_failed_to_translate_mutex;
std::vector<D3D12Shader::D3D12Translation*> shaders_failed_to_translate;
auto shader_translation_thread_function = [&]() {
auto& provider = command_processor_.GetD3D12Context().GetD3D12Provider();
const ui::d3d12::D3D12Provider& provider =
command_processor_.GetD3D12Provider();
StringBuffer ucode_disasm_buffer;
DxbcShaderTranslator translator(
provider.GetAdapterVendorID(), bindless_resources_used_,
@ -700,37 +703,39 @@ void PipelineCache::InitializeShaderStorage(
++pipelines_created;
}
CreateQueuedPipelinesOnProcessorThread();
if (creation_threads_.size() > creation_thread_original_count) {
{
std::lock_guard<std::mutex> lock(creation_request_lock_);
creation_threads_shutdown_from_ = creation_thread_original_count;
// Assuming the queue is empty because of
// CreateQueuedPipelinesOnProcessorThread.
}
creation_request_cond_.notify_all();
while (creation_threads_.size() > creation_thread_original_count) {
xe::threading::Wait(creation_threads_.back().get(), false);
creation_threads_.pop_back();
}
bool await_creation_completion_event;
{
// Cleanup so additional threads can be created later again.
std::lock_guard<std::mutex> lock(creation_request_lock_);
creation_threads_shutdown_from_ = SIZE_MAX;
// If the invocation is blocking, all the shader storage initialization
// is expected to be done before proceeding, to avoid latency in the
// command processor after the invocation.
await_creation_completion_event =
blocking && creation_threads_busy_ != 0;
if (await_creation_completion_event) {
creation_completion_event_->Reset();
creation_completion_set_event_ = true;
if (!creation_threads_.empty()) {
CreateQueuedPipelinesOnProcessorThread();
if (creation_threads_.size() > creation_thread_original_count) {
{
std::lock_guard<std::mutex> lock(creation_request_lock_);
creation_threads_shutdown_from_ = creation_thread_original_count;
// Assuming the queue is empty because of
// CreateQueuedPipelinesOnProcessorThread.
}
creation_request_cond_.notify_all();
while (creation_threads_.size() > creation_thread_original_count) {
xe::threading::Wait(creation_threads_.back().get(), false);
creation_threads_.pop_back();
}
bool await_creation_completion_event;
{
// Cleanup so additional threads can be created later again.
std::lock_guard<std::mutex> lock(creation_request_lock_);
creation_threads_shutdown_from_ = SIZE_MAX;
// If the invocation is blocking, all the shader storage
// initialization is expected to be done before proceeding, to avoid
// latency in the command processor after the invocation.
await_creation_completion_event =
blocking && creation_threads_busy_ != 0;
if (await_creation_completion_event) {
creation_completion_event_->Reset();
creation_completion_set_event_ = true;
}
}
if (await_creation_completion_event) {
creation_request_cond_.notify_one();
xe::threading::Wait(creation_completion_event_.get(), false);
}
}
if (await_creation_completion_event) {
creation_request_cond_.notify_one();
xe::threading::Wait(creation_completion_event_.get(), false);
}
}
@ -1241,7 +1246,8 @@ bool PipelineCache::TranslateAnalyzedShader(
}
// Disassemble the shader for dumping.
auto& provider = command_processor_.GetD3D12Context().GetD3D12Provider();
const ui::d3d12::D3D12Provider& provider =
command_processor_.GetD3D12Provider();
if (cvars::d3d12_dxbc_disasm_dxilconv) {
translation.DisassembleDxbcAndDxil(provider, cvars::d3d12_dxbc_disasm,
dxbc_converter, dxc_utils, dxc_compiler);
@ -2052,8 +2058,7 @@ ID3D12PipelineState* PipelineCache::CreateD3D12Pipeline(
}
// Create the D3D12 pipeline state object.
auto device =
command_processor_.GetD3D12Context().GetD3D12Provider().GetDevice();
ID3D12Device* device = command_processor_.GetD3D12Provider().GetDevice();
ID3D12PipelineState* state;
if (FAILED(device->CreateGraphicsPipelineState(&state_desc,
IID_PPV_ARGS(&state)))) {

View File

@ -869,8 +869,9 @@ TextureCache::TextureCache(D3D12CommandProcessor& command_processor,
TextureCache::~TextureCache() { Shutdown(); }
bool TextureCache::Initialize() {
auto& provider = command_processor_.GetD3D12Context().GetD3D12Provider();
auto device = provider.GetDevice();
const ui::d3d12::D3D12Provider& provider =
command_processor_.GetD3D12Provider();
ID3D12Device* device = provider.GetDevice();
if (IsDrawResolutionScaled()) {
// Buffers not used yet - no need aliasing barriers to change ownership of
@ -1444,7 +1445,8 @@ void TextureCache::WriteActiveTextureBindfulSRV(
}
}
}
auto& provider = command_processor_.GetD3D12Context().GetD3D12Provider();
const ui::d3d12::D3D12Provider& provider =
command_processor_.GetD3D12Provider();
D3D12_CPU_DESCRIPTOR_HANDLE source_handle;
if (descriptor_index != UINT32_MAX) {
assert_not_null(texture);
@ -1622,8 +1624,7 @@ void TextureCache::WriteSampler(SamplerParameters parameters,
desc.MinLOD = float(parameters.mip_min_level);
// Maximum mip level is in the texture resource itself.
desc.MaxLOD = FLT_MAX;
auto device =
command_processor_.GetD3D12Context().GetD3D12Provider().GetDevice();
ID3D12Device* device = command_processor_.GetD3D12Provider().GetDevice();
device->CreateSampler(&desc, handle);
}
@ -1712,8 +1713,9 @@ bool TextureCache::EnsureScaledResolveMemoryCommitted(
uint64_t last_scaled = uint64_t(start_unscaled + (length_unscaled - 1)) *
draw_resolution_scale_area;
auto& provider = command_processor_.GetD3D12Context().GetD3D12Provider();
auto device = provider.GetDevice();
const ui::d3d12::D3D12Provider& provider =
command_processor_.GetD3D12Provider();
ID3D12Device* device = provider.GetDevice();
// Ensure GPU virtual memory for buffers that may be used to access the range
// is allocated - buffers are created. Always creating both buffers for all
@ -1943,8 +1945,8 @@ void TextureCache::CreateCurrentScaledResolveRangeUintPow2SRV(
scaled_resolve_2gb_buffers_[buffer_index];
assert_not_null(buffer);
ui::d3d12::util::CreateBufferTypedSRV(
command_processor_.GetD3D12Context().GetD3D12Provider().GetDevice(),
handle, buffer->resource(),
command_processor_.GetD3D12Provider().GetDevice(), handle,
buffer->resource(),
ui::d3d12::util::GetUintPow2DXGIFormat(element_size_bytes_pow2),
uint32_t(scaled_resolve_current_range_length_scaled_ >>
element_size_bytes_pow2),
@ -1961,8 +1963,8 @@ void TextureCache::CreateCurrentScaledResolveRangeUintPow2UAV(
scaled_resolve_2gb_buffers_[buffer_index];
assert_not_null(buffer);
ui::d3d12::util::CreateBufferTypedUAV(
command_processor_.GetD3D12Context().GetD3D12Provider().GetDevice(),
handle, buffer->resource(),
command_processor_.GetD3D12Provider().GetDevice(), handle,
buffer->resource(),
ui::d3d12::util::GetUintPow2DXGIFormat(element_size_bytes_pow2),
uint32_t(scaled_resolve_current_range_length_scaled_ >>
element_size_bytes_pow2),
@ -2254,8 +2256,9 @@ TextureCache::Texture* TextureCache::FindOrCreateTexture(TextureKey key) {
// Untiling through a buffer instead of using unordered access because copying
// is not done that often.
desc.Flags = D3D12_RESOURCE_FLAG_NONE;
auto& provider = command_processor_.GetD3D12Context().GetD3D12Provider();
auto device = provider.GetDevice();
const ui::d3d12::D3D12Provider& provider =
command_processor_.GetD3D12Provider();
ID3D12Device* device = provider.GetDevice();
// Assuming untiling will be the next operation.
D3D12_RESOURCE_STATES state = D3D12_RESOURCE_STATE_COPY_DEST;
ID3D12Resource* resource;
@ -2317,9 +2320,9 @@ bool TextureCache::LoadTextureData(Texture* texture) {
return true;
}
auto& command_list = command_processor_.GetDeferredCommandList();
auto device =
command_processor_.GetD3D12Context().GetD3D12Provider().GetDevice();
DeferredCommandList& command_list =
command_processor_.GetDeferredCommandList();
ID3D12Device* device = command_processor_.GetD3D12Provider().GetDevice();
// Get the pipeline.
LoadMode load_mode = GetLoadMode(texture->key);
@ -2875,8 +2878,7 @@ uint32_t TextureCache::FindOrCreateTextureDescriptor(Texture& texture,
host_swizzle |
D3D12_SHADER_COMPONENT_MAPPING_ALWAYS_SET_BIT_AVOIDING_ZEROMEM_MISTAKES;
auto device =
command_processor_.GetD3D12Context().GetD3D12Provider().GetDevice();
ID3D12Device* device = command_processor_.GetD3D12Provider().GetDevice();
uint32_t descriptor_index;
if (bindless_resources_used_) {
descriptor_index =
@ -2928,7 +2930,8 @@ uint32_t TextureCache::FindOrCreateTextureDescriptor(Texture& texture,
D3D12_CPU_DESCRIPTOR_HANDLE TextureCache::GetTextureDescriptorCPUHandle(
uint32_t descriptor_index) const {
auto& provider = command_processor_.GetD3D12Context().GetD3D12Provider();
const ui::d3d12::D3D12Provider& provider =
command_processor_.GetD3D12Provider();
if (bindless_resources_used_) {
return provider.OffsetViewDescriptor(
command_processor_.GetViewBindlessHeapCPUStart(), descriptor_index);

View File

@ -23,6 +23,7 @@
#include "xenia/gpu/texture_info.h"
#include "xenia/gpu/texture_util.h"
#include "xenia/gpu/xenos.h"
#include "xenia/ui/graphics_util.h"
// Very prominent in 545407F2.
DEFINE_bool(
@ -33,85 +34,10 @@ DEFINE_bool(
"for certain games to display the scene graphics).",
"GPU");
DEFINE_bool(
present_rescale, true,
"Whether to rescale the image, instead of maintaining the original pixel "
"size, when presenting to the window. When this is disabled, other "
"positioning options are ignored.",
"GPU");
DEFINE_bool(
present_letterbox, true,
"Maintain aspect ratio when stretching by displaying bars around the image "
"when there's no more overscan area to crop out.",
"GPU");
// https://github.com/MonoGame/MonoGame/issues/4697#issuecomment-217779403
// Using the value from DirectXTK (5% cropped out from each side, thus 90%),
// which is not exactly the Xbox One title-safe area, but close, and within the
// action-safe area:
// https://github.com/microsoft/DirectXTK/blob/1e80a465c6960b457ef9ab6716672c1443a45024/Src/SimpleMath.cpp#L144
// XNA TitleSafeArea is 80%, but it's very conservative, designed for CRT, and
// is the title-safe area rather than the action-safe area.
// 90% is also exactly the fraction of 16:9 height in 16:10.
DEFINE_int32(
present_safe_area_x, 90,
"Percentage of the image width that can be kept when presenting to "
"maintain aspect ratio without letterboxing or stretching.",
"GPU");
DEFINE_int32(
present_safe_area_y, 90,
"Percentage of the image height that can be kept when presenting to "
"maintain aspect ratio without letterboxing or stretching.",
"GPU");
namespace xe {
namespace gpu {
namespace draw_util {
int32_t FloatToD3D11Fixed16p8(float f32) {
// https://microsoft.github.io/DirectX-Specs/d3d/archive/D3D11_3_FunctionalSpec.htm#3.2.4.1%20FLOAT%20-%3E%20Fixed%20Point%20Integer
// Early exit tests.
// n == NaN || n.unbiasedExponent < -f-1 -> 0 . 0
if (!(std::abs(f32) >= 1.0f / 512.0f)) {
return 0;
}
// n >= (2^(i-1)-2^-f) -> 2^(i-1)-1 . 2^f-1
if (f32 >= 32768.0f - 1.0f / 256.0f) {
return (1 << 23) - 1;
}
// n <= -2^(i-1) -> -2^(i-1) . 0
if (f32 <= -32768.0f) {
return -32768 * 256;
}
uint32_t f32_bits = *reinterpret_cast<const uint32_t*>(&f32);
// Copy float32 mantissa bits [22:0] into corresponding bits [22:0] of a
// result buffer that has at least 24 bits total storage (before reaching
// rounding step further below). This includes one bit for the hidden 1.
// Set bit [23] (float32 hidden bit).
// Clear bits [31:24].
union {
int32_t s;
uint32_t u;
} result;
result.u = (f32_bits & ((1 << 23) - 1)) | (1 << 23);
// If the sign bit is set in the float32 number (negative), then take the 2's
// component of the entire set of bits.
if ((f32_bits >> 31) != 0) {
result.s = -result.s;
}
// Final calculation: extraBits = (mantissa - f) - n.unbiasedExponent
// (guaranteed to be >= 0).
int32_t exponent = int32_t((f32_bits >> 23) & 255) - 127;
uint32_t extra_bits = uint32_t(15 - exponent);
if (extra_bits) {
// Round the 32-bit value to a decimal that is extraBits to the left of
// the LSB end, using nearest-even.
result.u += (1 << (extra_bits - 1)) - 1 + ((result.u >> extra_bits) & 1);
// Shift right by extraBits (sign extending).
result.s >>= extra_bits;
}
return result.s;
}
bool IsRasterizationPotentiallyDone(const RegisterFile& regs,
bool primitive_polygonal) {
// TODO(Triang3l): Investigate ModeControl::kIgnore better, with respect to
@ -746,7 +672,7 @@ bool GetResolveInfo(const RegisterFile& regs, const Memory& memory,
regs.Get<reg::PA_SU_VTX_CNTL>().pix_center ? 0.0f : 0.5f;
int32_t vertices_fixed[6];
for (size_t i = 0; i < xe::countof(vertices_fixed); ++i) {
vertices_fixed[i] = FloatToD3D11Fixed16p8(
vertices_fixed[i] = ui::FloatToD3D11Fixed16p8(
xenos::GpuSwap(vertices_guest[i], fetch.endian) + half_pixel_offset);
}
// Inclusive.
@ -1151,87 +1077,6 @@ ResolveCopyShaderIndex ResolveInfo::GetCopyShader(
return shader;
}
void GetPresentArea(uint32_t source_width, uint32_t source_height,
uint32_t window_width, uint32_t window_height,
int32_t& target_x_out, int32_t& target_y_out,
uint32_t& target_width_out, uint32_t& target_height_out) {
if (!cvars::present_rescale) {
target_x_out = (int32_t(window_width) - int32_t(source_width)) / 2;
target_y_out = (int32_t(window_height) - int32_t(source_height)) / 2;
target_width_out = source_width;
target_height_out = source_height;
return;
}
// Prevent division by zero.
if (!source_width || !source_height) {
target_x_out = 0;
target_y_out = 0;
target_width_out = 0;
target_height_out = 0;
return;
}
if (uint64_t(window_width) * source_height >
uint64_t(source_width) * window_height) {
// The window is wider that the source - crop along Y, then letterbox or
// stretch along X.
uint32_t present_safe_area;
if (cvars::present_safe_area_y > 0 && cvars::present_safe_area_y < 100) {
present_safe_area = uint32_t(cvars::present_safe_area_y);
} else {
present_safe_area = 100;
}
uint32_t target_height =
uint32_t(uint64_t(window_width) * source_height / source_width);
bool letterbox = false;
if (target_height * present_safe_area > window_height * 100) {
// Don't crop out more than the safe area margin - letterbox or stretch.
target_height = window_height * 100 / present_safe_area;
letterbox = true;
}
if (letterbox && cvars::present_letterbox) {
uint32_t target_width =
uint32_t(uint64_t(source_width) * window_height * 100 /
(source_height * present_safe_area));
target_x_out = (int32_t(window_width) - int32_t(target_width)) / 2;
target_width_out = target_width;
} else {
target_x_out = 0;
target_width_out = window_width;
}
target_y_out = (int32_t(window_height) - int32_t(target_height)) / 2;
target_height_out = target_height;
} else {
// The window is taller than the source - crop along X, then letterbox or
// stretch along Y.
uint32_t present_safe_area;
if (cvars::present_safe_area_x > 0 && cvars::present_safe_area_x < 100) {
present_safe_area = uint32_t(cvars::present_safe_area_x);
} else {
present_safe_area = 100;
}
uint32_t target_width =
uint32_t(uint64_t(window_height) * source_width / source_height);
bool letterbox = false;
if (target_width * present_safe_area > window_width * 100) {
// Don't crop out more than the safe area margin - letterbox or stretch.
target_width = window_width * 100 / present_safe_area;
letterbox = true;
}
if (letterbox && cvars::present_letterbox) {
uint32_t target_height =
uint32_t(uint64_t(source_height) * window_width * 100 /
(source_width * present_safe_area));
target_y_out = (int32_t(window_height) - int32_t(target_height)) / 2;
target_height_out = target_height;
} else {
target_y_out = 0;
target_height_out = window_height;
}
target_x_out = (int32_t(window_width) - int32_t(target_width)) / 2;
target_width_out = target_width;
}
}
} // namespace draw_util
} // namespace gpu
} // namespace xe

View File

@ -25,15 +25,6 @@ namespace xe {
namespace gpu {
namespace draw_util {
// For estimating coverage extents from vertices. This may give results that are
// different than what the host GPU will actually draw (this is the reference
// conversion with 1/2 ULP accuracy, but Direct3D 11 permits 0.6 ULP tolerance
// in floating point to fixed point conversion), but is enough to tie-break
// vertices at pixel centers (due to the half-pixel offset applied to integer
// coordinates incorrectly, for instance) with some error tolerance near 0.5,
// for use with the top-left rasterization rule later.
int32_t FloatToD3D11Fixed16p8(float f32);
// 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

View File

@ -849,7 +849,7 @@ void DxbcShaderTranslator::StartTranslation() {
system_temp_aL_ = PushSystemTemp(0b1111);
system_temp_loop_count_ = PushSystemTemp(0b1111);
system_temp_grad_h_lod_ = PushSystemTemp(0b1111);
system_temp_grad_v_ = PushSystemTemp(0b0111);
system_temp_grad_v_vfetch_address_ = PushSystemTemp(0b1111);
// Zero general-purpose registers to prevent crashes when the game
// references them after only initializing them conditionally.
@ -1039,7 +1039,7 @@ void DxbcShaderTranslator::CompleteShaderCode() {
// - system_temp_aL_.
// - system_temp_loop_count_.
// - system_temp_grad_h_lod_.
// - system_temp_grad_v_.
// - system_temp_grad_v_vfetch_address_.
PopSystemTemp(6);
// Write memexported data to the shared memory UAV.

View File

@ -1104,7 +1104,9 @@ class DxbcShaderTranslator : public ShaderTranslator {
uint32_t system_temp_loop_count_;
// Explicitly set texture gradients and LOD.
uint32_t system_temp_grad_h_lod_;
uint32_t system_temp_grad_v_;
// .w stores `base + index * stride` in bytes from the last vfetch_full as it
// may be needed by vfetch_mini.
uint32_t system_temp_grad_v_vfetch_address_;
// The bool constant number containing the condition for the currently
// processed exec (or the last - unless a label has reset this), or

View File

@ -35,7 +35,9 @@ void DxbcShaderTranslator::ProcessVertexFetchInstruction(
uint32_t used_result_components = instr.result.GetUsedResultComponents();
uint32_t needed_words = xenos::GetVertexFormatNeededWords(
instr.attributes.data_format, used_result_components);
if (!needed_words) {
// If this is vfetch_full, the address may still be needed for vfetch_mini -
// don't exit before calculating the address.
if (!needed_words && instr.is_mini_fetch) {
// Nothing to load - just constant 0/1 writes, or the swizzle includes only
// components that don't exist in the format (writing zero instead of them).
// Unpacking assumes at least some word is needed.
@ -59,47 +61,74 @@ void DxbcShaderTranslator::ProcessVertexFetchInstruction(
// fetch constants on the CPU when proper bound checks are added - vfetch may
// be conditional, so fetch constants may also be used conditionally.
// - Load the byte address in physical memory to system_temp_result_.w (so
// it's not overwritten by data loads until the last one).
// - Load the part of the byte address in the physical memory that is the same
// in vfetch_full and vfetch_mini to system_temp_grad_v_vfetch_address_.w
// (the index operand GPR must not be reloaded in vfetch_mini because it
// might have been overwritten previously, but that shouldn't have effect on
// vfetch_mini).
dxbc::Dest address_dest(dxbc::Dest::R(system_temp_result_, 0b1000));
dxbc::Src address_src(dxbc::Src::R(system_temp_result_, dxbc::Src::kWWWW));
if (instr.attributes.stride) {
// Convert the index to an integer by flooring or by rounding to the nearest
// (as floor(index + 0.5) because rounding to the nearest even makes no
// sense for addressing, both 1.5 and 2.5 would be 2).
// http://web.archive.org/web/20100302145413/http://msdn.microsoft.com:80/en-us/library/bb313960.aspx
{
bool index_operand_temp_pushed = false;
dxbc::Src index_operand(
LoadOperand(instr.operands[0], 0b0001, index_operand_temp_pushed)
.SelectFromSwizzled(0));
if (instr.attributes.is_index_rounded) {
a_.OpAdd(address_dest, index_operand, dxbc::Src::LF(0.5f));
a_.OpRoundNI(address_dest, address_src);
} else {
a_.OpRoundNI(address_dest, index_operand);
}
if (index_operand_temp_pushed) {
PopSystemTemp();
dxbc::Src address_src(
dxbc::Src::R(system_temp_grad_v_vfetch_address_, dxbc::Src::kWWWW));
if (!instr.is_mini_fetch) {
dxbc::Dest address_dest(
dxbc::Dest::R(system_temp_grad_v_vfetch_address_, 0b1000));
if (instr.attributes.stride) {
// Convert the index to an integer by flooring or by rounding to the
// nearest (as floor(index + 0.5) because rounding to the nearest even
// makes no sense for addressing, both 1.5 and 2.5 would be 2).
{
bool index_operand_temp_pushed = false;
dxbc::Src index_operand(
LoadOperand(instr.operands[0], 0b0001, index_operand_temp_pushed)
.SelectFromSwizzled(0));
if (instr.attributes.is_index_rounded) {
a_.OpAdd(address_dest, index_operand, dxbc::Src::LF(0.5f));
a_.OpRoundNI(address_dest, address_src);
} else {
a_.OpRoundNI(address_dest, index_operand);
}
if (index_operand_temp_pushed) {
PopSystemTemp();
}
}
a_.OpFToI(address_dest, address_src);
// Extract the byte address from the fetch constant to
// system_temp_result_.w (which is not used yet).
a_.OpAnd(dxbc::Dest::R(system_temp_result_, 0b1000),
fetch_constant_src.SelectFromSwizzled(0),
dxbc::Src::LU(~uint32_t(3)));
// Merge the index and the base address.
a_.OpIMAd(address_dest, address_src,
dxbc::Src::LU(instr.attributes.stride * sizeof(uint32_t)),
dxbc::Src::R(system_temp_result_, dxbc::Src::kWWWW));
} else {
// Fetching from the same location - extract the byte address of the
// beginning of the buffer.
a_.OpAnd(address_dest, fetch_constant_src.SelectFromSwizzled(0),
dxbc::Src::LU(~uint32_t(3)));
}
a_.OpFToI(address_dest, address_src);
// Extract the byte address from the fetch constant to
// system_temp_result_.z.
a_.OpAnd(dxbc::Dest::R(system_temp_result_, 0b0100),
fetch_constant_src.SelectFromSwizzled(0),
dxbc::Src::LU(~uint32_t(3)));
// Merge the index and the base address.
a_.OpIMAd(address_dest, address_src,
dxbc::Src::LU(instr.attributes.stride * sizeof(uint32_t)),
dxbc::Src::R(system_temp_result_, dxbc::Src::kZZZZ));
} else {
// Fetching from the same location - extract the byte address of the
// beginning of the buffer.
a_.OpAnd(address_dest, fetch_constant_src.SelectFromSwizzled(0),
dxbc::Src::LU(~uint32_t(3)));
}
if (!needed_words) {
// The vfetch_full address has been loaded for the subsequent vfetch_mini,
// but there's no data to load.
StoreResult(instr.result, dxbc::Src::LF(0.0f));
return;
}
dxbc::Dest address_temp_dest(dxbc::Dest::R(system_temp_result_, 0b1000));
dxbc::Src address_temp_src(
dxbc::Src::R(system_temp_result_, dxbc::Src::kWWWW));
// - From now on, if any additional offset must be applied to the
// `base + index * stride` part of the address, it must be done by writing
// to system_temp_result_.w (address_temp_dest) instead of
// system_temp_grad_v_vfetch_address_.w (since it must stay the same for the
// vfetch_full and all its vfetch_mini invocations), and changing
// address_src to address_temp_src afterwards. system_temp_result_.w can be
// used for this purpose safely because it won't be overwritten until the
// last dword is loaded (after which the address won't be needed anymore).
// Add the word offset from the instruction (signed), plus the offset of the
// first needed word within the element.
uint32_t first_word_index;
@ -108,8 +137,9 @@ void DxbcShaderTranslator::ProcessVertexFetchInstruction(
instr.attributes.offset + int32_t(first_word_index);
if (first_word_buffer_offset) {
// Add the constant word offset.
a_.OpIAdd(address_dest, address_src,
a_.OpIAdd(address_temp_dest, address_src,
dxbc::Src::LI(first_word_buffer_offset * sizeof(uint32_t)));
address_src = address_temp_src;
}
// - Load needed words to system_temp_result_, words 0, 1, 2, 3 to X, Y, Z, W
@ -159,9 +189,10 @@ void DxbcShaderTranslator::ProcessVertexFetchInstruction(
~((uint32_t(1) << (word_index + word_count)) - uint32_t(1));
if (word_index != word_index_previous) {
// Go to the word in the buffer.
a_.OpIAdd(address_dest, address_src,
a_.OpIAdd(address_temp_dest, address_src,
dxbc::Src::LU((word_index - word_index_previous) *
sizeof(uint32_t)));
address_src = address_temp_src;
word_index_previous = word_index;
}
// Can ld_raw either to the first multiple components, or to any scalar
@ -592,7 +623,7 @@ void DxbcShaderTranslator::ProcessTextureFetchInstruction(
case FetchOpcode::kSetTextureGradientsVert: {
bool grad_operand_temp_pushed = false;
a_.OpMov(
dxbc::Dest::R(system_temp_grad_v_, 0b0111),
dxbc::Dest::R(system_temp_grad_v_vfetch_address_, 0b0111),
LoadOperand(instr.operands[0], 0b0111, grad_operand_temp_pushed));
if (grad_operand_temp_pushed) {
PopSystemTemp();
@ -1521,15 +1552,15 @@ void DxbcShaderTranslator::ProcessTextureFetchInstruction(
// Extract gradient exponent biases from the fetch constant and merge
// them with the LOD bias.
a_.OpIBFE(dxbc::Dest::R(grad_h_lod_temp, 0b0011), dxbc::Src::LU(5),
dxbc::Src::LU(22, 27, 0, 0),
RequestTextureFetchConstantWord(tfetch_index, 4));
dxbc::Src::LU(22, 27, 0, 0),
RequestTextureFetchConstantWord(tfetch_index, 4));
a_.OpIMAd(dxbc::Dest::R(grad_h_lod_temp, 0b0011),
dxbc::Src::R(grad_h_lod_temp), dxbc::Src::LI(int32_t(1) << 23),
dxbc::Src::LF(1.0f));
dxbc::Src::R(grad_h_lod_temp),
dxbc::Src::LI(int32_t(1) << 23), dxbc::Src::LF(1.0f));
a_.OpMul(dxbc::Dest::R(grad_v_temp, 0b1000), lod_src,
dxbc::Src::R(grad_h_lod_temp, dxbc::Src::kYYYY));
dxbc::Src::R(grad_h_lod_temp, dxbc::Src::kYYYY));
a_.OpMul(lod_dest, lod_src,
dxbc::Src::R(grad_h_lod_temp, dxbc::Src::kXXXX));
dxbc::Src::R(grad_h_lod_temp, dxbc::Src::kXXXX));
#endif
// Obtain the gradients and apply biases to them.
if (instr.attributes.use_register_gradients) {
@ -1540,11 +1571,11 @@ void DxbcShaderTranslator::ProcessTextureFetchInstruction(
// done in getCompTexLOD, so don't do it here too.
#if 0
a_.OpMul(dxbc::Dest::R(grad_v_temp, grad_mask),
dxbc::Src::R(system_temp_grad_v_),
dxbc::Src::R(grad_v_temp, dxbc::Src::kWWWW));
dxbc::Src::R(system_temp_grad_v_vfetch_address_),
dxbc::Src::R(grad_v_temp, dxbc::Src::kWWWW));
#else
a_.OpMul(dxbc::Dest::R(grad_v_temp, grad_mask),
dxbc::Src::R(system_temp_grad_v_), lod_src);
dxbc::Src::R(system_temp_grad_v_vfetch_address_), lod_src);
#endif
// TODO(Triang3l): Are cube map register gradients unnormalized if
// the coordinates themselves are unnormalized?
@ -1586,8 +1617,8 @@ void DxbcShaderTranslator::ProcessTextureFetchInstruction(
// done in getCompTexLOD, so don't do it here too.
#if 0
a_.OpMul(dxbc::Dest::R(grad_v_temp, grad_mask),
dxbc::Src::R(grad_v_temp),
dxbc::Src::R(grad_v_temp, dxbc::Src::kWWWW));
dxbc::Src::R(grad_v_temp),
dxbc::Src::R(grad_v_temp, dxbc::Src::kWWWW));
#else
a_.OpMul(dxbc::Dest::R(grad_v_temp, grad_mask),
dxbc::Src::R(grad_v_temp), lod_src);

View File

@ -2,13 +2,19 @@
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2020 Ben Vanik. All rights reserved. *
* Copyright 2022 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/gpu/graphics_system.h"
#include <cstdint>
#include <functional>
#include <memory>
#include <mutex>
#include <utility>
#include "xenia/base/byte_stream.h"
#include "xenia/base/clock.h"
#include "xenia/base/logging.h"
@ -48,70 +54,40 @@ GraphicsSystem::~GraphicsSystem() = default;
X_STATUS GraphicsSystem::Setup(cpu::Processor* processor,
kernel::KernelState* kernel_state,
ui::Window* target_window) {
ui::WindowedAppContext* app_context,
[[maybe_unused]] bool is_surface_required) {
memory_ = processor->memory();
processor_ = processor;
kernel_state_ = kernel_state;
target_window_ = target_window;
app_context_ = app_context;
// Initialize display and rendering context.
// This must happen on the UI thread.
std::unique_ptr<xe::ui::GraphicsContext> processor_context = nullptr;
if (provider_) {
// Setup the context the command processor will do all its drawing in.
bool contexts_initialized = true;
processor_context = provider()->CreateEmulationContext();
if (processor_context) {
if (target_window_) {
if (!target_window_->app_context().CallInUIThreadSynchronous([&]() {
// Create the context used for presentation.
assert_null(target_window->context());
target_window_->set_context(
provider_->CreateHostContext(target_window_));
})) {
contexts_initialized = false;
}
}
// Safe if either the UI thread call or the presenter creation fails.
if (app_context_) {
app_context_->CallInUIThreadSynchronous([this]() {
presenter_ = provider_->CreatePresenter(
[this](bool is_responsible, bool statically_from_ui_thread) {
OnHostGpuLossFromAnyThread(is_responsible);
});
});
} else {
contexts_initialized = false;
}
if (!contexts_initialized) {
xe::FatalError(
"Unable to initialize graphics context. Xenia requires Vulkan "
"support.\n"
"\n"
"Ensure you have the latest drivers for your GPU and "
"that it supports Vulkan.\n"
"\n"
"See https://xenia.jp/faq/ for more information and a list of "
"supported GPUs.");
return X_STATUS_UNSUCCESSFUL;
// May be needed for offscreen use, such as capturing the guest output
// image.
presenter_ = provider_->CreatePresenter(
[this](bool is_responsible, bool statically_from_ui_thread) {
OnHostGpuLossFromAnyThread(is_responsible);
});
}
}
// Create command processor. This will spin up a thread to process all
// incoming ringbuffer packets.
command_processor_ = CreateCommandProcessor();
if (!command_processor_->Initialize(std::move(processor_context))) {
if (!command_processor_->Initialize()) {
XELOGE("Unable to initialize command processor");
return X_STATUS_UNSUCCESSFUL;
}
if (target_window) {
command_processor_->set_swap_request_handler(
[this]() { target_window_->Invalidate(); });
// Watch for paint requests to do our swap.
target_window->on_painting.AddListener(
[this](xe::ui::UIEvent* e) { Swap(e); });
// Watch for context lost events.
target_window->on_context_lost.AddListener(
[this](xe::ui::UIEvent* e) { Reset(); });
} else {
command_processor_->set_swap_request_handler([]() {});
}
// Let the processor know we want register access callbacks.
memory_->AddVirtualMappedRange(
0x7FC80000, 0xFFFF0000, 0x0000FFFF, this,
@ -152,6 +128,7 @@ void GraphicsSystem::Shutdown() {
if (command_processor_) {
EndTracing();
command_processor_->Shutdown();
command_processor_.reset();
}
if (vsync_worker_thread_) {
@ -159,13 +136,35 @@ void GraphicsSystem::Shutdown() {
vsync_worker_thread_->Wait(0, 0, 0, nullptr);
vsync_worker_thread_.reset();
}
if (presenter_) {
if (app_context_) {
app_context_->CallInUIThreadSynchronous([this]() { presenter_.reset(); });
}
// If there's no app context (thus the presenter is owned by the thread that
// initialized the GraphicsSystem) or can't be queueing UI thread calls
// anymore, shutdown anyway.
presenter_.reset();
}
provider_.reset();
}
void GraphicsSystem::Reset() {
// TODO(DrChat): Reset the system.
XELOGI("Context lost; Reset invoked");
Shutdown();
void GraphicsSystem::OnHostGpuLossFromAnyThread(
[[maybe_unused]] bool is_responsible) {
// TODO(Triang3l): Somehow gain exclusive ownership of the Provider (may be
// used by the command processor, the presenter, and possibly anything else,
// it's considered free-threaded, except for lifetime management which will be
// involved in this case) and reset it so a new host GPU API device is
// created. Then ask the command processor to reset itself in its thread, and
// ask the UI thread to reset the Presenter (the UI thread manages its
// lifetime - but if there's no WindowedAppContext, either don't reset it as
// in this case there's no user who needs uninterrupted gameplay, or somehow
// protect it with a mutex so any thread can be considered a UI thread and
// reset).
if (host_gpu_loss_reported_.test_and_set(std::memory_order_relaxed)) {
return;
}
xe::FatalError("Graphics device lost (probably due to an internal error)");
}

View File

@ -2,7 +2,7 @@
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2020 Ben Vanik. All rights reserved. *
* Copyright 2022 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
@ -11,7 +11,10 @@
#define XENIA_GPU_GRAPHICS_SYSTEM_H_
#include <atomic>
#include <cstdint>
#include <functional>
#include <memory>
#include <mutex>
#include <string>
#include <thread>
@ -19,7 +22,9 @@
#include "xenia/gpu/register_file.h"
#include "xenia/kernel/xthread.h"
#include "xenia/memory.h"
#include "xenia/ui/window.h"
#include "xenia/ui/graphics_provider.h"
#include "xenia/ui/presenter.h"
#include "xenia/ui/windowed_app_context.h"
#include "xenia/xbox.h"
namespace xe {
@ -41,14 +46,17 @@ class GraphicsSystem {
cpu::Processor* processor() const { return processor_; }
kernel::KernelState* kernel_state() const { return kernel_state_; }
ui::GraphicsProvider* provider() const { return provider_.get(); }
ui::Presenter* presenter() const { return presenter_.get(); }
virtual X_STATUS Setup(cpu::Processor* processor,
kernel::KernelState* kernel_state,
ui::Window* target_window);
ui::WindowedAppContext* app_context,
bool is_surface_required);
virtual void Shutdown();
virtual void Reset();
virtual std::unique_ptr<xe::ui::RawImage> Capture() { return nullptr; }
// May be called from any thread any number of times, even during recovery
// from a device loss.
void OnHostGpuLossFromAnyThread(bool is_responsible);
RegisterFile* register_file() { return &register_file_; }
CommandProcessor* command_processor() const {
@ -91,12 +99,11 @@ class GraphicsSystem {
void WriteRegister(uint32_t addr, uint32_t value);
void MarkVblank();
virtual void Swap(xe::ui::UIEvent* e) = 0;
Memory* memory_ = nullptr;
cpu::Processor* processor_ = nullptr;
kernel::KernelState* kernel_state_ = nullptr;
ui::Window* target_window_ = nullptr;
ui::WindowedAppContext* app_context_ = nullptr;
std::unique_ptr<ui::GraphicsProvider> provider_;
uint32_t interrupt_callback_ = 0;
@ -109,6 +116,11 @@ class GraphicsSystem {
std::unique_ptr<CommandProcessor> command_processor_;
bool paused_ = false;
private:
std::unique_ptr<ui::Presenter> presenter_;
std::atomic_flag host_gpu_loss_reported_;
};
} // namespace gpu

View File

@ -31,9 +31,9 @@ void NullCommandProcessor::ShutdownContext() {
return CommandProcessor::ShutdownContext();
}
void NullCommandProcessor::PerformSwap(uint32_t frontbuffer_ptr,
uint32_t frontbuffer_width,
uint32_t frontbuffer_height) {}
void NullCommandProcessor::IssueSwap(uint32_t frontbuffer_ptr,
uint32_t frontbuffer_width,
uint32_t frontbuffer_height) {}
Shader* NullCommandProcessor::LoadShader(xenos::ShaderType shader_type,
uint32_t guest_address,

View File

@ -33,8 +33,8 @@ class NullCommandProcessor : public CommandProcessor {
bool SetupContext() override;
void ShutdownContext() override;
void PerformSwap(uint32_t frontbuffer_ptr, uint32_t frontbuffer_width,
uint32_t frontbuffer_height) override;
void IssueSwap(uint32_t frontbuffer_ptr, uint32_t frontbuffer_width,
uint32_t frontbuffer_height) override;
Shader* LoadShader(xenos::ShaderType shader_type, uint32_t guest_address,
const uint32_t* host_address,

View File

@ -23,31 +23,20 @@ NullGraphicsSystem::~NullGraphicsSystem() {}
X_STATUS NullGraphicsSystem::Setup(cpu::Processor* processor,
kernel::KernelState* kernel_state,
ui::Window* target_window) {
ui::WindowedAppContext* app_context,
bool is_surface_required) {
// This is a null graphics system, but we still setup vulkan because UI needs
// it through us :|
provider_ = xe::ui::vulkan::VulkanProvider::Create();
return GraphicsSystem::Setup(processor, kernel_state, target_window);
provider_ = xe::ui::vulkan::VulkanProvider::Create(is_surface_required);
return GraphicsSystem::Setup(processor, kernel_state, app_context,
is_surface_required);
}
void NullGraphicsSystem::Shutdown() { GraphicsSystem::Shutdown(); }
std::unique_ptr<CommandProcessor> NullGraphicsSystem::CreateCommandProcessor() {
return std::unique_ptr<CommandProcessor>(
new NullCommandProcessor(this, kernel_state_));
}
void NullGraphicsSystem::Swap(xe::ui::UIEvent* e) {
if (!command_processor_) {
return;
}
auto& swap_state = command_processor_->swap_state();
std::lock_guard<std::mutex> lock(swap_state.mutex);
swap_state.pending = false;
}
} // namespace null
} // namespace gpu
} // namespace xe

View File

@ -29,13 +29,11 @@ class NullGraphicsSystem : public GraphicsSystem {
std::string name() const override { return "null"; }
X_STATUS Setup(cpu::Processor* processor, kernel::KernelState* kernel_state,
ui::Window* target_window) override;
void Shutdown() override;
ui::WindowedAppContext* app_context,
bool is_surface_required) override;
private:
std::unique_ptr<CommandProcessor> CreateCommandProcessor() override;
void Swap(xe::ui::UIEvent* e) override;
};
} // namespace null

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