Merge branch 'master' into vulkan
This commit is contained in:
commit
922efb13ce
|
@ -8,14 +8,17 @@ skip_tags: true
|
||||||
|
|
||||||
skip_commits:
|
skip_commits:
|
||||||
files:
|
files:
|
||||||
- .drone.yml
|
- .drone.star
|
||||||
- .github/**
|
- .github/**
|
||||||
- .travis.yml
|
- android/**
|
||||||
- docs/**
|
- docs/**
|
||||||
- src/**/*_posix.*
|
- src/**/*_posix.*
|
||||||
- src/**/*_linux.*
|
- src/**/*_linux.*
|
||||||
|
- src/**/*_gnulinux.*
|
||||||
- src/**/*_x11.*
|
- src/**/*_x11.*
|
||||||
- src/**/*_gtk.*
|
- src/**/*_gtk.*
|
||||||
|
- src/**/*_android.*
|
||||||
|
- src/**/*_mac.*
|
||||||
- LICENSE
|
- LICENSE
|
||||||
- README.md
|
- README.md
|
||||||
|
|
||||||
|
@ -27,23 +30,28 @@ pull_requests:
|
||||||
os: Visual Studio 2019
|
os: Visual Studio 2019
|
||||||
|
|
||||||
init:
|
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:
|
install:
|
||||||
- cmd: vcpkg integrate remove
|
- |
|
||||||
- cmd: xb setup
|
vcpkg integrate remove
|
||||||
|
xb setup
|
||||||
|
|
||||||
platform: Windows
|
platform: Windows
|
||||||
|
|
||||||
configuration:
|
configuration: [Release, Checked]
|
||||||
- Release
|
|
||||||
- Checked
|
|
||||||
|
|
||||||
build_script:
|
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:
|
after_build:
|
||||||
- cmd: |
|
- |
|
||||||
IF NOT "%CONFIGURATION%"=="Checked" SET "ARCHIVE_SUFFIX=%APPVEYOR_REPO_BRANCH%"
|
IF NOT "%CONFIGURATION%"=="Checked" SET "ARCHIVE_SUFFIX=%APPVEYOR_REPO_BRANCH%"
|
||||||
IF NOT "%CONFIGURATION%"=="Checked" SET "ARCHIVE_SWITCHES=--"
|
IF NOT "%CONFIGURATION%"=="Checked" SET "ARCHIVE_SWITCHES=--"
|
||||||
IF "%CONFIGURATION%"=="Checked" SET "ARCHIVE_SUFFIX=%APPVEYOR_REPO_BRANCH%_FOR-DEVS-ONLY"
|
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"
|
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:
|
before_test:
|
||||||
- cmd: xb gentests
|
- xb gentests
|
||||||
|
|
||||||
test_script:
|
test_script:
|
||||||
- cmd: xb test --config=%CONFIGURATION% --no_build
|
- xb test --config=%CONFIGURATION% --no_build
|
||||||
|
|
||||||
artifacts:
|
artifacts:
|
||||||
- path: '*.zip'
|
- path: '*.zip'
|
||||||
|
@ -73,3 +81,22 @@ deploy:
|
||||||
configuration: release
|
configuration: release
|
||||||
appveyor_repo_tag: true
|
appveyor_repo_tag: true
|
||||||
is_not_pr: 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
|
||||||
|
|
|
@ -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',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
214
.drone.yml
214
.drone.yml
|
@ -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
|
|
|
@ -1,2 +0,0 @@
|
||||||
patreon: xenia_project
|
|
||||||
github: [gibbed, JoelLinn, Razzile]
|
|
|
@ -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)
|
|
|
@ -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
|
|
@ -67,6 +67,12 @@
|
||||||
[submodule "third_party/premake-androidndk"]
|
[submodule "third_party/premake-androidndk"]
|
||||||
path = third_party/premake-androidndk
|
path = third_party/premake-androidndk
|
||||||
url = https://github.com/Triang3l/premake-androidndk.git
|
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"]
|
[submodule "third_party/glslang"]
|
||||||
path = third_party/glslang
|
path = third_party/glslang
|
||||||
url = https://github.com/KhronosGroup/glslang.git
|
url = https://github.com/KhronosGroup/glslang.git
|
||||||
|
|
|
@ -21,9 +21,9 @@ Discussing illegal activities will get you banned.
|
||||||
|
|
||||||
## Status
|
## Status
|
||||||
|
|
||||||
Buildbot | Status
|
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)
|
[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)
|
[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.
|
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:
|
that there are some major work areas still untouched:
|
||||||
|
|
||||||
* Help work through [missing functionality/bugs in games](https://github.com/xenia-project/xenia/labels/compat)
|
* Help work through [missing functionality/bugs in games](https://github.com/xenia-project/xenia/labels/compat)
|
||||||
* Add input drivers for [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)
|
* Skilled with Linux? A strong contributor is needed to [help with porting](https://github.com/xenia-project/xenia/labels/platform-linux)
|
||||||
|
|
||||||
See more projects [good for contributors](https://github.com/xenia-project/xenia/labels/good%20first%20issue). It's a good idea to ask on Discord and check the issues page before beginning work on
|
See more projects [good for contributors](https://github.com/xenia-project/xenia/labels/good%20first%20issue). It's a good idea to ask on Discord and check the issues page before beginning work on
|
||||||
|
|
|
@ -4,7 +4,6 @@ plugins {
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 30
|
compileSdkVersion 30
|
||||||
buildToolsVersion '30.0.2'
|
|
||||||
ndkVersion '23.0.7599858'
|
ndkVersion '23.0.7599858'
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
|
@ -83,3 +82,7 @@ android {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation 'org.jetbrains:annotations:15.0'
|
||||||
|
}
|
|
@ -29,7 +29,9 @@
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@android:style/Theme.Material.Light">
|
android:theme="@android:style/Theme.Material.Light">
|
||||||
|
|
||||||
<activity android:name="jp.xenia.emulator.WindowDemoActivity">
|
<activity
|
||||||
|
android:name="jp.xenia.emulator.WindowDemoActivity"
|
||||||
|
android:label="@string/activity_label_window_demo">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,8 +1,18 @@
|
||||||
package jp.xenia.emulator;
|
package jp.xenia.emulator;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
public class WindowDemoActivity extends WindowedAppActivity {
|
public class WindowDemoActivity extends WindowedAppActivity {
|
||||||
@Override
|
@Override
|
||||||
protected String getWindowedAppIdentifier() {
|
protected String getWindowedAppIdentifier() {
|
||||||
return "xenia_ui_window_vulkan_demo";
|
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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,43 +3,158 @@ package jp.xenia.emulator;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.res.AssetManager;
|
import android.content.res.AssetManager;
|
||||||
import android.os.Bundle;
|
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 {
|
public abstract class WindowedAppActivity extends Activity {
|
||||||
private static final String TAG = "WindowedAppActivity";
|
|
||||||
|
|
||||||
static {
|
static {
|
||||||
// TODO(Triang3l): Move all demos to libxenia.so.
|
// TODO(Triang3l): Move all demos to libxenia.so.
|
||||||
System.loadLibrary("xenia-ui-window-vulkan-demo");
|
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);
|
String windowedAppIdentifier, AssetManager assetManager);
|
||||||
|
|
||||||
private native void onDestroyNative(long appContext);
|
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 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
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(final Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
mAppContext = initializeWindowedAppOnCreateNative(getWindowedAppIdentifier(), getAssets());
|
final String windowedAppIdentifier = getWindowedAppIdentifier();
|
||||||
|
mAppContext = initializeWindowedAppOnCreate(windowedAppIdentifier, getAssets());
|
||||||
if (mAppContext == 0) {
|
if (mAppContext == 0) {
|
||||||
Log.e(TAG, "Error initializing the windowed app");
|
|
||||||
finish();
|
finish();
|
||||||
return;
|
throw new XeniaRuntimeException(
|
||||||
|
"Error initializing the windowed app " + windowedAppIdentifier);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onDestroy() {
|
protected void onDestroy() {
|
||||||
|
setWindowSurfaceView(null);
|
||||||
if (mAppContext != 0) {
|
if (mAppContext != 0) {
|
||||||
onDestroyNative(mAppContext);
|
onDestroyNative(mAppContext);
|
||||||
}
|
}
|
||||||
mAppContext = 0;
|
mAppContext = 0;
|
||||||
super.onDestroy();
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?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"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/window_demo_surface_view"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
tools:context="jp.xenia.emulator.WindowDemoActivity">
|
tools:context="jp.xenia.emulator.WindowDemoActivity" />
|
||||||
|
|
||||||
</RelativeLayout>
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">Xenia</string>
|
<string name="app_name">Xenia</string>
|
||||||
|
<string name="activity_label_window_demo">Xenia Window Demo</string>
|
||||||
</resources>
|
</resources>
|
|
@ -5,7 +5,7 @@ buildscript {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
dependencies {
|
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
|
// NOTE: Do not place your application dependencies here; they belong
|
||||||
// in the individual module build.gradle files
|
// in the individual module build.gradle files
|
||||||
|
|
|
@ -2,13 +2,20 @@
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
* Xenia : Xbox 360 Emulator Research Project *
|
* 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. *
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "xenia/app/emulator_window.h"
|
#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/fmt/include/fmt/format.h"
|
||||||
#include "third_party/imgui/imgui.h"
|
#include "third_party/imgui/imgui.h"
|
||||||
#include "xenia/base/assert.h"
|
#include "xenia/base/assert.h"
|
||||||
|
@ -20,11 +27,17 @@
|
||||||
#include "xenia/base/profiling.h"
|
#include "xenia/base/profiling.h"
|
||||||
#include "xenia/base/system.h"
|
#include "xenia/base/system.h"
|
||||||
#include "xenia/base/threading.h"
|
#include "xenia/base/threading.h"
|
||||||
|
#include "xenia/cpu/processor.h"
|
||||||
#include "xenia/emulator.h"
|
#include "xenia/emulator.h"
|
||||||
|
#include "xenia/gpu/command_processor.h"
|
||||||
#include "xenia/gpu/graphics_system.h"
|
#include "xenia/gpu/graphics_system.h"
|
||||||
#include "xenia/ui/file_picker.h"
|
#include "xenia/ui/file_picker.h"
|
||||||
|
#include "xenia/ui/graphics_provider.h"
|
||||||
#include "xenia/ui/imgui_dialog.h"
|
#include "xenia/ui/imgui_dialog.h"
|
||||||
#include "xenia/ui/imgui_drawer.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"
|
#include "xenia/ui/virtual_key.h"
|
||||||
|
|
||||||
// Autogenerated by `xb premake`.
|
// Autogenerated by `xb premake`.
|
||||||
|
@ -32,13 +45,91 @@
|
||||||
|
|
||||||
DECLARE_bool(debug);
|
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 xe {
|
||||||
namespace app {
|
namespace app {
|
||||||
|
|
||||||
using xe::ui::FileDropEvent;
|
using xe::ui::FileDropEvent;
|
||||||
using xe::ui::KeyEvent;
|
using xe::ui::KeyEvent;
|
||||||
using xe::ui::MenuItem;
|
using xe::ui::MenuItem;
|
||||||
using xe::ui::MouseEvent;
|
|
||||||
using xe::ui::UIEvent;
|
using xe::ui::UIEvent;
|
||||||
|
|
||||||
const std::string kBaseTitle = "Xenia";
|
const std::string kBaseTitle = "Xenia";
|
||||||
|
@ -47,7 +138,12 @@ EmulatorWindow::EmulatorWindow(Emulator* emulator,
|
||||||
ui::WindowedAppContext& app_context)
|
ui::WindowedAppContext& app_context)
|
||||||
: emulator_(emulator),
|
: emulator_(emulator),
|
||||||
app_context_(app_context),
|
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 +
|
base_title_ = kBaseTitle +
|
||||||
#ifdef DEBUG
|
#ifdef DEBUG
|
||||||
#if _NO_DEBUG_HEAP == 1
|
#if _NO_DEBUG_HEAP == 1
|
||||||
|
@ -76,107 +172,331 @@ std::unique_ptr<EmulatorWindow> EmulatorWindow::Create(
|
||||||
return emulator_window;
|
return emulator_window;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool EmulatorWindow::Initialize() {
|
EmulatorWindow::~EmulatorWindow() {
|
||||||
if (!window_->Initialize()) {
|
// Notify the ImGui drawer that the immediate drawer is being destroyed.
|
||||||
XELOGE("Failed to initialize platform window");
|
ShutdownGraphicsSystemPresenterPainting();
|
||||||
return false;
|
}
|
||||||
|
|
||||||
|
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(
|
ApplyDisplayConfigForCvars();
|
||||||
[this](UIEvent* e) { app_context_.QuitFromUIThread(); });
|
|
||||||
|
|
||||||
window_->on_file_drop.AddListener(
|
window_->SetPresenter(presenter);
|
||||||
[this](FileDropEvent* e) { FileDrop(e->filename()); });
|
|
||||||
|
|
||||||
window_->on_key_down.AddListener([this](KeyEvent* e) {
|
immediate_drawer_ =
|
||||||
bool handled = true;
|
emulator_->graphics_system()->provider()->CreateImmediateDrawer();
|
||||||
switch (e->virtual_key()) {
|
if (immediate_drawer_) {
|
||||||
case ui::VirtualKey::kO: {
|
immediate_drawer_->SetPresenter(presenter);
|
||||||
if (e->is_ctrl_pressed()) {
|
imgui_drawer_->SetPresenterAndImmediateDrawer(presenter,
|
||||||
FileOpen();
|
immediate_drawer_.get());
|
||||||
}
|
Profiler::SetUserIO(kZOrderProfiler, window_.get(), presenter,
|
||||||
} break;
|
immediate_drawer_.get());
|
||||||
case ui::VirtualKey::kMultiply: {
|
}
|
||||||
CpuTimeScalarReset();
|
}
|
||||||
} break;
|
|
||||||
case ui::VirtualKey::kSubtract: {
|
|
||||||
CpuTimeScalarSetHalf();
|
|
||||||
} break;
|
|
||||||
case ui::VirtualKey::kAdd: {
|
|
||||||
CpuTimeScalarSetDouble();
|
|
||||||
} break;
|
|
||||||
|
|
||||||
case ui::VirtualKey::kF3: {
|
void EmulatorWindow::ShutdownGraphicsSystemPresenterPainting() {
|
||||||
Profiler::ToggleDisplay();
|
Profiler::SetUserIO(kZOrderProfiler, window_.get(), nullptr, nullptr);
|
||||||
} break;
|
imgui_drawer_->SetPresenterAndImmediateDrawer(nullptr, nullptr);
|
||||||
|
immediate_drawer_.reset();
|
||||||
|
if (window_) {
|
||||||
|
window_->SetPresenter(nullptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
case ui::VirtualKey::kF4: {
|
void EmulatorWindow::OnEmulatorInitialized() {
|
||||||
GpuTraceFrame();
|
emulator_initialized_ = true;
|
||||||
} break;
|
window_->SetMainMenuEnabled(true);
|
||||||
case ui::VirtualKey::kF5: {
|
// When the user can see that the emulator isn't initializing anymore (the
|
||||||
GpuClearCaches();
|
// menu isn't disabled), enter fullscreen if requested.
|
||||||
} break;
|
if (cvars::fullscreen) {
|
||||||
case ui::VirtualKey::kF7: {
|
window_->SetFullscreen(true);
|
||||||
// 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;
|
|
||||||
|
|
||||||
case ui::VirtualKey::kPause: {
|
void EmulatorWindow::EmulatorWindowListener::OnClosing(ui::UIEvent& e) {
|
||||||
CpuBreakIntoDebugger();
|
emulator_window_.app_context_.QuitFromUIThread();
|
||||||
} break;
|
}
|
||||||
case ui::VirtualKey::kCancel: {
|
|
||||||
CpuBreakIntoHostDebugger();
|
|
||||||
} break;
|
|
||||||
|
|
||||||
case ui::VirtualKey::kF1: {
|
void EmulatorWindow::EmulatorWindowListener::OnFileDrop(ui::FileDropEvent& e) {
|
||||||
ShowHelpWebsite();
|
emulator_window_.FileDrop(e.filename());
|
||||||
} break;
|
}
|
||||||
|
|
||||||
case ui::VirtualKey::kF2: {
|
void EmulatorWindow::EmulatorWindowListener::OnKeyDown(ui::KeyEvent& e) {
|
||||||
ShowCommitID();
|
emulator_window_.OnKeyDown(e);
|
||||||
} break;
|
}
|
||||||
|
|
||||||
default: {
|
void EmulatorWindow::DisplayConfigGameConfigLoadCallback::PostGameConfigLoad() {
|
||||||
handled = false;
|
emulator_window_.ApplyDisplayConfigForCvars();
|
||||||
} break;
|
}
|
||||||
}
|
|
||||||
e->set_handled(handled);
|
|
||||||
});
|
|
||||||
|
|
||||||
window_->on_mouse_move.AddListener([this](MouseEvent* e) {
|
void EmulatorWindow::DisplayConfigDialog::OnDraw(ImGuiIO& io) {
|
||||||
if (window_->is_fullscreen() && (e->dx() > 2 || e->dy() > 2)) {
|
gpu::GraphicsSystem* graphics_system =
|
||||||
if (!window_->is_cursor_visible()) {
|
emulator_window_.emulator_->graphics_system();
|
||||||
window_->set_cursor_visible(true);
|
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.
|
// Main menu.
|
||||||
// FIXME: This code is really messy.
|
// FIXME: This code is really messy.
|
||||||
|
@ -186,17 +506,19 @@ bool EmulatorWindow::Initialize() {
|
||||||
file_menu->AddChild(
|
file_menu->AddChild(
|
||||||
MenuItem::Create(MenuItem::Type::kString, "&Open...", "Ctrl+O",
|
MenuItem::Create(MenuItem::Type::kString, "&Open...", "Ctrl+O",
|
||||||
std::bind(&EmulatorWindow::FileOpen, this)));
|
std::bind(&EmulatorWindow::FileOpen, this)));
|
||||||
|
#ifdef DEBUG
|
||||||
file_menu->AddChild(
|
file_menu->AddChild(
|
||||||
MenuItem::Create(MenuItem::Type::kString, "Close",
|
MenuItem::Create(MenuItem::Type::kString, "Close",
|
||||||
std::bind(&EmulatorWindow::FileClose, this)));
|
std::bind(&EmulatorWindow::FileClose, this)));
|
||||||
|
#endif // #ifdef DEBUG
|
||||||
file_menu->AddChild(MenuItem::Create(MenuItem::Type::kSeparator));
|
file_menu->AddChild(MenuItem::Create(MenuItem::Type::kSeparator));
|
||||||
file_menu->AddChild(MenuItem::Create(
|
file_menu->AddChild(MenuItem::Create(
|
||||||
MenuItem::Type::kString, "Show content directory...",
|
MenuItem::Type::kString, "Show content directory...",
|
||||||
std::bind(&EmulatorWindow::ShowContentDirectory, this)));
|
std::bind(&EmulatorWindow::ShowContentDirectory, this)));
|
||||||
file_menu->AddChild(MenuItem::Create(MenuItem::Type::kSeparator));
|
file_menu->AddChild(MenuItem::Create(MenuItem::Type::kSeparator));
|
||||||
file_menu->AddChild(MenuItem::Create(MenuItem::Type::kString, "E&xit",
|
file_menu->AddChild(
|
||||||
"Alt+F4",
|
MenuItem::Create(MenuItem::Type::kString, "E&xit", "Alt+F4",
|
||||||
[this]() { window_->Close(); }));
|
[this]() { window_->RequestClose(); }));
|
||||||
}
|
}
|
||||||
main_menu->AddChild(std::move(file_menu));
|
main_menu->AddChild(std::move(file_menu));
|
||||||
|
|
||||||
|
@ -249,21 +571,35 @@ bool EmulatorWindow::Initialize() {
|
||||||
}
|
}
|
||||||
main_menu->AddChild(std::move(gpu_menu));
|
main_menu->AddChild(std::move(gpu_menu));
|
||||||
|
|
||||||
// Window menu.
|
// Display menu.
|
||||||
auto window_menu = MenuItem::Create(MenuItem::Type::kPopup, "&Window");
|
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",
|
MenuItem::Create(MenuItem::Type::kString, "&Fullscreen", "F11",
|
||||||
std::bind(&EmulatorWindow::ToggleFullscreen, this)));
|
std::bind(&EmulatorWindow::ToggleFullscreen, this)));
|
||||||
}
|
}
|
||||||
main_menu->AddChild(std::move(window_menu));
|
main_menu->AddChild(std::move(display_menu));
|
||||||
|
|
||||||
// Help menu.
|
// Help menu.
|
||||||
auto help_menu = MenuItem::Create(MenuItem::Type::kPopup, "&Help");
|
auto help_menu = MenuItem::Create(MenuItem::Type::kPopup, "&Help");
|
||||||
{
|
{
|
||||||
help_menu->AddChild(
|
help_menu->AddChild(
|
||||||
MenuItem::Create(MenuItem::Type::kString, "Build commit on GitHub...",
|
MenuItem::Create(MenuItem::Type::kString, "FA&Q...", "F1",
|
||||||
"F2", std::bind(&EmulatorWindow::ShowCommitID, this)));
|
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(
|
help_menu->AddChild(MenuItem::Create(
|
||||||
MenuItem::Type::kString, "Recent changes on GitHub...", [this]() {
|
MenuItem::Type::kString, "Recent changes on GitHub...", [this]() {
|
||||||
LaunchWebBrowser(
|
LaunchWebBrowser(
|
||||||
|
@ -271,25 +607,202 @@ bool EmulatorWindow::Initialize() {
|
||||||
"..." XE_BUILD_BRANCH);
|
"..." XE_BUILD_BRANCH);
|
||||||
}));
|
}));
|
||||||
help_menu->AddChild(MenuItem::Create(MenuItem::Type::kSeparator));
|
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(
|
help_menu->AddChild(MenuItem::Create(
|
||||||
MenuItem::Type::kString, "&About...",
|
MenuItem::Type::kString, "&About...",
|
||||||
[this]() { LaunchWebBrowser("https://xenia.jp/about/"); }));
|
[this]() { LaunchWebBrowser("https://xenia.jp/about/"); }));
|
||||||
}
|
}
|
||||||
main_menu->AddChild(std::move(help_menu));
|
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;
|
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) {
|
void EmulatorWindow::FileDrop(const std::filesystem::path& filename) {
|
||||||
|
if (!emulator_initialized_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
auto result = emulator_->LaunchPath(filename);
|
auto result = emulator_->LaunchPath(filename);
|
||||||
if (XFAILED(result)) {
|
if (XFAILED(result)) {
|
||||||
// TODO: Display a message box.
|
// TODO: Display a message box.
|
||||||
|
@ -312,7 +825,7 @@ void EmulatorWindow::FileOpen() {
|
||||||
//{"Content Package (*.xcp)", "*.xcp" },
|
//{"Content Package (*.xcp)", "*.xcp" },
|
||||||
{"All Files (*.*)", "*.*"},
|
{"All Files (*.*)", "*.*"},
|
||||||
});
|
});
|
||||||
if (file_picker->Show(window_->native_handle())) {
|
if (file_picker->Show(window_.get())) {
|
||||||
auto selected_files = file_picker->selected_files();
|
auto selected_files = file_picker->selected_files();
|
||||||
if (!selected_files.empty()) {
|
if (!selected_files.empty()) {
|
||||||
path = selected_files[0];
|
path = selected_files[0];
|
||||||
|
@ -357,17 +870,6 @@ void EmulatorWindow::ShowContentDirectory() {
|
||||||
LaunchFileExplorer(target_path);
|
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() {
|
void EmulatorWindow::CpuTimeScalarReset() {
|
||||||
Clock::set_guest_time_scalar(1.0);
|
Clock::set_guest_time_scalar(1.0);
|
||||||
UpdateTitle();
|
UpdateTitle();
|
||||||
|
@ -385,7 +887,7 @@ void EmulatorWindow::CpuTimeScalarSetDouble() {
|
||||||
|
|
||||||
void EmulatorWindow::CpuBreakIntoDebugger() {
|
void EmulatorWindow::CpuBreakIntoDebugger() {
|
||||||
if (!cvars::debug) {
|
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 "
|
"Xenia must be launched with the "
|
||||||
"--debug flag in order to enable "
|
"--debug flag in order to enable "
|
||||||
"debugging.");
|
"debugging.");
|
||||||
|
@ -411,19 +913,48 @@ void EmulatorWindow::GpuClearCaches() {
|
||||||
emulator()->graphics_system()->ClearCaches();
|
emulator()->graphics_system()->ClearCaches();
|
||||||
}
|
}
|
||||||
|
|
||||||
void EmulatorWindow::ToggleFullscreen() {
|
void EmulatorWindow::SetFullscreen(bool fullscreen) {
|
||||||
window_->ToggleFullscreen(!window_->is_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
|
void EmulatorWindow::ToggleFullscreen() {
|
||||||
cursor_hide_time_ = Clock::QueryHostSystemTime() + 30000000;
|
SetFullscreen(!window_->IsFullscreen());
|
||||||
if (!window_->is_fullscreen()) {
|
}
|
||||||
window_->set_cursor_visible(true);
|
|
||||||
|
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
|
#ifdef XE_BUILD_IS_PR
|
||||||
LaunchWebBrowser(
|
LaunchWebBrowser(
|
||||||
"https://github.com/xenia-project/xenia/pull/" XE_BUILD_PR_NUMBER);
|
"https://github.com/xenia-project/xenia/pull/" XE_BUILD_PR_NUMBER);
|
||||||
|
@ -473,7 +1004,7 @@ void EmulatorWindow::UpdateTitle() {
|
||||||
sb.Append(u8" (Preloading shaders\u2026)");
|
sb.Append(u8" (Preloading shaders\u2026)");
|
||||||
}
|
}
|
||||||
|
|
||||||
window_->set_title(sb.to_string_view());
|
window_->SetTitle(sb.to_string_view());
|
||||||
}
|
}
|
||||||
|
|
||||||
void EmulatorWindow::SetInitializingShaderStorage(bool initializing) {
|
void EmulatorWindow::SetInitializingShaderStorage(bool initializing) {
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
* Xenia : Xbox 360 Emulator Research Project *
|
* 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. *
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
*/
|
*/
|
||||||
|
@ -13,42 +13,124 @@
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#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/menu_item.h"
|
||||||
|
#include "xenia/ui/presenter.h"
|
||||||
#include "xenia/ui/window.h"
|
#include "xenia/ui/window.h"
|
||||||
|
#include "xenia/ui/window_listener.h"
|
||||||
#include "xenia/ui/windowed_app_context.h"
|
#include "xenia/ui/windowed_app_context.h"
|
||||||
#include "xenia/xbox.h"
|
#include "xenia/xbox.h"
|
||||||
|
|
||||||
namespace xe {
|
|
||||||
class Emulator;
|
|
||||||
} // namespace xe
|
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
namespace app {
|
namespace app {
|
||||||
|
|
||||||
class EmulatorWindow {
|
class EmulatorWindow {
|
||||||
public:
|
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(
|
static std::unique_ptr<EmulatorWindow> Create(
|
||||||
Emulator* emulator, ui::WindowedAppContext& app_context);
|
Emulator* emulator, ui::WindowedAppContext& app_context);
|
||||||
|
|
||||||
Emulator* emulator() const { return emulator_; }
|
Emulator* emulator() const { return emulator_; }
|
||||||
ui::WindowedAppContext& app_context() const { return app_context_; }
|
ui::WindowedAppContext& app_context() const { return app_context_; }
|
||||||
ui::Window* window() const { return window_.get(); }
|
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 UpdateTitle();
|
||||||
|
void SetFullscreen(bool fullscreen);
|
||||||
void ToggleFullscreen();
|
void ToggleFullscreen();
|
||||||
void SetInitializingShaderStorage(bool initializing);
|
void SetInitializingShaderStorage(bool initializing);
|
||||||
|
|
||||||
private:
|
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,
|
explicit EmulatorWindow(Emulator* emulator,
|
||||||
ui::WindowedAppContext& app_context);
|
ui::WindowedAppContext& app_context);
|
||||||
|
|
||||||
bool Initialize();
|
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 FileDrop(const std::filesystem::path& filename);
|
||||||
void FileOpen();
|
void FileOpen();
|
||||||
void FileClose();
|
void FileClose();
|
||||||
void ShowContentDirectory();
|
void ShowContentDirectory();
|
||||||
void CheckHideCursor();
|
|
||||||
void CpuTimeScalarReset();
|
void CpuTimeScalarReset();
|
||||||
void CpuTimeScalarSetHalf();
|
void CpuTimeScalarSetHalf();
|
||||||
void CpuTimeScalarSetDouble();
|
void CpuTimeScalarSetDouble();
|
||||||
|
@ -56,15 +138,27 @@ class EmulatorWindow {
|
||||||
void CpuBreakIntoHostDebugger();
|
void CpuBreakIntoHostDebugger();
|
||||||
void GpuTraceFrame();
|
void GpuTraceFrame();
|
||||||
void GpuClearCaches();
|
void GpuClearCaches();
|
||||||
void ShowHelpWebsite();
|
void ToggleDisplayConfigDialog();
|
||||||
void ShowCommitID();
|
void ShowCompatibility();
|
||||||
|
void ShowFAQ();
|
||||||
|
void ShowBuildCommit();
|
||||||
|
|
||||||
Emulator* emulator_;
|
Emulator* emulator_;
|
||||||
ui::WindowedAppContext& app_context_;
|
ui::WindowedAppContext& app_context_;
|
||||||
|
EmulatorWindowListener window_listener_;
|
||||||
std::unique_ptr<ui::Window> window_;
|
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_;
|
std::string base_title_;
|
||||||
uint64_t cursor_hide_time_ = 0;
|
|
||||||
bool initializing_shader_storage_ = false;
|
bool initializing_shader_storage_ = false;
|
||||||
|
|
||||||
|
std::unique_ptr<DisplayConfigDialog> display_config_dialog_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace app
|
} // namespace app
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
* Xenia : Xbox 360 Emulator Research Project *
|
* 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. *
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
*/
|
*/
|
||||||
|
@ -28,6 +28,7 @@
|
||||||
#include "xenia/emulator.h"
|
#include "xenia/emulator.h"
|
||||||
#include "xenia/ui/file_picker.h"
|
#include "xenia/ui/file_picker.h"
|
||||||
#include "xenia/ui/window.h"
|
#include "xenia/ui/window.h"
|
||||||
|
#include "xenia/ui/window_listener.h"
|
||||||
#include "xenia/ui/windowed_app.h"
|
#include "xenia/ui/windowed_app.h"
|
||||||
#include "xenia/ui/windowed_app_context.h"
|
#include "xenia/ui/windowed_app_context.h"
|
||||||
#include "xenia/vfs/devices/host_path_device.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]",
|
DEFINE_string(hid, "any", "Input system. Use: [any, nop, sdl, winkey, xinput]",
|
||||||
"HID");
|
"HID");
|
||||||
|
|
||||||
DEFINE_bool(fullscreen, false, "Toggles fullscreen", "GPU");
|
|
||||||
|
|
||||||
DEFINE_path(
|
DEFINE_path(
|
||||||
storage_root, "",
|
storage_root, "",
|
||||||
"Root path for persistent internal data storage (config, etc.), or empty "
|
"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);
|
explicit EmulatorApp(xe::ui::WindowedAppContext& app_context);
|
||||||
|
|
||||||
static std::unique_ptr<apu::AudioSystem> CreateAudioSystem(
|
static std::unique_ptr<apu::AudioSystem> CreateAudioSystem(
|
||||||
|
@ -203,6 +213,8 @@ class EmulatorApp final : public xe::ui::WindowedApp {
|
||||||
void EmulatorThread();
|
void EmulatorThread();
|
||||||
void ShutdownEmulatorThreadFromUIThread();
|
void ShutdownEmulatorThreadFromUIThread();
|
||||||
|
|
||||||
|
DebugWindowClosedListener debug_window_closed_listener_;
|
||||||
|
|
||||||
std::unique_ptr<Emulator> emulator_;
|
std::unique_ptr<Emulator> emulator_;
|
||||||
std::unique_ptr<EmulatorWindow> emulator_window_;
|
std::unique_ptr<EmulatorWindow> emulator_window_;
|
||||||
|
|
||||||
|
@ -215,8 +227,15 @@ class EmulatorApp final : public xe::ui::WindowedApp {
|
||||||
std::thread emulator_thread_;
|
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)
|
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");
|
AddPositionalOption("target");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -253,9 +272,10 @@ std::vector<std::unique_ptr<hid::InputDriver>> EmulatorApp::CreateInputDrivers(
|
||||||
ui::Window* window) {
|
ui::Window* window) {
|
||||||
std::vector<std::unique_ptr<hid::InputDriver>> drivers;
|
std::vector<std::unique_ptr<hid::InputDriver>> drivers;
|
||||||
if (cvars::hid.compare("nop") == 0) {
|
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 {
|
} else {
|
||||||
Factory<hid::InputDriver, ui::Window*> factory;
|
Factory<hid::InputDriver, ui::Window*, size_t> factory;
|
||||||
#if XE_PLATFORM_WIN32
|
#if XE_PLATFORM_WIN32
|
||||||
factory.Add("xinput", xe::hid::xinput::Create);
|
factory.Add("xinput", xe::hid::xinput::Create);
|
||||||
#endif // XE_PLATFORM_WIN32
|
#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!
|
// WinKey input driver should always be the last input driver added!
|
||||||
factory.Add("winkey", xe::hid::winkey::Create);
|
factory.Add("winkey", xe::hid::winkey::Create);
|
||||||
#endif // XE_PLATFORM_WIN32
|
#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())) {
|
if (XSUCCEEDED(driver->Setup())) {
|
||||||
drivers.emplace_back(std::move(driver));
|
drivers.emplace_back(std::move(driver));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (drivers.empty()) {
|
if (drivers.empty()) {
|
||||||
// Fallback to nop if none created.
|
// 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;
|
return drivers;
|
||||||
|
@ -366,6 +388,9 @@ void EmulatorApp::OnDestroy() {
|
||||||
// The profiler needs to shut down before the graphics context.
|
// The profiler needs to shut down before the graphics context.
|
||||||
Profiler::Shutdown();
|
Profiler::Shutdown();
|
||||||
|
|
||||||
|
// Write all cvar overrides to the config.
|
||||||
|
config::SaveConfig();
|
||||||
|
|
||||||
// TODO(DrChat): Remove this code and do a proper exit.
|
// TODO(DrChat): Remove this code and do a proper exit.
|
||||||
XELOGI("Cheap-skate exit!");
|
XELOGI("Cheap-skate exit!");
|
||||||
std::quick_exit(EXIT_SUCCESS);
|
std::quick_exit(EXIT_SUCCESS);
|
||||||
|
@ -379,15 +404,18 @@ void EmulatorApp::EmulatorThread() {
|
||||||
|
|
||||||
// Setup and initialize all subsystems. If we can't do something
|
// Setup and initialize all subsystems. If we can't do something
|
||||||
// (unsupported system, memory issues, etc) this will fail early.
|
// (unsupported system, memory issues, etc) this will fail early.
|
||||||
X_STATUS result =
|
X_STATUS result = emulator_->Setup(
|
||||||
emulator_->Setup(emulator_window_->window(), CreateAudioSystem,
|
emulator_window_->window(), emulator_window_->imgui_drawer(),
|
||||||
CreateGraphicsSystem, CreateInputDrivers);
|
CreateAudioSystem, CreateGraphicsSystem, CreateInputDrivers);
|
||||||
if (XFAILED(result)) {
|
if (XFAILED(result)) {
|
||||||
XELOGE("Failed to setup emulator: {:08X}", result);
|
XELOGE("Failed to setup emulator: {:08X}", result);
|
||||||
app_context().RequestDeferredQuit();
|
app_context().RequestDeferredQuit();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
app_context().CallInUIThread(
|
||||||
|
[this]() { emulator_window_->SetupGraphicsSystemPresenterPainting(); });
|
||||||
|
|
||||||
if (cvars::mount_scratch) {
|
if (cvars::mount_scratch) {
|
||||||
auto scratch_device = std::make_unique<xe::vfs::HostPathDevice>(
|
auto scratch_device = std::make_unique<xe::vfs::HostPathDevice>(
|
||||||
"\\SCRATCH", "scratch", false);
|
"\\SCRATCH", "scratch", false);
|
||||||
|
@ -456,12 +484,8 @@ void EmulatorApp::EmulatorThread() {
|
||||||
app_context().CallInUIThreadSynchronous([this]() {
|
app_context().CallInUIThreadSynchronous([this]() {
|
||||||
debug_window_ = xe::debug::ui::DebugWindow::Create(emulator_.get(),
|
debug_window_ = xe::debug::ui::DebugWindow::Create(emulator_.get(),
|
||||||
app_context());
|
app_context());
|
||||||
debug_window_->window()->on_closed.AddListener(
|
debug_window_->window()->AddListener(
|
||||||
[this](xe::ui::UIEvent* e) {
|
&debug_window_closed_listener_);
|
||||||
emulator_->processor()->set_debug_listener(nullptr);
|
|
||||||
app_context().CallInUIThread(
|
|
||||||
[this]() { debug_window_.reset(); });
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
// If failed to enqueue the UI thread call, this will just be null.
|
// If failed to enqueue the UI thread call, this will just be null.
|
||||||
return debug_window_.get();
|
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(
|
app_context().CallInUIThread(
|
||||||
[this]() { emulator_window_->window()->EnableMainMenu(); });
|
[this]() { emulator_window_->OnEmulatorInitialized(); });
|
||||||
|
|
||||||
// Grab path from the flag or unnamed argument.
|
// Grab path from the flag or unnamed argument.
|
||||||
std::filesystem::path path;
|
std::filesystem::path path;
|
||||||
|
@ -500,12 +524,6 @@ void EmulatorApp::EmulatorThread() {
|
||||||
path = cvars::target;
|
path = cvars::target;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Toggles fullscreen
|
|
||||||
if (cvars::fullscreen) {
|
|
||||||
app_context().CallInUIThread(
|
|
||||||
[this]() { emulator_window_->ToggleFullscreen(); });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!path.empty()) {
|
if (!path.empty()) {
|
||||||
// Normalize the path and make absolute.
|
// Normalize the path and make absolute.
|
||||||
auto abs_path = std::filesystem::absolute(path);
|
auto abs_path = std::filesystem::absolute(path);
|
||||||
|
|
|
@ -177,6 +177,25 @@ void XmaContext::SwapInputBuffer(XMA_CONTEXT_DATA* data) {
|
||||||
data->input_buffer_read_offset = 0;
|
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(
|
void XmaContext::NextPacket(
|
||||||
uint8_t* input_buffer,
|
uint8_t* input_buffer,
|
||||||
|
@ -364,6 +383,7 @@ void XmaContext::Decode(XMA_CONTEXT_DATA* data) {
|
||||||
assert_false(data->stop_when_done);
|
assert_false(data->stop_when_done);
|
||||||
assert_false(data->interrupt_when_done);
|
assert_false(data->interrupt_when_done);
|
||||||
static int total_samples = 0;
|
static int total_samples = 0;
|
||||||
|
bool reuse_input_buffer = false;
|
||||||
// Decode until we can't write any more data.
|
// Decode until we can't write any more data.
|
||||||
while (output_remaining_bytes > 0) {
|
while (output_remaining_bytes > 0) {
|
||||||
if (!data->input_buffer_0_valid && !data->input_buffer_1_valid) {
|
if (!data->input_buffer_0_valid && !data->input_buffer_1_valid) {
|
||||||
|
@ -371,6 +391,10 @@ void XmaContext::Decode(XMA_CONTEXT_DATA* data) {
|
||||||
break;
|
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(packets_skip_ == 0);
|
||||||
// assert_true(split_frame_len_ == 0);
|
// assert_true(split_frame_len_ == 0);
|
||||||
// assert_true(split_frame_len_partial_ == 0);
|
// assert_true(split_frame_len_partial_ == 0);
|
||||||
|
@ -392,7 +416,13 @@ void XmaContext::Decode(XMA_CONTEXT_DATA* data) {
|
||||||
packets_skip_--;
|
packets_skip_--;
|
||||||
packet_idx++;
|
packet_idx++;
|
||||||
if (packet_idx >= current_input_packet_count) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -524,7 +554,13 @@ void XmaContext::Decode(XMA_CONTEXT_DATA* data) {
|
||||||
packet += kBytesPerPacket;
|
packet += kBytesPerPacket;
|
||||||
packet_idx++;
|
packet_idx++;
|
||||||
if (packet_idx >= current_input_packet_count) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -606,7 +642,13 @@ void XmaContext::Decode(XMA_CONTEXT_DATA* data) {
|
||||||
packets_skip_--;
|
packets_skip_--;
|
||||||
packet_idx++;
|
packet_idx++;
|
||||||
if (packet_idx >= current_input_packet_count) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -618,7 +660,13 @@ void XmaContext::Decode(XMA_CONTEXT_DATA* data) {
|
||||||
// Next packet but we already skipped to it
|
// Next packet but we already skipped to it
|
||||||
if (packet_idx >= current_input_packet_count) {
|
if (packet_idx >= current_input_packet_count) {
|
||||||
// Buffer is fully used
|
// 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;
|
break;
|
||||||
}
|
}
|
||||||
offset =
|
offset =
|
||||||
|
|
|
@ -171,6 +171,8 @@ class XmaContext {
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static void SwapInputBuffer(XMA_CONTEXT_DATA* data);
|
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 void NextPacket(XMA_CONTEXT_DATA* data);
|
||||||
static int GetSampleRate(int id);
|
static int GetSampleRate(int id);
|
||||||
// Get the offset of the next frame. Does not traverse packets.
|
// Get the offset of the next frame. Does not traverse packets.
|
||||||
|
|
|
@ -60,7 +60,7 @@ void* Arena::Alloc(size_t size, size_t align) {
|
||||||
|
|
||||||
if (active_chunk_) {
|
if (active_chunk_) {
|
||||||
if (active_chunk_->capacity - active_chunk_->offset <
|
if (active_chunk_->capacity - active_chunk_->offset <
|
||||||
size + get_padding() + 4096) {
|
size + get_padding() + 4_KiB) {
|
||||||
Chunk* next = active_chunk_->next;
|
Chunk* next = active_chunk_->next;
|
||||||
if (!next) {
|
if (!next) {
|
||||||
assert_true(size + get_padding() < chunk_size_,
|
assert_true(size + get_padding() < chunk_size_,
|
||||||
|
|
|
@ -14,11 +14,15 @@
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "xenia/base/literals.h"
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
|
|
||||||
|
using namespace xe::literals;
|
||||||
|
|
||||||
class Arena {
|
class Arena {
|
||||||
public:
|
public:
|
||||||
explicit Arena(size_t chunk_size = 4 * 1024 * 1024);
|
explicit Arena(size_t chunk_size = 4_MiB);
|
||||||
~Arena();
|
~Arena();
|
||||||
|
|
||||||
void Reset();
|
void Reset();
|
||||||
|
|
|
@ -24,7 +24,7 @@ extern "C" int main(int argc, char** argv) {
|
||||||
|
|
||||||
// Initialize Android globals, including logging. Needs parsed cvars.
|
// Initialize Android globals, including logging. Needs parsed cvars.
|
||||||
// TODO(Triang3l): Obtain the actual API level.
|
// TODO(Triang3l): Obtain the actual API level.
|
||||||
xe::InitializeAndroidAppFromMainThread(__ANDROID_API__);
|
xe::InitializeAndroidAppFromMainThread(__ANDROID_API__, nullptr, nullptr);
|
||||||
|
|
||||||
std::vector<std::string> args;
|
std::vector<std::string> args;
|
||||||
for (int n = 0; n < argc; n++) {
|
for (int n = 0; n < argc; n++) {
|
||||||
|
|
|
@ -13,7 +13,8 @@
|
||||||
#include "xenia/base/console_app_main.h"
|
#include "xenia/base/console_app_main.h"
|
||||||
#include "xenia/base/main_win.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();
|
xe::ConsoleAppEntryInfo entry_info = xe::GetConsoleAppEntryInfo();
|
||||||
|
|
||||||
std::vector<std::string> args;
|
std::vector<std::string> args;
|
||||||
|
|
|
@ -27,7 +27,7 @@ static bool has_shell_environment_variable() {
|
||||||
size_t size = 0;
|
size_t size = 0;
|
||||||
// Check if SHELL exists
|
// Check if SHELL exists
|
||||||
// If it doesn't, then we are in a Windows Terminal
|
// 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) {
|
if (error) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
* Xenia : Xbox 360 Emulator Research Project *
|
* 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. *
|
* 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 LoadGameConfigValue(std::shared_ptr<cpptoml::base> result) override;
|
||||||
void SetConfigValue(T val);
|
void SetConfigValue(T val);
|
||||||
void SetGameConfigValue(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:
|
private:
|
||||||
std::string category_;
|
std::string category_;
|
||||||
|
@ -260,6 +264,16 @@ void ConfigVar<T>::SetGameConfigValue(T val) {
|
||||||
UpdateValue();
|
UpdateValue();
|
||||||
}
|
}
|
||||||
template <class T>
|
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() {
|
void ConfigVar<T>::ResetConfigValueToDefault() {
|
||||||
SetConfigValue(this->default_value_);
|
SetConfigValue(this->default_value_);
|
||||||
}
|
}
|
||||||
|
@ -373,6 +387,28 @@ ICommandVar* define_cmdvar(const char* name, T* default_value,
|
||||||
extern type name; \
|
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
|
// 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
|
// users' configs (to distinguish between a leftover old default and an explicit
|
||||||
// override), without having to rename the variable.
|
// override), without having to rename the variable.
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
* Xenia : Xbox 360 Emulator Research Project *
|
* 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. *
|
* 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.
|
// attempting to create it.
|
||||||
bool CreateParentFolder(const std::filesystem::path& path);
|
bool CreateParentFolder(const std::filesystem::path& path);
|
||||||
|
|
||||||
// Creates an empty file at the given path.
|
// Creates an empty file at the given path, overwriting if it exists.
|
||||||
bool CreateFile(const std::filesystem::path& path);
|
bool CreateEmptyFile(const std::filesystem::path& path);
|
||||||
|
|
||||||
// Opens the file at the given path with the specified mode.
|
// Opens the file at the given path with the specified mode.
|
||||||
// This behaves like fopen and the returned handle can be used with stdio.
|
// This behaves like fopen and the returned handle can be used with stdio.
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
* Xenia : Xbox 360 Emulator Research Project *
|
* 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. *
|
* 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;
|
return filetime;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CreateFile(const std::filesystem::path& path) {
|
bool CreateEmptyFile(const std::filesystem::path& path) {
|
||||||
int file = creat(path.c_str(), 0774);
|
int file = creat(path.c_str(), 0774);
|
||||||
if (file >= 0) {
|
if (file >= 0) {
|
||||||
close(file);
|
close(file);
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
* Xenia : Xbox 360 Emulator Research Project *
|
* 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. *
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
*/
|
*/
|
||||||
|
@ -60,7 +60,7 @@ std::filesystem::path GetUserFolder() {
|
||||||
return result;
|
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,
|
auto handle = CreateFileW(path.c_str(), 0, 0, nullptr, CREATE_ALWAYS,
|
||||||
FILE_ATTRIBUTE_NORMAL, nullptr);
|
FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||||
if (handle == INVALID_HANDLE_VALUE) {
|
if (handle == INVALID_HANDLE_VALUE) {
|
||||||
|
|
|
@ -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_
|
|
@ -11,6 +11,7 @@
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
|
#include <cstdlib>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
@ -25,6 +26,7 @@
|
||||||
#include "xenia/base/cvar.h"
|
#include "xenia/base/cvar.h"
|
||||||
#include "xenia/base/debugging.h"
|
#include "xenia/base/debugging.h"
|
||||||
#include "xenia/base/filesystem.h"
|
#include "xenia/base/filesystem.h"
|
||||||
|
#include "xenia/base/literals.h"
|
||||||
#include "xenia/base/math.h"
|
#include "xenia/base/math.h"
|
||||||
#include "xenia/base/memory.h"
|
#include "xenia/base/memory.h"
|
||||||
#include "xenia/base/platform.h"
|
#include "xenia/base/platform.h"
|
||||||
|
@ -59,6 +61,7 @@ DEFINE_int32(
|
||||||
"Logging");
|
"Logging");
|
||||||
|
|
||||||
namespace dp = disruptorplus;
|
namespace dp = disruptorplus;
|
||||||
|
using namespace xe::literals;
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
|
|
||||||
|
@ -74,7 +77,7 @@ struct LogLine {
|
||||||
char prefix_char;
|
char prefix_char;
|
||||||
};
|
};
|
||||||
|
|
||||||
thread_local char thread_log_buffer_[64 * 1024];
|
thread_local char thread_log_buffer_[64_KiB];
|
||||||
|
|
||||||
FileLogSink::~FileLogSink() {
|
FileLogSink::~FileLogSink() {
|
||||||
if (file_) {
|
if (file_) {
|
||||||
|
@ -234,7 +237,7 @@ class Logger {
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static const size_t kBufferSize = 8 * 1024 * 1024;
|
static const size_t kBufferSize = 8_MiB;
|
||||||
uint8_t buffer_[kBufferSize];
|
uint8_t buffer_[kBufferSize];
|
||||||
|
|
||||||
static const size_t kBlockSize = 256;
|
static const size_t kBlockSize = 256;
|
||||||
|
@ -498,7 +501,13 @@ void FatalError(const std::string_view str) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ShutdownLogging();
|
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
|
} // namespace xe
|
||||||
|
|
|
@ -9,11 +9,15 @@
|
||||||
|
|
||||||
#include "xenia/base/main_android.h"
|
#include "xenia/base/main_android.h"
|
||||||
|
|
||||||
|
#include <android/log.h>
|
||||||
|
#include <pthread.h>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
|
#include <cstdlib>
|
||||||
|
|
||||||
#include "xenia/base/assert.h"
|
#include "xenia/base/assert.h"
|
||||||
#include "xenia/base/logging.h"
|
#include "xenia/base/logging.h"
|
||||||
#include "xenia/base/memory.h"
|
#include "xenia/base/memory.h"
|
||||||
|
#include "xenia/base/system.h"
|
||||||
#include "xenia/base/threading.h"
|
#include "xenia/base/threading.h"
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
|
@ -22,7 +26,25 @@ static size_t android_initializations_ = 0;
|
||||||
|
|
||||||
static int32_t android_api_level_ = __ANDROID_API__;
|
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_++) {
|
if (android_initializations_++) {
|
||||||
// Already initialized for another component in the process.
|
// Already initialized for another component in the process.
|
||||||
return;
|
return;
|
||||||
|
@ -32,6 +54,45 @@ void InitializeAndroidAppFromMainThread(int32_t api_level) {
|
||||||
// subsystem initialization itself.
|
// subsystem initialization itself.
|
||||||
android_api_level_ = api_level;
|
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.
|
// Logging uses threading.
|
||||||
xe::threading::AndroidInitialize();
|
xe::threading::AndroidInitialize();
|
||||||
|
|
||||||
|
@ -40,6 +101,15 @@ void InitializeAndroidAppFromMainThread(int32_t api_level) {
|
||||||
xe::InitializeLogging("xenia");
|
xe::InitializeLogging("xenia");
|
||||||
|
|
||||||
xe::memory::AndroidInitialize();
|
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() {
|
void ShutdownAndroidAppFromMainThread() {
|
||||||
|
@ -52,15 +122,36 @@ void ShutdownAndroidAppFromMainThread() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
xe::ShutdownAndroidSystem();
|
||||||
|
|
||||||
xe::memory::AndroidShutdown();
|
xe::memory::AndroidShutdown();
|
||||||
|
|
||||||
xe::ShutdownLogging();
|
xe::ShutdownLogging();
|
||||||
|
|
||||||
xe::threading::AndroidShutdown();
|
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__;
|
android_api_level_ = __ANDROID_API__;
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t GetAndroidApiLevel() { return android_api_level_; }
|
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
|
} // namespace xe
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
* Xenia : Xbox 360 Emulator Research Project *
|
* 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. *
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
*/
|
*/
|
||||||
|
@ -10,6 +10,7 @@
|
||||||
#ifndef XENIA_BASE_MAIN_ANDROID_H_
|
#ifndef XENIA_BASE_MAIN_ANDROID_H_
|
||||||
#define XENIA_BASE_MAIN_ANDROID_H_
|
#define XENIA_BASE_MAIN_ANDROID_H_
|
||||||
|
|
||||||
|
#include <jni.h>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|
||||||
#include "xenia/base/platform.h"
|
#include "xenia/base/platform.h"
|
||||||
|
@ -27,14 +28,33 @@ namespace xe {
|
||||||
// counting internally.
|
// counting internally.
|
||||||
//
|
//
|
||||||
// In standalone console apps built with $(BUILD_EXECUTABLE), these functions
|
// In standalone console apps built with $(BUILD_EXECUTABLE), these functions
|
||||||
// must be called in `main`.
|
// must be called in `main`, with a null main thread JNI environment.
|
||||||
void InitializeAndroidAppFromMainThread(int32_t api_level);
|
void InitializeAndroidAppFromMainThread(int32_t api_level,
|
||||||
|
JNIEnv* main_thread_jni_env,
|
||||||
|
jobject application_context);
|
||||||
void ShutdownAndroidAppFromMainThread();
|
void ShutdownAndroidAppFromMainThread();
|
||||||
|
|
||||||
// May be the minimum supported level if the initialization was done without a
|
// May be the minimum supported level if the initialization was done without a
|
||||||
// configuration.
|
// configuration.
|
||||||
int32_t GetAndroidApiLevel();
|
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
|
} // namespace xe
|
||||||
|
|
||||||
#endif // XENIA_BASE_MAIN_ANDROID_H_
|
#endif // XENIA_BASE_MAIN_ANDROID_H_
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
* Xenia : Xbox 360 Emulator Research Project *
|
* 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. *
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
*/
|
*/
|
||||||
|
@ -19,37 +19,64 @@
|
||||||
// Autogenerated by `xb premake`.
|
// Autogenerated by `xb premake`.
|
||||||
#include "build/version.h"
|
#include "build/version.h"
|
||||||
|
|
||||||
// For RequestHighPerformance.
|
// For RequestWin32MMCSS.
|
||||||
|
#include <dwmapi.h>
|
||||||
|
// For RequestWin32HighResolutionTimer.
|
||||||
#include <winternl.h>
|
#include <winternl.h>
|
||||||
|
|
||||||
DEFINE_bool(win32_high_freq, true,
|
DEFINE_bool(win32_high_resolution_timer, true,
|
||||||
"Requests high performance from the NT kernel", "Kernel");
|
"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 {
|
namespace xe {
|
||||||
|
|
||||||
static void RequestHighPerformance() {
|
static void RequestWin32HighResolutionTimer() {
|
||||||
#if XE_PLATFORM_WIN32
|
HMODULE ntdll_module = GetModuleHandleW(L"ntdll.dll");
|
||||||
NTSTATUS(*NtQueryTimerResolution)
|
if (!ntdll_module) {
|
||||||
(OUT PULONG MinimumResolution, OUT PULONG MaximumResolution,
|
return;
|
||||||
OUT PULONG CurrentResolution);
|
}
|
||||||
|
|
||||||
NTSTATUS(*NtSetTimerResolution)
|
// clang-format off
|
||||||
(IN ULONG DesiredResolution, IN BOOLEAN SetResolution,
|
NTSTATUS (NTAPI* nt_query_timer_resolution)(OUT PULONG MinimumResolution,
|
||||||
OUT PULONG CurrentResolution);
|
OUT PULONG MaximumResolution,
|
||||||
|
OUT PULONG CurrentResolution);
|
||||||
NtQueryTimerResolution = (decltype(NtQueryTimerResolution))GetProcAddress(
|
NTSTATUS (NTAPI* nt_set_timer_resolution)(IN ULONG DesiredResolution,
|
||||||
GetModuleHandleW(L"ntdll.dll"), "NtQueryTimerResolution");
|
IN BOOLEAN SetResolution,
|
||||||
NtSetTimerResolution = (decltype(NtSetTimerResolution))GetProcAddress(
|
OUT PULONG CurrentResolution);
|
||||||
GetModuleHandleW(L"ntdll.dll"), "NtSetTimerResolution");
|
// clang-format on
|
||||||
if (!NtQueryTimerResolution || !NtSetTimerResolution) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ULONG minimum_resolution, maximum_resolution, current_resolution;
|
ULONG minimum_resolution, maximum_resolution, current_resolution;
|
||||||
NtQueryTimerResolution(&minimum_resolution, &maximum_resolution,
|
nt_query_timer_resolution(&minimum_resolution, &maximum_resolution,
|
||||||
¤t_resolution);
|
¤t_resolution);
|
||||||
NtSetTimerResolution(maximum_resolution, TRUE, ¤t_resolution);
|
nt_set_timer_resolution(maximum_resolution, TRUE, ¤t_resolution);
|
||||||
#endif
|
}
|
||||||
|
|
||||||
|
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(
|
bool ParseWin32LaunchArguments(
|
||||||
|
@ -103,9 +130,12 @@ int InitializeWin32App(const std::string_view app_name) {
|
||||||
#endif
|
#endif
|
||||||
XE_BUILD_BRANCH "@" XE_BUILD_COMMIT_SHORT " on " XE_BUILD_DATE);
|
XE_BUILD_BRANCH "@" XE_BUILD_COMMIT_SHORT " on " XE_BUILD_DATE);
|
||||||
|
|
||||||
// Request high performance timing.
|
// Request high-performance timing and scheduling.
|
||||||
if (cvars::win32_high_freq) {
|
if (cvars::win32_high_resolution_timer) {
|
||||||
RequestHighPerformance();
|
RequestWin32HighResolutionTimer();
|
||||||
|
}
|
||||||
|
if (cvars::win32_mmcss) {
|
||||||
|
RequestWin32MMCSS();
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
* Xenia : Xbox 360 Emulator Research Project *
|
* 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. *
|
* 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/cvar.h"
|
||||||
#include "xenia/base/platform.h"
|
#include "xenia/base/platform.h"
|
||||||
|
|
||||||
|
#if XE_ARCH_ARM64
|
||||||
|
#include <arm_neon.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
DEFINE_bool(
|
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,
|
void copy_and_swap_16_in_32_aligned(void* dest_ptr, const void* src_ptr,
|
||||||
size_t count) {
|
size_t count) {
|
||||||
auto dest = reinterpret_cast<uint64_t*>(dest_ptr);
|
auto dest = reinterpret_cast<uint32_t*>(dest_ptr);
|
||||||
auto src = reinterpret_cast<const uint64_t*>(src_ptr);
|
auto src = reinterpret_cast<const uint32_t*>(src_ptr);
|
||||||
size_t i;
|
size_t i;
|
||||||
for (i = 0; i + 4 <= count; i += 4) {
|
for (i = 0; i + 4 <= count; i += 4) {
|
||||||
__m128i input = _mm_load_si128(reinterpret_cast<const __m128i*>(&src[i]));
|
__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,
|
void copy_and_swap_16_in_32_unaligned(void* dest_ptr, const void* src_ptr,
|
||||||
size_t count) {
|
size_t count) {
|
||||||
auto dest = reinterpret_cast<uint64_t*>(dest_ptr);
|
auto dest = reinterpret_cast<uint32_t*>(dest_ptr);
|
||||||
auto src = reinterpret_cast<const uint64_t*>(src_ptr);
|
auto src = reinterpret_cast<const uint32_t*>(src_ptr);
|
||||||
size_t i;
|
size_t i;
|
||||||
for (i = 0; i + 4 <= count; i += 4) {
|
for (i = 0; i + 4 <= count; i += 4) {
|
||||||
__m128i input = _mm_loadu_si128(reinterpret_cast<const __m128i*>(&src[i]));
|
__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);
|
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
|
#else
|
||||||
|
|
||||||
// Generic routines.
|
// Generic routines.
|
||||||
void copy_and_swap_16_aligned(void* dest, const void* src, size_t count) {
|
void copy_and_swap_16_aligned(void* dest, const void* src, size_t count) {
|
||||||
return copy_and_swap_16_unaligned(dest, src, 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);
|
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) {
|
size_t count) {
|
||||||
auto dest = reinterpret_cast<uint64_t*>(dest_ptr);
|
auto dst = reinterpret_cast<uint16_t*>(dst_ptr);
|
||||||
auto src = reinterpret_cast<const uint64_t*>(src_ptr);
|
auto src = reinterpret_cast<const uint16_t*>(src_ptr);
|
||||||
for (size_t i = 0; i < count; ++i) {
|
while (count > 0) {
|
||||||
dest[i] = (src[i] >> 16) | (src[i] << 16);
|
uint16_t word0 = *src++;
|
||||||
|
uint16_t word1 = *src++;
|
||||||
|
*dst++ = word1;
|
||||||
|
*dst++ = word0;
|
||||||
|
|
||||||
|
count--;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
} // namespace xe
|
} // namespace xe
|
||||||
|
|
|
@ -2,17 +2,20 @@
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
* Xenia : Xbox 360 Emulator Research Project *
|
* 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. *
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
// NOTE: this must be included before microprofile as macro expansion needs
|
// NOTE: this must be included before microprofile as macro expansion needs
|
||||||
// XELOGI.
|
// XELOGI.
|
||||||
#include "xenia/base/logging.h"
|
#include "xenia/base/logging.h"
|
||||||
|
|
||||||
|
#include "third_party/fmt/include/fmt/printf.h"
|
||||||
|
|
||||||
// NOTE: microprofile must be setup first, before profiling.h is included.
|
// NOTE: microprofile must be setup first, before profiling.h is included.
|
||||||
#define MICROPROFILE_ENABLED 1
|
#define MICROPROFILE_ENABLED 1
|
||||||
#define MICROPROFILEUI_ENABLED 1
|
#define MICROPROFILEUI_ENABLED 1
|
||||||
|
@ -21,7 +24,11 @@
|
||||||
#define MICROPROFILE_PER_THREAD_BUFFER_SIZE (1024 * 1024 * 10)
|
#define MICROPROFILE_PER_THREAD_BUFFER_SIZE (1024 * 1024 * 10)
|
||||||
#define MICROPROFILE_USE_THREAD_NAME_CALLBACK 1
|
#define MICROPROFILE_USE_THREAD_NAME_CALLBACK 1
|
||||||
#define MICROPROFILE_WEBSERVER_MAXFRAMES 3
|
#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_WEBSERVER 0
|
||||||
#define MICROPROFILE_DEBUG 0
|
#define MICROPROFILE_DEBUG 0
|
||||||
#define MICROPROFILE_MAX_THREADS 128
|
#define MICROPROFILE_MAX_THREADS 128
|
||||||
|
@ -30,6 +37,7 @@
|
||||||
#include "xenia/base/assert.h"
|
#include "xenia/base/assert.h"
|
||||||
#include "xenia/base/cvar.h"
|
#include "xenia/base/cvar.h"
|
||||||
#include "xenia/base/profiling.h"
|
#include "xenia/base/profiling.h"
|
||||||
|
#include "xenia/ui/ui_event.h"
|
||||||
#include "xenia/ui/virtual_key.h"
|
#include "xenia/ui/virtual_key.h"
|
||||||
#include "xenia/ui/window.h"
|
#include "xenia/ui/window.h"
|
||||||
|
|
||||||
|
@ -38,21 +46,27 @@
|
||||||
#endif // XE_OPTION_PROFILING
|
#endif // XE_OPTION_PROFILING
|
||||||
|
|
||||||
#if XE_OPTION_PROFILING_UI
|
#if XE_OPTION_PROFILING_UI
|
||||||
#undef DrawText
|
|
||||||
#include "xenia/ui/microprofile_drawer.h"
|
#include "xenia/ui/microprofile_drawer.h"
|
||||||
#endif // XE_OPTION_PROFILING_UI
|
#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");
|
DEFINE_bool(show_profiler, false, "Show profiling UI by default.", "UI");
|
||||||
|
|
||||||
namespace xe {
|
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
|
#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_enabled() { return true; }
|
||||||
|
|
||||||
bool Profiler::is_visible() { return is_enabled() && MicroProfileIsDrawing(); }
|
bool Profiler::is_visible() { return is_enabled() && MicroProfileIsDrawing(); }
|
||||||
|
@ -73,6 +87,7 @@ void Profiler::Initialize() {
|
||||||
g_MicroProfile.nActiveBars |= 0x1 | 0x2;
|
g_MicroProfile.nActiveBars |= 0x1 | 0x2;
|
||||||
|
|
||||||
#if XE_OPTION_PROFILING_UI
|
#if XE_OPTION_PROFILING_UI
|
||||||
|
dpi_scaling_ = cvars::profiler_dpi_scaling;
|
||||||
MicroProfileInitUI();
|
MicroProfileInitUI();
|
||||||
g_MicroProfileUI.bShowSpikes = true;
|
g_MicroProfileUI.bShowSpikes = true;
|
||||||
g_MicroProfileUI.nOpacityBackground = 0x40u << 24;
|
g_MicroProfileUI.nOpacityBackground = 0x40u << 24;
|
||||||
|
@ -96,7 +111,7 @@ void Profiler::Dump() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Profiler::Shutdown() {
|
void Profiler::Shutdown() {
|
||||||
drawer_.reset();
|
SetUserIO(0, nullptr, nullptr, nullptr);
|
||||||
window_ = nullptr;
|
window_ = nullptr;
|
||||||
MicroProfileShutdown();
|
MicroProfileShutdown();
|
||||||
}
|
}
|
||||||
|
@ -113,144 +128,208 @@ void Profiler::ThreadEnter(const char* name) {
|
||||||
|
|
||||||
void Profiler::ThreadExit() { MicroProfileOnThreadExit(); }
|
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
|
// 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: // `
|
case ui::VirtualKey::kOem3: // `
|
||||||
MicroProfileTogglePause();
|
MicroProfileTogglePause();
|
||||||
return true;
|
break;
|
||||||
#if XE_OPTION_PROFILING_UI
|
#if XE_OPTION_PROFILING_UI
|
||||||
case ui::VirtualKey::kTab:
|
case ui::VirtualKey::kTab:
|
||||||
MicroProfileToggleDisplayMode();
|
ToggleDisplay();
|
||||||
return true;
|
break;
|
||||||
case ui::VirtualKey::k1:
|
case ui::VirtualKey::k1:
|
||||||
MicroProfileModKey(1);
|
MicroProfileModKey(1);
|
||||||
return true;
|
break;
|
||||||
#endif // XE_OPTION_PROFILING_UI
|
#endif // XE_OPTION_PROFILING_UI
|
||||||
default:
|
default:
|
||||||
|
handled = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return false;
|
if (handled) {
|
||||||
|
e.set_handled(true);
|
||||||
|
}
|
||||||
|
PostInputEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Profiler::OnKeyUp(ui::VirtualKey virtual_key) {
|
void Profiler::ProfilerWindowInputListener::OnKeyUp(ui::KeyEvent& e) {
|
||||||
switch (virtual_key) {
|
bool handled = true;
|
||||||
|
switch (e.virtual_key()) {
|
||||||
#if XE_OPTION_PROFILING_UI
|
#if XE_OPTION_PROFILING_UI
|
||||||
case ui::VirtualKey::k1:
|
case ui::VirtualKey::k1:
|
||||||
MicroProfileModKey(0);
|
MicroProfileModKey(0);
|
||||||
return true;
|
break;
|
||||||
#endif // XE_OPTION_PROFILING_UI
|
#endif // XE_OPTION_PROFILING_UI
|
||||||
default:
|
default:
|
||||||
|
handled = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return false;
|
if (handled) {
|
||||||
|
e.set_handled(true);
|
||||||
|
}
|
||||||
|
PostInputEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
#if XE_OPTION_PROFILING_UI
|
#if XE_OPTION_PROFILING_UI
|
||||||
|
|
||||||
void Profiler::OnMouseDown(bool left_button, bool right_button) {
|
void Profiler::ProfilerWindowInputListener::OnMouseDown(ui::MouseEvent& e) {
|
||||||
MicroProfileMouseButton(left_button, right_button);
|
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::ProfilerWindowInputListener::OnMouseUp(ui::MouseEvent& e) {
|
||||||
|
Profiler::SetMousePosition(e.x(), e.y(), 0);
|
||||||
void Profiler::OnMouseMove(int x, int y) { MicroProfileMousePosition(x, y, 0); }
|
MicroProfileMouseButton(0, 0);
|
||||||
|
e.set_handled(true);
|
||||||
void Profiler::OnMouseWheel(int x, int y, int dy) {
|
PostInputEvent();
|
||||||
MicroProfileMousePosition(x, y, dy);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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(); }
|
void Profiler::TogglePause() { MicroProfileTogglePause(); }
|
||||||
|
|
||||||
#else
|
#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() {}
|
void Profiler::TogglePause() {}
|
||||||
|
|
||||||
#endif // XE_OPTION_PROFILING_UI
|
#endif // XE_OPTION_PROFILING_UI
|
||||||
|
|
||||||
void Profiler::set_window(ui::Window* window) {
|
void Profiler::ToggleDisplay() {
|
||||||
assert_null(window_);
|
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) {
|
if (!window) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
z_order_ = z_order;
|
||||||
window_ = window;
|
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.
|
if (is_visible()) {
|
||||||
window_->on_mouse_down.AddListener([](ui::MouseEvent* e) {
|
window_->AddInputListener(&input_listener_, z_order_);
|
||||||
if (Profiler::is_visible()) {
|
#if XE_OPTION_PROFILING_UI
|
||||||
Profiler::OnMouseDown(e->button() == ui::MouseEvent::Button::kLeft,
|
if (presenter_) {
|
||||||
e->button() == ui::MouseEvent::Button::kRight);
|
presenter_->AddUIDrawerFromUIThread(&ui_drawer_, z_order_);
|
||||||
e->set_handled(true);
|
|
||||||
window_->Invalidate();
|
|
||||||
}
|
}
|
||||||
});
|
#endif // XE_OPTION_PROFILING_UI
|
||||||
window_->on_mouse_up.AddListener([](ui::MouseEvent* e) {
|
}
|
||||||
if (Profiler::is_visible()) {
|
}
|
||||||
Profiler::OnMouseUp();
|
|
||||||
e->set_handled(true);
|
void Profiler::Flip() {
|
||||||
window_->Invalidate();
|
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
|
||||||
window_->on_mouse_move.AddListener([](ui::MouseEvent* e) {
|
// thread. Relying on continuous painting currently.
|
||||||
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();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Profiler::Present() {
|
|
||||||
SCOPE_profile_cpu_f("internal");
|
|
||||||
#if XE_OPTION_PROFILING_UI
|
#if XE_OPTION_PROFILING_UI
|
||||||
if (!window_ || !drawer_) {
|
void Profiler::ProfilerUIDrawer::Draw(ui::UIDrawContext& ui_draw_context) {
|
||||||
|
if (!window_ || !presenter_ || !drawer_) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
drawer_->Begin();
|
SCOPE_profile_cpu_f("internal");
|
||||||
MicroProfileDraw(window_->scaled_width(), window_->scaled_height());
|
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();
|
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
|
#else
|
||||||
|
|
||||||
|
@ -262,16 +341,11 @@ void Profiler::Shutdown() {}
|
||||||
uint32_t Profiler::GetColor(const char* str) { return 0; }
|
uint32_t Profiler::GetColor(const char* str) { return 0; }
|
||||||
void Profiler::ThreadEnter(const char* name) {}
|
void Profiler::ThreadEnter(const char* name) {}
|
||||||
void Profiler::ThreadExit() {}
|
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::ToggleDisplay() {}
|
||||||
void Profiler::TogglePause() {}
|
void Profiler::TogglePause() {}
|
||||||
void Profiler::set_window(ui::Window* window) {}
|
void Profiler::SetUserIO(size_t z_order, ui::Window* window,
|
||||||
void Profiler::Present() {}
|
ui::Presenter* presenter,
|
||||||
|
ui::ImmediateDrawer* immediate_drawer) {}
|
||||||
void Profiler::Flip() {}
|
void Profiler::Flip() {}
|
||||||
|
|
||||||
#endif // XE_OPTION_PROFILING
|
#endif // XE_OPTION_PROFILING
|
||||||
|
@ -310,7 +384,7 @@ void MicroProfileDrawText(int nX, int nY, uint32_t nColor, const char* pText,
|
||||||
if (!drawer) {
|
if (!drawer) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
drawer->DrawText(nX, nY, nColor, pText, nLen);
|
drawer->DrawTextString(nX, nY, nColor, pText, nLen);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // XE_OPTION_PROFILING_UI
|
#endif // XE_OPTION_PROFILING_UI
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
* Xenia : Xbox 360 Emulator Research Project *
|
* 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. *
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
*/
|
*/
|
||||||
|
@ -10,10 +10,15 @@
|
||||||
#ifndef XENIA_BASE_PROFILING_H_
|
#ifndef XENIA_BASE_PROFILING_H_
|
||||||
#define XENIA_BASE_PROFILING_H_
|
#define XENIA_BASE_PROFILING_H_
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
|
#include "xenia/base/platform.h"
|
||||||
#include "xenia/base/string.h"
|
#include "xenia/base/string.h"
|
||||||
|
#include "xenia/ui/ui_drawer.h"
|
||||||
#include "xenia/ui/virtual_key.h"
|
#include "xenia/ui/virtual_key.h"
|
||||||
|
#include "xenia/ui/window_listener.h"
|
||||||
|
|
||||||
#if XE_PLATFORM_WIN32
|
#if XE_PLATFORM_WIN32
|
||||||
#define XE_OPTION_PROFILING 1
|
#define XE_OPTION_PROFILING 1
|
||||||
|
@ -30,7 +35,9 @@
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
namespace ui {
|
namespace ui {
|
||||||
|
class ImmediateDrawer;
|
||||||
class MicroprofileDrawer;
|
class MicroprofileDrawer;
|
||||||
|
class Presenter;
|
||||||
class Window;
|
class Window;
|
||||||
} // namespace ui
|
} // namespace ui
|
||||||
} // namespace xe
|
} // namespace xe
|
||||||
|
@ -172,27 +179,65 @@ class Profiler {
|
||||||
// Deactivates the calling thread for profiling.
|
// Deactivates the calling thread for profiling.
|
||||||
static void ThreadExit();
|
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 ToggleDisplay();
|
||||||
static void TogglePause();
|
static void TogglePause();
|
||||||
|
|
||||||
// Initializes input and drawing with the given display.
|
// Initializes input for the given window and drawing for the given presenter
|
||||||
static void set_window(ui::Window* window);
|
// and immediate drawer.
|
||||||
// Gets the current display, if any.
|
static void SetUserIO(size_t z_order, ui::Window* window,
|
||||||
static ui::MicroprofileDrawer* drawer() { return drawer_.get(); }
|
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.
|
// 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
|
// Starts a new frame on the profiler
|
||||||
static void Flip();
|
static void Flip();
|
||||||
|
|
||||||
private:
|
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_;
|
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 std::unique_ptr<ui::MicroprofileDrawer> drawer_;
|
||||||
|
static bool dpi_scaling_;
|
||||||
|
#endif // XE_OPTION_PROFILING_UI
|
||||||
|
#endif // XE_OPTION_PROFILING
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace xe
|
} // namespace xe
|
||||||
|
|
|
@ -13,12 +13,15 @@
|
||||||
#include <cstdarg>
|
#include <cstdarg>
|
||||||
|
|
||||||
#include "xenia/base/assert.h"
|
#include "xenia/base/assert.h"
|
||||||
|
#include "xenia/base/literals.h"
|
||||||
#include "xenia/base/math.h"
|
#include "xenia/base/math.h"
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
|
|
||||||
|
using namespace xe::literals;
|
||||||
|
|
||||||
StringBuffer::StringBuffer(size_t initial_capacity) {
|
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_));
|
buffer_ = reinterpret_cast<char*>(std::malloc(buffer_capacity_));
|
||||||
assert_not_null(buffer_);
|
assert_not_null(buffer_);
|
||||||
buffer_[0] = 0;
|
buffer_[0] = 0;
|
||||||
|
@ -40,7 +43,7 @@ void StringBuffer::Grow(size_t additional_length) {
|
||||||
}
|
}
|
||||||
size_t old_capacity = buffer_capacity_;
|
size_t old_capacity = buffer_capacity_;
|
||||||
size_t new_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);
|
old_capacity * 2);
|
||||||
auto new_buffer = std::realloc(buffer_, new_capacity);
|
auto new_buffer = std::realloc(buffer_, new_capacity);
|
||||||
assert_not_null(new_buffer);
|
assert_not_null(new_buffer);
|
||||||
|
|
|
@ -134,7 +134,7 @@ inline std::string to_hex_string(double value) {
|
||||||
}
|
}
|
||||||
|
|
||||||
inline std::string to_hex_string(const vec128_t& 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]);
|
value.u32[1], value.u32[2], value.u32[3]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,10 +13,17 @@
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
|
||||||
|
#include "xenia/base/platform.h"
|
||||||
#include "xenia/base/string.h"
|
#include "xenia/base/string.h"
|
||||||
|
|
||||||
namespace xe {
|
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 LaunchWebBrowser(const std::string_view url);
|
||||||
void LaunchFileExplorer(const std::filesystem::path& path);
|
void LaunchFileExplorer(const std::filesystem::path& path);
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
@ -2,7 +2,7 @@
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
* Xenia : Xbox 360 Emulator Research Project *
|
* 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. *
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
*/
|
*/
|
||||||
|
@ -14,6 +14,8 @@
|
||||||
|
|
||||||
#include "xenia/base/clock.h"
|
#include "xenia/base/clock.h"
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
namespace base {
|
namespace base {
|
||||||
namespace test {
|
namespace test {
|
||||||
|
@ -113,6 +115,24 @@ TEST_CASE("copy_and_swap_16_unaligned", "[copy_and_swap]") {
|
||||||
REQUIRE(c[2] == 0xAB89);
|
REQUIRE(c[2] == 0xAB89);
|
||||||
REQUIRE(c[3] == 0xEFCD);
|
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;
|
uint64_t e;
|
||||||
copy_and_swap_16_unaligned(&e, d, 4);
|
copy_and_swap_16_unaligned(&e, d, 4);
|
||||||
REQUIRE(e == 0xEFCDAB8967452301);
|
REQUIRE(e == 0xEFCDAB8967452301);
|
||||||
|
@ -221,6 +241,32 @@ TEST_CASE("copy_and_swap_32_unaligned", "[copy_and_swap]") {
|
||||||
REQUIRE(c[2] == 0xEDEE87E8);
|
REQUIRE(c[2] == 0xEDEE87E8);
|
||||||
REQUIRE(c[3] == 0x994151D8);
|
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;
|
uint64_t e;
|
||||||
copy_and_swap_32_unaligned(&e, d, 2);
|
copy_and_swap_32_unaligned(&e, d, 2);
|
||||||
REQUIRE(e == 0xEFCDAB8967452301);
|
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]") {
|
TEST_CASE("copy_and_swap_16_in_32_aligned", "[copy_and_swap]") {
|
||||||
// TODO(bwrsandman): test once properly understood.
|
constexpr size_t count = 17;
|
||||||
REQUIRE(true == true);
|
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]") {
|
TEST_CASE("copy_and_swap_16_in_32_unaligned", "[copy_and_swap]") {
|
||||||
// TODO(bwrsandman): test once properly understood.
|
constexpr size_t count = 17;
|
||||||
REQUIRE(true == true);
|
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") {
|
TEST_CASE("create_and_close_file_mapping", "Virtual Memory Mapping") {
|
||||||
|
|
|
@ -824,7 +824,7 @@ TEST_CASE("Create and Run Thread", "[thread]") {
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("16kb stack size") {
|
SECTION("16kb stack size") {
|
||||||
params.stack_size = 16 * 1024 * 1024;
|
params.stack_size = 16_MiB;
|
||||||
thread = Thread::Create(params, [] {
|
thread = Thread::Create(params, [] {
|
||||||
Thread::Exit(-1);
|
Thread::Exit(-1);
|
||||||
FAIL("Function must not return");
|
FAIL("Function must not return");
|
||||||
|
|
|
@ -25,11 +25,14 @@
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "xenia/base/assert.h"
|
#include "xenia/base/assert.h"
|
||||||
|
#include "xenia/base/literals.h"
|
||||||
#include "xenia/base/platform.h"
|
#include "xenia/base/platform.h"
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
namespace threading {
|
namespace threading {
|
||||||
|
|
||||||
|
using namespace xe::literals;
|
||||||
|
|
||||||
#if XE_PLATFORM_ANDROID
|
#if XE_PLATFORM_ANDROID
|
||||||
void AndroidInitialize();
|
void AndroidInitialize();
|
||||||
void AndroidShutdown();
|
void AndroidShutdown();
|
||||||
|
@ -368,7 +371,7 @@ struct ThreadPriority {
|
||||||
class Thread : public WaitHandle {
|
class Thread : public WaitHandle {
|
||||||
public:
|
public:
|
||||||
struct CreationParameters {
|
struct CreationParameters {
|
||||||
size_t stack_size = 4 * 1024 * 1024;
|
size_t stack_size = 4_MiB;
|
||||||
bool create_suspended = false;
|
bool create_suspended = false;
|
||||||
int32_t initial_priority = 0;
|
int32_t initial_priority = 0;
|
||||||
};
|
};
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
#include "xenia/base/platform.h"
|
#include "xenia/base/platform.h"
|
||||||
|
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
|
#include <sched.h>
|
||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
#include <sys/eventfd.h>
|
#include <sys/eventfd.h>
|
||||||
#include <sys/syscall.h>
|
#include <sys/syscall.h>
|
||||||
|
@ -28,7 +29,6 @@
|
||||||
|
|
||||||
#if XE_PLATFORM_ANDROID
|
#if XE_PLATFORM_ANDROID
|
||||||
#include <dlfcn.h>
|
#include <dlfcn.h>
|
||||||
#include <sched.h>
|
|
||||||
|
|
||||||
#include "xenia/base/main_android.h"
|
#include "xenia/base/main_android.h"
|
||||||
#include "xenia/base/string_util.h"
|
#include "xenia/base/string_util.h"
|
||||||
|
@ -128,11 +128,7 @@ uint32_t current_thread_system_id() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void MaybeYield() {
|
void MaybeYield() {
|
||||||
#if XE_PLATFORM_ANDROID
|
|
||||||
sched_yield();
|
sched_yield();
|
||||||
#else
|
|
||||||
pthread_yield();
|
|
||||||
#endif
|
|
||||||
__sync_synchronize();
|
__sync_synchronize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,11 +8,13 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
|
#include <ostream>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include "third_party/fmt/include/fmt/format.h"
|
#include "third_party/fmt/include/fmt/format.h"
|
||||||
#include "xenia/base/math.h"
|
#include "xenia/base/math.h"
|
||||||
#include "xenia/base/platform.h"
|
#include "xenia/base/platform.h"
|
||||||
|
#include "xenia/base/string_util.h"
|
||||||
#include "xenia/base/vec128.h"
|
#include "xenia/base/vec128.h"
|
||||||
|
|
||||||
namespace xe {
|
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);
|
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
|
} // namespace xe
|
||||||
|
|
|
@ -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::string to_string(const vec128_t& value);
|
||||||
|
|
||||||
|
std::ostream& operator<<(std::ostream& os, const vec128_t& value);
|
||||||
|
|
||||||
} // namespace xe
|
} // namespace xe
|
||||||
|
|
||||||
#endif // XENIA_BASE_VEC128_H_
|
#endif // XENIA_BASE_VEC128_H_
|
||||||
|
|
|
@ -105,6 +105,10 @@ void ReadGameConfig(const std::filesystem::path& file_path) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void SaveConfig() {
|
void SaveConfig() {
|
||||||
|
if (config_path.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// All cvar defaults have been updated on loading - store the current date.
|
// All cvar defaults have been updated on loading - store the current date.
|
||||||
auto defaults_date_cvar =
|
auto defaults_date_cvar =
|
||||||
dynamic_cast<cvar::ConfigVar<uint32_t>*>(cv::cv_defaults_date);
|
dynamic_cast<cvar::ConfigVar<uint32_t>*>(cv::cv_defaults_date);
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
namespace config {
|
namespace config {
|
||||||
void SetupConfig(const std::filesystem::path& config_folder);
|
void SetupConfig(const std::filesystem::path& config_folder);
|
||||||
void LoadGameConfig(const std::string_view title_id);
|
void LoadGameConfig(const std::string_view title_id);
|
||||||
|
void SaveConfig();
|
||||||
} // namespace config
|
} // namespace config
|
||||||
|
|
||||||
#endif // XENIA_CONFIG_H_
|
#endif // XENIA_CONFIG_H_
|
||||||
|
|
|
@ -26,10 +26,24 @@
|
||||||
#include "xenia/cpu/processor.h"
|
#include "xenia/cpu/processor.h"
|
||||||
#include "xenia/cpu/stack_walker.h"
|
#include "xenia/cpu/stack_walker.h"
|
||||||
|
|
||||||
DEFINE_bool(
|
DEFINE_int32(x64_extension_mask, -1,
|
||||||
use_haswell_instructions, true,
|
"Allow the detection and utilization of specific instruction set "
|
||||||
"Uses the AVX2/FMA/etc instructions on Haswell processors when available.",
|
"features.\n"
|
||||||
"CPU");
|
" 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 xe {
|
||||||
namespace cpu {
|
namespace cpu {
|
||||||
|
@ -84,7 +98,7 @@ bool X64Backend::Initialize(Processor* processor) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Need movbe to do advanced LOAD/STORE tricks.
|
// 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 =
|
machine_info_.supports_extended_load_store =
|
||||||
cpu.has(Xbyak::util::Cpu::tMOVBE);
|
cpu.has(Xbyak::util::Cpu::tMOVBE);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
#include "xenia/base/cvar.h"
|
#include "xenia/base/cvar.h"
|
||||||
#include "xenia/cpu/backend/backend.h"
|
#include "xenia/cpu/backend/backend.h"
|
||||||
|
|
||||||
DECLARE_bool(use_haswell_instructions);
|
DECLARE_int32(x64_extension_mask);
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
class Exception;
|
class Exception;
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
#include "third_party/fmt/include/fmt/format.h"
|
#include "third_party/fmt/include/fmt/format.h"
|
||||||
#include "xenia/base/assert.h"
|
#include "xenia/base/assert.h"
|
||||||
#include "xenia/base/clock.h"
|
#include "xenia/base/clock.h"
|
||||||
|
#include "xenia/base/literals.h"
|
||||||
#include "xenia/base/logging.h"
|
#include "xenia/base/logging.h"
|
||||||
#include "xenia/base/math.h"
|
#include "xenia/base/math.h"
|
||||||
#include "xenia/base/memory.h"
|
#include "xenia/base/memory.h"
|
||||||
|
@ -31,6 +32,8 @@ namespace cpu {
|
||||||
namespace backend {
|
namespace backend {
|
||||||
namespace x64 {
|
namespace x64 {
|
||||||
|
|
||||||
|
using namespace xe::literals;
|
||||||
|
|
||||||
X64CodeCache::X64CodeCache() = default;
|
X64CodeCache::X64CodeCache() = default;
|
||||||
|
|
||||||
X64CodeCache::~X64CodeCache() {
|
X64CodeCache::~X64CodeCache() {
|
||||||
|
@ -227,7 +230,7 @@ void X64CodeCache::PlaceGuestCode(uint32_t guest_address, void* machine_code,
|
||||||
old_commit_mark = generated_code_commit_mark_;
|
old_commit_mark = generated_code_commit_mark_;
|
||||||
if (high_mark <= old_commit_mark) break;
|
if (high_mark <= old_commit_mark) break;
|
||||||
|
|
||||||
new_commit_mark = old_commit_mark + 16 * 1024 * 1024;
|
new_commit_mark = old_commit_mark + 16_MiB;
|
||||||
if (generated_code_execute_base_ == generated_code_write_base_) {
|
if (generated_code_execute_base_ == generated_code_write_base_) {
|
||||||
xe::memory::AllocFixed(generated_code_execute_base_, new_commit_mark,
|
xe::memory::AllocFixed(generated_code_execute_base_, new_commit_mark,
|
||||||
xe::memory::AllocationType::kCommit,
|
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_;
|
old_commit_mark = generated_code_commit_mark_;
|
||||||
if (high_mark <= old_commit_mark) break;
|
if (high_mark <= old_commit_mark) break;
|
||||||
|
|
||||||
new_commit_mark = old_commit_mark + 16 * 1024 * 1024;
|
new_commit_mark = old_commit_mark + 16_MiB;
|
||||||
if (generated_code_execute_base_ == generated_code_write_base_) {
|
if (generated_code_execute_base_ == generated_code_write_base_) {
|
||||||
xe::memory::AllocFixed(generated_code_execute_base_, new_commit_mark,
|
xe::memory::AllocFixed(generated_code_execute_base_, new_commit_mark,
|
||||||
xe::memory::AllocationType::kCommit,
|
xe::memory::AllocationType::kCommit,
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
#include "xenia/base/assert.h"
|
#include "xenia/base/assert.h"
|
||||||
#include "xenia/base/atomic.h"
|
#include "xenia/base/atomic.h"
|
||||||
#include "xenia/base/debugging.h"
|
#include "xenia/base/debugging.h"
|
||||||
|
#include "xenia/base/literals.h"
|
||||||
#include "xenia/base/logging.h"
|
#include "xenia/base/logging.h"
|
||||||
#include "xenia/base/math.h"
|
#include "xenia/base/math.h"
|
||||||
#include "xenia/base/memory.h"
|
#include "xenia/base/memory.h"
|
||||||
|
@ -50,8 +51,9 @@ namespace x64 {
|
||||||
|
|
||||||
using xe::cpu::hir::HIRBuilder;
|
using xe::cpu::hir::HIRBuilder;
|
||||||
using xe::cpu::hir::Instr;
|
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 kStashOffset = 32;
|
||||||
// static const size_t kStashOffsetHigh = 32 + 32;
|
// static const size_t kStashOffsetHigh = 32 + 32;
|
||||||
|
@ -72,21 +74,31 @@ X64Emitter::X64Emitter(X64Backend* backend, XbyakAllocator* allocator)
|
||||||
backend_(backend),
|
backend_(backend),
|
||||||
code_cache_(backend->code_cache()),
|
code_cache_(backend->code_cache()),
|
||||||
allocator_(allocator) {
|
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)) {
|
if (!cpu_.has(Xbyak::util::Cpu::tAVX)) {
|
||||||
xe::FatalError(
|
xe::FatalError(
|
||||||
"Your CPU does not support AVX, which is required by Xenia. See the "
|
"Your CPU does not support AVX, which is required by Xenia. See the "
|
||||||
"FAQ for system requirements at https://xenia.jp");
|
"FAQ for system requirements at https://xenia.jp");
|
||||||
return;
|
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;
|
X64Emitter::~X64Emitter() = default;
|
||||||
|
|
|
@ -125,12 +125,23 @@ class XbyakAllocator : public Xbyak::Allocator {
|
||||||
};
|
};
|
||||||
|
|
||||||
enum X64EmitterFeatureFlags {
|
enum X64EmitterFeatureFlags {
|
||||||
kX64EmitAVX2 = 1 << 1,
|
kX64EmitAVX2 = 1 << 0,
|
||||||
kX64EmitFMA = 1 << 2,
|
kX64EmitFMA = 1 << 1,
|
||||||
kX64EmitLZCNT = 1 << 3,
|
kX64EmitLZCNT = 1 << 2,
|
||||||
|
kX64EmitBMI1 = 1 << 3,
|
||||||
kX64EmitBMI2 = 1 << 4,
|
kX64EmitBMI2 = 1 << 4,
|
||||||
kX64EmitF16C = 1 << 5,
|
kX64EmitF16C = 1 << 5,
|
||||||
kX64EmitMovbe = 1 << 6,
|
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 {
|
class X64Emitter : public Xbyak::CodeGenerator {
|
||||||
|
@ -221,7 +232,7 @@ class X64Emitter : public Xbyak::CodeGenerator {
|
||||||
Xbyak::Address StashConstantXmm(int index, const vec128_t& v);
|
Xbyak::Address StashConstantXmm(int index, const vec128_t& v);
|
||||||
|
|
||||||
bool IsFeatureEnabled(uint32_t feature_flag) const {
|
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_; }
|
FunctionDebugInfo* debug_info() const { return debug_info_; }
|
||||||
|
|
|
@ -731,6 +731,25 @@ struct VECTOR_SHL_V128
|
||||||
static void EmitInt8(X64Emitter& e, const EmitArgType& i) {
|
static void EmitInt8(X64Emitter& e, const EmitArgType& i) {
|
||||||
// TODO(benvanik): native version (with shift magic).
|
// TODO(benvanik): native version (with shift magic).
|
||||||
if (i.src2.is_constant) {
|
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()));
|
e.lea(e.GetNativeParam(1), e.StashConstantXmm(1, i.src2.constant()));
|
||||||
} else {
|
} else {
|
||||||
e.lea(e.GetNativeParam(1), e.StashXmm(1, i.src2));
|
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) {
|
static void EmitInt8(X64Emitter& e, const EmitArgType& i) {
|
||||||
// TODO(benvanik): native version (with shift magic).
|
// TODO(benvanik): native version (with shift magic).
|
||||||
if (i.src2.is_constant) {
|
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()));
|
e.lea(e.GetNativeParam(1), e.StashConstantXmm(1, i.src2.constant()));
|
||||||
} else {
|
} else {
|
||||||
e.lea(e.GetNativeParam(1), e.StashXmm(1, i.src2));
|
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) {
|
static void EmitInt8(X64Emitter& e, const EmitArgType& i) {
|
||||||
// TODO(benvanik): native version (with shift magic).
|
// TODO(benvanik): native version (with shift magic).
|
||||||
if (i.src2.is_constant) {
|
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()));
|
e.lea(e.GetNativeParam(1), e.StashConstantXmm(1, i.src2.constant()));
|
||||||
} else {
|
} else {
|
||||||
e.lea(e.GetNativeParam(1), e.StashXmm(1, i.src2));
|
e.lea(e.GetNativeParam(1), e.StashXmm(1, i.src2));
|
||||||
|
|
|
@ -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);
|
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
|
// OPCODE_OR
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
|
@ -38,6 +38,13 @@ DEFINE_bool(
|
||||||
DEFINE_bool(validate_hir, false,
|
DEFINE_bool(validate_hir, false,
|
||||||
"Perform validation checks on the HIR during compilation.", "CPU");
|
"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:
|
// Breakpoints:
|
||||||
DEFINE_uint64(break_on_instruction, 0,
|
DEFINE_uint64(break_on_instruction, 0,
|
||||||
"int3 before the given guest address is executed.", "CPU");
|
"int3 before the given guest address is executed.", "CPU");
|
||||||
|
|
|
@ -26,6 +26,9 @@ DECLARE_bool(disable_global_lock);
|
||||||
|
|
||||||
DECLARE_bool(validate_hir);
|
DECLARE_bool(validate_hir);
|
||||||
|
|
||||||
|
DECLARE_uint64(pvr);
|
||||||
|
|
||||||
|
// Breakpoints:
|
||||||
DECLARE_uint64(break_on_instruction);
|
DECLARE_uint64(break_on_instruction);
|
||||||
DECLARE_int32(break_condition_gpr);
|
DECLARE_int32(break_condition_gpr);
|
||||||
DECLARE_uint64(break_condition_value);
|
DECLARE_uint64(break_condition_value);
|
||||||
|
|
|
@ -1759,6 +1759,26 @@ Value* HIRBuilder::And(Value* value1, Value* value2) {
|
||||||
return i->dest;
|
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) {
|
Value* HIRBuilder::Or(Value* value1, Value* value2) {
|
||||||
ASSERT_NON_FLOAT_TYPE(value1);
|
ASSERT_NON_FLOAT_TYPE(value1);
|
||||||
ASSERT_NON_FLOAT_TYPE(value2);
|
ASSERT_NON_FLOAT_TYPE(value2);
|
||||||
|
|
|
@ -224,6 +224,7 @@ class HIRBuilder {
|
||||||
Value* DotProduct4(Value* value1, Value* value2);
|
Value* DotProduct4(Value* value1, Value* value2);
|
||||||
|
|
||||||
Value* And(Value* value1, Value* value2);
|
Value* And(Value* value1, Value* value2);
|
||||||
|
Value* AndNot(Value* value1, Value* value2);
|
||||||
Value* Or(Value* value1, Value* value2);
|
Value* Or(Value* value1, Value* value2);
|
||||||
Value* Xor(Value* value1, Value* value2);
|
Value* Xor(Value* value1, Value* value2);
|
||||||
Value* Not(Value* value);
|
Value* Not(Value* value);
|
||||||
|
|
|
@ -255,6 +255,7 @@ enum Opcode {
|
||||||
OPCODE_DOT_PRODUCT_3,
|
OPCODE_DOT_PRODUCT_3,
|
||||||
OPCODE_DOT_PRODUCT_4,
|
OPCODE_DOT_PRODUCT_4,
|
||||||
OPCODE_AND,
|
OPCODE_AND,
|
||||||
|
OPCODE_AND_NOT,
|
||||||
OPCODE_OR,
|
OPCODE_OR,
|
||||||
OPCODE_XOR,
|
OPCODE_XOR,
|
||||||
OPCODE_NOT,
|
OPCODE_NOT,
|
||||||
|
|
|
@ -524,6 +524,12 @@ DEFINE_OPCODE(
|
||||||
OPCODE_SIG_V_V_V,
|
OPCODE_SIG_V_V_V,
|
||||||
OPCODE_FLAG_COMMUNATIVE)
|
OPCODE_FLAG_COMMUNATIVE)
|
||||||
|
|
||||||
|
DEFINE_OPCODE(
|
||||||
|
OPCODE_AND_NOT,
|
||||||
|
"and_not",
|
||||||
|
OPCODE_SIG_V_V_V,
|
||||||
|
0)
|
||||||
|
|
||||||
DEFINE_OPCODE(
|
DEFINE_OPCODE(
|
||||||
OPCODE_OR,
|
OPCODE_OR,
|
||||||
"or",
|
"or",
|
||||||
|
|
|
@ -286,7 +286,7 @@ int InstrEmit_stvlx_(PPCHIRBuilder& f, const InstrData& i, uint32_t vd,
|
||||||
// mask = FFFF... >> eb
|
// mask = FFFF... >> eb
|
||||||
Value* mask = f.Permute(f.LoadVectorShr(eb), f.LoadZeroVec128(),
|
Value* mask = f.Permute(f.LoadVectorShr(eb), f.LoadZeroVec128(),
|
||||||
f.Not(f.LoadZeroVec128()), INT8_TYPE);
|
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)
|
// ea &= ~0xF (handled above)
|
||||||
f.Store(ea, f.ByteSwap(v));
|
f.Store(ea, f.ByteSwap(v));
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -328,7 +328,7 @@ int InstrEmit_stvrx_(PPCHIRBuilder& f, const InstrData& i, uint32_t vd,
|
||||||
// mask = ~FFFF... >> eb
|
// mask = ~FFFF... >> eb
|
||||||
Value* mask = f.Permute(f.LoadVectorShr(eb), f.Not(f.LoadZeroVec128()),
|
Value* mask = f.Permute(f.LoadVectorShr(eb), f.Not(f.LoadZeroVec128()),
|
||||||
f.LoadZeroVec128(), INT8_TYPE);
|
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)
|
// ea &= ~0xF (handled above)
|
||||||
f.Store(ea, f.ByteSwap(v));
|
f.Store(ea, f.ByteSwap(v));
|
||||||
f.MarkLabel(skip_label);
|
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) {
|
int InstrEmit_vandc_(PPCHIRBuilder& f, uint32_t vd, uint32_t va, uint32_t vb) {
|
||||||
// VD <- (VA) & ¬(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);
|
f.StoreVR(vd, v);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -657,7 +657,7 @@ int InstrEmit_andx(PPCHIRBuilder& f, const InstrData& i) {
|
||||||
|
|
||||||
int InstrEmit_andcx(PPCHIRBuilder& f, const InstrData& i) {
|
int InstrEmit_andcx(PPCHIRBuilder& f, const InstrData& i) {
|
||||||
// RA <- (RS) & ¬(RB)
|
// 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);
|
f.StoreGPR(i.X.RA, ra);
|
||||||
if (i.X.Rc) {
|
if (i.X.Rc) {
|
||||||
f.UpdateCR(0, ra);
|
f.UpdateCR(0, ra);
|
||||||
|
|
|
@ -620,6 +620,16 @@ int InstrEmit_mfspr(PPCHIRBuilder& f, const InstrData& i) {
|
||||||
// TBU
|
// TBU
|
||||||
v = f.Shr(f.LoadClock(), 32);
|
v = f.Shr(f.LoadClock(), 32);
|
||||||
break;
|
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:
|
default:
|
||||||
XEINSTRNOTIMPLEMENTED();
|
XEINSTRNOTIMPLEMENTED();
|
||||||
return 1;
|
return 1;
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
#include "xenia/base/console_app_main.h"
|
#include "xenia/base/console_app_main.h"
|
||||||
#include "xenia/base/cvar.h"
|
#include "xenia/base/cvar.h"
|
||||||
#include "xenia/base/filesystem.h"
|
#include "xenia/base/filesystem.h"
|
||||||
|
#include "xenia/base/literals.h"
|
||||||
#include "xenia/base/logging.h"
|
#include "xenia/base/logging.h"
|
||||||
#include "xenia/base/math.h"
|
#include "xenia/base/math.h"
|
||||||
#include "xenia/base/platform.h"
|
#include "xenia/base/platform.h"
|
||||||
|
@ -36,6 +37,7 @@ namespace cpu {
|
||||||
namespace test {
|
namespace test {
|
||||||
|
|
||||||
using xe::cpu::ppc::PPCContext;
|
using xe::cpu::ppc::PPCContext;
|
||||||
|
using namespace xe::literals;
|
||||||
|
|
||||||
typedef std::vector<std::pair<std::string, std::string>> AnnotationList;
|
typedef std::vector<std::pair<std::string, std::string>> AnnotationList;
|
||||||
|
|
||||||
|
@ -177,7 +179,7 @@ class TestSuite {
|
||||||
|
|
||||||
class TestRunner {
|
class TestRunner {
|
||||||
public:
|
public:
|
||||||
TestRunner() : memory_size_(64 * 1024 * 1024) {
|
TestRunner() : memory_size_(64_MiB) {
|
||||||
memory_.reset(new Memory());
|
memory_.reset(new Memory());
|
||||||
memory_->Initialize();
|
memory_->Initialize();
|
||||||
}
|
}
|
||||||
|
@ -420,8 +422,7 @@ bool RunTests(const std::string_view test_name) {
|
||||||
int failed_count = 0;
|
int failed_count = 0;
|
||||||
int passed_count = 0;
|
int passed_count = 0;
|
||||||
|
|
||||||
XELOGI("Haswell instruction usage {}.",
|
XELOGI("Instruction feature mask {}.", cvars::x64_extension_mask);
|
||||||
cvars::use_haswell_instructions ? "enabled" : "disabled");
|
|
||||||
|
|
||||||
auto test_path_root = cvars::test_path;
|
auto test_path_root = cvars::test_path;
|
||||||
std::vector<std::filesystem::path> test_files;
|
std::vector<std::filesystem::path> test_files;
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
#include "xenia/base/cvar.h"
|
#include "xenia/base/cvar.h"
|
||||||
#include "xenia/base/debugging.h"
|
#include "xenia/base/debugging.h"
|
||||||
#include "xenia/base/exception_handler.h"
|
#include "xenia/base/exception_handler.h"
|
||||||
|
#include "xenia/base/literals.h"
|
||||||
#include "xenia/base/logging.h"
|
#include "xenia/base/logging.h"
|
||||||
#include "xenia/base/memory.h"
|
#include "xenia/base/memory.h"
|
||||||
#include "xenia/base/profiling.h"
|
#include "xenia/base/profiling.h"
|
||||||
|
@ -57,6 +58,8 @@ namespace cpu {
|
||||||
using xe::cpu::ppc::PPCOpcode;
|
using xe::cpu::ppc::PPCOpcode;
|
||||||
using xe::kernel::XThread;
|
using xe::kernel::XThread;
|
||||||
|
|
||||||
|
using namespace xe::literals;
|
||||||
|
|
||||||
class BuiltinModule : public Module {
|
class BuiltinModule : public Module {
|
||||||
public:
|
public:
|
||||||
explicit BuiltinModule(Processor* processor)
|
explicit BuiltinModule(Processor* processor)
|
||||||
|
@ -142,8 +145,8 @@ bool Processor::Setup(std::unique_ptr<backend::Backend> backend) {
|
||||||
// Open the trace data path, if requested.
|
// Open the trace data path, if requested.
|
||||||
functions_trace_path_ = cvars::trace_function_data_path;
|
functions_trace_path_ = cvars::trace_function_data_path;
|
||||||
if (!functions_trace_path_.empty()) {
|
if (!functions_trace_path_.empty()) {
|
||||||
functions_trace_file_ = ChunkedMappedMemoryWriter::Open(
|
functions_trace_file_ =
|
||||||
functions_trace_path_, 32 * 1024 * 1024, true);
|
ChunkedMappedMemoryWriter::Open(functions_trace_path_, 32_MiB, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -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]") {
|
TEST_CASE("VECTOR_SHA_I16", "[instr]") {
|
||||||
TestFunction test([](HIRBuilder& b) {
|
TestFunction test([](HIRBuilder& b) {
|
||||||
StoreVR(b, 3, b.VectorSha(LoadVR(b, 4), LoadVR(b, 5), INT16_TYPE));
|
StoreVR(b, 3, b.VectorSha(LoadVR(b, 4), LoadVR(b, 5), INT16_TYPE));
|
||||||
|
|
|
@ -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]") {
|
TEST_CASE("VECTOR_SHL_I16", "[instr]") {
|
||||||
TestFunction test([](HIRBuilder& b) {
|
TestFunction test([](HIRBuilder& b) {
|
||||||
StoreVR(b, 3, b.VectorShl(LoadVR(b, 4), LoadVR(b, 5), INT16_TYPE));
|
StoreVR(b, 3, b.VectorShl(LoadVR(b, 4), LoadVR(b, 5), INT16_TYPE));
|
||||||
|
|
|
@ -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]") {
|
TEST_CASE("VECTOR_SHR_I16", "[instr]") {
|
||||||
TestFunction test([](HIRBuilder& b) {
|
TestFunction test([](HIRBuilder& b) {
|
||||||
StoreVR(b, 3, b.VectorShr(LoadVR(b, 4), LoadVR(b, 5), INT16_TYPE));
|
StoreVR(b, 3, b.VectorShr(LoadVR(b, 4), LoadVR(b, 5), INT16_TYPE));
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
* Xenia : Xbox 360 Emulator Research Project *
|
* 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. *
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
*/
|
*/
|
||||||
|
@ -32,6 +32,8 @@
|
||||||
#include "xenia/kernel/xthread.h"
|
#include "xenia/kernel/xthread.h"
|
||||||
#include "xenia/ui/graphics_provider.h"
|
#include "xenia/ui/graphics_provider.h"
|
||||||
#include "xenia/ui/imgui_drawer.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"
|
#include "xenia/ui/windowed_app_context.h"
|
||||||
|
|
||||||
DEFINE_bool(imgui_debug, false, "Show ImGui debugging tools.", "UI");
|
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::MouseEvent;
|
||||||
using xe::ui::UIEvent;
|
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,
|
DebugWindow::DebugWindow(Emulator* emulator,
|
||||||
xe::ui::WindowedAppContext& app_context)
|
xe::ui::WindowedAppContext& app_context)
|
||||||
: emulator_(emulator),
|
: emulator_(emulator),
|
||||||
processor_(emulator->processor()),
|
processor_(emulator->processor()),
|
||||||
app_context_(app_context),
|
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) {
|
if (cs_open(CS_ARCH_X86, CS_MODE_64, &capstone_handle_) != CS_ERR_OK) {
|
||||||
assert_always("Failed to initialize capstone");
|
assert_always("Failed to initialize capstone");
|
||||||
}
|
}
|
||||||
|
@ -86,44 +92,57 @@ std::unique_ptr<DebugWindow> DebugWindow::Create(
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DebugWindow::Initialize() {
|
bool DebugWindow::Initialize() {
|
||||||
if (!window_->Initialize()) {
|
|
||||||
XELOGE("Failed to initialize platform window");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Main menu.
|
// Main menu.
|
||||||
auto main_menu = MenuItem::Create(MenuItem::Type::kNormal);
|
auto main_menu = MenuItem::Create(MenuItem::Type::kNormal);
|
||||||
auto file_menu = MenuItem::Create(MenuItem::Type::kPopup, "&File");
|
auto file_menu = MenuItem::Create(MenuItem::Type::kPopup, "&File");
|
||||||
{
|
{
|
||||||
file_menu->AddChild(MenuItem::Create(MenuItem::Type::kString, "&Close",
|
file_menu->AddChild(
|
||||||
"Alt+F4",
|
MenuItem::Create(MenuItem::Type::kString, "&Close", "Alt+F4",
|
||||||
[this]() { window_->Close(); }));
|
[this]() { window_->RequestClose(); }));
|
||||||
}
|
}
|
||||||
main_menu->AddChild(std::move(file_menu));
|
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.
|
// Setup drawing to the window.
|
||||||
auto provider = emulator_->display_window()->context()->provider();
|
|
||||||
window_->set_context(provider->CreateHostContext(window_.get()));
|
|
||||||
|
|
||||||
// Enable imgui input.
|
xe::ui::GraphicsProvider& graphics_provider =
|
||||||
window_->set_imgui_input_enabled(true);
|
*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();
|
UpdateCache();
|
||||||
window_->Invalidate();
|
|
||||||
|
// Begin drawing.
|
||||||
|
window_->SetPresenter(presenter_.get());
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DebugWindow::DrawFrame() {
|
void DebugWindow::DrawFrame(ImGuiIO& io) {
|
||||||
xe::ui::GraphicsContextLock lock(window_->context());
|
|
||||||
|
|
||||||
auto& io = window_->imgui_drawer()->GetIO();
|
|
||||||
|
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(-1, 0));
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(-1, 0));
|
||||||
ImGui::Begin("main_window", nullptr,
|
ImGui::Begin("main_window", nullptr,
|
||||||
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize |
|
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize |
|
||||||
|
@ -242,9 +261,6 @@ void DebugWindow::DrawFrame() {
|
||||||
ImGui::ShowDemoWindow();
|
ImGui::ShowDemoWindow();
|
||||||
ImGui::ShowMetricsWindow();
|
ImGui::ShowMetricsWindow();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Continuous paint.
|
|
||||||
window_->Invalidate();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DebugWindow::DrawToolbar() {
|
void DebugWindow::DrawToolbar() {
|
||||||
|
@ -1443,7 +1459,7 @@ void DebugWindow::UpdateCache() {
|
||||||
title += " (stepping)";
|
title += " (stepping)";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
window_->set_title(title);
|
window_->SetTitle(title);
|
||||||
});
|
});
|
||||||
|
|
||||||
cache_.is_running =
|
cache_.is_running =
|
||||||
|
@ -1573,7 +1589,7 @@ void DebugWindow::OnBreakpointHit(Breakpoint* breakpoint,
|
||||||
}
|
}
|
||||||
|
|
||||||
void DebugWindow::Focus() const {
|
void DebugWindow::Focus() const {
|
||||||
app_context_.CallInUIThread([this]() { window_->set_focus(true); });
|
app_context_.CallInUIThread([this]() { window_->Focus(); });
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace ui
|
} // namespace ui
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
* Xenia : Xbox 360 Emulator Research Project *
|
* 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. *
|
* 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/debug_listener.h"
|
||||||
#include "xenia/cpu/processor.h"
|
#include "xenia/cpu/processor.h"
|
||||||
#include "xenia/emulator.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/menu_item.h"
|
||||||
|
#include "xenia/ui/presenter.h"
|
||||||
#include "xenia/ui/window.h"
|
#include "xenia/ui/window.h"
|
||||||
#include "xenia/ui/windowed_app_context.h"
|
#include "xenia/ui/windowed_app_context.h"
|
||||||
#include "xenia/xbox.h"
|
#include "xenia/xbox.h"
|
||||||
|
@ -48,11 +52,24 @@ class DebugWindow : public cpu::DebugListener {
|
||||||
cpu::ThreadDebugInfo* thread_info) override;
|
cpu::ThreadDebugInfo* thread_info) override;
|
||||||
|
|
||||||
private:
|
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,
|
explicit DebugWindow(Emulator* emulator,
|
||||||
xe::ui::WindowedAppContext& app_context);
|
xe::ui::WindowedAppContext& app_context);
|
||||||
bool Initialize();
|
bool Initialize();
|
||||||
|
|
||||||
void DrawFrame();
|
void DrawFrame(ImGuiIO& io);
|
||||||
void DrawToolbar();
|
void DrawToolbar();
|
||||||
void DrawFunctionsPane();
|
void DrawFunctionsPane();
|
||||||
void DrawSourcePane();
|
void DrawSourcePane();
|
||||||
|
@ -93,7 +110,10 @@ class DebugWindow : public cpu::DebugListener {
|
||||||
cpu::Processor* processor_ = nullptr;
|
cpu::Processor* processor_ = nullptr;
|
||||||
xe::ui::WindowedAppContext& app_context_;
|
xe::ui::WindowedAppContext& app_context_;
|
||||||
std::unique_ptr<xe::ui::Window> window_;
|
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;
|
uintptr_t capstone_handle_ = 0;
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
|
|
||||||
#include "xenia/emulator.h"
|
#include "xenia/emulator.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <cinttypes>
|
#include <cinttypes>
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
@ -20,9 +21,9 @@
|
||||||
#include "xenia/base/cvar.h"
|
#include "xenia/base/cvar.h"
|
||||||
#include "xenia/base/debugging.h"
|
#include "xenia/base/debugging.h"
|
||||||
#include "xenia/base/exception_handler.h"
|
#include "xenia/base/exception_handler.h"
|
||||||
|
#include "xenia/base/literals.h"
|
||||||
#include "xenia/base/logging.h"
|
#include "xenia/base/logging.h"
|
||||||
#include "xenia/base/mapped_memory.h"
|
#include "xenia/base/mapped_memory.h"
|
||||||
#include "xenia/base/profiling.h"
|
|
||||||
#include "xenia/base/string.h"
|
#include "xenia/base/string.h"
|
||||||
#include "xenia/cpu/backend/code_cache.h"
|
#include "xenia/cpu/backend/code_cache.h"
|
||||||
#include "xenia/cpu/backend/x64/x64_backend.h"
|
#include "xenia/cpu/backend/x64/x64_backend.h"
|
||||||
|
@ -40,6 +41,7 @@
|
||||||
#include "xenia/kernel/xboxkrnl/xboxkrnl_module.h"
|
#include "xenia/kernel/xboxkrnl/xboxkrnl_module.h"
|
||||||
#include "xenia/memory.h"
|
#include "xenia/memory.h"
|
||||||
#include "xenia/ui/imgui_dialog.h"
|
#include "xenia/ui/imgui_dialog.h"
|
||||||
|
#include "xenia/ui/imgui_drawer.h"
|
||||||
#include "xenia/ui/window.h"
|
#include "xenia/ui/window.h"
|
||||||
#include "xenia/ui/windowed_app_context.h"
|
#include "xenia/ui/windowed_app_context.h"
|
||||||
#include "xenia/vfs/devices/disc_image_device.h"
|
#include "xenia/vfs/devices/disc_image_device.h"
|
||||||
|
@ -60,6 +62,17 @@ DEFINE_string(
|
||||||
|
|
||||||
namespace xe {
|
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,
|
Emulator::Emulator(const std::filesystem::path& command_line,
|
||||||
const std::filesystem::path& storage_root,
|
const std::filesystem::path& storage_root,
|
||||||
const std::filesystem::path& content_root,
|
const std::filesystem::path& content_root,
|
||||||
|
@ -113,7 +126,7 @@ Emulator::~Emulator() {
|
||||||
}
|
}
|
||||||
|
|
||||||
X_STATUS Emulator::Setup(
|
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*)>
|
std::function<std::unique_ptr<apu::AudioSystem>(cpu::Processor*)>
|
||||||
audio_system_factory,
|
audio_system_factory,
|
||||||
std::function<std::unique_ptr<gpu::GraphicsSystem>()>
|
std::function<std::unique_ptr<gpu::GraphicsSystem>()>
|
||||||
|
@ -123,6 +136,7 @@ X_STATUS Emulator::Setup(
|
||||||
X_STATUS result = X_STATUS_UNSUCCESSFUL;
|
X_STATUS result = X_STATUS_UNSUCCESSFUL;
|
||||||
|
|
||||||
display_window_ = display_window;
|
display_window_ = display_window;
|
||||||
|
imgui_drawer_ = imgui_drawer;
|
||||||
|
|
||||||
// Initialize clock.
|
// Initialize clock.
|
||||||
// 360 uses a 50MHz clock.
|
// 360 uses a 50MHz clock.
|
||||||
|
@ -209,8 +223,10 @@ X_STATUS Emulator::Setup(
|
||||||
kernel_state_ = std::make_unique<xe::kernel::KernelState>(this);
|
kernel_state_ = std::make_unique<xe::kernel::KernelState>(this);
|
||||||
|
|
||||||
// Setup the core components.
|
// Setup the core components.
|
||||||
result = graphics_system_->Setup(processor_.get(), kernel_state_.get(),
|
result = graphics_system_->Setup(
|
||||||
display_window_);
|
processor_.get(), kernel_state_.get(),
|
||||||
|
display_window_ ? &display_window_->app_context() : nullptr,
|
||||||
|
display_window_ != nullptr);
|
||||||
if (result) {
|
if (result) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -233,14 +249,6 @@ X_STATUS Emulator::Setup(
|
||||||
// Initialize emulator fallback exception handling last.
|
// Initialize emulator fallback exception handling last.
|
||||||
ExceptionHandler::Install(Emulator::ExceptionCallbackThunk, this);
|
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;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -414,9 +422,8 @@ void Emulator::Resume() {
|
||||||
bool Emulator::SaveToFile(const std::filesystem::path& path) {
|
bool Emulator::SaveToFile(const std::filesystem::path& path) {
|
||||||
Pause();
|
Pause();
|
||||||
|
|
||||||
filesystem::CreateFile(path);
|
filesystem::CreateEmptyFile(path);
|
||||||
auto map = MappedMemory::Open(path, MappedMemory::Mode::kReadWrite, 0,
|
auto map = MappedMemory::Open(path, MappedMemory::Mode::kReadWrite, 0, 2_GiB);
|
||||||
1024ull * 1024ull * 1024ull * 2ull);
|
|
||||||
if (!map) {
|
if (!map) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -585,14 +592,16 @@ bool Emulator::ExceptionCallback(Exception* ex) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Display a dialog telling the user the guest has crashed.
|
// Display a dialog telling the user the guest has crashed.
|
||||||
display_window()->app_context().CallInUIThreadSynchronous([this]() {
|
if (display_window_ && imgui_drawer_) {
|
||||||
xe::ui::ImGuiDialog::ShowMessageBox(
|
display_window_->app_context().CallInUIThreadSynchronous([this]() {
|
||||||
display_window(), "Uh-oh!",
|
xe::ui::ImGuiDialog::ShowMessageBox(
|
||||||
"The guest has crashed.\n\n"
|
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.");
|
"Xenia has now paused itself.\n"
|
||||||
});
|
"A crash dump has been written into the log.");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Now suspend ourself (we should be a guest thread).
|
// Now suspend ourself (we should be a guest thread).
|
||||||
current_thread->Suspend(nullptr);
|
current_thread->Suspend(nullptr);
|
||||||
|
@ -619,6 +628,41 @@ void Emulator::WaitUntilExit() {
|
||||||
on_exit();
|
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 Emulator::FindLaunchModule() {
|
||||||
std::string path("game:\\");
|
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,
|
X_STATUS Emulator::CompleteLaunch(const std::filesystem::path& path,
|
||||||
const std::string_view module_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
|
// Setup NullDevices for raw HDD partition accesses
|
||||||
// Cache/STFC code baked into games tries reading/writing to these
|
// Cache/STFC code baked into games tries reading/writing to these
|
||||||
// By using a NullDevice that just returns success to all IO requests it
|
// 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).
|
// Try and load the resource database (xex only).
|
||||||
if (module->title_id()) {
|
if (module->title_id()) {
|
||||||
auto title_id = fmt::format("{:08X}", 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);
|
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_data = 0;
|
||||||
uint32_t resource_size = 0;
|
uint32_t resource_size = 0;
|
||||||
if (XSUCCEEDED(module->GetSection(title_id.c_str(), &resource_data,
|
if (XSUCCEEDED(module->GetSection(title_id.c_str(), &resource_data,
|
||||||
|
|
|
@ -10,9 +10,12 @@
|
||||||
#ifndef XENIA_EMULATOR_H_
|
#ifndef XENIA_EMULATOR_H_
|
||||||
#define XENIA_EMULATOR_H_
|
#define XENIA_EMULATOR_H_
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include "xenia/base/delegate.h"
|
#include "xenia/base/delegate.h"
|
||||||
#include "xenia/base/exception_handler.h"
|
#include "xenia/base/exception_handler.h"
|
||||||
|
@ -38,6 +41,7 @@ class InputDriver;
|
||||||
class InputSystem;
|
class InputSystem;
|
||||||
} // namespace hid
|
} // namespace hid
|
||||||
namespace ui {
|
namespace ui {
|
||||||
|
class ImGuiDrawer;
|
||||||
class Window;
|
class Window;
|
||||||
} // namespace ui
|
} // namespace ui
|
||||||
} // namespace xe
|
} // namespace xe
|
||||||
|
@ -50,6 +54,37 @@ constexpr fourcc_t kEmulatorSaveSignature = make_fourcc("XSAV");
|
||||||
// This is responsible for initializing and managing all the various subsystems.
|
// This is responsible for initializing and managing all the various subsystems.
|
||||||
class Emulator {
|
class Emulator {
|
||||||
public:
|
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,
|
explicit Emulator(const std::filesystem::path& command_line,
|
||||||
const std::filesystem::path& storage_root,
|
const std::filesystem::path& storage_root,
|
||||||
const std::filesystem::path& content_root,
|
const std::filesystem::path& content_root,
|
||||||
|
@ -82,9 +117,13 @@ class Emulator {
|
||||||
// Are we currently running a title?
|
// Are we currently running a title?
|
||||||
bool is_title_open() const { return title_id_.has_value(); }
|
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_; }
|
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
|
// Guest memory system modelling the RAM (both virtual and physical) of the
|
||||||
// system.
|
// system.
|
||||||
Memory* memory() const { return memory_.get(); }
|
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
|
// Once this function returns a game can be launched using one of the Launch
|
||||||
// functions.
|
// functions.
|
||||||
X_STATUS Setup(
|
X_STATUS Setup(
|
||||||
ui::Window* display_window,
|
ui::Window* display_window, ui::ImGuiDrawer* imgui_drawer,
|
||||||
std::function<std::unique_ptr<apu::AudioSystem>(cpu::Processor*)>
|
std::function<std::unique_ptr<apu::AudioSystem>(cpu::Processor*)>
|
||||||
audio_system_factory,
|
audio_system_factory,
|
||||||
std::function<std::unique_ptr<gpu::GraphicsSystem>()>
|
std::function<std::unique_ptr<gpu::GraphicsSystem>()>
|
||||||
|
@ -170,6 +209,9 @@ class Emulator {
|
||||||
static bool ExceptionCallbackThunk(Exception* ex, void* data);
|
static bool ExceptionCallbackThunk(Exception* ex, void* data);
|
||||||
bool ExceptionCallback(Exception* ex);
|
bool ExceptionCallback(Exception* ex);
|
||||||
|
|
||||||
|
void AddGameConfigLoadCallback(GameConfigLoadCallback* callback);
|
||||||
|
void RemoveGameConfigLoadCallback(GameConfigLoadCallback* callback);
|
||||||
|
|
||||||
std::string FindLaunchModule();
|
std::string FindLaunchModule();
|
||||||
|
|
||||||
X_STATUS CompleteLaunch(const std::filesystem::path& path,
|
X_STATUS CompleteLaunch(const std::filesystem::path& path,
|
||||||
|
@ -183,7 +225,8 @@ class Emulator {
|
||||||
std::string title_name_;
|
std::string title_name_;
|
||||||
std::string title_version_;
|
std::string title_version_;
|
||||||
|
|
||||||
ui::Window* display_window_;
|
ui::Window* display_window_ = nullptr;
|
||||||
|
ui::ImGuiDrawer* imgui_drawer_ = nullptr;
|
||||||
|
|
||||||
std::unique_ptr<Memory> memory_;
|
std::unique_ptr<Memory> memory_;
|
||||||
|
|
||||||
|
@ -196,6 +239,16 @@ class Emulator {
|
||||||
std::unique_ptr<vfs::VirtualFileSystem> file_system_;
|
std::unique_ptr<vfs::VirtualFileSystem> file_system_;
|
||||||
|
|
||||||
std::unique_ptr<kernel::KernelState> kernel_state_;
|
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_;
|
kernel::object_ref<kernel::XThread> main_thread_;
|
||||||
std::optional<uint32_t> title_id_; // Currently running title ID
|
std::optional<uint32_t> title_id_; // Currently running title ID
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
* Xenia : Xbox 360 Emulator Research Project *
|
* 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. *
|
* 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;
|
CommandProcessor::~CommandProcessor() = default;
|
||||||
|
|
||||||
bool CommandProcessor::Initialize(
|
bool CommandProcessor::Initialize() {
|
||||||
std::unique_ptr<xe::ui::GraphicsContext> context) {
|
|
||||||
context_ = std::move(context);
|
|
||||||
|
|
||||||
// Initialize the gamma ramps to their default (linear) values - taken from
|
// Initialize the gamma ramps to their default (linear) values - taken from
|
||||||
// what games set when starting.
|
// what games set when starting.
|
||||||
for (uint32_t i = 0; i < 256; ++i) {
|
for (uint32_t i = 0; i < 256; ++i) {
|
||||||
uint32_t value = i * 1023 / 255;
|
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) {
|
for (uint32_t i = 0; i < 128; ++i) {
|
||||||
uint32_t value = (i * 65535 / 127) & ~63;
|
uint32_t value = (i * 65535 / 127) & ~63;
|
||||||
|
@ -64,7 +61,7 @@ bool CommandProcessor::Initialize(
|
||||||
gamma_ramp_.pwl[i].values[j].value = value;
|
gamma_ramp_.pwl[i].values[j].value = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dirty_gamma_ramp_normal_ = true;
|
dirty_gamma_ramp_table_ = true;
|
||||||
dirty_gamma_ramp_pwl_ = true;
|
dirty_gamma_ramp_pwl_ = true;
|
||||||
|
|
||||||
worker_running_ = true;
|
worker_running_ = true;
|
||||||
|
@ -140,8 +137,18 @@ void CommandProcessor::CallInThread(std::function<void()> fn) {
|
||||||
|
|
||||||
void CommandProcessor::ClearCaches() {}
|
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() {
|
void CommandProcessor::WorkerThreadMain() {
|
||||||
context_->MakeCurrent();
|
|
||||||
if (!SetupContext()) {
|
if (!SetupContext()) {
|
||||||
xe::FatalError("Unable to setup command processor internal state");
|
xe::FatalError("Unable to setup command processor internal state");
|
||||||
return;
|
return;
|
||||||
|
@ -212,9 +219,6 @@ void CommandProcessor::Pause() {
|
||||||
threading::Thread::GetCurrentThread()->Suspend();
|
threading::Thread::GetCurrentThread()->Suspend();
|
||||||
});
|
});
|
||||||
|
|
||||||
// HACK - Prevents a hang in IssueSwap()
|
|
||||||
swap_state_.pending = false;
|
|
||||||
|
|
||||||
fence.Wait();
|
fence.Wait();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -255,7 +259,7 @@ bool CommandProcessor::Restore(ByteStream* stream) {
|
||||||
|
|
||||||
bool CommandProcessor::SetupContext() { return true; }
|
bool CommandProcessor::SetupContext() { return true; }
|
||||||
|
|
||||||
void CommandProcessor::ShutdownContext() { context_.reset(); }
|
void CommandProcessor::ShutdownContext() {}
|
||||||
|
|
||||||
void CommandProcessor::InitializeRingBuffer(uint32_t ptr, uint32_t size_log2) {
|
void CommandProcessor::InitializeRingBuffer(uint32_t ptr, uint32_t size_log2) {
|
||||||
read_ptr_index_ = 0;
|
read_ptr_index_ = 0;
|
||||||
|
@ -326,14 +330,17 @@ void CommandProcessor::UpdateGammaRampValue(GammaRampType type,
|
||||||
|
|
||||||
if (mask_lo) {
|
if (mask_lo) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case GammaRampType::kNormal:
|
case GammaRampType::kTable:
|
||||||
assert_true(regs->values[XE_GPU_REG_DC_LUT_RW_MODE].u32 == 0);
|
assert_true(regs->values[XE_GPU_REG_DC_LUT_RW_MODE].u32 == 0);
|
||||||
gamma_ramp_.normal[index].value = value;
|
gamma_ramp_.table[index].value = value;
|
||||||
dirty_gamma_ramp_normal_ = true;
|
dirty_gamma_ramp_table_ = true;
|
||||||
break;
|
break;
|
||||||
case GammaRampType::kPWL:
|
case GammaRampType::kPWL:
|
||||||
assert_true(regs->values[XE_GPU_REG_DC_LUT_RW_MODE].u32 == 1);
|
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;
|
gamma_ramp_rw_subindex_ = (gamma_ramp_rw_subindex_ + 1) % 3;
|
||||||
dirty_gamma_ramp_pwl_ = true;
|
dirty_gamma_ramp_pwl_ = true;
|
||||||
break;
|
break;
|
||||||
|
@ -385,51 +392,6 @@ void CommandProcessor::PrepareForWait() { trace_writer_.Flush(); }
|
||||||
|
|
||||||
void CommandProcessor::ReturnFromWait() {}
|
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 CommandProcessor::ExecutePrimaryBuffer(uint32_t read_index,
|
||||||
uint32_t write_index) {
|
uint32_t write_index) {
|
||||||
SCOPE_profile_cpu_f("gpu");
|
SCOPE_profile_cpu_f("gpu");
|
||||||
|
@ -440,7 +402,7 @@ uint32_t CommandProcessor::ExecutePrimaryBuffer(uint32_t read_index,
|
||||||
uint32_t title_id = kernel_state_->GetExecutableModule()
|
uint32_t title_id = kernel_state_->GetExecutableModule()
|
||||||
? kernel_state_->GetExecutableModule()->title_id()
|
? kernel_state_->GetExecutableModule()->title_id()
|
||||||
: 0;
|
: 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;
|
auto path = trace_stream_path_ / file_name;
|
||||||
trace_writer_.Open(path, title_id);
|
trace_writer_.Open(path, title_id);
|
||||||
InitializeTrace();
|
InitializeTrace();
|
||||||
|
@ -767,7 +729,7 @@ bool CommandProcessor::ExecutePacketType3(RingBuffer* reader, uint32_t packet) {
|
||||||
} else if (trace_state_ == TraceState::kSingleFrame) {
|
} else if (trace_state_ == TraceState::kSingleFrame) {
|
||||||
// New trace request - we only start tracing at the beginning of a frame.
|
// New trace request - we only start tracing at the beginning of a frame.
|
||||||
uint32_t title_id = kernel_state_->GetExecutableModule()->title_id();
|
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;
|
auto path = trace_frame_path_ / file_name;
|
||||||
trace_writer_.Open(path, title_id);
|
trace_writer_.Open(path, title_id);
|
||||||
InitializeTrace();
|
InitializeTrace();
|
||||||
|
@ -837,7 +799,7 @@ bool CommandProcessor::ExecutePacketType3_XE_SWAP(RingBuffer* reader,
|
||||||
uint32_t frontbuffer_height = reader->ReadAndSwap<uint32_t>();
|
uint32_t frontbuffer_height = reader->ReadAndSwap<uint32_t>();
|
||||||
reader->AdvanceRead((count - 4) * sizeof(uint32_t));
|
reader->AdvanceRead((count - 4) * sizeof(uint32_t));
|
||||||
|
|
||||||
if (swap_mode_ == SwapMode::kNormal) {
|
if (!ignore_swap_) {
|
||||||
IssueSwap(frontbuffer_ptr, frontbuffer_width, frontbuffer_height);
|
IssueSwap(frontbuffer_ptr, frontbuffer_width, frontbuffer_height);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
* Xenia : Xbox 360 Emulator Research Project *
|
* 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. *
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
*/
|
*/
|
||||||
|
@ -26,7 +26,7 @@
|
||||||
#include "xenia/gpu/xenos.h"
|
#include "xenia/gpu/xenos.h"
|
||||||
#include "xenia/kernel/xthread.h"
|
#include "xenia/kernel/xthread.h"
|
||||||
#include "xenia/memory.h"
|
#include "xenia/memory.h"
|
||||||
#include "xenia/ui/graphics_context.h"
|
#include "xenia/ui/presenter.h"
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
|
|
||||||
|
@ -60,12 +60,20 @@ enum class SwapMode {
|
||||||
|
|
||||||
enum class GammaRampType {
|
enum class GammaRampType {
|
||||||
kUnknown = 0,
|
kUnknown = 0,
|
||||||
kNormal,
|
kTable,
|
||||||
kPWL,
|
kPWL,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct GammaRamp {
|
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 {
|
union {
|
||||||
uint32_t value;
|
uint32_t value;
|
||||||
struct {
|
struct {
|
||||||
|
@ -81,6 +89,15 @@ struct GammaRamp {
|
||||||
union {
|
union {
|
||||||
uint32_t value;
|
uint32_t value;
|
||||||
struct {
|
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 base;
|
||||||
uint16_t delta;
|
uint16_t delta;
|
||||||
};
|
};
|
||||||
|
@ -91,19 +108,25 @@ struct GammaRamp {
|
||||||
union {
|
union {
|
||||||
PWLValue values[3];
|
PWLValue values[3];
|
||||||
struct {
|
struct {
|
||||||
PWLValue r;
|
|
||||||
PWLValue g;
|
|
||||||
PWLValue b;
|
PWLValue b;
|
||||||
|
PWLValue g;
|
||||||
|
PWLValue r;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
NormalEntry normal[256];
|
TableEntry table[256];
|
||||||
PWLEntry pwl[128];
|
PWLEntry pwl[128];
|
||||||
};
|
};
|
||||||
|
|
||||||
class CommandProcessor {
|
class CommandProcessor {
|
||||||
public:
|
public:
|
||||||
|
enum class SwapPostEffect {
|
||||||
|
kNone,
|
||||||
|
kFxaa,
|
||||||
|
kFxaaExtreme,
|
||||||
|
};
|
||||||
|
|
||||||
CommandProcessor(GraphicsSystem* graphics_system,
|
CommandProcessor(GraphicsSystem* graphics_system,
|
||||||
kernel::KernelState* kernel_state);
|
kernel::KernelState* kernel_state);
|
||||||
virtual ~CommandProcessor();
|
virtual ~CommandProcessor();
|
||||||
|
@ -114,21 +137,26 @@ class CommandProcessor {
|
||||||
Shader* active_vertex_shader() const { return active_vertex_shader_; }
|
Shader* active_vertex_shader() const { return active_vertex_shader_; }
|
||||||
Shader* active_pixel_shader() const { return active_pixel_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();
|
virtual void Shutdown();
|
||||||
|
|
||||||
void CallInThread(std::function<void()> fn);
|
void CallInThread(std::function<void()> fn);
|
||||||
|
|
||||||
virtual void ClearCaches();
|
virtual void ClearCaches();
|
||||||
|
|
||||||
SwapState& swap_state() { return swap_state_; }
|
void SetIgnoreSwap(bool ignore_swap) { ignore_swap_ = ignore_swap; }
|
||||||
void set_swap_mode(SwapMode swap_mode) { swap_mode_ = swap_mode; }
|
// "Desired" is for the external thread managing the post-processing effect.
|
||||||
void IssueSwap(uint32_t frontbuffer_ptr, uint32_t frontbuffer_width,
|
SwapPostEffect GetDesiredSwapPostEffect() const {
|
||||||
uint32_t frontbuffer_height);
|
return swap_post_effect_desired_;
|
||||||
|
|
||||||
void set_swap_request_handler(std::function<void()> fn) {
|
|
||||||
swap_request_handler_ = fn;
|
|
||||||
}
|
}
|
||||||
|
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
|
// 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
|
// processor is paused, and the termination of this function may be explicitly
|
||||||
|
@ -179,9 +207,6 @@ class CommandProcessor {
|
||||||
virtual void PrepareForWait();
|
virtual void PrepareForWait();
|
||||||
virtual void ReturnFromWait();
|
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);
|
uint32_t ExecutePrimaryBuffer(uint32_t start_index, uint32_t end_index);
|
||||||
virtual void OnPrimaryBufferEnd() {}
|
virtual void OnPrimaryBufferEnd() {}
|
||||||
void ExecuteIndirectBuffer(uint32_t ptr, uint32_t length);
|
void ExecuteIndirectBuffer(uint32_t ptr, uint32_t length);
|
||||||
|
@ -254,6 +279,14 @@ class CommandProcessor {
|
||||||
bool major_mode_explicit) = 0;
|
bool major_mode_explicit) = 0;
|
||||||
virtual bool IssueCopy() = 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;
|
virtual void InitializeTrace() = 0;
|
||||||
|
|
||||||
Memory* memory_ = nullptr;
|
Memory* memory_ = nullptr;
|
||||||
|
@ -274,10 +307,8 @@ class CommandProcessor {
|
||||||
std::atomic<bool> worker_running_;
|
std::atomic<bool> worker_running_;
|
||||||
kernel::object_ref<kernel::XHostThread> worker_thread_;
|
kernel::object_ref<kernel::XHostThread> worker_thread_;
|
||||||
|
|
||||||
std::unique_ptr<xe::ui::GraphicsContext> context_;
|
bool ignore_swap_ = false;
|
||||||
SwapMode swap_mode_ = SwapMode::kNormal;
|
|
||||||
SwapState swap_state_;
|
|
||||||
std::function<void()> swap_request_handler_;
|
|
||||||
std::queue<std::function<void()>> pending_fns_;
|
std::queue<std::function<void()>> pending_fns_;
|
||||||
|
|
||||||
// MicroEngine binary from PM4_ME_INIT
|
// MicroEngine binary from PM4_ME_INIT
|
||||||
|
@ -305,8 +336,13 @@ class CommandProcessor {
|
||||||
|
|
||||||
GammaRamp gamma_ramp_ = {};
|
GammaRamp gamma_ramp_ = {};
|
||||||
int gamma_ramp_rw_subindex_ = 0;
|
int gamma_ramp_rw_subindex_ = 0;
|
||||||
bool dirty_gamma_ramp_normal_ = true;
|
bool dirty_gamma_ramp_table_ = true;
|
||||||
bool dirty_gamma_ramp_pwl_ = 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
|
} // namespace gpu
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -2,7 +2,7 @@
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
* Xenia : Xbox 360 Emulator Research Project *
|
* 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. *
|
* 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/dxbc_shader_translator.h"
|
||||||
#include "xenia/gpu/xenos.h"
|
#include "xenia/gpu/xenos.h"
|
||||||
#include "xenia/kernel/kernel_state.h"
|
#include "xenia/kernel/kernel_state.h"
|
||||||
#include "xenia/ui/d3d12/d3d12_context.h"
|
|
||||||
#include "xenia/ui/d3d12/d3d12_descriptor_heap_pool.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_upload_buffer_pool.h"
|
||||||
#include "xenia/ui/d3d12/d3d12_util.h"
|
#include "xenia/ui/d3d12/d3d12_util.h"
|
||||||
|
|
||||||
|
@ -58,8 +58,9 @@ class D3D12CommandProcessor : public CommandProcessor {
|
||||||
|
|
||||||
void RestoreEdramSnapshot(const void* snapshot) override;
|
void RestoreEdramSnapshot(const void* snapshot) override;
|
||||||
|
|
||||||
ui::d3d12::D3D12Context& GetD3D12Context() const {
|
ui::d3d12::D3D12Provider& GetD3D12Provider() const {
|
||||||
return static_cast<ui::d3d12::D3D12Context&>(*context_);
|
return *static_cast<ui::d3d12::D3D12Provider*>(
|
||||||
|
graphics_system_->provider());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the deferred drawing command list for the currently open
|
// Returns the deferred drawing command list for the currently open
|
||||||
|
@ -153,7 +154,7 @@ class D3D12CommandProcessor : public CommandProcessor {
|
||||||
kEdramR32G32UintUAV,
|
kEdramR32G32UintUAV,
|
||||||
kEdramR32G32B32A32UintUAV,
|
kEdramR32G32B32A32UintUAV,
|
||||||
|
|
||||||
kGammaRampNormalSRV,
|
kGammaRampTableSRV,
|
||||||
kGammaRampPWLSRV,
|
kGammaRampPWLSRV,
|
||||||
|
|
||||||
// Beyond this point, SRVs are accessible to shaders through an unbounded
|
// 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.
|
// Returns the text to display in the GPU backend name in the window title.
|
||||||
std::string GetWindowTitleText() const;
|
std::string GetWindowTitleText() const;
|
||||||
|
|
||||||
std::unique_ptr<xe::ui::RawImage> Capture();
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
bool SetupContext() override;
|
bool SetupContext() override;
|
||||||
void ShutdownContext() override;
|
void ShutdownContext() override;
|
||||||
|
|
||||||
void WriteRegister(uint32_t index, uint32_t value) override;
|
void WriteRegister(uint32_t index, uint32_t value) override;
|
||||||
|
|
||||||
void PerformSwap(uint32_t frontbuffer_ptr, uint32_t frontbuffer_width,
|
void IssueSwap(uint32_t frontbuffer_ptr, uint32_t frontbuffer_width,
|
||||||
uint32_t frontbuffer_height) override;
|
uint32_t frontbuffer_height) override;
|
||||||
|
|
||||||
void OnPrimaryBufferEnd() override;
|
void OnPrimaryBufferEnd() override;
|
||||||
|
|
||||||
|
@ -321,8 +320,9 @@ class D3D12CommandProcessor : public CommandProcessor {
|
||||||
void CheckSubmissionFence(uint64_t await_submission);
|
void CheckSubmissionFence(uint64_t await_submission);
|
||||||
// If is_guest_command is true, a new full frame - with full cleanup of
|
// 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
|
// resources and, if needed, starting capturing - is opened if pending (as
|
||||||
// opposed to simply resuming after mid-frame synchronization).
|
// opposed to simply resuming after mid-frame synchronization). Returns
|
||||||
void BeginSubmission(bool is_guest_command);
|
// 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
|
// If is_swap is true, a full frame is closed - with, if needed, cache
|
||||||
// clearing and stopping capturing. Returns whether the submission was done
|
// clearing and stopping capturing. Returns whether the submission was done
|
||||||
// successfully, if it has failed, leaves it open.
|
// 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;
|
void WriteGammaRampSRV(bool is_pwl, D3D12_CPU_DESCRIPTOR_HANDLE handle) const;
|
||||||
|
|
||||||
|
bool device_removed_ = false;
|
||||||
|
|
||||||
bool cache_clear_requested_ = false;
|
bool cache_clear_requested_ = false;
|
||||||
|
|
||||||
HANDLE fence_completion_event_ = nullptr;
|
HANDLE fence_completion_event_ = nullptr;
|
||||||
|
@ -497,42 +499,73 @@ class D3D12CommandProcessor : public CommandProcessor {
|
||||||
|
|
||||||
std::unique_ptr<TextureCache> texture_cache_;
|
std::unique_ptr<TextureCache> texture_cache_;
|
||||||
|
|
||||||
// Mip 0 contains the normal gamma ramp (256 entries), mip 1 contains the PWL
|
// Bytes 0x0...0x3FF - 256-entry R10G10B10X2 gamma ramp (red and blue must be
|
||||||
// ramp (128 entries). DXGI_FORMAT_R10G10B10A2_UNORM 1D.
|
// read as swapped - 535107D4 has settings allowing separate configuration).
|
||||||
ID3D12Resource* gamma_ramp_texture_ = nullptr;
|
// Bytes 0x400...0x9FF - 128-entry PWL R16G16 gamma ramp (R - base, G - delta,
|
||||||
D3D12_RESOURCE_STATES gamma_ramp_texture_state_;
|
// 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
|
// Upload buffer for an image that is the same as gamma_ramp_, but with
|
||||||
// kQueueFrames array layers.
|
// kQueueFrames array layers.
|
||||||
ID3D12Resource* gamma_ramp_upload_ = nullptr;
|
Microsoft::WRL::ComPtr<ID3D12Resource> gamma_ramp_upload_buffer_;
|
||||||
uint8_t* gamma_ramp_upload_mapping_ = nullptr;
|
uint8_t* gamma_ramp_upload_buffer_mapping_ = nullptr;
|
||||||
D3D12_PLACED_SUBRESOURCE_FOOTPRINT gamma_ramp_footprints_[kQueueFrames * 2];
|
|
||||||
|
|
||||||
static constexpr uint32_t kSwapTextureWidth = 1280;
|
struct ApplyGammaConstants {
|
||||||
static constexpr uint32_t kSwapTextureHeight = 720;
|
uint32_t size[2];
|
||||||
std::pair<uint32_t, uint32_t> GetSwapTextureSize() const {
|
};
|
||||||
return std::make_pair(
|
enum class ApplyGammaRootParameter : UINT {
|
||||||
kSwapTextureWidth * texture_cache_->GetDrawResolutionScaleX(),
|
kConstants,
|
||||||
kSwapTextureHeight * texture_cache_->GetDrawResolutionScaleY());
|
kDestination,
|
||||||
}
|
kSource,
|
||||||
std::pair<uint32_t, uint32_t> GetSwapScreenSize() const {
|
kRamp,
|
||||||
uint32_t resolution_scale =
|
|
||||||
std::max(texture_cache_->GetDrawResolutionScaleX(),
|
kCount,
|
||||||
texture_cache_->GetDrawResolutionScaleY());
|
};
|
||||||
return std::make_pair(kSwapTextureWidth * resolution_scale,
|
Microsoft::WRL::ComPtr<ID3D12RootSignature> apply_gamma_root_signature_;
|
||||||
kSwapTextureHeight * resolution_scale);
|
Microsoft::WRL::ComPtr<ID3D12PipelineState> apply_gamma_table_pipeline_;
|
||||||
}
|
Microsoft::WRL::ComPtr<ID3D12PipelineState>
|
||||||
ID3D12Resource* swap_texture_ = nullptr;
|
apply_gamma_table_fxaa_luma_pipeline_;
|
||||||
D3D12_PLACED_SUBRESOURCE_FOOTPRINT swap_texture_copy_footprint_;
|
Microsoft::WRL::ComPtr<ID3D12PipelineState> apply_gamma_pwl_pipeline_;
|
||||||
UINT64 swap_texture_copy_size_;
|
Microsoft::WRL::ComPtr<ID3D12PipelineState>
|
||||||
ID3D12DescriptorHeap* swap_texture_rtv_descriptor_heap_ = nullptr;
|
apply_gamma_pwl_fxaa_luma_pipeline_;
|
||||||
D3D12_CPU_DESCRIPTOR_HANDLE swap_texture_rtv_;
|
|
||||||
ID3D12DescriptorHeap* swap_texture_srv_descriptor_heap_ = nullptr;
|
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.
|
// Unsubmitted barrier batch.
|
||||||
std::vector<D3D12_RESOURCE_BARRIER> barriers_;
|
std::vector<D3D12_RESOURCE_BARRIER> barriers_;
|
||||||
|
|
||||||
// <Resource, submission where requested>, sorted by the submission number.
|
// <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;
|
static constexpr uint32_t kScratchBufferSizeIncrement = 16 * 1024 * 1024;
|
||||||
ID3D12Resource* scratch_buffer_ = nullptr;
|
ID3D12Resource* scratch_buffer_ = nullptr;
|
||||||
|
|
|
@ -22,13 +22,6 @@ namespace xe {
|
||||||
namespace gpu {
|
namespace gpu {
|
||||||
namespace d3d12 {
|
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() {}
|
||||||
|
|
||||||
D3D12GraphicsSystem::~D3D12GraphicsSystem() {}
|
D3D12GraphicsSystem::~D3D12GraphicsSystem() {}
|
||||||
|
@ -48,198 +41,11 @@ std::string D3D12GraphicsSystem::name() const {
|
||||||
|
|
||||||
X_STATUS D3D12GraphicsSystem::Setup(cpu::Processor* processor,
|
X_STATUS D3D12GraphicsSystem::Setup(cpu::Processor* processor,
|
||||||
kernel::KernelState* kernel_state,
|
kernel::KernelState* kernel_state,
|
||||||
ui::Window* target_window) {
|
ui::WindowedAppContext* app_context,
|
||||||
|
bool is_surface_required) {
|
||||||
provider_ = xe::ui::d3d12::D3D12Provider::Create();
|
provider_ = xe::ui::d3d12::D3D12Provider::Create();
|
||||||
auto d3d12_provider = static_cast<xe::ui::d3d12::D3D12Provider*>(provider());
|
return GraphicsSystem::Setup(processor, kernel_state, app_context,
|
||||||
auto device = d3d12_provider->GetDevice();
|
is_surface_required);
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<CommandProcessor>
|
std::unique_ptr<CommandProcessor>
|
||||||
|
@ -248,69 +54,6 @@ D3D12GraphicsSystem::CreateCommandProcessor() {
|
||||||
new D3D12CommandProcessor(this, kernel_state_));
|
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 d3d12
|
||||||
} // namespace gpu
|
} // namespace gpu
|
||||||
} // namespace xe
|
} // namespace xe
|
||||||
|
|
|
@ -15,7 +15,6 @@
|
||||||
#include "xenia/gpu/command_processor.h"
|
#include "xenia/gpu/command_processor.h"
|
||||||
#include "xenia/gpu/d3d12/deferred_command_list.h"
|
#include "xenia/gpu/d3d12/deferred_command_list.h"
|
||||||
#include "xenia/gpu/graphics_system.h"
|
#include "xenia/gpu/graphics_system.h"
|
||||||
#include "xenia/ui/d3d12/d3d12_context.h"
|
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
namespace gpu {
|
namespace gpu {
|
||||||
|
@ -31,37 +30,11 @@ class D3D12GraphicsSystem : public GraphicsSystem {
|
||||||
std::string name() const override;
|
std::string name() const override;
|
||||||
|
|
||||||
X_STATUS Setup(cpu::Processor* processor, kernel::KernelState* kernel_state,
|
X_STATUS Setup(cpu::Processor* processor, kernel::KernelState* kernel_state,
|
||||||
ui::Window* target_window) override;
|
ui::WindowedAppContext* app_context,
|
||||||
void Shutdown() override;
|
bool is_surface_required) 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);
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
std::unique_ptr<CommandProcessor> CreateCommandProcessor() override;
|
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
|
} // namespace d3d12
|
||||||
|
|
|
@ -33,7 +33,7 @@ bool D3D12PrimitiveProcessor::Initialize() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
frame_index_buffer_pool_ = std::make_unique<ui::d3d12::D3D12UploadBufferPool>(
|
frame_index_buffer_pool_ = std::make_unique<ui::d3d12::D3D12UploadBufferPool>(
|
||||||
command_processor_.GetD3D12Context().GetD3D12Provider(),
|
command_processor_.GetD3D12Provider(),
|
||||||
std::max(size_t(kMinRequiredConvertedIndexBufferSize),
|
std::max(size_t(kMinRequiredConvertedIndexBufferSize),
|
||||||
ui::GraphicsUploadBufferPool::kDefaultPageSize));
|
ui::GraphicsUploadBufferPool::kDefaultPageSize));
|
||||||
return true;
|
return true;
|
||||||
|
@ -90,7 +90,7 @@ bool D3D12PrimitiveProcessor::InitializeBuiltin16BitIndexBuffer(
|
||||||
assert_null(builtin_index_buffer_upload_);
|
assert_null(builtin_index_buffer_upload_);
|
||||||
|
|
||||||
const ui::d3d12::D3D12Provider& provider =
|
const ui::d3d12::D3D12Provider& provider =
|
||||||
command_processor_.GetD3D12Context().GetD3D12Provider();
|
command_processor_.GetD3D12Provider();
|
||||||
ID3D12Device* device = provider.GetDevice();
|
ID3D12Device* device = provider.GetDevice();
|
||||||
|
|
||||||
D3D12_RESOURCE_DESC resource_desc;
|
D3D12_RESOURCE_DESC resource_desc;
|
||||||
|
|
|
@ -215,7 +215,7 @@ D3D12RenderTargetCache::~D3D12RenderTargetCache() { Shutdown(true); }
|
||||||
|
|
||||||
bool D3D12RenderTargetCache::Initialize() {
|
bool D3D12RenderTargetCache::Initialize() {
|
||||||
const ui::d3d12::D3D12Provider& provider =
|
const ui::d3d12::D3D12Provider& provider =
|
||||||
command_processor_.GetD3D12Context().GetD3D12Provider();
|
command_processor_.GetD3D12Provider();
|
||||||
ID3D12Device* device = provider.GetDevice();
|
ID3D12Device* device = provider.GetDevice();
|
||||||
|
|
||||||
if (cvars::render_target_path_d3d12 == "rtv") {
|
if (cvars::render_target_path_d3d12 == "rtv") {
|
||||||
|
@ -1298,7 +1298,7 @@ bool D3D12RenderTargetCache::Update(bool is_rasterization_done,
|
||||||
void D3D12RenderTargetCache::WriteEdramRawSRVDescriptor(
|
void D3D12RenderTargetCache::WriteEdramRawSRVDescriptor(
|
||||||
D3D12_CPU_DESCRIPTOR_HANDLE handle) {
|
D3D12_CPU_DESCRIPTOR_HANDLE handle) {
|
||||||
const ui::d3d12::D3D12Provider& provider =
|
const ui::d3d12::D3D12Provider& provider =
|
||||||
command_processor_.GetD3D12Context().GetD3D12Provider();
|
command_processor_.GetD3D12Provider();
|
||||||
ID3D12Device* device = provider.GetDevice();
|
ID3D12Device* device = provider.GetDevice();
|
||||||
device->CopyDescriptorsSimple(
|
device->CopyDescriptorsSimple(
|
||||||
1, handle,
|
1, handle,
|
||||||
|
@ -1311,7 +1311,7 @@ void D3D12RenderTargetCache::WriteEdramRawSRVDescriptor(
|
||||||
void D3D12RenderTargetCache::WriteEdramRawUAVDescriptor(
|
void D3D12RenderTargetCache::WriteEdramRawUAVDescriptor(
|
||||||
D3D12_CPU_DESCRIPTOR_HANDLE handle) {
|
D3D12_CPU_DESCRIPTOR_HANDLE handle) {
|
||||||
const ui::d3d12::D3D12Provider& provider =
|
const ui::d3d12::D3D12Provider& provider =
|
||||||
command_processor_.GetD3D12Context().GetD3D12Provider();
|
command_processor_.GetD3D12Provider();
|
||||||
ID3D12Device* device = provider.GetDevice();
|
ID3D12Device* device = provider.GetDevice();
|
||||||
device->CopyDescriptorsSimple(
|
device->CopyDescriptorsSimple(
|
||||||
1, handle,
|
1, handle,
|
||||||
|
@ -1339,7 +1339,7 @@ void D3D12RenderTargetCache::WriteEdramUintPow2SRVDescriptor(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const ui::d3d12::D3D12Provider& provider =
|
const ui::d3d12::D3D12Provider& provider =
|
||||||
command_processor_.GetD3D12Context().GetD3D12Provider();
|
command_processor_.GetD3D12Provider();
|
||||||
ID3D12Device* device = provider.GetDevice();
|
ID3D12Device* device = provider.GetDevice();
|
||||||
device->CopyDescriptorsSimple(
|
device->CopyDescriptorsSimple(
|
||||||
1, handle,
|
1, handle,
|
||||||
|
@ -1366,7 +1366,7 @@ void D3D12RenderTargetCache::WriteEdramUintPow2UAVDescriptor(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const ui::d3d12::D3D12Provider& provider =
|
const ui::d3d12::D3D12Provider& provider =
|
||||||
command_processor_.GetD3D12Context().GetD3D12Provider();
|
command_processor_.GetD3D12Provider();
|
||||||
ID3D12Device* device = provider.GetDevice();
|
ID3D12Device* device = provider.GetDevice();
|
||||||
device->CopyDescriptorsSimple(
|
device->CopyDescriptorsSimple(
|
||||||
1, handle,
|
1, handle,
|
||||||
|
@ -1668,8 +1668,9 @@ bool D3D12RenderTargetCache::InitializeTraceSubmitDownloads() {
|
||||||
ui::d3d12::util::FillBufferResourceDesc(edram_snapshot_download_buffer_desc,
|
ui::d3d12::util::FillBufferResourceDesc(edram_snapshot_download_buffer_desc,
|
||||||
xenos::kEdramSizeBytes,
|
xenos::kEdramSizeBytes,
|
||||||
D3D12_RESOURCE_FLAG_NONE);
|
D3D12_RESOURCE_FLAG_NONE);
|
||||||
auto& provider = command_processor_.GetD3D12Context().GetD3D12Provider();
|
const ui::d3d12::D3D12Provider& provider =
|
||||||
auto device = provider.GetDevice();
|
command_processor_.GetD3D12Provider();
|
||||||
|
ID3D12Device* device = provider.GetDevice();
|
||||||
if (FAILED(device->CreateCommittedResource(
|
if (FAILED(device->CreateCommittedResource(
|
||||||
&ui::d3d12::util::kHeapPropertiesReadback,
|
&ui::d3d12::util::kHeapPropertiesReadback,
|
||||||
provider.GetHeapFlagCreateNotZeroed(),
|
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
|
// Create the buffer - will be used for copying to either a 32-bit 1280x2048
|
||||||
// render target or the EDRAM buffer.
|
// 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_) {
|
if (!edram_snapshot_restore_pool_) {
|
||||||
edram_snapshot_restore_pool_ =
|
edram_snapshot_restore_pool_ =
|
||||||
std::make_unique<ui::d3d12::D3D12UploadBufferPool>(
|
std::make_unique<ui::d3d12::D3D12UploadBufferPool>(
|
||||||
|
@ -1966,8 +1968,7 @@ DXGI_FORMAT D3D12RenderTargetCache::GetDepthSRVStencilDXGIFormat(
|
||||||
|
|
||||||
RenderTargetCache::RenderTarget* D3D12RenderTargetCache::CreateRenderTarget(
|
RenderTargetCache::RenderTarget* D3D12RenderTargetCache::CreateRenderTarget(
|
||||||
RenderTargetKey key) {
|
RenderTargetKey key) {
|
||||||
ID3D12Device* device =
|
ID3D12Device* device = command_processor_.GetD3D12Provider().GetDevice();
|
||||||
command_processor_.GetD3D12Context().GetD3D12Provider().GetDevice();
|
|
||||||
|
|
||||||
D3D12_RESOURCE_DESC resource_desc;
|
D3D12_RESOURCE_DESC resource_desc;
|
||||||
resource_desc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
|
resource_desc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
|
||||||
|
@ -4345,8 +4346,7 @@ D3D12RenderTargetCache::GetOrCreateTransferPipelines(TransferShaderKey key) {
|
||||||
// ***************************************************************************
|
// ***************************************************************************
|
||||||
|
|
||||||
ID3D12PipelineState* const* pipelines;
|
ID3D12PipelineState* const* pipelines;
|
||||||
ID3D12Device* device =
|
ID3D12Device* device = command_processor_.GetD3D12Provider().GetDevice();
|
||||||
command_processor_.GetD3D12Context().GetD3D12Provider().GetDevice();
|
|
||||||
D3D12_INPUT_ELEMENT_DESC pipeline_input_element_desc;
|
D3D12_INPUT_ELEMENT_DESC pipeline_input_element_desc;
|
||||||
pipeline_input_element_desc.SemanticName = "POSITION";
|
pipeline_input_element_desc.SemanticName = "POSITION";
|
||||||
pipeline_input_element_desc.SemanticIndex = 0;
|
pipeline_input_element_desc.SemanticIndex = 0;
|
||||||
|
@ -4516,7 +4516,7 @@ void D3D12RenderTargetCache::PerformTransfersAndResolveClears(
|
||||||
assert_true(GetPath() == Path::kHostRenderTargets);
|
assert_true(GetPath() == Path::kHostRenderTargets);
|
||||||
|
|
||||||
const ui::d3d12::D3D12Provider& provider =
|
const ui::d3d12::D3D12Provider& provider =
|
||||||
command_processor_.GetD3D12Context().GetD3D12Provider();
|
command_processor_.GetD3D12Provider();
|
||||||
ID3D12Device* device = provider.GetDevice();
|
ID3D12Device* device = provider.GetDevice();
|
||||||
uint64_t current_submission = command_processor_.GetCurrentSubmission();
|
uint64_t current_submission = command_processor_.GetCurrentSubmission();
|
||||||
DeferredCommandList& command_list =
|
DeferredCommandList& command_list =
|
||||||
|
@ -6476,8 +6476,8 @@ ID3D12PipelineState* D3D12RenderTargetCache::GetOrCreateDumpPipeline(
|
||||||
// Pipeline
|
// Pipeline
|
||||||
// ***************************************************************************
|
// ***************************************************************************
|
||||||
ID3D12PipelineState* pipeline = ui::d3d12::util::CreateComputePipeline(
|
ID3D12PipelineState* pipeline = ui::d3d12::util::CreateComputePipeline(
|
||||||
command_processor_.GetD3D12Context().GetD3D12Provider().GetDevice(),
|
command_processor_.GetD3D12Provider().GetDevice(), built_shader_.data(),
|
||||||
built_shader_.data(), built_shader_size_bytes,
|
built_shader_size_bytes,
|
||||||
key.is_depth ? dump_root_signature_depth_ : dump_root_signature_color_);
|
key.is_depth ? dump_root_signature_depth_ : dump_root_signature_color_);
|
||||||
const char* format_name =
|
const char* format_name =
|
||||||
key.is_depth
|
key.is_depth
|
||||||
|
@ -6561,7 +6561,7 @@ void D3D12RenderTargetCache::DumpRenderTargets(uint32_t dump_base,
|
||||||
// 32bpp and 64bpp.
|
// 32bpp and 64bpp.
|
||||||
size_t edram_uav_indices[2] = {SIZE_MAX, SIZE_MAX};
|
size_t edram_uav_indices[2] = {SIZE_MAX, SIZE_MAX};
|
||||||
const ui::d3d12::D3D12Provider& provider =
|
const ui::d3d12::D3D12Provider& provider =
|
||||||
command_processor_.GetD3D12Context().GetD3D12Provider();
|
command_processor_.GetD3D12Provider();
|
||||||
if (!bindless_resources_used_) {
|
if (!bindless_resources_used_) {
|
||||||
if (any_sources_32bpp_64bpp[0]) {
|
if (any_sources_32bpp_64bpp[0]) {
|
||||||
edram_uav_indices[0] = current_temporary_descriptors_cpu_.size();
|
edram_uav_indices[0] = current_temporary_descriptors_cpu_.size();
|
||||||
|
|
|
@ -43,7 +43,7 @@ bool D3D12SharedMemory::Initialize() {
|
||||||
InitializeCommon();
|
InitializeCommon();
|
||||||
|
|
||||||
const ui::d3d12::D3D12Provider& provider =
|
const ui::d3d12::D3D12Provider& provider =
|
||||||
command_processor_.GetD3D12Context().GetD3D12Provider();
|
command_processor_.GetD3D12Provider();
|
||||||
ID3D12Device* device = provider.GetDevice();
|
ID3D12Device* device = provider.GetDevice();
|
||||||
|
|
||||||
D3D12_RESOURCE_DESC buffer_desc;
|
D3D12_RESOURCE_DESC buffer_desc;
|
||||||
|
@ -215,8 +215,9 @@ void D3D12SharedMemory::CommitUAVWritesAndTransitionBuffer(
|
||||||
|
|
||||||
void D3D12SharedMemory::WriteRawSRVDescriptor(
|
void D3D12SharedMemory::WriteRawSRVDescriptor(
|
||||||
D3D12_CPU_DESCRIPTOR_HANDLE handle) {
|
D3D12_CPU_DESCRIPTOR_HANDLE handle) {
|
||||||
auto& provider = command_processor_.GetD3D12Context().GetD3D12Provider();
|
const ui::d3d12::D3D12Provider& provider =
|
||||||
auto device = provider.GetDevice();
|
command_processor_.GetD3D12Provider();
|
||||||
|
ID3D12Device* device = provider.GetDevice();
|
||||||
device->CopyDescriptorsSimple(
|
device->CopyDescriptorsSimple(
|
||||||
1, handle,
|
1, handle,
|
||||||
provider.OffsetViewDescriptor(buffer_descriptor_heap_start_,
|
provider.OffsetViewDescriptor(buffer_descriptor_heap_start_,
|
||||||
|
@ -226,8 +227,9 @@ void D3D12SharedMemory::WriteRawSRVDescriptor(
|
||||||
|
|
||||||
void D3D12SharedMemory::WriteRawUAVDescriptor(
|
void D3D12SharedMemory::WriteRawUAVDescriptor(
|
||||||
D3D12_CPU_DESCRIPTOR_HANDLE handle) {
|
D3D12_CPU_DESCRIPTOR_HANDLE handle) {
|
||||||
auto& provider = command_processor_.GetD3D12Context().GetD3D12Provider();
|
const ui::d3d12::D3D12Provider& provider =
|
||||||
auto device = provider.GetDevice();
|
command_processor_.GetD3D12Provider();
|
||||||
|
ID3D12Device* device = provider.GetDevice();
|
||||||
device->CopyDescriptorsSimple(
|
device->CopyDescriptorsSimple(
|
||||||
1, handle,
|
1, handle,
|
||||||
provider.OffsetViewDescriptor(buffer_descriptor_heap_start_,
|
provider.OffsetViewDescriptor(buffer_descriptor_heap_start_,
|
||||||
|
@ -252,8 +254,9 @@ void D3D12SharedMemory::WriteUintPow2SRVDescriptor(
|
||||||
assert_unhandled_case(element_size_bytes_pow2);
|
assert_unhandled_case(element_size_bytes_pow2);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
auto& provider = command_processor_.GetD3D12Context().GetD3D12Provider();
|
const ui::d3d12::D3D12Provider& provider =
|
||||||
auto device = provider.GetDevice();
|
command_processor_.GetD3D12Provider();
|
||||||
|
ID3D12Device* device = provider.GetDevice();
|
||||||
device->CopyDescriptorsSimple(
|
device->CopyDescriptorsSimple(
|
||||||
1, handle,
|
1, handle,
|
||||||
provider.OffsetViewDescriptor(buffer_descriptor_heap_start_,
|
provider.OffsetViewDescriptor(buffer_descriptor_heap_start_,
|
||||||
|
@ -278,8 +281,9 @@ void D3D12SharedMemory::WriteUintPow2UAVDescriptor(
|
||||||
assert_unhandled_case(element_size_bytes_pow2);
|
assert_unhandled_case(element_size_bytes_pow2);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
auto& provider = command_processor_.GetD3D12Context().GetD3D12Provider();
|
const ui::d3d12::D3D12Provider& provider =
|
||||||
auto device = provider.GetDevice();
|
command_processor_.GetD3D12Provider();
|
||||||
|
ID3D12Device* device = provider.GetDevice();
|
||||||
device->CopyDescriptorsSimple(
|
device->CopyDescriptorsSimple(
|
||||||
1, handle,
|
1, handle,
|
||||||
provider.OffsetViewDescriptor(buffer_descriptor_heap_start_,
|
provider.OffsetViewDescriptor(buffer_descriptor_heap_start_,
|
||||||
|
@ -298,8 +302,9 @@ bool D3D12SharedMemory::InitializeTraceSubmitDownloads() {
|
||||||
ui::d3d12::util::FillBufferResourceDesc(
|
ui::d3d12::util::FillBufferResourceDesc(
|
||||||
download_buffer_desc, download_page_count << page_size_log2(),
|
download_buffer_desc, download_page_count << page_size_log2(),
|
||||||
D3D12_RESOURCE_FLAG_NONE);
|
D3D12_RESOURCE_FLAG_NONE);
|
||||||
auto& provider = command_processor_.GetD3D12Context().GetD3D12Provider();
|
const ui::d3d12::D3D12Provider& provider =
|
||||||
auto device = provider.GetDevice();
|
command_processor_.GetD3D12Provider();
|
||||||
|
ID3D12Device* device = provider.GetDevice();
|
||||||
if (FAILED(device->CreateCommittedResource(
|
if (FAILED(device->CreateCommittedResource(
|
||||||
&ui::d3d12::util::kHeapPropertiesReadback,
|
&ui::d3d12::util::kHeapPropertiesReadback,
|
||||||
provider.GetHeapFlagCreateNotZeroed(), &download_buffer_desc,
|
provider.GetHeapFlagCreateNotZeroed(), &download_buffer_desc,
|
||||||
|
@ -365,7 +370,7 @@ bool D3D12SharedMemory::AllocateSparseHostGpuMemoryRange(
|
||||||
<< host_gpu_memory_sparse_granularity_log2();
|
<< host_gpu_memory_sparse_granularity_log2();
|
||||||
|
|
||||||
const ui::d3d12::D3D12Provider& provider =
|
const ui::d3d12::D3D12Provider& provider =
|
||||||
command_processor_.GetD3D12Context().GetD3D12Provider();
|
command_processor_.GetD3D12Provider();
|
||||||
ID3D12Device* device = provider.GetDevice();
|
ID3D12Device* device = provider.GetDevice();
|
||||||
ID3D12CommandQueue* direct_queue = provider.GetDirectQueue();
|
ID3D12CommandQueue* direct_queue = provider.GetDirectQueue();
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "xenia/base/assert.h"
|
#include "xenia/base/assert.h"
|
||||||
|
#include "xenia/base/literals.h"
|
||||||
#include "xenia/base/math.h"
|
#include "xenia/base/math.h"
|
||||||
#include "xenia/ui/d3d12/d3d12_api.h"
|
#include "xenia/ui/d3d12/d3d12_api.h"
|
||||||
|
|
||||||
|
@ -23,12 +24,14 @@ namespace xe {
|
||||||
namespace gpu {
|
namespace gpu {
|
||||||
namespace d3d12 {
|
namespace d3d12 {
|
||||||
|
|
||||||
|
using namespace xe::literals;
|
||||||
|
|
||||||
class D3D12CommandProcessor;
|
class D3D12CommandProcessor;
|
||||||
|
|
||||||
class DeferredCommandList {
|
class DeferredCommandList {
|
||||||
public:
|
public:
|
||||||
DeferredCommandList(const D3D12CommandProcessor& command_processor,
|
DeferredCommandList(const D3D12CommandProcessor& command_processor,
|
||||||
size_t initial_size_bytes = 1024 * 1024);
|
size_t initial_size_bytes = 1_MiB);
|
||||||
|
|
||||||
void Reset();
|
void Reset();
|
||||||
void Execute(ID3D12GraphicsCommandList* command_list,
|
void Execute(ID3D12GraphicsCommandList* command_list,
|
||||||
|
|
|
@ -87,7 +87,8 @@ PipelineCache::PipelineCache(D3D12CommandProcessor& command_processor,
|
||||||
register_file_(register_file),
|
register_file_(register_file),
|
||||||
render_target_cache_(render_target_cache),
|
render_target_cache_(render_target_cache),
|
||||||
bindless_resources_used_(bindless_resources_used) {
|
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() ==
|
bool edram_rov_used = render_target_cache.GetPath() ==
|
||||||
RenderTargetCache::Path::kPixelShaderInterlock;
|
RenderTargetCache::Path::kPixelShaderInterlock;
|
||||||
|
@ -109,7 +110,8 @@ PipelineCache::PipelineCache(D3D12CommandProcessor& command_processor,
|
||||||
PipelineCache::~PipelineCache() { Shutdown(); }
|
PipelineCache::~PipelineCache() { Shutdown(); }
|
||||||
|
|
||||||
bool PipelineCache::Initialize() {
|
bool PipelineCache::Initialize() {
|
||||||
auto& provider = command_processor_.GetD3D12Context().GetD3D12Provider();
|
const ui::d3d12::D3D12Provider& provider =
|
||||||
|
command_processor_.GetD3D12Provider();
|
||||||
|
|
||||||
// Initialize the command processor thread DXIL objects.
|
// Initialize the command processor thread DXIL objects.
|
||||||
dxbc_converter_ = nullptr;
|
dxbc_converter_ = nullptr;
|
||||||
|
@ -414,7 +416,8 @@ void PipelineCache::InitializeShaderStorage(
|
||||||
std::mutex shaders_failed_to_translate_mutex;
|
std::mutex shaders_failed_to_translate_mutex;
|
||||||
std::vector<D3D12Shader::D3D12Translation*> shaders_failed_to_translate;
|
std::vector<D3D12Shader::D3D12Translation*> shaders_failed_to_translate;
|
||||||
auto shader_translation_thread_function = [&]() {
|
auto shader_translation_thread_function = [&]() {
|
||||||
auto& provider = command_processor_.GetD3D12Context().GetD3D12Provider();
|
const ui::d3d12::D3D12Provider& provider =
|
||||||
|
command_processor_.GetD3D12Provider();
|
||||||
StringBuffer ucode_disasm_buffer;
|
StringBuffer ucode_disasm_buffer;
|
||||||
DxbcShaderTranslator translator(
|
DxbcShaderTranslator translator(
|
||||||
provider.GetAdapterVendorID(), bindless_resources_used_,
|
provider.GetAdapterVendorID(), bindless_resources_used_,
|
||||||
|
@ -700,37 +703,39 @@ void PipelineCache::InitializeShaderStorage(
|
||||||
++pipelines_created;
|
++pipelines_created;
|
||||||
}
|
}
|
||||||
|
|
||||||
CreateQueuedPipelinesOnProcessorThread();
|
if (!creation_threads_.empty()) {
|
||||||
if (creation_threads_.size() > creation_thread_original_count) {
|
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;
|
std::lock_guard<std::mutex> lock(creation_request_lock_);
|
||||||
// Assuming the queue is empty because of
|
creation_threads_shutdown_from_ = creation_thread_original_count;
|
||||||
// CreateQueuedPipelinesOnProcessorThread.
|
// Assuming the queue is empty because of
|
||||||
}
|
// CreateQueuedPipelinesOnProcessorThread.
|
||||||
creation_request_cond_.notify_all();
|
}
|
||||||
while (creation_threads_.size() > creation_thread_original_count) {
|
creation_request_cond_.notify_all();
|
||||||
xe::threading::Wait(creation_threads_.back().get(), false);
|
while (creation_threads_.size() > creation_thread_original_count) {
|
||||||
creation_threads_.pop_back();
|
xe::threading::Wait(creation_threads_.back().get(), false);
|
||||||
}
|
creation_threads_.pop_back();
|
||||||
bool await_creation_completion_event;
|
}
|
||||||
{
|
bool await_creation_completion_event;
|
||||||
// Cleanup so additional threads can be created later again.
|
{
|
||||||
std::lock_guard<std::mutex> lock(creation_request_lock_);
|
// Cleanup so additional threads can be created later again.
|
||||||
creation_threads_shutdown_from_ = SIZE_MAX;
|
std::lock_guard<std::mutex> lock(creation_request_lock_);
|
||||||
// If the invocation is blocking, all the shader storage initialization
|
creation_threads_shutdown_from_ = SIZE_MAX;
|
||||||
// is expected to be done before proceeding, to avoid latency in the
|
// If the invocation is blocking, all the shader storage
|
||||||
// command processor after the invocation.
|
// initialization is expected to be done before proceeding, to avoid
|
||||||
await_creation_completion_event =
|
// latency in the command processor after the invocation.
|
||||||
blocking && creation_threads_busy_ != 0;
|
await_creation_completion_event =
|
||||||
if (await_creation_completion_event) {
|
blocking && creation_threads_busy_ != 0;
|
||||||
creation_completion_event_->Reset();
|
if (await_creation_completion_event) {
|
||||||
creation_completion_set_event_ = true;
|
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.
|
// 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) {
|
if (cvars::d3d12_dxbc_disasm_dxilconv) {
|
||||||
translation.DisassembleDxbcAndDxil(provider, cvars::d3d12_dxbc_disasm,
|
translation.DisassembleDxbcAndDxil(provider, cvars::d3d12_dxbc_disasm,
|
||||||
dxbc_converter, dxc_utils, dxc_compiler);
|
dxbc_converter, dxc_utils, dxc_compiler);
|
||||||
|
@ -2052,8 +2058,7 @@ ID3D12PipelineState* PipelineCache::CreateD3D12Pipeline(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the D3D12 pipeline state object.
|
// Create the D3D12 pipeline state object.
|
||||||
auto device =
|
ID3D12Device* device = command_processor_.GetD3D12Provider().GetDevice();
|
||||||
command_processor_.GetD3D12Context().GetD3D12Provider().GetDevice();
|
|
||||||
ID3D12PipelineState* state;
|
ID3D12PipelineState* state;
|
||||||
if (FAILED(device->CreateGraphicsPipelineState(&state_desc,
|
if (FAILED(device->CreateGraphicsPipelineState(&state_desc,
|
||||||
IID_PPV_ARGS(&state)))) {
|
IID_PPV_ARGS(&state)))) {
|
||||||
|
|
|
@ -869,8 +869,9 @@ TextureCache::TextureCache(D3D12CommandProcessor& command_processor,
|
||||||
TextureCache::~TextureCache() { Shutdown(); }
|
TextureCache::~TextureCache() { Shutdown(); }
|
||||||
|
|
||||||
bool TextureCache::Initialize() {
|
bool TextureCache::Initialize() {
|
||||||
auto& provider = command_processor_.GetD3D12Context().GetD3D12Provider();
|
const ui::d3d12::D3D12Provider& provider =
|
||||||
auto device = provider.GetDevice();
|
command_processor_.GetD3D12Provider();
|
||||||
|
ID3D12Device* device = provider.GetDevice();
|
||||||
|
|
||||||
if (IsDrawResolutionScaled()) {
|
if (IsDrawResolutionScaled()) {
|
||||||
// Buffers not used yet - no need aliasing barriers to change ownership of
|
// 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;
|
D3D12_CPU_DESCRIPTOR_HANDLE source_handle;
|
||||||
if (descriptor_index != UINT32_MAX) {
|
if (descriptor_index != UINT32_MAX) {
|
||||||
assert_not_null(texture);
|
assert_not_null(texture);
|
||||||
|
@ -1622,8 +1624,7 @@ void TextureCache::WriteSampler(SamplerParameters parameters,
|
||||||
desc.MinLOD = float(parameters.mip_min_level);
|
desc.MinLOD = float(parameters.mip_min_level);
|
||||||
// Maximum mip level is in the texture resource itself.
|
// Maximum mip level is in the texture resource itself.
|
||||||
desc.MaxLOD = FLT_MAX;
|
desc.MaxLOD = FLT_MAX;
|
||||||
auto device =
|
ID3D12Device* device = command_processor_.GetD3D12Provider().GetDevice();
|
||||||
command_processor_.GetD3D12Context().GetD3D12Provider().GetDevice();
|
|
||||||
device->CreateSampler(&desc, handle);
|
device->CreateSampler(&desc, handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1712,8 +1713,9 @@ bool TextureCache::EnsureScaledResolveMemoryCommitted(
|
||||||
uint64_t last_scaled = uint64_t(start_unscaled + (length_unscaled - 1)) *
|
uint64_t last_scaled = uint64_t(start_unscaled + (length_unscaled - 1)) *
|
||||||
draw_resolution_scale_area;
|
draw_resolution_scale_area;
|
||||||
|
|
||||||
auto& provider = command_processor_.GetD3D12Context().GetD3D12Provider();
|
const ui::d3d12::D3D12Provider& provider =
|
||||||
auto device = provider.GetDevice();
|
command_processor_.GetD3D12Provider();
|
||||||
|
ID3D12Device* device = provider.GetDevice();
|
||||||
|
|
||||||
// Ensure GPU virtual memory for buffers that may be used to access the range
|
// 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
|
// is allocated - buffers are created. Always creating both buffers for all
|
||||||
|
@ -1943,8 +1945,8 @@ void TextureCache::CreateCurrentScaledResolveRangeUintPow2SRV(
|
||||||
scaled_resolve_2gb_buffers_[buffer_index];
|
scaled_resolve_2gb_buffers_[buffer_index];
|
||||||
assert_not_null(buffer);
|
assert_not_null(buffer);
|
||||||
ui::d3d12::util::CreateBufferTypedSRV(
|
ui::d3d12::util::CreateBufferTypedSRV(
|
||||||
command_processor_.GetD3D12Context().GetD3D12Provider().GetDevice(),
|
command_processor_.GetD3D12Provider().GetDevice(), handle,
|
||||||
handle, buffer->resource(),
|
buffer->resource(),
|
||||||
ui::d3d12::util::GetUintPow2DXGIFormat(element_size_bytes_pow2),
|
ui::d3d12::util::GetUintPow2DXGIFormat(element_size_bytes_pow2),
|
||||||
uint32_t(scaled_resolve_current_range_length_scaled_ >>
|
uint32_t(scaled_resolve_current_range_length_scaled_ >>
|
||||||
element_size_bytes_pow2),
|
element_size_bytes_pow2),
|
||||||
|
@ -1961,8 +1963,8 @@ void TextureCache::CreateCurrentScaledResolveRangeUintPow2UAV(
|
||||||
scaled_resolve_2gb_buffers_[buffer_index];
|
scaled_resolve_2gb_buffers_[buffer_index];
|
||||||
assert_not_null(buffer);
|
assert_not_null(buffer);
|
||||||
ui::d3d12::util::CreateBufferTypedUAV(
|
ui::d3d12::util::CreateBufferTypedUAV(
|
||||||
command_processor_.GetD3D12Context().GetD3D12Provider().GetDevice(),
|
command_processor_.GetD3D12Provider().GetDevice(), handle,
|
||||||
handle, buffer->resource(),
|
buffer->resource(),
|
||||||
ui::d3d12::util::GetUintPow2DXGIFormat(element_size_bytes_pow2),
|
ui::d3d12::util::GetUintPow2DXGIFormat(element_size_bytes_pow2),
|
||||||
uint32_t(scaled_resolve_current_range_length_scaled_ >>
|
uint32_t(scaled_resolve_current_range_length_scaled_ >>
|
||||||
element_size_bytes_pow2),
|
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
|
// Untiling through a buffer instead of using unordered access because copying
|
||||||
// is not done that often.
|
// is not done that often.
|
||||||
desc.Flags = D3D12_RESOURCE_FLAG_NONE;
|
desc.Flags = D3D12_RESOURCE_FLAG_NONE;
|
||||||
auto& provider = command_processor_.GetD3D12Context().GetD3D12Provider();
|
const ui::d3d12::D3D12Provider& provider =
|
||||||
auto device = provider.GetDevice();
|
command_processor_.GetD3D12Provider();
|
||||||
|
ID3D12Device* device = provider.GetDevice();
|
||||||
// Assuming untiling will be the next operation.
|
// Assuming untiling will be the next operation.
|
||||||
D3D12_RESOURCE_STATES state = D3D12_RESOURCE_STATE_COPY_DEST;
|
D3D12_RESOURCE_STATES state = D3D12_RESOURCE_STATE_COPY_DEST;
|
||||||
ID3D12Resource* resource;
|
ID3D12Resource* resource;
|
||||||
|
@ -2317,9 +2320,9 @@ bool TextureCache::LoadTextureData(Texture* texture) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto& command_list = command_processor_.GetDeferredCommandList();
|
DeferredCommandList& command_list =
|
||||||
auto device =
|
command_processor_.GetDeferredCommandList();
|
||||||
command_processor_.GetD3D12Context().GetD3D12Provider().GetDevice();
|
ID3D12Device* device = command_processor_.GetD3D12Provider().GetDevice();
|
||||||
|
|
||||||
// Get the pipeline.
|
// Get the pipeline.
|
||||||
LoadMode load_mode = GetLoadMode(texture->key);
|
LoadMode load_mode = GetLoadMode(texture->key);
|
||||||
|
@ -2875,8 +2878,7 @@ uint32_t TextureCache::FindOrCreateTextureDescriptor(Texture& texture,
|
||||||
host_swizzle |
|
host_swizzle |
|
||||||
D3D12_SHADER_COMPONENT_MAPPING_ALWAYS_SET_BIT_AVOIDING_ZEROMEM_MISTAKES;
|
D3D12_SHADER_COMPONENT_MAPPING_ALWAYS_SET_BIT_AVOIDING_ZEROMEM_MISTAKES;
|
||||||
|
|
||||||
auto device =
|
ID3D12Device* device = command_processor_.GetD3D12Provider().GetDevice();
|
||||||
command_processor_.GetD3D12Context().GetD3D12Provider().GetDevice();
|
|
||||||
uint32_t descriptor_index;
|
uint32_t descriptor_index;
|
||||||
if (bindless_resources_used_) {
|
if (bindless_resources_used_) {
|
||||||
descriptor_index =
|
descriptor_index =
|
||||||
|
@ -2928,7 +2930,8 @@ uint32_t TextureCache::FindOrCreateTextureDescriptor(Texture& texture,
|
||||||
|
|
||||||
D3D12_CPU_DESCRIPTOR_HANDLE TextureCache::GetTextureDescriptorCPUHandle(
|
D3D12_CPU_DESCRIPTOR_HANDLE TextureCache::GetTextureDescriptorCPUHandle(
|
||||||
uint32_t descriptor_index) const {
|
uint32_t descriptor_index) const {
|
||||||
auto& provider = command_processor_.GetD3D12Context().GetD3D12Provider();
|
const ui::d3d12::D3D12Provider& provider =
|
||||||
|
command_processor_.GetD3D12Provider();
|
||||||
if (bindless_resources_used_) {
|
if (bindless_resources_used_) {
|
||||||
return provider.OffsetViewDescriptor(
|
return provider.OffsetViewDescriptor(
|
||||||
command_processor_.GetViewBindlessHeapCPUStart(), descriptor_index);
|
command_processor_.GetViewBindlessHeapCPUStart(), descriptor_index);
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
#include "xenia/gpu/texture_info.h"
|
#include "xenia/gpu/texture_info.h"
|
||||||
#include "xenia/gpu/texture_util.h"
|
#include "xenia/gpu/texture_util.h"
|
||||||
#include "xenia/gpu/xenos.h"
|
#include "xenia/gpu/xenos.h"
|
||||||
|
#include "xenia/ui/graphics_util.h"
|
||||||
|
|
||||||
// Very prominent in 545407F2.
|
// Very prominent in 545407F2.
|
||||||
DEFINE_bool(
|
DEFINE_bool(
|
||||||
|
@ -33,85 +34,10 @@ DEFINE_bool(
|
||||||
"for certain games to display the scene graphics).",
|
"for certain games to display the scene graphics).",
|
||||||
"GPU");
|
"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 xe {
|
||||||
namespace gpu {
|
namespace gpu {
|
||||||
namespace draw_util {
|
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 IsRasterizationPotentiallyDone(const RegisterFile& regs,
|
||||||
bool primitive_polygonal) {
|
bool primitive_polygonal) {
|
||||||
// TODO(Triang3l): Investigate ModeControl::kIgnore better, with respect to
|
// 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;
|
regs.Get<reg::PA_SU_VTX_CNTL>().pix_center ? 0.0f : 0.5f;
|
||||||
int32_t vertices_fixed[6];
|
int32_t vertices_fixed[6];
|
||||||
for (size_t i = 0; i < xe::countof(vertices_fixed); ++i) {
|
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);
|
xenos::GpuSwap(vertices_guest[i], fetch.endian) + half_pixel_offset);
|
||||||
}
|
}
|
||||||
// Inclusive.
|
// Inclusive.
|
||||||
|
@ -1151,87 +1077,6 @@ ResolveCopyShaderIndex ResolveInfo::GetCopyShader(
|
||||||
return shader;
|
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 draw_util
|
||||||
} // namespace gpu
|
} // namespace gpu
|
||||||
} // namespace xe
|
} // namespace xe
|
||||||
|
|
|
@ -25,15 +25,6 @@ namespace xe {
|
||||||
namespace gpu {
|
namespace gpu {
|
||||||
namespace draw_util {
|
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
|
// Polygonal primitive types (not including points and lines) are rasterized as
|
||||||
// triangles, have front and back faces, and also support face culling and fill
|
// triangles, have front and back faces, and also support face culling and fill
|
||||||
// modes (polymode_front_ptype, polymode_back_ptype). Other primitive types are
|
// modes (polymode_front_ptype, polymode_back_ptype). Other primitive types are
|
||||||
|
|
|
@ -849,7 +849,7 @@ void DxbcShaderTranslator::StartTranslation() {
|
||||||
system_temp_aL_ = PushSystemTemp(0b1111);
|
system_temp_aL_ = PushSystemTemp(0b1111);
|
||||||
system_temp_loop_count_ = PushSystemTemp(0b1111);
|
system_temp_loop_count_ = PushSystemTemp(0b1111);
|
||||||
system_temp_grad_h_lod_ = 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
|
// Zero general-purpose registers to prevent crashes when the game
|
||||||
// references them after only initializing them conditionally.
|
// references them after only initializing them conditionally.
|
||||||
|
@ -1039,7 +1039,7 @@ void DxbcShaderTranslator::CompleteShaderCode() {
|
||||||
// - system_temp_aL_.
|
// - system_temp_aL_.
|
||||||
// - system_temp_loop_count_.
|
// - system_temp_loop_count_.
|
||||||
// - system_temp_grad_h_lod_.
|
// - system_temp_grad_h_lod_.
|
||||||
// - system_temp_grad_v_.
|
// - system_temp_grad_v_vfetch_address_.
|
||||||
PopSystemTemp(6);
|
PopSystemTemp(6);
|
||||||
|
|
||||||
// Write memexported data to the shared memory UAV.
|
// Write memexported data to the shared memory UAV.
|
||||||
|
|
|
@ -1104,7 +1104,9 @@ class DxbcShaderTranslator : public ShaderTranslator {
|
||||||
uint32_t system_temp_loop_count_;
|
uint32_t system_temp_loop_count_;
|
||||||
// Explicitly set texture gradients and LOD.
|
// Explicitly set texture gradients and LOD.
|
||||||
uint32_t system_temp_grad_h_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
|
// The bool constant number containing the condition for the currently
|
||||||
// processed exec (or the last - unless a label has reset this), or
|
// processed exec (or the last - unless a label has reset this), or
|
||||||
|
|
|
@ -35,7 +35,9 @@ void DxbcShaderTranslator::ProcessVertexFetchInstruction(
|
||||||
uint32_t used_result_components = instr.result.GetUsedResultComponents();
|
uint32_t used_result_components = instr.result.GetUsedResultComponents();
|
||||||
uint32_t needed_words = xenos::GetVertexFormatNeededWords(
|
uint32_t needed_words = xenos::GetVertexFormatNeededWords(
|
||||||
instr.attributes.data_format, used_result_components);
|
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
|
// 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).
|
// components that don't exist in the format (writing zero instead of them).
|
||||||
// Unpacking assumes at least some word is needed.
|
// 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
|
// fetch constants on the CPU when proper bound checks are added - vfetch may
|
||||||
// be conditional, so fetch constants may also be used conditionally.
|
// be conditional, so fetch constants may also be used conditionally.
|
||||||
|
|
||||||
// - Load the byte address in physical memory to system_temp_result_.w (so
|
// - Load the part of the byte address in the physical memory that is the same
|
||||||
// it's not overwritten by data loads until the last one).
|
// 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 address_src(dxbc::Src::R(system_temp_result_, dxbc::Src::kWWWW));
|
dxbc::Src::R(system_temp_grad_v_vfetch_address_, dxbc::Src::kWWWW));
|
||||||
if (instr.attributes.stride) {
|
if (!instr.is_mini_fetch) {
|
||||||
// Convert the index to an integer by flooring or by rounding to the nearest
|
dxbc::Dest address_dest(
|
||||||
// (as floor(index + 0.5) because rounding to the nearest even makes no
|
dxbc::Dest::R(system_temp_grad_v_vfetch_address_, 0b1000));
|
||||||
// sense for addressing, both 1.5 and 2.5 would be 2).
|
if (instr.attributes.stride) {
|
||||||
// http://web.archive.org/web/20100302145413/http://msdn.microsoft.com:80/en-us/library/bb313960.aspx
|
// 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
|
||||||
bool index_operand_temp_pushed = false;
|
// makes no sense for addressing, both 1.5 and 2.5 would be 2).
|
||||||
dxbc::Src index_operand(
|
{
|
||||||
LoadOperand(instr.operands[0], 0b0001, index_operand_temp_pushed)
|
bool index_operand_temp_pushed = false;
|
||||||
.SelectFromSwizzled(0));
|
dxbc::Src index_operand(
|
||||||
if (instr.attributes.is_index_rounded) {
|
LoadOperand(instr.operands[0], 0b0001, index_operand_temp_pushed)
|
||||||
a_.OpAdd(address_dest, index_operand, dxbc::Src::LF(0.5f));
|
.SelectFromSwizzled(0));
|
||||||
a_.OpRoundNI(address_dest, address_src);
|
if (instr.attributes.is_index_rounded) {
|
||||||
} else {
|
a_.OpAdd(address_dest, index_operand, dxbc::Src::LF(0.5f));
|
||||||
a_.OpRoundNI(address_dest, index_operand);
|
a_.OpRoundNI(address_dest, address_src);
|
||||||
}
|
} else {
|
||||||
if (index_operand_temp_pushed) {
|
a_.OpRoundNI(address_dest, index_operand);
|
||||||
PopSystemTemp();
|
}
|
||||||
|
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
|
// Add the word offset from the instruction (signed), plus the offset of the
|
||||||
// first needed word within the element.
|
// first needed word within the element.
|
||||||
uint32_t first_word_index;
|
uint32_t first_word_index;
|
||||||
|
@ -108,8 +137,9 @@ void DxbcShaderTranslator::ProcessVertexFetchInstruction(
|
||||||
instr.attributes.offset + int32_t(first_word_index);
|
instr.attributes.offset + int32_t(first_word_index);
|
||||||
if (first_word_buffer_offset) {
|
if (first_word_buffer_offset) {
|
||||||
// Add the constant word 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)));
|
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
|
// - 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));
|
~((uint32_t(1) << (word_index + word_count)) - uint32_t(1));
|
||||||
if (word_index != word_index_previous) {
|
if (word_index != word_index_previous) {
|
||||||
// Go to the word in the buffer.
|
// 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) *
|
dxbc::Src::LU((word_index - word_index_previous) *
|
||||||
sizeof(uint32_t)));
|
sizeof(uint32_t)));
|
||||||
|
address_src = address_temp_src;
|
||||||
word_index_previous = word_index;
|
word_index_previous = word_index;
|
||||||
}
|
}
|
||||||
// Can ld_raw either to the first multiple components, or to any scalar
|
// Can ld_raw either to the first multiple components, or to any scalar
|
||||||
|
@ -592,7 +623,7 @@ void DxbcShaderTranslator::ProcessTextureFetchInstruction(
|
||||||
case FetchOpcode::kSetTextureGradientsVert: {
|
case FetchOpcode::kSetTextureGradientsVert: {
|
||||||
bool grad_operand_temp_pushed = false;
|
bool grad_operand_temp_pushed = false;
|
||||||
a_.OpMov(
|
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));
|
LoadOperand(instr.operands[0], 0b0111, grad_operand_temp_pushed));
|
||||||
if (grad_operand_temp_pushed) {
|
if (grad_operand_temp_pushed) {
|
||||||
PopSystemTemp();
|
PopSystemTemp();
|
||||||
|
@ -1521,15 +1552,15 @@ void DxbcShaderTranslator::ProcessTextureFetchInstruction(
|
||||||
// Extract gradient exponent biases from the fetch constant and merge
|
// Extract gradient exponent biases from the fetch constant and merge
|
||||||
// them with the LOD bias.
|
// them with the LOD bias.
|
||||||
a_.OpIBFE(dxbc::Dest::R(grad_h_lod_temp, 0b0011), dxbc::Src::LU(5),
|
a_.OpIBFE(dxbc::Dest::R(grad_h_lod_temp, 0b0011), dxbc::Src::LU(5),
|
||||||
dxbc::Src::LU(22, 27, 0, 0),
|
dxbc::Src::LU(22, 27, 0, 0),
|
||||||
RequestTextureFetchConstantWord(tfetch_index, 4));
|
RequestTextureFetchConstantWord(tfetch_index, 4));
|
||||||
a_.OpIMAd(dxbc::Dest::R(grad_h_lod_temp, 0b0011),
|
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::R(grad_h_lod_temp),
|
||||||
dxbc::Src::LF(1.0f));
|
dxbc::Src::LI(int32_t(1) << 23), dxbc::Src::LF(1.0f));
|
||||||
a_.OpMul(dxbc::Dest::R(grad_v_temp, 0b1000), lod_src,
|
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,
|
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
|
#endif
|
||||||
// Obtain the gradients and apply biases to them.
|
// Obtain the gradients and apply biases to them.
|
||||||
if (instr.attributes.use_register_gradients) {
|
if (instr.attributes.use_register_gradients) {
|
||||||
|
@ -1540,11 +1571,11 @@ void DxbcShaderTranslator::ProcessTextureFetchInstruction(
|
||||||
// done in getCompTexLOD, so don't do it here too.
|
// done in getCompTexLOD, so don't do it here too.
|
||||||
#if 0
|
#if 0
|
||||||
a_.OpMul(dxbc::Dest::R(grad_v_temp, grad_mask),
|
a_.OpMul(dxbc::Dest::R(grad_v_temp, grad_mask),
|
||||||
dxbc::Src::R(system_temp_grad_v_),
|
dxbc::Src::R(system_temp_grad_v_vfetch_address_),
|
||||||
dxbc::Src::R(grad_v_temp, dxbc::Src::kWWWW));
|
dxbc::Src::R(grad_v_temp, dxbc::Src::kWWWW));
|
||||||
#else
|
#else
|
||||||
a_.OpMul(dxbc::Dest::R(grad_v_temp, grad_mask),
|
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
|
#endif
|
||||||
// TODO(Triang3l): Are cube map register gradients unnormalized if
|
// TODO(Triang3l): Are cube map register gradients unnormalized if
|
||||||
// the coordinates themselves are unnormalized?
|
// the coordinates themselves are unnormalized?
|
||||||
|
@ -1586,8 +1617,8 @@ void DxbcShaderTranslator::ProcessTextureFetchInstruction(
|
||||||
// done in getCompTexLOD, so don't do it here too.
|
// done in getCompTexLOD, so don't do it here too.
|
||||||
#if 0
|
#if 0
|
||||||
a_.OpMul(dxbc::Dest::R(grad_v_temp, grad_mask),
|
a_.OpMul(dxbc::Dest::R(grad_v_temp, grad_mask),
|
||||||
dxbc::Src::R(grad_v_temp),
|
dxbc::Src::R(grad_v_temp),
|
||||||
dxbc::Src::R(grad_v_temp, dxbc::Src::kWWWW));
|
dxbc::Src::R(grad_v_temp, dxbc::Src::kWWWW));
|
||||||
#else
|
#else
|
||||||
a_.OpMul(dxbc::Dest::R(grad_v_temp, grad_mask),
|
a_.OpMul(dxbc::Dest::R(grad_v_temp, grad_mask),
|
||||||
dxbc::Src::R(grad_v_temp), lod_src);
|
dxbc::Src::R(grad_v_temp), lod_src);
|
||||||
|
|
|
@ -2,13 +2,19 @@
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
* Xenia : Xbox 360 Emulator Research Project *
|
* 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. *
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "xenia/gpu/graphics_system.h"
|
#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/byte_stream.h"
|
||||||
#include "xenia/base/clock.h"
|
#include "xenia/base/clock.h"
|
||||||
#include "xenia/base/logging.h"
|
#include "xenia/base/logging.h"
|
||||||
|
@ -48,70 +54,40 @@ GraphicsSystem::~GraphicsSystem() = default;
|
||||||
|
|
||||||
X_STATUS GraphicsSystem::Setup(cpu::Processor* processor,
|
X_STATUS GraphicsSystem::Setup(cpu::Processor* processor,
|
||||||
kernel::KernelState* kernel_state,
|
kernel::KernelState* kernel_state,
|
||||||
ui::Window* target_window) {
|
ui::WindowedAppContext* app_context,
|
||||||
|
[[maybe_unused]] bool is_surface_required) {
|
||||||
memory_ = processor->memory();
|
memory_ = processor->memory();
|
||||||
processor_ = processor;
|
processor_ = processor;
|
||||||
kernel_state_ = kernel_state;
|
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_) {
|
if (provider_) {
|
||||||
// Setup the context the command processor will do all its drawing in.
|
// Safe if either the UI thread call or the presenter creation fails.
|
||||||
bool contexts_initialized = true;
|
if (app_context_) {
|
||||||
processor_context = provider()->CreateEmulationContext();
|
app_context_->CallInUIThreadSynchronous([this]() {
|
||||||
if (processor_context) {
|
presenter_ = provider_->CreatePresenter(
|
||||||
if (target_window_) {
|
[this](bool is_responsible, bool statically_from_ui_thread) {
|
||||||
if (!target_window_->app_context().CallInUIThreadSynchronous([&]() {
|
OnHostGpuLossFromAnyThread(is_responsible);
|
||||||
// Create the context used for presentation.
|
});
|
||||||
assert_null(target_window->context());
|
});
|
||||||
target_window_->set_context(
|
|
||||||
provider_->CreateHostContext(target_window_));
|
|
||||||
})) {
|
|
||||||
contexts_initialized = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
contexts_initialized = false;
|
// May be needed for offscreen use, such as capturing the guest output
|
||||||
}
|
// image.
|
||||||
if (!contexts_initialized) {
|
presenter_ = provider_->CreatePresenter(
|
||||||
xe::FatalError(
|
[this](bool is_responsible, bool statically_from_ui_thread) {
|
||||||
"Unable to initialize graphics context. Xenia requires Vulkan "
|
OnHostGpuLossFromAnyThread(is_responsible);
|
||||||
"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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create command processor. This will spin up a thread to process all
|
// Create command processor. This will spin up a thread to process all
|
||||||
// incoming ringbuffer packets.
|
// incoming ringbuffer packets.
|
||||||
command_processor_ = CreateCommandProcessor();
|
command_processor_ = CreateCommandProcessor();
|
||||||
if (!command_processor_->Initialize(std::move(processor_context))) {
|
if (!command_processor_->Initialize()) {
|
||||||
XELOGE("Unable to initialize command processor");
|
XELOGE("Unable to initialize command processor");
|
||||||
return X_STATUS_UNSUCCESSFUL;
|
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.
|
// Let the processor know we want register access callbacks.
|
||||||
memory_->AddVirtualMappedRange(
|
memory_->AddVirtualMappedRange(
|
||||||
0x7FC80000, 0xFFFF0000, 0x0000FFFF, this,
|
0x7FC80000, 0xFFFF0000, 0x0000FFFF, this,
|
||||||
|
@ -152,6 +128,7 @@ void GraphicsSystem::Shutdown() {
|
||||||
if (command_processor_) {
|
if (command_processor_) {
|
||||||
EndTracing();
|
EndTracing();
|
||||||
command_processor_->Shutdown();
|
command_processor_->Shutdown();
|
||||||
|
command_processor_.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (vsync_worker_thread_) {
|
if (vsync_worker_thread_) {
|
||||||
|
@ -159,13 +136,35 @@ void GraphicsSystem::Shutdown() {
|
||||||
vsync_worker_thread_->Wait(0, 0, 0, nullptr);
|
vsync_worker_thread_->Wait(0, 0, 0, nullptr);
|
||||||
vsync_worker_thread_.reset();
|
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() {
|
void GraphicsSystem::OnHostGpuLossFromAnyThread(
|
||||||
// TODO(DrChat): Reset the system.
|
[[maybe_unused]] bool is_responsible) {
|
||||||
XELOGI("Context lost; Reset invoked");
|
// TODO(Triang3l): Somehow gain exclusive ownership of the Provider (may be
|
||||||
Shutdown();
|
// 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)");
|
xe::FatalError("Graphics device lost (probably due to an internal error)");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
* Xenia : Xbox 360 Emulator Research Project *
|
* 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. *
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
*/
|
*/
|
||||||
|
@ -11,7 +11,10 @@
|
||||||
#define XENIA_GPU_GRAPHICS_SYSTEM_H_
|
#define XENIA_GPU_GRAPHICS_SYSTEM_H_
|
||||||
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <functional>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
|
@ -19,7 +22,9 @@
|
||||||
#include "xenia/gpu/register_file.h"
|
#include "xenia/gpu/register_file.h"
|
||||||
#include "xenia/kernel/xthread.h"
|
#include "xenia/kernel/xthread.h"
|
||||||
#include "xenia/memory.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"
|
#include "xenia/xbox.h"
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
|
@ -41,14 +46,17 @@ class GraphicsSystem {
|
||||||
cpu::Processor* processor() const { return processor_; }
|
cpu::Processor* processor() const { return processor_; }
|
||||||
kernel::KernelState* kernel_state() const { return kernel_state_; }
|
kernel::KernelState* kernel_state() const { return kernel_state_; }
|
||||||
ui::GraphicsProvider* provider() const { return provider_.get(); }
|
ui::GraphicsProvider* provider() const { return provider_.get(); }
|
||||||
|
ui::Presenter* presenter() const { return presenter_.get(); }
|
||||||
|
|
||||||
virtual X_STATUS Setup(cpu::Processor* processor,
|
virtual X_STATUS Setup(cpu::Processor* processor,
|
||||||
kernel::KernelState* kernel_state,
|
kernel::KernelState* kernel_state,
|
||||||
ui::Window* target_window);
|
ui::WindowedAppContext* app_context,
|
||||||
|
bool is_surface_required);
|
||||||
virtual void Shutdown();
|
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 ®ister_file_; }
|
RegisterFile* register_file() { return ®ister_file_; }
|
||||||
CommandProcessor* command_processor() const {
|
CommandProcessor* command_processor() const {
|
||||||
|
@ -91,12 +99,11 @@ class GraphicsSystem {
|
||||||
void WriteRegister(uint32_t addr, uint32_t value);
|
void WriteRegister(uint32_t addr, uint32_t value);
|
||||||
|
|
||||||
void MarkVblank();
|
void MarkVblank();
|
||||||
virtual void Swap(xe::ui::UIEvent* e) = 0;
|
|
||||||
|
|
||||||
Memory* memory_ = nullptr;
|
Memory* memory_ = nullptr;
|
||||||
cpu::Processor* processor_ = nullptr;
|
cpu::Processor* processor_ = nullptr;
|
||||||
kernel::KernelState* kernel_state_ = nullptr;
|
kernel::KernelState* kernel_state_ = nullptr;
|
||||||
ui::Window* target_window_ = nullptr;
|
ui::WindowedAppContext* app_context_ = nullptr;
|
||||||
std::unique_ptr<ui::GraphicsProvider> provider_;
|
std::unique_ptr<ui::GraphicsProvider> provider_;
|
||||||
|
|
||||||
uint32_t interrupt_callback_ = 0;
|
uint32_t interrupt_callback_ = 0;
|
||||||
|
@ -109,6 +116,11 @@ class GraphicsSystem {
|
||||||
std::unique_ptr<CommandProcessor> command_processor_;
|
std::unique_ptr<CommandProcessor> command_processor_;
|
||||||
|
|
||||||
bool paused_ = false;
|
bool paused_ = false;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unique_ptr<ui::Presenter> presenter_;
|
||||||
|
|
||||||
|
std::atomic_flag host_gpu_loss_reported_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace gpu
|
} // namespace gpu
|
||||||
|
|
|
@ -31,9 +31,9 @@ void NullCommandProcessor::ShutdownContext() {
|
||||||
return CommandProcessor::ShutdownContext();
|
return CommandProcessor::ShutdownContext();
|
||||||
}
|
}
|
||||||
|
|
||||||
void NullCommandProcessor::PerformSwap(uint32_t frontbuffer_ptr,
|
void NullCommandProcessor::IssueSwap(uint32_t frontbuffer_ptr,
|
||||||
uint32_t frontbuffer_width,
|
uint32_t frontbuffer_width,
|
||||||
uint32_t frontbuffer_height) {}
|
uint32_t frontbuffer_height) {}
|
||||||
|
|
||||||
Shader* NullCommandProcessor::LoadShader(xenos::ShaderType shader_type,
|
Shader* NullCommandProcessor::LoadShader(xenos::ShaderType shader_type,
|
||||||
uint32_t guest_address,
|
uint32_t guest_address,
|
||||||
|
|
|
@ -33,8 +33,8 @@ class NullCommandProcessor : public CommandProcessor {
|
||||||
bool SetupContext() override;
|
bool SetupContext() override;
|
||||||
void ShutdownContext() override;
|
void ShutdownContext() override;
|
||||||
|
|
||||||
void PerformSwap(uint32_t frontbuffer_ptr, uint32_t frontbuffer_width,
|
void IssueSwap(uint32_t frontbuffer_ptr, uint32_t frontbuffer_width,
|
||||||
uint32_t frontbuffer_height) override;
|
uint32_t frontbuffer_height) override;
|
||||||
|
|
||||||
Shader* LoadShader(xenos::ShaderType shader_type, uint32_t guest_address,
|
Shader* LoadShader(xenos::ShaderType shader_type, uint32_t guest_address,
|
||||||
const uint32_t* host_address,
|
const uint32_t* host_address,
|
||||||
|
|
|
@ -23,31 +23,20 @@ NullGraphicsSystem::~NullGraphicsSystem() {}
|
||||||
|
|
||||||
X_STATUS NullGraphicsSystem::Setup(cpu::Processor* processor,
|
X_STATUS NullGraphicsSystem::Setup(cpu::Processor* processor,
|
||||||
kernel::KernelState* kernel_state,
|
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
|
// This is a null graphics system, but we still setup vulkan because UI needs
|
||||||
// it through us :|
|
// it through us :|
|
||||||
provider_ = xe::ui::vulkan::VulkanProvider::Create();
|
provider_ = xe::ui::vulkan::VulkanProvider::Create(is_surface_required);
|
||||||
|
return GraphicsSystem::Setup(processor, kernel_state, app_context,
|
||||||
return GraphicsSystem::Setup(processor, kernel_state, target_window);
|
is_surface_required);
|
||||||
}
|
}
|
||||||
|
|
||||||
void NullGraphicsSystem::Shutdown() { GraphicsSystem::Shutdown(); }
|
|
||||||
|
|
||||||
std::unique_ptr<CommandProcessor> NullGraphicsSystem::CreateCommandProcessor() {
|
std::unique_ptr<CommandProcessor> NullGraphicsSystem::CreateCommandProcessor() {
|
||||||
return std::unique_ptr<CommandProcessor>(
|
return std::unique_ptr<CommandProcessor>(
|
||||||
new NullCommandProcessor(this, kernel_state_));
|
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 null
|
||||||
} // namespace gpu
|
} // namespace gpu
|
||||||
} // namespace xe
|
} // namespace xe
|
|
@ -29,13 +29,11 @@ class NullGraphicsSystem : public GraphicsSystem {
|
||||||
std::string name() const override { return "null"; }
|
std::string name() const override { return "null"; }
|
||||||
|
|
||||||
X_STATUS Setup(cpu::Processor* processor, kernel::KernelState* kernel_state,
|
X_STATUS Setup(cpu::Processor* processor, kernel::KernelState* kernel_state,
|
||||||
ui::Window* target_window) override;
|
ui::WindowedAppContext* app_context,
|
||||||
void Shutdown() override;
|
bool is_surface_required) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::unique_ptr<CommandProcessor> CreateCommandProcessor() override;
|
std::unique_ptr<CommandProcessor> CreateCommandProcessor() override;
|
||||||
|
|
||||||
void Swap(xe::ui::UIEvent* e) override;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace null
|
} // namespace null
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue