Merge branch 'master' into vulkan
This commit is contained in:
commit
922efb13ce
|
@ -8,14 +8,17 @@ skip_tags: true
|
|||
|
||||
skip_commits:
|
||||
files:
|
||||
- .drone.yml
|
||||
- .drone.star
|
||||
- .github/**
|
||||
- .travis.yml
|
||||
- android/**
|
||||
- docs/**
|
||||
- src/**/*_posix.*
|
||||
- src/**/*_linux.*
|
||||
- src/**/*_gnulinux.*
|
||||
- src/**/*_x11.*
|
||||
- src/**/*_gtk.*
|
||||
- src/**/*_android.*
|
||||
- src/**/*_mac.*
|
||||
- LICENSE
|
||||
- README.md
|
||||
|
||||
|
@ -27,23 +30,28 @@ pull_requests:
|
|||
os: Visual Studio 2019
|
||||
|
||||
init:
|
||||
- git config --global core.autocrlf input
|
||||
- ps: |
|
||||
If (-Not $env:APPVEYOR_PULL_REQUEST_NUMBER) {
|
||||
$env:is_not_pr = "true"
|
||||
}
|
||||
If (-Not $env:APPVEYOR_REPO_COMMIT_MESSAGE_EXTENDED) {
|
||||
$env:APPVEYOR_REPO_COMMIT_MESSAGE_EXTENDED = " "
|
||||
}
|
||||
|
||||
install:
|
||||
- cmd: vcpkg integrate remove
|
||||
- cmd: xb setup
|
||||
- |
|
||||
vcpkg integrate remove
|
||||
xb setup
|
||||
|
||||
platform: Windows
|
||||
|
||||
configuration:
|
||||
- Release
|
||||
- Checked
|
||||
configuration: [Release, Checked]
|
||||
|
||||
build_script:
|
||||
- cmd: xb build --config=%CONFIGURATION% --target=src\xenia-app --target=tests\xenia-base-tests --target=tests\xenia-cpu-ppc-tests --target=src\xenia-vfs-dump
|
||||
- xb build --config=%CONFIGURATION% --target=src\xenia-app --target=tests\xenia-base-tests --target=tests\xenia-cpu-ppc-tests --target=src\xenia-vfs-dump
|
||||
|
||||
after_build:
|
||||
- cmd: |
|
||||
- |
|
||||
IF NOT "%CONFIGURATION%"=="Checked" SET "ARCHIVE_SUFFIX=%APPVEYOR_REPO_BRANCH%"
|
||||
IF NOT "%CONFIGURATION%"=="Checked" SET "ARCHIVE_SWITCHES=--"
|
||||
IF "%CONFIGURATION%"=="Checked" SET "ARCHIVE_SUFFIX=%APPVEYOR_REPO_BRANCH%_FOR-DEVS-ONLY"
|
||||
|
@ -52,10 +60,10 @@ after_build:
|
|||
7z a xenia-vfs-dump_%ARCHIVE_SUFFIX%.zip %ARCHIVE_SWITCHES% LICENSE "%APPVEYOR_BUILD_FOLDER%\build\bin\%PLATFORM%\%CONFIGURATION%\xenia-vfs-dump.exe" "%APPVEYOR_BUILD_FOLDER%\build\bin\%PLATFORM%\%CONFIGURATION%\xenia-vfs-dump.pdb"
|
||||
|
||||
before_test:
|
||||
- cmd: xb gentests
|
||||
- xb gentests
|
||||
|
||||
test_script:
|
||||
- cmd: xb test --config=%CONFIGURATION% --no_build
|
||||
- xb test --config=%CONFIGURATION% --no_build
|
||||
|
||||
artifacts:
|
||||
- path: '*.zip'
|
||||
|
@ -73,3 +81,22 @@ deploy:
|
|||
configuration: release
|
||||
appveyor_repo_tag: true
|
||||
is_not_pr: true
|
||||
- provider: GitHub
|
||||
name: xenia-master
|
||||
repository: xenia-project/release-builds-windows
|
||||
auth_token:
|
||||
secure: /8he47z1WnPN7LcCTe5T5KMxxX0SmqFj9QMpeWEa3aZ64kMsfupOT/jKakqTM8af
|
||||
tag: v$(appveyor_build_version)
|
||||
release: v$(appveyor_build_version)
|
||||
description: |
|
||||
Windows release build for https://github.com/xenia-project/xenia/commit/$(APPVEYOR_REPO_COMMIT).
|
||||
|
||||
$(APPVEYOR_REPO_COMMIT_MESSAGE)
|
||||
|
||||
$(APPVEYOR_REPO_COMMIT_MESSAGE_EXTENDED)
|
||||
draft: false
|
||||
prerelease: false
|
||||
on:
|
||||
branch: master
|
||||
configuration: release
|
||||
is_not_pr: true
|
||||
|
|
|
@ -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"]
|
||||
path = third_party/premake-androidndk
|
||||
url = https://github.com/Triang3l/premake-androidndk.git
|
||||
[submodule "third_party/FidelityFX-CAS"]
|
||||
path = third_party/FidelityFX-CAS
|
||||
url = https://github.com/GPUOpen-Effects/FidelityFX-CAS.git
|
||||
[submodule "third_party/FidelityFX-FSR"]
|
||||
path = third_party/FidelityFX-FSR
|
||||
url = https://github.com/GPUOpen-Effects/FidelityFX-FSR.git
|
||||
[submodule "third_party/glslang"]
|
||||
path = third_party/glslang
|
||||
url = https://github.com/KhronosGroup/glslang.git
|
||||
|
|
|
@ -21,9 +21,9 @@ Discussing illegal activities will get you banned.
|
|||
|
||||
## Status
|
||||
|
||||
Buildbot | Status
|
||||
-------- | ------
|
||||
[Windows](https://ci.appveyor.com/project/benvanik/xenia/branch/master) | [![Build status](https://ci.appveyor.com/api/projects/status/ftqiy86kdfawyx3a/branch/master?svg=true)](https://ci.appveyor.com/project/benvanik/xenia/branch/master)
|
||||
Buildbot | Status | Releases
|
||||
-------- | ------ | --------
|
||||
[Windows](https://ci.appveyor.com/project/benvanik/xenia/branch/master) | [![Build status](https://ci.appveyor.com/api/projects/status/ftqiy86kdfawyx3a/branch/master?svg=true)](https://ci.appveyor.com/project/benvanik/xenia/branch/master) | [Latest](https://github.com/xenia-project/release-builds-windows/releases/latest) ◦ [All](https://github.com/xenia-project/release-builds-windows/releases)
|
||||
[Linux](https://cloud.drone.io/xenia-project/xenia) | [![Build status](https://cloud.drone.io/api/badges/xenia-project/xenia/status.svg)](https://cloud.drone.io/xenia-project/xenia)
|
||||
|
||||
Quite a few real games run. Quite a few don't.
|
||||
|
@ -61,7 +61,7 @@ Fixes and optimizations are always welcome (please!), but in addition to
|
|||
that there are some major work areas still untouched:
|
||||
|
||||
* Help work through [missing functionality/bugs in games](https://github.com/xenia-project/xenia/labels/compat)
|
||||
* Add input drivers for [third-party controllers](https://github.com/xenia-project/xenia/issues/1333)
|
||||
* Reduce the size of Xenia's [huge log files](https://github.com/xenia-project/xenia/issues/1526)
|
||||
* Skilled with Linux? A strong contributor is needed to [help with porting](https://github.com/xenia-project/xenia/labels/platform-linux)
|
||||
|
||||
See more projects [good for contributors](https://github.com/xenia-project/xenia/labels/good%20first%20issue). It's a good idea to ask on Discord and check the issues page before beginning work on
|
||||
|
|
|
@ -4,7 +4,6 @@ plugins {
|
|||
|
||||
android {
|
||||
compileSdkVersion 30
|
||||
buildToolsVersion '30.0.2'
|
||||
ndkVersion '23.0.7599858'
|
||||
|
||||
defaultConfig {
|
||||
|
@ -82,4 +81,8 @@ android {
|
|||
path file('../../../build/xenia.wks.Android.mk')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'org.jetbrains:annotations:15.0'
|
||||
}
|
|
@ -29,7 +29,9 @@
|
|||
android:supportsRtl="true"
|
||||
android:theme="@android:style/Theme.Material.Light">
|
||||
|
||||
<activity android:name="jp.xenia.emulator.WindowDemoActivity">
|
||||
<activity
|
||||
android:name="jp.xenia.emulator.WindowDemoActivity"
|
||||
android:label="@string/activity_label_window_demo">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
|
|
|
@ -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;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
public class WindowDemoActivity extends WindowedAppActivity {
|
||||
@Override
|
||||
protected String getWindowedAppIdentifier() {
|
||||
return "xenia_ui_window_vulkan_demo";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.activity_window_demo);
|
||||
setWindowSurfaceView(findViewById(R.id.window_demo_surface_view));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.content.res.AssetManager;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.Surface;
|
||||
import android.view.SurfaceHolder;
|
||||
import android.view.View;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jp.xenia.XeniaRuntimeException;
|
||||
|
||||
public abstract class WindowedAppActivity extends Activity {
|
||||
private static final String TAG = "WindowedAppActivity";
|
||||
|
||||
static {
|
||||
// TODO(Triang3l): Move all demos to libxenia.so.
|
||||
System.loadLibrary("xenia-ui-window-vulkan-demo");
|
||||
}
|
||||
|
||||
private long mAppContext;
|
||||
private final WindowSurfaceOnLayoutChangeListener mWindowSurfaceOnLayoutChangeListener =
|
||||
new WindowSurfaceOnLayoutChangeListener();
|
||||
private final WindowSurfaceHolderCallback mWindowSurfaceHolderCallback =
|
||||
new WindowSurfaceHolderCallback();
|
||||
|
||||
private native long initializeWindowedAppOnCreateNative(
|
||||
// May be 0 while destroying (mainly while the superclass is).
|
||||
private long mAppContext = 0;
|
||||
|
||||
@Nullable
|
||||
private WindowSurfaceView mWindowSurfaceView = null;
|
||||
|
||||
private native long initializeWindowedAppOnCreate(
|
||||
String windowedAppIdentifier, AssetManager assetManager);
|
||||
|
||||
private native void onDestroyNative(long appContext);
|
||||
|
||||
private native void onWindowSurfaceLayoutChange(
|
||||
long appContext, int left, int top, int right, int bottom);
|
||||
|
||||
private native void onWindowSurfaceChanged(long appContext, Surface windowSurface);
|
||||
|
||||
private native void paintWindow(long appContext, boolean forcePaint);
|
||||
|
||||
protected abstract String getWindowedAppIdentifier();
|
||||
|
||||
protected void setWindowSurfaceView(@Nullable final WindowSurfaceView windowSurfaceView) {
|
||||
if (mWindowSurfaceView == windowSurfaceView) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Detach from the old surface.
|
||||
if (mWindowSurfaceView != null) {
|
||||
mWindowSurfaceView.getHolder().removeCallback(mWindowSurfaceHolderCallback);
|
||||
mWindowSurfaceView.removeOnLayoutChangeListener(mWindowSurfaceOnLayoutChangeListener);
|
||||
mWindowSurfaceView = null;
|
||||
if (mAppContext != 0) {
|
||||
onWindowSurfaceChanged(mAppContext, null);
|
||||
}
|
||||
}
|
||||
|
||||
if (windowSurfaceView == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
mWindowSurfaceView = windowSurfaceView;
|
||||
// The native window code assumes that, when the surface exists, it covers the entire
|
||||
// window.
|
||||
// FIXME(Triang3l): This doesn't work if the layout has already been performed.
|
||||
mWindowSurfaceView.addOnLayoutChangeListener(mWindowSurfaceOnLayoutChangeListener);
|
||||
final SurfaceHolder windowSurfaceHolder = mWindowSurfaceView.getHolder();
|
||||
windowSurfaceHolder.addCallback(mWindowSurfaceHolderCallback);
|
||||
// If setting after the creation of the surface.
|
||||
if (mAppContext != 0) {
|
||||
final Surface windowSurface = windowSurfaceHolder.getSurface();
|
||||
if (windowSurface != null) {
|
||||
onWindowSurfaceChanged(mAppContext, windowSurface);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void onWindowSurfaceDraw(final boolean forcePaint) {
|
||||
if (mAppContext == 0) {
|
||||
return;
|
||||
}
|
||||
paintWindow(mAppContext, forcePaint);
|
||||
}
|
||||
|
||||
// Used from the native WindowedAppContext. May be called from non-UI threads.
|
||||
protected void postInvalidateWindowSurface() {
|
||||
if (mWindowSurfaceView == null) {
|
||||
return;
|
||||
}
|
||||
mWindowSurfaceView.postInvalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
protected void onCreate(final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
mAppContext = initializeWindowedAppOnCreateNative(getWindowedAppIdentifier(), getAssets());
|
||||
final String windowedAppIdentifier = getWindowedAppIdentifier();
|
||||
mAppContext = initializeWindowedAppOnCreate(windowedAppIdentifier, getAssets());
|
||||
if (mAppContext == 0) {
|
||||
Log.e(TAG, "Error initializing the windowed app");
|
||||
finish();
|
||||
return;
|
||||
throw new XeniaRuntimeException(
|
||||
"Error initializing the windowed app " + windowedAppIdentifier);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
setWindowSurfaceView(null);
|
||||
if (mAppContext != 0) {
|
||||
onDestroyNative(mAppContext);
|
||||
}
|
||||
mAppContext = 0;
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
private class WindowSurfaceOnLayoutChangeListener implements View.OnLayoutChangeListener {
|
||||
@Override
|
||||
public void onLayoutChange(
|
||||
final View v, final int left, final int top, final int right, final int bottom,
|
||||
final int oldLeft, final int oldTop, final int oldRight, final int oldBottom) {
|
||||
if (mAppContext != 0) {
|
||||
onWindowSurfaceLayoutChange(mAppContext, left, top, right, bottom);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class WindowSurfaceHolderCallback implements SurfaceHolder.Callback2 {
|
||||
@Override
|
||||
public void surfaceCreated(final SurfaceHolder holder) {
|
||||
if (mAppContext == 0) {
|
||||
return;
|
||||
}
|
||||
onWindowSurfaceChanged(mAppContext, holder.getSurface());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceChanged(
|
||||
final SurfaceHolder holder, final int format, final int width, final int height) {
|
||||
if (mAppContext == 0) {
|
||||
return;
|
||||
}
|
||||
onWindowSurfaceChanged(mAppContext, holder.getSurface());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceDestroyed(final SurfaceHolder holder) {
|
||||
if (mAppContext == 0) {
|
||||
return;
|
||||
}
|
||||
onWindowSurfaceChanged(mAppContext, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceRedrawNeeded(final SurfaceHolder holder) {
|
||||
onWindowSurfaceDraw(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<jp.xenia.emulator.WindowSurfaceView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/window_demo_surface_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context="jp.xenia.emulator.WindowDemoActivity">
|
||||
|
||||
</RelativeLayout>
|
||||
tools:context="jp.xenia.emulator.WindowDemoActivity" />
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
<resources>
|
||||
<string name="app_name">Xenia</string>
|
||||
<string name="activity_label_window_demo">Xenia Window Demo</string>
|
||||
</resources>
|
|
@ -5,7 +5,7 @@ buildscript {
|
|||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.0.3'
|
||||
classpath 'com.android.tools.build:gradle:7.1.0'
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
|
|
|
@ -2,13 +2,20 @@
|
|||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2020 Ben Vanik. All rights reserved. *
|
||||
* Copyright 2022 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include "xenia/app/emulator_window.h"
|
||||
|
||||
#include <filesystem>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "third_party/fmt/include/fmt/format.h"
|
||||
#include "third_party/imgui/imgui.h"
|
||||
#include "xenia/base/assert.h"
|
||||
|
@ -20,11 +27,17 @@
|
|||
#include "xenia/base/profiling.h"
|
||||
#include "xenia/base/system.h"
|
||||
#include "xenia/base/threading.h"
|
||||
#include "xenia/cpu/processor.h"
|
||||
#include "xenia/emulator.h"
|
||||
#include "xenia/gpu/command_processor.h"
|
||||
#include "xenia/gpu/graphics_system.h"
|
||||
#include "xenia/ui/file_picker.h"
|
||||
#include "xenia/ui/graphics_provider.h"
|
||||
#include "xenia/ui/imgui_dialog.h"
|
||||
#include "xenia/ui/imgui_drawer.h"
|
||||
#include "xenia/ui/immediate_drawer.h"
|
||||
#include "xenia/ui/presenter.h"
|
||||
#include "xenia/ui/ui_event.h"
|
||||
#include "xenia/ui/virtual_key.h"
|
||||
|
||||
// Autogenerated by `xb premake`.
|
||||
|
@ -32,13 +45,91 @@
|
|||
|
||||
DECLARE_bool(debug);
|
||||
|
||||
DEFINE_bool(fullscreen, false, "Whether to launch the emulator in fullscreen.",
|
||||
"Display");
|
||||
|
||||
DEFINE_string(
|
||||
postprocess_antialiasing, "",
|
||||
"Post-processing anti-aliasing effect to apply to the image output of the "
|
||||
"game.\n"
|
||||
"Using post-process anti-aliasing is heavily recommended when AMD "
|
||||
"FidelityFX Contrast Adaptive Sharpening or Super Resolution 1.0 is "
|
||||
"active.\n"
|
||||
"Use: [none, fxaa, fxaa_extreme]\n"
|
||||
" none (or any value not listed here):\n"
|
||||
" Don't alter the original image.\n"
|
||||
" fxaa:\n"
|
||||
" NVIDIA Fast Approximate Anti-Aliasing 3.11, normal quality preset (12)."
|
||||
"\n"
|
||||
" fxaa_extreme:\n"
|
||||
" NVIDIA Fast Approximate Anti-Aliasing 3.11, extreme quality preset "
|
||||
"(39).",
|
||||
"Display");
|
||||
DEFINE_string(
|
||||
postprocess_scaling_and_sharpening, "",
|
||||
"Post-processing effect to use for resampling and/or sharpening of the "
|
||||
"final display output.\n"
|
||||
"Use: [bilinear, cas, fsr]\n"
|
||||
" bilinear (or any value not listed here):\n"
|
||||
" Original image at 1:1, simple bilinear stretching for resampling.\n"
|
||||
" cas:\n"
|
||||
" Use AMD FidelityFX Contrast Adaptive Sharpening (CAS) for sharpening "
|
||||
"at scaling factors of up to 2x2, with additional bilinear stretching for "
|
||||
"larger factors.\n"
|
||||
" fsr:\n"
|
||||
" Use AMD FidelityFX Super Resolution 1.0 (FSR) for highest-quality "
|
||||
"upscaling, or AMD FidelityFX Contrast Adaptive Sharpening for sharpening "
|
||||
"while not scaling or downsampling.\n"
|
||||
" For scaling by factors of more than 2x2, multiple FSR passes are done.",
|
||||
"Display");
|
||||
DEFINE_double(
|
||||
postprocess_ffx_cas_additional_sharpness,
|
||||
xe::ui::Presenter::GuestOutputPaintConfig::kCasAdditionalSharpnessDefault,
|
||||
"Additional sharpness for AMD FidelityFX Contrast Adaptive Sharpening "
|
||||
"(CAS), from 0 to 1.\n"
|
||||
"Higher is sharper.",
|
||||
"Display");
|
||||
DEFINE_uint32(
|
||||
postprocess_ffx_fsr_max_upsampling_passes,
|
||||
xe::ui::Presenter::GuestOutputPaintConfig::kFsrMaxUpscalingPassesMax,
|
||||
"Maximum number of upsampling passes performed in AMD FidelityFX Super "
|
||||
"Resolution 1.0 (FSR) before falling back to bilinear stretching after the "
|
||||
"final pass.\n"
|
||||
"Each pass upscales only to up to 2x2 the previous size. If the game "
|
||||
"outputs a 1280x720 image, 1 pass will upscale it to up to 2560x1440 "
|
||||
"(below 4K), after 2 passes it will be upscaled to a maximum of 5120x2880 "
|
||||
"(including 3840x2160 for 4K), and so on.\n"
|
||||
"This variable has no effect if the display resolution isn't very high, "
|
||||
"but may be reduced on resolutions like 4K or 8K in case the performance "
|
||||
"impact of multiple FSR upsampling passes is too high, or if softer edges "
|
||||
"are desired.\n"
|
||||
"The default value is the maximum internally supported by Xenia.",
|
||||
"Display");
|
||||
DEFINE_double(
|
||||
postprocess_ffx_fsr_sharpness_reduction,
|
||||
xe::ui::Presenter::GuestOutputPaintConfig::kFsrSharpnessReductionDefault,
|
||||
"Sharpness reduction for AMD FidelityFX Super Resolution 1.0 (FSR), in "
|
||||
"stops.\n"
|
||||
"Lower is sharper.",
|
||||
"Display");
|
||||
// Dithering to 8bpc is enabled by default since the effect is minor, only
|
||||
// effects what can't be shown normally by host displays, and nothing is changed
|
||||
// by it for 8bpc source without resampling.
|
||||
DEFINE_bool(
|
||||
postprocess_dither, true,
|
||||
"Dither the final image output from the internal precision to 8 bits per "
|
||||
"channel so gradients are smoother.\n"
|
||||
"On a 10bpc display, the lower 2 bits will still be kept, but noise will "
|
||||
"be added to them - disabling may be recommended for 10bpc, but it "
|
||||
"depends on the 10bpc displaying capabilities of the actual display used.",
|
||||
"Display");
|
||||
|
||||
namespace xe {
|
||||
namespace app {
|
||||
|
||||
using xe::ui::FileDropEvent;
|
||||
using xe::ui::KeyEvent;
|
||||
using xe::ui::MenuItem;
|
||||
using xe::ui::MouseEvent;
|
||||
using xe::ui::UIEvent;
|
||||
|
||||
const std::string kBaseTitle = "Xenia";
|
||||
|
@ -47,7 +138,12 @@ EmulatorWindow::EmulatorWindow(Emulator* emulator,
|
|||
ui::WindowedAppContext& app_context)
|
||||
: emulator_(emulator),
|
||||
app_context_(app_context),
|
||||
window_(ui::Window::Create(app_context, kBaseTitle)) {
|
||||
window_listener_(*this),
|
||||
window_(ui::Window::Create(app_context, kBaseTitle, 1280, 720)),
|
||||
imgui_drawer_(
|
||||
std::make_unique<ui::ImGuiDrawer>(window_.get(), kZOrderImGui)),
|
||||
display_config_game_config_load_callback_(
|
||||
new DisplayConfigGameConfigLoadCallback(*emulator, *this)) {
|
||||
base_title_ = kBaseTitle +
|
||||
#ifdef DEBUG
|
||||
#if _NO_DEBUG_HEAP == 1
|
||||
|
@ -76,107 +172,331 @@ std::unique_ptr<EmulatorWindow> EmulatorWindow::Create(
|
|||
return emulator_window;
|
||||
}
|
||||
|
||||
bool EmulatorWindow::Initialize() {
|
||||
if (!window_->Initialize()) {
|
||||
XELOGE("Failed to initialize platform window");
|
||||
return false;
|
||||
EmulatorWindow::~EmulatorWindow() {
|
||||
// Notify the ImGui drawer that the immediate drawer is being destroyed.
|
||||
ShutdownGraphicsSystemPresenterPainting();
|
||||
}
|
||||
|
||||
ui::Presenter* EmulatorWindow::GetGraphicsSystemPresenter() const {
|
||||
gpu::GraphicsSystem* graphics_system = emulator_->graphics_system();
|
||||
return graphics_system ? graphics_system->presenter() : nullptr;
|
||||
}
|
||||
|
||||
void EmulatorWindow::SetupGraphicsSystemPresenterPainting() {
|
||||
ShutdownGraphicsSystemPresenterPainting();
|
||||
|
||||
if (!window_) {
|
||||
return;
|
||||
}
|
||||
|
||||
UpdateTitle();
|
||||
ui::Presenter* presenter = GetGraphicsSystemPresenter();
|
||||
if (!presenter) {
|
||||
return;
|
||||
}
|
||||
|
||||
window_->on_closed.AddListener(
|
||||
[this](UIEvent* e) { app_context_.QuitFromUIThread(); });
|
||||
ApplyDisplayConfigForCvars();
|
||||
|
||||
window_->on_file_drop.AddListener(
|
||||
[this](FileDropEvent* e) { FileDrop(e->filename()); });
|
||||
window_->SetPresenter(presenter);
|
||||
|
||||
window_->on_key_down.AddListener([this](KeyEvent* e) {
|
||||
bool handled = true;
|
||||
switch (e->virtual_key()) {
|
||||
case ui::VirtualKey::kO: {
|
||||
if (e->is_ctrl_pressed()) {
|
||||
FileOpen();
|
||||
}
|
||||
} break;
|
||||
case ui::VirtualKey::kMultiply: {
|
||||
CpuTimeScalarReset();
|
||||
} break;
|
||||
case ui::VirtualKey::kSubtract: {
|
||||
CpuTimeScalarSetHalf();
|
||||
} break;
|
||||
case ui::VirtualKey::kAdd: {
|
||||
CpuTimeScalarSetDouble();
|
||||
} break;
|
||||
immediate_drawer_ =
|
||||
emulator_->graphics_system()->provider()->CreateImmediateDrawer();
|
||||
if (immediate_drawer_) {
|
||||
immediate_drawer_->SetPresenter(presenter);
|
||||
imgui_drawer_->SetPresenterAndImmediateDrawer(presenter,
|
||||
immediate_drawer_.get());
|
||||
Profiler::SetUserIO(kZOrderProfiler, window_.get(), presenter,
|
||||
immediate_drawer_.get());
|
||||
}
|
||||
}
|
||||
|
||||
case ui::VirtualKey::kF3: {
|
||||
Profiler::ToggleDisplay();
|
||||
} break;
|
||||
void EmulatorWindow::ShutdownGraphicsSystemPresenterPainting() {
|
||||
Profiler::SetUserIO(kZOrderProfiler, window_.get(), nullptr, nullptr);
|
||||
imgui_drawer_->SetPresenterAndImmediateDrawer(nullptr, nullptr);
|
||||
immediate_drawer_.reset();
|
||||
if (window_) {
|
||||
window_->SetPresenter(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
case ui::VirtualKey::kF4: {
|
||||
GpuTraceFrame();
|
||||
} break;
|
||||
case ui::VirtualKey::kF5: {
|
||||
GpuClearCaches();
|
||||
} break;
|
||||
case ui::VirtualKey::kF7: {
|
||||
// Save to file
|
||||
// TODO: Choose path based on user input, or from options
|
||||
// TODO: Spawn a new thread to do this.
|
||||
emulator()->SaveToFile("test.sav");
|
||||
} break;
|
||||
case ui::VirtualKey::kF8: {
|
||||
// Restore from file
|
||||
// TODO: Choose path from user
|
||||
// TODO: Spawn a new thread to do this.
|
||||
emulator()->RestoreFromFile("test.sav");
|
||||
} break;
|
||||
case ui::VirtualKey::kF11: {
|
||||
ToggleFullscreen();
|
||||
} break;
|
||||
case ui::VirtualKey::kEscape: {
|
||||
// Allow users to escape fullscreen (but not enter it).
|
||||
if (window_->is_fullscreen()) {
|
||||
window_->ToggleFullscreen(false);
|
||||
} else {
|
||||
handled = false;
|
||||
}
|
||||
} break;
|
||||
void EmulatorWindow::OnEmulatorInitialized() {
|
||||
emulator_initialized_ = true;
|
||||
window_->SetMainMenuEnabled(true);
|
||||
// When the user can see that the emulator isn't initializing anymore (the
|
||||
// menu isn't disabled), enter fullscreen if requested.
|
||||
if (cvars::fullscreen) {
|
||||
window_->SetFullscreen(true);
|
||||
}
|
||||
}
|
||||
|
||||
case ui::VirtualKey::kPause: {
|
||||
CpuBreakIntoDebugger();
|
||||
} break;
|
||||
case ui::VirtualKey::kCancel: {
|
||||
CpuBreakIntoHostDebugger();
|
||||
} break;
|
||||
void EmulatorWindow::EmulatorWindowListener::OnClosing(ui::UIEvent& e) {
|
||||
emulator_window_.app_context_.QuitFromUIThread();
|
||||
}
|
||||
|
||||
case ui::VirtualKey::kF1: {
|
||||
ShowHelpWebsite();
|
||||
} break;
|
||||
void EmulatorWindow::EmulatorWindowListener::OnFileDrop(ui::FileDropEvent& e) {
|
||||
emulator_window_.FileDrop(e.filename());
|
||||
}
|
||||
|
||||
case ui::VirtualKey::kF2: {
|
||||
ShowCommitID();
|
||||
} break;
|
||||
void EmulatorWindow::EmulatorWindowListener::OnKeyDown(ui::KeyEvent& e) {
|
||||
emulator_window_.OnKeyDown(e);
|
||||
}
|
||||
|
||||
default: {
|
||||
handled = false;
|
||||
} break;
|
||||
}
|
||||
e->set_handled(handled);
|
||||
});
|
||||
void EmulatorWindow::DisplayConfigGameConfigLoadCallback::PostGameConfigLoad() {
|
||||
emulator_window_.ApplyDisplayConfigForCvars();
|
||||
}
|
||||
|
||||
window_->on_mouse_move.AddListener([this](MouseEvent* e) {
|
||||
if (window_->is_fullscreen() && (e->dx() > 2 || e->dy() > 2)) {
|
||||
if (!window_->is_cursor_visible()) {
|
||||
window_->set_cursor_visible(true);
|
||||
void EmulatorWindow::DisplayConfigDialog::OnDraw(ImGuiIO& io) {
|
||||
gpu::GraphicsSystem* graphics_system =
|
||||
emulator_window_.emulator_->graphics_system();
|
||||
if (!graphics_system) {
|
||||
return;
|
||||
}
|
||||
|
||||
// In the top-left corner so it's close to the menu bar from where it was
|
||||
// opened.
|
||||
// Origin Y coordinate 20 was taken from the Dear ImGui demo.
|
||||
ImGui::SetNextWindowPos(ImVec2(20, 20), ImGuiCond_FirstUseEver);
|
||||
ImGui::SetNextWindowSize(ImVec2(20, 20), ImGuiCond_FirstUseEver);
|
||||
// Alpha from Dear ImGui tooltips (0.35 from the overlay provides too low
|
||||
// visibility). Translucent so some effect of the changes can still be seen
|
||||
// through it.
|
||||
ImGui::SetNextWindowBgAlpha(0.6f);
|
||||
bool dialog_open = true;
|
||||
if (!ImGui::Begin("Post-processing", &dialog_open,
|
||||
ImGuiWindowFlags_NoCollapse |
|
||||
ImGuiWindowFlags_AlwaysAutoResize |
|
||||
ImGuiWindowFlags_HorizontalScrollbar)) {
|
||||
ImGui::End();
|
||||
return;
|
||||
}
|
||||
// Even if the close button has been pressed, still paint everything not to
|
||||
// have one frame with an empty window.
|
||||
|
||||
// Prevent user confusion which has been reported multiple times.
|
||||
ImGui::TextUnformatted("All effects can be used on GPUs of any brand.");
|
||||
ImGui::Spacing();
|
||||
|
||||
gpu::CommandProcessor* command_processor =
|
||||
graphics_system->command_processor();
|
||||
if (command_processor) {
|
||||
if (ImGui::TreeNodeEx(
|
||||
"Anti-aliasing",
|
||||
ImGuiTreeNodeFlags_Framed | ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
gpu::CommandProcessor::SwapPostEffect current_swap_post_effect =
|
||||
command_processor->GetDesiredSwapPostEffect();
|
||||
int new_swap_post_effect_index = int(current_swap_post_effect);
|
||||
ImGui::RadioButton("None", &new_swap_post_effect_index,
|
||||
int(gpu::CommandProcessor::SwapPostEffect::kNone));
|
||||
ImGui::RadioButton(
|
||||
"NVIDIA Fast Approximate Anti-Aliasing 3.11 (FXAA), normal quality",
|
||||
&new_swap_post_effect_index,
|
||||
int(gpu::CommandProcessor::SwapPostEffect::kFxaa));
|
||||
ImGui::RadioButton(
|
||||
"NVIDIA Fast Approximate Anti-Aliasing 3.11 (FXAA), extreme quality",
|
||||
&new_swap_post_effect_index,
|
||||
int(gpu::CommandProcessor::SwapPostEffect::kFxaaExtreme));
|
||||
gpu::CommandProcessor::SwapPostEffect new_swap_post_effect =
|
||||
gpu::CommandProcessor::SwapPostEffect(new_swap_post_effect_index);
|
||||
if (current_swap_post_effect != new_swap_post_effect) {
|
||||
command_processor->SetDesiredSwapPostEffect(new_swap_post_effect);
|
||||
}
|
||||
|
||||
cursor_hide_time_ = Clock::QueryHostSystemTime() + 30000000;
|
||||
// Override the values in the cvars to save them to the config at exit if
|
||||
// the user has set them to anything new.
|
||||
if (GetSwapPostEffectForCvarValue(cvars::postprocess_antialiasing) !=
|
||||
new_swap_post_effect) {
|
||||
OVERRIDE_string(postprocess_antialiasing,
|
||||
GetCvarValueForSwapPostEffect(new_swap_post_effect));
|
||||
}
|
||||
|
||||
ImGui::TreePop();
|
||||
}
|
||||
}
|
||||
|
||||
ui::Presenter* presenter = graphics_system->presenter();
|
||||
if (presenter) {
|
||||
const ui::Presenter::GuestOutputPaintConfig& current_presenter_config =
|
||||
presenter->GetGuestOutputPaintConfigFromUIThread();
|
||||
ui::Presenter::GuestOutputPaintConfig new_presenter_config =
|
||||
current_presenter_config;
|
||||
|
||||
if (ImGui::TreeNodeEx(
|
||||
"Resampling and sharpening",
|
||||
ImGuiTreeNodeFlags_Framed | ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
// Filtering effect.
|
||||
int new_effect_index = int(new_presenter_config.GetEffect());
|
||||
ImGui::RadioButton(
|
||||
"None / bilinear", &new_effect_index,
|
||||
int(ui::Presenter::GuestOutputPaintConfig::Effect::kBilinear));
|
||||
ImGui::RadioButton(
|
||||
"AMD FidelityFX Contrast Adaptive Sharpening (CAS)",
|
||||
&new_effect_index,
|
||||
int(ui::Presenter::GuestOutputPaintConfig::Effect::kCas));
|
||||
ImGui::RadioButton(
|
||||
"AMD FidelityFX Super Resolution 1.0 (FSR)", &new_effect_index,
|
||||
int(ui::Presenter::GuestOutputPaintConfig::Effect::kFsr));
|
||||
new_presenter_config.SetEffect(
|
||||
ui::Presenter::GuestOutputPaintConfig::Effect(new_effect_index));
|
||||
|
||||
// effect_description must be one complete, but short enough, sentence per
|
||||
// line, as TextWrapped doesn't work correctly in auto-resizing windows
|
||||
// (in the initial frames, the window becomes extremely tall, and widgets
|
||||
// added after the wrapped text have no effect on the width of the text).
|
||||
const char* effect_description = nullptr;
|
||||
switch (new_presenter_config.GetEffect()) {
|
||||
case ui::Presenter::GuestOutputPaintConfig::Effect::kBilinear:
|
||||
effect_description =
|
||||
"Simple bilinear filtering is done if resampling is needed.\n"
|
||||
"Otherwise, only anti-aliasing is done if enabled, or displaying "
|
||||
"as is.";
|
||||
break;
|
||||
case ui::Presenter::GuestOutputPaintConfig::Effect::kCas:
|
||||
effect_description =
|
||||
"Sharpening and resampling to up to 2x2 to improve the fidelity "
|
||||
"of details.\n"
|
||||
"For scaling by more than 2x2, bilinear stretching is done "
|
||||
"afterwards.";
|
||||
break;
|
||||
case ui::Presenter::GuestOutputPaintConfig::Effect::kFsr:
|
||||
effect_description =
|
||||
"High-quality edge-preserving upscaling to arbitrary target "
|
||||
"resolutions.\n"
|
||||
"For scaling by more than 2x2, multiple upsampling passes are "
|
||||
"done.\n"
|
||||
"If not upscaling, Contrast Adaptive Sharpening (CAS) is used "
|
||||
"instead.";
|
||||
break;
|
||||
}
|
||||
if (effect_description) {
|
||||
ImGui::TextUnformatted(effect_description);
|
||||
}
|
||||
|
||||
if (new_presenter_config.GetEffect() ==
|
||||
ui::Presenter::GuestOutputPaintConfig::Effect::kCas ||
|
||||
new_presenter_config.GetEffect() ==
|
||||
ui::Presenter::GuestOutputPaintConfig::Effect::kFsr) {
|
||||
if (effect_description) {
|
||||
ImGui::Spacing();
|
||||
}
|
||||
|
||||
ImGui::TextUnformatted(
|
||||
"FXAA is highly recommended when using CAS or FSR.");
|
||||
|
||||
ImGui::Spacing();
|
||||
|
||||
// 2 decimal places is more or less enough precision for the sharpness
|
||||
// given the minor visual effect of small changes, the width of the
|
||||
// slider, and readability convenience (2 decimal places is like an
|
||||
// integer percentage). However, because Dear ImGui parses the string
|
||||
// representation of the number and snaps the value to it internally,
|
||||
// 2 decimal places actually offer less precision than the slider itself
|
||||
// does. This is especially prominent in the low range of the non-linear
|
||||
// FSR sharpness reduction slider. 3 decimal places are optimal in this
|
||||
// case.
|
||||
|
||||
if (new_presenter_config.GetEffect() ==
|
||||
ui::Presenter::GuestOutputPaintConfig::Effect::kFsr) {
|
||||
float fsr_sharpness_reduction =
|
||||
new_presenter_config.GetFsrSharpnessReduction();
|
||||
ImGui::TextUnformatted(
|
||||
"FSR sharpness reduction when upscaling (lower is sharper):");
|
||||
// Power 2.0 as the reduction is in stops, used in exp2.
|
||||
ImGui::SliderFloat(
|
||||
"##FSRSharpnessReduction", &fsr_sharpness_reduction,
|
||||
ui::Presenter::GuestOutputPaintConfig::kFsrSharpnessReductionMin,
|
||||
ui::Presenter::GuestOutputPaintConfig::kFsrSharpnessReductionMax,
|
||||
"%.3f stops", 2.0f);
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Reset##ResetFSRSharpnessReduction")) {
|
||||
fsr_sharpness_reduction = ui::Presenter::GuestOutputPaintConfig ::
|
||||
kFsrSharpnessReductionDefault;
|
||||
}
|
||||
new_presenter_config.SetFsrSharpnessReduction(
|
||||
fsr_sharpness_reduction);
|
||||
}
|
||||
|
||||
float cas_additional_sharpness =
|
||||
new_presenter_config.GetCasAdditionalSharpness();
|
||||
ImGui::TextUnformatted(
|
||||
new_presenter_config.GetEffect() ==
|
||||
ui::Presenter::GuestOutputPaintConfig::Effect::kFsr
|
||||
? "CAS additional sharpness when not upscaling (higher is "
|
||||
"sharper):"
|
||||
: "CAS additional sharpness (higher is sharper):");
|
||||
ImGui::SliderFloat(
|
||||
"##CASAdditionalSharpness", &cas_additional_sharpness,
|
||||
ui::Presenter::GuestOutputPaintConfig::kCasAdditionalSharpnessMin,
|
||||
ui::Presenter::GuestOutputPaintConfig::kCasAdditionalSharpnessMax,
|
||||
"%.3f");
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Reset##ResetCASAdditionalSharpness")) {
|
||||
cas_additional_sharpness = ui::Presenter::GuestOutputPaintConfig ::
|
||||
kCasAdditionalSharpnessDefault;
|
||||
}
|
||||
new_presenter_config.SetCasAdditionalSharpness(
|
||||
cas_additional_sharpness);
|
||||
|
||||
// There's no need to expose the setting for the maximum number of FSR
|
||||
// EASU passes as it's largely meaningless if the user doesn't have a
|
||||
// very high-resolution monitor compared to the original image size as
|
||||
// most of the values of the slider will have no effect, and that's just
|
||||
// very fine-grained performance control for a fixed-overhead pass only
|
||||
// for huge screen resolutions.
|
||||
}
|
||||
|
||||
ImGui::TreePop();
|
||||
}
|
||||
|
||||
e->set_handled(false);
|
||||
});
|
||||
if (ImGui::TreeNodeEx("Dithering", ImGuiTreeNodeFlags_Framed |
|
||||
ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
bool dither = current_presenter_config.GetDither();
|
||||
ImGui::Checkbox(
|
||||
"Dither the final output to 8bpc to make gradients smoother",
|
||||
&dither);
|
||||
new_presenter_config.SetDither(dither);
|
||||
|
||||
window_->on_paint.AddListener([this](UIEvent* e) { CheckHideCursor(); });
|
||||
ImGui::TreePop();
|
||||
}
|
||||
|
||||
presenter->SetGuestOutputPaintConfigFromUIThread(new_presenter_config);
|
||||
|
||||
// Override the values in the cvars to save them to the config at exit if
|
||||
// the user has set them to anything new.
|
||||
ui::Presenter::GuestOutputPaintConfig cvars_presenter_config =
|
||||
GetGuestOutputPaintConfigForCvars();
|
||||
if (cvars_presenter_config.GetEffect() !=
|
||||
new_presenter_config.GetEffect()) {
|
||||
OVERRIDE_string(postprocess_scaling_and_sharpening,
|
||||
GetCvarValueForGuestOutputPaintEffect(
|
||||
new_presenter_config.GetEffect()));
|
||||
}
|
||||
if (cvars_presenter_config.GetCasAdditionalSharpness() !=
|
||||
new_presenter_config.GetCasAdditionalSharpness()) {
|
||||
OVERRIDE_double(postprocess_ffx_cas_additional_sharpness,
|
||||
new_presenter_config.GetCasAdditionalSharpness());
|
||||
}
|
||||
if (cvars_presenter_config.GetFsrSharpnessReduction() !=
|
||||
new_presenter_config.GetFsrSharpnessReduction()) {
|
||||
OVERRIDE_double(postprocess_ffx_fsr_sharpness_reduction,
|
||||
new_presenter_config.GetFsrSharpnessReduction());
|
||||
}
|
||||
if (cvars_presenter_config.GetDither() !=
|
||||
new_presenter_config.GetDither()) {
|
||||
OVERRIDE_bool(postprocess_dither, new_presenter_config.GetDither());
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::End();
|
||||
|
||||
if (!dialog_open) {
|
||||
emulator_window_.ToggleDisplayConfigDialog();
|
||||
// `this` might have been destroyed by ToggleDisplayConfigDialog.
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bool EmulatorWindow::Initialize() {
|
||||
window_->AddListener(&window_listener_);
|
||||
window_->AddInputListener(&window_listener_, kZOrderEmulatorWindowInput);
|
||||
|
||||
// Main menu.
|
||||
// FIXME: This code is really messy.
|
||||
|
@ -186,17 +506,19 @@ bool EmulatorWindow::Initialize() {
|
|||
file_menu->AddChild(
|
||||
MenuItem::Create(MenuItem::Type::kString, "&Open...", "Ctrl+O",
|
||||
std::bind(&EmulatorWindow::FileOpen, this)));
|
||||
#ifdef DEBUG
|
||||
file_menu->AddChild(
|
||||
MenuItem::Create(MenuItem::Type::kString, "Close",
|
||||
std::bind(&EmulatorWindow::FileClose, this)));
|
||||
#endif // #ifdef DEBUG
|
||||
file_menu->AddChild(MenuItem::Create(MenuItem::Type::kSeparator));
|
||||
file_menu->AddChild(MenuItem::Create(
|
||||
MenuItem::Type::kString, "Show content directory...",
|
||||
std::bind(&EmulatorWindow::ShowContentDirectory, this)));
|
||||
file_menu->AddChild(MenuItem::Create(MenuItem::Type::kSeparator));
|
||||
file_menu->AddChild(MenuItem::Create(MenuItem::Type::kString, "E&xit",
|
||||
"Alt+F4",
|
||||
[this]() { window_->Close(); }));
|
||||
file_menu->AddChild(
|
||||
MenuItem::Create(MenuItem::Type::kString, "E&xit", "Alt+F4",
|
||||
[this]() { window_->RequestClose(); }));
|
||||
}
|
||||
main_menu->AddChild(std::move(file_menu));
|
||||
|
||||
|
@ -249,21 +571,35 @@ bool EmulatorWindow::Initialize() {
|
|||
}
|
||||
main_menu->AddChild(std::move(gpu_menu));
|
||||
|
||||
// Window menu.
|
||||
auto window_menu = MenuItem::Create(MenuItem::Type::kPopup, "&Window");
|
||||
// Display menu.
|
||||
auto display_menu = MenuItem::Create(MenuItem::Type::kPopup, "&Display");
|
||||
{
|
||||
window_menu->AddChild(
|
||||
display_menu->AddChild(MenuItem::Create(
|
||||
MenuItem::Type::kString, "&Post-processing settings", "F6",
|
||||
std::bind(&EmulatorWindow::ToggleDisplayConfigDialog, this)));
|
||||
}
|
||||
display_menu->AddChild(MenuItem::Create(MenuItem::Type::kSeparator));
|
||||
{
|
||||
display_menu->AddChild(
|
||||
MenuItem::Create(MenuItem::Type::kString, "&Fullscreen", "F11",
|
||||
std::bind(&EmulatorWindow::ToggleFullscreen, this)));
|
||||
}
|
||||
main_menu->AddChild(std::move(window_menu));
|
||||
main_menu->AddChild(std::move(display_menu));
|
||||
|
||||
// Help menu.
|
||||
auto help_menu = MenuItem::Create(MenuItem::Type::kPopup, "&Help");
|
||||
{
|
||||
help_menu->AddChild(
|
||||
MenuItem::Create(MenuItem::Type::kString, "Build commit on GitHub...",
|
||||
"F2", std::bind(&EmulatorWindow::ShowCommitID, this)));
|
||||
MenuItem::Create(MenuItem::Type::kString, "FA&Q...", "F1",
|
||||
std::bind(&EmulatorWindow::ShowFAQ, this)));
|
||||
help_menu->AddChild(MenuItem::Create(MenuItem::Type::kSeparator));
|
||||
help_menu->AddChild(
|
||||
MenuItem::Create(MenuItem::Type::kString, "Game &compatibility...",
|
||||
std::bind(&EmulatorWindow::ShowCompatibility, this)));
|
||||
help_menu->AddChild(MenuItem::Create(MenuItem::Type::kSeparator));
|
||||
help_menu->AddChild(MenuItem::Create(
|
||||
MenuItem::Type::kString, "Build commit on GitHub...", "F2",
|
||||
std::bind(&EmulatorWindow::ShowBuildCommit, this)));
|
||||
help_menu->AddChild(MenuItem::Create(
|
||||
MenuItem::Type::kString, "Recent changes on GitHub...", [this]() {
|
||||
LaunchWebBrowser(
|
||||
|
@ -271,25 +607,202 @@ bool EmulatorWindow::Initialize() {
|
|||
"..." XE_BUILD_BRANCH);
|
||||
}));
|
||||
help_menu->AddChild(MenuItem::Create(MenuItem::Type::kSeparator));
|
||||
help_menu->AddChild(
|
||||
MenuItem::Create(MenuItem::Type::kString, "&Website...", "F1",
|
||||
std::bind(&EmulatorWindow::ShowHelpWebsite, this)));
|
||||
help_menu->AddChild(MenuItem::Create(
|
||||
MenuItem::Type::kString, "&About...",
|
||||
[this]() { LaunchWebBrowser("https://xenia.jp/about/"); }));
|
||||
}
|
||||
main_menu->AddChild(std::move(help_menu));
|
||||
|
||||
window_->set_main_menu(std::move(main_menu));
|
||||
window_->SetMainMenu(std::move(main_menu));
|
||||
|
||||
window_->Resize(1280, 720);
|
||||
window_->SetMainMenuEnabled(false);
|
||||
|
||||
window_->DisableMainMenu();
|
||||
UpdateTitle();
|
||||
|
||||
if (!window_->Open()) {
|
||||
XELOGE("Failed to open the platform window");
|
||||
return false;
|
||||
}
|
||||
|
||||
Profiler::SetUserIO(kZOrderProfiler, window_.get(), nullptr, nullptr);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const char* EmulatorWindow::GetCvarValueForSwapPostEffect(
|
||||
gpu::CommandProcessor::SwapPostEffect effect) {
|
||||
switch (effect) {
|
||||
case gpu::CommandProcessor::SwapPostEffect::kFxaa:
|
||||
return "fxaa";
|
||||
case gpu::CommandProcessor::SwapPostEffect::kFxaaExtreme:
|
||||
return "fxaa_extreme";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
gpu::CommandProcessor::SwapPostEffect
|
||||
EmulatorWindow::GetSwapPostEffectForCvarValue(const std::string& cvar_value) {
|
||||
if (cvar_value == GetCvarValueForSwapPostEffect(
|
||||
gpu::CommandProcessor::SwapPostEffect::kFxaa)) {
|
||||
return gpu::CommandProcessor::SwapPostEffect::kFxaa;
|
||||
}
|
||||
if (cvar_value == GetCvarValueForSwapPostEffect(
|
||||
gpu::CommandProcessor::SwapPostEffect::kFxaaExtreme)) {
|
||||
return gpu::CommandProcessor::SwapPostEffect::kFxaaExtreme;
|
||||
}
|
||||
return gpu::CommandProcessor::SwapPostEffect::kNone;
|
||||
}
|
||||
|
||||
const char* EmulatorWindow::GetCvarValueForGuestOutputPaintEffect(
|
||||
ui::Presenter::GuestOutputPaintConfig::Effect effect) {
|
||||
switch (effect) {
|
||||
case ui::Presenter::GuestOutputPaintConfig::Effect::kCas:
|
||||
return "cas";
|
||||
case ui::Presenter::GuestOutputPaintConfig::Effect::kFsr:
|
||||
return "fsr";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
ui::Presenter::GuestOutputPaintConfig::Effect
|
||||
EmulatorWindow::GetGuestOutputPaintEffectForCvarValue(
|
||||
const std::string& cvar_value) {
|
||||
if (cvar_value == GetCvarValueForGuestOutputPaintEffect(
|
||||
ui::Presenter::GuestOutputPaintConfig::Effect::kCas)) {
|
||||
return ui::Presenter::GuestOutputPaintConfig::Effect::kCas;
|
||||
}
|
||||
if (cvar_value == GetCvarValueForGuestOutputPaintEffect(
|
||||
ui::Presenter::GuestOutputPaintConfig::Effect::kFsr)) {
|
||||
return ui::Presenter::GuestOutputPaintConfig::Effect::kFsr;
|
||||
}
|
||||
return ui::Presenter::GuestOutputPaintConfig::Effect::kBilinear;
|
||||
}
|
||||
|
||||
ui::Presenter::GuestOutputPaintConfig
|
||||
EmulatorWindow::GetGuestOutputPaintConfigForCvars() {
|
||||
ui::Presenter::GuestOutputPaintConfig paint_config;
|
||||
paint_config.SetEffect(GetGuestOutputPaintEffectForCvarValue(
|
||||
cvars::postprocess_scaling_and_sharpening));
|
||||
paint_config.SetCasAdditionalSharpness(
|
||||
float(cvars::postprocess_ffx_cas_additional_sharpness));
|
||||
paint_config.SetFsrMaxUpsamplingPasses(
|
||||
cvars::postprocess_ffx_fsr_max_upsampling_passes);
|
||||
paint_config.SetFsrSharpnessReduction(
|
||||
float(cvars::postprocess_ffx_fsr_sharpness_reduction));
|
||||
paint_config.SetDither(cvars::postprocess_dither);
|
||||
return paint_config;
|
||||
}
|
||||
|
||||
void EmulatorWindow::ApplyDisplayConfigForCvars() {
|
||||
gpu::GraphicsSystem* graphics_system = emulator_->graphics_system();
|
||||
if (!graphics_system) {
|
||||
return;
|
||||
}
|
||||
|
||||
gpu::CommandProcessor* command_processor =
|
||||
graphics_system->command_processor();
|
||||
if (command_processor) {
|
||||
command_processor->SetDesiredSwapPostEffect(
|
||||
GetSwapPostEffectForCvarValue(cvars::postprocess_antialiasing));
|
||||
}
|
||||
|
||||
ui::Presenter* presenter = graphics_system->presenter();
|
||||
if (presenter) {
|
||||
presenter->SetGuestOutputPaintConfigFromUIThread(
|
||||
GetGuestOutputPaintConfigForCvars());
|
||||
}
|
||||
}
|
||||
|
||||
void EmulatorWindow::OnKeyDown(ui::KeyEvent& e) {
|
||||
if (!emulator_initialized_) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (e.virtual_key()) {
|
||||
case ui::VirtualKey::kO: {
|
||||
if (!e.is_ctrl_pressed()) {
|
||||
return;
|
||||
}
|
||||
FileOpen();
|
||||
} break;
|
||||
case ui::VirtualKey::kMultiply: {
|
||||
CpuTimeScalarReset();
|
||||
} break;
|
||||
case ui::VirtualKey::kSubtract: {
|
||||
CpuTimeScalarSetHalf();
|
||||
} break;
|
||||
case ui::VirtualKey::kAdd: {
|
||||
CpuTimeScalarSetDouble();
|
||||
} break;
|
||||
|
||||
case ui::VirtualKey::kF3: {
|
||||
Profiler::ToggleDisplay();
|
||||
} break;
|
||||
|
||||
case ui::VirtualKey::kF4: {
|
||||
GpuTraceFrame();
|
||||
} break;
|
||||
case ui::VirtualKey::kF5: {
|
||||
GpuClearCaches();
|
||||
} break;
|
||||
|
||||
case ui::VirtualKey::kF6: {
|
||||
ToggleDisplayConfigDialog();
|
||||
} break;
|
||||
case ui::VirtualKey::kF11: {
|
||||
ToggleFullscreen();
|
||||
} break;
|
||||
case ui::VirtualKey::kEscape: {
|
||||
// Allow users to escape fullscreen (but not enter it).
|
||||
if (!window_->IsFullscreen()) {
|
||||
return;
|
||||
}
|
||||
SetFullscreen(false);
|
||||
} break;
|
||||
|
||||
#ifdef DEBUG
|
||||
case ui::VirtualKey::kF7: {
|
||||
// Save to file
|
||||
// TODO: Choose path based on user input, or from options
|
||||
// TODO: Spawn a new thread to do this.
|
||||
emulator()->SaveToFile("test.sav");
|
||||
} break;
|
||||
case ui::VirtualKey::kF8: {
|
||||
// Restore from file
|
||||
// TODO: Choose path from user
|
||||
// TODO: Spawn a new thread to do this.
|
||||
emulator()->RestoreFromFile("test.sav");
|
||||
} break;
|
||||
#endif // #ifdef DEBUG
|
||||
|
||||
case ui::VirtualKey::kPause: {
|
||||
CpuBreakIntoDebugger();
|
||||
} break;
|
||||
case ui::VirtualKey::kCancel: {
|
||||
CpuBreakIntoHostDebugger();
|
||||
} break;
|
||||
|
||||
case ui::VirtualKey::kF1: {
|
||||
ShowFAQ();
|
||||
} break;
|
||||
|
||||
case ui::VirtualKey::kF2: {
|
||||
ShowBuildCommit();
|
||||
} break;
|
||||
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
e.set_handled(true);
|
||||
}
|
||||
|
||||
void EmulatorWindow::FileDrop(const std::filesystem::path& filename) {
|
||||
if (!emulator_initialized_) {
|
||||
return;
|
||||
}
|
||||
auto result = emulator_->LaunchPath(filename);
|
||||
if (XFAILED(result)) {
|
||||
// TODO: Display a message box.
|
||||
|
@ -312,7 +825,7 @@ void EmulatorWindow::FileOpen() {
|
|||
//{"Content Package (*.xcp)", "*.xcp" },
|
||||
{"All Files (*.*)", "*.*"},
|
||||
});
|
||||
if (file_picker->Show(window_->native_handle())) {
|
||||
if (file_picker->Show(window_.get())) {
|
||||
auto selected_files = file_picker->selected_files();
|
||||
if (!selected_files.empty()) {
|
||||
path = selected_files[0];
|
||||
|
@ -357,17 +870,6 @@ void EmulatorWindow::ShowContentDirectory() {
|
|||
LaunchFileExplorer(target_path);
|
||||
}
|
||||
|
||||
void EmulatorWindow::CheckHideCursor() {
|
||||
if (!window_->is_fullscreen()) {
|
||||
// Only hide when fullscreen.
|
||||
return;
|
||||
}
|
||||
|
||||
if (Clock::QueryHostSystemTime() > cursor_hide_time_) {
|
||||
window_->set_cursor_visible(false);
|
||||
}
|
||||
}
|
||||
|
||||
void EmulatorWindow::CpuTimeScalarReset() {
|
||||
Clock::set_guest_time_scalar(1.0);
|
||||
UpdateTitle();
|
||||
|
@ -385,7 +887,7 @@ void EmulatorWindow::CpuTimeScalarSetDouble() {
|
|||
|
||||
void EmulatorWindow::CpuBreakIntoDebugger() {
|
||||
if (!cvars::debug) {
|
||||
xe::ui::ImGuiDialog::ShowMessageBox(window_.get(), "Xenia Debugger",
|
||||
xe::ui::ImGuiDialog::ShowMessageBox(imgui_drawer_.get(), "Xenia Debugger",
|
||||
"Xenia must be launched with the "
|
||||
"--debug flag in order to enable "
|
||||
"debugging.");
|
||||
|
@ -411,19 +913,48 @@ void EmulatorWindow::GpuClearCaches() {
|
|||
emulator()->graphics_system()->ClearCaches();
|
||||
}
|
||||
|
||||
void EmulatorWindow::ToggleFullscreen() {
|
||||
window_->ToggleFullscreen(!window_->is_fullscreen());
|
||||
void EmulatorWindow::SetFullscreen(bool fullscreen) {
|
||||
if (window_->IsFullscreen() == fullscreen) {
|
||||
return;
|
||||
}
|
||||
window_->SetFullscreen(fullscreen);
|
||||
window_->SetCursorVisibility(fullscreen
|
||||
? ui::Window::CursorVisibility::kAutoHidden
|
||||
: ui::Window::CursorVisibility::kVisible);
|
||||
}
|
||||
|
||||
// Hide the cursor after a second if we're going fullscreen
|
||||
cursor_hide_time_ = Clock::QueryHostSystemTime() + 30000000;
|
||||
if (!window_->is_fullscreen()) {
|
||||
window_->set_cursor_visible(true);
|
||||
void EmulatorWindow::ToggleFullscreen() {
|
||||
SetFullscreen(!window_->IsFullscreen());
|
||||
}
|
||||
|
||||
void EmulatorWindow::ToggleDisplayConfigDialog() {
|
||||
if (!display_config_dialog_) {
|
||||
display_config_dialog_ = std::unique_ptr<DisplayConfigDialog>(
|
||||
new DisplayConfigDialog(imgui_drawer_.get(), *this));
|
||||
} else {
|
||||
display_config_dialog_.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void EmulatorWindow::ShowHelpWebsite() { LaunchWebBrowser("https://xenia.jp"); }
|
||||
void EmulatorWindow::ShowCompatibility() {
|
||||
const std::string_view base_url =
|
||||
"https://github.com/xenia-project/game-compatibility/issues";
|
||||
std::string url;
|
||||
// Avoid searching for a title ID of "00000000".
|
||||
uint32_t title_id = emulator_->title_id();
|
||||
if (!title_id) {
|
||||
url = base_url;
|
||||
} else {
|
||||
url = fmt::format("{}?q=is%3Aissue+is%3Aopen+{:08X}", base_url, title_id);
|
||||
}
|
||||
LaunchWebBrowser(url);
|
||||
}
|
||||
|
||||
void EmulatorWindow::ShowCommitID() {
|
||||
void EmulatorWindow::ShowFAQ() {
|
||||
LaunchWebBrowser("https://github.com/xenia-project/xenia/wiki/FAQ");
|
||||
}
|
||||
|
||||
void EmulatorWindow::ShowBuildCommit() {
|
||||
#ifdef XE_BUILD_IS_PR
|
||||
LaunchWebBrowser(
|
||||
"https://github.com/xenia-project/xenia/pull/" XE_BUILD_PR_NUMBER);
|
||||
|
@ -473,7 +1004,7 @@ void EmulatorWindow::UpdateTitle() {
|
|||
sb.Append(u8" (Preloading shaders\u2026)");
|
||||
}
|
||||
|
||||
window_->set_title(sb.to_string_view());
|
||||
window_->SetTitle(sb.to_string_view());
|
||||
}
|
||||
|
||||
void EmulatorWindow::SetInitializingShaderStorage(bool initializing) {
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2020 Ben Vanik. All rights reserved. *
|
||||
* Copyright 2022 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
@ -13,42 +13,124 @@
|
|||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "xenia/emulator.h"
|
||||
#include "xenia/gpu/command_processor.h"
|
||||
#include "xenia/ui/imgui_dialog.h"
|
||||
#include "xenia/ui/imgui_drawer.h"
|
||||
#include "xenia/ui/immediate_drawer.h"
|
||||
#include "xenia/ui/menu_item.h"
|
||||
#include "xenia/ui/presenter.h"
|
||||
#include "xenia/ui/window.h"
|
||||
#include "xenia/ui/window_listener.h"
|
||||
#include "xenia/ui/windowed_app_context.h"
|
||||
#include "xenia/xbox.h"
|
||||
|
||||
namespace xe {
|
||||
class Emulator;
|
||||
} // namespace xe
|
||||
|
||||
namespace xe {
|
||||
namespace app {
|
||||
|
||||
class EmulatorWindow {
|
||||
public:
|
||||
enum : size_t {
|
||||
// The UI is on top of the game and is open in special cases, so
|
||||
// lowest-priority.
|
||||
kZOrderHidInput,
|
||||
kZOrderImGui,
|
||||
kZOrderProfiler,
|
||||
// Emulator window controls are expected to be always accessible by the
|
||||
// user, so highest-priority.
|
||||
kZOrderEmulatorWindowInput,
|
||||
};
|
||||
|
||||
virtual ~EmulatorWindow();
|
||||
|
||||
static std::unique_ptr<EmulatorWindow> Create(
|
||||
Emulator* emulator, ui::WindowedAppContext& app_context);
|
||||
|
||||
Emulator* emulator() const { return emulator_; }
|
||||
ui::WindowedAppContext& app_context() const { return app_context_; }
|
||||
ui::Window* window() const { return window_.get(); }
|
||||
ui::ImGuiDrawer* imgui_drawer() const { return imgui_drawer_.get(); }
|
||||
|
||||
ui::Presenter* GetGraphicsSystemPresenter() const;
|
||||
void SetupGraphicsSystemPresenterPainting();
|
||||
void ShutdownGraphicsSystemPresenterPainting();
|
||||
|
||||
void OnEmulatorInitialized();
|
||||
|
||||
void UpdateTitle();
|
||||
void SetFullscreen(bool fullscreen);
|
||||
void ToggleFullscreen();
|
||||
void SetInitializingShaderStorage(bool initializing);
|
||||
|
||||
private:
|
||||
class EmulatorWindowListener final : public ui::WindowListener,
|
||||
public ui::WindowInputListener {
|
||||
public:
|
||||
explicit EmulatorWindowListener(EmulatorWindow& emulator_window)
|
||||
: emulator_window_(emulator_window) {}
|
||||
|
||||
void OnClosing(ui::UIEvent& e) override;
|
||||
void OnFileDrop(ui::FileDropEvent& e) override;
|
||||
|
||||
void OnKeyDown(ui::KeyEvent& e) override;
|
||||
|
||||
private:
|
||||
EmulatorWindow& emulator_window_;
|
||||
};
|
||||
|
||||
class DisplayConfigGameConfigLoadCallback
|
||||
: public Emulator::GameConfigLoadCallback {
|
||||
public:
|
||||
DisplayConfigGameConfigLoadCallback(Emulator& emulator,
|
||||
EmulatorWindow& emulator_window)
|
||||
: Emulator::GameConfigLoadCallback(emulator),
|
||||
emulator_window_(emulator_window) {}
|
||||
|
||||
void PostGameConfigLoad() override;
|
||||
|
||||
private:
|
||||
EmulatorWindow& emulator_window_;
|
||||
};
|
||||
|
||||
class DisplayConfigDialog final : public ui::ImGuiDialog {
|
||||
public:
|
||||
DisplayConfigDialog(ui::ImGuiDrawer* imgui_drawer,
|
||||
EmulatorWindow& emulator_window)
|
||||
: ui::ImGuiDialog(imgui_drawer), emulator_window_(emulator_window) {}
|
||||
|
||||
protected:
|
||||
void OnDraw(ImGuiIO& io) override;
|
||||
|
||||
private:
|
||||
EmulatorWindow& emulator_window_;
|
||||
};
|
||||
|
||||
explicit EmulatorWindow(Emulator* emulator,
|
||||
ui::WindowedAppContext& app_context);
|
||||
|
||||
bool Initialize();
|
||||
|
||||
// For comparisons, use GetSwapPostEffectForCvarValue instead as the default
|
||||
// fallback may be used for multiple values.
|
||||
static const char* GetCvarValueForSwapPostEffect(
|
||||
gpu::CommandProcessor::SwapPostEffect effect);
|
||||
static gpu::CommandProcessor::SwapPostEffect GetSwapPostEffectForCvarValue(
|
||||
const std::string& cvar_value);
|
||||
// For comparisons, use GetGuestOutputPaintEffectForCvarValue instead as the
|
||||
// default fallback may be used for multiple values.
|
||||
static const char* GetCvarValueForGuestOutputPaintEffect(
|
||||
ui::Presenter::GuestOutputPaintConfig::Effect effect);
|
||||
static ui::Presenter::GuestOutputPaintConfig::Effect
|
||||
GetGuestOutputPaintEffectForCvarValue(const std::string& cvar_value);
|
||||
static ui::Presenter::GuestOutputPaintConfig
|
||||
GetGuestOutputPaintConfigForCvars();
|
||||
void ApplyDisplayConfigForCvars();
|
||||
|
||||
void OnKeyDown(ui::KeyEvent& e);
|
||||
void FileDrop(const std::filesystem::path& filename);
|
||||
void FileOpen();
|
||||
void FileClose();
|
||||
void ShowContentDirectory();
|
||||
void CheckHideCursor();
|
||||
void CpuTimeScalarReset();
|
||||
void CpuTimeScalarSetHalf();
|
||||
void CpuTimeScalarSetDouble();
|
||||
|
@ -56,15 +138,27 @@ class EmulatorWindow {
|
|||
void CpuBreakIntoHostDebugger();
|
||||
void GpuTraceFrame();
|
||||
void GpuClearCaches();
|
||||
void ShowHelpWebsite();
|
||||
void ShowCommitID();
|
||||
void ToggleDisplayConfigDialog();
|
||||
void ShowCompatibility();
|
||||
void ShowFAQ();
|
||||
void ShowBuildCommit();
|
||||
|
||||
Emulator* emulator_;
|
||||
ui::WindowedAppContext& app_context_;
|
||||
EmulatorWindowListener window_listener_;
|
||||
std::unique_ptr<ui::Window> window_;
|
||||
std::unique_ptr<ui::ImGuiDrawer> imgui_drawer_;
|
||||
std::unique_ptr<DisplayConfigGameConfigLoadCallback>
|
||||
display_config_game_config_load_callback_;
|
||||
// Creation may fail, in this case immediate drawer UI must not be drawn.
|
||||
std::unique_ptr<ui::ImmediateDrawer> immediate_drawer_;
|
||||
|
||||
bool emulator_initialized_ = false;
|
||||
|
||||
std::string base_title_;
|
||||
uint64_t cursor_hide_time_ = 0;
|
||||
bool initializing_shader_storage_ = false;
|
||||
|
||||
std::unique_ptr<DisplayConfigDialog> display_config_dialog_;
|
||||
};
|
||||
|
||||
} // namespace app
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2020 Ben Vanik. All rights reserved. *
|
||||
* Copyright 2022 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
@ -28,6 +28,7 @@
|
|||
#include "xenia/emulator.h"
|
||||
#include "xenia/ui/file_picker.h"
|
||||
#include "xenia/ui/window.h"
|
||||
#include "xenia/ui/window_listener.h"
|
||||
#include "xenia/ui/windowed_app.h"
|
||||
#include "xenia/ui/windowed_app_context.h"
|
||||
#include "xenia/vfs/devices/host_path_device.h"
|
||||
|
@ -62,8 +63,6 @@ DEFINE_string(gpu, "any", "Graphics system. Use: [any, d3d12, vulkan, null]",
|
|||
DEFINE_string(hid, "any", "Input system. Use: [any, nop, sdl, winkey, xinput]",
|
||||
"HID");
|
||||
|
||||
DEFINE_bool(fullscreen, false, "Toggles fullscreen", "GPU");
|
||||
|
||||
DEFINE_path(
|
||||
storage_root, "",
|
||||
"Root path for persistent internal data storage (config, etc.), or empty "
|
||||
|
@ -192,6 +191,17 @@ class EmulatorApp final : public xe::ui::WindowedApp {
|
|||
}
|
||||
};
|
||||
|
||||
class DebugWindowClosedListener final : public xe::ui::WindowListener {
|
||||
public:
|
||||
explicit DebugWindowClosedListener(EmulatorApp& emulator_app)
|
||||
: emulator_app_(emulator_app) {}
|
||||
|
||||
void OnClosing(xe::ui::UIEvent& e) override;
|
||||
|
||||
private:
|
||||
EmulatorApp& emulator_app_;
|
||||
};
|
||||
|
||||
explicit EmulatorApp(xe::ui::WindowedAppContext& app_context);
|
||||
|
||||
static std::unique_ptr<apu::AudioSystem> CreateAudioSystem(
|
||||
|
@ -203,6 +213,8 @@ class EmulatorApp final : public xe::ui::WindowedApp {
|
|||
void EmulatorThread();
|
||||
void ShutdownEmulatorThreadFromUIThread();
|
||||
|
||||
DebugWindowClosedListener debug_window_closed_listener_;
|
||||
|
||||
std::unique_ptr<Emulator> emulator_;
|
||||
std::unique_ptr<EmulatorWindow> emulator_window_;
|
||||
|
||||
|
@ -215,8 +227,15 @@ class EmulatorApp final : public xe::ui::WindowedApp {
|
|||
std::thread emulator_thread_;
|
||||
};
|
||||
|
||||
void EmulatorApp::DebugWindowClosedListener::OnClosing(xe::ui::UIEvent& e) {
|
||||
EmulatorApp* emulator_app = &emulator_app_;
|
||||
emulator_app->emulator_->processor()->set_debug_listener(nullptr);
|
||||
emulator_app->debug_window_.reset();
|
||||
}
|
||||
|
||||
EmulatorApp::EmulatorApp(xe::ui::WindowedAppContext& app_context)
|
||||
: xe::ui::WindowedApp(app_context, "xenia", "[Path to .iso/.xex]") {
|
||||
: xe::ui::WindowedApp(app_context, "xenia", "[Path to .iso/.xex]"),
|
||||
debug_window_closed_listener_(*this) {
|
||||
AddPositionalOption("target");
|
||||
}
|
||||
|
||||
|
@ -253,9 +272,10 @@ std::vector<std::unique_ptr<hid::InputDriver>> EmulatorApp::CreateInputDrivers(
|
|||
ui::Window* window) {
|
||||
std::vector<std::unique_ptr<hid::InputDriver>> drivers;
|
||||
if (cvars::hid.compare("nop") == 0) {
|
||||
drivers.emplace_back(xe::hid::nop::Create(window));
|
||||
drivers.emplace_back(
|
||||
xe::hid::nop::Create(window, EmulatorWindow::kZOrderHidInput));
|
||||
} else {
|
||||
Factory<hid::InputDriver, ui::Window*> factory;
|
||||
Factory<hid::InputDriver, ui::Window*, size_t> factory;
|
||||
#if XE_PLATFORM_WIN32
|
||||
factory.Add("xinput", xe::hid::xinput::Create);
|
||||
#endif // XE_PLATFORM_WIN32
|
||||
|
@ -264,14 +284,16 @@ std::vector<std::unique_ptr<hid::InputDriver>> EmulatorApp::CreateInputDrivers(
|
|||
// WinKey input driver should always be the last input driver added!
|
||||
factory.Add("winkey", xe::hid::winkey::Create);
|
||||
#endif // XE_PLATFORM_WIN32
|
||||
for (auto& driver : factory.CreateAll(cvars::hid, window)) {
|
||||
for (auto& driver : factory.CreateAll(cvars::hid, window,
|
||||
EmulatorWindow::kZOrderHidInput)) {
|
||||
if (XSUCCEEDED(driver->Setup())) {
|
||||
drivers.emplace_back(std::move(driver));
|
||||
}
|
||||
}
|
||||
if (drivers.empty()) {
|
||||
// Fallback to nop if none created.
|
||||
drivers.emplace_back(xe::hid::nop::Create(window));
|
||||
drivers.emplace_back(
|
||||
xe::hid::nop::Create(window, EmulatorWindow::kZOrderHidInput));
|
||||
}
|
||||
}
|
||||
return drivers;
|
||||
|
@ -366,6 +388,9 @@ void EmulatorApp::OnDestroy() {
|
|||
// The profiler needs to shut down before the graphics context.
|
||||
Profiler::Shutdown();
|
||||
|
||||
// Write all cvar overrides to the config.
|
||||
config::SaveConfig();
|
||||
|
||||
// TODO(DrChat): Remove this code and do a proper exit.
|
||||
XELOGI("Cheap-skate exit!");
|
||||
std::quick_exit(EXIT_SUCCESS);
|
||||
|
@ -379,15 +404,18 @@ void EmulatorApp::EmulatorThread() {
|
|||
|
||||
// Setup and initialize all subsystems. If we can't do something
|
||||
// (unsupported system, memory issues, etc) this will fail early.
|
||||
X_STATUS result =
|
||||
emulator_->Setup(emulator_window_->window(), CreateAudioSystem,
|
||||
CreateGraphicsSystem, CreateInputDrivers);
|
||||
X_STATUS result = emulator_->Setup(
|
||||
emulator_window_->window(), emulator_window_->imgui_drawer(),
|
||||
CreateAudioSystem, CreateGraphicsSystem, CreateInputDrivers);
|
||||
if (XFAILED(result)) {
|
||||
XELOGE("Failed to setup emulator: {:08X}", result);
|
||||
app_context().RequestDeferredQuit();
|
||||
return;
|
||||
}
|
||||
|
||||
app_context().CallInUIThread(
|
||||
[this]() { emulator_window_->SetupGraphicsSystemPresenterPainting(); });
|
||||
|
||||
if (cvars::mount_scratch) {
|
||||
auto scratch_device = std::make_unique<xe::vfs::HostPathDevice>(
|
||||
"\\SCRATCH", "scratch", false);
|
||||
|
@ -456,12 +484,8 @@ void EmulatorApp::EmulatorThread() {
|
|||
app_context().CallInUIThreadSynchronous([this]() {
|
||||
debug_window_ = xe::debug::ui::DebugWindow::Create(emulator_.get(),
|
||||
app_context());
|
||||
debug_window_->window()->on_closed.AddListener(
|
||||
[this](xe::ui::UIEvent* e) {
|
||||
emulator_->processor()->set_debug_listener(nullptr);
|
||||
app_context().CallInUIThread(
|
||||
[this]() { debug_window_.reset(); });
|
||||
});
|
||||
debug_window_->window()->AddListener(
|
||||
&debug_window_closed_listener_);
|
||||
});
|
||||
// If failed to enqueue the UI thread call, this will just be null.
|
||||
return debug_window_.get();
|
||||
|
@ -490,9 +514,9 @@ void EmulatorApp::EmulatorThread() {
|
|||
}
|
||||
});
|
||||
|
||||
// Enable the main menu now that the emulator is properly loaded
|
||||
// Enable emulator input now that the emulator is properly loaded.
|
||||
app_context().CallInUIThread(
|
||||
[this]() { emulator_window_->window()->EnableMainMenu(); });
|
||||
[this]() { emulator_window_->OnEmulatorInitialized(); });
|
||||
|
||||
// Grab path from the flag or unnamed argument.
|
||||
std::filesystem::path path;
|
||||
|
@ -500,12 +524,6 @@ void EmulatorApp::EmulatorThread() {
|
|||
path = cvars::target;
|
||||
}
|
||||
|
||||
// Toggles fullscreen
|
||||
if (cvars::fullscreen) {
|
||||
app_context().CallInUIThread(
|
||||
[this]() { emulator_window_->ToggleFullscreen(); });
|
||||
}
|
||||
|
||||
if (!path.empty()) {
|
||||
// Normalize the path and make absolute.
|
||||
auto abs_path = std::filesystem::absolute(path);
|
||||
|
|
|
@ -177,6 +177,25 @@ void XmaContext::SwapInputBuffer(XMA_CONTEXT_DATA* data) {
|
|||
data->input_buffer_read_offset = 0;
|
||||
}
|
||||
|
||||
bool XmaContext::TrySetupNextLoop(XMA_CONTEXT_DATA* data,
|
||||
bool ignore_input_buffer_offset) {
|
||||
// Setup the input buffer offset if next loop exists.
|
||||
// TODO(Pseudo-Kernel): Need to handle loop in the following cases.
|
||||
// 1. loop_start == loop_end == 0
|
||||
// 2. loop_start > loop_end && loop_count > 0
|
||||
if (data->loop_count > 0 && data->loop_start < data->loop_end &&
|
||||
(ignore_input_buffer_offset ||
|
||||
data->input_buffer_read_offset >= data->loop_end)) {
|
||||
// Loop back to the beginning.
|
||||
data->input_buffer_read_offset = data->loop_start;
|
||||
if (data->loop_count < 255) {
|
||||
data->loop_count--;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
void XmaContext::NextPacket(
|
||||
uint8_t* input_buffer,
|
||||
|
@ -364,6 +383,7 @@ void XmaContext::Decode(XMA_CONTEXT_DATA* data) {
|
|||
assert_false(data->stop_when_done);
|
||||
assert_false(data->interrupt_when_done);
|
||||
static int total_samples = 0;
|
||||
bool reuse_input_buffer = false;
|
||||
// Decode until we can't write any more data.
|
||||
while (output_remaining_bytes > 0) {
|
||||
if (!data->input_buffer_0_valid && !data->input_buffer_1_valid) {
|
||||
|
@ -371,6 +391,10 @@ void XmaContext::Decode(XMA_CONTEXT_DATA* data) {
|
|||
break;
|
||||
}
|
||||
|
||||
// Setup the input buffer if we are at loop_end.
|
||||
// The input buffer must not be swapped out until all loops are processed.
|
||||
reuse_input_buffer = TrySetupNextLoop(data, false);
|
||||
|
||||
// assert_true(packets_skip_ == 0);
|
||||
// assert_true(split_frame_len_ == 0);
|
||||
// assert_true(split_frame_len_partial_ == 0);
|
||||
|
@ -392,7 +416,13 @@ void XmaContext::Decode(XMA_CONTEXT_DATA* data) {
|
|||
packets_skip_--;
|
||||
packet_idx++;
|
||||
if (packet_idx >= current_input_packet_count) {
|
||||
SwapInputBuffer(data);
|
||||
if (!reuse_input_buffer) {
|
||||
// Last packet. Try setup once more.
|
||||
reuse_input_buffer = TrySetupNextLoop(data, true);
|
||||
}
|
||||
if (!reuse_input_buffer) {
|
||||
SwapInputBuffer(data);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -524,7 +554,13 @@ void XmaContext::Decode(XMA_CONTEXT_DATA* data) {
|
|||
packet += kBytesPerPacket;
|
||||
packet_idx++;
|
||||
if (packet_idx >= current_input_packet_count) {
|
||||
SwapInputBuffer(data);
|
||||
if (!reuse_input_buffer) {
|
||||
// Last packet. Try setup once more.
|
||||
reuse_input_buffer = TrySetupNextLoop(data, true);
|
||||
}
|
||||
if (!reuse_input_buffer) {
|
||||
SwapInputBuffer(data);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -606,7 +642,13 @@ void XmaContext::Decode(XMA_CONTEXT_DATA* data) {
|
|||
packets_skip_--;
|
||||
packet_idx++;
|
||||
if (packet_idx >= current_input_packet_count) {
|
||||
SwapInputBuffer(data);
|
||||
if (!reuse_input_buffer) {
|
||||
// Last packet. Try setup once more.
|
||||
reuse_input_buffer = TrySetupNextLoop(data, true);
|
||||
}
|
||||
if (!reuse_input_buffer) {
|
||||
SwapInputBuffer(data);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -618,7 +660,13 @@ void XmaContext::Decode(XMA_CONTEXT_DATA* data) {
|
|||
// Next packet but we already skipped to it
|
||||
if (packet_idx >= current_input_packet_count) {
|
||||
// Buffer is fully used
|
||||
SwapInputBuffer(data);
|
||||
if (!reuse_input_buffer) {
|
||||
// Last packet. Try setup once more.
|
||||
reuse_input_buffer = TrySetupNextLoop(data, true);
|
||||
}
|
||||
if (!reuse_input_buffer) {
|
||||
SwapInputBuffer(data);
|
||||
}
|
||||
break;
|
||||
}
|
||||
offset =
|
||||
|
|
|
@ -171,6 +171,8 @@ class XmaContext {
|
|||
|
||||
private:
|
||||
static void SwapInputBuffer(XMA_CONTEXT_DATA* data);
|
||||
static bool TrySetupNextLoop(XMA_CONTEXT_DATA* data,
|
||||
bool ignore_input_buffer_offset);
|
||||
static void NextPacket(XMA_CONTEXT_DATA* data);
|
||||
static int GetSampleRate(int id);
|
||||
// Get the offset of the next frame. Does not traverse packets.
|
||||
|
|
|
@ -60,7 +60,7 @@ void* Arena::Alloc(size_t size, size_t align) {
|
|||
|
||||
if (active_chunk_) {
|
||||
if (active_chunk_->capacity - active_chunk_->offset <
|
||||
size + get_padding() + 4096) {
|
||||
size + get_padding() + 4_KiB) {
|
||||
Chunk* next = active_chunk_->next;
|
||||
if (!next) {
|
||||
assert_true(size + get_padding() < chunk_size_,
|
||||
|
|
|
@ -14,11 +14,15 @@
|
|||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#include "xenia/base/literals.h"
|
||||
|
||||
namespace xe {
|
||||
|
||||
using namespace xe::literals;
|
||||
|
||||
class Arena {
|
||||
public:
|
||||
explicit Arena(size_t chunk_size = 4 * 1024 * 1024);
|
||||
explicit Arena(size_t chunk_size = 4_MiB);
|
||||
~Arena();
|
||||
|
||||
void Reset();
|
||||
|
|
|
@ -24,7 +24,7 @@ extern "C" int main(int argc, char** argv) {
|
|||
|
||||
// Initialize Android globals, including logging. Needs parsed cvars.
|
||||
// TODO(Triang3l): Obtain the actual API level.
|
||||
xe::InitializeAndroidAppFromMainThread(__ANDROID_API__);
|
||||
xe::InitializeAndroidAppFromMainThread(__ANDROID_API__, nullptr, nullptr);
|
||||
|
||||
std::vector<std::string> args;
|
||||
for (int n = 0; n < argc; n++) {
|
||||
|
|
|
@ -13,7 +13,8 @@
|
|||
#include "xenia/base/console_app_main.h"
|
||||
#include "xenia/base/main_win.h"
|
||||
|
||||
int main(int argc_ignored, char** argv_ignored) {
|
||||
// A wide character entry point is required for functions like _get_wpgmptr.
|
||||
int wmain(int argc_ignored, wchar_t** argv_ignored) {
|
||||
xe::ConsoleAppEntryInfo entry_info = xe::GetConsoleAppEntryInfo();
|
||||
|
||||
std::vector<std::string> args;
|
||||
|
|
|
@ -27,7 +27,7 @@ static bool has_shell_environment_variable() {
|
|||
size_t size = 0;
|
||||
// Check if SHELL exists
|
||||
// If it doesn't, then we are in a Windows Terminal
|
||||
auto error = getenv_s(&size, nullptr, 0, "SHELL");
|
||||
auto error = _wgetenv_s(&size, nullptr, 0, L"SHELL");
|
||||
if (error) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2020 Ben Vanik. All rights reserved. *
|
||||
* Copyright 2022 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
@ -86,6 +86,10 @@ class ConfigVar : public CommandVar<T>, virtual public IConfigVar {
|
|||
void LoadGameConfigValue(std::shared_ptr<cpptoml::base> result) override;
|
||||
void SetConfigValue(T val);
|
||||
void SetGameConfigValue(T val);
|
||||
// Changes the actual value used to the one specified, and also makes it the
|
||||
// one that will be stored when the global config is written next time. After
|
||||
// overriding, however, the next game config loaded may still change it.
|
||||
void OverrideConfigValue(T val);
|
||||
|
||||
private:
|
||||
std::string category_;
|
||||
|
@ -260,6 +264,16 @@ void ConfigVar<T>::SetGameConfigValue(T val) {
|
|||
UpdateValue();
|
||||
}
|
||||
template <class T>
|
||||
void ConfigVar<T>::OverrideConfigValue(T val) {
|
||||
config_value_ = std::make_unique<T>(val);
|
||||
// The user explicitly changes the value at runtime and wants it to take
|
||||
// effect immediately. Drop everything with a higher priority. The next game
|
||||
// config load, however, may still change it.
|
||||
game_config_value_.reset();
|
||||
this->commandline_value_.reset();
|
||||
UpdateValue();
|
||||
}
|
||||
template <class T>
|
||||
void ConfigVar<T>::ResetConfigValueToDefault() {
|
||||
SetConfigValue(this->default_value_);
|
||||
}
|
||||
|
@ -373,6 +387,28 @@ ICommandVar* define_cmdvar(const char* name, T* default_value,
|
|||
extern type name; \
|
||||
}
|
||||
|
||||
#define ACCESS_CVar(name) (*cv::cv_##name)
|
||||
|
||||
// dynamic_cast is needed because of virtual inheritance.
|
||||
#define OVERRIDE_CVar(name, type, value) \
|
||||
dynamic_cast<cvar::ConfigVar<type>*>(&ACCESS_CVar(name)) \
|
||||
->OverrideConfigValue(value);
|
||||
|
||||
#define OVERRIDE_bool(name, value) OVERRIDE_CVar(name, bool, value)
|
||||
|
||||
#define OVERRIDE_int32(name, value) OVERRIDE_CVar(name, int32_t, value)
|
||||
|
||||
#define OVERRIDE_uint32(name, value) OVERRIDE_CVar(name, uint32_t, value)
|
||||
|
||||
#define OVERRIDE_uint64(name, value) OVERRIDE_CVar(name, uint64_t, value)
|
||||
|
||||
#define OVERRIDE_double(name, value) OVERRIDE_CVar(name, double, value)
|
||||
|
||||
#define OVERRIDE_string(name, value) OVERRIDE_CVar(name, std::string, value)
|
||||
|
||||
#define OVERRIDE_path(name, value) \
|
||||
OVERRIDE_CVar(name, std::filesystem::path, value)
|
||||
|
||||
// Interface for changing the default value of a variable with auto-upgrading of
|
||||
// users' configs (to distinguish between a leftover old default and an explicit
|
||||
// override), without having to rename the variable.
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2020 Ben Vanik. All rights reserved. *
|
||||
* Copyright 2022 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
@ -41,8 +41,8 @@ std::filesystem::path GetUserFolder();
|
|||
// attempting to create it.
|
||||
bool CreateParentFolder(const std::filesystem::path& path);
|
||||
|
||||
// Creates an empty file at the given path.
|
||||
bool CreateFile(const std::filesystem::path& path);
|
||||
// Creates an empty file at the given path, overwriting if it exists.
|
||||
bool CreateEmptyFile(const std::filesystem::path& path);
|
||||
|
||||
// Opens the file at the given path with the specified mode.
|
||||
// This behaves like fopen and the returned handle can be used with stdio.
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2020 Ben Vanik. All rights reserved. *
|
||||
* Copyright 2022 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
@ -122,7 +122,7 @@ static uint64_t convertUnixtimeToWinFiletime(time_t unixtime) {
|
|||
return filetime;
|
||||
}
|
||||
|
||||
bool CreateFile(const std::filesystem::path& path) {
|
||||
bool CreateEmptyFile(const std::filesystem::path& path) {
|
||||
int file = creat(path.c_str(), 0774);
|
||||
if (file >= 0) {
|
||||
close(file);
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2020 Ben Vanik. All rights reserved. *
|
||||
* Copyright 2022 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
@ -60,7 +60,7 @@ std::filesystem::path GetUserFolder() {
|
|||
return result;
|
||||
}
|
||||
|
||||
bool CreateFile(const std::filesystem::path& path) {
|
||||
bool CreateEmptyFile(const std::filesystem::path& path) {
|
||||
auto handle = CreateFileW(path.c_str(), 0, 0, nullptr, CREATE_ALWAYS,
|
||||
FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||
if (handle == INVALID_HANDLE_VALUE) {
|
||||
|
|
|
@ -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 <atomic>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
@ -25,6 +26,7 @@
|
|||
#include "xenia/base/cvar.h"
|
||||
#include "xenia/base/debugging.h"
|
||||
#include "xenia/base/filesystem.h"
|
||||
#include "xenia/base/literals.h"
|
||||
#include "xenia/base/math.h"
|
||||
#include "xenia/base/memory.h"
|
||||
#include "xenia/base/platform.h"
|
||||
|
@ -59,6 +61,7 @@ DEFINE_int32(
|
|||
"Logging");
|
||||
|
||||
namespace dp = disruptorplus;
|
||||
using namespace xe::literals;
|
||||
|
||||
namespace xe {
|
||||
|
||||
|
@ -74,7 +77,7 @@ struct LogLine {
|
|||
char prefix_char;
|
||||
};
|
||||
|
||||
thread_local char thread_log_buffer_[64 * 1024];
|
||||
thread_local char thread_log_buffer_[64_KiB];
|
||||
|
||||
FileLogSink::~FileLogSink() {
|
||||
if (file_) {
|
||||
|
@ -234,7 +237,7 @@ class Logger {
|
|||
}
|
||||
|
||||
private:
|
||||
static const size_t kBufferSize = 8 * 1024 * 1024;
|
||||
static const size_t kBufferSize = 8_MiB;
|
||||
uint8_t buffer_[kBufferSize];
|
||||
|
||||
static const size_t kBlockSize = 256;
|
||||
|
@ -498,7 +501,13 @@ void FatalError(const std::string_view str) {
|
|||
}
|
||||
|
||||
ShutdownLogging();
|
||||
std::exit(1);
|
||||
|
||||
#if XE_PLATFORM_ANDROID
|
||||
// Throw an error that can be reported to the developers via the store.
|
||||
std::abort();
|
||||
#else
|
||||
std::exit(EXIT_FAILURE);
|
||||
#endif // XE_PLATFORM_ANDROID
|
||||
}
|
||||
|
||||
} // namespace xe
|
||||
|
|
|
@ -9,11 +9,15 @@
|
|||
|
||||
#include "xenia/base/main_android.h"
|
||||
|
||||
#include <android/log.h>
|
||||
#include <pthread.h>
|
||||
#include <cstddef>
|
||||
#include <cstdlib>
|
||||
|
||||
#include "xenia/base/assert.h"
|
||||
#include "xenia/base/logging.h"
|
||||
#include "xenia/base/memory.h"
|
||||
#include "xenia/base/system.h"
|
||||
#include "xenia/base/threading.h"
|
||||
|
||||
namespace xe {
|
||||
|
@ -22,7 +26,25 @@ static size_t android_initializations_ = 0;
|
|||
|
||||
static int32_t android_api_level_ = __ANDROID_API__;
|
||||
|
||||
void InitializeAndroidAppFromMainThread(int32_t api_level) {
|
||||
static JNIEnv* android_main_thread_jni_env_ = nullptr;
|
||||
static JavaVM* android_java_vm_ = nullptr;
|
||||
static pthread_key_t android_thread_jni_env_key_;
|
||||
static jobject android_application_context_ = nullptr;
|
||||
|
||||
static void AndroidThreadJNIEnvDestructor(void* jni_env_pointer) {
|
||||
// The JNIEnv pointer for the main thread is taken externally, the lifetime of
|
||||
// the attachment is not managed by the key.
|
||||
JNIEnv* jni_env = static_cast<JNIEnv*>(jni_env_pointer);
|
||||
if (jni_env && jni_env != android_main_thread_jni_env_) {
|
||||
android_java_vm_->DetachCurrentThread();
|
||||
}
|
||||
// Multiple iterations of destructor invocations can be done - clear.
|
||||
pthread_setspecific(android_thread_jni_env_key_, nullptr);
|
||||
}
|
||||
|
||||
void InitializeAndroidAppFromMainThread(int32_t api_level,
|
||||
JNIEnv* main_thread_jni_env,
|
||||
jobject application_context) {
|
||||
if (android_initializations_++) {
|
||||
// Already initialized for another component in the process.
|
||||
return;
|
||||
|
@ -32,6 +54,45 @@ void InitializeAndroidAppFromMainThread(int32_t api_level) {
|
|||
// subsystem initialization itself.
|
||||
android_api_level_ = api_level;
|
||||
|
||||
android_main_thread_jni_env_ = main_thread_jni_env;
|
||||
if (main_thread_jni_env) {
|
||||
// In a Java VM, not just in a process that runs an executable - set up
|
||||
// the attachment of threads to the Java VM.
|
||||
if (main_thread_jni_env->GetJavaVM(&android_java_vm_) < 0) {
|
||||
// Logging has not been initialized yet.
|
||||
__android_log_write(
|
||||
ANDROID_LOG_ERROR, "InitializeAndroidAppFromMainThread",
|
||||
"Failed to get the Java VM from the JNI environment of the main "
|
||||
"thread");
|
||||
std::abort();
|
||||
}
|
||||
if (pthread_key_create(&android_thread_jni_env_key_,
|
||||
AndroidThreadJNIEnvDestructor)) {
|
||||
__android_log_write(
|
||||
ANDROID_LOG_ERROR, "InitializeAndroidAppFromMainThread",
|
||||
"Failed to create the thread-specific JNI environment key");
|
||||
std::abort();
|
||||
}
|
||||
if (pthread_setspecific(android_thread_jni_env_key_, main_thread_jni_env)) {
|
||||
__android_log_write(
|
||||
ANDROID_LOG_ERROR, "InitializeAndroidAppFromMainThread",
|
||||
"Failed to set the thread-specific JNI environment pointer for the "
|
||||
"main thread");
|
||||
std::abort();
|
||||
}
|
||||
if (application_context) {
|
||||
android_application_context_ =
|
||||
main_thread_jni_env->NewGlobalRef(application_context);
|
||||
if (!android_application_context_) {
|
||||
__android_log_write(
|
||||
ANDROID_LOG_ERROR, "InitializeAndroidAppFromMainThread",
|
||||
"Failed to create a global reference to the application context "
|
||||
"object");
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Logging uses threading.
|
||||
xe::threading::AndroidInitialize();
|
||||
|
||||
|
@ -40,6 +101,15 @@ void InitializeAndroidAppFromMainThread(int32_t api_level) {
|
|||
xe::InitializeLogging("xenia");
|
||||
|
||||
xe::memory::AndroidInitialize();
|
||||
|
||||
if (android_application_context_) {
|
||||
if (!xe::InitializeAndroidSystemForApplicationContext()) {
|
||||
__android_log_write(ANDROID_LOG_ERROR,
|
||||
"InitializeAndroidAppFromMainThread",
|
||||
"Failed to initialize system UI interaction");
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ShutdownAndroidAppFromMainThread() {
|
||||
|
@ -52,15 +122,36 @@ void ShutdownAndroidAppFromMainThread() {
|
|||
return;
|
||||
}
|
||||
|
||||
xe::ShutdownAndroidSystem();
|
||||
|
||||
xe::memory::AndroidShutdown();
|
||||
|
||||
xe::ShutdownLogging();
|
||||
|
||||
xe::threading::AndroidShutdown();
|
||||
|
||||
if (android_application_context_) {
|
||||
android_main_thread_jni_env_->DeleteGlobalRef(android_application_context_);
|
||||
android_application_context_ = nullptr;
|
||||
}
|
||||
if (android_java_vm_) {
|
||||
android_java_vm_ = nullptr;
|
||||
pthread_key_delete(android_thread_jni_env_key_);
|
||||
}
|
||||
android_main_thread_jni_env_ = nullptr;
|
||||
|
||||
android_api_level_ = __ANDROID_API__;
|
||||
}
|
||||
|
||||
int32_t GetAndroidApiLevel() { return android_api_level_; }
|
||||
|
||||
JNIEnv* GetAndroidThreadJniEnv() {
|
||||
if (!android_java_vm_) {
|
||||
return nullptr;
|
||||
}
|
||||
return static_cast<JNIEnv*>(pthread_getspecific(android_thread_jni_env_key_));
|
||||
}
|
||||
|
||||
jobject GetAndroidApplicationContext() { return android_application_context_; }
|
||||
|
||||
} // namespace xe
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2021 Ben Vanik. All rights reserved. *
|
||||
* Copyright 2022 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
@ -10,6 +10,7 @@
|
|||
#ifndef XENIA_BASE_MAIN_ANDROID_H_
|
||||
#define XENIA_BASE_MAIN_ANDROID_H_
|
||||
|
||||
#include <jni.h>
|
||||
#include <cstdint>
|
||||
|
||||
#include "xenia/base/platform.h"
|
||||
|
@ -27,14 +28,33 @@ namespace xe {
|
|||
// counting internally.
|
||||
//
|
||||
// In standalone console apps built with $(BUILD_EXECUTABLE), these functions
|
||||
// must be called in `main`.
|
||||
void InitializeAndroidAppFromMainThread(int32_t api_level);
|
||||
// must be called in `main`, with a null main thread JNI environment.
|
||||
void InitializeAndroidAppFromMainThread(int32_t api_level,
|
||||
JNIEnv* main_thread_jni_env,
|
||||
jobject application_context);
|
||||
void ShutdownAndroidAppFromMainThread();
|
||||
|
||||
// May be the minimum supported level if the initialization was done without a
|
||||
// configuration.
|
||||
int32_t GetAndroidApiLevel();
|
||||
|
||||
// Useful notes about JNI usage on Android within Xenia:
|
||||
// - All static libraries defining JNI native functions must be linked to shared
|
||||
// libraries via LOCAL_WHOLE_STATIC_LIBRARIES.
|
||||
// - If method or field IDs are cached, a global reference to the class needs to
|
||||
// be held - it prevents the class from being unloaded by the class loaders
|
||||
// (in a way that would make the IDs invalid when it's reloaded).
|
||||
// - GetStringUTFChars (UTF-8) returns null-terminated strings, GetStringChars
|
||||
// (UTF-16) does not.
|
||||
|
||||
// May return null if not in a Java VM process, or in case of a failure to
|
||||
// attach on a non-main thread.
|
||||
JNIEnv* GetAndroidThreadJniEnv();
|
||||
// Returns the global reference if in an application context, or null otherwise.
|
||||
// This is the application context, not the activity one, because multiple
|
||||
// activities may be running in one process.
|
||||
jobject GetAndroidApplicationContext();
|
||||
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_BASE_MAIN_ANDROID_H_
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2021 Ben Vanik. All rights reserved. *
|
||||
* Copyright 2022 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
@ -19,37 +19,64 @@
|
|||
// Autogenerated by `xb premake`.
|
||||
#include "build/version.h"
|
||||
|
||||
// For RequestHighPerformance.
|
||||
// For RequestWin32MMCSS.
|
||||
#include <dwmapi.h>
|
||||
// For RequestWin32HighResolutionTimer.
|
||||
#include <winternl.h>
|
||||
|
||||
DEFINE_bool(win32_high_freq, true,
|
||||
"Requests high performance from the NT kernel", "Kernel");
|
||||
DEFINE_bool(win32_high_resolution_timer, true,
|
||||
"Requests high-resolution timer from the NT kernel", "Win32");
|
||||
DEFINE_bool(
|
||||
win32_mmcss, true,
|
||||
"Opt in the Multimedia Class Scheduler Service (MMCSS) scheduling for "
|
||||
"prioritized access to CPU resources",
|
||||
"Win32");
|
||||
|
||||
namespace xe {
|
||||
|
||||
static void RequestHighPerformance() {
|
||||
#if XE_PLATFORM_WIN32
|
||||
NTSTATUS(*NtQueryTimerResolution)
|
||||
(OUT PULONG MinimumResolution, OUT PULONG MaximumResolution,
|
||||
OUT PULONG CurrentResolution);
|
||||
static void RequestWin32HighResolutionTimer() {
|
||||
HMODULE ntdll_module = GetModuleHandleW(L"ntdll.dll");
|
||||
if (!ntdll_module) {
|
||||
return;
|
||||
}
|
||||
|
||||
NTSTATUS(*NtSetTimerResolution)
|
||||
(IN ULONG DesiredResolution, IN BOOLEAN SetResolution,
|
||||
OUT PULONG CurrentResolution);
|
||||
|
||||
NtQueryTimerResolution = (decltype(NtQueryTimerResolution))GetProcAddress(
|
||||
GetModuleHandleW(L"ntdll.dll"), "NtQueryTimerResolution");
|
||||
NtSetTimerResolution = (decltype(NtSetTimerResolution))GetProcAddress(
|
||||
GetModuleHandleW(L"ntdll.dll"), "NtSetTimerResolution");
|
||||
if (!NtQueryTimerResolution || !NtSetTimerResolution) {
|
||||
// clang-format off
|
||||
NTSTATUS (NTAPI* nt_query_timer_resolution)(OUT PULONG MinimumResolution,
|
||||
OUT PULONG MaximumResolution,
|
||||
OUT PULONG CurrentResolution);
|
||||
NTSTATUS (NTAPI* nt_set_timer_resolution)(IN ULONG DesiredResolution,
|
||||
IN BOOLEAN SetResolution,
|
||||
OUT PULONG CurrentResolution);
|
||||
// clang-format on
|
||||
nt_query_timer_resolution =
|
||||
reinterpret_cast<decltype(nt_query_timer_resolution)>(
|
||||
GetProcAddress(ntdll_module, "NtQueryTimerResolution"));
|
||||
nt_set_timer_resolution = reinterpret_cast<decltype(nt_set_timer_resolution)>(
|
||||
GetProcAddress(ntdll_module, "NtSetTimerResolution"));
|
||||
if (!nt_query_timer_resolution || !nt_set_timer_resolution) {
|
||||
return;
|
||||
}
|
||||
|
||||
ULONG minimum_resolution, maximum_resolution, current_resolution;
|
||||
NtQueryTimerResolution(&minimum_resolution, &maximum_resolution,
|
||||
¤t_resolution);
|
||||
NtSetTimerResolution(maximum_resolution, TRUE, ¤t_resolution);
|
||||
#endif
|
||||
nt_query_timer_resolution(&minimum_resolution, &maximum_resolution,
|
||||
¤t_resolution);
|
||||
nt_set_timer_resolution(maximum_resolution, TRUE, ¤t_resolution);
|
||||
}
|
||||
|
||||
static void RequestWin32MMCSS() {
|
||||
HMODULE dwmapi_module = LoadLibraryW(L"dwmapi.dll");
|
||||
if (!dwmapi_module) {
|
||||
return;
|
||||
}
|
||||
// clang-format off
|
||||
HRESULT (STDAPICALLTYPE* dwm_enable_mmcss)(BOOL fEnableMMCSS);
|
||||
// clang-format on
|
||||
dwm_enable_mmcss = reinterpret_cast<decltype(dwm_enable_mmcss)>(
|
||||
GetProcAddress(dwmapi_module, "DwmEnableMMCSS"));
|
||||
if (dwm_enable_mmcss) {
|
||||
dwm_enable_mmcss(TRUE);
|
||||
}
|
||||
FreeLibrary(dwmapi_module);
|
||||
}
|
||||
|
||||
bool ParseWin32LaunchArguments(
|
||||
|
@ -103,9 +130,12 @@ int InitializeWin32App(const std::string_view app_name) {
|
|||
#endif
|
||||
XE_BUILD_BRANCH "@" XE_BUILD_COMMIT_SHORT " on " XE_BUILD_DATE);
|
||||
|
||||
// Request high performance timing.
|
||||
if (cvars::win32_high_freq) {
|
||||
RequestHighPerformance();
|
||||
// Request high-performance timing and scheduling.
|
||||
if (cvars::win32_high_resolution_timer) {
|
||||
RequestWin32HighResolutionTimer();
|
||||
}
|
||||
if (cvars::win32_mmcss) {
|
||||
RequestWin32MMCSS();
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2014 Ben Vanik. All rights reserved. *
|
||||
* Copyright 2022 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
@ -11,6 +11,10 @@
|
|||
#include "xenia/base/cvar.h"
|
||||
#include "xenia/base/platform.h"
|
||||
|
||||
#if XE_ARCH_ARM64
|
||||
#include <arm_neon.h>
|
||||
#endif
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
DEFINE_bool(
|
||||
|
@ -184,8 +188,8 @@ void copy_and_swap_64_unaligned(void* dest_ptr, const void* src_ptr,
|
|||
|
||||
void copy_and_swap_16_in_32_aligned(void* dest_ptr, const void* src_ptr,
|
||||
size_t count) {
|
||||
auto dest = reinterpret_cast<uint64_t*>(dest_ptr);
|
||||
auto src = reinterpret_cast<const uint64_t*>(src_ptr);
|
||||
auto dest = reinterpret_cast<uint32_t*>(dest_ptr);
|
||||
auto src = reinterpret_cast<const uint32_t*>(src_ptr);
|
||||
size_t i;
|
||||
for (i = 0; i + 4 <= count; i += 4) {
|
||||
__m128i input = _mm_load_si128(reinterpret_cast<const __m128i*>(&src[i]));
|
||||
|
@ -201,8 +205,8 @@ void copy_and_swap_16_in_32_aligned(void* dest_ptr, const void* src_ptr,
|
|||
|
||||
void copy_and_swap_16_in_32_unaligned(void* dest_ptr, const void* src_ptr,
|
||||
size_t count) {
|
||||
auto dest = reinterpret_cast<uint64_t*>(dest_ptr);
|
||||
auto src = reinterpret_cast<const uint64_t*>(src_ptr);
|
||||
auto dest = reinterpret_cast<uint32_t*>(dest_ptr);
|
||||
auto src = reinterpret_cast<const uint32_t*>(src_ptr);
|
||||
size_t i;
|
||||
for (i = 0; i + 4 <= count; i += 4) {
|
||||
__m128i input = _mm_loadu_si128(reinterpret_cast<const __m128i*>(&src[i]));
|
||||
|
@ -215,7 +219,133 @@ void copy_and_swap_16_in_32_unaligned(void* dest_ptr, const void* src_ptr,
|
|||
dest[i] = (src[i] >> 16) | (src[i] << 16);
|
||||
}
|
||||
}
|
||||
|
||||
#elif XE_ARCH_ARM64
|
||||
|
||||
// Although NEON offers vector rev instructions (like vrev32q_u8), they are
|
||||
// slower in benchmarks. Also, using uint8x16xN_t wasn't any faster in the
|
||||
// benchmarks, hence we use just use one SIMD register to minimize residual
|
||||
// processing.
|
||||
|
||||
void copy_and_swap_16_aligned(void* dst_ptr, const void* src_ptr,
|
||||
size_t count) {
|
||||
copy_and_swap_16_unaligned(dst_ptr, src_ptr, count);
|
||||
}
|
||||
|
||||
void copy_and_swap_16_unaligned(void* dst_ptr, const void* src_ptr,
|
||||
size_t count) {
|
||||
auto dst = reinterpret_cast<uint8_t*>(dst_ptr);
|
||||
auto src = reinterpret_cast<const uint8_t*>(src_ptr);
|
||||
|
||||
const uint8x16_t tbl_idx =
|
||||
vcombine_u8(vcreate_u8(UINT64_C(0x0607040502030001)),
|
||||
vcreate_u8(UINT64_C(0x0E0F0C0D0A0B0809)));
|
||||
|
||||
while (count >= 8) {
|
||||
uint8x16_t data = vld1q_u8(src);
|
||||
data = vqtbl1q_u8(data, tbl_idx);
|
||||
vst1q_u8(dst, data);
|
||||
|
||||
count -= 8;
|
||||
// These pointer increments will be combined with the load/stores (ldr/str)
|
||||
// into single instructions (at least by clang)
|
||||
dst += 16;
|
||||
src += 16;
|
||||
}
|
||||
|
||||
while (count > 0) {
|
||||
store_and_swap<uint16_t>(dst, load<uint16_t>(src));
|
||||
|
||||
count--;
|
||||
dst += 2;
|
||||
src += 2;
|
||||
}
|
||||
}
|
||||
|
||||
void copy_and_swap_32_aligned(void* dst, const void* src, size_t count) {
|
||||
copy_and_swap_32_unaligned(dst, src, count);
|
||||
}
|
||||
|
||||
void copy_and_swap_32_unaligned(void* dst_ptr, const void* src_ptr,
|
||||
size_t count) {
|
||||
auto dst = reinterpret_cast<uint8_t*>(dst_ptr);
|
||||
auto src = reinterpret_cast<const uint8_t*>(src_ptr);
|
||||
|
||||
const uint8x16_t tbl_idx =
|
||||
vcombine_u8(vcreate_u8(UINT64_C(0x405060700010203)),
|
||||
vcreate_u8(UINT64_C(0x0C0D0E0F08090A0B)));
|
||||
|
||||
while (count >= 4) {
|
||||
uint8x16_t data = vld1q_u8(src);
|
||||
data = vqtbl1q_u8(data, tbl_idx);
|
||||
vst1q_u8(dst, data);
|
||||
|
||||
count -= 4;
|
||||
dst += 16;
|
||||
src += 16;
|
||||
}
|
||||
|
||||
while (count > 0) {
|
||||
store_and_swap<uint32_t>(dst, load<uint32_t>(src));
|
||||
|
||||
count--;
|
||||
dst += 4;
|
||||
src += 4;
|
||||
}
|
||||
}
|
||||
|
||||
void copy_and_swap_64_aligned(void* dst, const void* src, size_t count) {
|
||||
copy_and_swap_64_unaligned(dst, src, count);
|
||||
}
|
||||
|
||||
void copy_and_swap_64_unaligned(void* dst_ptr, const void* src_ptr,
|
||||
size_t count) {
|
||||
auto dst = reinterpret_cast<uint8_t*>(dst_ptr);
|
||||
auto src = reinterpret_cast<const uint8_t*>(src_ptr);
|
||||
|
||||
const uint8x16_t tbl_idx =
|
||||
vcombine_u8(vcreate_u8(UINT64_C(0x0001020304050607)),
|
||||
vcreate_u8(UINT64_C(0x08090A0B0C0D0E0F)));
|
||||
|
||||
while (count >= 2) {
|
||||
uint8x16_t data = vld1q_u8(src);
|
||||
data = vqtbl1q_u8(data, tbl_idx);
|
||||
vst1q_u8(dst, data);
|
||||
|
||||
count -= 2;
|
||||
dst += 16;
|
||||
src += 16;
|
||||
}
|
||||
|
||||
while (count > 0) {
|
||||
store_and_swap<uint64_t>(dst, load<uint64_t>(src));
|
||||
|
||||
count--;
|
||||
dst += 8;
|
||||
src += 8;
|
||||
}
|
||||
}
|
||||
|
||||
void copy_and_swap_16_in_32_aligned(void* dst, const void* src, size_t count) {
|
||||
return copy_and_swap_16_in_32_unaligned(dst, src, count);
|
||||
}
|
||||
|
||||
void copy_and_swap_16_in_32_unaligned(void* dst_ptr, const void* src_ptr,
|
||||
size_t count) {
|
||||
auto dst = reinterpret_cast<uint16_t*>(dst_ptr);
|
||||
auto src = reinterpret_cast<const uint16_t*>(src_ptr);
|
||||
while (count > 0) {
|
||||
uint16_t word0 = *src++;
|
||||
uint16_t word1 = *src++;
|
||||
*dst++ = word1;
|
||||
*dst++ = word0;
|
||||
|
||||
count--;
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
// Generic routines.
|
||||
void copy_and_swap_16_aligned(void* dest, const void* src, size_t count) {
|
||||
return copy_and_swap_16_unaligned(dest, src, count);
|
||||
|
@ -260,14 +390,20 @@ void copy_and_swap_16_in_32_aligned(void* dest, const void* src, size_t count) {
|
|||
return copy_and_swap_16_in_32_unaligned(dest, src, count);
|
||||
}
|
||||
|
||||
void copy_and_swap_16_in_32_unaligned(void* dest_ptr, const void* src_ptr,
|
||||
void copy_and_swap_16_in_32_unaligned(void* dst_ptr, const void* src_ptr,
|
||||
size_t count) {
|
||||
auto dest = reinterpret_cast<uint64_t*>(dest_ptr);
|
||||
auto src = reinterpret_cast<const uint64_t*>(src_ptr);
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
dest[i] = (src[i] >> 16) | (src[i] << 16);
|
||||
auto dst = reinterpret_cast<uint16_t*>(dst_ptr);
|
||||
auto src = reinterpret_cast<const uint16_t*>(src_ptr);
|
||||
while (count > 0) {
|
||||
uint16_t word0 = *src++;
|
||||
uint16_t word1 = *src++;
|
||||
*dst++ = word1;
|
||||
*dst++ = word0;
|
||||
|
||||
count--;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace xe
|
||||
|
|
|
@ -2,17 +2,20 @@
|
|||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2014 Ben Vanik. All rights reserved. *
|
||||
* Copyright 2022 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
|
||||
// NOTE: this must be included before microprofile as macro expansion needs
|
||||
// XELOGI.
|
||||
#include "xenia/base/logging.h"
|
||||
|
||||
#include "third_party/fmt/include/fmt/printf.h"
|
||||
|
||||
// NOTE: microprofile must be setup first, before profiling.h is included.
|
||||
#define MICROPROFILE_ENABLED 1
|
||||
#define MICROPROFILEUI_ENABLED 1
|
||||
|
@ -21,7 +24,11 @@
|
|||
#define MICROPROFILE_PER_THREAD_BUFFER_SIZE (1024 * 1024 * 10)
|
||||
#define MICROPROFILE_USE_THREAD_NAME_CALLBACK 1
|
||||
#define MICROPROFILE_WEBSERVER_MAXFRAMES 3
|
||||
#define MICROPROFILE_PRINTF XELOGI
|
||||
#define MICROPROFILE_PRINTF(...) \
|
||||
do { \
|
||||
auto xenia_profiler_formatted = fmt::sprintf(__VA_ARGS__); \
|
||||
XELOGI("{}", xenia_profiler_formatted); \
|
||||
} while (false);
|
||||
#define MICROPROFILE_WEBSERVER 0
|
||||
#define MICROPROFILE_DEBUG 0
|
||||
#define MICROPROFILE_MAX_THREADS 128
|
||||
|
@ -30,6 +37,7 @@
|
|||
#include "xenia/base/assert.h"
|
||||
#include "xenia/base/cvar.h"
|
||||
#include "xenia/base/profiling.h"
|
||||
#include "xenia/ui/ui_event.h"
|
||||
#include "xenia/ui/virtual_key.h"
|
||||
#include "xenia/ui/window.h"
|
||||
|
||||
|
@ -38,21 +46,27 @@
|
|||
#endif // XE_OPTION_PROFILING
|
||||
|
||||
#if XE_OPTION_PROFILING_UI
|
||||
#undef DrawText
|
||||
#include "xenia/ui/microprofile_drawer.h"
|
||||
#endif // XE_OPTION_PROFILING_UI
|
||||
|
||||
DEFINE_bool(profiler_dpi_scaling, false,
|
||||
"Apply window DPI scaling to the profiler.", "UI");
|
||||
DEFINE_bool(show_profiler, false, "Show profiling UI by default.", "UI");
|
||||
|
||||
namespace xe {
|
||||
|
||||
#if XE_OPTION_PROFILING_UI
|
||||
ui::Window* Profiler::window_ = nullptr;
|
||||
std::unique_ptr<ui::MicroprofileDrawer> Profiler::drawer_ = nullptr;
|
||||
#endif // XE_OPTION_PROFILING_UI
|
||||
|
||||
#if XE_OPTION_PROFILING
|
||||
|
||||
Profiler::ProfilerWindowInputListener Profiler::input_listener_;
|
||||
size_t Profiler::z_order_ = 0;
|
||||
ui::Window* Profiler::window_ = nullptr;
|
||||
#if XE_OPTION_PROFILING_UI
|
||||
Profiler::ProfilerUIDrawer Profiler::ui_drawer_;
|
||||
ui::Presenter* Profiler::presenter_ = nullptr;
|
||||
std::unique_ptr<ui::MicroprofileDrawer> Profiler::drawer_;
|
||||
bool Profiler::dpi_scaling_ = false;
|
||||
#endif // XE_OPTION_PROFILING_UI
|
||||
|
||||
bool Profiler::is_enabled() { return true; }
|
||||
|
||||
bool Profiler::is_visible() { return is_enabled() && MicroProfileIsDrawing(); }
|
||||
|
@ -73,6 +87,7 @@ void Profiler::Initialize() {
|
|||
g_MicroProfile.nActiveBars |= 0x1 | 0x2;
|
||||
|
||||
#if XE_OPTION_PROFILING_UI
|
||||
dpi_scaling_ = cvars::profiler_dpi_scaling;
|
||||
MicroProfileInitUI();
|
||||
g_MicroProfileUI.bShowSpikes = true;
|
||||
g_MicroProfileUI.nOpacityBackground = 0x40u << 24;
|
||||
|
@ -96,7 +111,7 @@ void Profiler::Dump() {
|
|||
}
|
||||
|
||||
void Profiler::Shutdown() {
|
||||
drawer_.reset();
|
||||
SetUserIO(0, nullptr, nullptr, nullptr);
|
||||
window_ = nullptr;
|
||||
MicroProfileShutdown();
|
||||
}
|
||||
|
@ -113,144 +128,208 @@ void Profiler::ThreadEnter(const char* name) {
|
|||
|
||||
void Profiler::ThreadExit() { MicroProfileOnThreadExit(); }
|
||||
|
||||
bool Profiler::OnKeyDown(ui::VirtualKey virtual_key) {
|
||||
void Profiler::ProfilerWindowInputListener::OnKeyDown(ui::KeyEvent& e) {
|
||||
// https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx
|
||||
switch (virtual_key) {
|
||||
bool handled = true;
|
||||
switch (e.virtual_key()) {
|
||||
case ui::VirtualKey::kOem3: // `
|
||||
MicroProfileTogglePause();
|
||||
return true;
|
||||
break;
|
||||
#if XE_OPTION_PROFILING_UI
|
||||
case ui::VirtualKey::kTab:
|
||||
MicroProfileToggleDisplayMode();
|
||||
return true;
|
||||
ToggleDisplay();
|
||||
break;
|
||||
case ui::VirtualKey::k1:
|
||||
MicroProfileModKey(1);
|
||||
return true;
|
||||
break;
|
||||
#endif // XE_OPTION_PROFILING_UI
|
||||
default:
|
||||
handled = false;
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
if (handled) {
|
||||
e.set_handled(true);
|
||||
}
|
||||
PostInputEvent();
|
||||
}
|
||||
|
||||
bool Profiler::OnKeyUp(ui::VirtualKey virtual_key) {
|
||||
switch (virtual_key) {
|
||||
void Profiler::ProfilerWindowInputListener::OnKeyUp(ui::KeyEvent& e) {
|
||||
bool handled = true;
|
||||
switch (e.virtual_key()) {
|
||||
#if XE_OPTION_PROFILING_UI
|
||||
case ui::VirtualKey::k1:
|
||||
MicroProfileModKey(0);
|
||||
return true;
|
||||
break;
|
||||
#endif // XE_OPTION_PROFILING_UI
|
||||
default:
|
||||
handled = false;
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
if (handled) {
|
||||
e.set_handled(true);
|
||||
}
|
||||
PostInputEvent();
|
||||
}
|
||||
|
||||
#if XE_OPTION_PROFILING_UI
|
||||
|
||||
void Profiler::OnMouseDown(bool left_button, bool right_button) {
|
||||
MicroProfileMouseButton(left_button, right_button);
|
||||
void Profiler::ProfilerWindowInputListener::OnMouseDown(ui::MouseEvent& e) {
|
||||
Profiler::SetMousePosition(e.x(), e.y(), 0);
|
||||
MicroProfileMouseButton(e.button() == ui::MouseEvent::Button::kLeft,
|
||||
e.button() == ui::MouseEvent::Button::kRight);
|
||||
e.set_handled(true);
|
||||
PostInputEvent();
|
||||
}
|
||||
|
||||
void Profiler::OnMouseUp() { MicroProfileMouseButton(0, 0); }
|
||||
|
||||
void Profiler::OnMouseMove(int x, int y) { MicroProfileMousePosition(x, y, 0); }
|
||||
|
||||
void Profiler::OnMouseWheel(int x, int y, int dy) {
|
||||
MicroProfileMousePosition(x, y, dy);
|
||||
void Profiler::ProfilerWindowInputListener::OnMouseUp(ui::MouseEvent& e) {
|
||||
Profiler::SetMousePosition(e.x(), e.y(), 0);
|
||||
MicroProfileMouseButton(0, 0);
|
||||
e.set_handled(true);
|
||||
PostInputEvent();
|
||||
}
|
||||
|
||||
void Profiler::ToggleDisplay() { MicroProfileToggleDisplayMode(); }
|
||||
void Profiler::ProfilerWindowInputListener::OnMouseMove(ui::MouseEvent& e) {
|
||||
Profiler::SetMousePosition(e.x(), e.y(), 0);
|
||||
e.set_handled(true);
|
||||
PostInputEvent();
|
||||
}
|
||||
|
||||
void Profiler::ProfilerWindowInputListener::OnMouseWheel(ui::MouseEvent& e) {
|
||||
Profiler::SetMousePosition(e.x(), e.y(), e.scroll_y());
|
||||
e.set_handled(true);
|
||||
PostInputEvent();
|
||||
}
|
||||
|
||||
void Profiler::TogglePause() { MicroProfileTogglePause(); }
|
||||
|
||||
#else
|
||||
|
||||
void Profiler::OnMouseDown(bool left_button, bool right_button) {}
|
||||
|
||||
void Profiler::OnMouseUp() {}
|
||||
|
||||
void Profiler::OnMouseMove(int x, int y) {}
|
||||
|
||||
void Profiler::OnMouseWheel(int x, int y, int dy) {}
|
||||
|
||||
void Profiler::ToggleDisplay() {}
|
||||
|
||||
void Profiler::TogglePause() {}
|
||||
|
||||
#endif // XE_OPTION_PROFILING_UI
|
||||
|
||||
void Profiler::set_window(ui::Window* window) {
|
||||
assert_null(window_);
|
||||
void Profiler::ToggleDisplay() {
|
||||
bool was_visible = is_visible();
|
||||
MicroProfileToggleDisplayMode();
|
||||
if (is_visible() != was_visible) {
|
||||
if (window_) {
|
||||
if (was_visible) {
|
||||
window_->RemoveInputListener(&input_listener_);
|
||||
} else {
|
||||
window_->AddInputListener(&input_listener_, z_order_);
|
||||
}
|
||||
}
|
||||
#if XE_OPTION_PROFILING_UI
|
||||
if (presenter_) {
|
||||
if (was_visible) {
|
||||
presenter_->RemoveUIDrawerFromUIThread(&ui_drawer_);
|
||||
} else {
|
||||
presenter_->AddUIDrawerFromUIThread(&ui_drawer_, z_order_);
|
||||
}
|
||||
}
|
||||
#endif // XE_OPTION_PROFILING_UI
|
||||
}
|
||||
}
|
||||
|
||||
void Profiler::SetUserIO(size_t z_order, ui::Window* window,
|
||||
ui::Presenter* presenter,
|
||||
ui::ImmediateDrawer* immediate_drawer) {
|
||||
#if XE_OPTION_PROFILING_UI
|
||||
if (presenter_ && is_visible()) {
|
||||
presenter_->RemoveUIDrawerFromUIThread(&ui_drawer_);
|
||||
}
|
||||
drawer_.reset();
|
||||
presenter_ = nullptr;
|
||||
#endif // XE_OPTION_PROFILING_UI
|
||||
|
||||
if (window_) {
|
||||
if (is_visible()) {
|
||||
window_->RemoveInputListener(&input_listener_);
|
||||
}
|
||||
window_ = nullptr;
|
||||
}
|
||||
|
||||
if (!window) {
|
||||
return;
|
||||
}
|
||||
|
||||
z_order_ = z_order;
|
||||
window_ = window;
|
||||
drawer_ = std::make_unique<ui::MicroprofileDrawer>(window);
|
||||
|
||||
window_->on_painted.AddListener([](ui::UIEvent* e) { Profiler::Present(); });
|
||||
#if XE_OPTION_PROFILING_UI
|
||||
if (presenter && immediate_drawer) {
|
||||
presenter_ = presenter;
|
||||
drawer_ = std::make_unique<ui::MicroprofileDrawer>(immediate_drawer);
|
||||
}
|
||||
#endif // XE_OPTION_PROFILING_UI
|
||||
|
||||
// Pass through mouse events.
|
||||
window_->on_mouse_down.AddListener([](ui::MouseEvent* e) {
|
||||
if (Profiler::is_visible()) {
|
||||
Profiler::OnMouseDown(e->button() == ui::MouseEvent::Button::kLeft,
|
||||
e->button() == ui::MouseEvent::Button::kRight);
|
||||
e->set_handled(true);
|
||||
window_->Invalidate();
|
||||
if (is_visible()) {
|
||||
window_->AddInputListener(&input_listener_, z_order_);
|
||||
#if XE_OPTION_PROFILING_UI
|
||||
if (presenter_) {
|
||||
presenter_->AddUIDrawerFromUIThread(&ui_drawer_, z_order_);
|
||||
}
|
||||
});
|
||||
window_->on_mouse_up.AddListener([](ui::MouseEvent* e) {
|
||||
if (Profiler::is_visible()) {
|
||||
Profiler::OnMouseUp();
|
||||
e->set_handled(true);
|
||||
window_->Invalidate();
|
||||
}
|
||||
});
|
||||
window_->on_mouse_move.AddListener([](ui::MouseEvent* e) {
|
||||
if (Profiler::is_visible()) {
|
||||
Profiler::OnMouseMove(e->x(), e->y());
|
||||
e->set_handled(true);
|
||||
window_->Invalidate();
|
||||
}
|
||||
});
|
||||
window_->on_mouse_wheel.AddListener([](ui::MouseEvent* e) {
|
||||
if (Profiler::is_visible()) {
|
||||
Profiler::OnMouseWheel(e->x(), e->y(), -e->dy());
|
||||
e->set_handled(true);
|
||||
window_->Invalidate();
|
||||
}
|
||||
});
|
||||
|
||||
// Watch for toggle/mode keys and such.
|
||||
window_->on_key_down.AddListener([](ui::KeyEvent* e) {
|
||||
if (Profiler::is_visible()) {
|
||||
Profiler::OnKeyDown(e->virtual_key());
|
||||
e->set_handled(true);
|
||||
window_->Invalidate();
|
||||
}
|
||||
});
|
||||
window_->on_key_up.AddListener([](ui::KeyEvent* e) {
|
||||
if (Profiler::is_visible()) {
|
||||
Profiler::OnKeyUp(e->virtual_key());
|
||||
e->set_handled(true);
|
||||
window_->Invalidate();
|
||||
}
|
||||
});
|
||||
#endif // XE_OPTION_PROFILING_UI
|
||||
}
|
||||
}
|
||||
|
||||
void Profiler::Flip() {
|
||||
MicroProfileFlip();
|
||||
// This can be called from non-UI threads, so not trying to access the drawer
|
||||
// to trigger redraw here as it's owned and managed exclusively by the UI
|
||||
// thread. Relying on continuous painting currently.
|
||||
}
|
||||
|
||||
void Profiler::Present() {
|
||||
SCOPE_profile_cpu_f("internal");
|
||||
#if XE_OPTION_PROFILING_UI
|
||||
if (!window_ || !drawer_) {
|
||||
void Profiler::ProfilerUIDrawer::Draw(ui::UIDrawContext& ui_draw_context) {
|
||||
if (!window_ || !presenter_ || !drawer_) {
|
||||
return;
|
||||
}
|
||||
drawer_->Begin();
|
||||
MicroProfileDraw(window_->scaled_width(), window_->scaled_height());
|
||||
SCOPE_profile_cpu_f("internal");
|
||||
uint32_t coordinate_space_width = dpi_scaling_
|
||||
? window_->GetActualLogicalWidth()
|
||||
: window_->GetActualPhysicalWidth();
|
||||
uint32_t coordinate_space_height = dpi_scaling_
|
||||
? window_->GetActualLogicalHeight()
|
||||
: window_->GetActualPhysicalHeight();
|
||||
drawer_->Begin(ui_draw_context, coordinate_space_width,
|
||||
coordinate_space_height);
|
||||
MicroProfileDraw(coordinate_space_width, coordinate_space_height);
|
||||
drawer_->End();
|
||||
#endif // XE_OPTION_PROFILING_UI
|
||||
// Continuous repaint.
|
||||
if (is_visible()) {
|
||||
presenter_->RequestUIPaintFromUIThread();
|
||||
}
|
||||
}
|
||||
#endif // XE_OPTION_PROFILING_UI
|
||||
|
||||
void Profiler::Flip() { MicroProfileFlip(); }
|
||||
#if XE_OPTION_PROFILING_UI
|
||||
void Profiler::SetMousePosition(int32_t x, int32_t y, int32_t wheel_delta) {
|
||||
if (!window_) {
|
||||
return;
|
||||
}
|
||||
if (dpi_scaling_) {
|
||||
x = window_->PositionToLogical(x);
|
||||
y = window_->PositionToLogical(y);
|
||||
}
|
||||
MicroProfileMousePosition(uint32_t(std::max(int32_t(0), x)),
|
||||
uint32_t(std::max(int32_t(0), y)), wheel_delta);
|
||||
}
|
||||
#endif // XE_OPTION_PROFILING_UI
|
||||
|
||||
void Profiler::PostInputEvent() {
|
||||
// The profiler can be hidden from within the profiler (Mode > Off).
|
||||
if (!is_visible()) {
|
||||
window_->RemoveInputListener(&input_listener_);
|
||||
#if XE_OPTION_PROFILING_UI
|
||||
if (presenter_) {
|
||||
presenter_->RemoveUIDrawerFromUIThread(&ui_drawer_);
|
||||
}
|
||||
#endif // XE_OPTION_PROFILING_UI
|
||||
return;
|
||||
}
|
||||
// Relying on continuous painting currently, no need to request drawing.
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
|
@ -262,16 +341,11 @@ void Profiler::Shutdown() {}
|
|||
uint32_t Profiler::GetColor(const char* str) { return 0; }
|
||||
void Profiler::ThreadEnter(const char* name) {}
|
||||
void Profiler::ThreadExit() {}
|
||||
bool Profiler::OnKeyDown(ui::VirtualKey virtual_key) { return false; }
|
||||
bool Profiler::OnKeyUp(ui::VirtualKey virtual_key) { return false; }
|
||||
void Profiler::OnMouseDown(bool left_button, bool right_button) {}
|
||||
void Profiler::OnMouseUp() {}
|
||||
void Profiler::OnMouseMove(int x, int y) {}
|
||||
void Profiler::OnMouseWheel(int x, int y, int dy) {}
|
||||
void Profiler::ToggleDisplay() {}
|
||||
void Profiler::TogglePause() {}
|
||||
void Profiler::set_window(ui::Window* window) {}
|
||||
void Profiler::Present() {}
|
||||
void Profiler::SetUserIO(size_t z_order, ui::Window* window,
|
||||
ui::Presenter* presenter,
|
||||
ui::ImmediateDrawer* immediate_drawer) {}
|
||||
void Profiler::Flip() {}
|
||||
|
||||
#endif // XE_OPTION_PROFILING
|
||||
|
@ -310,7 +384,7 @@ void MicroProfileDrawText(int nX, int nY, uint32_t nColor, const char* pText,
|
|||
if (!drawer) {
|
||||
return;
|
||||
}
|
||||
drawer->DrawText(nX, nY, nColor, pText, nLen);
|
||||
drawer->DrawTextString(nX, nY, nColor, pText, nLen);
|
||||
}
|
||||
|
||||
#endif // XE_OPTION_PROFILING_UI
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2014 Ben Vanik. All rights reserved. *
|
||||
* Copyright 2022 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
@ -10,10 +10,15 @@
|
|||
#ifndef XENIA_BASE_PROFILING_H_
|
||||
#define XENIA_BASE_PROFILING_H_
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
|
||||
#include "xenia/base/platform.h"
|
||||
#include "xenia/base/string.h"
|
||||
#include "xenia/ui/ui_drawer.h"
|
||||
#include "xenia/ui/virtual_key.h"
|
||||
#include "xenia/ui/window_listener.h"
|
||||
|
||||
#if XE_PLATFORM_WIN32
|
||||
#define XE_OPTION_PROFILING 1
|
||||
|
@ -30,7 +35,9 @@
|
|||
|
||||
namespace xe {
|
||||
namespace ui {
|
||||
class ImmediateDrawer;
|
||||
class MicroprofileDrawer;
|
||||
class Presenter;
|
||||
class Window;
|
||||
} // namespace ui
|
||||
} // namespace xe
|
||||
|
@ -172,27 +179,65 @@ class Profiler {
|
|||
// Deactivates the calling thread for profiling.
|
||||
static void ThreadExit();
|
||||
|
||||
static bool OnKeyDown(ui::VirtualKey virtual_key);
|
||||
static bool OnKeyUp(ui::VirtualKey virtual_key);
|
||||
static void OnMouseDown(bool left_button, bool right_button);
|
||||
static void OnMouseUp();
|
||||
static void OnMouseMove(int x, int y);
|
||||
static void OnMouseWheel(int x, int y, int dy);
|
||||
static void ToggleDisplay();
|
||||
static void TogglePause();
|
||||
|
||||
// Initializes input and drawing with the given display.
|
||||
static void set_window(ui::Window* window);
|
||||
// Gets the current display, if any.
|
||||
static ui::MicroprofileDrawer* drawer() { return drawer_.get(); }
|
||||
// Initializes input for the given window and drawing for the given presenter
|
||||
// and immediate drawer.
|
||||
static void SetUserIO(size_t z_order, ui::Window* window,
|
||||
ui::Presenter* presenter,
|
||||
ui::ImmediateDrawer* immediate_drawer);
|
||||
// Gets the current drawer, if any.
|
||||
static ui::MicroprofileDrawer* drawer() {
|
||||
#if XE_OPTION_PROFILING_UI
|
||||
return drawer_.get();
|
||||
#else
|
||||
return nullptr;
|
||||
#endif
|
||||
}
|
||||
// Presents the profiler to the bound display, if any.
|
||||
static void Present();
|
||||
static void Present(ui::UIDrawContext& ui_draw_context);
|
||||
// Starts a new frame on the profiler
|
||||
static void Flip();
|
||||
|
||||
private:
|
||||
#if XE_OPTION_PROFILING
|
||||
class ProfilerWindowInputListener final : public ui::WindowInputListener {
|
||||
public:
|
||||
void OnKeyDown(ui::KeyEvent& e) override;
|
||||
void OnKeyUp(ui::KeyEvent& e) override;
|
||||
#if XE_OPTION_PROFILING_UI
|
||||
void OnMouseDown(ui::MouseEvent& e) override;
|
||||
void OnMouseMove(ui::MouseEvent& e) override;
|
||||
void OnMouseUp(ui::MouseEvent& e) override;
|
||||
void OnMouseWheel(ui::MouseEvent& e) override;
|
||||
#endif // XE_OPTION_PROFILING_UI
|
||||
};
|
||||
// For now, no need for OnDpiChanged in a WindowListener because redrawing is
|
||||
// done continuously.
|
||||
|
||||
#if XE_OPTION_PROFILING_UI
|
||||
class ProfilerUIDrawer final : public ui::UIDrawer {
|
||||
public:
|
||||
void Draw(ui::UIDrawContext& context) override;
|
||||
};
|
||||
#endif // XE_OPTION_PROFILING_UI
|
||||
|
||||
#if XE_OPTION_PROFILING_UI
|
||||
static void SetMousePosition(int32_t x, int32_t y, int32_t wheel_delta);
|
||||
#endif // XE_OPTION_PROFILING_UI
|
||||
static void PostInputEvent();
|
||||
|
||||
static ProfilerWindowInputListener input_listener_;
|
||||
static size_t z_order_;
|
||||
static ui::Window* window_;
|
||||
#if XE_OPTION_PROFILING_UI
|
||||
static ProfilerUIDrawer ui_drawer_;
|
||||
static ui::Presenter* presenter_;
|
||||
static std::unique_ptr<ui::MicroprofileDrawer> drawer_;
|
||||
static bool dpi_scaling_;
|
||||
#endif // XE_OPTION_PROFILING_UI
|
||||
#endif // XE_OPTION_PROFILING
|
||||
};
|
||||
|
||||
} // namespace xe
|
||||
|
|
|
@ -13,12 +13,15 @@
|
|||
#include <cstdarg>
|
||||
|
||||
#include "xenia/base/assert.h"
|
||||
#include "xenia/base/literals.h"
|
||||
#include "xenia/base/math.h"
|
||||
|
||||
namespace xe {
|
||||
|
||||
using namespace xe::literals;
|
||||
|
||||
StringBuffer::StringBuffer(size_t initial_capacity) {
|
||||
buffer_capacity_ = std::max(initial_capacity, static_cast<size_t>(16 * 1024));
|
||||
buffer_capacity_ = std::max(initial_capacity, static_cast<size_t>(16_KiB));
|
||||
buffer_ = reinterpret_cast<char*>(std::malloc(buffer_capacity_));
|
||||
assert_not_null(buffer_);
|
||||
buffer_[0] = 0;
|
||||
|
@ -40,7 +43,7 @@ void StringBuffer::Grow(size_t additional_length) {
|
|||
}
|
||||
size_t old_capacity = buffer_capacity_;
|
||||
size_t new_capacity =
|
||||
std::max(xe::round_up(buffer_offset_ + additional_length, 16 * 1024),
|
||||
std::max(xe::round_up(buffer_offset_ + additional_length, 16_KiB),
|
||||
old_capacity * 2);
|
||||
auto new_buffer = std::realloc(buffer_, new_capacity);
|
||||
assert_not_null(new_buffer);
|
||||
|
|
|
@ -134,7 +134,7 @@ inline std::string to_hex_string(double value) {
|
|||
}
|
||||
|
||||
inline std::string to_hex_string(const vec128_t& value) {
|
||||
return fmt::format("[{:08X} {:08X} {:08X} {:08X} {:08X}]", value.u32[0],
|
||||
return fmt::format("[{:08X} {:08X} {:08X} {:08X}]", value.u32[0],
|
||||
value.u32[1], value.u32[2], value.u32[3]);
|
||||
}
|
||||
|
||||
|
|
|
@ -13,10 +13,17 @@
|
|||
#include <filesystem>
|
||||
#include <string_view>
|
||||
|
||||
#include "xenia/base/platform.h"
|
||||
#include "xenia/base/string.h"
|
||||
|
||||
namespace xe {
|
||||
|
||||
#if XE_PLATFORM_ANDROID
|
||||
bool InitializeAndroidSystemForApplicationContext();
|
||||
void ShutdownAndroidSystem();
|
||||
#endif
|
||||
|
||||
// The URL must include the protocol.
|
||||
void LaunchWebBrowser(const std::string_view url);
|
||||
void LaunchFileExplorer(const std::filesystem::path& path);
|
||||
|
||||
|
|
|
@ -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 *
|
||||
******************************************************************************
|
||||
* Copyright 2021 Ben Vanik. All rights reserved. *
|
||||
* Copyright 2022 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
@ -14,6 +14,8 @@
|
|||
|
||||
#include "xenia/base/clock.h"
|
||||
|
||||
#include <array>
|
||||
|
||||
namespace xe {
|
||||
namespace base {
|
||||
namespace test {
|
||||
|
@ -113,6 +115,24 @@ TEST_CASE("copy_and_swap_16_unaligned", "[copy_and_swap]") {
|
|||
REQUIRE(c[2] == 0xAB89);
|
||||
REQUIRE(c[3] == 0xEFCD);
|
||||
|
||||
{
|
||||
constexpr size_t count = 100;
|
||||
std::array<uint8_t, count * 2> src{};
|
||||
std::array<uint8_t, count * 2> dst{};
|
||||
for (size_t i = 0; i < src.size(); ++i) {
|
||||
src[i] = static_cast<uint8_t>(i) + 1; // no zero in array
|
||||
}
|
||||
copy_and_swap_16_unaligned(dst.data(), src.data(), count);
|
||||
for (size_t i = 0; i < src.size(); i += 2) {
|
||||
// Check src is untouched
|
||||
REQUIRE(static_cast<size_t>(src[i + 0]) == i + 1);
|
||||
REQUIRE(static_cast<size_t>(src[i + 1]) == i + 2);
|
||||
// Check swapped bytes
|
||||
REQUIRE(static_cast<size_t>(dst[i]) == static_cast<size_t>(src[i + 1]));
|
||||
REQUIRE(static_cast<size_t>(dst[i + 1]) == static_cast<size_t>(src[i]));
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t e;
|
||||
copy_and_swap_16_unaligned(&e, d, 4);
|
||||
REQUIRE(e == 0xEFCDAB8967452301);
|
||||
|
@ -221,6 +241,32 @@ TEST_CASE("copy_and_swap_32_unaligned", "[copy_and_swap]") {
|
|||
REQUIRE(c[2] == 0xEDEE87E8);
|
||||
REQUIRE(c[3] == 0x994151D8);
|
||||
|
||||
{
|
||||
constexpr size_t count = 17;
|
||||
std::array<uint8_t, count * 4> src{};
|
||||
std::array<uint8_t, count * 4> dst{};
|
||||
for (size_t i = 0; i < src.size(); ++i) {
|
||||
src[i] = static_cast<uint8_t>(i) + 1; // no zero in array
|
||||
}
|
||||
copy_and_swap_32_unaligned(dst.data(), src.data(), count);
|
||||
for (size_t i = 0; i < src.size(); i += 4) {
|
||||
// Check src is untouched
|
||||
REQUIRE(static_cast<size_t>(src[i + 0]) == i + 1);
|
||||
REQUIRE(static_cast<size_t>(src[i + 1]) == i + 2);
|
||||
REQUIRE(static_cast<size_t>(src[i + 2]) == i + 3);
|
||||
REQUIRE(static_cast<size_t>(src[i + 3]) == i + 4);
|
||||
// Check swapped bytes
|
||||
REQUIRE(static_cast<size_t>(dst[i + 0]) ==
|
||||
static_cast<size_t>(src[i + 3]));
|
||||
REQUIRE(static_cast<size_t>(dst[i + 1]) ==
|
||||
static_cast<size_t>(src[i + 2]));
|
||||
REQUIRE(static_cast<size_t>(dst[i + 2]) ==
|
||||
static_cast<size_t>(src[i + 1]));
|
||||
REQUIRE(static_cast<size_t>(dst[i + 3]) ==
|
||||
static_cast<size_t>(src[i + 0]));
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t e;
|
||||
copy_and_swap_32_unaligned(&e, d, 2);
|
||||
REQUIRE(e == 0xEFCDAB8967452301);
|
||||
|
@ -408,13 +454,56 @@ TEST_CASE("copy_and_swap_64_unaligned", "[copy_and_swap]") {
|
|||
}
|
||||
|
||||
TEST_CASE("copy_and_swap_16_in_32_aligned", "[copy_and_swap]") {
|
||||
// TODO(bwrsandman): test once properly understood.
|
||||
REQUIRE(true == true);
|
||||
constexpr size_t count = 17;
|
||||
alignas(16) std::array<uint8_t, count * 4> src{};
|
||||
alignas(16) std::array<uint8_t, count * 4> dst{};
|
||||
|
||||
// Check alignment (if this fails, adjust allocation)
|
||||
REQUIRE((reinterpret_cast<uintptr_t>(src.data()) & 0xF) == 0);
|
||||
REQUIRE((reinterpret_cast<uintptr_t>(dst.data()) & 0xF) == 0);
|
||||
|
||||
for (size_t i = 0; i < src.size(); ++i) {
|
||||
src[i] = static_cast<uint8_t>(i) + 1; // no zero in array
|
||||
}
|
||||
|
||||
copy_and_swap_16_in_32_aligned(dst.data(), src.data(), count);
|
||||
|
||||
for (size_t i = 0; i < src.size(); i += 4) {
|
||||
// Check src is untouched
|
||||
REQUIRE(static_cast<size_t>(src[i + 0]) == i + 1);
|
||||
REQUIRE(static_cast<size_t>(src[i + 1]) == i + 2);
|
||||
REQUIRE(static_cast<size_t>(src[i + 2]) == i + 3);
|
||||
REQUIRE(static_cast<size_t>(src[i + 3]) == i + 4);
|
||||
// Check swapped bytes
|
||||
REQUIRE(static_cast<size_t>(dst[i + 0]) == static_cast<size_t>(src[i + 2]));
|
||||
REQUIRE(static_cast<size_t>(dst[i + 1]) == static_cast<size_t>(src[i + 3]));
|
||||
REQUIRE(static_cast<size_t>(dst[i + 2]) == static_cast<size_t>(src[i + 0]));
|
||||
REQUIRE(static_cast<size_t>(dst[i + 3]) == static_cast<size_t>(src[i + 1]));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("copy_and_swap_16_in_32_unaligned", "[copy_and_swap]") {
|
||||
// TODO(bwrsandman): test once properly understood.
|
||||
REQUIRE(true == true);
|
||||
constexpr size_t count = 17;
|
||||
std::array<uint8_t, count * 4> src{};
|
||||
std::array<uint8_t, count * 4> dst{};
|
||||
for (size_t i = 0; i < src.size(); ++i) {
|
||||
src[i] = static_cast<uint8_t>(i) + 1; // no zero in array
|
||||
}
|
||||
|
||||
copy_and_swap_16_in_32_unaligned(dst.data(), src.data(), count);
|
||||
|
||||
for (size_t i = 0; i < src.size(); i += 4) {
|
||||
// Check src is untouched
|
||||
REQUIRE(static_cast<size_t>(src[i + 0]) == i + 1);
|
||||
REQUIRE(static_cast<size_t>(src[i + 1]) == i + 2);
|
||||
REQUIRE(static_cast<size_t>(src[i + 2]) == i + 3);
|
||||
REQUIRE(static_cast<size_t>(src[i + 3]) == i + 4);
|
||||
// Check swapped bytes
|
||||
REQUIRE(static_cast<size_t>(dst[i + 0]) == static_cast<size_t>(src[i + 2]));
|
||||
REQUIRE(static_cast<size_t>(dst[i + 1]) == static_cast<size_t>(src[i + 3]));
|
||||
REQUIRE(static_cast<size_t>(dst[i + 2]) == static_cast<size_t>(src[i + 0]));
|
||||
REQUIRE(static_cast<size_t>(dst[i + 3]) == static_cast<size_t>(src[i + 1]));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("create_and_close_file_mapping", "Virtual Memory Mapping") {
|
||||
|
|
|
@ -824,7 +824,7 @@ TEST_CASE("Create and Run Thread", "[thread]") {
|
|||
}
|
||||
|
||||
SECTION("16kb stack size") {
|
||||
params.stack_size = 16 * 1024 * 1024;
|
||||
params.stack_size = 16_MiB;
|
||||
thread = Thread::Create(params, [] {
|
||||
Thread::Exit(-1);
|
||||
FAIL("Function must not return");
|
||||
|
|
|
@ -25,11 +25,14 @@
|
|||
#include <vector>
|
||||
|
||||
#include "xenia/base/assert.h"
|
||||
#include "xenia/base/literals.h"
|
||||
#include "xenia/base/platform.h"
|
||||
|
||||
namespace xe {
|
||||
namespace threading {
|
||||
|
||||
using namespace xe::literals;
|
||||
|
||||
#if XE_PLATFORM_ANDROID
|
||||
void AndroidInitialize();
|
||||
void AndroidShutdown();
|
||||
|
@ -368,7 +371,7 @@ struct ThreadPriority {
|
|||
class Thread : public WaitHandle {
|
||||
public:
|
||||
struct CreationParameters {
|
||||
size_t stack_size = 4 * 1024 * 1024;
|
||||
size_t stack_size = 4_MiB;
|
||||
bool create_suspended = false;
|
||||
int32_t initial_priority = 0;
|
||||
};
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
#include "xenia/base/platform.h"
|
||||
|
||||
#include <pthread.h>
|
||||
#include <sched.h>
|
||||
#include <signal.h>
|
||||
#include <sys/eventfd.h>
|
||||
#include <sys/syscall.h>
|
||||
|
@ -28,7 +29,6 @@
|
|||
|
||||
#if XE_PLATFORM_ANDROID
|
||||
#include <dlfcn.h>
|
||||
#include <sched.h>
|
||||
|
||||
#include "xenia/base/main_android.h"
|
||||
#include "xenia/base/string_util.h"
|
||||
|
@ -128,11 +128,7 @@ uint32_t current_thread_system_id() {
|
|||
}
|
||||
|
||||
void MaybeYield() {
|
||||
#if XE_PLATFORM_ANDROID
|
||||
sched_yield();
|
||||
#else
|
||||
pthread_yield();
|
||||
#endif
|
||||
__sync_synchronize();
|
||||
}
|
||||
|
||||
|
|
|
@ -8,11 +8,13 @@
|
|||
*/
|
||||
|
||||
#include <cstddef>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
|
||||
#include "third_party/fmt/include/fmt/format.h"
|
||||
#include "xenia/base/math.h"
|
||||
#include "xenia/base/platform.h"
|
||||
#include "xenia/base/string_util.h"
|
||||
#include "xenia/base/vec128.h"
|
||||
|
||||
namespace xe {
|
||||
|
@ -21,4 +23,9 @@ std::string to_string(const vec128_t& value) {
|
|||
return fmt::format("({}, {}, {}, {})", value.x, value.y, value.z, value.w);
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const vec128_t& value) {
|
||||
os << string_util::to_hex_string(value);
|
||||
return os;
|
||||
}
|
||||
|
||||
} // namespace xe
|
||||
|
|
|
@ -257,6 +257,8 @@ static inline vec128_t vec128b(uint8_t x0, uint8_t x1, uint8_t x2, uint8_t x3,
|
|||
|
||||
std::string to_string(const vec128_t& value);
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const vec128_t& value);
|
||||
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_BASE_VEC128_H_
|
||||
|
|
|
@ -105,6 +105,10 @@ void ReadGameConfig(const std::filesystem::path& file_path) {
|
|||
}
|
||||
|
||||
void SaveConfig() {
|
||||
if (config_path.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// All cvar defaults have been updated on loading - store the current date.
|
||||
auto defaults_date_cvar =
|
||||
dynamic_cast<cvar::ConfigVar<uint32_t>*>(cv::cv_defaults_date);
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
namespace config {
|
||||
void SetupConfig(const std::filesystem::path& config_folder);
|
||||
void LoadGameConfig(const std::string_view title_id);
|
||||
void SaveConfig();
|
||||
} // namespace config
|
||||
|
||||
#endif // XENIA_CONFIG_H_
|
||||
|
|
|
@ -26,10 +26,24 @@
|
|||
#include "xenia/cpu/processor.h"
|
||||
#include "xenia/cpu/stack_walker.h"
|
||||
|
||||
DEFINE_bool(
|
||||
use_haswell_instructions, true,
|
||||
"Uses the AVX2/FMA/etc instructions on Haswell processors when available.",
|
||||
"CPU");
|
||||
DEFINE_int32(x64_extension_mask, -1,
|
||||
"Allow the detection and utilization of specific instruction set "
|
||||
"features.\n"
|
||||
" 0 = x86_64 + AVX1\n"
|
||||
" 1 = AVX2\n"
|
||||
" 2 = FMA\n"
|
||||
" 4 = LZCNT\n"
|
||||
" 8 = BMI1\n"
|
||||
" 16 = BMI2\n"
|
||||
" 32 = F16C\n"
|
||||
" 64 = Movbe\n"
|
||||
" 128 = GFNI\n"
|
||||
" 256 = AVX512F\n"
|
||||
" 512 = AVX512VL\n"
|
||||
" 1024 = AVX512BW\n"
|
||||
" 2048 = AVX512DQ\n"
|
||||
" -1 = Detect and utilize all possible processor features\n",
|
||||
"x64");
|
||||
|
||||
namespace xe {
|
||||
namespace cpu {
|
||||
|
@ -84,7 +98,7 @@ bool X64Backend::Initialize(Processor* processor) {
|
|||
}
|
||||
|
||||
// Need movbe to do advanced LOAD/STORE tricks.
|
||||
if (cvars::use_haswell_instructions) {
|
||||
if (cvars::x64_extension_mask & kX64EmitMovbe) {
|
||||
machine_info_.supports_extended_load_store =
|
||||
cpu.has(Xbyak::util::Cpu::tMOVBE);
|
||||
} else {
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
#include "xenia/base/cvar.h"
|
||||
#include "xenia/cpu/backend/backend.h"
|
||||
|
||||
DECLARE_bool(use_haswell_instructions);
|
||||
DECLARE_int32(x64_extension_mask);
|
||||
|
||||
namespace xe {
|
||||
class Exception;
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#include "third_party/fmt/include/fmt/format.h"
|
||||
#include "xenia/base/assert.h"
|
||||
#include "xenia/base/clock.h"
|
||||
#include "xenia/base/literals.h"
|
||||
#include "xenia/base/logging.h"
|
||||
#include "xenia/base/math.h"
|
||||
#include "xenia/base/memory.h"
|
||||
|
@ -31,6 +32,8 @@ namespace cpu {
|
|||
namespace backend {
|
||||
namespace x64 {
|
||||
|
||||
using namespace xe::literals;
|
||||
|
||||
X64CodeCache::X64CodeCache() = default;
|
||||
|
||||
X64CodeCache::~X64CodeCache() {
|
||||
|
@ -227,7 +230,7 @@ void X64CodeCache::PlaceGuestCode(uint32_t guest_address, void* machine_code,
|
|||
old_commit_mark = generated_code_commit_mark_;
|
||||
if (high_mark <= old_commit_mark) break;
|
||||
|
||||
new_commit_mark = old_commit_mark + 16 * 1024 * 1024;
|
||||
new_commit_mark = old_commit_mark + 16_MiB;
|
||||
if (generated_code_execute_base_ == generated_code_write_base_) {
|
||||
xe::memory::AllocFixed(generated_code_execute_base_, new_commit_mark,
|
||||
xe::memory::AllocationType::kCommit,
|
||||
|
@ -310,7 +313,7 @@ uint32_t X64CodeCache::PlaceData(const void* data, size_t length) {
|
|||
old_commit_mark = generated_code_commit_mark_;
|
||||
if (high_mark <= old_commit_mark) break;
|
||||
|
||||
new_commit_mark = old_commit_mark + 16 * 1024 * 1024;
|
||||
new_commit_mark = old_commit_mark + 16_MiB;
|
||||
if (generated_code_execute_base_ == generated_code_write_base_) {
|
||||
xe::memory::AllocFixed(generated_code_execute_base_, new_commit_mark,
|
||||
xe::memory::AllocationType::kCommit,
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
#include "xenia/base/assert.h"
|
||||
#include "xenia/base/atomic.h"
|
||||
#include "xenia/base/debugging.h"
|
||||
#include "xenia/base/literals.h"
|
||||
#include "xenia/base/logging.h"
|
||||
#include "xenia/base/math.h"
|
||||
#include "xenia/base/memory.h"
|
||||
|
@ -50,8 +51,9 @@ namespace x64 {
|
|||
|
||||
using xe::cpu::hir::HIRBuilder;
|
||||
using xe::cpu::hir::Instr;
|
||||
using namespace xe::literals;
|
||||
|
||||
static const size_t kMaxCodeSize = 1 * 1024 * 1024;
|
||||
static const size_t kMaxCodeSize = 1_MiB;
|
||||
|
||||
static const size_t kStashOffset = 32;
|
||||
// static const size_t kStashOffsetHigh = 32 + 32;
|
||||
|
@ -72,21 +74,31 @@ X64Emitter::X64Emitter(X64Backend* backend, XbyakAllocator* allocator)
|
|||
backend_(backend),
|
||||
code_cache_(backend->code_cache()),
|
||||
allocator_(allocator) {
|
||||
if (cvars::use_haswell_instructions) {
|
||||
feature_flags_ |= cpu_.has(Xbyak::util::Cpu::tAVX2) ? kX64EmitAVX2 : 0;
|
||||
feature_flags_ |= cpu_.has(Xbyak::util::Cpu::tFMA) ? kX64EmitFMA : 0;
|
||||
feature_flags_ |= cpu_.has(Xbyak::util::Cpu::tLZCNT) ? kX64EmitLZCNT : 0;
|
||||
feature_flags_ |= cpu_.has(Xbyak::util::Cpu::tBMI2) ? kX64EmitBMI2 : 0;
|
||||
feature_flags_ |= cpu_.has(Xbyak::util::Cpu::tF16C) ? kX64EmitF16C : 0;
|
||||
feature_flags_ |= cpu_.has(Xbyak::util::Cpu::tMOVBE) ? kX64EmitMovbe : 0;
|
||||
}
|
||||
|
||||
if (!cpu_.has(Xbyak::util::Cpu::tAVX)) {
|
||||
xe::FatalError(
|
||||
"Your CPU does not support AVX, which is required by Xenia. See the "
|
||||
"FAQ for system requirements at https://xenia.jp");
|
||||
return;
|
||||
}
|
||||
|
||||
#define TEST_EMIT_FEATURE(emit, ext) \
|
||||
if ((cvars::x64_extension_mask & emit) == emit) { \
|
||||
feature_flags_ |= (cpu_.has(ext) ? emit : 0); \
|
||||
}
|
||||
|
||||
TEST_EMIT_FEATURE(kX64EmitAVX2, Xbyak::util::Cpu::tAVX2);
|
||||
TEST_EMIT_FEATURE(kX64EmitFMA, Xbyak::util::Cpu::tFMA);
|
||||
TEST_EMIT_FEATURE(kX64EmitLZCNT, Xbyak::util::Cpu::tLZCNT);
|
||||
TEST_EMIT_FEATURE(kX64EmitBMI1, Xbyak::util::Cpu::tBMI1);
|
||||
TEST_EMIT_FEATURE(kX64EmitF16C, Xbyak::util::Cpu::tF16C);
|
||||
TEST_EMIT_FEATURE(kX64EmitMovbe, Xbyak::util::Cpu::tMOVBE);
|
||||
TEST_EMIT_FEATURE(kX64EmitGFNI, Xbyak::util::Cpu::tGFNI);
|
||||
TEST_EMIT_FEATURE(kX64EmitAVX512F, Xbyak::util::Cpu::tAVX512F);
|
||||
TEST_EMIT_FEATURE(kX64EmitAVX512VL, Xbyak::util::Cpu::tAVX512VL);
|
||||
TEST_EMIT_FEATURE(kX64EmitAVX512BW, Xbyak::util::Cpu::tAVX512BW);
|
||||
TEST_EMIT_FEATURE(kX64EmitAVX512DQ, Xbyak::util::Cpu::tAVX512DQ);
|
||||
|
||||
#undef TEST_EMIT_FEATURE
|
||||
}
|
||||
|
||||
X64Emitter::~X64Emitter() = default;
|
||||
|
|
|
@ -125,12 +125,23 @@ class XbyakAllocator : public Xbyak::Allocator {
|
|||
};
|
||||
|
||||
enum X64EmitterFeatureFlags {
|
||||
kX64EmitAVX2 = 1 << 1,
|
||||
kX64EmitFMA = 1 << 2,
|
||||
kX64EmitLZCNT = 1 << 3,
|
||||
kX64EmitAVX2 = 1 << 0,
|
||||
kX64EmitFMA = 1 << 1,
|
||||
kX64EmitLZCNT = 1 << 2,
|
||||
kX64EmitBMI1 = 1 << 3,
|
||||
kX64EmitBMI2 = 1 << 4,
|
||||
kX64EmitF16C = 1 << 5,
|
||||
kX64EmitMovbe = 1 << 6,
|
||||
kX64EmitGFNI = 1 << 7,
|
||||
|
||||
kX64EmitAVX512F = 1 << 8,
|
||||
kX64EmitAVX512VL = 1 << 9,
|
||||
|
||||
kX64EmitAVX512BW = 1 << 10,
|
||||
kX64EmitAVX512DQ = 1 << 11,
|
||||
|
||||
kX64EmitAVX512Ortho = kX64EmitAVX512F | kX64EmitAVX512VL,
|
||||
kX64EmitAVX512Ortho64 = kX64EmitAVX512Ortho | kX64EmitAVX512DQ
|
||||
};
|
||||
|
||||
class X64Emitter : public Xbyak::CodeGenerator {
|
||||
|
@ -221,7 +232,7 @@ class X64Emitter : public Xbyak::CodeGenerator {
|
|||
Xbyak::Address StashConstantXmm(int index, const vec128_t& v);
|
||||
|
||||
bool IsFeatureEnabled(uint32_t feature_flag) const {
|
||||
return (feature_flags_ & feature_flag) != 0;
|
||||
return (feature_flags_ & feature_flag) == feature_flag;
|
||||
}
|
||||
|
||||
FunctionDebugInfo* debug_info() const { return debug_info_; }
|
||||
|
|
|
@ -731,6 +731,25 @@ struct VECTOR_SHL_V128
|
|||
static void EmitInt8(X64Emitter& e, const EmitArgType& i) {
|
||||
// TODO(benvanik): native version (with shift magic).
|
||||
if (i.src2.is_constant) {
|
||||
if (e.IsFeatureEnabled(kX64EmitGFNI)) {
|
||||
const auto& shamt = i.src2.constant();
|
||||
bool all_same = true;
|
||||
for (size_t n = 0; n < 16 - n; ++n) {
|
||||
if (shamt.u8[n] != shamt.u8[n + 1]) {
|
||||
all_same = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (all_same) {
|
||||
// Every count is the same, so we can use gf2p8affineqb.
|
||||
const uint8_t shift_amount = shamt.u8[0] & 0b111;
|
||||
const uint64_t shift_matrix =
|
||||
UINT64_C(0x0102040810204080) >> (shift_amount * 8);
|
||||
e.vgf2p8affineqb(i.dest, i.src1,
|
||||
e.StashConstantXmm(0, vec128q(shift_matrix)), 0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
e.lea(e.GetNativeParam(1), e.StashConstantXmm(1, i.src2.constant()));
|
||||
} else {
|
||||
e.lea(e.GetNativeParam(1), e.StashXmm(1, i.src2));
|
||||
|
@ -920,6 +939,25 @@ struct VECTOR_SHR_V128
|
|||
static void EmitInt8(X64Emitter& e, const EmitArgType& i) {
|
||||
// TODO(benvanik): native version (with shift magic).
|
||||
if (i.src2.is_constant) {
|
||||
if (e.IsFeatureEnabled(kX64EmitGFNI)) {
|
||||
const auto& shamt = i.src2.constant();
|
||||
bool all_same = true;
|
||||
for (size_t n = 0; n < 16 - n; ++n) {
|
||||
if (shamt.u8[n] != shamt.u8[n + 1]) {
|
||||
all_same = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (all_same) {
|
||||
// Every count is the same, so we can use gf2p8affineqb.
|
||||
const uint8_t shift_amount = shamt.u8[0] & 0b111;
|
||||
const uint64_t shift_matrix = UINT64_C(0x0102040810204080)
|
||||
<< (shift_amount * 8);
|
||||
e.vgf2p8affineqb(i.dest, i.src1,
|
||||
e.StashConstantXmm(0, vec128q(shift_matrix)), 0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
e.lea(e.GetNativeParam(1), e.StashConstantXmm(1, i.src2.constant()));
|
||||
} else {
|
||||
e.lea(e.GetNativeParam(1), e.StashXmm(1, i.src2));
|
||||
|
@ -1084,6 +1122,27 @@ struct VECTOR_SHA_V128
|
|||
static void EmitInt8(X64Emitter& e, const EmitArgType& i) {
|
||||
// TODO(benvanik): native version (with shift magic).
|
||||
if (i.src2.is_constant) {
|
||||
if (e.IsFeatureEnabled(kX64EmitGFNI)) {
|
||||
const auto& shamt = i.src2.constant();
|
||||
bool all_same = true;
|
||||
for (size_t n = 0; n < 16 - n; ++n) {
|
||||
if (shamt.u8[n] != shamt.u8[n + 1]) {
|
||||
all_same = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (all_same) {
|
||||
// Every count is the same, so we can use gf2p8affineqb.
|
||||
const uint8_t shift_amount = shamt.u8[0] & 0b111;
|
||||
const uint64_t shift_matrix =
|
||||
(UINT64_C(0x0102040810204080) << (shift_amount * 8)) |
|
||||
(UINT64_C(0x8080808080808080) >> (64 - shift_amount * 8));
|
||||
;
|
||||
e.vgf2p8affineqb(i.dest, i.src1,
|
||||
e.StashConstantXmm(0, vec128q(shift_matrix)), 0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
e.lea(e.GetNativeParam(1), e.StashConstantXmm(1, i.src2.constant()));
|
||||
} else {
|
||||
e.lea(e.GetNativeParam(1), e.StashXmm(1, i.src2));
|
||||
|
|
|
@ -2627,6 +2627,115 @@ struct AND_V128 : Sequence<AND_V128, I<OPCODE_AND, V128Op, V128Op, V128Op>> {
|
|||
};
|
||||
EMITTER_OPCODE_TABLE(OPCODE_AND, AND_I8, AND_I16, AND_I32, AND_I64, AND_V128);
|
||||
|
||||
// ============================================================================
|
||||
// OPCODE_AND_NOT
|
||||
// ============================================================================
|
||||
template <typename SEQ, typename REG, typename ARGS>
|
||||
void EmitAndNotXX(X64Emitter& e, const ARGS& i) {
|
||||
if (i.src1.is_constant) {
|
||||
if (i.src2.is_constant) {
|
||||
// Both constants.
|
||||
e.mov(i.dest, i.src1.constant() & ~i.src2.constant());
|
||||
} else {
|
||||
// src1 constant.
|
||||
|
||||
// `and` instruction only supports up to 32-bit immediate constants
|
||||
// 64-bit constants will need a temp register
|
||||
if (i.dest.reg().getBit() == 64) {
|
||||
auto temp = GetTempReg<typename decltype(i.src1)::reg_type>(e);
|
||||
e.mov(temp, i.src1.constant());
|
||||
|
||||
if (e.IsFeatureEnabled(kX64EmitBMI1)) {
|
||||
if (i.dest.reg().getBit() == 64) {
|
||||
e.andn(i.dest.reg().cvt64(), i.src2.reg().cvt64(), temp.cvt64());
|
||||
} else {
|
||||
e.andn(i.dest.reg().cvt32(), i.src2.reg().cvt32(), temp.cvt32());
|
||||
}
|
||||
} else {
|
||||
e.mov(i.dest, i.src2);
|
||||
e.not_(i.dest);
|
||||
e.and_(i.dest, temp);
|
||||
}
|
||||
} else {
|
||||
e.mov(i.dest, i.src2);
|
||||
e.not_(i.dest);
|
||||
e.and_(i.dest, uint32_t(i.src1.constant()));
|
||||
}
|
||||
}
|
||||
} else if (i.src2.is_constant) {
|
||||
// src2 constant.
|
||||
if (i.dest == i.src1) {
|
||||
auto temp = GetTempReg<typename decltype(i.src2)::reg_type>(e);
|
||||
e.mov(temp, ~i.src2.constant());
|
||||
e.and_(i.dest, temp);
|
||||
} else {
|
||||
e.mov(i.dest, i.src1);
|
||||
auto temp = GetTempReg<typename decltype(i.src2)::reg_type>(e);
|
||||
e.mov(temp, ~i.src2.constant());
|
||||
e.and_(i.dest, temp);
|
||||
}
|
||||
} else {
|
||||
// neither are constant
|
||||
if (e.IsFeatureEnabled(kX64EmitBMI1)) {
|
||||
if (i.dest.reg().getBit() == 64) {
|
||||
e.andn(i.dest.reg().cvt64(), i.src2.reg().cvt64(),
|
||||
i.src1.reg().cvt64());
|
||||
} else {
|
||||
e.andn(i.dest.reg().cvt32(), i.src2.reg().cvt32(),
|
||||
i.src1.reg().cvt32());
|
||||
}
|
||||
} else {
|
||||
if (i.dest == i.src2) {
|
||||
e.not_(i.dest);
|
||||
e.and_(i.dest, i.src1);
|
||||
} else if (i.dest == i.src1) {
|
||||
auto temp = GetTempReg<typename decltype(i.dest)::reg_type>(e);
|
||||
e.mov(temp, i.src2);
|
||||
e.not_(temp);
|
||||
e.and_(i.dest, temp);
|
||||
} else {
|
||||
e.mov(i.dest, i.src2);
|
||||
e.not_(i.dest);
|
||||
e.and_(i.dest, i.src1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
struct AND_NOT_I8 : Sequence<AND_NOT_I8, I<OPCODE_AND_NOT, I8Op, I8Op, I8Op>> {
|
||||
static void Emit(X64Emitter& e, const EmitArgType& i) {
|
||||
EmitAndNotXX<AND_NOT_I8, Reg8>(e, i);
|
||||
}
|
||||
};
|
||||
struct AND_NOT_I16
|
||||
: Sequence<AND_NOT_I16, I<OPCODE_AND_NOT, I16Op, I16Op, I16Op>> {
|
||||
static void Emit(X64Emitter& e, const EmitArgType& i) {
|
||||
EmitAndNotXX<AND_NOT_I16, Reg16>(e, i);
|
||||
}
|
||||
};
|
||||
struct AND_NOT_I32
|
||||
: Sequence<AND_NOT_I32, I<OPCODE_AND_NOT, I32Op, I32Op, I32Op>> {
|
||||
static void Emit(X64Emitter& e, const EmitArgType& i) {
|
||||
EmitAndNotXX<AND_NOT_I32, Reg32>(e, i);
|
||||
}
|
||||
};
|
||||
struct AND_NOT_I64
|
||||
: Sequence<AND_NOT_I64, I<OPCODE_AND_NOT, I64Op, I64Op, I64Op>> {
|
||||
static void Emit(X64Emitter& e, const EmitArgType& i) {
|
||||
EmitAndNotXX<AND_NOT_I64, Reg64>(e, i);
|
||||
}
|
||||
};
|
||||
struct AND_NOT_V128
|
||||
: Sequence<AND_NOT_V128, I<OPCODE_AND_NOT, V128Op, V128Op, V128Op>> {
|
||||
static void Emit(X64Emitter& e, const EmitArgType& i) {
|
||||
EmitCommutativeBinaryXmmOp(e, i,
|
||||
[](X64Emitter& e, Xmm dest, Xmm src1, Xmm src2) {
|
||||
e.vpandn(dest, src2, src1);
|
||||
});
|
||||
}
|
||||
};
|
||||
EMITTER_OPCODE_TABLE(OPCODE_AND_NOT, AND_NOT_I8, AND_NOT_I16, AND_NOT_I32,
|
||||
AND_NOT_I64, AND_NOT_V128);
|
||||
|
||||
// ============================================================================
|
||||
// OPCODE_OR
|
||||
// ============================================================================
|
||||
|
|
|
@ -38,6 +38,13 @@ DEFINE_bool(
|
|||
DEFINE_bool(validate_hir, false,
|
||||
"Perform validation checks on the HIR during compilation.", "CPU");
|
||||
|
||||
DEFINE_uint64(
|
||||
pvr, 0x710700,
|
||||
"Processor version and revision number.\nBits 0 to 15 are the version "
|
||||
"number.\nBits 16 to 31 are the revision number.\nNote: Some XEXs (such as "
|
||||
"mfgbootlauncher.xex) may check for a value that's less than 0x710700.",
|
||||
"CPU");
|
||||
|
||||
// Breakpoints:
|
||||
DEFINE_uint64(break_on_instruction, 0,
|
||||
"int3 before the given guest address is executed.", "CPU");
|
||||
|
|
|
@ -26,6 +26,9 @@ DECLARE_bool(disable_global_lock);
|
|||
|
||||
DECLARE_bool(validate_hir);
|
||||
|
||||
DECLARE_uint64(pvr);
|
||||
|
||||
// Breakpoints:
|
||||
DECLARE_uint64(break_on_instruction);
|
||||
DECLARE_int32(break_condition_gpr);
|
||||
DECLARE_uint64(break_condition_value);
|
||||
|
|
|
@ -1759,6 +1759,26 @@ Value* HIRBuilder::And(Value* value1, Value* value2) {
|
|||
return i->dest;
|
||||
}
|
||||
|
||||
Value* HIRBuilder::AndNot(Value* value1, Value* value2) {
|
||||
ASSERT_NON_FLOAT_TYPE(value1);
|
||||
ASSERT_NON_FLOAT_TYPE(value2);
|
||||
ASSERT_TYPES_EQUAL(value1, value2);
|
||||
|
||||
if (value1 == value2) {
|
||||
return LoadZero(value1->type);
|
||||
} else if (value1->IsConstantZero()) {
|
||||
return value1;
|
||||
} else if (value2->IsConstantZero()) {
|
||||
return value1;
|
||||
}
|
||||
|
||||
Instr* i = AppendInstr(OPCODE_AND_NOT_info, 0, AllocValue(value1->type));
|
||||
i->set_src1(value1);
|
||||
i->set_src2(value2);
|
||||
i->src3.value = NULL;
|
||||
return i->dest;
|
||||
}
|
||||
|
||||
Value* HIRBuilder::Or(Value* value1, Value* value2) {
|
||||
ASSERT_NON_FLOAT_TYPE(value1);
|
||||
ASSERT_NON_FLOAT_TYPE(value2);
|
||||
|
|
|
@ -224,6 +224,7 @@ class HIRBuilder {
|
|||
Value* DotProduct4(Value* value1, Value* value2);
|
||||
|
||||
Value* And(Value* value1, Value* value2);
|
||||
Value* AndNot(Value* value1, Value* value2);
|
||||
Value* Or(Value* value1, Value* value2);
|
||||
Value* Xor(Value* value1, Value* value2);
|
||||
Value* Not(Value* value);
|
||||
|
|
|
@ -255,6 +255,7 @@ enum Opcode {
|
|||
OPCODE_DOT_PRODUCT_3,
|
||||
OPCODE_DOT_PRODUCT_4,
|
||||
OPCODE_AND,
|
||||
OPCODE_AND_NOT,
|
||||
OPCODE_OR,
|
||||
OPCODE_XOR,
|
||||
OPCODE_NOT,
|
||||
|
|
|
@ -524,6 +524,12 @@ DEFINE_OPCODE(
|
|||
OPCODE_SIG_V_V_V,
|
||||
OPCODE_FLAG_COMMUNATIVE)
|
||||
|
||||
DEFINE_OPCODE(
|
||||
OPCODE_AND_NOT,
|
||||
"and_not",
|
||||
OPCODE_SIG_V_V_V,
|
||||
0)
|
||||
|
||||
DEFINE_OPCODE(
|
||||
OPCODE_OR,
|
||||
"or",
|
||||
|
|
|
@ -286,7 +286,7 @@ int InstrEmit_stvlx_(PPCHIRBuilder& f, const InstrData& i, uint32_t vd,
|
|||
// mask = FFFF... >> eb
|
||||
Value* mask = f.Permute(f.LoadVectorShr(eb), f.LoadZeroVec128(),
|
||||
f.Not(f.LoadZeroVec128()), INT8_TYPE);
|
||||
Value* v = f.Or(f.And(old_value, f.Not(mask)), f.And(new_value, mask));
|
||||
Value* v = f.Or(f.AndNot(old_value, mask), f.And(new_value, mask));
|
||||
// ea &= ~0xF (handled above)
|
||||
f.Store(ea, f.ByteSwap(v));
|
||||
return 0;
|
||||
|
@ -328,7 +328,7 @@ int InstrEmit_stvrx_(PPCHIRBuilder& f, const InstrData& i, uint32_t vd,
|
|||
// mask = ~FFFF... >> eb
|
||||
Value* mask = f.Permute(f.LoadVectorShr(eb), f.Not(f.LoadZeroVec128()),
|
||||
f.LoadZeroVec128(), INT8_TYPE);
|
||||
Value* v = f.Or(f.And(old_value, f.Not(mask)), f.And(new_value, mask));
|
||||
Value* v = f.Or(f.AndNot(old_value, mask), f.And(new_value, mask));
|
||||
// ea &= ~0xF (handled above)
|
||||
f.Store(ea, f.ByteSwap(v));
|
||||
f.MarkLabel(skip_label);
|
||||
|
@ -459,7 +459,7 @@ int InstrEmit_vand128(PPCHIRBuilder& f, const InstrData& i) {
|
|||
|
||||
int InstrEmit_vandc_(PPCHIRBuilder& f, uint32_t vd, uint32_t va, uint32_t vb) {
|
||||
// VD <- (VA) & ¬(VB)
|
||||
Value* v = f.And(f.LoadVR(va), f.Not(f.LoadVR(vb)));
|
||||
Value* v = f.AndNot(f.LoadVR(va), f.LoadVR(vb));
|
||||
f.StoreVR(vd, v);
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -657,7 +657,7 @@ int InstrEmit_andx(PPCHIRBuilder& f, const InstrData& i) {
|
|||
|
||||
int InstrEmit_andcx(PPCHIRBuilder& f, const InstrData& i) {
|
||||
// RA <- (RS) & ¬(RB)
|
||||
Value* ra = f.And(f.LoadGPR(i.X.RT), f.Not(f.LoadGPR(i.X.RB)));
|
||||
Value* ra = f.AndNot(f.LoadGPR(i.X.RT), f.LoadGPR(i.X.RB));
|
||||
f.StoreGPR(i.X.RA, ra);
|
||||
if (i.X.Rc) {
|
||||
f.UpdateCR(0, ra);
|
||||
|
|
|
@ -620,6 +620,16 @@ int InstrEmit_mfspr(PPCHIRBuilder& f, const InstrData& i) {
|
|||
// TBU
|
||||
v = f.Shr(f.LoadClock(), 32);
|
||||
break;
|
||||
case 287:
|
||||
// [ Processor Version Register (PVR) ]
|
||||
// PVR is a 32 bit, read-only register within the supervisor level.
|
||||
// Bits 0 to 15 are the version number.
|
||||
// Bits 16 to 31 are the revision number.
|
||||
// Known Values: 0x710600?, 0x710700, 0x710800 (Corona?);
|
||||
// Note: Some XEXs (such as mfgbootlauncher.xex) may check for a value
|
||||
// that's less than 0x710700.
|
||||
v = f.LoadConstantUint64(cvars::pvr);
|
||||
break;
|
||||
default:
|
||||
XEINSTRNOTIMPLEMENTED();
|
||||
return 1;
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include "xenia/base/console_app_main.h"
|
||||
#include "xenia/base/cvar.h"
|
||||
#include "xenia/base/filesystem.h"
|
||||
#include "xenia/base/literals.h"
|
||||
#include "xenia/base/logging.h"
|
||||
#include "xenia/base/math.h"
|
||||
#include "xenia/base/platform.h"
|
||||
|
@ -36,6 +37,7 @@ namespace cpu {
|
|||
namespace test {
|
||||
|
||||
using xe::cpu::ppc::PPCContext;
|
||||
using namespace xe::literals;
|
||||
|
||||
typedef std::vector<std::pair<std::string, std::string>> AnnotationList;
|
||||
|
||||
|
@ -177,7 +179,7 @@ class TestSuite {
|
|||
|
||||
class TestRunner {
|
||||
public:
|
||||
TestRunner() : memory_size_(64 * 1024 * 1024) {
|
||||
TestRunner() : memory_size_(64_MiB) {
|
||||
memory_.reset(new Memory());
|
||||
memory_->Initialize();
|
||||
}
|
||||
|
@ -420,8 +422,7 @@ bool RunTests(const std::string_view test_name) {
|
|||
int failed_count = 0;
|
||||
int passed_count = 0;
|
||||
|
||||
XELOGI("Haswell instruction usage {}.",
|
||||
cvars::use_haswell_instructions ? "enabled" : "disabled");
|
||||
XELOGI("Instruction feature mask {}.", cvars::x64_extension_mask);
|
||||
|
||||
auto test_path_root = cvars::test_path;
|
||||
std::vector<std::filesystem::path> test_files;
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#include "xenia/base/cvar.h"
|
||||
#include "xenia/base/debugging.h"
|
||||
#include "xenia/base/exception_handler.h"
|
||||
#include "xenia/base/literals.h"
|
||||
#include "xenia/base/logging.h"
|
||||
#include "xenia/base/memory.h"
|
||||
#include "xenia/base/profiling.h"
|
||||
|
@ -57,6 +58,8 @@ namespace cpu {
|
|||
using xe::cpu::ppc::PPCOpcode;
|
||||
using xe::kernel::XThread;
|
||||
|
||||
using namespace xe::literals;
|
||||
|
||||
class BuiltinModule : public Module {
|
||||
public:
|
||||
explicit BuiltinModule(Processor* processor)
|
||||
|
@ -142,8 +145,8 @@ bool Processor::Setup(std::unique_ptr<backend::Backend> backend) {
|
|||
// Open the trace data path, if requested.
|
||||
functions_trace_path_ = cvars::trace_function_data_path;
|
||||
if (!functions_trace_path_.empty()) {
|
||||
functions_trace_file_ = ChunkedMappedMemoryWriter::Open(
|
||||
functions_trace_path_, 32 * 1024 * 1024, true);
|
||||
functions_trace_file_ =
|
||||
ChunkedMappedMemoryWriter::Open(functions_trace_path_, 32_MiB, true);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
|
|
@ -58,6 +58,28 @@ TEST_CASE("VECTOR_SHA_I8_CONSTANT", "[instr]") {
|
|||
});
|
||||
}
|
||||
|
||||
// This targets the "all_same" optimization of the Int8 specialization of
|
||||
// VECTOR_SHA_V128
|
||||
TEST_CASE("VECTOR_SHA_I8_SAME_CONSTANT", "[instr]") {
|
||||
TestFunction test([](HIRBuilder& b) {
|
||||
StoreVR(
|
||||
b, 3,
|
||||
b.VectorSha(LoadVR(b, 4), b.LoadConstantVec128(vec128b(5)), INT8_TYPE));
|
||||
b.Return();
|
||||
});
|
||||
test.Run(
|
||||
[](PPCContext* ctx) {
|
||||
ctx->v[4] = vec128b(0x7E, 0x7E, 0x7E, 0x7F, 0x80, 0xFF, 0x01, 0x12,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
|
||||
},
|
||||
[](PPCContext* ctx) {
|
||||
auto result = ctx->v[3];
|
||||
REQUIRE(result == vec128b(0x03, 0x03, 0x03, 0x03, 0xfc, 0xff, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00));
|
||||
});
|
||||
}
|
||||
|
||||
TEST_CASE("VECTOR_SHA_I16", "[instr]") {
|
||||
TestFunction test([](HIRBuilder& b) {
|
||||
StoreVR(b, 3, b.VectorSha(LoadVR(b, 4), LoadVR(b, 5), INT16_TYPE));
|
||||
|
|
|
@ -58,6 +58,28 @@ TEST_CASE("VECTOR_SHL_I8_CONSTANT", "[instr]") {
|
|||
});
|
||||
}
|
||||
|
||||
// This targets the "all_same" optimization of the Int8 specialization of
|
||||
// VECTOR_SHL_V128
|
||||
TEST_CASE("VECTOR_SHL_I8_SAME_CONSTANT", "[instr]") {
|
||||
TestFunction test([](HIRBuilder& b) {
|
||||
StoreVR(
|
||||
b, 3,
|
||||
b.VectorShl(LoadVR(b, 4), b.LoadConstantVec128(vec128b(5)), INT8_TYPE));
|
||||
b.Return();
|
||||
});
|
||||
test.Run(
|
||||
[](PPCContext* ctx) {
|
||||
ctx->v[4] = vec128b(0x7E, 0x7E, 0x7E, 0x7F, 0x80, 0xFF, 0x01, 0x12,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
|
||||
},
|
||||
[](PPCContext* ctx) {
|
||||
auto result = ctx->v[3];
|
||||
REQUIRE(result == vec128b(0xC0, 0xC0, 0xC0, 0xE0, 0x00, 0xE0, 0x20,
|
||||
0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00));
|
||||
});
|
||||
}
|
||||
|
||||
TEST_CASE("VECTOR_SHL_I16", "[instr]") {
|
||||
TestFunction test([](HIRBuilder& b) {
|
||||
StoreVR(b, 3, b.VectorShl(LoadVR(b, 4), LoadVR(b, 5), INT16_TYPE));
|
||||
|
|
|
@ -58,6 +58,28 @@ TEST_CASE("VECTOR_SHR_I8_CONSTANT", "[instr]") {
|
|||
});
|
||||
}
|
||||
|
||||
// This targets the "all_same" optimization of the Int8 specialization of
|
||||
// VECTOR_SHR_V128
|
||||
TEST_CASE("VECTOR_SHR_I8_SAME_CONSTANT", "[instr]") {
|
||||
TestFunction test([](HIRBuilder& b) {
|
||||
StoreVR(
|
||||
b, 3,
|
||||
b.VectorShr(LoadVR(b, 4), b.LoadConstantVec128(vec128b(3)), INT8_TYPE));
|
||||
b.Return();
|
||||
});
|
||||
test.Run(
|
||||
[](PPCContext* ctx) {
|
||||
ctx->v[4] = vec128b(0x7E, 0x7E, 0x7E, 0x7F, 0x80, 0xFF, 0x01, 0x12,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
|
||||
},
|
||||
[](PPCContext* ctx) {
|
||||
auto result = ctx->v[3];
|
||||
REQUIRE(result == vec128b(0x0F, 0x0F, 0x0F, 0x0F, 0x10, 0x1F, 0x00,
|
||||
0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00));
|
||||
});
|
||||
}
|
||||
|
||||
TEST_CASE("VECTOR_SHR_I16", "[instr]") {
|
||||
TestFunction test([](HIRBuilder& b) {
|
||||
StoreVR(b, 3, b.VectorShr(LoadVR(b, 4), LoadVR(b, 5), INT16_TYPE));
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2020 Ben Vanik. All rights reserved. *
|
||||
* Copyright 2022 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
@ -32,6 +32,8 @@
|
|||
#include "xenia/kernel/xthread.h"
|
||||
#include "xenia/ui/graphics_provider.h"
|
||||
#include "xenia/ui/imgui_drawer.h"
|
||||
#include "xenia/ui/immediate_drawer.h"
|
||||
#include "xenia/ui/presenter.h"
|
||||
#include "xenia/ui/windowed_app_context.h"
|
||||
|
||||
DEFINE_bool(imgui_debug, false, "Show ImGui debugging tools.", "UI");
|
||||
|
@ -49,14 +51,18 @@ using xe::ui::MenuItem;
|
|||
using xe::ui::MouseEvent;
|
||||
using xe::ui::UIEvent;
|
||||
|
||||
const std::string kBaseTitle = "Xenia Debugger";
|
||||
void DebugWindow::DebugDialog::OnDraw(ImGuiIO& io) {
|
||||
debug_window_.DrawFrame(io);
|
||||
}
|
||||
|
||||
static const std::string kBaseTitle = "Xenia Debugger";
|
||||
|
||||
DebugWindow::DebugWindow(Emulator* emulator,
|
||||
xe::ui::WindowedAppContext& app_context)
|
||||
: emulator_(emulator),
|
||||
processor_(emulator->processor()),
|
||||
app_context_(app_context),
|
||||
window_(xe::ui::Window::Create(app_context_, kBaseTitle)) {
|
||||
window_(xe::ui::Window::Create(app_context_, kBaseTitle, 1500, 1000)) {
|
||||
if (cs_open(CS_ARCH_X86, CS_MODE_64, &capstone_handle_) != CS_ERR_OK) {
|
||||
assert_always("Failed to initialize capstone");
|
||||
}
|
||||
|
@ -86,44 +92,57 @@ std::unique_ptr<DebugWindow> DebugWindow::Create(
|
|||
}
|
||||
|
||||
bool DebugWindow::Initialize() {
|
||||
if (!window_->Initialize()) {
|
||||
XELOGE("Failed to initialize platform window");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Main menu.
|
||||
auto main_menu = MenuItem::Create(MenuItem::Type::kNormal);
|
||||
auto file_menu = MenuItem::Create(MenuItem::Type::kPopup, "&File");
|
||||
{
|
||||
file_menu->AddChild(MenuItem::Create(MenuItem::Type::kString, "&Close",
|
||||
"Alt+F4",
|
||||
[this]() { window_->Close(); }));
|
||||
file_menu->AddChild(
|
||||
MenuItem::Create(MenuItem::Type::kString, "&Close", "Alt+F4",
|
||||
[this]() { window_->RequestClose(); }));
|
||||
}
|
||||
main_menu->AddChild(std::move(file_menu));
|
||||
window_->set_main_menu(std::move(main_menu));
|
||||
window_->SetMainMenu(std::move(main_menu));
|
||||
|
||||
window_->Resize(1500, 1000);
|
||||
// Open the window once it's configured.
|
||||
if (!window_->Open()) {
|
||||
XELOGE("Failed to open the platform window for the debugger");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create the graphics context used for drawing.
|
||||
auto provider = emulator_->display_window()->context()->provider();
|
||||
window_->set_context(provider->CreateHostContext(window_.get()));
|
||||
// Setup drawing to the window.
|
||||
|
||||
// Enable imgui input.
|
||||
window_->set_imgui_input_enabled(true);
|
||||
xe::ui::GraphicsProvider& graphics_provider =
|
||||
*emulator_->graphics_system()->provider();
|
||||
|
||||
window_->on_painting.AddListener([this](UIEvent* e) { DrawFrame(); });
|
||||
presenter_ = graphics_provider.CreatePresenter();
|
||||
if (!presenter_) {
|
||||
XELOGE("Failed to initialize the presenter for the debugger");
|
||||
return false;
|
||||
}
|
||||
|
||||
immediate_drawer_ = graphics_provider.CreateImmediateDrawer();
|
||||
if (!immediate_drawer_) {
|
||||
XELOGE("Failed to initialize the immediate drawer for the debugger");
|
||||
return false;
|
||||
}
|
||||
immediate_drawer_->SetPresenter(presenter_.get());
|
||||
|
||||
imgui_drawer_ = std::make_unique<xe::ui::ImGuiDrawer>(window_.get(), 0);
|
||||
imgui_drawer_->SetPresenterAndImmediateDrawer(presenter_.get(),
|
||||
immediate_drawer_.get());
|
||||
debug_dialog_ =
|
||||
std::unique_ptr<DebugDialog>(new DebugDialog(imgui_drawer_.get(), *this));
|
||||
|
||||
// Update the cache before the first frame.
|
||||
UpdateCache();
|
||||
window_->Invalidate();
|
||||
|
||||
// Begin drawing.
|
||||
window_->SetPresenter(presenter_.get());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void DebugWindow::DrawFrame() {
|
||||
xe::ui::GraphicsContextLock lock(window_->context());
|
||||
|
||||
auto& io = window_->imgui_drawer()->GetIO();
|
||||
|
||||
void DebugWindow::DrawFrame(ImGuiIO& io) {
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(-1, 0));
|
||||
ImGui::Begin("main_window", nullptr,
|
||||
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize |
|
||||
|
@ -242,9 +261,6 @@ void DebugWindow::DrawFrame() {
|
|||
ImGui::ShowDemoWindow();
|
||||
ImGui::ShowMetricsWindow();
|
||||
}
|
||||
|
||||
// Continuous paint.
|
||||
window_->Invalidate();
|
||||
}
|
||||
|
||||
void DebugWindow::DrawToolbar() {
|
||||
|
@ -1443,7 +1459,7 @@ void DebugWindow::UpdateCache() {
|
|||
title += " (stepping)";
|
||||
break;
|
||||
}
|
||||
window_->set_title(title);
|
||||
window_->SetTitle(title);
|
||||
});
|
||||
|
||||
cache_.is_running =
|
||||
|
@ -1573,7 +1589,7 @@ void DebugWindow::OnBreakpointHit(Breakpoint* breakpoint,
|
|||
}
|
||||
|
||||
void DebugWindow::Focus() const {
|
||||
app_context_.CallInUIThread([this]() { window_->set_focus(true); });
|
||||
app_context_.CallInUIThread([this]() { window_->Focus(); });
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2015 Ben Vanik. All rights reserved. *
|
||||
* Copyright 2022 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
@ -18,7 +18,11 @@
|
|||
#include "xenia/cpu/debug_listener.h"
|
||||
#include "xenia/cpu/processor.h"
|
||||
#include "xenia/emulator.h"
|
||||
#include "xenia/ui/imgui_dialog.h"
|
||||
#include "xenia/ui/imgui_drawer.h"
|
||||
#include "xenia/ui/immediate_drawer.h"
|
||||
#include "xenia/ui/menu_item.h"
|
||||
#include "xenia/ui/presenter.h"
|
||||
#include "xenia/ui/window.h"
|
||||
#include "xenia/ui/windowed_app_context.h"
|
||||
#include "xenia/xbox.h"
|
||||
|
@ -48,11 +52,24 @@ class DebugWindow : public cpu::DebugListener {
|
|||
cpu::ThreadDebugInfo* thread_info) override;
|
||||
|
||||
private:
|
||||
class DebugDialog final : public xe::ui::ImGuiDialog {
|
||||
public:
|
||||
explicit DebugDialog(xe::ui::ImGuiDrawer* imgui_drawer,
|
||||
DebugWindow& debug_window)
|
||||
: xe::ui::ImGuiDialog(imgui_drawer), debug_window_(debug_window) {}
|
||||
|
||||
protected:
|
||||
void OnDraw(ImGuiIO& io) override;
|
||||
|
||||
private:
|
||||
DebugWindow& debug_window_;
|
||||
};
|
||||
|
||||
explicit DebugWindow(Emulator* emulator,
|
||||
xe::ui::WindowedAppContext& app_context);
|
||||
bool Initialize();
|
||||
|
||||
void DrawFrame();
|
||||
void DrawFrame(ImGuiIO& io);
|
||||
void DrawToolbar();
|
||||
void DrawFunctionsPane();
|
||||
void DrawSourcePane();
|
||||
|
@ -93,7 +110,10 @@ class DebugWindow : public cpu::DebugListener {
|
|||
cpu::Processor* processor_ = nullptr;
|
||||
xe::ui::WindowedAppContext& app_context_;
|
||||
std::unique_ptr<xe::ui::Window> window_;
|
||||
uint64_t last_draw_tick_count_ = 0;
|
||||
std::unique_ptr<xe::ui::Presenter> presenter_;
|
||||
std::unique_ptr<xe::ui::ImmediateDrawer> immediate_drawer_;
|
||||
std::unique_ptr<xe::ui::ImGuiDrawer> imgui_drawer_;
|
||||
std::unique_ptr<DebugDialog> debug_dialog_;
|
||||
|
||||
uintptr_t capstone_handle_ = 0;
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
#include "xenia/emulator.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cinttypes>
|
||||
|
||||
#include "config.h"
|
||||
|
@ -20,9 +21,9 @@
|
|||
#include "xenia/base/cvar.h"
|
||||
#include "xenia/base/debugging.h"
|
||||
#include "xenia/base/exception_handler.h"
|
||||
#include "xenia/base/literals.h"
|
||||
#include "xenia/base/logging.h"
|
||||
#include "xenia/base/mapped_memory.h"
|
||||
#include "xenia/base/profiling.h"
|
||||
#include "xenia/base/string.h"
|
||||
#include "xenia/cpu/backend/code_cache.h"
|
||||
#include "xenia/cpu/backend/x64/x64_backend.h"
|
||||
|
@ -40,6 +41,7 @@
|
|||
#include "xenia/kernel/xboxkrnl/xboxkrnl_module.h"
|
||||
#include "xenia/memory.h"
|
||||
#include "xenia/ui/imgui_dialog.h"
|
||||
#include "xenia/ui/imgui_drawer.h"
|
||||
#include "xenia/ui/window.h"
|
||||
#include "xenia/ui/windowed_app_context.h"
|
||||
#include "xenia/vfs/devices/disc_image_device.h"
|
||||
|
@ -60,6 +62,17 @@ DEFINE_string(
|
|||
|
||||
namespace xe {
|
||||
|
||||
using namespace xe::literals;
|
||||
|
||||
Emulator::GameConfigLoadCallback::GameConfigLoadCallback(Emulator& emulator)
|
||||
: emulator_(emulator) {
|
||||
emulator_.AddGameConfigLoadCallback(this);
|
||||
}
|
||||
|
||||
Emulator::GameConfigLoadCallback::~GameConfigLoadCallback() {
|
||||
emulator_.RemoveGameConfigLoadCallback(this);
|
||||
}
|
||||
|
||||
Emulator::Emulator(const std::filesystem::path& command_line,
|
||||
const std::filesystem::path& storage_root,
|
||||
const std::filesystem::path& content_root,
|
||||
|
@ -113,7 +126,7 @@ Emulator::~Emulator() {
|
|||
}
|
||||
|
||||
X_STATUS Emulator::Setup(
|
||||
ui::Window* display_window,
|
||||
ui::Window* display_window, ui::ImGuiDrawer* imgui_drawer,
|
||||
std::function<std::unique_ptr<apu::AudioSystem>(cpu::Processor*)>
|
||||
audio_system_factory,
|
||||
std::function<std::unique_ptr<gpu::GraphicsSystem>()>
|
||||
|
@ -123,6 +136,7 @@ X_STATUS Emulator::Setup(
|
|||
X_STATUS result = X_STATUS_UNSUCCESSFUL;
|
||||
|
||||
display_window_ = display_window;
|
||||
imgui_drawer_ = imgui_drawer;
|
||||
|
||||
// Initialize clock.
|
||||
// 360 uses a 50MHz clock.
|
||||
|
@ -209,8 +223,10 @@ X_STATUS Emulator::Setup(
|
|||
kernel_state_ = std::make_unique<xe::kernel::KernelState>(this);
|
||||
|
||||
// Setup the core components.
|
||||
result = graphics_system_->Setup(processor_.get(), kernel_state_.get(),
|
||||
display_window_);
|
||||
result = graphics_system_->Setup(
|
||||
processor_.get(), kernel_state_.get(),
|
||||
display_window_ ? &display_window_->app_context() : nullptr,
|
||||
display_window_ != nullptr);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
|
@ -233,14 +249,6 @@ X_STATUS Emulator::Setup(
|
|||
// Initialize emulator fallback exception handling last.
|
||||
ExceptionHandler::Install(Emulator::ExceptionCallbackThunk, this);
|
||||
|
||||
if (display_window_) {
|
||||
// Finish initializing the display.
|
||||
display_window_->app_context().CallInUIThreadSynchronous([this]() {
|
||||
xe::ui::GraphicsContextLock context_lock(display_window_->context());
|
||||
Profiler::set_window(display_window_);
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -414,9 +422,8 @@ void Emulator::Resume() {
|
|||
bool Emulator::SaveToFile(const std::filesystem::path& path) {
|
||||
Pause();
|
||||
|
||||
filesystem::CreateFile(path);
|
||||
auto map = MappedMemory::Open(path, MappedMemory::Mode::kReadWrite, 0,
|
||||
1024ull * 1024ull * 1024ull * 2ull);
|
||||
filesystem::CreateEmptyFile(path);
|
||||
auto map = MappedMemory::Open(path, MappedMemory::Mode::kReadWrite, 0, 2_GiB);
|
||||
if (!map) {
|
||||
return false;
|
||||
}
|
||||
|
@ -585,14 +592,16 @@ bool Emulator::ExceptionCallback(Exception* ex) {
|
|||
}
|
||||
|
||||
// Display a dialog telling the user the guest has crashed.
|
||||
display_window()->app_context().CallInUIThreadSynchronous([this]() {
|
||||
xe::ui::ImGuiDialog::ShowMessageBox(
|
||||
display_window(), "Uh-oh!",
|
||||
"The guest has crashed.\n\n"
|
||||
""
|
||||
"Xenia has now paused itself.\n"
|
||||
"A crash dump has been written into the log.");
|
||||
});
|
||||
if (display_window_ && imgui_drawer_) {
|
||||
display_window_->app_context().CallInUIThreadSynchronous([this]() {
|
||||
xe::ui::ImGuiDialog::ShowMessageBox(
|
||||
imgui_drawer_, "Uh-oh!",
|
||||
"The guest has crashed.\n\n"
|
||||
""
|
||||
"Xenia has now paused itself.\n"
|
||||
"A crash dump has been written into the log.");
|
||||
});
|
||||
}
|
||||
|
||||
// Now suspend ourself (we should be a guest thread).
|
||||
current_thread->Suspend(nullptr);
|
||||
|
@ -619,6 +628,41 @@ void Emulator::WaitUntilExit() {
|
|||
on_exit();
|
||||
}
|
||||
|
||||
void Emulator::AddGameConfigLoadCallback(GameConfigLoadCallback* callback) {
|
||||
assert_not_null(callback);
|
||||
// Game config load callbacks handling is entirely in the UI thread.
|
||||
assert_true(!display_window_ ||
|
||||
display_window_->app_context().IsInUIThread());
|
||||
// Check if already added.
|
||||
if (std::find(game_config_load_callbacks_.cbegin(),
|
||||
game_config_load_callbacks_.cend(),
|
||||
callback) != game_config_load_callbacks_.cend()) {
|
||||
return;
|
||||
}
|
||||
game_config_load_callbacks_.push_back(callback);
|
||||
}
|
||||
|
||||
void Emulator::RemoveGameConfigLoadCallback(GameConfigLoadCallback* callback) {
|
||||
assert_not_null(callback);
|
||||
// Game config load callbacks handling is entirely in the UI thread.
|
||||
assert_true(!display_window_ ||
|
||||
display_window_->app_context().IsInUIThread());
|
||||
auto it = std::find(game_config_load_callbacks_.cbegin(),
|
||||
game_config_load_callbacks_.cend(), callback);
|
||||
if (it == game_config_load_callbacks_.cend()) {
|
||||
return;
|
||||
}
|
||||
if (game_config_load_callback_loop_next_index_ != SIZE_MAX) {
|
||||
// Actualize the next callback index after the erasure from the vector.
|
||||
size_t existing_index =
|
||||
size_t(std::distance(game_config_load_callbacks_.cbegin(), it));
|
||||
if (game_config_load_callback_loop_next_index_ > existing_index) {
|
||||
--game_config_load_callback_loop_next_index_;
|
||||
}
|
||||
}
|
||||
game_config_load_callbacks_.erase(it);
|
||||
}
|
||||
|
||||
std::string Emulator::FindLaunchModule() {
|
||||
std::string path("game:\\");
|
||||
|
||||
|
@ -675,6 +719,10 @@ static std::string format_version(xex2_version version) {
|
|||
|
||||
X_STATUS Emulator::CompleteLaunch(const std::filesystem::path& path,
|
||||
const std::string_view module_path) {
|
||||
// Making changes to the UI (setting the icon) and executing game config load
|
||||
// callbacks which expect to be called from the UI thread.
|
||||
assert_true(display_window_->app_context().IsInUIThread());
|
||||
|
||||
// Setup NullDevices for raw HDD partition accesses
|
||||
// Cache/STFC code baked into games tries reading/writing to these
|
||||
// By using a NullDevice that just returns success to all IO requests it
|
||||
|
@ -727,7 +775,19 @@ X_STATUS Emulator::CompleteLaunch(const std::filesystem::path& path,
|
|||
// Try and load the resource database (xex only).
|
||||
if (module->title_id()) {
|
||||
auto title_id = fmt::format("{:08X}", module->title_id());
|
||||
|
||||
// Load the per-game configuration file and make sure updates are handled by
|
||||
// the callbacks.
|
||||
config::LoadGameConfig(title_id);
|
||||
assert_true(game_config_load_callback_loop_next_index_ == SIZE_MAX);
|
||||
game_config_load_callback_loop_next_index_ = 0;
|
||||
while (game_config_load_callback_loop_next_index_ <
|
||||
game_config_load_callbacks_.size()) {
|
||||
game_config_load_callbacks_[game_config_load_callback_loop_next_index_++]
|
||||
->PostGameConfigLoad();
|
||||
}
|
||||
game_config_load_callback_loop_next_index_ = SIZE_MAX;
|
||||
|
||||
uint32_t resource_data = 0;
|
||||
uint32_t resource_size = 0;
|
||||
if (XSUCCEEDED(module->GetSection(title_id.c_str(), &resource_data,
|
||||
|
|
|
@ -10,9 +10,12 @@
|
|||
#ifndef XENIA_EMULATOR_H_
|
||||
#define XENIA_EMULATOR_H_
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "xenia/base/delegate.h"
|
||||
#include "xenia/base/exception_handler.h"
|
||||
|
@ -38,6 +41,7 @@ class InputDriver;
|
|||
class InputSystem;
|
||||
} // namespace hid
|
||||
namespace ui {
|
||||
class ImGuiDrawer;
|
||||
class Window;
|
||||
} // namespace ui
|
||||
} // namespace xe
|
||||
|
@ -50,6 +54,37 @@ constexpr fourcc_t kEmulatorSaveSignature = make_fourcc("XSAV");
|
|||
// This is responsible for initializing and managing all the various subsystems.
|
||||
class Emulator {
|
||||
public:
|
||||
// This is the class for the top-level callbacks. They may be called in an
|
||||
// undefined order, so among them there must be no dependencies on each other,
|
||||
// especially hierarchical ones. If hierarchical handling is needed, for
|
||||
// instance, if a specific implementation of a subsystem needs to handle
|
||||
// changes, but the entire implementation must be reloaded, the implementation
|
||||
// in this example _must not_ register / unregister its own callback - rather,
|
||||
// the proper ordering and hierarchy should be constructed in a single
|
||||
// callback (in this example, for the whole subsystem).
|
||||
//
|
||||
// All callbacks must be created and destroyed in the UI thread only (or the
|
||||
// thread that takes its place in the architecture of the specific app if
|
||||
// there's no UI), as they are invoked in the UI thread.
|
||||
class GameConfigLoadCallback {
|
||||
public:
|
||||
GameConfigLoadCallback(Emulator& emulator);
|
||||
GameConfigLoadCallback(const GameConfigLoadCallback& callback) = delete;
|
||||
GameConfigLoadCallback& operator=(const GameConfigLoadCallback& callback) =
|
||||
delete;
|
||||
virtual ~GameConfigLoadCallback();
|
||||
|
||||
// The callback is invoked in the UI thread (or the thread that takes its
|
||||
// place in the architecture of the specific app if there's no UI).
|
||||
virtual void PostGameConfigLoad() = 0;
|
||||
|
||||
protected:
|
||||
Emulator& emulator() const { return emulator_; }
|
||||
|
||||
private:
|
||||
Emulator& emulator_;
|
||||
};
|
||||
|
||||
explicit Emulator(const std::filesystem::path& command_line,
|
||||
const std::filesystem::path& storage_root,
|
||||
const std::filesystem::path& content_root,
|
||||
|
@ -82,9 +117,13 @@ class Emulator {
|
|||
// Are we currently running a title?
|
||||
bool is_title_open() const { return title_id_.has_value(); }
|
||||
|
||||
// Window used for displaying graphical output.
|
||||
// Window used for displaying graphical output. Can be null.
|
||||
ui::Window* display_window() const { return display_window_; }
|
||||
|
||||
// ImGui drawer for various kinds of dialogs requested by the guest. Can be
|
||||
// null.
|
||||
ui::ImGuiDrawer* imgui_drawer() const { return imgui_drawer_; }
|
||||
|
||||
// Guest memory system modelling the RAM (both virtual and physical) of the
|
||||
// system.
|
||||
Memory* memory() const { return memory_.get(); }
|
||||
|
@ -121,7 +160,7 @@ class Emulator {
|
|||
// Once this function returns a game can be launched using one of the Launch
|
||||
// functions.
|
||||
X_STATUS Setup(
|
||||
ui::Window* display_window,
|
||||
ui::Window* display_window, ui::ImGuiDrawer* imgui_drawer,
|
||||
std::function<std::unique_ptr<apu::AudioSystem>(cpu::Processor*)>
|
||||
audio_system_factory,
|
||||
std::function<std::unique_ptr<gpu::GraphicsSystem>()>
|
||||
|
@ -170,6 +209,9 @@ class Emulator {
|
|||
static bool ExceptionCallbackThunk(Exception* ex, void* data);
|
||||
bool ExceptionCallback(Exception* ex);
|
||||
|
||||
void AddGameConfigLoadCallback(GameConfigLoadCallback* callback);
|
||||
void RemoveGameConfigLoadCallback(GameConfigLoadCallback* callback);
|
||||
|
||||
std::string FindLaunchModule();
|
||||
|
||||
X_STATUS CompleteLaunch(const std::filesystem::path& path,
|
||||
|
@ -183,7 +225,8 @@ class Emulator {
|
|||
std::string title_name_;
|
||||
std::string title_version_;
|
||||
|
||||
ui::Window* display_window_;
|
||||
ui::Window* display_window_ = nullptr;
|
||||
ui::ImGuiDrawer* imgui_drawer_ = nullptr;
|
||||
|
||||
std::unique_ptr<Memory> memory_;
|
||||
|
||||
|
@ -196,6 +239,16 @@ class Emulator {
|
|||
std::unique_ptr<vfs::VirtualFileSystem> file_system_;
|
||||
|
||||
std::unique_ptr<kernel::KernelState> kernel_state_;
|
||||
|
||||
// Accessible only from the thread that invokes those callbacks (the UI thread
|
||||
// if the UI is available).
|
||||
std::vector<GameConfigLoadCallback*> game_config_load_callbacks_;
|
||||
// Using an index, not an iterator, because after the erasure, the adjustment
|
||||
// must be done for the vector element indices that would be in the iterator
|
||||
// range that would be invalidated.
|
||||
// SIZE_MAX if not currently in the game config load callback loop.
|
||||
size_t game_config_load_callback_loop_next_index_ = SIZE_MAX;
|
||||
|
||||
kernel::object_ref<kernel::XThread> main_thread_;
|
||||
std::optional<uint32_t> title_id_; // Currently running title ID
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2020 Ben Vanik. All rights reserved. *
|
||||
* Copyright 2022 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
@ -45,15 +45,12 @@ CommandProcessor::CommandProcessor(GraphicsSystem* graphics_system,
|
|||
|
||||
CommandProcessor::~CommandProcessor() = default;
|
||||
|
||||
bool CommandProcessor::Initialize(
|
||||
std::unique_ptr<xe::ui::GraphicsContext> context) {
|
||||
context_ = std::move(context);
|
||||
|
||||
bool CommandProcessor::Initialize() {
|
||||
// Initialize the gamma ramps to their default (linear) values - taken from
|
||||
// what games set when starting.
|
||||
for (uint32_t i = 0; i < 256; ++i) {
|
||||
uint32_t value = i * 1023 / 255;
|
||||
gamma_ramp_.normal[i].value = value | (value << 10) | (value << 20);
|
||||
gamma_ramp_.table[i].value = value | (value << 10) | (value << 20);
|
||||
}
|
||||
for (uint32_t i = 0; i < 128; ++i) {
|
||||
uint32_t value = (i * 65535 / 127) & ~63;
|
||||
|
@ -64,7 +61,7 @@ bool CommandProcessor::Initialize(
|
|||
gamma_ramp_.pwl[i].values[j].value = value;
|
||||
}
|
||||
}
|
||||
dirty_gamma_ramp_normal_ = true;
|
||||
dirty_gamma_ramp_table_ = true;
|
||||
dirty_gamma_ramp_pwl_ = true;
|
||||
|
||||
worker_running_ = true;
|
||||
|
@ -140,8 +137,18 @@ void CommandProcessor::CallInThread(std::function<void()> fn) {
|
|||
|
||||
void CommandProcessor::ClearCaches() {}
|
||||
|
||||
void CommandProcessor::SetDesiredSwapPostEffect(
|
||||
SwapPostEffect swap_post_effect) {
|
||||
if (swap_post_effect_desired_ == swap_post_effect) {
|
||||
return;
|
||||
}
|
||||
swap_post_effect_desired_ = swap_post_effect;
|
||||
CallInThread([this, swap_post_effect]() {
|
||||
swap_post_effect_actual_ = swap_post_effect;
|
||||
});
|
||||
}
|
||||
|
||||
void CommandProcessor::WorkerThreadMain() {
|
||||
context_->MakeCurrent();
|
||||
if (!SetupContext()) {
|
||||
xe::FatalError("Unable to setup command processor internal state");
|
||||
return;
|
||||
|
@ -212,9 +219,6 @@ void CommandProcessor::Pause() {
|
|||
threading::Thread::GetCurrentThread()->Suspend();
|
||||
});
|
||||
|
||||
// HACK - Prevents a hang in IssueSwap()
|
||||
swap_state_.pending = false;
|
||||
|
||||
fence.Wait();
|
||||
}
|
||||
|
||||
|
@ -255,7 +259,7 @@ bool CommandProcessor::Restore(ByteStream* stream) {
|
|||
|
||||
bool CommandProcessor::SetupContext() { return true; }
|
||||
|
||||
void CommandProcessor::ShutdownContext() { context_.reset(); }
|
||||
void CommandProcessor::ShutdownContext() {}
|
||||
|
||||
void CommandProcessor::InitializeRingBuffer(uint32_t ptr, uint32_t size_log2) {
|
||||
read_ptr_index_ = 0;
|
||||
|
@ -326,14 +330,17 @@ void CommandProcessor::UpdateGammaRampValue(GammaRampType type,
|
|||
|
||||
if (mask_lo) {
|
||||
switch (type) {
|
||||
case GammaRampType::kNormal:
|
||||
case GammaRampType::kTable:
|
||||
assert_true(regs->values[XE_GPU_REG_DC_LUT_RW_MODE].u32 == 0);
|
||||
gamma_ramp_.normal[index].value = value;
|
||||
dirty_gamma_ramp_normal_ = true;
|
||||
gamma_ramp_.table[index].value = value;
|
||||
dirty_gamma_ramp_table_ = true;
|
||||
break;
|
||||
case GammaRampType::kPWL:
|
||||
assert_true(regs->values[XE_GPU_REG_DC_LUT_RW_MODE].u32 == 1);
|
||||
gamma_ramp_.pwl[index].values[gamma_ramp_rw_subindex_].value = value;
|
||||
// The lower 6 bits are hardwired to 0.
|
||||
// https://developer.amd.com/wordpress/media/2012/10/RRG-216M56-03oOEM.pdf
|
||||
gamma_ramp_.pwl[index].values[gamma_ramp_rw_subindex_].value =
|
||||
value & ~(uint32_t(63) | (uint32_t(63) << 16));
|
||||
gamma_ramp_rw_subindex_ = (gamma_ramp_rw_subindex_ + 1) % 3;
|
||||
dirty_gamma_ramp_pwl_ = true;
|
||||
break;
|
||||
|
@ -385,51 +392,6 @@ void CommandProcessor::PrepareForWait() { trace_writer_.Flush(); }
|
|||
|
||||
void CommandProcessor::ReturnFromWait() {}
|
||||
|
||||
void CommandProcessor::IssueSwap(uint32_t frontbuffer_ptr,
|
||||
uint32_t frontbuffer_width,
|
||||
uint32_t frontbuffer_height) {
|
||||
SCOPE_profile_cpu_f("gpu");
|
||||
if (!swap_request_handler_) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If there was a swap pending we drop it on the floor.
|
||||
// This prevents the display from pulling the backbuffer out from under us.
|
||||
// If we skip a lot then we may need to buffer more, but as the display
|
||||
// thread should be fairly idle that shouldn't happen.
|
||||
if (!cvars::vsync) {
|
||||
std::lock_guard<std::mutex> lock(swap_state_.mutex);
|
||||
if (swap_state_.pending) {
|
||||
swap_state_.pending = false;
|
||||
// TODO(benvanik): frame skip counter.
|
||||
XELOGW("Skipped frame!");
|
||||
}
|
||||
} else {
|
||||
// Spin until no more pending swap.
|
||||
while (worker_running_) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(swap_state_.mutex);
|
||||
if (!swap_state_.pending) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
xe::threading::MaybeYield();
|
||||
}
|
||||
}
|
||||
|
||||
PerformSwap(frontbuffer_ptr, frontbuffer_width, frontbuffer_height);
|
||||
|
||||
{
|
||||
// Set pending so that the display will swap the next time it can.
|
||||
std::lock_guard<std::mutex> lock(swap_state_.mutex);
|
||||
swap_state_.pending = true;
|
||||
}
|
||||
|
||||
// Notify the display a swap is pending so that our changes are picked up.
|
||||
// It does the actual front/back buffer swap.
|
||||
swap_request_handler_();
|
||||
}
|
||||
|
||||
uint32_t CommandProcessor::ExecutePrimaryBuffer(uint32_t read_index,
|
||||
uint32_t write_index) {
|
||||
SCOPE_profile_cpu_f("gpu");
|
||||
|
@ -440,7 +402,7 @@ uint32_t CommandProcessor::ExecutePrimaryBuffer(uint32_t read_index,
|
|||
uint32_t title_id = kernel_state_->GetExecutableModule()
|
||||
? kernel_state_->GetExecutableModule()->title_id()
|
||||
: 0;
|
||||
auto file_name = fmt::format("{:8X}_stream.xtr", title_id);
|
||||
auto file_name = fmt::format("{:08X}_stream.xtr", title_id);
|
||||
auto path = trace_stream_path_ / file_name;
|
||||
trace_writer_.Open(path, title_id);
|
||||
InitializeTrace();
|
||||
|
@ -767,7 +729,7 @@ bool CommandProcessor::ExecutePacketType3(RingBuffer* reader, uint32_t packet) {
|
|||
} else if (trace_state_ == TraceState::kSingleFrame) {
|
||||
// New trace request - we only start tracing at the beginning of a frame.
|
||||
uint32_t title_id = kernel_state_->GetExecutableModule()->title_id();
|
||||
auto file_name = fmt::format("{:8X}_{}.xtr", title_id, counter_ - 1);
|
||||
auto file_name = fmt::format("{:08X}_{}.xtr", title_id, counter_ - 1);
|
||||
auto path = trace_frame_path_ / file_name;
|
||||
trace_writer_.Open(path, title_id);
|
||||
InitializeTrace();
|
||||
|
@ -837,7 +799,7 @@ bool CommandProcessor::ExecutePacketType3_XE_SWAP(RingBuffer* reader,
|
|||
uint32_t frontbuffer_height = reader->ReadAndSwap<uint32_t>();
|
||||
reader->AdvanceRead((count - 4) * sizeof(uint32_t));
|
||||
|
||||
if (swap_mode_ == SwapMode::kNormal) {
|
||||
if (!ignore_swap_) {
|
||||
IssueSwap(frontbuffer_ptr, frontbuffer_width, frontbuffer_height);
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2020 Ben Vanik. All rights reserved. *
|
||||
* Copyright 2022 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
@ -26,7 +26,7 @@
|
|||
#include "xenia/gpu/xenos.h"
|
||||
#include "xenia/kernel/xthread.h"
|
||||
#include "xenia/memory.h"
|
||||
#include "xenia/ui/graphics_context.h"
|
||||
#include "xenia/ui/presenter.h"
|
||||
|
||||
namespace xe {
|
||||
|
||||
|
@ -60,12 +60,20 @@ enum class SwapMode {
|
|||
|
||||
enum class GammaRampType {
|
||||
kUnknown = 0,
|
||||
kNormal,
|
||||
kTable,
|
||||
kPWL,
|
||||
};
|
||||
|
||||
struct GammaRamp {
|
||||
struct NormalEntry {
|
||||
// A lot of gamma ramp (DC_LUT) documentation:
|
||||
// https://developer.amd.com/wordpress/media/2012/10/RRG-216M56-03oOEM.pdf
|
||||
// The ramps entries are BGR, not RGB.
|
||||
// For the 256-entry table (used by Direct3D 9 for a 8bpc front buffer),
|
||||
// 535107D4 has in-game settings allowing separate configuration.
|
||||
// The component order of the PWL table is untested, however, it's likely BGR
|
||||
// too, since DC_LUTA/B registers have values for blue first, and for red
|
||||
// last.
|
||||
struct TableEntry {
|
||||
union {
|
||||
uint32_t value;
|
||||
struct {
|
||||
|
@ -81,6 +89,15 @@ struct GammaRamp {
|
|||
union {
|
||||
uint32_t value;
|
||||
struct {
|
||||
// The lower 6 bits are always zero (these are 10-bit in the upper bits
|
||||
// thus, not fully 16-bit).
|
||||
// See DC_LUTA/B_CONTROL for information about the way they should be
|
||||
// interpreted (`output = base + (multiplier * delta) / 2^increment`,
|
||||
// where the increment is the value specified in DC_LUTA/B_CONTROL for
|
||||
// the specific color channel, the base is 7 bits of the front buffer
|
||||
// value above `increment` bits, the multiplier is the lower `increment`
|
||||
// bits of it; the increment is nonzero, otherwise the 256-entry table
|
||||
// should be used instead).
|
||||
uint16_t base;
|
||||
uint16_t delta;
|
||||
};
|
||||
|
@ -91,19 +108,25 @@ struct GammaRamp {
|
|||
union {
|
||||
PWLValue values[3];
|
||||
struct {
|
||||
PWLValue r;
|
||||
PWLValue g;
|
||||
PWLValue b;
|
||||
PWLValue g;
|
||||
PWLValue r;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
NormalEntry normal[256];
|
||||
TableEntry table[256];
|
||||
PWLEntry pwl[128];
|
||||
};
|
||||
|
||||
class CommandProcessor {
|
||||
public:
|
||||
enum class SwapPostEffect {
|
||||
kNone,
|
||||
kFxaa,
|
||||
kFxaaExtreme,
|
||||
};
|
||||
|
||||
CommandProcessor(GraphicsSystem* graphics_system,
|
||||
kernel::KernelState* kernel_state);
|
||||
virtual ~CommandProcessor();
|
||||
|
@ -114,21 +137,26 @@ class CommandProcessor {
|
|||
Shader* active_vertex_shader() const { return active_vertex_shader_; }
|
||||
Shader* active_pixel_shader() const { return active_pixel_shader_; }
|
||||
|
||||
virtual bool Initialize(std::unique_ptr<xe::ui::GraphicsContext> context);
|
||||
virtual bool Initialize();
|
||||
virtual void Shutdown();
|
||||
|
||||
void CallInThread(std::function<void()> fn);
|
||||
|
||||
virtual void ClearCaches();
|
||||
|
||||
SwapState& swap_state() { return swap_state_; }
|
||||
void set_swap_mode(SwapMode swap_mode) { swap_mode_ = swap_mode; }
|
||||
void IssueSwap(uint32_t frontbuffer_ptr, uint32_t frontbuffer_width,
|
||||
uint32_t frontbuffer_height);
|
||||
|
||||
void set_swap_request_handler(std::function<void()> fn) {
|
||||
swap_request_handler_ = fn;
|
||||
void SetIgnoreSwap(bool ignore_swap) { ignore_swap_ = ignore_swap; }
|
||||
// "Desired" is for the external thread managing the post-processing effect.
|
||||
SwapPostEffect GetDesiredSwapPostEffect() const {
|
||||
return swap_post_effect_desired_;
|
||||
}
|
||||
void SetDesiredSwapPostEffect(SwapPostEffect swap_post_effect);
|
||||
// Implementations must not make assumptions that the front buffer will
|
||||
// necessarily be a resolve destination - it may be a texture generated by any
|
||||
// means like written to by the CPU or loaded from a file (the disclaimer
|
||||
// screen right in the beginning of 4D530AA4 is not a resolved render target,
|
||||
// for instance).
|
||||
virtual void IssueSwap(uint32_t frontbuffer_ptr, uint32_t frontbuffer_width,
|
||||
uint32_t frontbuffer_height) = 0;
|
||||
|
||||
// May be called not only from the command processor thread when the command
|
||||
// processor is paused, and the termination of this function may be explicitly
|
||||
|
@ -179,9 +207,6 @@ class CommandProcessor {
|
|||
virtual void PrepareForWait();
|
||||
virtual void ReturnFromWait();
|
||||
|
||||
virtual void PerformSwap(uint32_t frontbuffer_ptr, uint32_t frontbuffer_width,
|
||||
uint32_t frontbuffer_height) = 0;
|
||||
|
||||
uint32_t ExecutePrimaryBuffer(uint32_t start_index, uint32_t end_index);
|
||||
virtual void OnPrimaryBufferEnd() {}
|
||||
void ExecuteIndirectBuffer(uint32_t ptr, uint32_t length);
|
||||
|
@ -254,6 +279,14 @@ class CommandProcessor {
|
|||
bool major_mode_explicit) = 0;
|
||||
virtual bool IssueCopy() = 0;
|
||||
|
||||
// "Actual" is for the command processor thread, to be read by the
|
||||
// implementations.
|
||||
SwapPostEffect GetActualSwapPostEffect() const {
|
||||
return swap_post_effect_actual_;
|
||||
}
|
||||
|
||||
// TODO(Triang3l): Write the gamma ramp (including the display controller
|
||||
// write pointers) in the common code.
|
||||
virtual void InitializeTrace() = 0;
|
||||
|
||||
Memory* memory_ = nullptr;
|
||||
|
@ -274,10 +307,8 @@ class CommandProcessor {
|
|||
std::atomic<bool> worker_running_;
|
||||
kernel::object_ref<kernel::XHostThread> worker_thread_;
|
||||
|
||||
std::unique_ptr<xe::ui::GraphicsContext> context_;
|
||||
SwapMode swap_mode_ = SwapMode::kNormal;
|
||||
SwapState swap_state_;
|
||||
std::function<void()> swap_request_handler_;
|
||||
bool ignore_swap_ = false;
|
||||
|
||||
std::queue<std::function<void()>> pending_fns_;
|
||||
|
||||
// MicroEngine binary from PM4_ME_INIT
|
||||
|
@ -305,8 +336,13 @@ class CommandProcessor {
|
|||
|
||||
GammaRamp gamma_ramp_ = {};
|
||||
int gamma_ramp_rw_subindex_ = 0;
|
||||
bool dirty_gamma_ramp_normal_ = true;
|
||||
bool dirty_gamma_ramp_table_ = true;
|
||||
bool dirty_gamma_ramp_pwl_ = true;
|
||||
|
||||
// By default (such as for tools), post-processing is disabled.
|
||||
// "Desired" is for the external thread managing the post-processing effect.
|
||||
SwapPostEffect swap_post_effect_desired_ = SwapPostEffect::kNone;
|
||||
SwapPostEffect swap_post_effect_actual_ = SwapPostEffect::kNone;
|
||||
};
|
||||
|
||||
} // namespace gpu
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -2,7 +2,7 @@
|
|||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2020 Ben Vanik. All rights reserved. *
|
||||
* Copyright 2022 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
@ -32,8 +32,8 @@
|
|||
#include "xenia/gpu/dxbc_shader_translator.h"
|
||||
#include "xenia/gpu/xenos.h"
|
||||
#include "xenia/kernel/kernel_state.h"
|
||||
#include "xenia/ui/d3d12/d3d12_context.h"
|
||||
#include "xenia/ui/d3d12/d3d12_descriptor_heap_pool.h"
|
||||
#include "xenia/ui/d3d12/d3d12_provider.h"
|
||||
#include "xenia/ui/d3d12/d3d12_upload_buffer_pool.h"
|
||||
#include "xenia/ui/d3d12/d3d12_util.h"
|
||||
|
||||
|
@ -58,8 +58,9 @@ class D3D12CommandProcessor : public CommandProcessor {
|
|||
|
||||
void RestoreEdramSnapshot(const void* snapshot) override;
|
||||
|
||||
ui::d3d12::D3D12Context& GetD3D12Context() const {
|
||||
return static_cast<ui::d3d12::D3D12Context&>(*context_);
|
||||
ui::d3d12::D3D12Provider& GetD3D12Provider() const {
|
||||
return *static_cast<ui::d3d12::D3D12Provider*>(
|
||||
graphics_system_->provider());
|
||||
}
|
||||
|
||||
// Returns the deferred drawing command list for the currently open
|
||||
|
@ -153,7 +154,7 @@ class D3D12CommandProcessor : public CommandProcessor {
|
|||
kEdramR32G32UintUAV,
|
||||
kEdramR32G32B32A32UintUAV,
|
||||
|
||||
kGammaRampNormalSRV,
|
||||
kGammaRampTableSRV,
|
||||
kGammaRampPWLSRV,
|
||||
|
||||
// Beyond this point, SRVs are accessible to shaders through an unbounded
|
||||
|
@ -210,16 +211,14 @@ class D3D12CommandProcessor : public CommandProcessor {
|
|||
// Returns the text to display in the GPU backend name in the window title.
|
||||
std::string GetWindowTitleText() const;
|
||||
|
||||
std::unique_ptr<xe::ui::RawImage> Capture();
|
||||
|
||||
protected:
|
||||
bool SetupContext() override;
|
||||
void ShutdownContext() override;
|
||||
|
||||
void WriteRegister(uint32_t index, uint32_t value) override;
|
||||
|
||||
void PerformSwap(uint32_t frontbuffer_ptr, uint32_t frontbuffer_width,
|
||||
uint32_t frontbuffer_height) override;
|
||||
void IssueSwap(uint32_t frontbuffer_ptr, uint32_t frontbuffer_width,
|
||||
uint32_t frontbuffer_height) override;
|
||||
|
||||
void OnPrimaryBufferEnd() override;
|
||||
|
||||
|
@ -321,8 +320,9 @@ class D3D12CommandProcessor : public CommandProcessor {
|
|||
void CheckSubmissionFence(uint64_t await_submission);
|
||||
// If is_guest_command is true, a new full frame - with full cleanup of
|
||||
// resources and, if needed, starting capturing - is opened if pending (as
|
||||
// opposed to simply resuming after mid-frame synchronization).
|
||||
void BeginSubmission(bool is_guest_command);
|
||||
// opposed to simply resuming after mid-frame synchronization). Returns
|
||||
// whether a submission is open currently and the device is not removed.
|
||||
bool BeginSubmission(bool is_guest_command);
|
||||
// If is_swap is true, a full frame is closed - with, if needed, cache
|
||||
// clearing and stopping capturing. Returns whether the submission was done
|
||||
// successfully, if it has failed, leaves it open.
|
||||
|
@ -380,6 +380,8 @@ class D3D12CommandProcessor : public CommandProcessor {
|
|||
|
||||
void WriteGammaRampSRV(bool is_pwl, D3D12_CPU_DESCRIPTOR_HANDLE handle) const;
|
||||
|
||||
bool device_removed_ = false;
|
||||
|
||||
bool cache_clear_requested_ = false;
|
||||
|
||||
HANDLE fence_completion_event_ = nullptr;
|
||||
|
@ -497,42 +499,73 @@ class D3D12CommandProcessor : public CommandProcessor {
|
|||
|
||||
std::unique_ptr<TextureCache> texture_cache_;
|
||||
|
||||
// Mip 0 contains the normal gamma ramp (256 entries), mip 1 contains the PWL
|
||||
// ramp (128 entries). DXGI_FORMAT_R10G10B10A2_UNORM 1D.
|
||||
ID3D12Resource* gamma_ramp_texture_ = nullptr;
|
||||
D3D12_RESOURCE_STATES gamma_ramp_texture_state_;
|
||||
// Bytes 0x0...0x3FF - 256-entry R10G10B10X2 gamma ramp (red and blue must be
|
||||
// read as swapped - 535107D4 has settings allowing separate configuration).
|
||||
// Bytes 0x400...0x9FF - 128-entry PWL R16G16 gamma ramp (R - base, G - delta,
|
||||
// low 6 bits of each are zero, 3 elements per entry).
|
||||
// https://www.x.org/docs/AMD/old/42590_m76_rrg_1.01o.pdf
|
||||
Microsoft::WRL::ComPtr<ID3D12Resource> gamma_ramp_buffer_;
|
||||
D3D12_RESOURCE_STATES gamma_ramp_buffer_state_;
|
||||
// Upload buffer for an image that is the same as gamma_ramp_, but with
|
||||
// kQueueFrames array layers.
|
||||
ID3D12Resource* gamma_ramp_upload_ = nullptr;
|
||||
uint8_t* gamma_ramp_upload_mapping_ = nullptr;
|
||||
D3D12_PLACED_SUBRESOURCE_FOOTPRINT gamma_ramp_footprints_[kQueueFrames * 2];
|
||||
Microsoft::WRL::ComPtr<ID3D12Resource> gamma_ramp_upload_buffer_;
|
||||
uint8_t* gamma_ramp_upload_buffer_mapping_ = nullptr;
|
||||
|
||||
static constexpr uint32_t kSwapTextureWidth = 1280;
|
||||
static constexpr uint32_t kSwapTextureHeight = 720;
|
||||
std::pair<uint32_t, uint32_t> GetSwapTextureSize() const {
|
||||
return std::make_pair(
|
||||
kSwapTextureWidth * texture_cache_->GetDrawResolutionScaleX(),
|
||||
kSwapTextureHeight * texture_cache_->GetDrawResolutionScaleY());
|
||||
}
|
||||
std::pair<uint32_t, uint32_t> GetSwapScreenSize() const {
|
||||
uint32_t resolution_scale =
|
||||
std::max(texture_cache_->GetDrawResolutionScaleX(),
|
||||
texture_cache_->GetDrawResolutionScaleY());
|
||||
return std::make_pair(kSwapTextureWidth * resolution_scale,
|
||||
kSwapTextureHeight * resolution_scale);
|
||||
}
|
||||
ID3D12Resource* swap_texture_ = nullptr;
|
||||
D3D12_PLACED_SUBRESOURCE_FOOTPRINT swap_texture_copy_footprint_;
|
||||
UINT64 swap_texture_copy_size_;
|
||||
ID3D12DescriptorHeap* swap_texture_rtv_descriptor_heap_ = nullptr;
|
||||
D3D12_CPU_DESCRIPTOR_HANDLE swap_texture_rtv_;
|
||||
ID3D12DescriptorHeap* swap_texture_srv_descriptor_heap_ = nullptr;
|
||||
struct ApplyGammaConstants {
|
||||
uint32_t size[2];
|
||||
};
|
||||
enum class ApplyGammaRootParameter : UINT {
|
||||
kConstants,
|
||||
kDestination,
|
||||
kSource,
|
||||
kRamp,
|
||||
|
||||
kCount,
|
||||
};
|
||||
Microsoft::WRL::ComPtr<ID3D12RootSignature> apply_gamma_root_signature_;
|
||||
Microsoft::WRL::ComPtr<ID3D12PipelineState> apply_gamma_table_pipeline_;
|
||||
Microsoft::WRL::ComPtr<ID3D12PipelineState>
|
||||
apply_gamma_table_fxaa_luma_pipeline_;
|
||||
Microsoft::WRL::ComPtr<ID3D12PipelineState> apply_gamma_pwl_pipeline_;
|
||||
Microsoft::WRL::ComPtr<ID3D12PipelineState>
|
||||
apply_gamma_pwl_fxaa_luma_pipeline_;
|
||||
|
||||
struct FxaaConstants {
|
||||
uint32_t size[2];
|
||||
float size_inv[2];
|
||||
};
|
||||
enum class FxaaRootParameter : UINT {
|
||||
kConstants,
|
||||
kDestination,
|
||||
kSource,
|
||||
|
||||
kCount,
|
||||
};
|
||||
Microsoft::WRL::ComPtr<ID3D12RootSignature> fxaa_root_signature_;
|
||||
Microsoft::WRL::ComPtr<ID3D12PipelineState> fxaa_pipeline_;
|
||||
Microsoft::WRL::ComPtr<ID3D12PipelineState> fxaa_extreme_pipeline_;
|
||||
|
||||
// PWL gamma ramp can result in values with more precision than 10bpc. Though
|
||||
// those sub-10bpc bits don't have any noticeable visual effect, so normally
|
||||
// R10G10B10A2_UNORM is enough. But what's the most important is that for the
|
||||
// original FXAA shader, the luma needs to be written to the alpha channel.
|
||||
// For simplicity (to avoid modifying the FXAA shader and adding more texture
|
||||
// fetches into it), and for the highest quality (preserving all 13 bits that
|
||||
// may be generated by applying the PWL gamma ramp with an increment of 2^3,
|
||||
// and also leaving some space for the result of applying fractional weights
|
||||
// to calculate the luma), using R16G16B16A16_UNORM instead of
|
||||
// R10G10B10X2_UNORM with a separate alpha texture.
|
||||
static constexpr DXGI_FORMAT kFxaaSourceTextureFormat =
|
||||
DXGI_FORMAT_R16G16B16A16_UNORM;
|
||||
// Kept in NON_PIXEL_SHADER_RESOURCE state.
|
||||
Microsoft::WRL::ComPtr<ID3D12Resource> fxaa_source_texture_;
|
||||
uint64_t fxaa_source_texture_submission_ = 0;
|
||||
|
||||
// Unsubmitted barrier batch.
|
||||
std::vector<D3D12_RESOURCE_BARRIER> barriers_;
|
||||
|
||||
// <Resource, submission where requested>, sorted by the submission number.
|
||||
std::deque<std::pair<ID3D12Resource*, uint64_t>> buffers_for_deletion_;
|
||||
std::deque<std::pair<uint64_t, ID3D12Resource*>> resources_for_deletion_;
|
||||
|
||||
static constexpr uint32_t kScratchBufferSizeIncrement = 16 * 1024 * 1024;
|
||||
ID3D12Resource* scratch_buffer_ = nullptr;
|
||||
|
|
|
@ -22,13 +22,6 @@ namespace xe {
|
|||
namespace gpu {
|
||||
namespace d3d12 {
|
||||
|
||||
// Generated with `xb buildshaders`.
|
||||
namespace shaders {
|
||||
#include "xenia/gpu/shaders/bytecode/d3d12_5_1/fullscreen_tc_vs.h"
|
||||
#include "xenia/gpu/shaders/bytecode/d3d12_5_1/stretch_gamma_ps.h"
|
||||
#include "xenia/gpu/shaders/bytecode/d3d12_5_1/stretch_ps.h"
|
||||
} // namespace shaders
|
||||
|
||||
D3D12GraphicsSystem::D3D12GraphicsSystem() {}
|
||||
|
||||
D3D12GraphicsSystem::~D3D12GraphicsSystem() {}
|
||||
|
@ -48,198 +41,11 @@ std::string D3D12GraphicsSystem::name() const {
|
|||
|
||||
X_STATUS D3D12GraphicsSystem::Setup(cpu::Processor* processor,
|
||||
kernel::KernelState* kernel_state,
|
||||
ui::Window* target_window) {
|
||||
ui::WindowedAppContext* app_context,
|
||||
bool is_surface_required) {
|
||||
provider_ = xe::ui::d3d12::D3D12Provider::Create();
|
||||
auto d3d12_provider = static_cast<xe::ui::d3d12::D3D12Provider*>(provider());
|
||||
auto device = d3d12_provider->GetDevice();
|
||||
|
||||
auto result = GraphicsSystem::Setup(processor, kernel_state, target_window);
|
||||
if (result != X_STATUS_SUCCESS) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (target_window) {
|
||||
display_context_ = reinterpret_cast<xe::ui::d3d12::D3D12Context*>(
|
||||
target_window->context());
|
||||
}
|
||||
|
||||
// Create the stretch pipeline root signature, with 1 parameter (source
|
||||
// texture) for raw stretch and 3 parameters (source texture, gamma ramp LUT,
|
||||
// inverse of the size of the gamma ramp LUT) for gamma-correcting stretch.
|
||||
// Raw.
|
||||
D3D12_ROOT_PARAMETER stretch_root_parameters[3];
|
||||
stretch_root_parameters[0].ParameterType =
|
||||
D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE;
|
||||
stretch_root_parameters[0].DescriptorTable.NumDescriptorRanges = 1;
|
||||
D3D12_DESCRIPTOR_RANGE stretch_root_texture_range;
|
||||
stretch_root_texture_range.RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SRV;
|
||||
stretch_root_texture_range.NumDescriptors = 1;
|
||||
stretch_root_texture_range.BaseShaderRegister = 0;
|
||||
stretch_root_texture_range.RegisterSpace = 0;
|
||||
stretch_root_texture_range.OffsetInDescriptorsFromTableStart = 0;
|
||||
stretch_root_parameters[0].DescriptorTable.pDescriptorRanges =
|
||||
&stretch_root_texture_range;
|
||||
stretch_root_parameters[0].ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL;
|
||||
D3D12_STATIC_SAMPLER_DESC stretch_sampler_desc;
|
||||
stretch_sampler_desc.Filter = D3D12_FILTER_MIN_MAG_MIP_LINEAR;
|
||||
stretch_sampler_desc.AddressU = D3D12_TEXTURE_ADDRESS_MODE_CLAMP;
|
||||
stretch_sampler_desc.AddressV = D3D12_TEXTURE_ADDRESS_MODE_CLAMP;
|
||||
stretch_sampler_desc.AddressW = D3D12_TEXTURE_ADDRESS_MODE_CLAMP;
|
||||
stretch_sampler_desc.MipLODBias = 0.0f;
|
||||
stretch_sampler_desc.MaxAnisotropy = 1;
|
||||
stretch_sampler_desc.ComparisonFunc = D3D12_COMPARISON_FUNC_NEVER;
|
||||
stretch_sampler_desc.BorderColor = D3D12_STATIC_BORDER_COLOR_OPAQUE_BLACK;
|
||||
stretch_sampler_desc.MinLOD = 0.0f;
|
||||
stretch_sampler_desc.MaxLOD = 0.0f;
|
||||
stretch_sampler_desc.ShaderRegister = 0;
|
||||
stretch_sampler_desc.RegisterSpace = 0;
|
||||
stretch_sampler_desc.ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL;
|
||||
D3D12_ROOT_SIGNATURE_DESC stretch_root_desc;
|
||||
stretch_root_desc.NumParameters = 1;
|
||||
stretch_root_desc.pParameters = stretch_root_parameters;
|
||||
stretch_root_desc.NumStaticSamplers = 1;
|
||||
stretch_root_desc.pStaticSamplers = &stretch_sampler_desc;
|
||||
stretch_root_desc.Flags =
|
||||
D3D12_ROOT_SIGNATURE_FLAG_DENY_VERTEX_SHADER_ROOT_ACCESS;
|
||||
stretch_root_signature_ =
|
||||
ui::d3d12::util::CreateRootSignature(*d3d12_provider, stretch_root_desc);
|
||||
if (stretch_root_signature_ == nullptr) {
|
||||
XELOGE("Failed to create the front buffer stretch root signature");
|
||||
return X_STATUS_UNSUCCESSFUL;
|
||||
}
|
||||
// Gamma.
|
||||
stretch_root_parameters[1].ParameterType =
|
||||
D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE;
|
||||
stretch_root_parameters[1].DescriptorTable.NumDescriptorRanges = 1;
|
||||
D3D12_DESCRIPTOR_RANGE stretch_root_gamma_ramp_range;
|
||||
stretch_root_gamma_ramp_range.RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SRV;
|
||||
stretch_root_gamma_ramp_range.NumDescriptors = 1;
|
||||
stretch_root_gamma_ramp_range.BaseShaderRegister = 1;
|
||||
stretch_root_gamma_ramp_range.RegisterSpace = 0;
|
||||
stretch_root_gamma_ramp_range.OffsetInDescriptorsFromTableStart = 0;
|
||||
stretch_root_parameters[1].DescriptorTable.pDescriptorRanges =
|
||||
&stretch_root_gamma_ramp_range;
|
||||
stretch_root_parameters[1].ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL;
|
||||
stretch_root_parameters[2].ParameterType =
|
||||
D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS;
|
||||
stretch_root_parameters[2].Constants.ShaderRegister = 0;
|
||||
stretch_root_parameters[2].Constants.RegisterSpace = 0;
|
||||
stretch_root_parameters[2].Constants.Num32BitValues = 1;
|
||||
stretch_root_parameters[2].ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL;
|
||||
stretch_root_desc.NumParameters = 3;
|
||||
stretch_root_desc.pParameters = stretch_root_parameters;
|
||||
stretch_gamma_root_signature_ =
|
||||
ui::d3d12::util::CreateRootSignature(*d3d12_provider, stretch_root_desc);
|
||||
if (stretch_gamma_root_signature_ == nullptr) {
|
||||
XELOGE(
|
||||
"Failed to create the gamma-correcting front buffer stretch root "
|
||||
"signature");
|
||||
stretch_root_signature_->Release();
|
||||
stretch_root_signature_ = nullptr;
|
||||
return X_STATUS_UNSUCCESSFUL;
|
||||
}
|
||||
|
||||
// Create the stretch pipelines.
|
||||
D3D12_GRAPHICS_PIPELINE_STATE_DESC stretch_pipeline_desc = {};
|
||||
stretch_pipeline_desc.pRootSignature = stretch_root_signature_;
|
||||
stretch_pipeline_desc.VS.pShaderBytecode = shaders::fullscreen_tc_vs;
|
||||
stretch_pipeline_desc.VS.BytecodeLength = sizeof(shaders::fullscreen_tc_vs);
|
||||
stretch_pipeline_desc.PS.pShaderBytecode = shaders::stretch_ps;
|
||||
stretch_pipeline_desc.PS.BytecodeLength = sizeof(shaders::stretch_ps);
|
||||
// The shader will set alpha to 1, don't use output-merger to preserve it.
|
||||
stretch_pipeline_desc.BlendState.RenderTarget[0].RenderTargetWriteMask =
|
||||
D3D12_COLOR_WRITE_ENABLE_ALL;
|
||||
stretch_pipeline_desc.SampleMask = UINT_MAX;
|
||||
stretch_pipeline_desc.RasterizerState.FillMode = D3D12_FILL_MODE_SOLID;
|
||||
stretch_pipeline_desc.RasterizerState.CullMode = D3D12_CULL_MODE_NONE;
|
||||
stretch_pipeline_desc.RasterizerState.DepthClipEnable = TRUE;
|
||||
stretch_pipeline_desc.PrimitiveTopologyType =
|
||||
D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
|
||||
stretch_pipeline_desc.NumRenderTargets = 1;
|
||||
stretch_pipeline_desc.RTVFormats[0] =
|
||||
ui::d3d12::D3D12Context::kSwapChainFormat;
|
||||
stretch_pipeline_desc.SampleDesc.Count = 1;
|
||||
if (FAILED(device->CreateGraphicsPipelineState(
|
||||
&stretch_pipeline_desc, IID_PPV_ARGS(&stretch_pipeline_)))) {
|
||||
XELOGE("Failed to create the front buffer stretch pipeline");
|
||||
stretch_gamma_root_signature_->Release();
|
||||
stretch_gamma_root_signature_ = nullptr;
|
||||
stretch_root_signature_->Release();
|
||||
stretch_root_signature_ = nullptr;
|
||||
return X_STATUS_UNSUCCESSFUL;
|
||||
}
|
||||
stretch_pipeline_desc.pRootSignature = stretch_gamma_root_signature_;
|
||||
stretch_pipeline_desc.PS.pShaderBytecode = shaders::stretch_gamma_ps;
|
||||
stretch_pipeline_desc.PS.BytecodeLength = sizeof(shaders::stretch_gamma_ps);
|
||||
if (FAILED(device->CreateGraphicsPipelineState(
|
||||
&stretch_pipeline_desc, IID_PPV_ARGS(&stretch_gamma_pipeline_)))) {
|
||||
XELOGE(
|
||||
"Failed to create the gamma-correcting front buffer stretch pipeline");
|
||||
stretch_pipeline_->Release();
|
||||
stretch_pipeline_ = nullptr;
|
||||
stretch_gamma_root_signature_->Release();
|
||||
stretch_gamma_root_signature_ = nullptr;
|
||||
stretch_root_signature_->Release();
|
||||
stretch_root_signature_ = nullptr;
|
||||
return X_STATUS_UNSUCCESSFUL;
|
||||
}
|
||||
|
||||
return X_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
void D3D12GraphicsSystem::Shutdown() {
|
||||
ui::d3d12::util::ReleaseAndNull(stretch_gamma_pipeline_);
|
||||
ui::d3d12::util::ReleaseAndNull(stretch_pipeline_);
|
||||
ui::d3d12::util::ReleaseAndNull(stretch_gamma_root_signature_);
|
||||
ui::d3d12::util::ReleaseAndNull(stretch_root_signature_);
|
||||
|
||||
GraphicsSystem::Shutdown();
|
||||
}
|
||||
|
||||
std::unique_ptr<xe::ui::RawImage> D3D12GraphicsSystem::Capture() {
|
||||
auto d3d12_command_processor =
|
||||
static_cast<D3D12CommandProcessor*>(command_processor());
|
||||
if (!d3d12_command_processor) {
|
||||
return nullptr;
|
||||
}
|
||||
return d3d12_command_processor->Capture();
|
||||
}
|
||||
|
||||
void D3D12GraphicsSystem::StretchTextureToFrontBuffer(
|
||||
D3D12_GPU_DESCRIPTOR_HANDLE handle,
|
||||
D3D12_GPU_DESCRIPTOR_HANDLE* gamma_ramp_handle, float gamma_ramp_inv_size,
|
||||
ID3D12GraphicsCommandList* command_list) {
|
||||
if (gamma_ramp_handle != nullptr) {
|
||||
command_list->SetPipelineState(stretch_gamma_pipeline_);
|
||||
command_list->SetGraphicsRootSignature(stretch_gamma_root_signature_);
|
||||
command_list->SetGraphicsRootDescriptorTable(1, *gamma_ramp_handle);
|
||||
command_list->SetGraphicsRoot32BitConstants(2, 1, &gamma_ramp_inv_size, 0);
|
||||
} else {
|
||||
command_list->SetPipelineState(stretch_pipeline_);
|
||||
command_list->SetGraphicsRootSignature(stretch_root_signature_);
|
||||
}
|
||||
command_list->SetGraphicsRootDescriptorTable(0, handle);
|
||||
command_list->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
|
||||
command_list->DrawInstanced(3, 1, 0, 0);
|
||||
}
|
||||
|
||||
void D3D12GraphicsSystem::StretchTextureToFrontBuffer(
|
||||
D3D12_GPU_DESCRIPTOR_HANDLE handle,
|
||||
D3D12_GPU_DESCRIPTOR_HANDLE* gamma_ramp_handle, float gamma_ramp_inv_size,
|
||||
DeferredCommandList& command_list) {
|
||||
if (gamma_ramp_handle != nullptr) {
|
||||
command_list.D3DSetPipelineState(stretch_gamma_pipeline_);
|
||||
command_list.D3DSetGraphicsRootSignature(stretch_gamma_root_signature_);
|
||||
command_list.D3DSetGraphicsRootDescriptorTable(1, *gamma_ramp_handle);
|
||||
command_list.D3DSetGraphicsRoot32BitConstants(2, 1, &gamma_ramp_inv_size,
|
||||
0);
|
||||
} else {
|
||||
command_list.D3DSetPipelineState(stretch_pipeline_);
|
||||
command_list.D3DSetGraphicsRootSignature(stretch_root_signature_);
|
||||
}
|
||||
command_list.D3DSetGraphicsRootDescriptorTable(0, handle);
|
||||
command_list.D3DIASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
|
||||
command_list.D3DDrawInstanced(3, 1, 0, 0);
|
||||
return GraphicsSystem::Setup(processor, kernel_state, app_context,
|
||||
is_surface_required);
|
||||
}
|
||||
|
||||
std::unique_ptr<CommandProcessor>
|
||||
|
@ -248,69 +54,6 @@ D3D12GraphicsSystem::CreateCommandProcessor() {
|
|||
new D3D12CommandProcessor(this, kernel_state_));
|
||||
}
|
||||
|
||||
void D3D12GraphicsSystem::Swap(xe::ui::UIEvent* e) {
|
||||
if (display_context_->WasLost()) {
|
||||
// We're crashing. Cheese it.
|
||||
return;
|
||||
}
|
||||
|
||||
if (!command_processor_) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& swap_state = command_processor_->swap_state();
|
||||
ID3D12DescriptorHeap* swap_srv_heap;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(swap_state.mutex);
|
||||
swap_state.pending = false;
|
||||
swap_srv_heap = reinterpret_cast<ID3D12DescriptorHeap*>(
|
||||
swap_state.front_buffer_texture);
|
||||
}
|
||||
if (swap_srv_heap == nullptr) {
|
||||
// Not ready yet.
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t window_width, window_height;
|
||||
display_context_->GetSwapChainSize(window_width, window_height);
|
||||
|
||||
int32_t target_x, target_y;
|
||||
uint32_t target_width, target_height;
|
||||
draw_util::GetPresentArea(swap_state.width, swap_state.height, window_width,
|
||||
window_height, target_x, target_y, target_width,
|
||||
target_height);
|
||||
// For safety.
|
||||
target_x = clamp(target_x, int32_t(D3D12_VIEWPORT_BOUNDS_MIN),
|
||||
int32_t(D3D12_VIEWPORT_BOUNDS_MAX));
|
||||
target_y = clamp(target_y, int32_t(D3D12_VIEWPORT_BOUNDS_MIN),
|
||||
int32_t(D3D12_VIEWPORT_BOUNDS_MAX));
|
||||
target_width = std::min(
|
||||
target_width, uint32_t(int32_t(D3D12_VIEWPORT_BOUNDS_MAX) - target_x));
|
||||
target_height = std::min(
|
||||
target_height, uint32_t(int32_t(D3D12_VIEWPORT_BOUNDS_MAX) - target_y));
|
||||
|
||||
auto command_list = display_context_->GetSwapCommandList();
|
||||
// Assuming the window has already been cleared to the needed letterbox color.
|
||||
D3D12_VIEWPORT viewport;
|
||||
viewport.TopLeftX = float(target_x);
|
||||
viewport.TopLeftY = float(target_y);
|
||||
viewport.Width = float(target_width);
|
||||
viewport.Height = float(target_height);
|
||||
viewport.MinDepth = 0.0f;
|
||||
viewport.MaxDepth = 0.0f;
|
||||
command_list->RSSetViewports(1, &viewport);
|
||||
D3D12_RECT scissor;
|
||||
scissor.left = 0;
|
||||
scissor.top = 0;
|
||||
scissor.right = window_width;
|
||||
scissor.bottom = window_height;
|
||||
command_list->RSSetScissorRects(1, &scissor);
|
||||
command_list->SetDescriptorHeaps(1, &swap_srv_heap);
|
||||
StretchTextureToFrontBuffer(
|
||||
swap_srv_heap->GetGPUDescriptorHandleForHeapStart(), nullptr, 0.0f,
|
||||
command_list);
|
||||
}
|
||||
|
||||
} // namespace d3d12
|
||||
} // namespace gpu
|
||||
} // namespace xe
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
#include "xenia/gpu/command_processor.h"
|
||||
#include "xenia/gpu/d3d12/deferred_command_list.h"
|
||||
#include "xenia/gpu/graphics_system.h"
|
||||
#include "xenia/ui/d3d12/d3d12_context.h"
|
||||
|
||||
namespace xe {
|
||||
namespace gpu {
|
||||
|
@ -31,37 +30,11 @@ class D3D12GraphicsSystem : public GraphicsSystem {
|
|||
std::string name() const override;
|
||||
|
||||
X_STATUS Setup(cpu::Processor* processor, kernel::KernelState* kernel_state,
|
||||
ui::Window* target_window) override;
|
||||
void Shutdown() override;
|
||||
|
||||
std::unique_ptr<xe::ui::RawImage> Capture() override;
|
||||
|
||||
// Draws a texture covering the entire viewport to the render target currently
|
||||
// bound on the specified command list (in D3D12Context::kSwapChainFormat).
|
||||
// This changes the current pipeline, graphics root signature and primitive
|
||||
// topology. The gamma ramp texture must be 1D if present at all, for linear
|
||||
// space, pass nullptr as the gamma ramp.
|
||||
void StretchTextureToFrontBuffer(
|
||||
D3D12_GPU_DESCRIPTOR_HANDLE handle,
|
||||
D3D12_GPU_DESCRIPTOR_HANDLE* gamma_ramp_handle, float gamma_ramp_inv_size,
|
||||
ID3D12GraphicsCommandList* command_list);
|
||||
void StretchTextureToFrontBuffer(
|
||||
D3D12_GPU_DESCRIPTOR_HANDLE handle,
|
||||
D3D12_GPU_DESCRIPTOR_HANDLE* gamma_ramp_handle, float gamma_ramp_inv_size,
|
||||
DeferredCommandList& command_list);
|
||||
ui::WindowedAppContext* app_context,
|
||||
bool is_surface_required) override;
|
||||
|
||||
protected:
|
||||
std::unique_ptr<CommandProcessor> CreateCommandProcessor() override;
|
||||
|
||||
void Swap(xe::ui::UIEvent* e) override;
|
||||
|
||||
private:
|
||||
ui::d3d12::D3D12Context* display_context_ = nullptr;
|
||||
|
||||
ID3D12RootSignature* stretch_root_signature_ = nullptr;
|
||||
ID3D12RootSignature* stretch_gamma_root_signature_ = nullptr;
|
||||
ID3D12PipelineState* stretch_pipeline_ = nullptr;
|
||||
ID3D12PipelineState* stretch_gamma_pipeline_ = nullptr;
|
||||
};
|
||||
|
||||
} // namespace d3d12
|
||||
|
|
|
@ -33,7 +33,7 @@ bool D3D12PrimitiveProcessor::Initialize() {
|
|||
return false;
|
||||
}
|
||||
frame_index_buffer_pool_ = std::make_unique<ui::d3d12::D3D12UploadBufferPool>(
|
||||
command_processor_.GetD3D12Context().GetD3D12Provider(),
|
||||
command_processor_.GetD3D12Provider(),
|
||||
std::max(size_t(kMinRequiredConvertedIndexBufferSize),
|
||||
ui::GraphicsUploadBufferPool::kDefaultPageSize));
|
||||
return true;
|
||||
|
@ -90,7 +90,7 @@ bool D3D12PrimitiveProcessor::InitializeBuiltin16BitIndexBuffer(
|
|||
assert_null(builtin_index_buffer_upload_);
|
||||
|
||||
const ui::d3d12::D3D12Provider& provider =
|
||||
command_processor_.GetD3D12Context().GetD3D12Provider();
|
||||
command_processor_.GetD3D12Provider();
|
||||
ID3D12Device* device = provider.GetDevice();
|
||||
|
||||
D3D12_RESOURCE_DESC resource_desc;
|
||||
|
|
|
@ -215,7 +215,7 @@ D3D12RenderTargetCache::~D3D12RenderTargetCache() { Shutdown(true); }
|
|||
|
||||
bool D3D12RenderTargetCache::Initialize() {
|
||||
const ui::d3d12::D3D12Provider& provider =
|
||||
command_processor_.GetD3D12Context().GetD3D12Provider();
|
||||
command_processor_.GetD3D12Provider();
|
||||
ID3D12Device* device = provider.GetDevice();
|
||||
|
||||
if (cvars::render_target_path_d3d12 == "rtv") {
|
||||
|
@ -1298,7 +1298,7 @@ bool D3D12RenderTargetCache::Update(bool is_rasterization_done,
|
|||
void D3D12RenderTargetCache::WriteEdramRawSRVDescriptor(
|
||||
D3D12_CPU_DESCRIPTOR_HANDLE handle) {
|
||||
const ui::d3d12::D3D12Provider& provider =
|
||||
command_processor_.GetD3D12Context().GetD3D12Provider();
|
||||
command_processor_.GetD3D12Provider();
|
||||
ID3D12Device* device = provider.GetDevice();
|
||||
device->CopyDescriptorsSimple(
|
||||
1, handle,
|
||||
|
@ -1311,7 +1311,7 @@ void D3D12RenderTargetCache::WriteEdramRawSRVDescriptor(
|
|||
void D3D12RenderTargetCache::WriteEdramRawUAVDescriptor(
|
||||
D3D12_CPU_DESCRIPTOR_HANDLE handle) {
|
||||
const ui::d3d12::D3D12Provider& provider =
|
||||
command_processor_.GetD3D12Context().GetD3D12Provider();
|
||||
command_processor_.GetD3D12Provider();
|
||||
ID3D12Device* device = provider.GetDevice();
|
||||
device->CopyDescriptorsSimple(
|
||||
1, handle,
|
||||
|
@ -1339,7 +1339,7 @@ void D3D12RenderTargetCache::WriteEdramUintPow2SRVDescriptor(
|
|||
return;
|
||||
}
|
||||
const ui::d3d12::D3D12Provider& provider =
|
||||
command_processor_.GetD3D12Context().GetD3D12Provider();
|
||||
command_processor_.GetD3D12Provider();
|
||||
ID3D12Device* device = provider.GetDevice();
|
||||
device->CopyDescriptorsSimple(
|
||||
1, handle,
|
||||
|
@ -1366,7 +1366,7 @@ void D3D12RenderTargetCache::WriteEdramUintPow2UAVDescriptor(
|
|||
return;
|
||||
}
|
||||
const ui::d3d12::D3D12Provider& provider =
|
||||
command_processor_.GetD3D12Context().GetD3D12Provider();
|
||||
command_processor_.GetD3D12Provider();
|
||||
ID3D12Device* device = provider.GetDevice();
|
||||
device->CopyDescriptorsSimple(
|
||||
1, handle,
|
||||
|
@ -1668,8 +1668,9 @@ bool D3D12RenderTargetCache::InitializeTraceSubmitDownloads() {
|
|||
ui::d3d12::util::FillBufferResourceDesc(edram_snapshot_download_buffer_desc,
|
||||
xenos::kEdramSizeBytes,
|
||||
D3D12_RESOURCE_FLAG_NONE);
|
||||
auto& provider = command_processor_.GetD3D12Context().GetD3D12Provider();
|
||||
auto device = provider.GetDevice();
|
||||
const ui::d3d12::D3D12Provider& provider =
|
||||
command_processor_.GetD3D12Provider();
|
||||
ID3D12Device* device = provider.GetDevice();
|
||||
if (FAILED(device->CreateCommittedResource(
|
||||
&ui::d3d12::util::kHeapPropertiesReadback,
|
||||
provider.GetHeapFlagCreateNotZeroed(),
|
||||
|
@ -1721,7 +1722,8 @@ void D3D12RenderTargetCache::RestoreEdramSnapshot(const void* snapshot) {
|
|||
|
||||
// Create the buffer - will be used for copying to either a 32-bit 1280x2048
|
||||
// render target or the EDRAM buffer.
|
||||
auto& provider = command_processor_.GetD3D12Context().GetD3D12Provider();
|
||||
const ui::d3d12::D3D12Provider& provider =
|
||||
command_processor_.GetD3D12Provider();
|
||||
if (!edram_snapshot_restore_pool_) {
|
||||
edram_snapshot_restore_pool_ =
|
||||
std::make_unique<ui::d3d12::D3D12UploadBufferPool>(
|
||||
|
@ -1966,8 +1968,7 @@ DXGI_FORMAT D3D12RenderTargetCache::GetDepthSRVStencilDXGIFormat(
|
|||
|
||||
RenderTargetCache::RenderTarget* D3D12RenderTargetCache::CreateRenderTarget(
|
||||
RenderTargetKey key) {
|
||||
ID3D12Device* device =
|
||||
command_processor_.GetD3D12Context().GetD3D12Provider().GetDevice();
|
||||
ID3D12Device* device = command_processor_.GetD3D12Provider().GetDevice();
|
||||
|
||||
D3D12_RESOURCE_DESC resource_desc;
|
||||
resource_desc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
|
||||
|
@ -4345,8 +4346,7 @@ D3D12RenderTargetCache::GetOrCreateTransferPipelines(TransferShaderKey key) {
|
|||
// ***************************************************************************
|
||||
|
||||
ID3D12PipelineState* const* pipelines;
|
||||
ID3D12Device* device =
|
||||
command_processor_.GetD3D12Context().GetD3D12Provider().GetDevice();
|
||||
ID3D12Device* device = command_processor_.GetD3D12Provider().GetDevice();
|
||||
D3D12_INPUT_ELEMENT_DESC pipeline_input_element_desc;
|
||||
pipeline_input_element_desc.SemanticName = "POSITION";
|
||||
pipeline_input_element_desc.SemanticIndex = 0;
|
||||
|
@ -4516,7 +4516,7 @@ void D3D12RenderTargetCache::PerformTransfersAndResolveClears(
|
|||
assert_true(GetPath() == Path::kHostRenderTargets);
|
||||
|
||||
const ui::d3d12::D3D12Provider& provider =
|
||||
command_processor_.GetD3D12Context().GetD3D12Provider();
|
||||
command_processor_.GetD3D12Provider();
|
||||
ID3D12Device* device = provider.GetDevice();
|
||||
uint64_t current_submission = command_processor_.GetCurrentSubmission();
|
||||
DeferredCommandList& command_list =
|
||||
|
@ -6476,8 +6476,8 @@ ID3D12PipelineState* D3D12RenderTargetCache::GetOrCreateDumpPipeline(
|
|||
// Pipeline
|
||||
// ***************************************************************************
|
||||
ID3D12PipelineState* pipeline = ui::d3d12::util::CreateComputePipeline(
|
||||
command_processor_.GetD3D12Context().GetD3D12Provider().GetDevice(),
|
||||
built_shader_.data(), built_shader_size_bytes,
|
||||
command_processor_.GetD3D12Provider().GetDevice(), built_shader_.data(),
|
||||
built_shader_size_bytes,
|
||||
key.is_depth ? dump_root_signature_depth_ : dump_root_signature_color_);
|
||||
const char* format_name =
|
||||
key.is_depth
|
||||
|
@ -6561,7 +6561,7 @@ void D3D12RenderTargetCache::DumpRenderTargets(uint32_t dump_base,
|
|||
// 32bpp and 64bpp.
|
||||
size_t edram_uav_indices[2] = {SIZE_MAX, SIZE_MAX};
|
||||
const ui::d3d12::D3D12Provider& provider =
|
||||
command_processor_.GetD3D12Context().GetD3D12Provider();
|
||||
command_processor_.GetD3D12Provider();
|
||||
if (!bindless_resources_used_) {
|
||||
if (any_sources_32bpp_64bpp[0]) {
|
||||
edram_uav_indices[0] = current_temporary_descriptors_cpu_.size();
|
||||
|
|
|
@ -43,7 +43,7 @@ bool D3D12SharedMemory::Initialize() {
|
|||
InitializeCommon();
|
||||
|
||||
const ui::d3d12::D3D12Provider& provider =
|
||||
command_processor_.GetD3D12Context().GetD3D12Provider();
|
||||
command_processor_.GetD3D12Provider();
|
||||
ID3D12Device* device = provider.GetDevice();
|
||||
|
||||
D3D12_RESOURCE_DESC buffer_desc;
|
||||
|
@ -215,8 +215,9 @@ void D3D12SharedMemory::CommitUAVWritesAndTransitionBuffer(
|
|||
|
||||
void D3D12SharedMemory::WriteRawSRVDescriptor(
|
||||
D3D12_CPU_DESCRIPTOR_HANDLE handle) {
|
||||
auto& provider = command_processor_.GetD3D12Context().GetD3D12Provider();
|
||||
auto device = provider.GetDevice();
|
||||
const ui::d3d12::D3D12Provider& provider =
|
||||
command_processor_.GetD3D12Provider();
|
||||
ID3D12Device* device = provider.GetDevice();
|
||||
device->CopyDescriptorsSimple(
|
||||
1, handle,
|
||||
provider.OffsetViewDescriptor(buffer_descriptor_heap_start_,
|
||||
|
@ -226,8 +227,9 @@ void D3D12SharedMemory::WriteRawSRVDescriptor(
|
|||
|
||||
void D3D12SharedMemory::WriteRawUAVDescriptor(
|
||||
D3D12_CPU_DESCRIPTOR_HANDLE handle) {
|
||||
auto& provider = command_processor_.GetD3D12Context().GetD3D12Provider();
|
||||
auto device = provider.GetDevice();
|
||||
const ui::d3d12::D3D12Provider& provider =
|
||||
command_processor_.GetD3D12Provider();
|
||||
ID3D12Device* device = provider.GetDevice();
|
||||
device->CopyDescriptorsSimple(
|
||||
1, handle,
|
||||
provider.OffsetViewDescriptor(buffer_descriptor_heap_start_,
|
||||
|
@ -252,8 +254,9 @@ void D3D12SharedMemory::WriteUintPow2SRVDescriptor(
|
|||
assert_unhandled_case(element_size_bytes_pow2);
|
||||
return;
|
||||
}
|
||||
auto& provider = command_processor_.GetD3D12Context().GetD3D12Provider();
|
||||
auto device = provider.GetDevice();
|
||||
const ui::d3d12::D3D12Provider& provider =
|
||||
command_processor_.GetD3D12Provider();
|
||||
ID3D12Device* device = provider.GetDevice();
|
||||
device->CopyDescriptorsSimple(
|
||||
1, handle,
|
||||
provider.OffsetViewDescriptor(buffer_descriptor_heap_start_,
|
||||
|
@ -278,8 +281,9 @@ void D3D12SharedMemory::WriteUintPow2UAVDescriptor(
|
|||
assert_unhandled_case(element_size_bytes_pow2);
|
||||
return;
|
||||
}
|
||||
auto& provider = command_processor_.GetD3D12Context().GetD3D12Provider();
|
||||
auto device = provider.GetDevice();
|
||||
const ui::d3d12::D3D12Provider& provider =
|
||||
command_processor_.GetD3D12Provider();
|
||||
ID3D12Device* device = provider.GetDevice();
|
||||
device->CopyDescriptorsSimple(
|
||||
1, handle,
|
||||
provider.OffsetViewDescriptor(buffer_descriptor_heap_start_,
|
||||
|
@ -298,8 +302,9 @@ bool D3D12SharedMemory::InitializeTraceSubmitDownloads() {
|
|||
ui::d3d12::util::FillBufferResourceDesc(
|
||||
download_buffer_desc, download_page_count << page_size_log2(),
|
||||
D3D12_RESOURCE_FLAG_NONE);
|
||||
auto& provider = command_processor_.GetD3D12Context().GetD3D12Provider();
|
||||
auto device = provider.GetDevice();
|
||||
const ui::d3d12::D3D12Provider& provider =
|
||||
command_processor_.GetD3D12Provider();
|
||||
ID3D12Device* device = provider.GetDevice();
|
||||
if (FAILED(device->CreateCommittedResource(
|
||||
&ui::d3d12::util::kHeapPropertiesReadback,
|
||||
provider.GetHeapFlagCreateNotZeroed(), &download_buffer_desc,
|
||||
|
@ -365,7 +370,7 @@ bool D3D12SharedMemory::AllocateSparseHostGpuMemoryRange(
|
|||
<< host_gpu_memory_sparse_granularity_log2();
|
||||
|
||||
const ui::d3d12::D3D12Provider& provider =
|
||||
command_processor_.GetD3D12Context().GetD3D12Provider();
|
||||
command_processor_.GetD3D12Provider();
|
||||
ID3D12Device* device = provider.GetDevice();
|
||||
ID3D12CommandQueue* direct_queue = provider.GetDirectQueue();
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#include <vector>
|
||||
|
||||
#include "xenia/base/assert.h"
|
||||
#include "xenia/base/literals.h"
|
||||
#include "xenia/base/math.h"
|
||||
#include "xenia/ui/d3d12/d3d12_api.h"
|
||||
|
||||
|
@ -23,12 +24,14 @@ namespace xe {
|
|||
namespace gpu {
|
||||
namespace d3d12 {
|
||||
|
||||
using namespace xe::literals;
|
||||
|
||||
class D3D12CommandProcessor;
|
||||
|
||||
class DeferredCommandList {
|
||||
public:
|
||||
DeferredCommandList(const D3D12CommandProcessor& command_processor,
|
||||
size_t initial_size_bytes = 1024 * 1024);
|
||||
size_t initial_size_bytes = 1_MiB);
|
||||
|
||||
void Reset();
|
||||
void Execute(ID3D12GraphicsCommandList* command_list,
|
||||
|
|
|
@ -87,7 +87,8 @@ PipelineCache::PipelineCache(D3D12CommandProcessor& command_processor,
|
|||
register_file_(register_file),
|
||||
render_target_cache_(render_target_cache),
|
||||
bindless_resources_used_(bindless_resources_used) {
|
||||
auto& provider = command_processor_.GetD3D12Context().GetD3D12Provider();
|
||||
const ui::d3d12::D3D12Provider& provider =
|
||||
command_processor_.GetD3D12Provider();
|
||||
|
||||
bool edram_rov_used = render_target_cache.GetPath() ==
|
||||
RenderTargetCache::Path::kPixelShaderInterlock;
|
||||
|
@ -109,7 +110,8 @@ PipelineCache::PipelineCache(D3D12CommandProcessor& command_processor,
|
|||
PipelineCache::~PipelineCache() { Shutdown(); }
|
||||
|
||||
bool PipelineCache::Initialize() {
|
||||
auto& provider = command_processor_.GetD3D12Context().GetD3D12Provider();
|
||||
const ui::d3d12::D3D12Provider& provider =
|
||||
command_processor_.GetD3D12Provider();
|
||||
|
||||
// Initialize the command processor thread DXIL objects.
|
||||
dxbc_converter_ = nullptr;
|
||||
|
@ -414,7 +416,8 @@ void PipelineCache::InitializeShaderStorage(
|
|||
std::mutex shaders_failed_to_translate_mutex;
|
||||
std::vector<D3D12Shader::D3D12Translation*> shaders_failed_to_translate;
|
||||
auto shader_translation_thread_function = [&]() {
|
||||
auto& provider = command_processor_.GetD3D12Context().GetD3D12Provider();
|
||||
const ui::d3d12::D3D12Provider& provider =
|
||||
command_processor_.GetD3D12Provider();
|
||||
StringBuffer ucode_disasm_buffer;
|
||||
DxbcShaderTranslator translator(
|
||||
provider.GetAdapterVendorID(), bindless_resources_used_,
|
||||
|
@ -700,37 +703,39 @@ void PipelineCache::InitializeShaderStorage(
|
|||
++pipelines_created;
|
||||
}
|
||||
|
||||
CreateQueuedPipelinesOnProcessorThread();
|
||||
if (creation_threads_.size() > creation_thread_original_count) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(creation_request_lock_);
|
||||
creation_threads_shutdown_from_ = creation_thread_original_count;
|
||||
// Assuming the queue is empty because of
|
||||
// CreateQueuedPipelinesOnProcessorThread.
|
||||
}
|
||||
creation_request_cond_.notify_all();
|
||||
while (creation_threads_.size() > creation_thread_original_count) {
|
||||
xe::threading::Wait(creation_threads_.back().get(), false);
|
||||
creation_threads_.pop_back();
|
||||
}
|
||||
bool await_creation_completion_event;
|
||||
{
|
||||
// Cleanup so additional threads can be created later again.
|
||||
std::lock_guard<std::mutex> lock(creation_request_lock_);
|
||||
creation_threads_shutdown_from_ = SIZE_MAX;
|
||||
// If the invocation is blocking, all the shader storage initialization
|
||||
// is expected to be done before proceeding, to avoid latency in the
|
||||
// command processor after the invocation.
|
||||
await_creation_completion_event =
|
||||
blocking && creation_threads_busy_ != 0;
|
||||
if (await_creation_completion_event) {
|
||||
creation_completion_event_->Reset();
|
||||
creation_completion_set_event_ = true;
|
||||
if (!creation_threads_.empty()) {
|
||||
CreateQueuedPipelinesOnProcessorThread();
|
||||
if (creation_threads_.size() > creation_thread_original_count) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(creation_request_lock_);
|
||||
creation_threads_shutdown_from_ = creation_thread_original_count;
|
||||
// Assuming the queue is empty because of
|
||||
// CreateQueuedPipelinesOnProcessorThread.
|
||||
}
|
||||
creation_request_cond_.notify_all();
|
||||
while (creation_threads_.size() > creation_thread_original_count) {
|
||||
xe::threading::Wait(creation_threads_.back().get(), false);
|
||||
creation_threads_.pop_back();
|
||||
}
|
||||
bool await_creation_completion_event;
|
||||
{
|
||||
// Cleanup so additional threads can be created later again.
|
||||
std::lock_guard<std::mutex> lock(creation_request_lock_);
|
||||
creation_threads_shutdown_from_ = SIZE_MAX;
|
||||
// If the invocation is blocking, all the shader storage
|
||||
// initialization is expected to be done before proceeding, to avoid
|
||||
// latency in the command processor after the invocation.
|
||||
await_creation_completion_event =
|
||||
blocking && creation_threads_busy_ != 0;
|
||||
if (await_creation_completion_event) {
|
||||
creation_completion_event_->Reset();
|
||||
creation_completion_set_event_ = true;
|
||||
}
|
||||
}
|
||||
if (await_creation_completion_event) {
|
||||
creation_request_cond_.notify_one();
|
||||
xe::threading::Wait(creation_completion_event_.get(), false);
|
||||
}
|
||||
}
|
||||
if (await_creation_completion_event) {
|
||||
creation_request_cond_.notify_one();
|
||||
xe::threading::Wait(creation_completion_event_.get(), false);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1241,7 +1246,8 @@ bool PipelineCache::TranslateAnalyzedShader(
|
|||
}
|
||||
|
||||
// Disassemble the shader for dumping.
|
||||
auto& provider = command_processor_.GetD3D12Context().GetD3D12Provider();
|
||||
const ui::d3d12::D3D12Provider& provider =
|
||||
command_processor_.GetD3D12Provider();
|
||||
if (cvars::d3d12_dxbc_disasm_dxilconv) {
|
||||
translation.DisassembleDxbcAndDxil(provider, cvars::d3d12_dxbc_disasm,
|
||||
dxbc_converter, dxc_utils, dxc_compiler);
|
||||
|
@ -2052,8 +2058,7 @@ ID3D12PipelineState* PipelineCache::CreateD3D12Pipeline(
|
|||
}
|
||||
|
||||
// Create the D3D12 pipeline state object.
|
||||
auto device =
|
||||
command_processor_.GetD3D12Context().GetD3D12Provider().GetDevice();
|
||||
ID3D12Device* device = command_processor_.GetD3D12Provider().GetDevice();
|
||||
ID3D12PipelineState* state;
|
||||
if (FAILED(device->CreateGraphicsPipelineState(&state_desc,
|
||||
IID_PPV_ARGS(&state)))) {
|
||||
|
|
|
@ -869,8 +869,9 @@ TextureCache::TextureCache(D3D12CommandProcessor& command_processor,
|
|||
TextureCache::~TextureCache() { Shutdown(); }
|
||||
|
||||
bool TextureCache::Initialize() {
|
||||
auto& provider = command_processor_.GetD3D12Context().GetD3D12Provider();
|
||||
auto device = provider.GetDevice();
|
||||
const ui::d3d12::D3D12Provider& provider =
|
||||
command_processor_.GetD3D12Provider();
|
||||
ID3D12Device* device = provider.GetDevice();
|
||||
|
||||
if (IsDrawResolutionScaled()) {
|
||||
// Buffers not used yet - no need aliasing barriers to change ownership of
|
||||
|
@ -1444,7 +1445,8 @@ void TextureCache::WriteActiveTextureBindfulSRV(
|
|||
}
|
||||
}
|
||||
}
|
||||
auto& provider = command_processor_.GetD3D12Context().GetD3D12Provider();
|
||||
const ui::d3d12::D3D12Provider& provider =
|
||||
command_processor_.GetD3D12Provider();
|
||||
D3D12_CPU_DESCRIPTOR_HANDLE source_handle;
|
||||
if (descriptor_index != UINT32_MAX) {
|
||||
assert_not_null(texture);
|
||||
|
@ -1622,8 +1624,7 @@ void TextureCache::WriteSampler(SamplerParameters parameters,
|
|||
desc.MinLOD = float(parameters.mip_min_level);
|
||||
// Maximum mip level is in the texture resource itself.
|
||||
desc.MaxLOD = FLT_MAX;
|
||||
auto device =
|
||||
command_processor_.GetD3D12Context().GetD3D12Provider().GetDevice();
|
||||
ID3D12Device* device = command_processor_.GetD3D12Provider().GetDevice();
|
||||
device->CreateSampler(&desc, handle);
|
||||
}
|
||||
|
||||
|
@ -1712,8 +1713,9 @@ bool TextureCache::EnsureScaledResolveMemoryCommitted(
|
|||
uint64_t last_scaled = uint64_t(start_unscaled + (length_unscaled - 1)) *
|
||||
draw_resolution_scale_area;
|
||||
|
||||
auto& provider = command_processor_.GetD3D12Context().GetD3D12Provider();
|
||||
auto device = provider.GetDevice();
|
||||
const ui::d3d12::D3D12Provider& provider =
|
||||
command_processor_.GetD3D12Provider();
|
||||
ID3D12Device* device = provider.GetDevice();
|
||||
|
||||
// Ensure GPU virtual memory for buffers that may be used to access the range
|
||||
// is allocated - buffers are created. Always creating both buffers for all
|
||||
|
@ -1943,8 +1945,8 @@ void TextureCache::CreateCurrentScaledResolveRangeUintPow2SRV(
|
|||
scaled_resolve_2gb_buffers_[buffer_index];
|
||||
assert_not_null(buffer);
|
||||
ui::d3d12::util::CreateBufferTypedSRV(
|
||||
command_processor_.GetD3D12Context().GetD3D12Provider().GetDevice(),
|
||||
handle, buffer->resource(),
|
||||
command_processor_.GetD3D12Provider().GetDevice(), handle,
|
||||
buffer->resource(),
|
||||
ui::d3d12::util::GetUintPow2DXGIFormat(element_size_bytes_pow2),
|
||||
uint32_t(scaled_resolve_current_range_length_scaled_ >>
|
||||
element_size_bytes_pow2),
|
||||
|
@ -1961,8 +1963,8 @@ void TextureCache::CreateCurrentScaledResolveRangeUintPow2UAV(
|
|||
scaled_resolve_2gb_buffers_[buffer_index];
|
||||
assert_not_null(buffer);
|
||||
ui::d3d12::util::CreateBufferTypedUAV(
|
||||
command_processor_.GetD3D12Context().GetD3D12Provider().GetDevice(),
|
||||
handle, buffer->resource(),
|
||||
command_processor_.GetD3D12Provider().GetDevice(), handle,
|
||||
buffer->resource(),
|
||||
ui::d3d12::util::GetUintPow2DXGIFormat(element_size_bytes_pow2),
|
||||
uint32_t(scaled_resolve_current_range_length_scaled_ >>
|
||||
element_size_bytes_pow2),
|
||||
|
@ -2254,8 +2256,9 @@ TextureCache::Texture* TextureCache::FindOrCreateTexture(TextureKey key) {
|
|||
// Untiling through a buffer instead of using unordered access because copying
|
||||
// is not done that often.
|
||||
desc.Flags = D3D12_RESOURCE_FLAG_NONE;
|
||||
auto& provider = command_processor_.GetD3D12Context().GetD3D12Provider();
|
||||
auto device = provider.GetDevice();
|
||||
const ui::d3d12::D3D12Provider& provider =
|
||||
command_processor_.GetD3D12Provider();
|
||||
ID3D12Device* device = provider.GetDevice();
|
||||
// Assuming untiling will be the next operation.
|
||||
D3D12_RESOURCE_STATES state = D3D12_RESOURCE_STATE_COPY_DEST;
|
||||
ID3D12Resource* resource;
|
||||
|
@ -2317,9 +2320,9 @@ bool TextureCache::LoadTextureData(Texture* texture) {
|
|||
return true;
|
||||
}
|
||||
|
||||
auto& command_list = command_processor_.GetDeferredCommandList();
|
||||
auto device =
|
||||
command_processor_.GetD3D12Context().GetD3D12Provider().GetDevice();
|
||||
DeferredCommandList& command_list =
|
||||
command_processor_.GetDeferredCommandList();
|
||||
ID3D12Device* device = command_processor_.GetD3D12Provider().GetDevice();
|
||||
|
||||
// Get the pipeline.
|
||||
LoadMode load_mode = GetLoadMode(texture->key);
|
||||
|
@ -2875,8 +2878,7 @@ uint32_t TextureCache::FindOrCreateTextureDescriptor(Texture& texture,
|
|||
host_swizzle |
|
||||
D3D12_SHADER_COMPONENT_MAPPING_ALWAYS_SET_BIT_AVOIDING_ZEROMEM_MISTAKES;
|
||||
|
||||
auto device =
|
||||
command_processor_.GetD3D12Context().GetD3D12Provider().GetDevice();
|
||||
ID3D12Device* device = command_processor_.GetD3D12Provider().GetDevice();
|
||||
uint32_t descriptor_index;
|
||||
if (bindless_resources_used_) {
|
||||
descriptor_index =
|
||||
|
@ -2928,7 +2930,8 @@ uint32_t TextureCache::FindOrCreateTextureDescriptor(Texture& texture,
|
|||
|
||||
D3D12_CPU_DESCRIPTOR_HANDLE TextureCache::GetTextureDescriptorCPUHandle(
|
||||
uint32_t descriptor_index) const {
|
||||
auto& provider = command_processor_.GetD3D12Context().GetD3D12Provider();
|
||||
const ui::d3d12::D3D12Provider& provider =
|
||||
command_processor_.GetD3D12Provider();
|
||||
if (bindless_resources_used_) {
|
||||
return provider.OffsetViewDescriptor(
|
||||
command_processor_.GetViewBindlessHeapCPUStart(), descriptor_index);
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
#include "xenia/gpu/texture_info.h"
|
||||
#include "xenia/gpu/texture_util.h"
|
||||
#include "xenia/gpu/xenos.h"
|
||||
#include "xenia/ui/graphics_util.h"
|
||||
|
||||
// Very prominent in 545407F2.
|
||||
DEFINE_bool(
|
||||
|
@ -33,85 +34,10 @@ DEFINE_bool(
|
|||
"for certain games to display the scene graphics).",
|
||||
"GPU");
|
||||
|
||||
DEFINE_bool(
|
||||
present_rescale, true,
|
||||
"Whether to rescale the image, instead of maintaining the original pixel "
|
||||
"size, when presenting to the window. When this is disabled, other "
|
||||
"positioning options are ignored.",
|
||||
"GPU");
|
||||
DEFINE_bool(
|
||||
present_letterbox, true,
|
||||
"Maintain aspect ratio when stretching by displaying bars around the image "
|
||||
"when there's no more overscan area to crop out.",
|
||||
"GPU");
|
||||
// https://github.com/MonoGame/MonoGame/issues/4697#issuecomment-217779403
|
||||
// Using the value from DirectXTK (5% cropped out from each side, thus 90%),
|
||||
// which is not exactly the Xbox One title-safe area, but close, and within the
|
||||
// action-safe area:
|
||||
// https://github.com/microsoft/DirectXTK/blob/1e80a465c6960b457ef9ab6716672c1443a45024/Src/SimpleMath.cpp#L144
|
||||
// XNA TitleSafeArea is 80%, but it's very conservative, designed for CRT, and
|
||||
// is the title-safe area rather than the action-safe area.
|
||||
// 90% is also exactly the fraction of 16:9 height in 16:10.
|
||||
DEFINE_int32(
|
||||
present_safe_area_x, 90,
|
||||
"Percentage of the image width that can be kept when presenting to "
|
||||
"maintain aspect ratio without letterboxing or stretching.",
|
||||
"GPU");
|
||||
DEFINE_int32(
|
||||
present_safe_area_y, 90,
|
||||
"Percentage of the image height that can be kept when presenting to "
|
||||
"maintain aspect ratio without letterboxing or stretching.",
|
||||
"GPU");
|
||||
|
||||
namespace xe {
|
||||
namespace gpu {
|
||||
namespace draw_util {
|
||||
|
||||
int32_t FloatToD3D11Fixed16p8(float f32) {
|
||||
// https://microsoft.github.io/DirectX-Specs/d3d/archive/D3D11_3_FunctionalSpec.htm#3.2.4.1%20FLOAT%20-%3E%20Fixed%20Point%20Integer
|
||||
// Early exit tests.
|
||||
// n == NaN || n.unbiasedExponent < -f-1 -> 0 . 0
|
||||
if (!(std::abs(f32) >= 1.0f / 512.0f)) {
|
||||
return 0;
|
||||
}
|
||||
// n >= (2^(i-1)-2^-f) -> 2^(i-1)-1 . 2^f-1
|
||||
if (f32 >= 32768.0f - 1.0f / 256.0f) {
|
||||
return (1 << 23) - 1;
|
||||
}
|
||||
// n <= -2^(i-1) -> -2^(i-1) . 0
|
||||
if (f32 <= -32768.0f) {
|
||||
return -32768 * 256;
|
||||
}
|
||||
uint32_t f32_bits = *reinterpret_cast<const uint32_t*>(&f32);
|
||||
// Copy float32 mantissa bits [22:0] into corresponding bits [22:0] of a
|
||||
// result buffer that has at least 24 bits total storage (before reaching
|
||||
// rounding step further below). This includes one bit for the hidden 1.
|
||||
// Set bit [23] (float32 hidden bit).
|
||||
// Clear bits [31:24].
|
||||
union {
|
||||
int32_t s;
|
||||
uint32_t u;
|
||||
} result;
|
||||
result.u = (f32_bits & ((1 << 23) - 1)) | (1 << 23);
|
||||
// If the sign bit is set in the float32 number (negative), then take the 2's
|
||||
// component of the entire set of bits.
|
||||
if ((f32_bits >> 31) != 0) {
|
||||
result.s = -result.s;
|
||||
}
|
||||
// Final calculation: extraBits = (mantissa - f) - n.unbiasedExponent
|
||||
// (guaranteed to be >= 0).
|
||||
int32_t exponent = int32_t((f32_bits >> 23) & 255) - 127;
|
||||
uint32_t extra_bits = uint32_t(15 - exponent);
|
||||
if (extra_bits) {
|
||||
// Round the 32-bit value to a decimal that is extraBits to the left of
|
||||
// the LSB end, using nearest-even.
|
||||
result.u += (1 << (extra_bits - 1)) - 1 + ((result.u >> extra_bits) & 1);
|
||||
// Shift right by extraBits (sign extending).
|
||||
result.s >>= extra_bits;
|
||||
}
|
||||
return result.s;
|
||||
}
|
||||
|
||||
bool IsRasterizationPotentiallyDone(const RegisterFile& regs,
|
||||
bool primitive_polygonal) {
|
||||
// TODO(Triang3l): Investigate ModeControl::kIgnore better, with respect to
|
||||
|
@ -746,7 +672,7 @@ bool GetResolveInfo(const RegisterFile& regs, const Memory& memory,
|
|||
regs.Get<reg::PA_SU_VTX_CNTL>().pix_center ? 0.0f : 0.5f;
|
||||
int32_t vertices_fixed[6];
|
||||
for (size_t i = 0; i < xe::countof(vertices_fixed); ++i) {
|
||||
vertices_fixed[i] = FloatToD3D11Fixed16p8(
|
||||
vertices_fixed[i] = ui::FloatToD3D11Fixed16p8(
|
||||
xenos::GpuSwap(vertices_guest[i], fetch.endian) + half_pixel_offset);
|
||||
}
|
||||
// Inclusive.
|
||||
|
@ -1151,87 +1077,6 @@ ResolveCopyShaderIndex ResolveInfo::GetCopyShader(
|
|||
return shader;
|
||||
}
|
||||
|
||||
void GetPresentArea(uint32_t source_width, uint32_t source_height,
|
||||
uint32_t window_width, uint32_t window_height,
|
||||
int32_t& target_x_out, int32_t& target_y_out,
|
||||
uint32_t& target_width_out, uint32_t& target_height_out) {
|
||||
if (!cvars::present_rescale) {
|
||||
target_x_out = (int32_t(window_width) - int32_t(source_width)) / 2;
|
||||
target_y_out = (int32_t(window_height) - int32_t(source_height)) / 2;
|
||||
target_width_out = source_width;
|
||||
target_height_out = source_height;
|
||||
return;
|
||||
}
|
||||
// Prevent division by zero.
|
||||
if (!source_width || !source_height) {
|
||||
target_x_out = 0;
|
||||
target_y_out = 0;
|
||||
target_width_out = 0;
|
||||
target_height_out = 0;
|
||||
return;
|
||||
}
|
||||
if (uint64_t(window_width) * source_height >
|
||||
uint64_t(source_width) * window_height) {
|
||||
// The window is wider that the source - crop along Y, then letterbox or
|
||||
// stretch along X.
|
||||
uint32_t present_safe_area;
|
||||
if (cvars::present_safe_area_y > 0 && cvars::present_safe_area_y < 100) {
|
||||
present_safe_area = uint32_t(cvars::present_safe_area_y);
|
||||
} else {
|
||||
present_safe_area = 100;
|
||||
}
|
||||
uint32_t target_height =
|
||||
uint32_t(uint64_t(window_width) * source_height / source_width);
|
||||
bool letterbox = false;
|
||||
if (target_height * present_safe_area > window_height * 100) {
|
||||
// Don't crop out more than the safe area margin - letterbox or stretch.
|
||||
target_height = window_height * 100 / present_safe_area;
|
||||
letterbox = true;
|
||||
}
|
||||
if (letterbox && cvars::present_letterbox) {
|
||||
uint32_t target_width =
|
||||
uint32_t(uint64_t(source_width) * window_height * 100 /
|
||||
(source_height * present_safe_area));
|
||||
target_x_out = (int32_t(window_width) - int32_t(target_width)) / 2;
|
||||
target_width_out = target_width;
|
||||
} else {
|
||||
target_x_out = 0;
|
||||
target_width_out = window_width;
|
||||
}
|
||||
target_y_out = (int32_t(window_height) - int32_t(target_height)) / 2;
|
||||
target_height_out = target_height;
|
||||
} else {
|
||||
// The window is taller than the source - crop along X, then letterbox or
|
||||
// stretch along Y.
|
||||
uint32_t present_safe_area;
|
||||
if (cvars::present_safe_area_x > 0 && cvars::present_safe_area_x < 100) {
|
||||
present_safe_area = uint32_t(cvars::present_safe_area_x);
|
||||
} else {
|
||||
present_safe_area = 100;
|
||||
}
|
||||
uint32_t target_width =
|
||||
uint32_t(uint64_t(window_height) * source_width / source_height);
|
||||
bool letterbox = false;
|
||||
if (target_width * present_safe_area > window_width * 100) {
|
||||
// Don't crop out more than the safe area margin - letterbox or stretch.
|
||||
target_width = window_width * 100 / present_safe_area;
|
||||
letterbox = true;
|
||||
}
|
||||
if (letterbox && cvars::present_letterbox) {
|
||||
uint32_t target_height =
|
||||
uint32_t(uint64_t(source_height) * window_width * 100 /
|
||||
(source_width * present_safe_area));
|
||||
target_y_out = (int32_t(window_height) - int32_t(target_height)) / 2;
|
||||
target_height_out = target_height;
|
||||
} else {
|
||||
target_y_out = 0;
|
||||
target_height_out = window_height;
|
||||
}
|
||||
target_x_out = (int32_t(window_width) - int32_t(target_width)) / 2;
|
||||
target_width_out = target_width;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace draw_util
|
||||
} // namespace gpu
|
||||
} // namespace xe
|
||||
|
|
|
@ -25,15 +25,6 @@ namespace xe {
|
|||
namespace gpu {
|
||||
namespace draw_util {
|
||||
|
||||
// For estimating coverage extents from vertices. This may give results that are
|
||||
// different than what the host GPU will actually draw (this is the reference
|
||||
// conversion with 1/2 ULP accuracy, but Direct3D 11 permits 0.6 ULP tolerance
|
||||
// in floating point to fixed point conversion), but is enough to tie-break
|
||||
// vertices at pixel centers (due to the half-pixel offset applied to integer
|
||||
// coordinates incorrectly, for instance) with some error tolerance near 0.5,
|
||||
// for use with the top-left rasterization rule later.
|
||||
int32_t FloatToD3D11Fixed16p8(float f32);
|
||||
|
||||
// Polygonal primitive types (not including points and lines) are rasterized as
|
||||
// triangles, have front and back faces, and also support face culling and fill
|
||||
// modes (polymode_front_ptype, polymode_back_ptype). Other primitive types are
|
||||
|
|
|
@ -849,7 +849,7 @@ void DxbcShaderTranslator::StartTranslation() {
|
|||
system_temp_aL_ = PushSystemTemp(0b1111);
|
||||
system_temp_loop_count_ = PushSystemTemp(0b1111);
|
||||
system_temp_grad_h_lod_ = PushSystemTemp(0b1111);
|
||||
system_temp_grad_v_ = PushSystemTemp(0b0111);
|
||||
system_temp_grad_v_vfetch_address_ = PushSystemTemp(0b1111);
|
||||
|
||||
// Zero general-purpose registers to prevent crashes when the game
|
||||
// references them after only initializing them conditionally.
|
||||
|
@ -1039,7 +1039,7 @@ void DxbcShaderTranslator::CompleteShaderCode() {
|
|||
// - system_temp_aL_.
|
||||
// - system_temp_loop_count_.
|
||||
// - system_temp_grad_h_lod_.
|
||||
// - system_temp_grad_v_.
|
||||
// - system_temp_grad_v_vfetch_address_.
|
||||
PopSystemTemp(6);
|
||||
|
||||
// Write memexported data to the shared memory UAV.
|
||||
|
|
|
@ -1104,7 +1104,9 @@ class DxbcShaderTranslator : public ShaderTranslator {
|
|||
uint32_t system_temp_loop_count_;
|
||||
// Explicitly set texture gradients and LOD.
|
||||
uint32_t system_temp_grad_h_lod_;
|
||||
uint32_t system_temp_grad_v_;
|
||||
// .w stores `base + index * stride` in bytes from the last vfetch_full as it
|
||||
// may be needed by vfetch_mini.
|
||||
uint32_t system_temp_grad_v_vfetch_address_;
|
||||
|
||||
// The bool constant number containing the condition for the currently
|
||||
// processed exec (or the last - unless a label has reset this), or
|
||||
|
|
|
@ -35,7 +35,9 @@ void DxbcShaderTranslator::ProcessVertexFetchInstruction(
|
|||
uint32_t used_result_components = instr.result.GetUsedResultComponents();
|
||||
uint32_t needed_words = xenos::GetVertexFormatNeededWords(
|
||||
instr.attributes.data_format, used_result_components);
|
||||
if (!needed_words) {
|
||||
// If this is vfetch_full, the address may still be needed for vfetch_mini -
|
||||
// don't exit before calculating the address.
|
||||
if (!needed_words && instr.is_mini_fetch) {
|
||||
// Nothing to load - just constant 0/1 writes, or the swizzle includes only
|
||||
// components that don't exist in the format (writing zero instead of them).
|
||||
// Unpacking assumes at least some word is needed.
|
||||
|
@ -59,47 +61,74 @@ void DxbcShaderTranslator::ProcessVertexFetchInstruction(
|
|||
// fetch constants on the CPU when proper bound checks are added - vfetch may
|
||||
// be conditional, so fetch constants may also be used conditionally.
|
||||
|
||||
// - Load the byte address in physical memory to system_temp_result_.w (so
|
||||
// it's not overwritten by data loads until the last one).
|
||||
// - Load the part of the byte address in the physical memory that is the same
|
||||
// in vfetch_full and vfetch_mini to system_temp_grad_v_vfetch_address_.w
|
||||
// (the index operand GPR must not be reloaded in vfetch_mini because it
|
||||
// might have been overwritten previously, but that shouldn't have effect on
|
||||
// vfetch_mini).
|
||||
|
||||
dxbc::Dest address_dest(dxbc::Dest::R(system_temp_result_, 0b1000));
|
||||
dxbc::Src address_src(dxbc::Src::R(system_temp_result_, dxbc::Src::kWWWW));
|
||||
if (instr.attributes.stride) {
|
||||
// Convert the index to an integer by flooring or by rounding to the nearest
|
||||
// (as floor(index + 0.5) because rounding to the nearest even makes no
|
||||
// sense for addressing, both 1.5 and 2.5 would be 2).
|
||||
// http://web.archive.org/web/20100302145413/http://msdn.microsoft.com:80/en-us/library/bb313960.aspx
|
||||
{
|
||||
bool index_operand_temp_pushed = false;
|
||||
dxbc::Src index_operand(
|
||||
LoadOperand(instr.operands[0], 0b0001, index_operand_temp_pushed)
|
||||
.SelectFromSwizzled(0));
|
||||
if (instr.attributes.is_index_rounded) {
|
||||
a_.OpAdd(address_dest, index_operand, dxbc::Src::LF(0.5f));
|
||||
a_.OpRoundNI(address_dest, address_src);
|
||||
} else {
|
||||
a_.OpRoundNI(address_dest, index_operand);
|
||||
}
|
||||
if (index_operand_temp_pushed) {
|
||||
PopSystemTemp();
|
||||
dxbc::Src address_src(
|
||||
dxbc::Src::R(system_temp_grad_v_vfetch_address_, dxbc::Src::kWWWW));
|
||||
if (!instr.is_mini_fetch) {
|
||||
dxbc::Dest address_dest(
|
||||
dxbc::Dest::R(system_temp_grad_v_vfetch_address_, 0b1000));
|
||||
if (instr.attributes.stride) {
|
||||
// Convert the index to an integer by flooring or by rounding to the
|
||||
// nearest (as floor(index + 0.5) because rounding to the nearest even
|
||||
// makes no sense for addressing, both 1.5 and 2.5 would be 2).
|
||||
{
|
||||
bool index_operand_temp_pushed = false;
|
||||
dxbc::Src index_operand(
|
||||
LoadOperand(instr.operands[0], 0b0001, index_operand_temp_pushed)
|
||||
.SelectFromSwizzled(0));
|
||||
if (instr.attributes.is_index_rounded) {
|
||||
a_.OpAdd(address_dest, index_operand, dxbc::Src::LF(0.5f));
|
||||
a_.OpRoundNI(address_dest, address_src);
|
||||
} else {
|
||||
a_.OpRoundNI(address_dest, index_operand);
|
||||
}
|
||||
if (index_operand_temp_pushed) {
|
||||
PopSystemTemp();
|
||||
}
|
||||
}
|
||||
a_.OpFToI(address_dest, address_src);
|
||||
// Extract the byte address from the fetch constant to
|
||||
// system_temp_result_.w (which is not used yet).
|
||||
a_.OpAnd(dxbc::Dest::R(system_temp_result_, 0b1000),
|
||||
fetch_constant_src.SelectFromSwizzled(0),
|
||||
dxbc::Src::LU(~uint32_t(3)));
|
||||
// Merge the index and the base address.
|
||||
a_.OpIMAd(address_dest, address_src,
|
||||
dxbc::Src::LU(instr.attributes.stride * sizeof(uint32_t)),
|
||||
dxbc::Src::R(system_temp_result_, dxbc::Src::kWWWW));
|
||||
} else {
|
||||
// Fetching from the same location - extract the byte address of the
|
||||
// beginning of the buffer.
|
||||
a_.OpAnd(address_dest, fetch_constant_src.SelectFromSwizzled(0),
|
||||
dxbc::Src::LU(~uint32_t(3)));
|
||||
}
|
||||
a_.OpFToI(address_dest, address_src);
|
||||
// Extract the byte address from the fetch constant to
|
||||
// system_temp_result_.z.
|
||||
a_.OpAnd(dxbc::Dest::R(system_temp_result_, 0b0100),
|
||||
fetch_constant_src.SelectFromSwizzled(0),
|
||||
dxbc::Src::LU(~uint32_t(3)));
|
||||
// Merge the index and the base address.
|
||||
a_.OpIMAd(address_dest, address_src,
|
||||
dxbc::Src::LU(instr.attributes.stride * sizeof(uint32_t)),
|
||||
dxbc::Src::R(system_temp_result_, dxbc::Src::kZZZZ));
|
||||
} else {
|
||||
// Fetching from the same location - extract the byte address of the
|
||||
// beginning of the buffer.
|
||||
a_.OpAnd(address_dest, fetch_constant_src.SelectFromSwizzled(0),
|
||||
dxbc::Src::LU(~uint32_t(3)));
|
||||
}
|
||||
|
||||
if (!needed_words) {
|
||||
// The vfetch_full address has been loaded for the subsequent vfetch_mini,
|
||||
// but there's no data to load.
|
||||
StoreResult(instr.result, dxbc::Src::LF(0.0f));
|
||||
return;
|
||||
}
|
||||
|
||||
dxbc::Dest address_temp_dest(dxbc::Dest::R(system_temp_result_, 0b1000));
|
||||
dxbc::Src address_temp_src(
|
||||
dxbc::Src::R(system_temp_result_, dxbc::Src::kWWWW));
|
||||
|
||||
// - From now on, if any additional offset must be applied to the
|
||||
// `base + index * stride` part of the address, it must be done by writing
|
||||
// to system_temp_result_.w (address_temp_dest) instead of
|
||||
// system_temp_grad_v_vfetch_address_.w (since it must stay the same for the
|
||||
// vfetch_full and all its vfetch_mini invocations), and changing
|
||||
// address_src to address_temp_src afterwards. system_temp_result_.w can be
|
||||
// used for this purpose safely because it won't be overwritten until the
|
||||
// last dword is loaded (after which the address won't be needed anymore).
|
||||
|
||||
// Add the word offset from the instruction (signed), plus the offset of the
|
||||
// first needed word within the element.
|
||||
uint32_t first_word_index;
|
||||
|
@ -108,8 +137,9 @@ void DxbcShaderTranslator::ProcessVertexFetchInstruction(
|
|||
instr.attributes.offset + int32_t(first_word_index);
|
||||
if (first_word_buffer_offset) {
|
||||
// Add the constant word offset.
|
||||
a_.OpIAdd(address_dest, address_src,
|
||||
a_.OpIAdd(address_temp_dest, address_src,
|
||||
dxbc::Src::LI(first_word_buffer_offset * sizeof(uint32_t)));
|
||||
address_src = address_temp_src;
|
||||
}
|
||||
|
||||
// - Load needed words to system_temp_result_, words 0, 1, 2, 3 to X, Y, Z, W
|
||||
|
@ -159,9 +189,10 @@ void DxbcShaderTranslator::ProcessVertexFetchInstruction(
|
|||
~((uint32_t(1) << (word_index + word_count)) - uint32_t(1));
|
||||
if (word_index != word_index_previous) {
|
||||
// Go to the word in the buffer.
|
||||
a_.OpIAdd(address_dest, address_src,
|
||||
a_.OpIAdd(address_temp_dest, address_src,
|
||||
dxbc::Src::LU((word_index - word_index_previous) *
|
||||
sizeof(uint32_t)));
|
||||
address_src = address_temp_src;
|
||||
word_index_previous = word_index;
|
||||
}
|
||||
// Can ld_raw either to the first multiple components, or to any scalar
|
||||
|
@ -592,7 +623,7 @@ void DxbcShaderTranslator::ProcessTextureFetchInstruction(
|
|||
case FetchOpcode::kSetTextureGradientsVert: {
|
||||
bool grad_operand_temp_pushed = false;
|
||||
a_.OpMov(
|
||||
dxbc::Dest::R(system_temp_grad_v_, 0b0111),
|
||||
dxbc::Dest::R(system_temp_grad_v_vfetch_address_, 0b0111),
|
||||
LoadOperand(instr.operands[0], 0b0111, grad_operand_temp_pushed));
|
||||
if (grad_operand_temp_pushed) {
|
||||
PopSystemTemp();
|
||||
|
@ -1521,15 +1552,15 @@ void DxbcShaderTranslator::ProcessTextureFetchInstruction(
|
|||
// Extract gradient exponent biases from the fetch constant and merge
|
||||
// them with the LOD bias.
|
||||
a_.OpIBFE(dxbc::Dest::R(grad_h_lod_temp, 0b0011), dxbc::Src::LU(5),
|
||||
dxbc::Src::LU(22, 27, 0, 0),
|
||||
RequestTextureFetchConstantWord(tfetch_index, 4));
|
||||
dxbc::Src::LU(22, 27, 0, 0),
|
||||
RequestTextureFetchConstantWord(tfetch_index, 4));
|
||||
a_.OpIMAd(dxbc::Dest::R(grad_h_lod_temp, 0b0011),
|
||||
dxbc::Src::R(grad_h_lod_temp), dxbc::Src::LI(int32_t(1) << 23),
|
||||
dxbc::Src::LF(1.0f));
|
||||
dxbc::Src::R(grad_h_lod_temp),
|
||||
dxbc::Src::LI(int32_t(1) << 23), dxbc::Src::LF(1.0f));
|
||||
a_.OpMul(dxbc::Dest::R(grad_v_temp, 0b1000), lod_src,
|
||||
dxbc::Src::R(grad_h_lod_temp, dxbc::Src::kYYYY));
|
||||
dxbc::Src::R(grad_h_lod_temp, dxbc::Src::kYYYY));
|
||||
a_.OpMul(lod_dest, lod_src,
|
||||
dxbc::Src::R(grad_h_lod_temp, dxbc::Src::kXXXX));
|
||||
dxbc::Src::R(grad_h_lod_temp, dxbc::Src::kXXXX));
|
||||
#endif
|
||||
// Obtain the gradients and apply biases to them.
|
||||
if (instr.attributes.use_register_gradients) {
|
||||
|
@ -1540,11 +1571,11 @@ void DxbcShaderTranslator::ProcessTextureFetchInstruction(
|
|||
// done in getCompTexLOD, so don't do it here too.
|
||||
#if 0
|
||||
a_.OpMul(dxbc::Dest::R(grad_v_temp, grad_mask),
|
||||
dxbc::Src::R(system_temp_grad_v_),
|
||||
dxbc::Src::R(grad_v_temp, dxbc::Src::kWWWW));
|
||||
dxbc::Src::R(system_temp_grad_v_vfetch_address_),
|
||||
dxbc::Src::R(grad_v_temp, dxbc::Src::kWWWW));
|
||||
#else
|
||||
a_.OpMul(dxbc::Dest::R(grad_v_temp, grad_mask),
|
||||
dxbc::Src::R(system_temp_grad_v_), lod_src);
|
||||
dxbc::Src::R(system_temp_grad_v_vfetch_address_), lod_src);
|
||||
#endif
|
||||
// TODO(Triang3l): Are cube map register gradients unnormalized if
|
||||
// the coordinates themselves are unnormalized?
|
||||
|
@ -1586,8 +1617,8 @@ void DxbcShaderTranslator::ProcessTextureFetchInstruction(
|
|||
// done in getCompTexLOD, so don't do it here too.
|
||||
#if 0
|
||||
a_.OpMul(dxbc::Dest::R(grad_v_temp, grad_mask),
|
||||
dxbc::Src::R(grad_v_temp),
|
||||
dxbc::Src::R(grad_v_temp, dxbc::Src::kWWWW));
|
||||
dxbc::Src::R(grad_v_temp),
|
||||
dxbc::Src::R(grad_v_temp, dxbc::Src::kWWWW));
|
||||
#else
|
||||
a_.OpMul(dxbc::Dest::R(grad_v_temp, grad_mask),
|
||||
dxbc::Src::R(grad_v_temp), lod_src);
|
||||
|
|
|
@ -2,13 +2,19 @@
|
|||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2020 Ben Vanik. All rights reserved. *
|
||||
* Copyright 2022 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include "xenia/gpu/graphics_system.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <utility>
|
||||
|
||||
#include "xenia/base/byte_stream.h"
|
||||
#include "xenia/base/clock.h"
|
||||
#include "xenia/base/logging.h"
|
||||
|
@ -48,70 +54,40 @@ GraphicsSystem::~GraphicsSystem() = default;
|
|||
|
||||
X_STATUS GraphicsSystem::Setup(cpu::Processor* processor,
|
||||
kernel::KernelState* kernel_state,
|
||||
ui::Window* target_window) {
|
||||
ui::WindowedAppContext* app_context,
|
||||
[[maybe_unused]] bool is_surface_required) {
|
||||
memory_ = processor->memory();
|
||||
processor_ = processor;
|
||||
kernel_state_ = kernel_state;
|
||||
target_window_ = target_window;
|
||||
app_context_ = app_context;
|
||||
|
||||
// Initialize display and rendering context.
|
||||
// This must happen on the UI thread.
|
||||
std::unique_ptr<xe::ui::GraphicsContext> processor_context = nullptr;
|
||||
if (provider_) {
|
||||
// Setup the context the command processor will do all its drawing in.
|
||||
bool contexts_initialized = true;
|
||||
processor_context = provider()->CreateEmulationContext();
|
||||
if (processor_context) {
|
||||
if (target_window_) {
|
||||
if (!target_window_->app_context().CallInUIThreadSynchronous([&]() {
|
||||
// Create the context used for presentation.
|
||||
assert_null(target_window->context());
|
||||
target_window_->set_context(
|
||||
provider_->CreateHostContext(target_window_));
|
||||
})) {
|
||||
contexts_initialized = false;
|
||||
}
|
||||
}
|
||||
// Safe if either the UI thread call or the presenter creation fails.
|
||||
if (app_context_) {
|
||||
app_context_->CallInUIThreadSynchronous([this]() {
|
||||
presenter_ = provider_->CreatePresenter(
|
||||
[this](bool is_responsible, bool statically_from_ui_thread) {
|
||||
OnHostGpuLossFromAnyThread(is_responsible);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
contexts_initialized = false;
|
||||
}
|
||||
if (!contexts_initialized) {
|
||||
xe::FatalError(
|
||||
"Unable to initialize graphics context. Xenia requires Vulkan "
|
||||
"support.\n"
|
||||
"\n"
|
||||
"Ensure you have the latest drivers for your GPU and "
|
||||
"that it supports Vulkan.\n"
|
||||
"\n"
|
||||
"See https://xenia.jp/faq/ for more information and a list of "
|
||||
"supported GPUs.");
|
||||
return X_STATUS_UNSUCCESSFUL;
|
||||
// May be needed for offscreen use, such as capturing the guest output
|
||||
// image.
|
||||
presenter_ = provider_->CreatePresenter(
|
||||
[this](bool is_responsible, bool statically_from_ui_thread) {
|
||||
OnHostGpuLossFromAnyThread(is_responsible);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Create command processor. This will spin up a thread to process all
|
||||
// incoming ringbuffer packets.
|
||||
command_processor_ = CreateCommandProcessor();
|
||||
if (!command_processor_->Initialize(std::move(processor_context))) {
|
||||
if (!command_processor_->Initialize()) {
|
||||
XELOGE("Unable to initialize command processor");
|
||||
return X_STATUS_UNSUCCESSFUL;
|
||||
}
|
||||
|
||||
if (target_window) {
|
||||
command_processor_->set_swap_request_handler(
|
||||
[this]() { target_window_->Invalidate(); });
|
||||
|
||||
// Watch for paint requests to do our swap.
|
||||
target_window->on_painting.AddListener(
|
||||
[this](xe::ui::UIEvent* e) { Swap(e); });
|
||||
|
||||
// Watch for context lost events.
|
||||
target_window->on_context_lost.AddListener(
|
||||
[this](xe::ui::UIEvent* e) { Reset(); });
|
||||
} else {
|
||||
command_processor_->set_swap_request_handler([]() {});
|
||||
}
|
||||
|
||||
// Let the processor know we want register access callbacks.
|
||||
memory_->AddVirtualMappedRange(
|
||||
0x7FC80000, 0xFFFF0000, 0x0000FFFF, this,
|
||||
|
@ -152,6 +128,7 @@ void GraphicsSystem::Shutdown() {
|
|||
if (command_processor_) {
|
||||
EndTracing();
|
||||
command_processor_->Shutdown();
|
||||
command_processor_.reset();
|
||||
}
|
||||
|
||||
if (vsync_worker_thread_) {
|
||||
|
@ -159,13 +136,35 @@ void GraphicsSystem::Shutdown() {
|
|||
vsync_worker_thread_->Wait(0, 0, 0, nullptr);
|
||||
vsync_worker_thread_.reset();
|
||||
}
|
||||
|
||||
if (presenter_) {
|
||||
if (app_context_) {
|
||||
app_context_->CallInUIThreadSynchronous([this]() { presenter_.reset(); });
|
||||
}
|
||||
// If there's no app context (thus the presenter is owned by the thread that
|
||||
// initialized the GraphicsSystem) or can't be queueing UI thread calls
|
||||
// anymore, shutdown anyway.
|
||||
presenter_.reset();
|
||||
}
|
||||
|
||||
provider_.reset();
|
||||
}
|
||||
|
||||
void GraphicsSystem::Reset() {
|
||||
// TODO(DrChat): Reset the system.
|
||||
XELOGI("Context lost; Reset invoked");
|
||||
Shutdown();
|
||||
|
||||
void GraphicsSystem::OnHostGpuLossFromAnyThread(
|
||||
[[maybe_unused]] bool is_responsible) {
|
||||
// TODO(Triang3l): Somehow gain exclusive ownership of the Provider (may be
|
||||
// used by the command processor, the presenter, and possibly anything else,
|
||||
// it's considered free-threaded, except for lifetime management which will be
|
||||
// involved in this case) and reset it so a new host GPU API device is
|
||||
// created. Then ask the command processor to reset itself in its thread, and
|
||||
// ask the UI thread to reset the Presenter (the UI thread manages its
|
||||
// lifetime - but if there's no WindowedAppContext, either don't reset it as
|
||||
// in this case there's no user who needs uninterrupted gameplay, or somehow
|
||||
// protect it with a mutex so any thread can be considered a UI thread and
|
||||
// reset).
|
||||
if (host_gpu_loss_reported_.test_and_set(std::memory_order_relaxed)) {
|
||||
return;
|
||||
}
|
||||
xe::FatalError("Graphics device lost (probably due to an internal error)");
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2020 Ben Vanik. All rights reserved. *
|
||||
* Copyright 2022 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
@ -11,7 +11,10 @@
|
|||
#define XENIA_GPU_GRAPHICS_SYSTEM_H_
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
|
@ -19,7 +22,9 @@
|
|||
#include "xenia/gpu/register_file.h"
|
||||
#include "xenia/kernel/xthread.h"
|
||||
#include "xenia/memory.h"
|
||||
#include "xenia/ui/window.h"
|
||||
#include "xenia/ui/graphics_provider.h"
|
||||
#include "xenia/ui/presenter.h"
|
||||
#include "xenia/ui/windowed_app_context.h"
|
||||
#include "xenia/xbox.h"
|
||||
|
||||
namespace xe {
|
||||
|
@ -41,14 +46,17 @@ class GraphicsSystem {
|
|||
cpu::Processor* processor() const { return processor_; }
|
||||
kernel::KernelState* kernel_state() const { return kernel_state_; }
|
||||
ui::GraphicsProvider* provider() const { return provider_.get(); }
|
||||
ui::Presenter* presenter() const { return presenter_.get(); }
|
||||
|
||||
virtual X_STATUS Setup(cpu::Processor* processor,
|
||||
kernel::KernelState* kernel_state,
|
||||
ui::Window* target_window);
|
||||
ui::WindowedAppContext* app_context,
|
||||
bool is_surface_required);
|
||||
virtual void Shutdown();
|
||||
virtual void Reset();
|
||||
|
||||
virtual std::unique_ptr<xe::ui::RawImage> Capture() { return nullptr; }
|
||||
// May be called from any thread any number of times, even during recovery
|
||||
// from a device loss.
|
||||
void OnHostGpuLossFromAnyThread(bool is_responsible);
|
||||
|
||||
RegisterFile* register_file() { return ®ister_file_; }
|
||||
CommandProcessor* command_processor() const {
|
||||
|
@ -91,12 +99,11 @@ class GraphicsSystem {
|
|||
void WriteRegister(uint32_t addr, uint32_t value);
|
||||
|
||||
void MarkVblank();
|
||||
virtual void Swap(xe::ui::UIEvent* e) = 0;
|
||||
|
||||
Memory* memory_ = nullptr;
|
||||
cpu::Processor* processor_ = nullptr;
|
||||
kernel::KernelState* kernel_state_ = nullptr;
|
||||
ui::Window* target_window_ = nullptr;
|
||||
ui::WindowedAppContext* app_context_ = nullptr;
|
||||
std::unique_ptr<ui::GraphicsProvider> provider_;
|
||||
|
||||
uint32_t interrupt_callback_ = 0;
|
||||
|
@ -109,6 +116,11 @@ class GraphicsSystem {
|
|||
std::unique_ptr<CommandProcessor> command_processor_;
|
||||
|
||||
bool paused_ = false;
|
||||
|
||||
private:
|
||||
std::unique_ptr<ui::Presenter> presenter_;
|
||||
|
||||
std::atomic_flag host_gpu_loss_reported_;
|
||||
};
|
||||
|
||||
} // namespace gpu
|
||||
|
|
|
@ -31,9 +31,9 @@ void NullCommandProcessor::ShutdownContext() {
|
|||
return CommandProcessor::ShutdownContext();
|
||||
}
|
||||
|
||||
void NullCommandProcessor::PerformSwap(uint32_t frontbuffer_ptr,
|
||||
uint32_t frontbuffer_width,
|
||||
uint32_t frontbuffer_height) {}
|
||||
void NullCommandProcessor::IssueSwap(uint32_t frontbuffer_ptr,
|
||||
uint32_t frontbuffer_width,
|
||||
uint32_t frontbuffer_height) {}
|
||||
|
||||
Shader* NullCommandProcessor::LoadShader(xenos::ShaderType shader_type,
|
||||
uint32_t guest_address,
|
||||
|
|
|
@ -33,8 +33,8 @@ class NullCommandProcessor : public CommandProcessor {
|
|||
bool SetupContext() override;
|
||||
void ShutdownContext() override;
|
||||
|
||||
void PerformSwap(uint32_t frontbuffer_ptr, uint32_t frontbuffer_width,
|
||||
uint32_t frontbuffer_height) override;
|
||||
void IssueSwap(uint32_t frontbuffer_ptr, uint32_t frontbuffer_width,
|
||||
uint32_t frontbuffer_height) override;
|
||||
|
||||
Shader* LoadShader(xenos::ShaderType shader_type, uint32_t guest_address,
|
||||
const uint32_t* host_address,
|
||||
|
|
|
@ -23,31 +23,20 @@ NullGraphicsSystem::~NullGraphicsSystem() {}
|
|||
|
||||
X_STATUS NullGraphicsSystem::Setup(cpu::Processor* processor,
|
||||
kernel::KernelState* kernel_state,
|
||||
ui::Window* target_window) {
|
||||
ui::WindowedAppContext* app_context,
|
||||
bool is_surface_required) {
|
||||
// This is a null graphics system, but we still setup vulkan because UI needs
|
||||
// it through us :|
|
||||
provider_ = xe::ui::vulkan::VulkanProvider::Create();
|
||||
|
||||
return GraphicsSystem::Setup(processor, kernel_state, target_window);
|
||||
provider_ = xe::ui::vulkan::VulkanProvider::Create(is_surface_required);
|
||||
return GraphicsSystem::Setup(processor, kernel_state, app_context,
|
||||
is_surface_required);
|
||||
}
|
||||
|
||||
void NullGraphicsSystem::Shutdown() { GraphicsSystem::Shutdown(); }
|
||||
|
||||
std::unique_ptr<CommandProcessor> NullGraphicsSystem::CreateCommandProcessor() {
|
||||
return std::unique_ptr<CommandProcessor>(
|
||||
new NullCommandProcessor(this, kernel_state_));
|
||||
}
|
||||
|
||||
void NullGraphicsSystem::Swap(xe::ui::UIEvent* e) {
|
||||
if (!command_processor_) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& swap_state = command_processor_->swap_state();
|
||||
std::lock_guard<std::mutex> lock(swap_state.mutex);
|
||||
swap_state.pending = false;
|
||||
}
|
||||
|
||||
} // namespace null
|
||||
} // namespace gpu
|
||||
} // namespace xe
|
|
@ -29,13 +29,11 @@ class NullGraphicsSystem : public GraphicsSystem {
|
|||
std::string name() const override { return "null"; }
|
||||
|
||||
X_STATUS Setup(cpu::Processor* processor, kernel::KernelState* kernel_state,
|
||||
ui::Window* target_window) override;
|
||||
void Shutdown() override;
|
||||
ui::WindowedAppContext* app_context,
|
||||
bool is_surface_required) override;
|
||||
|
||||
private:
|
||||
std::unique_ptr<CommandProcessor> CreateCommandProcessor() override;
|
||||
|
||||
void Swap(xe::ui::UIEvent* e) override;
|
||||
};
|
||||
|
||||
} // namespace null
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue