Merge branch 'master' into linux_windowing

This commit is contained in:
Triang3l 2021-08-26 22:58:14 +03:00
commit 7edfdc2672
813 changed files with 429062 additions and 250262 deletions

View File

@ -8,6 +8,7 @@ skip_tags: true
skip_commits: skip_commits:
files: files:
- .drone.yml
- .github/** - .github/**
- .travis.yml - .travis.yml
- docs/** - docs/**

214
.drone.yml Normal file
View File

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

2
.gitattributes vendored
View File

@ -7,3 +7,5 @@
*.csproj text eol=crlf -whitespace merge=union *.csproj text eol=crlf -whitespace merge=union
*.vcxproj text eol=crlf -whitespace merge=union *.vcxproj text eol=crlf -whitespace merge=union
*.props text eol=crlf -whitespace merge=union *.props text eol=crlf -whitespace merge=union
src/**/shaders/bytecode/** linguist-generated=true

2
.gitmodules vendored
View File

@ -48,7 +48,7 @@
url = https://github.com/jarro2783/cxxopts.git url = https://github.com/jarro2783/cxxopts.git
[submodule "third_party/SDL2"] [submodule "third_party/SDL2"]
path = third_party/SDL2 path = third_party/SDL2
url = https://github.com/JoelLinn/SDL.git url = https://github.com/libsdl-org/SDL.git
[submodule "third_party/utfcpp"] [submodule "third_party/utfcpp"]
path = third_party/utfcpp path = third_party/utfcpp
url = https://github.com/xenia-project/utfcpp.git url = https://github.com/xenia-project/utfcpp.git

View File

@ -1,73 +0,0 @@
# Make Travis use docker (for faster builds, in theory).
language: cpp
os:
- linux
dist: bionic
addons:
apt:
sources:
- ubuntu-toolchain-r-test
- llvm-toolchain-9
packages:
- clang-9
- clang-format-9
- llvm-9-dev
- g++-8
- python3
- libc++-dev
- libc++abi-dev
- libgtk-3-dev
- libpthread-stubs0-dev
- libsdl2-dev
#- libvulkan1
#- libvulkan-dev
- libx11-dev
- liblz4-dev
jobs:
include:
- env: C_COMPILER=clang-9 CXX_COMPILER=clang++-9 AR_COMPILER=llvm-ar-9 LINT=true
- env: C_COMPILER=clang-9 CXX_COMPILER=clang++-9 AR_COMPILER=llvm-ar-9 BUILD=true CONFIG=Debug
- env: C_COMPILER=clang-9 CXX_COMPILER=clang++-9 AR_COMPILER=llvm-ar-9 BUILD=true CONFIG=Release
git:
# We handle submodules ourselves in xenia-build setup.
submodules: false
before_script:
- export LIBVULKAN_VERSION=1.1.70
- export CXX=$CXX_COMPILER
- export CC=$C_COMPILER
- export AR=$AR_COMPILER
# Dump useful info.
- $CXX --version
- $AR_COMPILER --version
- python3 --version
- clang-format-9 --version
- clang-format-9 -style=file -dump-config
# Add Vulkan dependencies.
- travis_retry wget http://mirrors.kernel.org/ubuntu/pool/universe/v/vulkan/libvulkan1_$LIBVULKAN_VERSION+dfsg1-1_amd64.deb
- travis_retry wget http://mirrors.kernel.org/ubuntu/pool/universe/v/vulkan/libvulkan-dev_$LIBVULKAN_VERSION+dfsg1-1_amd64.deb
- if [[ $BUILD == true ]]; then sudo dpkg -i libvulkan1_$LIBVULKAN_VERSION+dfsg1-1_amd64.deb libvulkan-dev_$LIBVULKAN_VERSION+dfsg1-1_amd64.deb; fi
# Prepare environment (pull dependencies, build tools).
- travis_retry ./xenia-build setup
script:
# Run linter.
- if [[ $LINT == true ]]; then ./xenia-build lint --all; fi
# Build and run base tests.
- if [[ $BUILD == true ]]; then ./xenia-build build --config=$CONFIG --target=xenia-base-tests; fi
- if [[ $BUILD == true ]]; then ./build/bin/Linux/$CONFIG/xenia-base-tests; fi
# Build and run ppc tests.
- if [[ $BUILD == true ]]; then ./xenia-build build --config=$CONFIG --target=xenia-cpu-ppc-tests; fi
# - if [[ $BUILD == true ]]; then ./build/bin/Linux/$CONFIG/xenia-cpu-ppc-tests --log_file=stdout; fi
# Build all of xenia.
- if [[ $BUILD == true ]]; then ./xenia-build build --config=$CONFIG; fi
# All tests (without haswell support).
#- ./xenia-build test --config=debug --no-build -- --enable_haswell_instructions=false
# All tests (with haswell support).
#- ./xenia-build test --config=debug --no-build -- --enable_haswell_instructions=true

View File

@ -24,7 +24,7 @@ Discussing illegal activities will get you banned.
Buildbot | 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) [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)
[Linux](https://travis-ci.org/xenia-project/xenia) | [![Build status](https://travis-ci.org/xenia-project/xenia.svg?branch=master)](https://travis-ci.org/xenia-project/xenia) [Linux](https://cloud.drone.io/xenia-project/xenia) | [![Build status](https://cloud.drone.io/api/badges/xenia-project/xenia/status.svg)](https://cloud.drone.io/xenia-project/xenia)
Quite a few real games run. Quite a few don't. Quite a few real games run. Quite a few don't.
See the [Game compatibility list](https://github.com/xenia-project/game-compatibility/issues) See the [Game compatibility list](https://github.com/xenia-project/game-compatibility/issues)

View File

@ -99,7 +99,7 @@ Clang-9 or newer should be available from system repositories on all up to date
You will also need some development libraries. To get them on an Ubuntu system: You will also need some development libraries. To get them on an Ubuntu system:
```bash ```bash
sudo apt-get install libgtk-3-dev libpthread-stubs0-dev liblz4-dev libx11-dev libvulkan-dev libsdl2-dev libiberty-dev libunwind-dev libc++-dev libc++abi-dev sudo apt-get install libgtk-3-dev libpthread-stubs0-dev liblz4-dev libx11-dev libx11-xcb-dev libvulkan-dev libsdl2-dev libiberty-dev libunwind-dev libc++-dev libc++abi-dev
``` ```
In addition, you will need up to date Vulkan libraries and drivers for your hardware, which most distributions have in their standard repositories nowadays. In addition, you will need up to date Vulkan libraries and drivers for your hardware, which most distributions have in their standard repositories nowadays.

View File

@ -99,8 +99,8 @@ filter("platforms:Linux")
toolset("clang") toolset("clang")
buildoptions({ buildoptions({
-- "-mlzcnt", -- (don't) Assume lzcnt is supported. -- "-mlzcnt", -- (don't) Assume lzcnt is supported.
({os.outputof("pkg-config --cflags gtk+-x11-3.0")})[1],
}) })
pkg_config.all("gtk+-x11-3.0")
links({ links({
"stdc++fs", "stdc++fs",
"dl", "dl",
@ -108,9 +108,6 @@ filter("platforms:Linux")
"pthread", "pthread",
"rt", "rt",
}) })
linkoptions({
({os.outputof("pkg-config --libs gtk+-3.0")})[1],
})
filter({"platforms:Linux", "kind:*App"}) filter({"platforms:Linux", "kind:*App"})
linkgroups("On") linkgroups("On")
@ -234,7 +231,6 @@ workspace("xenia")
include("third_party/mspack.lua") include("third_party/mspack.lua")
include("third_party/snappy.lua") include("third_party/snappy.lua")
include("third_party/spirv-tools.lua") include("third_party/spirv-tools.lua")
include("third_party/volk.lua")
include("third_party/xxhash.lua") include("third_party/xxhash.lua")
if not os.istarget("android") then if not os.istarget("android") then

View File

@ -24,6 +24,7 @@
#include "xenia/ui/file_picker.h" #include "xenia/ui/file_picker.h"
#include "xenia/ui/imgui_dialog.h" #include "xenia/ui/imgui_dialog.h"
#include "xenia/ui/imgui_drawer.h" #include "xenia/ui/imgui_drawer.h"
#include "xenia/ui/virtual_key.h"
// Autogenerated by `xb premake`. // Autogenerated by `xb premake`.
#include "build/version.h" #include "build/version.h"
@ -53,7 +54,12 @@ EmulatorWindow::EmulatorWindow(Emulator* emulator)
" CHECKED" " CHECKED"
#endif #endif
#endif #endif
" (" XE_BUILD_BRANCH "/" XE_BUILD_COMMIT_SHORT "/" XE_BUILD_DATE " ("
#ifdef XE_BUILD_IS_PR
"PR#" XE_BUILD_PR_NUMBER " " XE_BUILD_PR_REPO
" " XE_BUILD_PR_BRANCH "@" XE_BUILD_PR_COMMIT_SHORT " against "
#endif
XE_BUILD_BRANCH "@" XE_BUILD_COMMIT_SHORT " on " XE_BUILD_DATE
")"; ")";
} }
@ -93,49 +99,49 @@ bool EmulatorWindow::Initialize() {
window_->on_key_down.AddListener([this](KeyEvent* e) { window_->on_key_down.AddListener([this](KeyEvent* e) {
bool handled = true; bool handled = true;
switch (e->key_code()) { switch (e->virtual_key()) {
case 0x4F: { // o case ui::VirtualKey::kO: {
if (e->is_ctrl_pressed()) { if (e->is_ctrl_pressed()) {
FileOpen(); FileOpen();
} }
} break; } break;
case 0x6A: { // numpad * case ui::VirtualKey::kMultiply: {
CpuTimeScalarReset(); CpuTimeScalarReset();
} break; } break;
case 0x6D: { // numpad minus case ui::VirtualKey::kSubtract: {
CpuTimeScalarSetHalf(); CpuTimeScalarSetHalf();
} break; } break;
case 0x6B: { // numpad plus case ui::VirtualKey::kAdd: {
CpuTimeScalarSetDouble(); CpuTimeScalarSetDouble();
} break; } break;
case 0x72: { // F3 case ui::VirtualKey::kF3: {
Profiler::ToggleDisplay(); Profiler::ToggleDisplay();
} break; } break;
case 0x73: { // VK_F4 case ui::VirtualKey::kF4: {
GpuTraceFrame(); GpuTraceFrame();
} break; } break;
case 0x74: { // VK_F5 case ui::VirtualKey::kF5: {
GpuClearCaches(); GpuClearCaches();
} break; } break;
case 0x76: { // VK_F7 case ui::VirtualKey::kF7: {
// Save to file // Save to file
// TODO: Choose path based on user input, or from options // TODO: Choose path based on user input, or from options
// TODO: Spawn a new thread to do this. // TODO: Spawn a new thread to do this.
emulator()->SaveToFile("test.sav"); emulator()->SaveToFile("test.sav");
} break; } break;
case 0x77: { // VK_F8 case ui::VirtualKey::kF8: {
// Restore from file // Restore from file
// TODO: Choose path from user // TODO: Choose path from user
// TODO: Spawn a new thread to do this. // TODO: Spawn a new thread to do this.
emulator()->RestoreFromFile("test.sav"); emulator()->RestoreFromFile("test.sav");
} break; } break;
case 0x7A: { // VK_F11 case ui::VirtualKey::kF11: {
ToggleFullscreen(); ToggleFullscreen();
} break; } break;
case 0x1B: { // VK_ESCAPE case ui::VirtualKey::kEscape: {
// Allow users to escape fullscreen (but not enter it). // Allow users to escape fullscreen (but not enter it).
if (window_->is_fullscreen()) { if (window_->is_fullscreen()) {
window_->ToggleFullscreen(false); window_->ToggleFullscreen(false);
} else { } else {
@ -143,18 +149,18 @@ bool EmulatorWindow::Initialize() {
} }
} break; } break;
case 0x13: { // VK_PAUSE case ui::VirtualKey::kPause: {
CpuBreakIntoDebugger(); CpuBreakIntoDebugger();
} break; } break;
case 0x03: { // VK_CANCEL case ui::VirtualKey::kCancel: {
CpuBreakIntoHostDebugger(); CpuBreakIntoHostDebugger();
} break; } break;
case 0x70: { // VK_F1 case ui::VirtualKey::kF1: {
ShowHelpWebsite(); ShowHelpWebsite();
} break; } break;
case 0x71: { // VK_F2 case ui::VirtualKey::kF2: {
ShowCommitID(); ShowCommitID();
} break; } break;
@ -425,8 +431,13 @@ void EmulatorWindow::ToggleFullscreen() {
void EmulatorWindow::ShowHelpWebsite() { LaunchWebBrowser("https://xenia.jp"); } void EmulatorWindow::ShowHelpWebsite() { LaunchWebBrowser("https://xenia.jp"); }
void EmulatorWindow::ShowCommitID() { void EmulatorWindow::ShowCommitID() {
#ifdef XE_BUILD_IS_PR
LaunchWebBrowser(
"https://github.com/xenia-project/xenia/pull/" XE_BUILD_PR_NUMBER);
#else
LaunchWebBrowser( LaunchWebBrowser(
"https://github.com/xenia-project/xenia/commit/" XE_BUILD_COMMIT "/"); "https://github.com/xenia-project/xenia/commit/" XE_BUILD_COMMIT "/");
#endif
} }
void EmulatorWindow::UpdateTitle() { void EmulatorWindow::UpdateTitle() {

View File

@ -43,7 +43,6 @@ project("xenia-app")
"mspack", "mspack",
"snappy", "snappy",
"spirv-tools", "spirv-tools",
"volk",
"xxhash", "xxhash",
}) })
defines({ defines({

View File

@ -329,6 +329,22 @@ int xenia_main(const std::vector<std::string>& args) {
emulator->file_system()->RegisterSymbolicLink("cache1:", "\\CACHE1"); emulator->file_system()->RegisterSymbolicLink("cache1:", "\\CACHE1");
} }
} }
// Some (older?) games try accessing cache:\ too
// NOTE: this must be registered _after_ the cache0/cache1 devices, due to
// substring/start_with logic inside VirtualFileSystem::ResolvePath, else
// accesses to those devices will go here instead
auto cache_device =
std::make_unique<xe::vfs::HostPathDevice>("\\CACHE", "cache", false);
if (!cache_device->Initialize()) {
XELOGE("Unable to scan cache path");
} else {
if (!emulator->file_system()->RegisterDevice(std::move(cache_device))) {
XELOGE("Unable to register cache path");
} else {
emulator->file_system()->RegisterSymbolicLink("cache:", "\\CACHE");
}
}
} }
// Set a debug handler. // Set a debug handler.

View File

@ -223,7 +223,7 @@ void AudioSystem::UnregisterClient(size_t index) {
} }
bool AudioSystem::Save(ByteStream* stream) { bool AudioSystem::Save(ByteStream* stream) {
stream->Write('XAUD'); stream->Write(kAudioSaveSignature);
// Count the number of used clients first. // Count the number of used clients first.
// Any gaps should be handled gracefully. // Any gaps should be handled gracefully.
@ -251,7 +251,7 @@ bool AudioSystem::Save(ByteStream* stream) {
} }
bool AudioSystem::Restore(ByteStream* stream) { bool AudioSystem::Restore(ByteStream* stream) {
if (stream->Read<uint32_t>() != 'XAUD') { if (stream->Read<uint32_t>() != kAudioSaveSignature) {
XELOGE("AudioSystem::Restore - Invalid magic value!"); XELOGE("AudioSystem::Restore - Invalid magic value!");
return false; return false;
} }

View File

@ -23,6 +23,8 @@
namespace xe { namespace xe {
namespace apu { namespace apu {
constexpr fourcc_t kAudioSaveSignature = make_fourcc("XAUD");
class AudioDriver; class AudioDriver;
class XmaDecoder; class XmaDecoder;

119
src/xenia/apu/conversion.h Normal file
View File

@ -0,0 +1,119 @@
/**
******************************************************************************
* 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_APU_CONVERSION_H_
#define XENIA_APU_CONVERSION_H_
#include <cstdint>
#include "xenia/base/byte_order.h"
#include "xenia/base/platform.h"
namespace xe {
namespace apu {
namespace conversion {
#if XE_ARCH_AMD64
inline void sequential_6_BE_to_interleaved_6_LE(float* output,
const float* input,
size_t ch_sample_count) {
const uint32_t* in = reinterpret_cast<const uint32_t*>(input);
uint32_t* out = reinterpret_cast<uint32_t*>(output);
const __m128i byte_swap_shuffle =
_mm_set_epi8(12, 13, 14, 15, 8, 9, 10, 11, 4, 5, 6, 7, 0, 1, 2, 3);
for (size_t sample = 0; sample < ch_sample_count; sample++) {
__m128i sample0 = _mm_set_epi32(
in[3 * ch_sample_count + sample], in[2 * ch_sample_count + sample],
in[1 * ch_sample_count + sample], in[0 * ch_sample_count + sample]);
uint32_t sample1 = in[4 * ch_sample_count + sample];
uint32_t sample2 = in[5 * ch_sample_count + sample];
sample0 = _mm_shuffle_epi8(sample0, byte_swap_shuffle);
_mm_storeu_si128(reinterpret_cast<__m128i*>(&out[sample * 6]), sample0);
sample1 = xe::byte_swap(sample1);
out[sample * 6 + 4] = sample1;
sample2 = xe::byte_swap(sample2);
out[sample * 6 + 5] = sample2;
}
}
inline void sequential_6_BE_to_interleaved_2_LE(float* output,
const float* input,
size_t ch_sample_count) {
assert_true(ch_sample_count % 4 == 0);
const uint32_t* in = reinterpret_cast<const uint32_t*>(input);
uint32_t* out = reinterpret_cast<uint32_t*>(output);
const __m128i byte_swap_shuffle =
_mm_set_epi8(12, 13, 14, 15, 8, 9, 10, 11, 4, 5, 6, 7, 0, 1, 2, 3);
const __m128 half = _mm_set1_ps(0.5f);
const __m128 two_fifths = _mm_set1_ps(1.0f / 2.5f);
// put center on left and right, discard low frequency
for (size_t sample = 0; sample < ch_sample_count; sample += 4) {
// load 4 samples from 6 channels each
__m128 fl = _mm_loadu_ps(&input[0 * ch_sample_count + sample]);
__m128 fr = _mm_loadu_ps(&input[1 * ch_sample_count + sample]);
__m128 fc = _mm_loadu_ps(&input[2 * ch_sample_count + sample]);
__m128 bl = _mm_loadu_ps(&input[4 * ch_sample_count + sample]);
__m128 br = _mm_loadu_ps(&input[5 * ch_sample_count + sample]);
// byte swap
fl = _mm_castsi128_ps(
_mm_shuffle_epi8(_mm_castps_si128(fl), byte_swap_shuffle));
fr = _mm_castsi128_ps(
_mm_shuffle_epi8(_mm_castps_si128(fr), byte_swap_shuffle));
fc = _mm_castsi128_ps(
_mm_shuffle_epi8(_mm_castps_si128(fc), byte_swap_shuffle));
bl = _mm_castsi128_ps(
_mm_shuffle_epi8(_mm_castps_si128(bl), byte_swap_shuffle));
br = _mm_castsi128_ps(
_mm_shuffle_epi8(_mm_castps_si128(br), byte_swap_shuffle));
__m128 center_halved = _mm_mul_ps(fc, half);
__m128 left = _mm_add_ps(_mm_add_ps(fl, bl), center_halved);
__m128 right = _mm_add_ps(_mm_add_ps(fr, br), center_halved);
left = _mm_mul_ps(left, two_fifths);
right = _mm_mul_ps(right, two_fifths);
_mm_storeu_ps(&output[sample * 2], _mm_unpacklo_ps(left, right));
_mm_storeu_ps(&output[(sample + 2) * 2], _mm_unpackhi_ps(left, right));
}
}
#else
inline void sequential_6_BE_to_interleaved_6_LE(float* output,
const float* input,
size_t ch_sample_count) {
for (size_t sample = 0; sample < ch_sample_count; sample++) {
for (size_t channel = 0; channel < 6; channel++) {
output[sample * 6 + channel] =
xe::byte_swap(input[channel * ch_sample_count + sample]);
}
}
}
inline void sequential_6_BE_to_interleaved_2_LE(float* output,
const float* input,
size_t ch_sample_count) {
// Default 5.1 channel mapping is fl, fr, fc, lf, bl, br
// https://docs.microsoft.com/en-us/windows/win32/xaudio2/xaudio2-default-channel-mapping
for (size_t sample = 0; sample < ch_sample_count; sample++) {
// put center on left and right, discard low frequency
float fl = xe::byte_swap(input[0 * ch_sample_count + sample]);
float fr = xe::byte_swap(input[1 * ch_sample_count + sample]);
float fc = xe::byte_swap(input[2 * ch_sample_count + sample]);
float br = xe::byte_swap(input[4 * ch_sample_count + sample]);
float bl = xe::byte_swap(input[5 * ch_sample_count + sample]);
float center_halved = fc * 0.5f;
output[sample * 2] = (fl + bl + center_halved) * (1.0f / 2.5f);
output[sample * 2 + 1] = (fr + br + center_halved) * (1.0f / 2.5f);
}
}
#endif
} // namespace conversion
} // namespace apu
} // namespace xe
#endif

View File

@ -10,9 +10,13 @@
#include "xenia/apu/sdl/sdl_audio_driver.h" #include "xenia/apu/sdl/sdl_audio_driver.h"
#include <array> #include <array>
#include <cstring>
#include "xenia/apu/apu_flags.h" #include "xenia/apu/apu_flags.h"
#include "xenia/apu/conversion.h"
#include "xenia/base/assert.h"
#include "xenia/base/logging.h" #include "xenia/base/logging.h"
#include "xenia/base/profiling.h"
#include "xenia/helper/sdl/sdl_helper.h" #include "xenia/helper/sdl/sdl_helper.h"
namespace xe { namespace xe {
@ -46,41 +50,37 @@ bool SDLAudioDriver::Initialize() {
} }
sdl_initialized_ = true; sdl_initialized_ = true;
SDL_AudioCallback audio_callback = [](void* userdata, Uint8* stream, SDL_AudioSpec desired_spec = {};
int len) -> void { SDL_AudioSpec obtained_spec;
assert_true(len == frame_size_); desired_spec.freq = frame_frequency_;
const auto driver = static_cast<SDLAudioDriver*>(userdata); desired_spec.format = AUDIO_F32;
desired_spec.channels = frame_channels_;
std::unique_lock<std::mutex> guard(driver->frames_mutex_); desired_spec.samples = channel_samples_;
if (driver->frames_queued_.empty()) { desired_spec.callback = SDLCallback;
memset(stream, 0, len); desired_spec.userdata = this;
} else { // Allow the hardware to decide between 5.1 and stereo
auto buffer = driver->frames_queued_.front(); int allowed_change = SDL_AUDIO_ALLOW_CHANNELS_CHANGE;
driver->frames_queued_.pop(); for (int i = 0; i < 2; i++) {
if (cvars::mute) { sdl_device_id_ = SDL_OpenAudioDevice(nullptr, 0, &desired_spec,
memset(stream, 0, len); &obtained_spec, allowed_change);
} else { if (sdl_device_id_ <= 0) {
memcpy(stream, buffer, len); XELOGE("SDL_OpenAudioDevice() failed.");
} return false;
driver->frames_unused_.push(buffer);
auto ret = driver->semaphore_->Release(1, nullptr);
assert_true(ret);
} }
}; if (obtained_spec.channels == 2 || obtained_spec.channels == 6) {
break;
SDL_AudioSpec wanted_spec = {}; }
wanted_spec.freq = frame_frequency_; // If the system is 4 or 7.1, let SDL convert
wanted_spec.format = AUDIO_F32; allowed_change = 0;
wanted_spec.channels = frame_channels_; SDL_CloseAudioDevice(sdl_device_id_);
wanted_spec.samples = channel_samples_; sdl_device_id_ = -1;
wanted_spec.callback = audio_callback; }
wanted_spec.userdata = this;
sdl_device_id_ = SDL_OpenAudioDevice(nullptr, 0, &wanted_spec, nullptr, 0);
if (sdl_device_id_ <= 0) { if (sdl_device_id_ <= 0) {
XELOGE("SDL_OpenAudioDevice() failed."); XELOGE("Failed to get a compatible SDL Audio Device.");
return false; return false;
} }
sdl_device_channels_ = obtained_spec.channels;
SDL_PauseAudioDevice(sdl_device_id_, 0); SDL_PauseAudioDevice(sdl_device_id_, 0);
return true; return true;
@ -99,13 +99,7 @@ void SDLAudioDriver::SubmitFrame(uint32_t frame_ptr) {
} }
} }
// interleave the data std::memcpy(output_frame, input_frame, frame_samples_ * sizeof(float));
for (size_t index = 0, o = 0; index < channel_samples_; ++index) {
for (size_t channel = 0, table = 0; channel < frame_channels_;
++channel, table += channel_samples_) {
output_frame[o++] = xe::byte_swap(input_frame[table + index]);
}
}
{ {
std::unique_lock<std::mutex> guard(frames_mutex_); std::unique_lock<std::mutex> guard(frames_mutex_);
@ -133,6 +127,45 @@ void SDLAudioDriver::Shutdown() {
}; };
} }
void SDLAudioDriver::SDLCallback(void* userdata, Uint8* stream, int len) {
SCOPE_profile_cpu_f("apu");
if (!userdata || !stream) {
XELOGE("SDLAudioDriver::sdl_callback called with nullptr.");
return;
}
const auto driver = static_cast<SDLAudioDriver*>(userdata);
assert_true(len ==
sizeof(float) * channel_samples_ * driver->sdl_device_channels_);
std::unique_lock<std::mutex> guard(driver->frames_mutex_);
if (driver->frames_queued_.empty()) {
std::memset(stream, 0, len);
} else {
auto buffer = driver->frames_queued_.front();
driver->frames_queued_.pop();
if (cvars::mute) {
std::memset(stream, 0, len);
} else {
switch (driver->sdl_device_channels_) {
case 2:
conversion::sequential_6_BE_to_interleaved_2_LE(
reinterpret_cast<float*>(stream), buffer, channel_samples_);
break;
case 6:
conversion::sequential_6_BE_to_interleaved_6_LE(
reinterpret_cast<float*>(stream), buffer, channel_samples_);
break;
default:
assert_unhandled_case(driver->sdl_device_channels_);
break;
}
}
driver->frames_unused_.push(buffer);
auto ret = driver->semaphore_->Release(1, nullptr);
assert_true(ret);
}
};
} // namespace sdl } // namespace sdl
} // namespace apu } // namespace apu
} // namespace xe } // namespace xe

View File

@ -32,10 +32,13 @@ class SDLAudioDriver : public AudioDriver {
void Shutdown(); void Shutdown();
protected: protected:
static void SDLCallback(void* userdata, Uint8* stream, int len);
xe::threading::Semaphore* semaphore_ = nullptr; xe::threading::Semaphore* semaphore_ = nullptr;
SDL_AudioDeviceID sdl_device_id_ = -1; SDL_AudioDeviceID sdl_device_id_ = -1;
bool sdl_initialized_ = false; bool sdl_initialized_ = false;
uint8_t sdl_device_channels_ = 0;
static const uint32_t frame_frequency_ = 48000; static const uint32_t frame_frequency_ = 48000;
static const uint32_t frame_channels_ = 6; static const uint32_t frame_channels_ = 6;

View File

@ -13,6 +13,7 @@
#include "xenia/base/platform_win.h" #include "xenia/base/platform_win.h"
#include "xenia/apu/apu_flags.h" #include "xenia/apu/apu_flags.h"
#include "xenia/apu/conversion.h"
#include "xenia/base/clock.h" #include "xenia/base/clock.h"
#include "xenia/base/logging.h" #include "xenia/base/logging.h"
@ -208,12 +209,8 @@ void XAudio2AudioDriver::SubmitFrame(uint32_t frame_ptr) {
auto interleave_channels = frame_channels_; auto interleave_channels = frame_channels_;
// interleave the data // interleave the data
for (uint32_t index = 0, o = 0; index < channel_samples_; ++index) { conversion::sequential_6_BE_to_interleaved_6_LE(output_frame, input_frame,
for (uint32_t channel = 0, table = 0; channel < interleave_channels; channel_samples_);
++channel, table += channel_samples_) {
output_frame[o++] = xe::byte_swap(input_frame[table + index]);
}
}
api::XAUDIO2_BUFFER buffer; api::XAUDIO2_BUFFER buffer;
buffer.Flags = 0; buffer.Flags = 0;

View File

@ -16,6 +16,7 @@
#include "xenia/apu/xma_helpers.h" #include "xenia/apu/xma_helpers.h"
#include "xenia/base/bit_stream.h" #include "xenia/base/bit_stream.h"
#include "xenia/base/logging.h" #include "xenia/base/logging.h"
#include "xenia/base/platform.h"
#include "xenia/base/profiling.h" #include "xenia/base/profiling.h"
#include "xenia/base/ring_buffer.h" #include "xenia/base/ring_buffer.h"
@ -351,14 +352,12 @@ void XmaContext::Decode(XMA_CONTEXT_DATA* data) {
output_rb.set_read_offset(output_read_offset); output_rb.set_read_offset(output_read_offset);
output_rb.set_write_offset(output_write_offset); output_rb.set_write_offset(output_write_offset);
int num_channels = data->is_stereo ? 2 : 1;
// We can only decode an entire frame and write it out at a time, so // We can only decode an entire frame and write it out at a time, so
// don't save any samples. // don't save any samples.
// TODO(JoelLinn): subframes when looping // TODO(JoelLinn): subframes when looping
size_t output_remaining_bytes = output_rb.write_count(); size_t output_remaining_bytes = output_rb.write_count();
output_remaining_bytes -= output_remaining_bytes -=
output_remaining_bytes % (kBytesPerFrameChannel * num_channels); output_remaining_bytes % (kBytesPerFrameChannel << data->is_stereo);
// is_dirty_ = true; // TODO // is_dirty_ = true; // TODO
// is_dirty_ = false; // TODO // is_dirty_ = false; // TODO
@ -486,7 +485,7 @@ void XmaContext::Decode(XMA_CONTEXT_DATA* data) {
std::tie(frame_count, frame_last_split) = GetPacketFrameCount(packet); std::tie(frame_count, frame_last_split) = GetPacketFrameCount(packet);
assert_true(frame_count >= 0); // TODO end assert_true(frame_count >= 0); // TODO end
PrepareDecoder(packet, data->sample_rate, num_channels); PrepareDecoder(packet, data->sample_rate, bool(data->is_stereo));
// Current frame is split to next packet: // Current frame is split to next packet:
bool frame_is_split = frame_last_split && (frame_idx >= frame_count - 1); bool frame_is_split = frame_last_split && (frame_idx >= frame_count - 1);
@ -580,11 +579,11 @@ void XmaContext::Decode(XMA_CONTEXT_DATA* data) {
// assert_true(frame_is_split == (frame_idx == -1)); // assert_true(frame_is_split == (frame_idx == -1));
// dump_raw(av_frame_, id()); // dump_raw(av_frame_, id());
ConvertFrame((const uint8_t**)av_frame_->data, num_channels, ConvertFrame((const uint8_t**)av_frame_->data, bool(data->is_stereo),
kSamplesPerFrame, raw_frame_.data()); raw_frame_.data());
// decoded_consumed_samples_ += kSamplesPerFrame; // decoded_consumed_samples_ += kSamplesPerFrame;
auto byte_count = kBytesPerFrameChannel * num_channels; auto byte_count = kBytesPerFrameChannel << data->is_stereo;
assert_true(output_remaining_bytes >= byte_count); assert_true(output_remaining_bytes >= byte_count);
output_rb.Write(raw_frame_.data(), byte_count); output_rb.Write(raw_frame_.data(), byte_count);
output_remaining_bytes -= byte_count; output_remaining_bytes -= byte_count;
@ -720,7 +719,7 @@ std::tuple<int, int> XmaContext::GetFrameNumber(uint8_t* block, size_t size,
int frame_idx = 0; int frame_idx = 0;
while (true) { while (true) {
if (stream.BitsRemaining() < 15) { if (stream.BitsRemaining() < 15) {
return {packet_idx, -1}; break;
} }
if (stream.offset_bits() == bit_offset) { if (stream.offset_bits() == bit_offset) {
@ -780,13 +779,15 @@ std::tuple<int, bool> XmaContext::GetPacketFrameCount(uint8_t* packet) {
} }
} }
int XmaContext::PrepareDecoder(uint8_t* packet, int sample_rate, int channels) { int XmaContext::PrepareDecoder(uint8_t* packet, int sample_rate,
bool is_two_channel) {
// Sanity check: Packet metadata is always 1 for XMA2/0 for XMA // Sanity check: Packet metadata is always 1 for XMA2/0 for XMA
assert_true((packet[2] & 0x7) == 1 || (packet[2] & 0x7) == 0); assert_true((packet[2] & 0x7) == 1 || (packet[2] & 0x7) == 0);
sample_rate = GetSampleRate(sample_rate); sample_rate = GetSampleRate(sample_rate);
// Re-initialize the context with new sample rate and channels. // Re-initialize the context with new sample rate and channels.
uint32_t channels = is_two_channel ? 2 : 1;
if (av_context_->sample_rate != sample_rate || if (av_context_->sample_rate != sample_rate ||
av_context_->channels != channels) { av_context_->channels != channels) {
// We have to reopen the codec so it'll realloc whatever data it needs. // We have to reopen the codec so it'll realloc whatever data it needs.
@ -805,30 +806,80 @@ int XmaContext::PrepareDecoder(uint8_t* packet, int sample_rate, int channels) {
return 0; return 0;
} }
bool XmaContext::ConvertFrame(const uint8_t** samples, int num_channels, void XmaContext::ConvertFrame(const uint8_t** samples, bool is_two_channel,
int num_samples, uint8_t* output_buffer) { uint8_t* output_buffer) {
// Loop through every sample, convert and drop it into the output array. // Loop through every sample, convert and drop it into the output array.
// If more than one channel, we need to interleave the samples from each // If more than one channel, we need to interleave the samples from each
// channel next to each other. // channel next to each other. Always saturate because FFmpeg output is
// TODO: This can definitely be optimized with AVX/SSE intrinsics! // not limited to [-1, 1] (for example 1.095 as seen in RDR)
uint32_t o = 0; constexpr float scale = (1 << 15) - 1;
for (int i = 0; i < num_samples; i++) { auto out = reinterpret_cast<int16_t*>(output_buffer);
for (int j = 0; j < num_channels; j++) {
// Select the appropriate array based on the current channel.
auto sample_array = reinterpret_cast<const float*>(samples[j]);
// Raw sample should be within [-1, 1]. // For testing of vectorized versions, stereo audio is common in Halo 3, since
// Clamp it, just in case. // the first menu frame; the intro cutscene also has more than 2 channels.
float raw_sample = xe::saturate(sample_array[i]); #if XE_ARCH_AMD64
static_assert(kSamplesPerFrame % 8 == 0);
// Convert the sample and output it in big endian. const auto in_channel_0 = reinterpret_cast<const float*>(samples[0]);
float scaled_sample = raw_sample * ((1 << 15) - 1); const __m128 scale_mm = _mm_set1_ps(scale);
int sample = static_cast<int>(scaled_sample); if (is_two_channel) {
xe::store_and_swap<uint16_t>(&output_buffer[o++ * 2], sample & 0xFFFF); const auto in_channel_1 = reinterpret_cast<const float*>(samples[1]);
const __m128i shufmask =
_mm_set_epi8(14, 15, 6, 7, 12, 13, 4, 5, 10, 11, 2, 3, 8, 9, 0, 1);
for (uint32_t i = 0; i < kSamplesPerFrame; i += 4) {
// Load 8 samples, 4 for each channel.
__m128 in_mm0 = _mm_loadu_ps(&in_channel_0[i]);
__m128 in_mm1 = _mm_loadu_ps(&in_channel_1[i]);
// Rescale.
in_mm0 = _mm_mul_ps(in_mm0, scale_mm);
in_mm1 = _mm_mul_ps(in_mm1, scale_mm);
// Cast to int32.
__m128i out_mm0 = _mm_cvtps_epi32(in_mm0);
__m128i out_mm1 = _mm_cvtps_epi32(in_mm1);
// Saturated cast and pack to int16.
__m128i out_mm = _mm_packs_epi32(out_mm0, out_mm1);
// Interleave channels and byte swap.
out_mm = _mm_shuffle_epi8(out_mm, shufmask);
// Store, as [out + i * 4] movdqu.
_mm_storeu_si128(reinterpret_cast<__m128i*>(&out[i * 2]), out_mm);
}
} else {
const __m128i shufmask =
_mm_set_epi8(14, 15, 12, 13, 10, 11, 8, 9, 6, 7, 4, 5, 2, 3, 0, 1);
for (uint32_t i = 0; i < kSamplesPerFrame; i += 8) {
// Load 8 samples, as [in_channel_0 + i * 4] and
// [in_channel_0 + i * 4 + 16] movups.
__m128 in_mm0 = _mm_loadu_ps(&in_channel_0[i]);
__m128 in_mm1 = _mm_loadu_ps(&in_channel_0[i + 4]);
// Rescale.
in_mm0 = _mm_mul_ps(in_mm0, scale_mm);
in_mm1 = _mm_mul_ps(in_mm1, scale_mm);
// Cast to int32.
__m128i out_mm0 = _mm_cvtps_epi32(in_mm0);
__m128i out_mm1 = _mm_cvtps_epi32(in_mm1);
// Saturated cast and pack to int16.
__m128i out_mm = _mm_packs_epi32(out_mm0, out_mm1);
// Byte swap.
out_mm = _mm_shuffle_epi8(out_mm, shufmask);
// Store, as [out + i * 2] movdqu.
_mm_storeu_si128(reinterpret_cast<__m128i*>(&out[i]), out_mm);
} }
} }
#else
uint32_t o = 0;
for (uint32_t i = 0; i < kSamplesPerFrame; i++) {
for (uint32_t j = 0; j <= uint32_t(is_two_channel); j++) {
// Select the appropriate array based on the current channel.
auto in = reinterpret_cast<const float*>(samples[j]);
return true; // Raw samples sometimes aren't within [-1, 1]
float scaled_sample = xe::saturate_signed(in[i]) * scale;
// Convert the sample and output it in big endian.
auto sample = static_cast<int16_t>(scaled_sample);
out[o++] = xe::byte_swap(sample);
}
}
#endif
} }
} // namespace apu } // namespace apu

View File

@ -186,13 +186,13 @@ class XmaContext {
static std::tuple<int, bool> GetPacketFrameCount(uint8_t* packet); static std::tuple<int, bool> GetPacketFrameCount(uint8_t* packet);
// Convert sample format and swap bytes // Convert sample format and swap bytes
static bool ConvertFrame(const uint8_t** samples, int num_channels, static void ConvertFrame(const uint8_t** samples, bool is_two_channel,
int num_samples, uint8_t* output_buffer); uint8_t* output_buffer);
bool ValidFrameOffset(uint8_t* block, size_t size_bytes, bool ValidFrameOffset(uint8_t* block, size_t size_bytes,
size_t frame_offset_bits); size_t frame_offset_bits);
void Decode(XMA_CONTEXT_DATA* data); void Decode(XMA_CONTEXT_DATA* data);
int PrepareDecoder(uint8_t* packet, int sample_rate, int channels); int PrepareDecoder(uint8_t* packet, int sample_rate, bool is_two_channel);
Memory* memory_ = nullptr; Memory* memory_ = nullptr;

View File

@ -123,7 +123,7 @@ X_STATUS XmaDecoder::Setup(kernel::KernelState* kernel_state) {
sizeof(XMA_CONTEXT_DATA) * kContextCount, 256, kSystemHeapPhysical); sizeof(XMA_CONTEXT_DATA) * kContextCount, 256, kSystemHeapPhysical);
context_data_last_ptr_ = context_data_last_ptr_ =
context_data_first_ptr_ + (sizeof(XMA_CONTEXT_DATA) * kContextCount - 1); context_data_first_ptr_ + (sizeof(XMA_CONTEXT_DATA) * kContextCount - 1);
register_file_[XE_XMA_REG_CONTEXT_ARRAY_ADDRESS].u32 = register_file_[XmaRegister::ContextArrayAddress] =
memory()->GetPhysicalAddress(context_data_first_ptr_); memory()->GetPhysicalAddress(context_data_first_ptr_);
// Setup XMA contexts. // Setup XMA contexts.
@ -134,7 +134,7 @@ X_STATUS XmaDecoder::Setup(kernel::KernelState* kernel_state) {
assert_always(); assert_always();
} }
} }
register_file_[XE_XMA_REG_NEXT_CONTEXT_INDEX].u32 = 1; register_file_[XmaRegister::NextContextIndex] = 1;
context_bitmap_.Resize(kContextCount); context_bitmap_.Resize(kContextCount);
worker_running_ = true; worker_running_ = true;
@ -254,27 +254,29 @@ bool XmaDecoder::BlockOnContext(uint32_t guest_ptr, bool poll) {
} }
uint32_t XmaDecoder::ReadRegister(uint32_t addr) { uint32_t XmaDecoder::ReadRegister(uint32_t addr) {
uint32_t r = (addr & 0xFFFF) / 4; auto r = (addr & 0xFFFF) / 4;
assert_true(r < XmaRegisterFile::kRegisterCount); assert_true(r < XmaRegisterFile::kRegisterCount);
switch (r) { switch (r) {
case XE_XMA_REG_CURRENT_CONTEXT_INDEX: { case XmaRegister::ContextArrayAddress:
break;
case XmaRegister::CurrentContextIndex: {
// 0606h (1818h) is rotating context processing # set to hardware ID of // 0606h (1818h) is rotating context processing # set to hardware ID of
// context being processed. // context being processed.
// If bit 200h is set, the locking code will possibly collide on hardware // If bit 200h is set, the locking code will possibly collide on hardware
// IDs and error out, so we should never set it (I think?). // IDs and error out, so we should never set it (I think?).
uint32_t& current_context_index = uint32_t& current_context_index =
register_file_[XE_XMA_REG_CURRENT_CONTEXT_INDEX].u32; register_file_[XmaRegister::CurrentContextIndex];
uint32_t& next_context_index = uint32_t& next_context_index =
register_file_[XE_XMA_REG_NEXT_CONTEXT_INDEX].u32; register_file_[XmaRegister::NextContextIndex];
// To prevent games from seeing a stuck XMA context, return a rotating // To prevent games from seeing a stuck XMA context, return a rotating
// number. // number.
current_context_index = next_context_index; current_context_index = next_context_index;
next_context_index = (next_context_index + 1) % kContextCount; next_context_index = (next_context_index + 1) % kContextCount;
break; break;
} }
default: { default:
const auto register_info = register_file_.GetRegisterInfo(r); const auto register_info = register_file_.GetRegisterInfo(r);
if (register_info) { if (register_info) {
XELOGW("XMA: Read from unhandled register ({:04X}, {})", r, XELOGW("XMA: Read from unhandled register ({:04X}, {})", r,
@ -283,10 +285,9 @@ uint32_t XmaDecoder::ReadRegister(uint32_t addr) {
XELOGW("XMA: Read from unknown register ({:04X})", r); XELOGW("XMA: Read from unknown register ({:04X})", r);
} }
break; break;
}
} }
return xe::byte_swap(register_file_.values[r].u32); return xe::byte_swap(register_file_[r]);
} }
void XmaDecoder::WriteRegister(uint32_t addr, uint32_t value) { void XmaDecoder::WriteRegister(uint32_t addr, uint32_t value) {
@ -296,16 +297,16 @@ void XmaDecoder::WriteRegister(uint32_t addr, uint32_t value) {
value = xe::byte_swap(value); value = xe::byte_swap(value);
assert_true(r < XmaRegisterFile::kRegisterCount); assert_true(r < XmaRegisterFile::kRegisterCount);
register_file_.values[r].u32 = value; register_file_[r] = value;
if (r >= XE_XMA_REG_CONTEXT_KICK_0 && r <= XE_XMA_REG_CONTEXT_KICK_9) { if (r >= XmaRegister::Context0Kick && r <= XmaRegister::Context9Kick) {
// Context kick command. // Context kick command.
// This will kick off the given hardware contexts. // This will kick off the given hardware contexts.
// Basically, this kicks the SPU and says "hey, decode that audio!" // Basically, this kicks the SPU and says "hey, decode that audio!"
// XMAEnableContext // XMAEnableContext
// The context ID is a bit in the range of the entire context array. // The context ID is a bit in the range of the entire context array.
uint32_t base_context_id = (r - XE_XMA_REG_CONTEXT_KICK_0) * 32; uint32_t base_context_id = (r - XmaRegister::Context0Kick) * 32;
for (int i = 0; value && i < 32; ++i, value >>= 1) { for (int i = 0; value && i < 32; ++i, value >>= 1) {
if (value & 1) { if (value & 1) {
uint32_t context_id = base_context_id + i; uint32_t context_id = base_context_id + i;
@ -315,11 +316,11 @@ void XmaDecoder::WriteRegister(uint32_t addr, uint32_t value) {
} }
// Signal the decoder thread to start processing. // Signal the decoder thread to start processing.
work_event_->Set(); work_event_->Set();
} else if (r >= XE_XMA_REG_CONTEXT_LOCK_0 && r <= XE_XMA_REG_CONTEXT_LOCK_9) { } else if (r >= XmaRegister::Context0Lock && r <= XmaRegister::Context9Lock) {
// Context lock command. // Context lock command.
// This requests a lock by flagging the context. // This requests a lock by flagging the context.
// XMADisableContext // XMADisableContext
uint32_t base_context_id = (r - XE_XMA_REG_CONTEXT_LOCK_0) * 32; uint32_t base_context_id = (r - XmaRegister::Context0Lock) * 32;
for (int i = 0; value && i < 32; ++i, value >>= 1) { for (int i = 0; value && i < 32; ++i, value >>= 1) {
if (value & 1) { if (value & 1) {
uint32_t context_id = base_context_id + i; uint32_t context_id = base_context_id + i;
@ -328,12 +329,12 @@ void XmaDecoder::WriteRegister(uint32_t addr, uint32_t value) {
} }
} }
// Signal the decoder thread to start processing. // Signal the decoder thread to start processing.
work_event_->Set(); // work_event_->Set();
} else if (r >= XE_XMA_REG_CONTEXT_CLEAR_0 && } else if (r >= XmaRegister::Context0Clear &&
r <= XE_XMA_REG_CONTEXT_CLEAR_9) { r <= XmaRegister::Context9Clear) {
// Context clear command. // Context clear command.
// This will reset the given hardware contexts. // This will reset the given hardware contexts.
uint32_t base_context_id = (r - XE_XMA_REG_CONTEXT_CLEAR_0) * 32; uint32_t base_context_id = (r - XmaRegister::Context0Clear) * 32;
for (int i = 0; value && i < 32; ++i, value >>= 1) { for (int i = 0; value && i < 32; ++i, value >>= 1) {
if (value & 1) { if (value & 1) {
uint32_t context_id = base_context_id + i; uint32_t context_id = base_context_id + i;

View File

@ -2,7 +2,7 @@
****************************************************************************** ******************************************************************************
* Xenia : Xbox 360 Emulator Research Project * * Xenia : Xbox 360 Emulator Research Project *
****************************************************************************** ******************************************************************************
* Copyright 2013 Ben Vanik. All rights reserved. * * Copyright 2021 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. * * Released under the BSD license - see LICENSE in the root for more details. *
****************************************************************************** ******************************************************************************
*/ */
@ -37,7 +37,7 @@ class XmaDecoder {
void Shutdown(); void Shutdown();
uint32_t context_array_ptr() const { uint32_t context_array_ptr() const {
return register_file_.values[XE_XMA_REG_CONTEXT_ARRAY_ADDRESS].u32; return register_file_[XmaRegister::ContextArrayAddress];
} }
uint32_t AllocateContext(); uint32_t AllocateContext();

View File

@ -2,7 +2,7 @@
****************************************************************************** ******************************************************************************
* Xenia : Xbox 360 Emulator Research Project * * Xenia : Xbox 360 Emulator Research Project *
****************************************************************************** ******************************************************************************
* Copyright 2014 Ben Vanik. All rights reserved. * * Copyright 2021 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. * * Released under the BSD license - see LICENSE in the root for more details. *
****************************************************************************** ******************************************************************************
*/ */
@ -20,10 +20,9 @@ XmaRegisterFile::XmaRegisterFile() { std::memset(values, 0, sizeof(values)); }
const XmaRegisterInfo* XmaRegisterFile::GetRegisterInfo(uint32_t index) { const XmaRegisterInfo* XmaRegisterFile::GetRegisterInfo(uint32_t index) {
switch (index) { switch (index) {
#define XE_XMA_REGISTER(index, type, name) \ #define XE_XMA_REGISTER(index, name) \
case index: { \ case index: { \
static const XmaRegisterInfo reg_info = { \ static const XmaRegisterInfo reg_info = { \
XmaRegisterInfo::Type::type, \
#name, \ #name, \
}; \ }; \
return &reg_info; \ return &reg_info; \

View File

@ -2,7 +2,7 @@
****************************************************************************** ******************************************************************************
* Xenia : Xbox 360 Emulator Research Project * * Xenia : Xbox 360 Emulator Research Project *
****************************************************************************** ******************************************************************************
* Copyright 2014 Ben Vanik. All rights reserved. * * Copyright 2021 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. * * Released under the BSD license - see LICENSE in the root for more details. *
****************************************************************************** ******************************************************************************
*/ */
@ -16,18 +16,13 @@
namespace xe { namespace xe {
namespace apu { namespace apu {
enum XmaRegister { struct XmaRegister {
#define XE_XMA_REGISTER(index, type, name) XE_XMA_REG_##name = index, #define XE_XMA_REGISTER(index, name) static const uint32_t name = index;
#include "xenia/apu/xma_register_table.inc" #include "xenia/apu/xma_register_table.inc"
#undef XE_XMA_REGISTER #undef XE_XMA_REGISTER
}; };
struct XmaRegisterInfo { struct XmaRegisterInfo {
enum class Type {
kDword,
kFloat,
};
Type type;
const char* name; const char* name;
}; };
@ -38,14 +33,10 @@ class XmaRegisterFile {
static const XmaRegisterInfo* GetRegisterInfo(uint32_t index); static const XmaRegisterInfo* GetRegisterInfo(uint32_t index);
static const size_t kRegisterCount = (0xFFFF + 1) / 4; static const size_t kRegisterCount = (0xFFFF + 1) / 4;
union RegisterValue { uint32_t values[kRegisterCount];
uint32_t u32;
float f32;
};
RegisterValue values[kRegisterCount];
RegisterValue& operator[](int reg) { return values[reg]; } uint32_t operator[](uint32_t reg) const { return values[reg]; }
RegisterValue& operator[](XmaRegister reg) { return values[reg]; } uint32_t& operator[](uint32_t reg) { return values[reg]; }
}; };
} // namespace apu } // namespace apu

View File

@ -2,7 +2,7 @@
****************************************************************************** ******************************************************************************
* Xenia : Xbox 360 Emulator Research Project * * Xenia : Xbox 360 Emulator Research Project *
****************************************************************************** ******************************************************************************
* Copyright 2013 Ben Vanik. All rights reserved. * * Copyright 2021 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. * * Released under the BSD license - see LICENSE in the root for more details. *
****************************************************************************** ******************************************************************************
*/ */
@ -10,42 +10,72 @@
// This is a partial file designed to be included by other files when // This is a partial file designed to be included by other files when
// constructing various tables. // constructing various tables.
//#define XE_XMA_REGISTER(index, type, name) #ifndef XE_XMA_REGISTER
#define XE_XMA_REGISTER(index, name)
#define __XE_XMA_REGISTER_UNSET
#endif
XE_XMA_REGISTER(0x0600, kDword, CONTEXT_ARRAY_ADDRESS) #ifndef XE_XMA_REGISTER_CONTEXT_GROUP
#define XE_XMA_REGISTER_CONTEXT_GROUP(index, suffix) \
XE_XMA_REGISTER(index + 0, Context0##suffix) \
XE_XMA_REGISTER(index + 1, Context1##suffix) \
XE_XMA_REGISTER(index + 2, Context2##suffix) \
XE_XMA_REGISTER(index + 3, Context3##suffix) \
XE_XMA_REGISTER(index + 4, Context4##suffix) \
XE_XMA_REGISTER(index + 5, Context5##suffix) \
XE_XMA_REGISTER(index + 6, Context6##suffix) \
XE_XMA_REGISTER(index + 7, Context7##suffix) \
XE_XMA_REGISTER(index + 8, Context8##suffix) \
XE_XMA_REGISTER(index + 9, Context9##suffix)
#endif
XE_XMA_REGISTER(0x0606, kDword, CURRENT_CONTEXT_INDEX) // 0x0000..0x001F : ???
XE_XMA_REGISTER(0x0607, kDword, NEXT_CONTEXT_INDEX) // 0x0020..0x03FF : all 0xFFs?
// 0x0400..0x043F : ???
// 0x0440..0x047F : all 0xFFs?
// 0x0480..0x048B : ???
// 0x048C..0x04C0 : all 0xFFs?
// 0x04C1..0x04CB : ???
// 0x04CC..0x04FF : all 0xFFs?
// 0x0500..0x051F : ???
// 0x0520..0x057F : all 0xFFs?
// 0x0580..0x058F : ???
// 0x0590..0x05FF : all 0xFFs?
XE_XMA_REGISTER(0x0650, kDword, CONTEXT_KICK_0) // XMA stuff is probably only 0x0600..0x06FF
XE_XMA_REGISTER(0x0651, kDword, CONTEXT_KICK_1) //---------------------------------------------------------------------------//
XE_XMA_REGISTER(0x0652, kDword, CONTEXT_KICK_2)
XE_XMA_REGISTER(0x0653, kDword, CONTEXT_KICK_3)
XE_XMA_REGISTER(0x0654, kDword, CONTEXT_KICK_4)
XE_XMA_REGISTER(0x0655, kDword, CONTEXT_KICK_5)
XE_XMA_REGISTER(0x0656, kDword, CONTEXT_KICK_6)
XE_XMA_REGISTER(0x0657, kDword, CONTEXT_KICK_7)
XE_XMA_REGISTER(0x0658, kDword, CONTEXT_KICK_8)
XE_XMA_REGISTER(0x0659, kDword, CONTEXT_KICK_9)
XE_XMA_REGISTER(0x0690, kDword, CONTEXT_LOCK_0) XE_XMA_REGISTER(0x0600, ContextArrayAddress)
XE_XMA_REGISTER(0x0691, kDword, CONTEXT_LOCK_1) // 0x0601..0x0605 : ???
XE_XMA_REGISTER(0x0692, kDword, CONTEXT_LOCK_2) XE_XMA_REGISTER(0x0606, CurrentContextIndex)
XE_XMA_REGISTER(0x0693, kDword, CONTEXT_LOCK_3) XE_XMA_REGISTER(0x0607, NextContextIndex)
XE_XMA_REGISTER(0x0694, kDword, CONTEXT_LOCK_4) // 0x0608 : ???
XE_XMA_REGISTER(0x0695, kDword, CONTEXT_LOCK_5) // 0x0609..0x060F : zero?
XE_XMA_REGISTER(0x0696, kDword, CONTEXT_LOCK_6) XE_XMA_REGISTER_CONTEXT_GROUP(0x0610, Unknown610)
XE_XMA_REGISTER(0x0697, kDword, CONTEXT_LOCK_7) // 0x061A..0x061F : zero?
XE_XMA_REGISTER(0x0698, kDword, CONTEXT_LOCK_8) XE_XMA_REGISTER_CONTEXT_GROUP(0x0620, Unknown620)
XE_XMA_REGISTER(0x0699, kDword, CONTEXT_LOCK_9) // 0x062A..0x0641 : zero?
// 0x0642..0x0644 : ???
// 0x0645..0x064F : zero?
XE_XMA_REGISTER_CONTEXT_GROUP(0x0650, Kick)
// 0x065A..0x065F : zero?
XE_XMA_REGISTER_CONTEXT_GROUP(0x0660, Unknown660)
// 0x066A..0x0681 : zero?
// 0x0682..0x0684 : ???
// 0x0685..0x068F : zero?
XE_XMA_REGISTER_CONTEXT_GROUP(0x0690, Lock)
// 0x069A..0x069F : zero?
XE_XMA_REGISTER_CONTEXT_GROUP(0x06A0, Clear)
XE_XMA_REGISTER(0x06A0, kDword, CONTEXT_CLEAR_0) //---------------------------------------------------------------------------//
XE_XMA_REGISTER(0x06A1, kDword, CONTEXT_CLEAR_1)
XE_XMA_REGISTER(0x06A2, kDword, CONTEXT_CLEAR_2) // 0x0700..0x07FF : all 0xFFs?
XE_XMA_REGISTER(0x06A3, kDword, CONTEXT_CLEAR_3) // 0x0800..0x17FF : ???
XE_XMA_REGISTER(0x06A4, kDword, CONTEXT_CLEAR_4) // 0x1800..0x2FFF : all 0xFFs?
XE_XMA_REGISTER(0x06A5, kDword, CONTEXT_CLEAR_5) // 0x3000..0x30FF : ???
XE_XMA_REGISTER(0x06A6, kDword, CONTEXT_CLEAR_6) // 0x3100..0x3FFF : all 0xFFs?
XE_XMA_REGISTER(0x06A7, kDword, CONTEXT_CLEAR_7)
XE_XMA_REGISTER(0x06A8, kDword, CONTEXT_CLEAR_8) #ifdef __XE_XMA_REGISTER_UNSET
XE_XMA_REGISTER(0x06A9, kDword, CONTEXT_CLEAR_9) #undef __XE_XMA_REGISTER_UNSET
#undef XE_XMA_REGISTER
#endif

View File

@ -2,7 +2,7 @@
****************************************************************************** ******************************************************************************
* Xenia : Xbox 360 Emulator Research Project * * Xenia : Xbox 360 Emulator Research Project *
****************************************************************************** ******************************************************************************
* Copyright 2013 Ben Vanik. All rights reserved. * * Copyright 2021 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. * * Released under the BSD license - see LICENSE in the root for more details. *
****************************************************************************** ******************************************************************************
*/ */
@ -13,6 +13,7 @@
#include <memory> #include <memory>
#include "xenia/base/assert.h" #include "xenia/base/assert.h"
#include "xenia/base/math.h"
namespace xe { namespace xe {
@ -45,12 +46,25 @@ void Arena::DebugFill() {
} }
} }
void* Arena::Alloc(size_t size) { void* Arena::Alloc(size_t size, size_t align) {
assert_true(
xe::bit_count(align) == 1 && align <= 16,
"align needs to be a power of 2 and not greater than Chunk alignment");
// for alignment
const auto get_padding = [this, align]() -> size_t {
const size_t mask = align - 1;
size_t deviation = active_chunk_->offset & mask;
return (align - deviation) & mask;
};
if (active_chunk_) { if (active_chunk_) {
if (active_chunk_->capacity - active_chunk_->offset < size + 4096) { if (active_chunk_->capacity - active_chunk_->offset <
size + get_padding() + 4096) {
Chunk* next = active_chunk_->next; Chunk* next = active_chunk_->next;
if (!next) { if (!next) {
assert_true(size < chunk_size_, "need to support larger chunks"); assert_true(size + get_padding() < chunk_size_,
"need to support larger chunks");
next = new Chunk(chunk_size_); next = new Chunk(chunk_size_);
active_chunk_->next = next; active_chunk_->next = next;
} }
@ -61,8 +75,11 @@ void* Arena::Alloc(size_t size) {
head_chunk_ = active_chunk_ = new Chunk(chunk_size_); head_chunk_ = active_chunk_ = new Chunk(chunk_size_);
} }
active_chunk_->offset += get_padding();
uint8_t* p = active_chunk_->buffer + active_chunk_->offset; uint8_t* p = active_chunk_->buffer + active_chunk_->offset;
active_chunk_->offset += size; active_chunk_->offset += size;
assert_true((reinterpret_cast<size_t>(p) & (align - 1)) == 0,
"alignment failed");
return p; return p;
} }
@ -113,6 +130,8 @@ void Arena::CloneContents(void* buffer, size_t buffer_length) {
Arena::Chunk::Chunk(size_t chunk_size) Arena::Chunk::Chunk(size_t chunk_size)
: next(nullptr), capacity(chunk_size), buffer(0), offset(0) { : next(nullptr), capacity(chunk_size), buffer(0), offset(0) {
buffer = reinterpret_cast<uint8_t*>(malloc(capacity)); buffer = reinterpret_cast<uint8_t*>(malloc(capacity));
assert_true((reinterpret_cast<size_t>(buffer) & size_t(15)) == 0,
"16 byte alignment required");
} }
Arena::Chunk::~Chunk() { Arena::Chunk::~Chunk() {

View File

@ -2,7 +2,7 @@
****************************************************************************** ******************************************************************************
* Xenia : Xbox 360 Emulator Research Project * * Xenia : Xbox 360 Emulator Research Project *
****************************************************************************** ******************************************************************************
* Copyright 2013 Ben Vanik. All rights reserved. * * Copyright 2021 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. * * Released under the BSD license - see LICENSE in the root for more details. *
****************************************************************************** ******************************************************************************
*/ */
@ -24,11 +24,13 @@ class Arena {
void Reset(); void Reset();
void DebugFill(); void DebugFill();
void* Alloc(size_t size); void* Alloc(size_t size, size_t align);
template <typename T> template <typename T>
T* Alloc() { T* Alloc() {
return reinterpret_cast<T*>(Alloc(sizeof(T))); return reinterpret_cast<T*>(Alloc(sizeof(T), alignof(T)));
} }
// When rewinding aligned allocations, any padding that was applied during
// allocation will be leaked
void Rewind(size_t size); void Rewind(size_t size);
void* CloneContents(); void* CloneContents();

View File

@ -11,103 +11,116 @@
#define XENIA_BASE_BYTE_ORDER_H_ #define XENIA_BASE_BYTE_ORDER_H_
#include <cstdint> #include <cstdint>
#if defined __has_include
#if __has_include(<version>)
#include <version>
#endif
#endif
#if __cpp_lib_endian
#include <bit>
#endif
#include "xenia/base/assert.h" #include "xenia/base/assert.h"
#include "xenia/base/platform.h" #include "xenia/base/platform.h"
#if XE_PLATFORM_LINUX #if !__cpp_lib_endian
#include <byteswap.h> // Polyfill
#ifdef __BYTE_ORDER__
namespace std {
enum class endian {
little = __ORDER_LITTLE_ENDIAN__,
big = __ORDER_BIG_ENDIAN__,
native = __BYTE_ORDER__
};
}
#else
// Hardcode to little endian for now
namespace std {
enum class endian { little = 0, big = 1, native = 0 };
}
#endif #endif
#endif
// Check for mixed endian
static_assert((std::endian::native == std::endian::big) ||
(std::endian::native == std::endian::little));
namespace xe { namespace xe {
#if XE_PLATFORM_WIN32 #if XE_COMPILER_MSVC
#define XENIA_BASE_BYTE_SWAP_16 _byteswap_ushort #define XENIA_BASE_BYTE_SWAP_16 _byteswap_ushort
#define XENIA_BASE_BYTE_SWAP_32 _byteswap_ulong #define XENIA_BASE_BYTE_SWAP_32 _byteswap_ulong
#define XENIA_BASE_BYTE_SWAP_64 _byteswap_uint64 #define XENIA_BASE_BYTE_SWAP_64 _byteswap_uint64
#elif XE_PLATFORM_MAC
#define XENIA_BASE_BYTE_SWAP_16 OSSwapInt16
#define XENIA_BASE_BYTE_SWAP_32 OSSwapInt32
#define XENIA_BASE_BYTE_SWAP_64 OSSwapInt64
#else #else
#define XENIA_BASE_BYTE_SWAP_16 bswap_16 #define XENIA_BASE_BYTE_SWAP_16 __builtin_bswap16
#define XENIA_BASE_BYTE_SWAP_32 bswap_32 #define XENIA_BASE_BYTE_SWAP_32 __builtin_bswap32
#define XENIA_BASE_BYTE_SWAP_64 bswap_64 #define XENIA_BASE_BYTE_SWAP_64 __builtin_bswap64
#endif // XE_PLATFORM_WIN32 #endif // XE_PLATFORM_WIN32
inline int8_t byte_swap(int8_t value) { return value; } template <class T>
inline uint8_t byte_swap(uint8_t value) { return value; }
inline int16_t byte_swap(int16_t value) {
return static_cast<int16_t>(
XENIA_BASE_BYTE_SWAP_16(static_cast<int16_t>(value)));
}
inline uint16_t byte_swap(uint16_t value) {
return XENIA_BASE_BYTE_SWAP_16(value);
}
inline uint16_t byte_swap(char16_t value) {
return static_cast<char16_t>(XENIA_BASE_BYTE_SWAP_16(value));
}
inline int32_t byte_swap(int32_t value) {
return static_cast<int32_t>(
XENIA_BASE_BYTE_SWAP_32(static_cast<int32_t>(value)));
}
inline uint32_t byte_swap(uint32_t value) {
return XENIA_BASE_BYTE_SWAP_32(value);
}
inline int64_t byte_swap(int64_t value) {
return static_cast<int64_t>(
XENIA_BASE_BYTE_SWAP_64(static_cast<int64_t>(value)));
}
inline uint64_t byte_swap(uint64_t value) {
return XENIA_BASE_BYTE_SWAP_64(value);
}
inline float byte_swap(float value) {
uint32_t temp = byte_swap(*reinterpret_cast<uint32_t*>(&value));
return *reinterpret_cast<float*>(&temp);
}
inline double byte_swap(double value) {
uint64_t temp = byte_swap(*reinterpret_cast<uint64_t*>(&value));
return *reinterpret_cast<double*>(&temp);
}
template <typename T>
inline T byte_swap(T value) { inline T byte_swap(T value) {
if (sizeof(T) == 4) { static_assert(
return static_cast<T>(byte_swap(static_cast<uint32_t>(value))); sizeof(T) == 8 || sizeof(T) == 4 || sizeof(T) == 2 || sizeof(T) == 1,
} else if (sizeof(T) == 2) { "byte_swap(T value): Type T has illegal size");
return static_cast<T>(byte_swap(static_cast<uint16_t>(value))); if constexpr (sizeof(T) == 8) {
} else { uint64_t temp =
assert_always("not handled"); XENIA_BASE_BYTE_SWAP_64(*reinterpret_cast<uint64_t*>(&value));
return *reinterpret_cast<T*>(&temp);
} else if constexpr (sizeof(T) == 4) {
uint32_t temp =
XENIA_BASE_BYTE_SWAP_32(*reinterpret_cast<uint32_t*>(&value));
return *reinterpret_cast<T*>(&temp);
} else if constexpr (sizeof(T) == 2) {
uint16_t temp =
XENIA_BASE_BYTE_SWAP_16(*reinterpret_cast<uint16_t*>(&value));
return *reinterpret_cast<T*>(&temp);
} else if constexpr (sizeof(T) == 1) {
return value;
} }
} }
template <typename T> template <typename T, std::endian E>
struct be { struct endian_store {
be() = default; endian_store() = default;
be(const T& src) : value(xe::byte_swap(src)) {} // NOLINT(runtime/explicit) endian_store(const T& src) { set(src); }
be(const be& other) { value = other.value; } // NOLINT(runtime/explicit) endian_store(const endian_store& other) { set(other); }
operator T() const { return xe::byte_swap(value); } operator T() const { return get(); }
be<T>& operator+=(int a) { void set(const T& src) {
if constexpr (std::endian::native == E) {
value = src;
} else {
value = xe::byte_swap(src);
}
}
void set(const endian_store& other) { value = other.value; }
T get() const {
if constexpr (std::endian::native == E) {
return value;
}
return xe::byte_swap(value);
}
endian_store<T, E>& operator+=(int a) {
*this = *this + a; *this = *this + a;
return *this; return *this;
} }
be<T>& operator-=(int a) { endian_store<T, E>& operator-=(int a) {
*this = *this - a; *this = *this - a;
return *this; return *this;
} }
be<T>& operator++() { endian_store<T, E>& operator++() {
*this += 1; *this += 1;
return *this; return *this;
} // ++a } // ++a
be<T> operator++(int) { endian_store<T, E> operator++(int) {
*this += 1; *this += 1;
return (*this - 1); return (*this - 1);
} // a++ } // a++
be<T>& operator--() { endian_store<T, E>& operator--() {
*this -= 1; *this -= 1;
return *this; return *this;
} // --a } // --a
be<T> operator--(int) { endian_store<T, E> operator--(int) {
*this -= 1; *this -= 1;
return (*this + 1); return (*this + 1);
} // a-- } // a--
@ -115,6 +128,11 @@ struct be {
T value; T value;
}; };
template <typename T>
using be = endian_store<T, std::endian::big>;
template <typename T>
using le = endian_store<T, std::endian::little>;
} // namespace xe } // namespace xe
#endif // XENIA_BASE_BYTE_ORDER_H_ #endif // XENIA_BASE_BYTE_ORDER_H_

View File

@ -20,18 +20,19 @@ ByteStream::ByteStream(uint8_t* data, size_t data_length, size_t offset)
ByteStream::~ByteStream() = default; ByteStream::~ByteStream() = default;
void ByteStream::Advance(size_t num_bytes) { offset_ += num_bytes; } void ByteStream::Advance(size_t num_bytes) {
assert_true(offset_ + num_bytes <= data_length_);
offset_ += num_bytes;
}
void ByteStream::Read(uint8_t* buf, size_t len) { void ByteStream::Read(uint8_t* buf, size_t len) {
assert_true(offset_ < data_length_); assert_true(offset_ + len <= data_length_);
std::memcpy(buf, data_ + offset_, len); std::memcpy(buf, data_ + offset_, len);
Advance(len); Advance(len);
} }
void ByteStream::Write(const uint8_t* buf, size_t len) { void ByteStream::Write(const uint8_t* buf, size_t len) {
assert_true(offset_ < data_length_); assert_true(offset_ + len <= data_length_);
std::memcpy(data_ + offset_, buf, len); std::memcpy(data_ + offset_, buf, len);
Advance(len); Advance(len);
} }
@ -41,7 +42,6 @@ std::string ByteStream::Read() {
std::string str; std::string str;
uint32_t len = Read<uint32_t>(); uint32_t len = Read<uint32_t>();
str.resize(len); str.resize(len);
Read(reinterpret_cast<uint8_t*>(&str[0]), len); Read(reinterpret_cast<uint8_t*>(&str[0]), len);
return str; return str;
} }
@ -49,9 +49,8 @@ std::string ByteStream::Read() {
template <> template <>
std::u16string ByteStream::Read() { std::u16string ByteStream::Read() {
std::u16string str; std::u16string str;
uint32_t len = Read<uint32_t>(); size_t len = Read<uint32_t>();
str.resize(len); str.resize(len);
Read(reinterpret_cast<uint8_t*>(&str[0]), len * 2); Read(reinterpret_cast<uint8_t*>(&str[0]), len * 2);
return str; return str;
} }

View File

@ -2,41 +2,51 @@
****************************************************************************** ******************************************************************************
* Xenia : Xbox 360 Emulator Research Project * * Xenia : Xbox 360 Emulator Research Project *
****************************************************************************** ******************************************************************************
* Copyright 2019 Ben Vanik. All rights reserved. * * Copyright 2021 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. * * Released under the BSD license - see LICENSE in the root for more details. *
****************************************************************************** ******************************************************************************
*/ */
#include "xenia/base/clock.h"
#include <sys/time.h> #include <sys/time.h>
#include <time.h>
#include "xenia/base/assert.h"
#include "xenia/base/clock.h"
namespace xe { namespace xe {
uint64_t Clock::host_tick_frequency_platform() { uint64_t Clock::host_tick_frequency_platform() {
timespec res; timespec res;
clock_getres(CLOCK_MONOTONIC_RAW, &res); int error = clock_getres(CLOCK_MONOTONIC_RAW, &res);
assert_zero(error);
assert_zero(res.tv_sec); // Sub second resolution is required.
return uint64_t(res.tv_sec) + uint64_t(res.tv_nsec) * 1000000000ull; // Convert nano seconds to hertz. Resolution is 1ns on most systems.
return 1000000000ull / res.tv_nsec;
} }
uint64_t Clock::host_tick_count_platform() { uint64_t Clock::host_tick_count_platform() {
timespec res; timespec tp;
clock_gettime(CLOCK_MONOTONIC_RAW, &res); int error = clock_gettime(CLOCK_MONOTONIC_RAW, &tp);
assert_zero(error);
return uint64_t(res.tv_sec) + uint64_t(res.tv_nsec) * 1000000000ull; return tp.tv_nsec + tp.tv_sec * 1000000000ull;
} }
uint64_t Clock::QueryHostSystemTime() { uint64_t Clock::QueryHostSystemTime() {
struct timeval tv; // https://docs.microsoft.com/en-us/windows/win32/sysinfo/converting-a-time-t-value-to-a-file-time
gettimeofday(&tv, NULL); constexpr uint64_t seconds_per_day = 3600 * 24;
// Don't forget the 89 leap days.
constexpr uint64_t seconds_1601_to_1970 =
((369 * 365 + 89) * seconds_per_day);
uint64_t ret = tv.tv_usec; timeval now;
ret /= 1000; // usec -> msec int error = gettimeofday(&now, nullptr);
assert_zero(error);
ret += (tv.tv_sec * 1000); // sec -> msec // NT systems use 100ns intervals.
return ret; return static_cast<uint64_t>(
(static_cast<int64_t>(now.tv_sec) + seconds_1601_to_1970) * 10000000ull +
now.tv_usec * 10);
} }
uint64_t Clock::QueryHostUptimeMillis() { uint64_t Clock::QueryHostUptimeMillis() {

View File

@ -14,6 +14,10 @@
#define UTF_CPP_CPLUSPLUS 201703L #define UTF_CPP_CPLUSPLUS 201703L
#include "third_party/utfcpp/source/utf8.h" #include "third_party/utfcpp/source/utf8.h"
#include "xenia/base/logging.h"
#include "xenia/base/main.h"
#include "xenia/base/system.h"
namespace utfcpp = utf8; namespace utfcpp = utf8;
using u8_citer = utfcpp::iterator<std::string_view::const_iterator>; using u8_citer = utfcpp::iterator<std::string_view::const_iterator>;
@ -61,7 +65,14 @@ void ParseLaunchArguments(int& argc, char**& argv,
auto result = options.parse(argc, argv); auto result = options.parse(argc, argv);
if (result.count("help")) { if (result.count("help")) {
PrintHelpAndExit(); xe::AttachConsole();
if (xe::has_console_attached()) {
PrintHelpAndExit();
} else {
xe::ShowSimpleMessageBox(xe::SimpleMessageBoxType::Help,
options.help({""}));
exit(0);
}
} }
for (auto& it : *CmdVars) { for (auto& it : *CmdVars) {
@ -78,8 +89,16 @@ void ParseLaunchArguments(int& argc, char**& argv,
} }
} }
} catch (const cxxopts::OptionException& e) { } catch (const cxxopts::OptionException& e) {
std::cout << e.what() << std::endl; xe::AttachConsole();
PrintHelpAndExit(); if (xe::has_console_attached()) {
std::cout << e.what() << std::endl;
PrintHelpAndExit();
} else {
std::string m =
"Invalid launch options were given.\n" + options.help({""});
xe::ShowSimpleMessageBox(xe::SimpleMessageBoxType::Error, m);
exit(0);
}
} }
} }

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010"> <AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
<!-- Automatically convert endianness for xe::be --> <!-- Automatically convert endianness for xe::endian_store<,1> -->
<Type Name="xe::be&lt;unsigned __int64&gt;"> <Type Name="xe::endian_store&lt;unsigned __int64, 1&gt;">
<DisplayString> <DisplayString>
{(((value &amp; 0xFF00000000000000) &gt;&gt; 56) | {(((value &amp; 0xFF00000000000000) &gt;&gt; 56) |
((value &amp; 0x00FF000000000000) &gt;&gt; 40) | ((value &amp; 0x00FF000000000000) &gt;&gt; 40) |
@ -14,7 +14,7 @@
((value &amp; 0x00000000000000FF) &lt;&lt; 56))} ((value &amp; 0x00000000000000FF) &lt;&lt; 56))}
</DisplayString> </DisplayString>
</Type> </Type>
<Type Name="xe::be&lt;__int64&gt;"> <Type Name="xe::endian_store&lt;__int64, 1&gt;">
<DisplayString> <DisplayString>
{(((value &amp; 0xFF00000000000000) &gt;&gt; 56) | {(((value &amp; 0xFF00000000000000) &gt;&gt; 56) |
((value &amp; 0x00FF000000000000) &gt;&gt; 40) | ((value &amp; 0x00FF000000000000) &gt;&gt; 40) |
@ -27,7 +27,7 @@
</DisplayString> </DisplayString>
</Type> </Type>
<Type Name="xe::be&lt;unsigned int&gt;"> <Type Name="xe::endian_store&lt;unsigned int, 1&gt;">
<DisplayString> <DisplayString>
{(((value &amp; 0xFF000000) &gt;&gt; 24) | {(((value &amp; 0xFF000000) &gt;&gt; 24) |
((value &amp; 0x00FF0000) &gt;&gt; 8) | ((value &amp; 0x00FF0000) &gt;&gt; 8) |
@ -35,7 +35,7 @@
((value &amp; 0x000000FF) &lt;&lt; 24))} ((value &amp; 0x000000FF) &lt;&lt; 24))}
</DisplayString> </DisplayString>
</Type> </Type>
<Type Name="xe::be&lt;int&gt;"> <Type Name="xe::endian_store&lt;int, 1&gt;">
<DisplayString> <DisplayString>
{(((value &amp; 0xFF000000) &gt;&gt; 24) | {(((value &amp; 0xFF000000) &gt;&gt; 24) |
((value &amp; 0x00FF0000) &gt;&gt; 8) | ((value &amp; 0x00FF0000) &gt;&gt; 8) |
@ -44,25 +44,25 @@
</DisplayString> </DisplayString>
</Type> </Type>
<Type Name="xe::be&lt;unsigned short&gt;"> <Type Name="xe::endian_store&lt;unsigned short, 1&gt;">
<DisplayString> <DisplayString>
{(((value &amp; 0xFF00) &gt;&gt; 8) | {(((value &amp; 0xFF00) &gt;&gt; 8) |
((value &amp; 0x00FF) &lt;&lt; 8))} ((value &amp; 0x00FF) &lt;&lt; 8))}
</DisplayString> </DisplayString>
</Type> </Type>
<Type Name="xe::be&lt;short&gt;"> <Type Name="xe::endian_store&lt;short, 1&gt;">
<DisplayString> <DisplayString>
{(((value &amp; 0xFF00) &gt;&gt; 8) | {(((value &amp; 0xFF00) &gt;&gt; 8) |
((value &amp; 0x00FF) &lt;&lt; 8))} ((value &amp; 0x00FF) &lt;&lt; 8))}
</DisplayString> </DisplayString>
</Type> </Type>
<Type Name="xe::be&lt;unsigned char&gt;"> <Type Name="xe::endian_store&lt;unsigned char, 1&gt;">
<DisplayString> <DisplayString>
{value} {value}
</DisplayString> </DisplayString>
</Type> </Type>
<Type Name="xe::be&lt;char&gt;"> <Type Name="xe::endian_store&lt;char, 1&gt;">
<DisplayString> <DisplayString>
{value} {value}
</DisplayString> </DisplayString>

View File

@ -26,6 +26,7 @@
#include "xenia/base/memory.h" #include "xenia/base/memory.h"
#include "xenia/base/ring_buffer.h" #include "xenia/base/ring_buffer.h"
#include "xenia/base/string.h" #include "xenia/base/string.h"
#include "xenia/base/system.h"
#include "xenia/base/threading.h" #include "xenia/base/threading.h"
// For MessageBox: // For MessageBox:
@ -58,7 +59,7 @@ struct LogLine {
size_t buffer_length; size_t buffer_length;
uint32_t thread_id; uint32_t thread_id;
uint16_t _pad_0; // (2b) padding uint16_t _pad_0; // (2b) padding
uint8_t _pad_1; // (1b) padding bool terminate;
char prefix_char; char prefix_char;
}; };
@ -81,8 +82,7 @@ class Logger {
explicit Logger(const std::string_view app_name) explicit Logger(const std::string_view app_name)
: wait_strategy_(), : wait_strategy_(),
claim_strategy_(kBlockCount, wait_strategy_), claim_strategy_(kBlockCount, wait_strategy_),
consumed_(wait_strategy_), consumed_(wait_strategy_) {
running_(true) {
claim_strategy_.add_claim_barrier(consumed_); claim_strategy_.add_claim_barrier(consumed_);
write_thread_ = write_thread_ =
@ -91,7 +91,7 @@ class Logger {
} }
~Logger() { ~Logger() {
running_ = false; AppendLine(0, '\0', nullptr, 0, true); // append a terminator
xe::threading::Wait(write_thread_.get(), true); xe::threading::Wait(write_thread_.get(), true);
} }
@ -124,7 +124,6 @@ class Logger {
std::vector<std::unique_ptr<LogSink>> sinks_; std::vector<std::unique_ptr<LogSink>> sinks_;
std::atomic<bool> running_;
std::unique_ptr<xe::threading::Thread> write_thread_; std::unique_ptr<xe::threading::Thread> write_thread_;
void Write(const char* buf, size_t size) { void Write(const char* buf, size_t size) {
@ -153,35 +152,30 @@ class Logger {
auto available_sequence = claim_strategy_.wait_until_published( auto available_sequence = claim_strategy_.wait_until_published(
next_range.last(), last_sequence); next_range.last(), last_sequence);
auto available_difference =
dp::difference(available_sequence, next_sequence);
size_t read_count = 0; size_t read_count = 0;
auto available_range = next_range;
auto available_count = available_range.size();
if (available_difference > 0 && rb.set_write_offset(BlockOffset(available_range.end()));
static_cast<size_t>(available_difference) >= desired_count) {
auto available_range = dp::sequence_range(
next_sequence, static_cast<size_t>(available_difference));
auto available_count = available_range.size();
rb.set_write_offset(BlockOffset(available_range.end())); bool terminate = false;
for (size_t i = available_range.first(); i != available_range.end();) {
rb.set_read_offset(BlockOffset(i));
for (size_t i = available_range.first(); i != available_range.end();) { LogLine line;
rb.set_read_offset(BlockOffset(i)); rb.Read(&line, sizeof(line));
LogLine line; auto needed_count = BlockCount(sizeof(LogLine) + line.buffer_length);
rb.Read(&line, sizeof(line)); if (read_count + needed_count > available_count) {
// More blocks are needed for a complete line.
auto needed_count = BlockCount(sizeof(LogLine) + line.buffer_length); desired_count = needed_count;
if (read_count + needed_count > available_count) { break;
// More blocks are needed for a complete line. } else {
desired_count = needed_count; // Enough blocks to read this log line, advance by that many.
break; read_count += needed_count;
} else { i += needed_count;
// Enough blocks to read this log line, advance by that many.
read_count += needed_count;
i += needed_count;
if (line.prefix_char) {
char prefix[] = { char prefix[] = {
line.prefix_char, line.prefix_char,
'>', '>',
@ -200,44 +194,53 @@ class Logger {
fmt::format_to_n(prefix + 3, sizeof(prefix) - 3, "{:08X}", fmt::format_to_n(prefix + 3, sizeof(prefix) - 3, "{:08X}",
line.thread_id); line.thread_id);
Write(prefix, sizeof(prefix) - 1); Write(prefix, sizeof(prefix) - 1);
}
if (line.buffer_length) { if (line.buffer_length) {
// Get access to the line data - which may be split in the ring // Get access to the line data - which may be split in the ring
// buffer - and write it out in parts. // buffer - and write it out in parts.
auto line_range = rb.BeginRead(line.buffer_length); auto line_range = rb.BeginRead(line.buffer_length);
Write(reinterpret_cast<const char*>(line_range.first), Write(reinterpret_cast<const char*>(line_range.first),
line_range.first_length); line_range.first_length);
if (line_range.second_length) { if (line_range.second_length) {
Write(reinterpret_cast<const char*>(line_range.second), Write(reinterpret_cast<const char*>(line_range.second),
line_range.second_length); line_range.second_length);
} }
// Always ensure there is a newline. // Always ensure there is a newline.
char last_char = char last_char =
line_range.second line_range.second
? line_range.second[line_range.second_length - 1] ? line_range.second[line_range.second_length - 1]
: line_range.first[line_range.first_length - 1]; : line_range.first[line_range.first_length - 1];
if (last_char != '\n') { if (last_char != '\n') {
const char suffix[1] = {'\n'};
Write(suffix, 1);
}
rb.EndRead(std::move(line_range));
} else {
// Always ensure there is a newline.
const char suffix[1] = {'\n'}; const char suffix[1] = {'\n'};
Write(suffix, 1); Write(suffix, 1);
} }
rb.EndRead(std::move(line_range));
} else {
// Always ensure there is a newline.
const char suffix[1] = {'\n'};
Write(suffix, 1);
}
if (line.terminate) {
terminate = true;
break;
} }
} }
} }
if (terminate) {
break;
}
if (read_count) { if (read_count) {
// Advance by the number of blocks we read. // Advance by the number of blocks we read.
auto read_range = dp::sequence_range(next_sequence, read_count); auto read_range = dp::sequence_range(next_sequence, read_count);
next_sequence = read_range.end(); next_sequence = read_range.end();
last_sequence = read_range.last(); last_sequence = read_range.last();
consumed_.publish(read_range.last()); consumed_.publish(last_sequence);
desired_count = 1; desired_count = 1;
@ -249,9 +252,6 @@ class Logger {
idle_loops = 0; idle_loops = 0;
} else { } else {
if (!running_) {
break;
}
if (idle_loops >= 1000) { if (idle_loops >= 1000) {
// Introduce a waiting period. // Introduce a waiting period.
xe::threading::Sleep(std::chrono::milliseconds(50)); xe::threading::Sleep(std::chrono::milliseconds(50));
@ -264,7 +264,8 @@ class Logger {
public: public:
void AppendLine(uint32_t thread_id, const char prefix_char, void AppendLine(uint32_t thread_id, const char prefix_char,
const char* buffer_data, size_t buffer_length) { const char* buffer_data, size_t buffer_length,
bool terminate = false) {
size_t count = BlockCount(sizeof(LogLine) + buffer_length); size_t count = BlockCount(sizeof(LogLine) + buffer_length);
auto range = claim_strategy_.claim(count); auto range = claim_strategy_.claim(count);
@ -278,9 +279,12 @@ class Logger {
line.buffer_length = buffer_length; line.buffer_length = buffer_length;
line.thread_id = thread_id; line.thread_id = thread_id;
line.prefix_char = prefix_char; line.prefix_char = prefix_char;
line.terminate = terminate;
rb.Write(&line, sizeof(LogLine)); rb.Write(&line, sizeof(LogLine));
rb.Write(buffer_data, buffer_length); if (buffer_length) {
rb.Write(buffer_data, buffer_length);
}
claim_strategy_.publish(range); claim_strategy_.publish(range);
} }
@ -350,12 +354,10 @@ void logging::AppendLogLine(LogLevel log_level, const char prefix_char,
void FatalError(const std::string_view str) { void FatalError(const std::string_view str) {
logging::AppendLogLine(LogLevel::Error, 'X', str); logging::AppendLogLine(LogLevel::Error, 'X', str);
#if XE_PLATFORM_WIN32
if (!xe::has_console_attached()) { if (!xe::has_console_attached()) {
MessageBoxW(NULL, (LPCWSTR)xe::to_utf16(str).c_str(), L"Xenia Error", ShowSimpleMessageBox(SimpleMessageBoxType::Error, str);
MB_OK | MB_ICONERROR | MB_APPLMODAL | MB_SETFOREGROUND);
} }
#endif // WIN32
ShutdownLogging(); ShutdownLogging();
std::exit(1); std::exit(1);
} }

View File

@ -10,6 +10,7 @@
#ifndef XENIA_BASE_MAIN_H_ #ifndef XENIA_BASE_MAIN_H_
#define XENIA_BASE_MAIN_H_ #define XENIA_BASE_MAIN_H_
#include <optional>
#include <string> #include <string>
#include <vector> #include <vector>
@ -21,23 +22,32 @@ namespace xe {
// Returns true if there is a user-visible console attached to receive stdout. // Returns true if there is a user-visible console attached to receive stdout.
bool has_console_attached(); bool has_console_attached();
void AttachConsole();
// Extern defined by user code. This must be present for the application to // Extern defined by user code. This must be present for the application to
// launch. // launch.
struct EntryInfo { struct EntryInfo {
std::string name; std::string name;
std::string positional_usage;
std::vector<std::string> positional_options;
int (*entry_point)(const std::vector<std::string>& args); int (*entry_point)(const std::vector<std::string>& args);
bool transparent_options; // no argument parsing
std::optional<std::string> positional_usage;
std::optional<std::vector<std::string>> positional_options;
}; };
EntryInfo GetEntryInfo(); EntryInfo GetEntryInfo();
#define DEFINE_ENTRY_POINT(name, entry_point, positional_usage, ...) \ #define DEFINE_ENTRY_POINT(name, entry_point, positional_usage, ...) \
xe::EntryInfo xe::GetEntryInfo() { \ xe::EntryInfo xe::GetEntryInfo() { \
std::initializer_list<std::string> positional_options = {__VA_ARGS__}; \ std::initializer_list<std::string> positional_options = {__VA_ARGS__}; \
return xe::EntryInfo( \ return xe::EntryInfo{ \
{name, positional_usage, \ name, entry_point, false, positional_usage, \
std::vector<std::string>(std::move(positional_options)), \ std::vector<std::string>(std::move(positional_options))}; \
entry_point}); \ }
// TODO(Joel Linn): Add some way to filter consumed arguments in
// cvar::ParseLaunchArguments()
#define DEFINE_ENTRY_POINT_TRANSPARENT(name, entry_point) \
xe::EntryInfo xe::GetEntryInfo() { \
return xe::EntryInfo{name, entry_point, true, std::nullopt, std::nullopt}; \
} }
} // namespace xe } // namespace xe

View File

@ -2,11 +2,14 @@
****************************************************************************** ******************************************************************************
* Xenia : Xbox 360 Emulator Research Project * * Xenia : Xbox 360 Emulator Research Project *
****************************************************************************** ******************************************************************************
* Copyright 2020 Ben Vanik. All rights reserved. * * Copyright 2021 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. * * Released under the BSD license - see LICENSE in the root for more details. *
****************************************************************************** ******************************************************************************
*/ */
#include <stdio.h>
#include <unistd.h>
#include "xenia/base/cvar.h" #include "xenia/base/cvar.h"
#include "xenia/base/main.h" #include "xenia/base/main.h"
@ -16,15 +19,19 @@
namespace xe { namespace xe {
bool has_console_attached() { return true; } bool has_console_attached() { return isatty(fileno(stdin)) == 1; }
void AttachConsole() {}
} // namespace xe } // namespace xe
extern "C" int main(int argc, char** argv) { extern "C" int main(int argc, char** argv) {
auto entry_info = xe::GetEntryInfo(); auto entry_info = xe::GetEntryInfo();
cvar::ParseLaunchArguments(argc, argv, entry_info.positional_usage, if (!entry_info.transparent_options) {
entry_info.positional_options); cvar::ParseLaunchArguments(argc, argv, entry_info.positional_usage.value(),
entry_info.positional_options.value());
}
std::vector<std::string> args; std::vector<std::string> args;
for (int n = 0; n < argc; n++) { for (int n = 0; n < argc; n++) {

View File

@ -2,7 +2,7 @@
****************************************************************************** ******************************************************************************
* Xenia : Xbox 360 Emulator Research Project * * Xenia : Xbox 360 Emulator Research Project *
****************************************************************************** ******************************************************************************
* Copyright 2020 Ben Vanik. All rights reserved. * * Copyright 2021 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. * * Released under the BSD license - see LICENSE in the root for more details. *
****************************************************************************** ******************************************************************************
*/ */
@ -38,8 +38,22 @@ bool has_console_attached_ = true;
bool has_console_attached() { return has_console_attached_; } bool has_console_attached() { return has_console_attached_; }
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");
if (error) {
return false;
}
return !!size;
}
void AttachConsole() { void AttachConsole() {
if (!cvars::enable_console) { bool has_console = ::AttachConsole(ATTACH_PARENT_PROCESS) == TRUE;
if (!has_console || !has_shell_environment_variable()) {
// We weren't launched from a console, so just return.
has_console_attached_ = false;
return; return;
} }
@ -104,8 +118,10 @@ static bool parse_launch_arguments(const xe::EntryInfo& entry_info,
LocalFree(wargv); LocalFree(wargv);
cvar::ParseLaunchArguments(argc, argv, entry_info.positional_usage, if (!entry_info.transparent_options) {
entry_info.positional_options); cvar::ParseLaunchArguments(argc, argv, entry_info.positional_usage.value(),
entry_info.positional_options.value());
}
args.clear(); args.clear();
for (int n = 0; n < argc; n++) { for (int n = 0; n < argc; n++) {
@ -125,7 +141,9 @@ int Main() {
// Attach a console so we can write output to stdout. If the user hasn't // Attach a console so we can write output to stdout. If the user hasn't
// redirected output themselves it'll pop up a window. // redirected output themselves it'll pop up a window.
xe::AttachConsole(); if (cvars::enable_console) {
xe::AttachConsole();
}
// Setup COM on the main thread. // Setup COM on the main thread.
// NOTE: this may fail if COM has already been initialized - that's OK. // NOTE: this may fail if COM has already been initialized - that's OK.
@ -144,7 +162,13 @@ int Main() {
} }
// Print version info. // Print version info.
XELOGI("Build: " XE_BUILD_BRANCH " / " XE_BUILD_COMMIT " on " XE_BUILD_DATE); XELOGI(
"Build: "
#ifdef XE_BUILD_IS_PR
"PR#" XE_BUILD_PR_NUMBER " " XE_BUILD_PR_REPO " " XE_BUILD_PR_BRANCH
"@" XE_BUILD_PR_COMMIT_SHORT " against "
#endif
XE_BUILD_BRANCH "@" XE_BUILD_COMMIT_SHORT " on " XE_BUILD_DATE);
// Request high performance timing. // Request high performance timing.
if (cvars::win32_high_freq) { if (cvars::win32_high_freq) {

View File

@ -17,6 +17,16 @@
#include <limits> #include <limits>
#include <numeric> #include <numeric>
#include <type_traits> #include <type_traits>
#if defined __has_include
#if __has_include(<version>)
#include <version>
#endif
#endif
#if __cpp_lib_bitops
#include <bit>
#endif
#include "xenia/base/platform.h" #include "xenia/base/platform.h"
#if XE_ARCH_AMD64 #if XE_ARCH_AMD64
@ -47,8 +57,20 @@ constexpr T round_up(T value, V multiple) {
return value ? (((value + multiple - 1) / multiple) * multiple) : multiple; return value ? (((value + multiple - 1) / multiple) * multiple) : multiple;
} }
constexpr float saturate(float value) { // Using the same conventions as in shading languages, returning 0 for NaN.
return std::max(std::min(1.0f, value), -1.0f); // std::max is `a < b ? b : a`, thus in case of NaN, the first argument is
// always returned. Also -0 is not < +0, so +0 is also chosen for it.
template <typename T>
constexpr T saturate_unsigned(T value) {
return std::min(static_cast<T>(1.0f), std::max(static_cast<T>(0.0f), value));
}
// This diverges from the GPU NaN rules for signed normalized formats (NaN
// should be converted to 0, not to -1), but this expectation is not needed most
// of time, and cannot be met for free (unlike for 0...1 clamping).
template <typename T>
constexpr T saturate_signed(T value) {
return std::min(static_cast<T>(1.0f), std::max(static_cast<T>(-1.0f), value));
} }
// Gets the next power of two value that is greater than or equal to the given // Gets the next power of two value that is greater than or equal to the given
@ -101,6 +123,23 @@ constexpr uint32_t select_bits(uint32_t value, uint32_t a, uint32_t b) {
return (value & make_bitmask(a, b)) >> a; return (value & make_bitmask(a, b)) >> a;
} }
#if __cpp_lib_bitops
template <class T>
constexpr inline uint32_t bit_count(T v) {
return static_cast<uint32_t>(std::popcount(v));
}
#else
#if XE_COMPILER_MSVC || XE_COMPILER_INTEL
inline uint32_t bit_count(uint32_t v) { return __popcnt(v); }
inline uint32_t bit_count(uint64_t v) {
return static_cast<uint32_t>(__popcnt64(v));
}
#elif XE_COMPILER_GCC || XE_COMPILER_CLANG
static_assert(sizeof(unsigned int) == sizeof(uint32_t));
static_assert(sizeof(unsigned long long) == sizeof(uint64_t));
inline uint32_t bit_count(uint32_t v) { return __builtin_popcount(v); }
inline uint32_t bit_count(uint64_t v) { return __builtin_popcountll(v); }
#else
inline uint32_t bit_count(uint32_t v) { inline uint32_t bit_count(uint32_t v) {
v = v - ((v >> 1) & 0x55555555); v = v - ((v >> 1) & 0x55555555);
v = (v & 0x33333333) + ((v >> 2) & 0x33333333); v = (v & 0x33333333) + ((v >> 2) & 0x33333333);
@ -116,6 +155,8 @@ inline uint32_t bit_count(uint64_t v) {
v = v + (v >> 32) & 0x0000007F; v = v + (v >> 32) & 0x0000007F;
return static_cast<uint32_t>(v); return static_cast<uint32_t>(v);
} }
#endif
#endif
// lzcnt instruction, typed for integers of all sizes. // lzcnt instruction, typed for integers of all sizes.
// The number of leading zero bits in the value parameter. If value is zero, the // The number of leading zero bits in the value parameter. If value is zero, the

View File

@ -43,6 +43,16 @@ void copy_128_aligned(void* dest, const void* src, size_t count) {
} }
#if XE_ARCH_AMD64 #if XE_ARCH_AMD64
// This works around a GCC bug
// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=100801
// TODO(Joel Linn): Remove this when fixed GCC versions are common place.
#if XE_COMPILER_GNUC
#define XE_WORKAROUND_LOOP_KILL_MOD(x) \
if ((count % (x)) == 0) __builtin_unreachable();
#else
#define XE_WORKAROUND_LOOP_KILL_MOD(x)
#endif
void copy_and_swap_16_aligned(void* dest_ptr, const void* src_ptr, void copy_and_swap_16_aligned(void* dest_ptr, const void* src_ptr,
size_t count) { size_t count) {
assert_zero(reinterpret_cast<uintptr_t>(dest_ptr) & 0xF); assert_zero(reinterpret_cast<uintptr_t>(dest_ptr) & 0xF);
@ -61,6 +71,7 @@ void copy_and_swap_16_aligned(void* dest_ptr, const void* src_ptr,
_mm_store_si128(reinterpret_cast<__m128i*>(&dest[i]), output); _mm_store_si128(reinterpret_cast<__m128i*>(&dest[i]), output);
} }
for (; i < count; ++i) { // handle residual elements for (; i < count; ++i) { // handle residual elements
XE_WORKAROUND_LOOP_KILL_MOD(8);
dest[i] = byte_swap(src[i]); dest[i] = byte_swap(src[i]);
} }
} }
@ -80,6 +91,7 @@ void copy_and_swap_16_unaligned(void* dest_ptr, const void* src_ptr,
_mm_storeu_si128(reinterpret_cast<__m128i*>(&dest[i]), output); _mm_storeu_si128(reinterpret_cast<__m128i*>(&dest[i]), output);
} }
for (; i < count; ++i) { // handle residual elements for (; i < count; ++i) { // handle residual elements
XE_WORKAROUND_LOOP_KILL_MOD(8);
dest[i] = byte_swap(src[i]); dest[i] = byte_swap(src[i]);
} }
} }
@ -102,6 +114,7 @@ void copy_and_swap_32_aligned(void* dest_ptr, const void* src_ptr,
_mm_store_si128(reinterpret_cast<__m128i*>(&dest[i]), output); _mm_store_si128(reinterpret_cast<__m128i*>(&dest[i]), output);
} }
for (; i < count; ++i) { // handle residual elements for (; i < count; ++i) { // handle residual elements
XE_WORKAROUND_LOOP_KILL_MOD(4);
dest[i] = byte_swap(src[i]); dest[i] = byte_swap(src[i]);
} }
} }
@ -121,6 +134,7 @@ void copy_and_swap_32_unaligned(void* dest_ptr, const void* src_ptr,
_mm_storeu_si128(reinterpret_cast<__m128i*>(&dest[i]), output); _mm_storeu_si128(reinterpret_cast<__m128i*>(&dest[i]), output);
} }
for (; i < count; ++i) { // handle residual elements for (; i < count; ++i) { // handle residual elements
XE_WORKAROUND_LOOP_KILL_MOD(4);
dest[i] = byte_swap(src[i]); dest[i] = byte_swap(src[i]);
} }
} }
@ -143,6 +157,7 @@ void copy_and_swap_64_aligned(void* dest_ptr, const void* src_ptr,
_mm_store_si128(reinterpret_cast<__m128i*>(&dest[i]), output); _mm_store_si128(reinterpret_cast<__m128i*>(&dest[i]), output);
} }
for (; i < count; ++i) { // handle residual elements for (; i < count; ++i) { // handle residual elements
XE_WORKAROUND_LOOP_KILL_MOD(2);
dest[i] = byte_swap(src[i]); dest[i] = byte_swap(src[i]);
} }
} }
@ -162,6 +177,7 @@ void copy_and_swap_64_unaligned(void* dest_ptr, const void* src_ptr,
_mm_storeu_si128(reinterpret_cast<__m128i*>(&dest[i]), output); _mm_storeu_si128(reinterpret_cast<__m128i*>(&dest[i]), output);
} }
for (; i < count; ++i) { // handle residual elements for (; i < count; ++i) { // handle residual elements
XE_WORKAROUND_LOOP_KILL_MOD(2);
dest[i] = byte_swap(src[i]); dest[i] = byte_swap(src[i]);
} }
} }
@ -178,6 +194,7 @@ void copy_and_swap_16_in_32_aligned(void* dest_ptr, const void* src_ptr,
_mm_store_si128(reinterpret_cast<__m128i*>(&dest[i]), output); _mm_store_si128(reinterpret_cast<__m128i*>(&dest[i]), output);
} }
for (; i < count; ++i) { // handle residual elements for (; i < count; ++i) { // handle residual elements
XE_WORKAROUND_LOOP_KILL_MOD(4);
dest[i] = (src[i] >> 16) | (src[i] << 16); dest[i] = (src[i] >> 16) | (src[i] << 16);
} }
} }
@ -194,6 +211,7 @@ void copy_and_swap_16_in_32_unaligned(void* dest_ptr, const void* src_ptr,
_mm_storeu_si128(reinterpret_cast<__m128i*>(&dest[i]), output); _mm_storeu_si128(reinterpret_cast<__m128i*>(&dest[i]), output);
} }
for (; i < count; ++i) { // handle residual elements for (; i < count; ++i) { // handle residual elements
XE_WORKAROUND_LOOP_KILL_MOD(4);
dest[i] = (src[i] >> 16) | (src[i] << 16); dest[i] = (src[i] >> 16) | (src[i] << 16);
} }
} }

View File

@ -15,6 +15,7 @@
#include <filesystem> #include <filesystem>
#include <functional> #include <functional>
#include <string> #include <string>
#include <string_view>
#include "xenia/base/assert.h" #include "xenia/base/assert.h"
#include "xenia/base/byte_order.h" #include "xenia/base/byte_order.h"
@ -441,6 +442,26 @@ inline void store_and_swap<std::u16string>(void* mem,
return store_and_swap<std::u16string_view>(mem, value); return store_and_swap<std::u16string_view>(mem, value);
} }
using fourcc_t = uint32_t;
// Get FourCC in host byte order
// make_fourcc('a', 'b', 'c', 'd') == 0x61626364
constexpr inline fourcc_t make_fourcc(char a, char b, char c, char d) {
return fourcc_t((static_cast<fourcc_t>(a) << 24) |
(static_cast<fourcc_t>(b) << 16) |
(static_cast<fourcc_t>(c) << 8) | static_cast<fourcc_t>(d));
}
// Get FourCC in host byte order
// This overload requires fourcc.length() == 4
// make_fourcc("abcd") == 'abcd' == 0x61626364 for most compilers
constexpr inline fourcc_t make_fourcc(const std::string_view fourcc) {
if (fourcc.length() != 4) {
throw std::runtime_error("Invalid fourcc length");
}
return make_fourcc(fourcc[0], fourcc[1], fourcc[2], fourcc[3]);
}
} // namespace xe } // namespace xe
#endif // XENIA_BASE_MEMORY_H_ #endif // XENIA_BASE_MEMORY_H_

View File

@ -85,18 +85,17 @@
#endif // XE_PLATFORM_MAC #endif // XE_PLATFORM_MAC
#if XE_COMPILER_MSVC #if XE_COMPILER_MSVC
#define XEPACKEDSTRUCT(name, value) \ #define _XEPACKEDSCOPE(body) __pragma(pack(push, 1)) body __pragma(pack(pop));
__pragma(pack(push, 1)) struct name value __pragma(pack(pop));
#define XEPACKEDSTRUCTANONYMOUS(value) \
__pragma(pack(push, 1)) struct value __pragma(pack(pop));
#define XEPACKEDUNION(name, value) \
__pragma(pack(push, 1)) union name value __pragma(pack(pop));
#else #else
#define XEPACKEDSTRUCT(name, value) struct __attribute__((packed)) name value; #define _XEPACKEDSCOPE(body) \
#define XEPACKEDSTRUCTANONYMOUS(value) struct __attribute__((packed)) value; _Pragma("pack(push, 1)") body; \
#define XEPACKEDUNION(name, value) union __attribute__((packed)) name value; _Pragma("pack(pop)");
#endif // XE_PLATFORM_WIN32 #endif // XE_PLATFORM_WIN32
#define XEPACKEDSTRUCT(name, value) _XEPACKEDSCOPE(struct name value)
#define XEPACKEDSTRUCTANONYMOUS(value) _XEPACKEDSCOPE(struct value)
#define XEPACKEDUNION(name, value) _XEPACKEDSCOPE(union name value)
namespace xe { namespace xe {
#if XE_PLATFORM_WIN32 #if XE_PLATFORM_WIN32

View File

@ -30,6 +30,7 @@
#include "xenia/base/assert.h" #include "xenia/base/assert.h"
#include "xenia/base/cvar.h" #include "xenia/base/cvar.h"
#include "xenia/base/profiling.h" #include "xenia/base/profiling.h"
#include "xenia/ui/virtual_key.h"
#include "xenia/ui/window.h" #include "xenia/ui/window.h"
#if XE_OPTION_PROFILING #if XE_OPTION_PROFILING
@ -112,31 +113,35 @@ void Profiler::ThreadEnter(const char* name) {
void Profiler::ThreadExit() { MicroProfileOnThreadExit(); } void Profiler::ThreadExit() { MicroProfileOnThreadExit(); }
bool Profiler::OnKeyDown(int key_code) { bool Profiler::OnKeyDown(ui::VirtualKey virtual_key) {
// https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx // https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx
switch (key_code) { switch (virtual_key) {
case VK_OEM_3: // ` case ui::VirtualKey::kOem3: // `
MicroProfileTogglePause(); MicroProfileTogglePause();
return true; return true;
#if XE_OPTION_PROFILING_UI #if XE_OPTION_PROFILING_UI
case VK_TAB: case ui::VirtualKey::kTab:
MicroProfileToggleDisplayMode(); MicroProfileToggleDisplayMode();
return true; return true;
case 0x31: // 1 case ui::VirtualKey::k1:
MicroProfileModKey(1); MicroProfileModKey(1);
return true; return true;
#endif // XE_OPTION_PROFILING_UI #endif // XE_OPTION_PROFILING_UI
default:
break;
} }
return false; return false;
} }
bool Profiler::OnKeyUp(int key_code) { bool Profiler::OnKeyUp(ui::VirtualKey virtual_key) {
switch (key_code) { switch (virtual_key) {
#if XE_OPTION_PROFILING_UI #if XE_OPTION_PROFILING_UI
case 0x31: // 1 case ui::VirtualKey::k1:
MicroProfileModKey(0); MicroProfileModKey(0);
return true; return true;
#endif // XE_OPTION_PROFILING_UI #endif // XE_OPTION_PROFILING_UI
default:
break;
} }
return false; return false;
} }
@ -219,14 +224,14 @@ void Profiler::set_window(ui::Window* window) {
// Watch for toggle/mode keys and such. // Watch for toggle/mode keys and such.
window_->on_key_down.AddListener([](ui::KeyEvent* e) { window_->on_key_down.AddListener([](ui::KeyEvent* e) {
if (Profiler::is_visible()) { if (Profiler::is_visible()) {
Profiler::OnKeyDown(e->key_code()); Profiler::OnKeyDown(e->virtual_key());
e->set_handled(true); e->set_handled(true);
window_->Invalidate(); window_->Invalidate();
} }
}); });
window_->on_key_up.AddListener([](ui::KeyEvent* e) { window_->on_key_up.AddListener([](ui::KeyEvent* e) {
if (Profiler::is_visible()) { if (Profiler::is_visible()) {
Profiler::OnKeyUp(e->key_code()); Profiler::OnKeyUp(e->virtual_key());
e->set_handled(true); e->set_handled(true);
window_->Invalidate(); window_->Invalidate();
} }
@ -257,8 +262,8 @@ void Profiler::Shutdown() {}
uint32_t Profiler::GetColor(const char* str) { return 0; } uint32_t Profiler::GetColor(const char* str) { return 0; }
void Profiler::ThreadEnter(const char* name) {} void Profiler::ThreadEnter(const char* name) {}
void Profiler::ThreadExit() {} void Profiler::ThreadExit() {}
bool Profiler::OnKeyDown(int key_code) { return false; } bool Profiler::OnKeyDown(ui::VirtualKey virtual_key) { return false; }
bool Profiler::OnKeyUp(int key_code) { return false; } bool Profiler::OnKeyUp(ui::VirtualKey virtual_key) { return false; }
void Profiler::OnMouseDown(bool left_button, bool right_button) {} void Profiler::OnMouseDown(bool left_button, bool right_button) {}
void Profiler::OnMouseUp() {} void Profiler::OnMouseUp() {}
void Profiler::OnMouseMove(int x, int y) {} void Profiler::OnMouseMove(int x, int y) {}

View File

@ -13,6 +13,7 @@
#include <memory> #include <memory>
#include "xenia/base/string.h" #include "xenia/base/string.h"
#include "xenia/ui/virtual_key.h"
#if XE_PLATFORM_WIN32 #if XE_PLATFORM_WIN32
#define XE_OPTION_PROFILING 1 #define XE_OPTION_PROFILING 1
@ -171,8 +172,8 @@ class Profiler {
// Deactivates the calling thread for profiling. // Deactivates the calling thread for profiling.
static void ThreadExit(); static void ThreadExit();
static bool OnKeyDown(int key_code); static bool OnKeyDown(ui::VirtualKey virtual_key);
static bool OnKeyUp(int key_code); static bool OnKeyUp(ui::VirtualKey virtual_key);
static void OnMouseDown(bool left_button, bool right_button); static void OnMouseDown(bool left_button, bool right_button);
static void OnMouseUp(); static void OnMouseUp();
static void OnMouseMove(int x, int y); static void OnMouseMove(int x, int y);

View File

@ -87,12 +87,12 @@ struct string_key_case : internal::string_key_base {
namespace std { namespace std {
template <> template <>
struct std::hash<xe::string_key> { struct hash<xe::string_key> {
std::size_t operator()(const xe::string_key& t) const { return t.hash(); } std::size_t operator()(const xe::string_key& t) const { return t.hash(); }
}; };
template <> template <>
struct std::hash<xe::string_key_case> { struct hash<xe::string_key_case> {
std::size_t operator()(const xe::string_key_case& t) const { std::size_t operator()(const xe::string_key_case& t) const {
return t.hash(); return t.hash();
} }

View File

@ -221,7 +221,7 @@ inline T fpfs(const std::string_view value, bool force_hex) {
} else { } else {
#if XE_COMPILER_CLANG || XE_COMPILER_GNUC #if XE_COMPILER_CLANG || XE_COMPILER_GNUC
auto temp = std::string(range); auto temp = std::string(range);
result = std::strtof(temp.c_str(), nullptr); result = std::strtod(temp.c_str(), nullptr);
#else #else
auto [p, error] = std::from_chars(range.data(), range.data() + range.size(), auto [p, error] = std::from_chars(range.data(), range.data() + range.size(),
result, std::chars_format::general); result, std::chars_format::general);

View File

@ -11,15 +11,24 @@
#define XENIA_BASE_SYSTEM_H_ #define XENIA_BASE_SYSTEM_H_
#include <filesystem> #include <filesystem>
#include <string> #include <string_view>
#include "xenia/base/string.h" #include "xenia/base/string.h"
namespace xe { namespace xe {
void LaunchWebBrowser(const std::string& url); void LaunchWebBrowser(const std::string_view url);
void LaunchFileExplorer(const std::filesystem::path& path); void LaunchFileExplorer(const std::filesystem::path& path);
enum class SimpleMessageBoxType {
Help,
Warning,
Error,
};
// This is expected to block the caller until the message box is closed.
void ShowSimpleMessageBox(SimpleMessageBoxType type, std::string_view message);
} // namespace xe } // namespace xe
#endif // XENIA_BASE_SYSTEM_H_ #endif // XENIA_BASE_SYSTEM_H_

View File

@ -7,22 +7,64 @@
****************************************************************************** ******************************************************************************
*/ */
#include <alloca.h>
#include <dlfcn.h>
#include <stdlib.h> #include <stdlib.h>
#include <string> #include <cstring>
#include "xenia/base/assert.h" #include "xenia/base/assert.h"
#include "xenia/base/platform_linux.h" #include "xenia/base/logging.h"
#include "xenia/base/string.h" #include "xenia/base/string.h"
#include "xenia/base/system.h" #include "xenia/base/system.h"
// Use headers in third party to not depend on system sdl headers for building
#include "third_party/SDL2/include/SDL.h"
namespace xe { namespace xe {
void LaunchWebBrowser(const std::string& url) { void LaunchWebBrowser(const std::string_view url) {
auto cmd = "xdg-open " + url; auto cmd = std::string("xdg-open ");
cmd.append(url);
system(cmd.c_str()); system(cmd.c_str());
} }
void LaunchFileExplorer(const std::filesystem::path& path) { assert_always(); } void LaunchFileExplorer(const std::filesystem::path& path) { assert_always(); }
void ShowSimpleMessageBox(SimpleMessageBoxType type, std::string_view message) {
void* libsdl2 = dlopen("libSDL2.so", RTLD_LAZY | RTLD_LOCAL);
assert_not_null(libsdl2);
if (libsdl2) {
auto* pSDL_ShowSimpleMessageBox =
reinterpret_cast<decltype(SDL_ShowSimpleMessageBox)*>(
dlsym(libsdl2, "SDL_ShowSimpleMessageBox"));
assert_not_null(pSDL_ShowSimpleMessageBox);
if (pSDL_ShowSimpleMessageBox) {
Uint32 flags;
const char* title;
char* message_copy = reinterpret_cast<char*>(alloca(message.size() + 1));
std::memcpy(message_copy, message.data(), message.size());
message_copy[message.size()] = '\0';
switch (type) {
default:
case SimpleMessageBoxType::Help:
title = "Xenia Help";
flags = SDL_MESSAGEBOX_INFORMATION;
break;
case SimpleMessageBoxType::Warning:
title = "Xenia Warning";
flags = SDL_MESSAGEBOX_WARNING;
break;
case SimpleMessageBoxType::Error:
title = "Xenia Error";
flags = SDL_MESSAGEBOX_ERROR;
break;
}
pSDL_ShowSimpleMessageBox(flags, title, message_copy, NULL);
}
dlclose(libsdl2);
}
}
} // namespace xe } // namespace xe

View File

@ -13,9 +13,9 @@
namespace xe { namespace xe {
void LaunchWebBrowser(const std::string& url) { void LaunchWebBrowser(const std::string_view url) {
auto temp = xe::to_utf16(url); auto wide_url = xe::to_utf16(url);
ShellExecuteW(nullptr, L"open", reinterpret_cast<LPCWSTR>(temp.c_str()), ShellExecuteW(nullptr, L"open", reinterpret_cast<LPCWSTR>(wide_url.c_str()),
nullptr, nullptr, SW_SHOWNORMAL); nullptr, nullptr, SW_SHOWNORMAL);
} }
@ -24,4 +24,28 @@ void LaunchFileExplorer(const std::filesystem::path& url) {
SW_SHOWNORMAL); SW_SHOWNORMAL);
} }
void ShowSimpleMessageBox(SimpleMessageBoxType type,
const std::string_view message) {
const wchar_t* title;
std::u16string wide_message = xe::to_utf16(message);
DWORD type_flags = MB_OK | MB_APPLMODAL | MB_SETFOREGROUND;
switch (type) {
default:
case SimpleMessageBoxType::Help:
title = L"Xenia Help";
type_flags |= MB_ICONINFORMATION;
break;
case SimpleMessageBoxType::Warning:
title = L"Xenia Warning";
type_flags |= MB_ICONWARNING;
break;
case SimpleMessageBoxType::Error:
title = L"Xenia Error";
type_flags |= MB_ICONERROR;
break;
}
MessageBoxW(nullptr, reinterpret_cast<LPCWSTR>(wide_message.c_str()), title,
type_flags);
}
} // namespace xe } // namespace xe

View File

@ -2,7 +2,7 @@
****************************************************************************** ******************************************************************************
* Xenia : Xbox 360 Emulator Research Project * * Xenia : Xbox 360 Emulator Research Project *
****************************************************************************** ******************************************************************************
* Copyright 2015 Ben Vanik. All rights reserved. * * Copyright 2021 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. * * Released under the BSD license - see LICENSE in the root for more details. *
****************************************************************************** ******************************************************************************
*/ */
@ -18,7 +18,7 @@ namespace xe {
namespace base { namespace base {
namespace test { namespace test {
TEST_CASE("copy_128_aligned", "Copy and Swap") { TEST_CASE("copy_128_aligned", "[copy_and_swap]") {
alignas(128) uint8_t src[256], dest[256]; alignas(128) uint8_t src[256], dest[256];
for (uint8_t i = 0; i < 255; ++i) { for (uint8_t i = 0; i < 255; ++i) {
src[i] = 255 - i; src[i] = 255 - i;
@ -37,7 +37,7 @@ TEST_CASE("copy_128_aligned", "Copy and Swap") {
REQUIRE(std::memcmp(dest, src + 1, 128)); REQUIRE(std::memcmp(dest, src + 1, 128));
} }
TEST_CASE("copy_and_swap_16_aligned", "Copy and Swap") { TEST_CASE("copy_and_swap_16_aligned", "[copy_and_swap]") {
alignas(16) uint16_t a = 0x1111, b = 0xABCD; alignas(16) uint16_t a = 0x1111, b = 0xABCD;
copy_and_swap_16_aligned(&a, &b, 1); copy_and_swap_16_aligned(&a, &b, 1);
REQUIRE(a == 0xCDAB); REQUIRE(a == 0xCDAB);
@ -93,7 +93,7 @@ TEST_CASE("copy_and_swap_16_aligned", "Copy and Swap") {
REQUIRE(std::strcmp(f, "s atdnra dlagimnne.t") == 0); REQUIRE(std::strcmp(f, "s atdnra dlagimnne.t") == 0);
} }
TEST_CASE("copy_and_swap_16_unaligned", "Copy and Swap") { TEST_CASE("copy_and_swap_16_unaligned", "[copy_and_swap]") {
uint16_t a = 0x1111, b = 0xABCD; uint16_t a = 0x1111, b = 0xABCD;
copy_and_swap_16_unaligned(&a, &b, 1); copy_and_swap_16_unaligned(&a, &b, 1);
REQUIRE(a == 0xCDAB); REQUIRE(a == 0xCDAB);
@ -139,7 +139,7 @@ TEST_CASE("copy_and_swap_16_unaligned", "Copy and Swap") {
"noeg rhtnas atdnra dlagimnne.t") == 0); "noeg rhtnas atdnra dlagimnne.t") == 0);
} }
TEST_CASE("copy_and_swap_32_aligned", "Copy and Swap") { TEST_CASE("copy_and_swap_32_aligned", "[copy_and_swap]") {
alignas(32) uint32_t a = 0x11111111, b = 0x89ABCDEF; alignas(32) uint32_t a = 0x11111111, b = 0x89ABCDEF;
copy_and_swap_32_aligned(&a, &b, 1); copy_and_swap_32_aligned(&a, &b, 1);
REQUIRE(a == 0xEFCDAB89); REQUIRE(a == 0xEFCDAB89);
@ -195,7 +195,7 @@ TEST_CASE("copy_and_swap_32_aligned", "Copy and Swap") {
REQUIRE(std::strcmp(f, "ats radnla dmngi.tne") == 0); REQUIRE(std::strcmp(f, "ats radnla dmngi.tne") == 0);
} }
TEST_CASE("copy_and_swap_32_unaligned", "Copy and Swap") { TEST_CASE("copy_and_swap_32_unaligned", "[copy_and_swap]") {
uint32_t a = 0x11111111, b = 0x89ABCDEF; uint32_t a = 0x11111111, b = 0x89ABCDEF;
copy_and_swap_32_unaligned(&a, &b, 1); copy_and_swap_32_unaligned(&a, &b, 1);
REQUIRE(a == 0xEFCDAB89); REQUIRE(a == 0xEFCDAB89);
@ -259,7 +259,7 @@ TEST_CASE("copy_and_swap_32_unaligned", "Copy and Swap") {
"regnahtats radnla dmngi.tne") == 0); "regnahtats radnla dmngi.tne") == 0);
} }
TEST_CASE("copy_and_swap_64_aligned", "Copy and Swap") { TEST_CASE("copy_and_swap_64_aligned", "[copy_and_swap]") {
alignas(64) uint64_t a = 0x1111111111111111, b = 0x0123456789ABCDEF; alignas(64) uint64_t a = 0x1111111111111111, b = 0x0123456789ABCDEF;
copy_and_swap_64_aligned(&a, &b, 1); copy_and_swap_64_aligned(&a, &b, 1);
REQUIRE(a == 0xEFCDAB8967452301); REQUIRE(a == 0xEFCDAB8967452301);
@ -317,7 +317,7 @@ TEST_CASE("copy_and_swap_64_aligned", "Copy and Swap") {
REQUIRE(std::strcmp(f, "radnats mngila d") == 0); REQUIRE(std::strcmp(f, "radnats mngila d") == 0);
} }
TEST_CASE("copy_and_swap_64_unaligned", "Copy and Swap") { TEST_CASE("copy_and_swap_64_unaligned", "[copy_and_swap]") {
uint64_t a = 0x1111111111111111, b = 0x0123456789ABCDEF; uint64_t a = 0x1111111111111111, b = 0x0123456789ABCDEF;
copy_and_swap_64_unaligned(&a, &b, 1); copy_and_swap_64_unaligned(&a, &b, 1);
REQUIRE(a == 0xEFCDAB8967452301); REQUIRE(a == 0xEFCDAB8967452301);
@ -407,12 +407,12 @@ TEST_CASE("copy_and_swap_64_unaligned", "Copy and Swap") {
"regradnats mngila d") == 0); "regradnats mngila d") == 0);
} }
TEST_CASE("copy_and_swap_16_in_32_aligned", "Copy and Swap") { TEST_CASE("copy_and_swap_16_in_32_aligned", "[copy_and_swap]") {
// TODO(bwrsandman): test once properly understood. // TODO(bwrsandman): test once properly understood.
REQUIRE(true == true); REQUIRE(true == true);
} }
TEST_CASE("copy_and_swap_16_in_32_unaligned", "Copy and Swap") { TEST_CASE("copy_and_swap_16_in_32_unaligned", "[copy_and_swap]") {
// TODO(bwrsandman): test once properly understood. // TODO(bwrsandman): test once properly understood.
REQUIRE(true == true); REQUIRE(true == true);
} }
@ -425,7 +425,7 @@ TEST_CASE("create_and_close_file_mapping", "Virtual Memory Mapping") {
xe::memory::CloseFileMappingHandle(memory, path); xe::memory::CloseFileMappingHandle(memory, path);
} }
TEST_CASE("map_view", "Virtual Memory Mapping") { TEST_CASE("map_view", "[virtual_memory_mapping]") {
auto path = fmt::format("xenia_test_{}", Clock::QueryHostTickCount()); auto path = fmt::format("xenia_test_{}", Clock::QueryHostTickCount());
const size_t length = 0x100; const size_t length = 0x100;
auto memory = xe::memory::CreateFileMappingHandle( auto memory = xe::memory::CreateFileMappingHandle(
@ -442,7 +442,7 @@ TEST_CASE("map_view", "Virtual Memory Mapping") {
xe::memory::CloseFileMappingHandle(memory, path); xe::memory::CloseFileMappingHandle(memory, path);
} }
TEST_CASE("read_write_view", "Virtual Memory Mapping") { TEST_CASE("read_write_view", "[virtual_memory_mapping]") {
const size_t length = 0x100; const size_t length = 0x100;
auto path = fmt::format("xenia_test_{}", Clock::QueryHostTickCount()); auto path = fmt::format("xenia_test_{}", Clock::QueryHostTickCount());
auto memory = xe::memory::CreateFileMappingHandle( auto memory = xe::memory::CreateFileMappingHandle(
@ -469,6 +469,40 @@ TEST_CASE("read_write_view", "Virtual Memory Mapping") {
xe::memory::CloseFileMappingHandle(memory, path); xe::memory::CloseFileMappingHandle(memory, path);
} }
TEST_CASE("make_fourcc", "[fourcc]") {
SECTION("'1234'") {
const uint32_t fourcc_host = 0x31323334;
constexpr fourcc_t fourcc_1 = make_fourcc('1', '2', '3', '4');
constexpr fourcc_t fourcc_2 = make_fourcc("1234");
REQUIRE(fourcc_1 == fourcc_host);
REQUIRE(fourcc_2 == fourcc_host);
REQUIRE(fourcc_1 == fourcc_2);
REQUIRE(fourcc_2 == fourcc_1);
}
SECTION("'ABcd'") {
const uint32_t fourcc_host = 0x41426364;
constexpr fourcc_t fourcc_1 = make_fourcc('A', 'B', 'c', 'd');
constexpr fourcc_t fourcc_2 = make_fourcc("ABcd");
REQUIRE(fourcc_1 == fourcc_host);
REQUIRE(fourcc_2 == fourcc_host);
REQUIRE(fourcc_1 == fourcc_2);
REQUIRE(fourcc_2 == fourcc_1);
}
SECTION("'XEN\\0'") {
const uint32_t fourcc_host = 0x58454E00;
constexpr fourcc_t fourcc = make_fourcc('X', 'E', 'N', '\0');
REQUIRE(fourcc == fourcc_host);
}
SECTION("length()!=4") {
REQUIRE_THROWS(make_fourcc("AB\0\0"));
REQUIRE_THROWS(make_fourcc("AB\0\0AB"));
REQUIRE_THROWS(make_fourcc("ABCDEFGH"));
}
}
} // namespace test } // namespace test
} // namespace base } // namespace base
} // namespace xe } // namespace xe

View File

@ -2,7 +2,7 @@
****************************************************************************** ******************************************************************************
* Xenia : Xbox 360 Emulator Research Project * * Xenia : Xbox 360 Emulator Research Project *
****************************************************************************** ******************************************************************************
* Copyright 2018 Ben Vanik. All rights reserved. * * Copyright 2021 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. * * Released under the BSD license - see LICENSE in the root for more details. *
****************************************************************************** ******************************************************************************
*/ */
@ -84,17 +84,17 @@ TEST_CASE("Enable process to set thread affinity") {
EnableAffinityConfiguration(); EnableAffinityConfiguration();
} }
TEST_CASE("Yield Current Thread", "MaybeYield") { TEST_CASE("Yield Current Thread", "[maybe_yield]") {
// Run to see if there are any errors // Run to see if there are any errors
MaybeYield(); MaybeYield();
} }
TEST_CASE("Sync with Memory Barrier", "SyncMemory") { TEST_CASE("Sync with Memory Barrier", "[sync_memory]") {
// Run to see if there are any errors // Run to see if there are any errors
SyncMemory(); SyncMemory();
} }
TEST_CASE("Sleep Current Thread", "Sleep") { TEST_CASE("Sleep Current Thread", "[sleep]") {
auto wait_time = 50ms; auto wait_time = 50ms;
auto start = std::chrono::steady_clock::now(); auto start = std::chrono::steady_clock::now();
Sleep(wait_time); Sleep(wait_time);
@ -102,7 +102,7 @@ TEST_CASE("Sleep Current Thread", "Sleep") {
REQUIRE(duration >= wait_time); REQUIRE(duration >= wait_time);
} }
TEST_CASE("Sleep Current Thread in Alertable State", "Sleep") { TEST_CASE("Sleep Current Thread in Alertable State", "[sleep]") {
auto wait_time = 50ms; auto wait_time = 50ms;
auto start = std::chrono::steady_clock::now(); auto start = std::chrono::steady_clock::now();
auto result = threading::AlertableSleep(wait_time); auto result = threading::AlertableSleep(wait_time);
@ -154,7 +154,7 @@ TEST_CASE("HighResolutionTimer") {
// Time the actual sleep duration // Time the actual sleep duration
{ {
const auto interval = 50ms; const auto interval = 50ms;
std::atomic<uint64_t> counter; std::atomic<uint64_t> counter(0);
auto start = std::chrono::steady_clock::now(); auto start = std::chrono::steady_clock::now();
auto cb = [&counter] { ++counter; }; auto cb = [&counter] { ++counter; };
auto pTimer = HighResolutionTimer::CreateRepeating(interval, cb); auto pTimer = HighResolutionTimer::CreateRepeating(interval, cb);
@ -201,7 +201,7 @@ TEST_CASE("HighResolutionTimer") {
// spawned from differing threads // spawned from differing threads
} }
TEST_CASE("Wait on Multiple Handles", "Wait") { TEST_CASE("Wait on Multiple Handles", "[wait]") {
auto mutant = Mutant::Create(true); auto mutant = Mutant::Create(true);
auto semaphore = Semaphore::Create(10, 10); auto semaphore = Semaphore::Create(10, 10);
auto event_ = Event::CreateManualResetEvent(false); auto event_ = Event::CreateManualResetEvent(false);
@ -244,7 +244,7 @@ TEST_CASE("Signal and Wait") {
REQUIRE(result == WaitResult::kSuccess); REQUIRE(result == WaitResult::kSuccess);
} }
TEST_CASE("Wait on Event", "Event") { TEST_CASE("Wait on Event", "[event]") {
auto evt = Event::CreateAutoResetEvent(false); auto evt = Event::CreateAutoResetEvent(false);
WaitResult result; WaitResult result;
@ -262,7 +262,7 @@ TEST_CASE("Wait on Event", "Event") {
REQUIRE(result == WaitResult::kTimeout); REQUIRE(result == WaitResult::kTimeout);
} }
TEST_CASE("Reset Event", "Event") { TEST_CASE("Reset Event", "[event]") {
auto evt = Event::CreateAutoResetEvent(false); auto evt = Event::CreateAutoResetEvent(false);
WaitResult result; WaitResult result;
@ -283,7 +283,7 @@ TEST_CASE("Reset Event", "Event") {
REQUIRE(result == WaitResult::kSuccess); REQUIRE(result == WaitResult::kSuccess);
} }
TEST_CASE("Wait on Multiple Events", "Event") { TEST_CASE("Wait on Multiple Events", "[event]") {
auto events = std::array<std::unique_ptr<Event>, 4>{ auto events = std::array<std::unique_ptr<Event>, 4>{
Event::CreateAutoResetEvent(false), Event::CreateAutoResetEvent(false),
Event::CreateAutoResetEvent(false), Event::CreateAutoResetEvent(false),
@ -348,7 +348,7 @@ TEST_CASE("Wait on Multiple Events", "Event") {
// REQUIRE(order[3] == '3'); // REQUIRE(order[3] == '3');
} }
TEST_CASE("Wait on Semaphore", "Semaphore") { TEST_CASE("Wait on Semaphore", "[semaphore]") {
WaitResult result; WaitResult result;
std::unique_ptr<Semaphore> sem; std::unique_ptr<Semaphore> sem;
int previous_count = 0; int previous_count = 0;
@ -406,9 +406,13 @@ TEST_CASE("Wait on Semaphore", "Semaphore") {
sem = Semaphore::Create(5, 5); sem = Semaphore::Create(5, 5);
Sleep(10ms); Sleep(10ms);
// Occupy the semaphore with 5 threads // Occupy the semaphore with 5 threads
auto func = [&sem] { std::atomic<int> wait_count(0);
volatile bool threads_terminate(false);
auto func = [&sem, &wait_count, &threads_terminate] {
auto res = Wait(sem.get(), false, 100ms); auto res = Wait(sem.get(), false, 100ms);
Sleep(500ms); wait_count++;
while (!threads_terminate) {
}
if (res == WaitResult::kSuccess) { if (res == WaitResult::kSuccess) {
sem->Release(1, nullptr); sem->Release(1, nullptr);
} }
@ -417,12 +421,14 @@ TEST_CASE("Wait on Semaphore", "Semaphore") {
std::thread(func), std::thread(func), std::thread(func), std::thread(func), std::thread(func), std::thread(func),
std::thread(func), std::thread(func), std::thread(func), std::thread(func),
}; };
// Give threads time to acquire semaphore // Wait for threads to finish semaphore calls
Sleep(10ms); while (wait_count != 5) {
}
// Attempt to acquire full semaphore with current (6th) thread // Attempt to acquire full semaphore with current (6th) thread
result = Wait(sem.get(), false, 20ms); result = Wait(sem.get(), false, 20ms);
REQUIRE(result == WaitResult::kTimeout); REQUIRE(result == WaitResult::kTimeout);
// Give threads time to release semaphore // Give threads time to release semaphore
threads_terminate = true;
for (auto& t : threads) { for (auto& t : threads) {
t.join(); t.join();
} }
@ -444,7 +450,7 @@ TEST_CASE("Wait on Semaphore", "Semaphore") {
// REQUIRE(sem.get() == nullptr); // REQUIRE(sem.get() == nullptr);
} }
TEST_CASE("Wait on Multiple Semaphores", "Semaphore") { TEST_CASE("Wait on Multiple Semaphores", "[semaphore]") {
WaitResult all_result; WaitResult all_result;
std::pair<WaitResult, size_t> any_result; std::pair<WaitResult, size_t> any_result;
int previous_count; int previous_count;
@ -501,7 +507,7 @@ TEST_CASE("Wait on Multiple Semaphores", "Semaphore") {
REQUIRE(previous_count == 4); REQUIRE(previous_count == 4);
} }
TEST_CASE("Wait on Mutant", "Mutant") { TEST_CASE("Wait on Mutant", "[mutant]") {
WaitResult result; WaitResult result;
std::unique_ptr<Mutant> mut; std::unique_ptr<Mutant> mut;
@ -558,7 +564,7 @@ TEST_CASE("Wait on Mutant", "Mutant") {
REQUIRE(mut->Release()); REQUIRE(mut->Release());
} }
TEST_CASE("Wait on Multiple Mutants", "Mutant") { TEST_CASE("Wait on Multiple Mutants", "[mutant]") {
WaitResult all_result; WaitResult all_result;
std::pair<WaitResult, size_t> any_result; std::pair<WaitResult, size_t> any_result;
std::unique_ptr<Mutant> mut0, mut1; std::unique_ptr<Mutant> mut0, mut1;
@ -621,7 +627,7 @@ TEST_CASE("Wait on Multiple Mutants", "Mutant") {
thread2.join(); thread2.join();
} }
TEST_CASE("Wait on Timer", "Timer") { TEST_CASE("Wait on Timer", "[timer]") {
WaitResult result; WaitResult result;
std::unique_ptr<Timer> timer; std::unique_ptr<Timer> timer;
@ -686,7 +692,7 @@ TEST_CASE("Wait on Timer", "Timer") {
REQUIRE(result == WaitResult::kTimeout); // No more signals from repeating REQUIRE(result == WaitResult::kTimeout); // No more signals from repeating
} }
TEST_CASE("Wait on Multiple Timers", "Timer") { TEST_CASE("Wait on Multiple Timers", "[timer]") {
WaitResult all_result; WaitResult all_result;
std::pair<WaitResult, size_t> any_result; std::pair<WaitResult, size_t> any_result;
@ -724,13 +730,13 @@ TEST_CASE("Wait on Multiple Timers", "Timer") {
REQUIRE(any_result.second == 1); REQUIRE(any_result.second == 1);
} }
TEST_CASE("Create and Trigger Timer Callbacks", "Timer") { TEST_CASE("Create and Trigger Timer Callbacks", "[timer]") {
// TODO(bwrsandman): Check which thread performs callback and timing of // TODO(bwrsandman): Check which thread performs callback and timing of
// callback // callback
REQUIRE(true); REQUIRE(true);
} }
TEST_CASE("Set and Test Current Thread ID", "Thread") { TEST_CASE("Set and Test Current Thread ID", "[thread]") {
// System ID // System ID
auto system_id = current_thread_system_id(); auto system_id = current_thread_system_id();
REQUIRE(system_id > 0); REQUIRE(system_id > 0);
@ -763,71 +769,76 @@ TEST_CASE("Set and Test Current Thread Name", "Thread") {
REQUIRE_NOTHROW(set_name(old_thread_name)); REQUIRE_NOTHROW(set_name(old_thread_name));
} }
TEST_CASE("Create and Run Thread", "Thread") { TEST_CASE("Create and Run Thread", "[thread]") {
std::unique_ptr<Thread> thread; std::unique_ptr<Thread> thread;
WaitResult result; WaitResult result;
Thread::CreationParameters params = {}; Thread::CreationParameters params = {};
auto func = [] { Sleep(20ms); }; auto func = [] { Sleep(20ms); };
// Create most basic case of thread SECTION("Create most basic case of thread") {
thread = Thread::Create(params, func); thread = Thread::Create(params, func);
REQUIRE(thread->native_handle() != nullptr); REQUIRE(thread->native_handle() != nullptr);
REQUIRE_NOTHROW(thread->affinity_mask()); REQUIRE_NOTHROW(thread->affinity_mask());
REQUIRE(thread->name().empty()); REQUIRE(thread->name().empty());
result = Wait(thread.get(), false, 50ms); result = Wait(thread.get(), false, 50ms);
REQUIRE(result == WaitResult::kSuccess); REQUIRE(result == WaitResult::kSuccess);
}
// Add thread name SECTION("Add thread name") {
std::string new_name = "Test thread name"; std::string new_name = "Test thread name";
thread = Thread::Create(params, func); thread = Thread::Create(params, func);
auto name = thread->name(); auto name = thread->name();
INFO(name.c_str()); INFO(name.c_str());
REQUIRE(name.empty()); REQUIRE(name.empty());
thread->set_name(new_name); thread->set_name(new_name);
REQUIRE(thread->name() == new_name); REQUIRE(thread->name() == new_name);
result = Wait(thread.get(), false, 50ms); result = Wait(thread.get(), false, 50ms);
REQUIRE(result == WaitResult::kSuccess); REQUIRE(result == WaitResult::kSuccess);
}
// Use Terminate to end an infinitely looping thread SECTION("Use Terminate to end an infinitely looping thread") {
thread = Thread::Create(params, [] { thread = Thread::Create(params, [] {
while (true) { while (true) {
Sleep(1ms); Sleep(1ms);
} }
}); });
result = Wait(thread.get(), false, 50ms); result = Wait(thread.get(), false, 50ms);
REQUIRE(result == WaitResult::kTimeout); REQUIRE(result == WaitResult::kTimeout);
thread->Terminate(-1); thread->Terminate(-1);
result = Wait(thread.get(), false, 50ms); result = Wait(thread.get(), false, 50ms);
REQUIRE(result == WaitResult::kSuccess); REQUIRE(result == WaitResult::kSuccess);
}
// Call Exit from inside an infinitely looping thread SECTION("Call Exit from inside an infinitely looping thread") {
thread = Thread::Create(params, [] { thread = Thread::Create(params, [] {
while (true) {
Thread::Exit(-1); Thread::Exit(-1);
} FAIL("Function must not return");
}); });
result = Wait(thread.get(), false, 50ms); result = Wait(thread.get(), false, 50ms);
REQUIRE(result == WaitResult::kSuccess); REQUIRE(result == WaitResult::kSuccess);
}
// Call timeout wait on self SECTION("Call timeout wait on self") {
result = Wait(Thread::GetCurrentThread(), false, 50ms); result = Wait(Thread::GetCurrentThread(), false, 50ms);
REQUIRE(result == WaitResult::kTimeout); REQUIRE(result == WaitResult::kTimeout);
}
params.stack_size = 16 * 1024 * 1024; SECTION("16kb stack size") {
thread = Thread::Create(params, [] { params.stack_size = 16 * 1024 * 1024;
while (true) { thread = Thread::Create(params, [] {
Thread::Exit(-1); Thread::Exit(-1);
} FAIL("Function must not return");
}); });
REQUIRE(thread != nullptr); REQUIRE(thread != nullptr);
result = Wait(thread.get(), false, 50ms); result = Wait(thread.get(), false, 50ms);
REQUIRE(result == WaitResult::kSuccess); REQUIRE(result == WaitResult::kSuccess);
}
// TODO(bwrsandman): Test with different priorities // TODO(bwrsandman): Test with different priorities
// TODO(bwrsandman): Test setting and getting thread affinity // TODO(bwrsandman): Test setting and getting thread affinity
} }
TEST_CASE("Test Suspending Thread", "Thread") { TEST_CASE("Test Suspending Thread", "[thread]") {
std::unique_ptr<Thread> thread; std::unique_ptr<Thread> thread;
WaitResult result; WaitResult result;
Thread::CreationParameters params = {}; Thread::CreationParameters params = {};
@ -888,7 +899,7 @@ TEST_CASE("Test Suspending Thread", "Thread") {
REQUIRE(result == threading::WaitResult::kSuccess); REQUIRE(result == threading::WaitResult::kSuccess);
} }
TEST_CASE("Test Thread QueueUserCallback", "Thread") { TEST_CASE("Test Thread QueueUserCallback", "[thread]") {
std::unique_ptr<Thread> thread; std::unique_ptr<Thread> thread;
WaitResult result; WaitResult result;
Thread::CreationParameters params = {}; Thread::CreationParameters params = {};

View File

@ -2,7 +2,7 @@
****************************************************************************** ******************************************************************************
* Xenia : Xbox 360 Emulator Research Project * * Xenia : Xbox 360 Emulator Research Project *
****************************************************************************** ******************************************************************************
* Copyright 2020 Ben Vanik. All rights reserved. * * Copyright 2021 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. * * Released under the BSD license - see LICENSE in the root for more details. *
****************************************************************************** ******************************************************************************
*/ */
@ -194,7 +194,7 @@ struct example_results {
}; };
#undef TEST_EXAMPLE_RESULT #undef TEST_EXAMPLE_RESULT
TEST_CASE("utf8::count", "UTF-8 Count") { TEST_CASE("UTF-8 Count", "[utf8]") {
example_results<size_t> results = {}; example_results<size_t> results = {};
results.Danish[0] = 88; results.Danish[0] = 88;
results.German[0] = 58; results.German[0] = 58;
@ -225,7 +225,7 @@ TEST_CASE("utf8::count", "UTF-8 Count") {
// TODO(gibbed): hash_fnv1a // TODO(gibbed): hash_fnv1a
// TODO(gibbed): hash_fnv1a_case // TODO(gibbed): hash_fnv1a_case
TEST_CASE("utf8::split", "UTF-8 Split") { TEST_CASE("UTF-8 Split", "[utf8]") {
std::vector<std::string_view> parts; std::vector<std::string_view> parts;
// Danish // Danish
@ -290,17 +290,17 @@ TEST_CASE("utf8::split", "UTF-8 Split") {
// TODO(gibbed): Turkish // TODO(gibbed): Turkish
} }
TEST_CASE("utf8::equal_z", "UTF-8 Equal Z") { TEST_CASE("UTF-8 Equal Z", "[utf8]") {
REQUIRE(utf8::equal_z(u8"foo", u8"foo\0")); REQUIRE(utf8::equal_z(u8"foo", u8"foo\0"));
REQUIRE_FALSE(utf8::equal_z(u8"bar", u8"baz\0")); REQUIRE_FALSE(utf8::equal_z(u8"bar", u8"baz\0"));
} }
TEST_CASE("utf8::equal_case", "UTF-8 Equal Case") { TEST_CASE("UTF-8 Equal Case", "[utf8]") {
REQUIRE(utf8::equal_case(u8"foo", u8"foo\0")); REQUIRE(utf8::equal_case(u8"foo", u8"foo\0"));
REQUIRE_FALSE(utf8::equal_case(u8"bar", u8"baz\0")); REQUIRE_FALSE(utf8::equal_case(u8"bar", u8"baz\0"));
} }
TEST_CASE("utf8::equal_case_z", "UTF-8 Equal Case Z") { TEST_CASE("UTF-8 Equal Case Z", "[utf8]") {
REQUIRE(utf8::equal_case_z(u8"foo", u8"foo\0")); REQUIRE(utf8::equal_case_z(u8"foo", u8"foo\0"));
REQUIRE_FALSE(utf8::equal_case_z(u8"bar", u8"baz\0")); REQUIRE_FALSE(utf8::equal_case_z(u8"bar", u8"baz\0"));
} }
@ -345,7 +345,7 @@ TEST_CASE("utf8::equal_case_z", "UTF-8 Equal Case Z") {
REQUIRE(func(input_values, '\\') == output_value); \ REQUIRE(func(input_values, '\\') == output_value); \
} while (0) } while (0)
TEST_CASE("utf8::join_paths", "UTF-8 Join Paths") { TEST_CASE("UTF-8 Join Paths", "[utf8]") {
TEST_PATHS(utf8::join_paths, u8""); TEST_PATHS(utf8::join_paths, u8"");
TEST_PATHS(utf8::join_paths, u8"foo", u8"foo"); TEST_PATHS(utf8::join_paths, u8"foo", u8"foo");
TEST_PATHS(utf8::join_paths, u8"foo/bar", u8"foo", u8"bar"); TEST_PATHS(utf8::join_paths, u8"foo/bar", u8"foo", u8"bar");
@ -355,7 +355,7 @@ TEST_CASE("utf8::join_paths", "UTF-8 Join Paths") {
// TODO(gibbed): join_guest_paths // TODO(gibbed): join_guest_paths
TEST_CASE("utf8::fix_path_separators", "UTF-8 Fix Path Separators") { TEST_CASE("UTF-8 Fix Path Separators", "[utf8]") {
TEST_PATH_RAW(utf8::fix_path_separators, "", ""); TEST_PATH_RAW(utf8::fix_path_separators, "", "");
TEST_PATH_RAW(utf8::fix_path_separators, "\\", "/"); TEST_PATH_RAW(utf8::fix_path_separators, "\\", "/");
TEST_PATH_RAW(utf8::fix_path_separators, "/", "/"); TEST_PATH_RAW(utf8::fix_path_separators, "/", "/");
@ -386,22 +386,47 @@ TEST_CASE("utf8::fix_path_separators", "UTF-8 Fix Path Separators") {
// TODO(gibbed): fix_guest_path_separators // TODO(gibbed): fix_guest_path_separators
TEST_CASE("utf8::find_name_from_path", "UTF-8 Find Name From Path") { TEST_CASE("UTF-8 Find Name From Path", "[utf8]") {
TEST_PATH(utf8::find_name_from_path, "/", ""); TEST_PATH(utf8::find_name_from_path, "/", "");
TEST_PATH(utf8::find_name_from_path, "//", "");
TEST_PATH(utf8::find_name_from_path, "///", "");
TEST_PATH(utf8::find_name_from_path, "C/", "C");
TEST_PATH(utf8::find_name_from_path, "/C/", "C");
TEST_PATH(utf8::find_name_from_path, "C/D/", "D");
TEST_PATH(utf8::find_name_from_path, "/C/D/E/", "E");
TEST_PATH(utf8::find_name_from_path, "foo/bar/D/", "D");
TEST_PATH(utf8::find_name_from_path, "/foo/bar/E/qux/", "qux");
TEST_PATH(utf8::find_name_from_path, "foo/bar/baz/qux/", "qux"); TEST_PATH(utf8::find_name_from_path, "foo/bar/baz/qux/", "qux");
TEST_PATH(utf8::find_name_from_path, "foo/bar/baz/qux//", "qux");
TEST_PATH(utf8::find_name_from_path, "foo/bar/baz/qux///", "qux");
TEST_PATH(utf8::find_name_from_path, "foo/bar/baz/qux.txt", "qux.txt"); TEST_PATH(utf8::find_name_from_path, "foo/bar/baz/qux.txt", "qux.txt");
TEST_PATH(utf8::find_name_from_path, "ほげ/ぴよ/ふが/ほげら/ほげほげ/", TEST_PATH(utf8::find_name_from_path, "ほげ/ぴよ/ふが/ほげら/ほげほげ/",
"ほげほげ"); "ほげほげ");
TEST_PATH(utf8::find_name_from_path, "ほげ/ぴよ/ふが/ほげら/ほげほげ//",
"ほげほげ");
TEST_PATH(utf8::find_name_from_path, "ほげ/ぴよ/ふが/ほげら/ほげほげ///",
"ほげほげ");
TEST_PATH(utf8::find_name_from_path, "ほげ/ぴよ/ふが/ほげら/ほげほげ.txt", TEST_PATH(utf8::find_name_from_path, "ほげ/ぴよ/ふが/ほげら/ほげほげ.txt",
"ほげほげ.txt"); "ほげほげ.txt");
TEST_PATH(utf8::find_name_from_path, "/foo", "foo");
TEST_PATH(utf8::find_name_from_path, "//foo", "foo");
TEST_PATH(utf8::find_name_from_path, "///foo", "foo");
TEST_PATH(utf8::find_name_from_path, "/foo/bar/baz/qux.txt", "qux.txt"); TEST_PATH(utf8::find_name_from_path, "/foo/bar/baz/qux.txt", "qux.txt");
TEST_PATH(utf8::find_name_from_path, "/ほげ/ぴよ/ふが/ほげら/ほげほげ/", TEST_PATH(utf8::find_name_from_path, "/ほげ/ぴよ/ふが/ほげら/ほげほげ/",
"ほげほげ"); "ほげほげ");
TEST_PATH(utf8::find_name_from_path, "/ほげ/ぴよ/ふが/ほげら/ほげほげ//",
"ほげほげ");
TEST_PATH(utf8::find_name_from_path, "/ほげ/ぴよ/ふが/ほげら/ほげほげ///",
"ほげほげ");
TEST_PATH(utf8::find_name_from_path, "/ほげ/ぴよ/ふが/ほげら/ほげほげ.txt", TEST_PATH(utf8::find_name_from_path, "/ほげ/ぴよ/ふが/ほげら/ほげほげ.txt",
"ほげほげ.txt"); "ほげほげ.txt");
TEST_PATH(utf8::find_name_from_path, "X:/foo/bar/baz/qux.txt", "qux.txt"); TEST_PATH(utf8::find_name_from_path, "X:/foo/bar/baz/qux.txt", "qux.txt");
TEST_PATH(utf8::find_name_from_path, "X:/ほげ/ぴよ/ふが/ほげら/ほげほげ/", TEST_PATH(utf8::find_name_from_path, "X:/ほげ/ぴよ/ふが/ほげら/ほげほげ/",
"ほげほげ"); "ほげほげ");
TEST_PATH(utf8::find_name_from_path, "X:/ほげ/ぴよ/ふが/ほげら/ほげほげ//",
"ほげほげ");
TEST_PATH(utf8::find_name_from_path, "X:/ほげ/ぴよ/ふが/ほげら/ほげほげ///",
"ほげほげ");
TEST_PATH(utf8::find_name_from_path, "X:/ほげ/ぴよ/ふが/ほげら/ほげほげ.txt", TEST_PATH(utf8::find_name_from_path, "X:/ほげ/ぴよ/ふが/ほげら/ほげほげ.txt",
"ほげほげ.txt"); "ほげほげ.txt");
TEST_PATH(utf8::find_name_from_path, "X:/ほげ/ぴよ/ふが/ほげら.ほげほげ", TEST_PATH(utf8::find_name_from_path, "X:/ほげ/ぴよ/ふが/ほげら.ほげほげ",
@ -410,78 +435,144 @@ TEST_CASE("utf8::find_name_from_path", "UTF-8 Find Name From Path") {
// TODO(gibbed): find_name_from_guest_path // TODO(gibbed): find_name_from_guest_path
TEST_CASE("utf8::find_base_name_from_path", "UTF-8 Find Base Name From Path") { TEST_CASE("UTF-8 Find Base Name From Path", "[utf8]") {
TEST_PATH(utf8::find_base_name_from_path, "foo/bar/baz/qux.txt", "qux"); TEST_PATH(utf8::find_base_name_from_path, "foo/bar/baz/qux.txt", "qux");
TEST_PATH(utf8::find_base_name_from_path, "foo/bar/baz/qux/", "qux"); TEST_PATH(utf8::find_base_name_from_path, "foo/bar/baz/qux/", "qux");
TEST_PATH(utf8::find_base_name_from_path, "foo/bar/baz/qux//", "qux");
TEST_PATH(utf8::find_base_name_from_path, "foo/bar/baz/qux///", "qux");
TEST_PATH(utf8::find_base_name_from_path, "C/", "C");
TEST_PATH(utf8::find_base_name_from_path, "/C/", "C");
TEST_PATH(utf8::find_base_name_from_path, "C/D/", "D");
TEST_PATH(utf8::find_base_name_from_path, "/C/D/E/", "E");
TEST_PATH(utf8::find_base_name_from_path, "foo/bar/D/", "D");
TEST_PATH(utf8::find_base_name_from_path, TEST_PATH(utf8::find_base_name_from_path,
"ほげ/ぴよ/ふが/ほげら/ほげほげ.txt", "ほげほげ"); "ほげ/ぴよ/ふが/ほげら/ほげほげ.txt", "ほげほげ");
TEST_PATH(utf8::find_base_name_from_path, "ほげ/ぴよ/ふが/ほげら/ほげほげ/", TEST_PATH(utf8::find_base_name_from_path, "ほげ/ぴよ/ふが/ほげら/ほげほげ/",
"ほげほげ"); "ほげほげ");
TEST_PATH(utf8::find_base_name_from_path, "ほげ/ぴよ/ふが/ほげら/ほげほげ//",
"ほげほげ");
TEST_PATH(utf8::find_base_name_from_path, "ほげ/ぴよ/ふが/ほげら/ほげほげ///",
"ほげほげ");
TEST_PATH(utf8::find_base_name_from_path, "ほげ/ぴよ/ふが/ほげら.ほげほげ", TEST_PATH(utf8::find_base_name_from_path, "ほげ/ぴよ/ふが/ほげら.ほげほげ",
"ほげら"); "ほげら");
TEST_PATH(utf8::find_base_name_from_path, "/foo/bar/baz/qux.txt", "qux"); TEST_PATH(utf8::find_base_name_from_path, "/foo/bar/baz/qux.txt", "qux");
TEST_PATH(utf8::find_base_name_from_path, "/foo/bar/baz/qux/", "qux"); TEST_PATH(utf8::find_base_name_from_path, "/foo/bar/baz/qux/", "qux");
TEST_PATH(utf8::find_base_name_from_path, "/foo/bar/baz/qux//", "qux");
TEST_PATH(utf8::find_base_name_from_path, "/foo/bar/baz/qux///", "qux");
TEST_PATH(utf8::find_base_name_from_path, TEST_PATH(utf8::find_base_name_from_path,
"/ほげ/ぴよ/ふが/ほげら/ほげほげ.txt", "ほげほげ"); "/ほげ/ぴよ/ふが/ほげら/ほげほげ.txt", "ほげほげ");
TEST_PATH(utf8::find_base_name_from_path, "/ほげ/ぴよ/ふが/ほげら/ほげほげ/", TEST_PATH(utf8::find_base_name_from_path, "/ほげ/ぴよ/ふが/ほげら/ほげほげ/",
"ほげほげ"); "ほげほげ");
TEST_PATH(utf8::find_base_name_from_path, "/ほげ/ぴよ/ふが/ほげら/ほげほげ//",
"ほげほげ");
TEST_PATH(utf8::find_base_name_from_path,
"/ほげ/ぴよ/ふが/ほげら/ほげほげ///", "ほげほげ");
TEST_PATH(utf8::find_base_name_from_path, "/ほげ/ぴよ/ふが/ほげら.ほげほげ", TEST_PATH(utf8::find_base_name_from_path, "/ほげ/ぴよ/ふが/ほげら.ほげほげ",
"ほげら"); "ほげら");
TEST_PATH(utf8::find_base_name_from_path, "X:/foo/bar/baz/qux.txt", "qux"); TEST_PATH(utf8::find_base_name_from_path, "X:/foo/bar/baz/qux.txt", "qux");
TEST_PATH(utf8::find_base_name_from_path, "X:/foo/bar/baz/qux/", "qux"); TEST_PATH(utf8::find_base_name_from_path, "X:/foo/bar/baz/qux/", "qux");
TEST_PATH(utf8::find_base_name_from_path, "X:/foo/bar/baz/qux//", "qux");
TEST_PATH(utf8::find_base_name_from_path, "X:/foo/bar/baz/qux///", "qux");
TEST_PATH(utf8::find_base_name_from_path, TEST_PATH(utf8::find_base_name_from_path,
"X:/ほげ/ぴよ/ふが/ほげら/ほげほげ.txt", "ほげほげ"); "X:/ほげ/ぴよ/ふが/ほげら/ほげほげ.txt", "ほげほげ");
TEST_PATH(utf8::find_base_name_from_path, TEST_PATH(utf8::find_base_name_from_path,
"X:/ほげ/ぴよ/ふが/ほげら/ほげほげ/", "ほげほげ"); "X:/ほげ/ぴよ/ふが/ほげら/ほげほげ/", "ほげほげ");
TEST_PATH(utf8::find_base_name_from_path,
"X:/ほげ/ぴよ/ふが/ほげら/ほげほげ//", "ほげほげ");
TEST_PATH(utf8::find_base_name_from_path,
"X:/ほげ/ぴよ/ふが/ほげら/ほげほげ///", "ほげほげ");
TEST_PATH(utf8::find_base_name_from_path, "X:/ほげ/ぴよ/ふが/ほげら.ほげほげ", TEST_PATH(utf8::find_base_name_from_path, "X:/ほげ/ぴよ/ふが/ほげら.ほげほげ",
"ほげら"); "ほげら");
} }
// TODO(gibbed): find_base_name_from_guest_path // TODO(gibbed): find_base_name_from_guest_path
TEST_CASE("utf8::find_base_path", "UTF-8 Find Base Path") { TEST_CASE("UTF-8 Find Base Path", "[utf8]") {
TEST_PATH(utf8::find_base_path, "", ""); TEST_PATH(utf8::find_base_path, "", "");
TEST_PATH(utf8::find_base_path, "/", ""); TEST_PATH(utf8::find_base_path, "/", "");
TEST_PATH(utf8::find_base_path, "//", ""); TEST_PATH(utf8::find_base_path, "//", "");
TEST_PATH(utf8::find_base_path, "///", "");
TEST_PATH(utf8::find_base_path, "/foo", ""); TEST_PATH(utf8::find_base_path, "/foo", "");
TEST_PATH(utf8::find_base_path, "//foo", "");
TEST_PATH(utf8::find_base_path, "///foo", "");
TEST_PATH(utf8::find_base_path, "/foo/", ""); TEST_PATH(utf8::find_base_path, "/foo/", "");
TEST_PATH(utf8::find_base_path, "/foo//", "");
TEST_PATH(utf8::find_base_path, "/foo///", "");
TEST_PATH(utf8::find_base_path, "//foo/", "");
TEST_PATH(utf8::find_base_path, "//foo//", "");
TEST_PATH(utf8::find_base_path, "//foo///", "");
TEST_PATH(utf8::find_base_path, "///foo/", "");
TEST_PATH(utf8::find_base_path, "///foo//", "");
TEST_PATH(utf8::find_base_path, "///foo///", "");
TEST_PATH(utf8::find_base_path, "/foo/bar", "/foo"); TEST_PATH(utf8::find_base_path, "/foo/bar", "/foo");
TEST_PATH(utf8::find_base_path, "/foo/bar/", "/foo"); TEST_PATH(utf8::find_base_path, "/foo/bar/", "/foo");
TEST_PATH(utf8::find_base_path, "/foo/bar//", "/foo");
TEST_PATH(utf8::find_base_path, "/foo/bar///", "/foo");
TEST_PATH(utf8::find_base_path, "/foo/bar/baz/qux", "/foo/bar/baz"); TEST_PATH(utf8::find_base_path, "/foo/bar/baz/qux", "/foo/bar/baz");
TEST_PATH(utf8::find_base_path, "/foo/bar/baz/qux/", "/foo/bar/baz"); TEST_PATH(utf8::find_base_path, "/foo/bar/baz/qux/", "/foo/bar/baz");
TEST_PATH(utf8::find_base_path, "/foo/bar/baz/qux//", "/foo/bar/baz");
TEST_PATH(utf8::find_base_path, "/foo/bar/baz/qux///", "/foo/bar/baz");
TEST_PATH(utf8::find_base_path, "/ほげ/ぴよ/ふが/ほげら/ほげほげ", TEST_PATH(utf8::find_base_path, "/ほげ/ぴよ/ふが/ほげら/ほげほげ",
"/ほげ/ぴよ/ふが/ほげら"); "/ほげ/ぴよ/ふが/ほげら");
TEST_PATH(utf8::find_base_path, "/ほげ/ぴよ/ふが/ほげら/ほげほげ/", TEST_PATH(utf8::find_base_path, "/ほげ/ぴよ/ふが/ほげら/ほげほげ/",
"/ほげ/ぴよ/ふが/ほげら"); "/ほげ/ぴよ/ふが/ほげら");
TEST_PATH(utf8::find_base_path, "/ほげ/ぴよ/ふが/ほげら/ほげほげ//",
"/ほげ/ぴよ/ふが/ほげら");
TEST_PATH(utf8::find_base_path, "/ほげ/ぴよ/ふが/ほげら/ほげほげ///",
"/ほげ/ぴよ/ふが/ほげら");
TEST_PATH(utf8::find_base_path, "foo", ""); TEST_PATH(utf8::find_base_path, "foo", "");
TEST_PATH(utf8::find_base_path, "foo/", ""); TEST_PATH(utf8::find_base_path, "foo/", "");
TEST_PATH(utf8::find_base_path, "foo//", "");
TEST_PATH(utf8::find_base_path, "foo///", "");
TEST_PATH(utf8::find_base_path, "foo/bar", "foo"); TEST_PATH(utf8::find_base_path, "foo/bar", "foo");
TEST_PATH(utf8::find_base_path, "foo/bar/", "foo"); TEST_PATH(utf8::find_base_path, "foo/bar/", "foo");
TEST_PATH(utf8::find_base_path, "foo/bar//", "foo");
TEST_PATH(utf8::find_base_path, "foo/bar///", "foo");
TEST_PATH(utf8::find_base_path, "foo/bar/baz/qux", "foo/bar/baz"); TEST_PATH(utf8::find_base_path, "foo/bar/baz/qux", "foo/bar/baz");
TEST_PATH(utf8::find_base_path, "foo/bar/baz/qux/", "foo/bar/baz"); TEST_PATH(utf8::find_base_path, "foo/bar/baz/qux/", "foo/bar/baz");
TEST_PATH(utf8::find_base_path, "foo/bar/baz/qux//", "foo/bar/baz");
TEST_PATH(utf8::find_base_path, "foo/bar/baz/qux///", "foo/bar/baz");
TEST_PATH(utf8::find_base_path, "ほげ/ぴよ/ふが/ほげら/ほげほげ", TEST_PATH(utf8::find_base_path, "ほげ/ぴよ/ふが/ほげら/ほげほげ",
"ほげ/ぴよ/ふが/ほげら"); "ほげ/ぴよ/ふが/ほげら");
TEST_PATH(utf8::find_base_path, "ほげ/ぴよ/ふが/ほげら/ほげほげ/", TEST_PATH(utf8::find_base_path, "ほげ/ぴよ/ふが/ほげら/ほげほげ/",
"ほげ/ぴよ/ふが/ほげら"); "ほげ/ぴよ/ふが/ほげら");
TEST_PATH(utf8::find_base_path, "ほげ/ぴよ/ふが/ほげら/ほげほげ//",
"ほげ/ぴよ/ふが/ほげら");
TEST_PATH(utf8::find_base_path, "ほげ/ぴよ/ふが/ほげら/ほげほげ///",
"ほげ/ぴよ/ふが/ほげら");
TEST_PATH(utf8::find_base_path, "X:", ""); TEST_PATH(utf8::find_base_path, "X:", "");
TEST_PATH(utf8::find_base_path, "X:/", ""); TEST_PATH(utf8::find_base_path, "X:/", "");
TEST_PATH(utf8::find_base_path, "X://", "");
TEST_PATH(utf8::find_base_path, "X:///", "");
TEST_PATH(utf8::find_base_path, "X:/foo", "X:"); TEST_PATH(utf8::find_base_path, "X:/foo", "X:");
TEST_PATH(utf8::find_base_path, "X:/foo/", "X:"); TEST_PATH(utf8::find_base_path, "X:/foo/", "X:");
TEST_PATH(utf8::find_base_path, "X:/foo//", "X:");
TEST_PATH(utf8::find_base_path, "X:/foo///", "X:");
TEST_PATH(utf8::find_base_path, "X:/foo/bar", "X:/foo"); TEST_PATH(utf8::find_base_path, "X:/foo/bar", "X:/foo");
TEST_PATH(utf8::find_base_path, "X:/foo/bar/", "X:/foo"); TEST_PATH(utf8::find_base_path, "X:/foo/bar/", "X:/foo");
TEST_PATH(utf8::find_base_path, "X:/foo/bar//", "X:/foo");
TEST_PATH(utf8::find_base_path, "X:/foo/bar///", "X:/foo");
TEST_PATH(utf8::find_base_path, "X:/foo/bar/baz/qux", "X:/foo/bar/baz"); TEST_PATH(utf8::find_base_path, "X:/foo/bar/baz/qux", "X:/foo/bar/baz");
TEST_PATH(utf8::find_base_path, "X:/foo/bar/baz/qux/", "X:/foo/bar/baz"); TEST_PATH(utf8::find_base_path, "X:/foo/bar/baz/qux/", "X:/foo/bar/baz");
TEST_PATH(utf8::find_base_path, "X:/foo/bar/baz/qux//", "X:/foo/bar/baz");
TEST_PATH(utf8::find_base_path, "X:/foo/bar/baz/qux///", "X:/foo/bar/baz");
TEST_PATH(utf8::find_base_path, "X:/ほげ/ぴよ/ふが/ほげら/ほげほげ", TEST_PATH(utf8::find_base_path, "X:/ほげ/ぴよ/ふが/ほげら/ほげほげ",
"X:/ほげ/ぴよ/ふが/ほげら"); "X:/ほげ/ぴよ/ふが/ほげら");
TEST_PATH(utf8::find_base_path, "X:/ほげ/ぴよ/ふが/ほげら/ほげほげ/", TEST_PATH(utf8::find_base_path, "X:/ほげ/ぴよ/ふが/ほげら/ほげほげ/",
"X:/ほげ/ぴよ/ふが/ほげら"); "X:/ほげ/ぴよ/ふが/ほげら");
TEST_PATH(utf8::find_base_path, "X:/ほげ/ぴよ/ふが/ほげら/ほげほげ//",
"X:/ほげ/ぴよ/ふが/ほげら");
TEST_PATH(utf8::find_base_path, "X:/ほげ/ぴよ/ふが/ほげら/ほげほげ///",
"X:/ほげ/ぴよ/ふが/ほげら");
} }
// TODO(gibbed): find_base_guest_path // TODO(gibbed): find_base_guest_path
TEST_CASE("utf8::canonicalize_path", "UTF-8 Canonicalize Path") { TEST_CASE("UTF-8 Canonicalize Path", "[utf8]") {
TEST_PATH(utf8::canonicalize_path, "foo/bar/baz/qux", "foo/bar/baz/qux"); TEST_PATH(utf8::canonicalize_path, "foo/bar/baz/qux", "foo/bar/baz/qux");
TEST_PATH(utf8::canonicalize_path, "foo/bar/baz/qux/", "foo/bar/baz/qux"); TEST_PATH(utf8::canonicalize_path, "foo/bar/baz/qux/", "foo/bar/baz/qux");
TEST_PATH(utf8::canonicalize_path, "foo/bar/baz/qux//", "foo/bar/baz/qux");
TEST_PATH(utf8::canonicalize_path, "foo/bar/baz/qux///", "foo/bar/baz/qux");
TEST_PATH(utf8::canonicalize_path, "foo/./baz/qux", "foo/baz/qux"); TEST_PATH(utf8::canonicalize_path, "foo/./baz/qux", "foo/baz/qux");
TEST_PATH(utf8::canonicalize_path, "foo/./baz/qux/", "foo/baz/qux"); TEST_PATH(utf8::canonicalize_path, "foo/./baz/qux/", "foo/baz/qux");
TEST_PATH(utf8::canonicalize_path, "foo/../baz/qux", "baz/qux"); TEST_PATH(utf8::canonicalize_path, "foo/../baz/qux", "baz/qux");

View File

@ -155,29 +155,36 @@ bool SetTlsValue(TlsHandle handle, uintptr_t value) {
class PosixHighResolutionTimer : public HighResolutionTimer { class PosixHighResolutionTimer : public HighResolutionTimer {
public: public:
explicit PosixHighResolutionTimer(std::function<void()> callback) explicit PosixHighResolutionTimer(std::function<void()> callback)
: callback_(std::move(callback)), timer_(nullptr) {} : callback_(std::move(callback)), valid_(false) {}
~PosixHighResolutionTimer() override { ~PosixHighResolutionTimer() override {
if (timer_) timer_delete(timer_); if (valid_) timer_delete(timer_);
} }
bool Initialize(std::chrono::milliseconds period) { bool Initialize(std::chrono::milliseconds period) {
if (valid_) {
// Double initialization
assert_always();
return false;
}
// Create timer // Create timer
sigevent sev{}; sigevent sev{};
sev.sigev_notify = SIGEV_SIGNAL; sev.sigev_notify = SIGEV_SIGNAL;
sev.sigev_signo = GetSystemSignal(SignalType::kHighResolutionTimer); sev.sigev_signo = GetSystemSignal(SignalType::kHighResolutionTimer);
sev.sigev_value.sival_ptr = (void*)&callback_; sev.sigev_value.sival_ptr = (void*)&callback_;
if (timer_create(CLOCK_REALTIME, &sev, &timer_) == -1) return false; if (timer_create(CLOCK_MONOTONIC, &sev, &timer_) == -1) return false;
// Start timer // Start timer
itimerspec its{}; itimerspec its{};
its.it_value = DurationToTimeSpec(period); its.it_value = DurationToTimeSpec(period);
its.it_interval = its.it_value; its.it_interval = its.it_value;
return timer_settime(timer_, 0, &its, nullptr) != -1; valid_ = timer_settime(timer_, 0, &its, nullptr) != -1;
return valid_;
} }
private: private:
std::function<void()> callback_; std::function<void()> callback_;
timer_t timer_; timer_t timer_;
bool valid_; // all values for timer_t are legal so we need this
}; };
std::unique_ptr<HighResolutionTimer> HighResolutionTimer::CreateRepeating( std::unique_ptr<HighResolutionTimer> HighResolutionTimer::CreateRepeating(
@ -187,7 +194,7 @@ std::unique_ptr<HighResolutionTimer> HighResolutionTimer::CreateRepeating(
if (!timer->Initialize(period)) { if (!timer->Initialize(period)) {
return nullptr; return nullptr;
} }
return std::unique_ptr<HighResolutionTimer>(timer.release()); return std::move(timer);
} }
class PosixConditionBase { class PosixConditionBase {
@ -419,7 +426,7 @@ class PosixCondition<Timer> : public PosixConditionBase {
sev.sigev_notify = SIGEV_SIGNAL; sev.sigev_notify = SIGEV_SIGNAL;
sev.sigev_signo = GetSystemSignal(SignalType::kTimer); sev.sigev_signo = GetSystemSignal(SignalType::kTimer);
sev.sigev_value.sival_ptr = this; sev.sigev_value.sival_ptr = this;
if (timer_create(CLOCK_REALTIME, &sev, &timer_) == -1) return false; if (timer_create(CLOCK_MONOTONIC, &sev, &timer_) == -1) return false;
} }
// Start timer // Start timer
@ -728,31 +735,44 @@ class PosixCondition<Thread> : public PosixConditionBase {
} }
void Terminate(int exit_code) { void Terminate(int exit_code) {
bool is_current_thread = pthread_self() == thread_;
{ {
std::unique_lock<std::mutex> lock(state_mutex_); std::unique_lock<std::mutex> lock(state_mutex_);
if (state_ == State::kFinished) {
if (is_current_thread) {
// This is really bad. Some thread must have called Terminate() on us
// just before we decided to terminate ourselves
assert_always();
for (;;) {
// Wait for pthread_cancel() to actually happen.
}
}
return;
}
state_ = State::kFinished; state_ = State::kFinished;
} }
std::lock_guard<std::mutex> lock(mutex_); {
std::lock_guard<std::mutex> lock(mutex_);
// Sometimes the thread can call terminate twice before stopping
if (thread_ == 0) return;
auto thread = thread_;
exit_code_ = exit_code;
signaled_ = true;
cond_.notify_all();
exit_code_ = exit_code;
signaled_ = true;
cond_.notify_all();
}
if (is_current_thread) {
pthread_exit(reinterpret_cast<void*>(exit_code));
} else {
#ifdef XE_PLATFORM_ANDROID #ifdef XE_PLATFORM_ANDROID
if (pthread_kill(thread, GetSystemSignal(SignalType::kThreadTerminate)) != if (pthread_kill(thread_,
0) { GetSystemSignal(SignalType::kThreadTerminate)) != 0) {
assert_always(); assert_always();
} }
#else #else
if (pthread_cancel(thread) != 0) { if (pthread_cancel(thread_) != 0) {
assert_always(); assert_always();
} }
#endif #endif
}
} }
void WaitStarted() const { void WaitStarted() const {
@ -778,7 +798,6 @@ class PosixCondition<Thread> : public PosixConditionBase {
inline void post_execution() override { inline void post_execution() override {
if (thread_) { if (thread_) {
pthread_join(thread_, nullptr); pthread_join(thread_, nullptr);
thread_ = 0;
} }
} }
pthread_t thread_; pthread_t thread_;
@ -1115,13 +1134,12 @@ Thread* Thread::GetCurrentThread() {
void Thread::Exit(int exit_code) { void Thread::Exit(int exit_code) {
if (current_thread_) { if (current_thread_) {
current_thread_->Terminate(exit_code); current_thread_->Terminate(exit_code);
// Sometimes the current thread keeps running after being cancelled.
// Prevent other calls from this thread from using current_thread_.
current_thread_ = nullptr;
} else { } else {
// Should only happen with the main thread // Should only happen with the main thread
pthread_exit(reinterpret_cast<void*>(exit_code)); pthread_exit(reinterpret_cast<void*>(exit_code));
} }
// Function must not return
assert_always();
} }
void set_name(const std::string_view name) { void set_name(const std::string_view name) {

View File

@ -111,30 +111,34 @@ bool SetTlsValue(TlsHandle handle, uintptr_t value) {
class Win32HighResolutionTimer : public HighResolutionTimer { class Win32HighResolutionTimer : public HighResolutionTimer {
public: public:
Win32HighResolutionTimer(std::function<void()> callback) Win32HighResolutionTimer(std::function<void()> callback)
: callback_(callback) {} : callback_(std::move(callback)) {}
~Win32HighResolutionTimer() override { ~Win32HighResolutionTimer() override {
if (handle_) { if (valid_) {
DeleteTimerQueueTimer(nullptr, handle_, INVALID_HANDLE_VALUE); DeleteTimerQueueTimer(nullptr, handle_, INVALID_HANDLE_VALUE);
handle_ = nullptr; handle_ = nullptr;
} }
} }
bool Initialize(std::chrono::milliseconds period) { bool Initialize(std::chrono::milliseconds period) {
return CreateTimerQueueTimer( if (valid_) {
&handle_, nullptr, // Double initialization
[](PVOID param, BOOLEAN timer_or_wait_fired) { assert_always();
auto timer = return false;
reinterpret_cast<Win32HighResolutionTimer*>(param); }
timer->callback_(); valid_ = !!CreateTimerQueueTimer(
}, &handle_, nullptr,
this, 0, DWORD(period.count()), WT_EXECUTEINTIMERTHREAD) [](PVOID param, BOOLEAN timer_or_wait_fired) {
? true auto timer = reinterpret_cast<Win32HighResolutionTimer*>(param);
: false; timer->callback_();
},
this, 0, DWORD(period.count()), WT_EXECUTEINTIMERTHREAD);
return valid_;
} }
private: private:
HANDLE handle_ = nullptr;
std::function<void()> callback_; std::function<void()> callback_;
HANDLE handle_ = nullptr;
bool valid_ = false; // Documentation does not state which HANDLE is invalid
}; };
std::unique_ptr<HighResolutionTimer> HighResolutionTimer::CreateRepeating( std::unique_ptr<HighResolutionTimer> HighResolutionTimer::CreateRepeating(
@ -143,7 +147,7 @@ std::unique_ptr<HighResolutionTimer> HighResolutionTimer::CreateRepeating(
if (!timer->Initialize(period)) { if (!timer->Initialize(period)) {
return nullptr; return nullptr;
} }
return std::unique_ptr<HighResolutionTimer>(timer.release()); return std::move(timer);
} }
template <typename T> template <typename T>

View File

@ -582,43 +582,40 @@ std::string find_name_from_path(const std::string_view path,
auto it = end; auto it = end;
--it; --it;
// path is padded with separator // skip trailing separators at the end of the path
size_t padding = 0; while (*it == uint32_t(separator)) {
if (*it == uint32_t(separator)) {
if (it == begin) { if (it == begin) {
// path is all separators, name is empty
return std::string(); return std::string();
} }
--it; --it;
padding = 1;
} }
// path is just separator // update end so it is before any trailing separators
if (it == begin) { end = std::next(it);
return std::string();
}
// search for separator // skip non-separators
while (it != begin) { while (*it != uint32_t(separator)) {
if (*it == uint32_t(separator)) { if (it == begin) {
break; break;
} }
--it; --it;
} }
// no separator -- copy entire string (except trailing separator) // if the iterator is on a separator, advance
if (it == begin) { if (*it == uint32_t(separator)) {
return std::string(path.substr(0, path.size() - padding)); ++it;
} }
auto length = byte_length(std::next(it), end); auto offset = byte_length(begin, it);
auto offset = path.length() - length; auto length = byte_length(it, end);
return std::string(path.substr(offset, length - padding)); return std::string(path.substr(offset, length));
} }
std::string find_base_name_from_path(const std::string_view path, std::string find_base_name_from_path(const std::string_view path,
char32_t separator) { char32_t separator) {
auto name = find_name_from_path(path, separator); auto name = find_name_from_path(path, separator);
if (!name.size()) { if (!name.length()) {
return std::string(); return std::string();
} }
@ -653,28 +650,34 @@ std::string find_base_path(const std::string_view path, char32_t separator) {
auto it = end; auto it = end;
--it; --it;
// skip trailing separator // skip trailing separators at the end of the path
if (*it == uint32_t(separator)) { while (*it == uint32_t(separator)) {
if (it == begin) { if (it == begin) {
return std::string(); return std::string();
} }
--it; --it;
} }
while (it != begin) { // skip non-separators
if (*it == uint32_t(separator)) { while (*it != uint32_t(separator)) {
break; if (it == begin) {
// there are no separators, base path is empty
return std::string();
} }
--it; --it;
} }
if (it == begin) { // skip trailing separators at the end of the base path
return std::string(); while (*it == uint32_t(separator)) {
if (it == begin) {
// base path is all separators, base path is empty
return std::string();
}
--it;
} }
auto length = byte_length(it, end); auto length = byte_length(begin, std::next(it));
auto offset = path.length() - length; return std::string(path.substr(0, length));
return std::string(path.substr(0, offset));
} }
std::string canonicalize_path(const std::string_view path, char32_t separator) { std::string canonicalize_path(const std::string_view path, char32_t separator) {

View File

@ -106,18 +106,6 @@ typedef struct alignas(16) vec128_s {
}; };
}; };
vec128_s() = default;
vec128_s(const vec128_s& other) {
high = other.high;
low = other.low;
}
vec128_s& operator=(const vec128_s& b) {
high = b.high;
low = b.low;
return *this;
}
bool operator==(const vec128_s& b) const { bool operator==(const vec128_s& b) const {
return low == b.low && high == b.high; return low == b.low && high == b.high;
} }

View File

@ -25,6 +25,14 @@ std::shared_ptr<cpptoml::table> ParseFile(
throw cpptoml::parse_exception(xe::path_to_utf8(filename) + throw cpptoml::parse_exception(xe::path_to_utf8(filename) +
" could not be opened for parsing"); " could not be opened for parsing");
} }
// since cpptoml can't parse files with a UTF-8 BOM we need to skip them
char bom[3];
file.read(bom, sizeof(bom));
if (file.fail() || bom[0] != '\xEF' || bom[1] != '\xBB' || bom[2] != '\xBF') {
file.clear();
file.seekg(0);
}
cpptoml::parser p(file); cpptoml::parser p(file);
return p.parse(); return p.parse();
} }

View File

@ -519,7 +519,7 @@ GuestToHostThunk X64ThunkEmitter::EmitGuestToHostThunk() {
} }
// X64Emitter handles actually resolving functions. // X64Emitter handles actually resolving functions.
extern "C" uint64_t ResolveFunction(void* raw_context, uint32_t target_address); uint64_t ResolveFunction(void* raw_context, uint64_t target_address);
ResolveFunctionThunk X64ThunkEmitter::EmitResolveFunctionThunk() { ResolveFunctionThunk X64ThunkEmitter::EmitResolveFunctionThunk() {
// ebx = target PPC address // ebx = target PPC address
@ -548,7 +548,7 @@ ResolveFunctionThunk X64ThunkEmitter::EmitResolveFunctionThunk() {
mov(rcx, rsi); // context mov(rcx, rsi); // context
mov(rdx, rbx); mov(rdx, rbx);
mov(rax, uint64_t(&ResolveFunction)); mov(rax, reinterpret_cast<uint64_t>(&ResolveFunction));
call(rax); call(rax);
EmitLoadVolatileRegs(); EmitLoadVolatileRegs();

View File

@ -382,15 +382,14 @@ void X64Emitter::UnimplementedInstr(const hir::Instr* i) {
} }
// This is used by the X64ThunkEmitter's ResolveFunctionThunk. // This is used by the X64ThunkEmitter's ResolveFunctionThunk.
extern "C" uint64_t ResolveFunction(void* raw_context, uint64_t ResolveFunction(void* raw_context, uint64_t target_address) {
uint64_t target_address) {
auto thread_state = *reinterpret_cast<ThreadState**>(raw_context); auto thread_state = *reinterpret_cast<ThreadState**>(raw_context);
// TODO(benvanik): required? // TODO(benvanik): required?
assert_not_zero(target_address); assert_not_zero(target_address);
auto fn = auto fn = thread_state->processor()->ResolveFunction(
thread_state->processor()->ResolveFunction((uint32_t)target_address); static_cast<uint32_t>(target_address));
assert_not_null(fn); assert_not_null(fn);
auto x64_fn = static_cast<X64Function*>(fn); auto x64_fn = static_cast<X64Function*>(fn);
uint64_t addr = reinterpret_cast<uint64_t>(x64_fn->machine_code()); uint64_t addr = reinterpret_cast<uint64_t>(x64_fn->machine_code());

View File

@ -105,8 +105,7 @@ struct Op : OpBase {
struct VoidOp : Op<VoidOp, KEY_TYPE_X> { struct VoidOp : Op<VoidOp, KEY_TYPE_X> {
protected: protected:
template <typename T, KeyType KEY_TYPE> friend struct Op<VoidOp, KEY_TYPE_X>;
friend struct Op;
template <hir::Opcode OPCODE, typename... Ts> template <hir::Opcode OPCODE, typename... Ts>
friend struct I; friend struct I;
void Load(const Instr::Op& op) {} void Load(const Instr::Op& op) {}
@ -116,8 +115,7 @@ struct OffsetOp : Op<OffsetOp, KEY_TYPE_O> {
uint64_t value; uint64_t value;
protected: protected:
template <typename T, KeyType KEY_TYPE> friend struct Op<OffsetOp, KEY_TYPE_O>;
friend struct Op;
template <hir::Opcode OPCODE, typename... Ts> template <hir::Opcode OPCODE, typename... Ts>
friend struct I; friend struct I;
void Load(const Instr::Op& op) { this->value = op.offset; } void Load(const Instr::Op& op) { this->value = op.offset; }
@ -127,8 +125,7 @@ struct SymbolOp : Op<SymbolOp, KEY_TYPE_S> {
Function* value; Function* value;
protected: protected:
template <typename T, KeyType KEY_TYPE> friend struct Op<SymbolOp, KEY_TYPE_S>;
friend struct Op;
template <hir::Opcode OPCODE, typename... Ts> template <hir::Opcode OPCODE, typename... Ts>
friend struct I; friend struct I;
bool Load(const Instr::Op& op) { bool Load(const Instr::Op& op) {
@ -141,8 +138,7 @@ struct LabelOp : Op<LabelOp, KEY_TYPE_L> {
hir::Label* value; hir::Label* value;
protected: protected:
template <typename T, KeyType KEY_TYPE> friend struct Op<LabelOp, KEY_TYPE_L>;
friend struct Op;
template <hir::Opcode OPCODE, typename... Ts> template <hir::Opcode OPCODE, typename... Ts>
friend struct I; friend struct I;
void Load(const Instr::Op& op) { this->value = op.label; } void Load(const Instr::Op& op) { this->value = op.label; }

View File

@ -2,7 +2,7 @@
****************************************************************************** ******************************************************************************
* Xenia : Xbox 360 Emulator Research Project * * Xenia : Xbox 360 Emulator Research Project *
****************************************************************************** ******************************************************************************
* Copyright 2014 Ben Vanik. All rights reserved. * * Copyright 2021 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. * * Released under the BSD license - see LICENSE in the root for more details. *
****************************************************************************** ******************************************************************************
*/ */
@ -73,14 +73,14 @@ void DataFlowAnalysisPass::AnalyzeFlow(HIRBuilder* builder,
// Stash for value map. We may want to maintain this during building. // Stash for value map. We may want to maintain this during building.
auto arena = builder->arena(); auto arena = builder->arena();
auto value_map = reinterpret_cast<Value**>( auto value_map = reinterpret_cast<Value**>(
arena->Alloc(sizeof(Value*) * max_value_estimate)); arena->Alloc(sizeof(Value*) * max_value_estimate, alignof(Value)));
// Allocate incoming bitvectors for use by blocks. We don't need outgoing // Allocate incoming bitvectors for use by blocks. We don't need outgoing
// because they are only used during the block iteration. // because they are only used during the block iteration.
// Mapped by block ordinal. // Mapped by block ordinal.
// TODO(benvanik): cache this list, grow as needed, etc. // TODO(benvanik): cache this list, grow as needed, etc.
auto incoming_bitvectors = auto incoming_bitvectors = (llvm::BitVector**)arena->Alloc(
(llvm::BitVector**)arena->Alloc(sizeof(llvm::BitVector*) * block_count); sizeof(llvm::BitVector*) * block_count, alignof(llvm::BitVector));
for (auto n = 0u; n < block_count; n++) { for (auto n = 0u; n < block_count; n++) {
incoming_bitvectors[n] = new llvm::BitVector(max_value_estimate); incoming_bitvectors[n] = new llvm::BitVector(max_value_estimate);
} }

View File

@ -2,7 +2,7 @@
****************************************************************************** ******************************************************************************
* Xenia : Xbox 360 Emulator Research Project * * Xenia : Xbox 360 Emulator Research Project *
****************************************************************************** ******************************************************************************
* Copyright 2020 Ben Vanik. All rights reserved. * * Copyright 2021 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. * * Released under the BSD license - see LICENSE in the root for more details. *
****************************************************************************** ******************************************************************************
*/ */
@ -45,7 +45,7 @@ bool FinalizationPass::Run(HIRBuilder* builder) {
while (label) { while (label) {
if (!label->name) { if (!label->name) {
const size_t label_len = 6 + 4; const size_t label_len = 6 + 4;
char* name = reinterpret_cast<char*>(arena->Alloc(label_len + 1)); char* name = reinterpret_cast<char*>(arena->Alloc(label_len + 1, 1));
assert_true(label->id <= 9999); assert_true(label->id <= 9999);
auto end = fmt::format_to_n(name, label_len, "_label{}", label->id); auto end = fmt::format_to_n(name, label_len, "_label{}", label->id);
name[end.size] = '\0'; name[end.size] = '\0';

View File

@ -39,7 +39,7 @@ enum class ExportCategory : uint8_t {
}; };
struct ExportTag { struct ExportTag {
typedef uint32_t type; using type = uint32_t;
// packed like so: // packed like so:
// ll...... cccccccc ........ ..bihssi // ll...... cccccccc ........ ..bihssi

View File

@ -2,7 +2,7 @@
****************************************************************************** ******************************************************************************
* Xenia : Xbox 360 Emulator Research Project * * Xenia : Xbox 360 Emulator Research Project *
****************************************************************************** ******************************************************************************
* Copyright 2020 Ben Vanik. All rights reserved. * * Copyright 2021 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. * * Released under the BSD license - see LICENSE in the root for more details. *
****************************************************************************** ******************************************************************************
*/ */
@ -739,7 +739,7 @@ void HIRBuilder::Comment(std::string_view value) {
return; return;
} }
auto size = value.size(); auto size = value.size();
auto p = reinterpret_cast<char*>(arena_->Alloc(size + 1)); auto p = reinterpret_cast<char*>(arena_->Alloc(size + 1, 1));
std::memcpy(p, value.data(), size); std::memcpy(p, value.data(), size);
p[size] = '\0'; p[size] = '\0';
Instr* i = AppendInstr(OPCODE_COMMENT_info, 0); Instr* i = AppendInstr(OPCODE_COMMENT_info, 0);
@ -752,7 +752,7 @@ void HIRBuilder::Comment(const StringBuffer& value) {
return; return;
} }
auto size = value.length(); auto size = value.length();
auto p = reinterpret_cast<char*>(arena_->Alloc(size + 1)); auto p = reinterpret_cast<char*>(arena_->Alloc(size + 1, 1));
std::memcpy(p, value.buffer(), size); std::memcpy(p, value.buffer(), size);
p[size] = '\0'; p[size] = '\0';
Instr* i = AppendInstr(OPCODE_COMMENT_info, 0); Instr* i = AppendInstr(OPCODE_COMMENT_info, 0);

View File

@ -2,7 +2,7 @@
****************************************************************************** ******************************************************************************
* Xenia : Xbox 360 Emulator Research Project * * Xenia : Xbox 360 Emulator Research Project *
****************************************************************************** ******************************************************************************
* Copyright 2020 Ben Vanik. All rights reserved. * * Copyright 2021 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. * * Released under the BSD license - see LICENSE in the root for more details. *
****************************************************************************** ******************************************************************************
*/ */
@ -75,7 +75,7 @@ class HIRBuilder {
template <typename... Args> template <typename... Args>
void CommentFormat(const std::string_view format, const Args&... args) { void CommentFormat(const std::string_view format, const Args&... args) {
static const uint32_t kMaxCommentSize = 1024; static const uint32_t kMaxCommentSize = 1024;
char* p = reinterpret_cast<char*>(arena_->Alloc(kMaxCommentSize)); char* p = reinterpret_cast<char*>(arena_->Alloc(kMaxCommentSize, 1));
auto result = fmt::format_to_n(p, kMaxCommentSize - 1, format, args...); auto result = fmt::format_to_n(p, kMaxCommentSize - 1, format, args...);
p[result.size] = '\0'; p[result.size] = '\0';
size_t rewind = kMaxCommentSize - 1 - result.size; size_t rewind = kMaxCommentSize - 1 - result.size;

View File

@ -2,7 +2,7 @@
****************************************************************************** ******************************************************************************
* Xenia : Xbox 360 Emulator Research Project * * Xenia : Xbox 360 Emulator Research Project *
****************************************************************************** ******************************************************************************
* Copyright 2013 Ben Vanik. All rights reserved. * * Copyright 2021 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. * * Released under the BSD license - see LICENSE in the root for more details. *
****************************************************************************** ******************************************************************************
*/ */
@ -245,65 +245,34 @@ void Value::Convert(TypeName target_type, RoundMode round_mode) {
} }
} }
template <typename T>
T __inline RoundValue(RoundMode round_mode, T value) {
switch (round_mode) {
case ROUND_TO_ZERO:
return std::trunc(value);
case ROUND_TO_NEAREST:
return std::round(value);
case ROUND_TO_MINUS_INFINITY:
return std::floor(value);
case ROUND_TO_POSITIVE_INFINITY:
return std::ceil(value);
default:
assert_unhandled_case(round_mode);
return value;
}
}
void Value::Round(RoundMode round_mode) { void Value::Round(RoundMode round_mode) {
switch (type) { switch (type) {
case FLOAT32_TYPE: case FLOAT32_TYPE:
switch (round_mode) { constant.f32 = RoundValue(round_mode, constant.f32);
case ROUND_TO_ZERO:
constant.f32 = std::trunc(constant.f32);
break;
case ROUND_TO_NEAREST:
constant.f32 = std::round(constant.f32);
return;
case ROUND_TO_MINUS_INFINITY:
constant.f32 = std::floor(constant.f32);
break;
case ROUND_TO_POSITIVE_INFINITY:
constant.f32 = std::ceil(constant.f32);
break;
default:
assert_unhandled_case(round_mode);
return;
}
return; return;
case FLOAT64_TYPE: case FLOAT64_TYPE:
switch (round_mode) { constant.f64 = RoundValue(round_mode, constant.f64);
case ROUND_TO_ZERO:
constant.f64 = std::trunc(constant.f64);
break;
case ROUND_TO_NEAREST:
constant.f64 = std::round(constant.f64);
return;
case ROUND_TO_MINUS_INFINITY:
constant.f64 = std::floor(constant.f64);
break;
case ROUND_TO_POSITIVE_INFINITY:
constant.f64 = std::ceil(constant.f64);
break;
default:
assert_unhandled_case(round_mode);
return;
}
return; return;
case VEC128_TYPE: case VEC128_TYPE:
for (int i = 0; i < 4; i++) { for (int i = 0; i < 4; i++) {
switch (round_mode) { constant.v128.f32[i] = RoundValue(round_mode, constant.v128.f32[i]);
case ROUND_TO_ZERO:
constant.v128.f32[i] = std::trunc(constant.v128.f32[i]);
break;
case ROUND_TO_NEAREST:
constant.v128.f32[i] = std::round(constant.v128.f32[i]);
break;
case ROUND_TO_MINUS_INFINITY:
constant.v128.f32[i] = std::floor(constant.v128.f32[i]);
break;
case ROUND_TO_POSITIVE_INFINITY:
constant.v128.f32[i] = std::ceil(constant.v128.f32[i]);
break;
default:
assert_unhandled_case(round_mode);
return;
}
} }
return; return;
default: default:

View File

@ -2,7 +2,7 @@
****************************************************************************** ******************************************************************************
* Xenia : Xbox 360 Emulator Research Project * * Xenia : Xbox 360 Emulator Research Project *
****************************************************************************** ******************************************************************************
* Copyright 2013 Ben Vanik. All rights reserved. * * Copyright 2021 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. * * Released under the BSD license - see LICENSE in the root for more details. *
****************************************************************************** ******************************************************************************
*/ */
@ -426,6 +426,10 @@ int InstrEmit_sc(PPCHIRBuilder& f, const InstrData& i) {
// Game code should only ever use LEV=0. // Game code should only ever use LEV=0.
// LEV=2 is to signify 'call import' from Xenia. // LEV=2 is to signify 'call import' from Xenia.
// TODO(gibbed): syscalls! // TODO(gibbed): syscalls!
if (i.SC.LEV == 0) {
f.CallExtern(f.builtins()->syscall_handler);
return 0;
}
if (i.SC.LEV == 2) { if (i.SC.LEV == 2) {
f.CallExtern(f.function()); f.CallExtern(f.function());
return 0; return 0;
@ -807,5 +811,5 @@ void RegisterEmitCategoryControl() {
} }
} // namespace cpu } // namespace cpu
} // namespace xe } // namespace cpu
} // namespace xe } // namespace xe

View File

@ -10,6 +10,7 @@
#include "xenia/cpu/ppc/ppc_frontend.h" #include "xenia/cpu/ppc/ppc_frontend.h"
#include "xenia/base/atomic.h" #include "xenia/base/atomic.h"
#include "xenia/base/logging.h"
#include "xenia/cpu/ppc/ppc_context.h" #include "xenia/cpu/ppc/ppc_context.h"
#include "xenia/cpu/ppc/ppc_emit.h" #include "xenia/cpu/ppc/ppc_emit.h"
#include "xenia/cpu/ppc/ppc_opcode_info.h" #include "xenia/cpu/ppc/ppc_opcode_info.h"
@ -78,6 +79,17 @@ void LeaveGlobalLock(PPCContext* ppc_context, void* arg0, void* arg1) {
global_mutex->unlock(); global_mutex->unlock();
} }
void SyscallHandler(PPCContext* ppc_context, void* arg0, void* arg1) {
uint64_t syscall_number = ppc_context->r[0];
switch (syscall_number) {
default:
assert_unhandled_case(syscall_number);
XELOGE("Unhandled syscall {}!", syscall_number);
break;
#pragma warning(suppress : 4065)
}
}
bool PPCFrontend::Initialize() { bool PPCFrontend::Initialize() {
void* arg0 = reinterpret_cast<void*>(&xe::global_critical_region::mutex()); void* arg0 = reinterpret_cast<void*>(&xe::global_critical_region::mutex());
void* arg1 = reinterpret_cast<void*>(&builtins_.global_lock_count); void* arg1 = reinterpret_cast<void*>(&builtins_.global_lock_count);
@ -87,6 +99,8 @@ bool PPCFrontend::Initialize() {
processor_->DefineBuiltin("EnterGlobalLock", EnterGlobalLock, arg0, arg1); processor_->DefineBuiltin("EnterGlobalLock", EnterGlobalLock, arg0, arg1);
builtins_.leave_global_lock = builtins_.leave_global_lock =
processor_->DefineBuiltin("LeaveGlobalLock", LeaveGlobalLock, arg0, arg1); processor_->DefineBuiltin("LeaveGlobalLock", LeaveGlobalLock, arg0, arg1);
builtins_.syscall_handler = processor_->DefineBuiltin(
"SyscallHandler", SyscallHandler, nullptr, nullptr);
return true; return true;
} }

View File

@ -33,6 +33,7 @@ struct PPCBuiltins {
Function* check_global_lock; Function* check_global_lock;
Function* enter_global_lock; Function* enter_global_lock;
Function* leave_global_lock; Function* leave_global_lock;
Function* syscall_handler;
}; };
class PPCFrontend { class PPCFrontend {

View File

@ -2,7 +2,7 @@
****************************************************************************** ******************************************************************************
* Xenia : Xbox 360 Emulator Research Project * * Xenia : Xbox 360 Emulator Research Project *
****************************************************************************** ******************************************************************************
* Copyright 2020 Ben Vanik. All rights reserved. * * Copyright 2021 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. * * Released under the BSD license - see LICENSE in the root for more details. *
****************************************************************************** ******************************************************************************
*/ */
@ -104,8 +104,8 @@ bool PPCHIRBuilder::Emit(GuestFunction* function, uint32_t flags) {
// instruction may have a label assigned to it if it hasn't been hit // instruction may have a label assigned to it if it hasn't been hit
// yet. // yet.
size_t list_size = instr_count_ * sizeof(void*); size_t list_size = instr_count_ * sizeof(void*);
instr_offset_list_ = (Instr**)arena_->Alloc(list_size); instr_offset_list_ = (Instr**)arena_->Alloc(list_size, alignof(void*));
label_list_ = (Label**)arena_->Alloc(list_size); label_list_ = (Label**)arena_->Alloc(list_size, alignof(void*));
std::memset(instr_offset_list_, 0, list_size); std::memset(instr_offset_list_, 0, list_size);
std::memset(label_list_, 0, list_size); std::memset(label_list_, 0, list_size);
@ -244,7 +244,7 @@ void PPCHIRBuilder::AnnotateLabel(uint32_t address, Label* label) {
char name_buffer[13]; char name_buffer[13];
auto format_result = fmt::format_to_n(name_buffer, 12, "loc_{:08X}", address); auto format_result = fmt::format_to_n(name_buffer, 12, "loc_{:08X}", address);
name_buffer[format_result.size] = '\0'; name_buffer[format_result.size] = '\0';
label->name = (char*)arena_->Alloc(sizeof(name_buffer)); label->name = (char*)arena_->Alloc(sizeof(name_buffer), 1);
memcpy(label->name, name_buffer, sizeof(name_buffer)); memcpy(label->name, name_buffer, sizeof(name_buffer));
} }

View File

@ -432,12 +432,12 @@ void Processor::LowerIrql(Irql old_value) {
} }
bool Processor::Save(ByteStream* stream) { bool Processor::Save(ByteStream* stream) {
stream->Write('PROC'); stream->Write(kProcessorSaveSignature);
return true; return true;
} }
bool Processor::Restore(ByteStream* stream) { bool Processor::Restore(ByteStream* stream) {
if (stream->Read<uint32_t>() != 'PROC') { if (stream->Read<uint32_t>() != kProcessorSaveSignature) {
XELOGE("Processor::Restore - Invalid magic value!"); XELOGE("Processor::Restore - Invalid magic value!");
return false; return false;
} }

View File

@ -34,6 +34,8 @@ DECLARE_bool(debug);
namespace xe { namespace xe {
namespace cpu { namespace cpu {
constexpr fourcc_t kProcessorSaveSignature = make_fourcc("PROC");
class Breakpoint; class Breakpoint;
class StackWalker; class StackWalker;
class XexModule; class XexModule;

View File

@ -20,7 +20,7 @@
#include "xenia/cpu/processor.h" #include "xenia/cpu/processor.h"
#include "xenia/cpu/test_module.h" #include "xenia/cpu/test_module.h"
#include "third_party/catch/single_include/catch.hpp" #include "third_party/catch/include/catch.hpp"
#define XENIA_TEST_X64 1 #define XENIA_TEST_X64 1

View File

@ -2,7 +2,7 @@
****************************************************************************** ******************************************************************************
* Xenia : Xbox 360 Emulator Research Project * * Xenia : Xbox 360 Emulator Research Project *
****************************************************************************** ******************************************************************************
* Copyright 2020 Ben Vanik. All rights reserved. * * Copyright 2021 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. * * Released under the BSD license - see LICENSE in the root for more details. *
****************************************************************************** ******************************************************************************
*/ */
@ -249,12 +249,11 @@ int XexModule::ApplyPatch(XexModule* module) {
// Patch base XEX header // Patch base XEX header
uint32_t original_image_size = module->image_size(); uint32_t original_image_size = module->image_size();
uint32_t header_target_size = patch_header->delta_headers_target_offset + uint32_t header_target_size = patch_header->size_of_target_headers;
patch_header->delta_headers_source_size;
if (!header_target_size) { if (!header_target_size) {
header_target_size = header_target_size = patch_header->delta_headers_target_offset +
patch_header->size_of_target_headers; // unsure which is more correct.. patch_header->delta_headers_source_size;
} }
size_t mem_size = module->xex_header_mem_.size(); size_t mem_size = module->xex_header_mem_.size();
@ -899,9 +898,9 @@ bool XexModule::Load(const std::string_view name, const std::string_view path,
const void* xex_addr, size_t xex_length) { const void* xex_addr, size_t xex_length) {
auto src_header = reinterpret_cast<const xex2_header*>(xex_addr); auto src_header = reinterpret_cast<const xex2_header*>(xex_addr);
if (src_header->magic == 'XEX1') { if (src_header->magic == kXEX1Signature) {
xex_format_ = kFormatXex1; xex_format_ = kFormatXex1;
} else if (src_header->magic == 'XEX2') { } else if (src_header->magic == kXEX2Signature) {
xex_format_ = kFormatXex2; xex_format_ = kFormatXex2;
} else { } else {
return false; return false;
@ -967,6 +966,16 @@ bool XexModule::LoadContinue() {
return false; return false;
} }
// Parse any "unsafe" headers into safer variants
xex2_opt_generic_u32* alternate_titleids;
if (GetOptHeader(xex2_header_keys::XEX_HEADER_ALTERNATE_TITLE_IDS,
&alternate_titleids)) {
auto count = alternate_titleids->count();
for (uint32_t i = 0; i < count; i++) {
opt_alternate_title_ids_.push_back(alternate_titleids->values[i]);
}
}
// Scan and find the low/high addresses. // Scan and find the low/high addresses.
// All code sections are continuous, so this should be easy. // All code sections are continuous, so this should be easy.
auto heap = memory()->LookupHeap(base_address_); auto heap = memory()->LookupHeap(base_address_);

View File

@ -25,6 +25,10 @@ class KernelState;
namespace xe { namespace xe {
namespace cpu { namespace cpu {
constexpr fourcc_t kXEX1Signature = make_fourcc("XEX1");
constexpr fourcc_t kXEX2Signature = make_fourcc("XEX2");
constexpr fourcc_t kElfSignature = make_fourcc(0x7F, 'E', 'L', 'F');
class Runtime; class Runtime;
class XexModule : public xe::cpu::Module { class XexModule : public xe::cpu::Module {
@ -103,6 +107,10 @@ class XexModule : public xe::cpu::Module {
return retval; return retval;
} }
std::vector<uint32_t> opt_alternate_title_ids() const {
return opt_alternate_title_ids_;
}
const uint32_t base_address() const { return base_address_; } const uint32_t base_address() const { return base_address_; }
const bool is_dev_kit() const { return is_dev_kit_; } const bool is_dev_kit() const { return is_dev_kit_; }
@ -194,6 +202,9 @@ class XexModule : public xe::cpu::Module {
import_libs_; // pre-loaded import libraries for ease of use import_libs_; // pre-loaded import libraries for ease of use
std::vector<PESection> pe_sections_; std::vector<PESection> pe_sections_;
// XEX_HEADER_ALTERNATE_TITLE_IDS loaded into a safe std::vector
std::vector<uint32_t> opt_alternate_title_ids_;
uint8_t session_key_[0x10]; uint8_t session_key_[0x10];
bool is_dev_kit_ = false; bool is_dev_kit_ = false;

View File

@ -42,6 +42,7 @@
#include "xenia/ui/imgui_dialog.h" #include "xenia/ui/imgui_dialog.h"
#include "xenia/vfs/devices/disc_image_device.h" #include "xenia/vfs/devices/disc_image_device.h"
#include "xenia/vfs/devices/host_path_device.h" #include "xenia/vfs/devices/host_path_device.h"
#include "xenia/vfs/devices/null_device.h"
#include "xenia/vfs/devices/stfs_container_device.h" #include "xenia/vfs/devices/stfs_container_device.h"
#include "xenia/vfs/virtual_file_system.h" #include "xenia/vfs/virtual_file_system.h"
@ -279,7 +280,7 @@ X_STATUS Emulator::LaunchXexFile(const std::filesystem::path& path) {
// and then get that symlinked to game:\, so // and then get that symlinked to game:\, so
// -> game:\foo.xex // -> game:\foo.xex
auto mount_path = "\\Device\\Harddisk0\\Partition0"; auto mount_path = "\\Device\\Harddisk0\\Partition1";
// Register the local directory in the virtual filesystem. // Register the local directory in the virtual filesystem.
auto parent_path = path.parent_path(); auto parent_path = path.parent_path();
@ -420,7 +421,7 @@ bool Emulator::SaveToFile(const std::filesystem::path& path) {
// Save the emulator state to a file // Save the emulator state to a file
ByteStream stream(map->data(), map->size()); ByteStream stream(map->data(), map->size());
stream.Write('XSAV'); stream.Write(kEmulatorSaveSignature);
stream.Write(title_id_.has_value()); stream.Write(title_id_.has_value());
if (title_id_.has_value()) { if (title_id_.has_value()) {
stream.Write(title_id_.value()); stream.Write(title_id_.value());
@ -454,7 +455,7 @@ bool Emulator::RestoreFromFile(const std::filesystem::path& path) {
auto lock = global_critical_region::AcquireDirect(); auto lock = global_critical_region::AcquireDirect();
ByteStream stream(map->data(), map->size()); ByteStream stream(map->data(), map->size());
if (stream.Read<uint32_t>() != 'XSAV') { if (stream.Read<uint32_t>() != kEmulatorSaveSignature) {
return false; return false;
} }
@ -672,6 +673,25 @@ static std::string format_version(xex2_version version) {
X_STATUS Emulator::CompleteLaunch(const std::filesystem::path& path, X_STATUS Emulator::CompleteLaunch(const std::filesystem::path& path,
const std::string_view module_path) { const std::string_view module_path) {
// 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
// should allow games to believe cache/raw disk was accessed successfully
// NOTE: this should probably be moved to xenia_main.cc, but right now we need
// to register the \Device\Harddisk0\ NullDevice _after_ the
// \Device\Harddisk0\Partition1 HostPathDevice, otherwise requests to
// Partition1 will go to this. Registering during CompleteLaunch allows us to
// make sure any HostPathDevices are ready beforehand.
// (see comment above cache:\ device registration for more info about why)
auto null_paths = {std::string("\\Partition0"), std::string("\\Cache0"),
std::string("\\Cache1")};
auto null_device =
std::make_unique<vfs::NullDevice>("\\Device\\Harddisk0", null_paths);
if (null_device->Initialize()) {
file_system_->RegisterDevice(std::move(null_device));
}
// Reset state. // Reset state.
title_id_ = std::nullopt; title_id_ = std::nullopt;
title_name_ = ""; title_name_ = "";

View File

@ -44,6 +44,8 @@ class Window;
namespace xe { namespace xe {
constexpr fourcc_t kEmulatorSaveSignature = make_fourcc("XSAV");
// The main type that runs the whole emulator. // The main type that runs the whole emulator.
// This is responsible for initializing and managing all the various subsystems. // This is responsible for initializing and managing all the various subsystems.
class Emulator { class Emulator {

View File

@ -517,6 +517,10 @@ bool CommandProcessor::ExecutePacket(RingBuffer* reader) {
return true; return true;
} }
if (packet == 0xCDCDCDCD) {
XELOGW("GPU packet is CDCDCDCD - probably read uninitialized memory!");
}
switch (packet_type) { switch (packet_type) {
case 0x00: case 0x00:
return ExecutePacketType0(reader, packet); return ExecutePacketType0(reader, packet);
@ -824,8 +828,8 @@ bool CommandProcessor::ExecutePacketType3_XE_SWAP(RingBuffer* reader,
// VdSwap will post this to tell us we need to swap the screen/fire an // VdSwap will post this to tell us we need to swap the screen/fire an
// interrupt. // interrupt.
// 63 words here, but only the first has any data. // 63 words here, but only the first has any data.
uint32_t magic = reader->ReadAndSwap<uint32_t>(); uint32_t magic = reader->ReadAndSwap<fourcc_t>();
assert_true(magic == 'SWAP'); assert_true(magic == kSwapSignature);
// TODO(benvanik): only swap frontbuffer ptr. // TODO(benvanik): only swap frontbuffer ptr.
uint32_t frontbuffer_ptr = reader->ReadAndSwap<uint32_t>(); uint32_t frontbuffer_ptr = reader->ReadAndSwap<uint32_t>();
@ -1145,6 +1149,8 @@ bool CommandProcessor::ExecutePacketType3_EVENT_WRITE_EXT(RingBuffer* reader,
bool CommandProcessor::ExecutePacketType3_EVENT_WRITE_ZPD(RingBuffer* reader, bool CommandProcessor::ExecutePacketType3_EVENT_WRITE_ZPD(RingBuffer* reader,
uint32_t packet, uint32_t packet,
uint32_t count) { uint32_t count) {
// Set by D3D as BE but struct ABI is LE
const uint32_t kQueryFinished = xe::byte_swap(0xFFFFFEED);
assert_true(count == 1); assert_true(count == 1);
uint32_t initiator = reader->ReadAndSwap<uint32_t>(); uint32_t initiator = reader->ReadAndSwap<uint32_t>();
// Writeback initiator. // Writeback initiator.
@ -1160,10 +1166,13 @@ bool CommandProcessor::ExecutePacketType3_EVENT_WRITE_ZPD(RingBuffer* reader,
register_file_->values[XE_GPU_REG_RB_SAMPLE_COUNT_ADDR].u32); register_file_->values[XE_GPU_REG_RB_SAMPLE_COUNT_ADDR].u32);
// 0xFFFFFEED is written to this two locations by D3D only on D3DISSUE_END // 0xFFFFFEED is written to this two locations by D3D only on D3DISSUE_END
// and used to detect a finished query. // and used to detect a finished query.
bool isEnd = pSampleCounts->ZPass_A == xe::byte_swap(0xFFFFFEED) && bool is_end_via_z_pass = pSampleCounts->ZPass_A == kQueryFinished &&
pSampleCounts->ZPass_B == xe::byte_swap(0xFFFFFEED); pSampleCounts->ZPass_B == kQueryFinished;
// Older versions of D3D also checks for ZFail (First Gears of War)
bool is_end_via_z_fail = pSampleCounts->ZFail_A == kQueryFinished &&
pSampleCounts->ZFail_B == kQueryFinished;
std::memset(pSampleCounts, 0, sizeof(xe_gpu_depth_sample_counts)); std::memset(pSampleCounts, 0, sizeof(xe_gpu_depth_sample_counts));
if (isEnd) { if (is_end_via_z_pass || is_end_via_z_fail) {
pSampleCounts->ZPass_A = fake_sample_count; pSampleCounts->ZPass_A = fake_sample_count;
pSampleCounts->Total_A = fake_sample_count; pSampleCounts->Total_A = fake_sample_count;
} }
@ -1172,40 +1181,77 @@ bool CommandProcessor::ExecutePacketType3_EVENT_WRITE_ZPD(RingBuffer* reader,
return true; return true;
} }
bool CommandProcessor::ExecutePacketType3_DRAW_INDX(RingBuffer* reader, bool CommandProcessor::ExecutePacketType3Draw(RingBuffer* reader,
uint32_t packet, uint32_t packet,
uint32_t count) { const char* opcode_name,
// initiate fetch of index buffer and draw uint32_t viz_query_condition,
// if dword0 != 0, this is a conditional draw based on viz query. uint32_t count_remaining) {
// if viz_query_condition != 0, this is a conditional draw based on viz query.
// This ID matches the one issued in PM4_VIZ_QUERY // This ID matches the one issued in PM4_VIZ_QUERY
uint32_t dword0 = reader->ReadAndSwap<uint32_t>(); // viz query info // uint32_t viz_id = viz_query_condition & 0x3F;
// uint32_t viz_id = dword0 & 0x3F;
// when true, render conditionally based on query result // when true, render conditionally based on query result
// uint32_t viz_use = dword0 & 0x100; // uint32_t viz_use = viz_query_condition & 0x100;
assert_not_zero(count_remaining);
if (!count_remaining) {
XELOGE("{}: Packet too small, can't read VGT_DRAW_INITIATOR", opcode_name);
return false;
}
reg::VGT_DRAW_INITIATOR vgt_draw_initiator; reg::VGT_DRAW_INITIATOR vgt_draw_initiator;
vgt_draw_initiator.value = reader->ReadAndSwap<uint32_t>(); vgt_draw_initiator.value = reader->ReadAndSwap<uint32_t>();
--count_remaining;
WriteRegister(XE_GPU_REG_VGT_DRAW_INITIATOR, vgt_draw_initiator.value); WriteRegister(XE_GPU_REG_VGT_DRAW_INITIATOR, vgt_draw_initiator.value);
bool success = true;
// TODO(Triang3l): Remove IndexBufferInfo and replace handling of all this
// with PrimitiveProcessor when the old Vulkan renderer is removed.
bool is_indexed = false; bool is_indexed = false;
IndexBufferInfo index_buffer_info; IndexBufferInfo index_buffer_info;
switch (vgt_draw_initiator.source_select) { switch (vgt_draw_initiator.source_select) {
case xenos::SourceSelect::kDMA: { case xenos::SourceSelect::kDMA: {
// Indexed draw. // Indexed draw.
is_indexed = true; is_indexed = true;
index_buffer_info.guest_base = reader->ReadAndSwap<uint32_t>();
uint32_t index_size = reader->ReadAndSwap<uint32_t>(); // Two separate bounds checks so if there's only one missing register
index_buffer_info.endianness = // value out of two, one uint32_t will be skipped in the command buffer,
static_cast<xenos::Endian>(index_size >> 30); // not two.
index_size &= 0x00FFFFFF; assert_not_zero(count_remaining);
if (!count_remaining) {
XELOGE("{}: Packet too small, can't read VGT_DMA_BASE", opcode_name);
return false;
}
uint32_t vgt_dma_base = reader->ReadAndSwap<uint32_t>();
--count_remaining;
WriteRegister(XE_GPU_REG_VGT_DMA_BASE, vgt_dma_base);
reg::VGT_DMA_SIZE vgt_dma_size;
assert_not_zero(count_remaining);
if (!count_remaining) {
XELOGE("{}: Packet too small, can't read VGT_DMA_SIZE", opcode_name);
return false;
}
vgt_dma_size.value = reader->ReadAndSwap<uint32_t>();
--count_remaining;
WriteRegister(XE_GPU_REG_VGT_DMA_SIZE, vgt_dma_size.value);
uint32_t index_size_bytes =
vgt_draw_initiator.index_size == xenos::IndexFormat::kInt16
? sizeof(uint16_t)
: sizeof(uint32_t);
// The base address must already be word-aligned according to the R6xx
// documentation, but for safety.
index_buffer_info.guest_base = vgt_dma_base & ~(index_size_bytes - 1);
index_buffer_info.endianness = vgt_dma_size.swap_mode;
index_buffer_info.format = vgt_draw_initiator.index_size; index_buffer_info.format = vgt_draw_initiator.index_size;
index_size *= index_buffer_info.length = vgt_dma_size.num_words * index_size_bytes;
(vgt_draw_initiator.index_size == xenos::IndexFormat::kInt32) ? 4 : 2;
index_buffer_info.length = index_size;
index_buffer_info.count = vgt_draw_initiator.num_indices; index_buffer_info.count = vgt_draw_initiator.num_indices;
} break; } break;
case xenos::SourceSelect::kImmediate: { case xenos::SourceSelect::kImmediate: {
// TODO(Triang3l): VGT_IMMED_DATA. // TODO(Triang3l): VGT_IMMED_DATA.
XELOGE(
"{}: Using immediate vertex indices, which are not supported yet. "
"Report the game to Xenia developers!",
opcode_name, uint32_t(vgt_draw_initiator.source_select));
success = false;
assert_always(); assert_always();
} break; } break;
case xenos::SourceSelect::kAutoIndex: { case xenos::SourceSelect::kAutoIndex: {
@ -1214,71 +1260,65 @@ bool CommandProcessor::ExecutePacketType3_DRAW_INDX(RingBuffer* reader,
index_buffer_info.length = 0; index_buffer_info.length = 0;
} break; } break;
default: { default: {
// Invalid source select. // Invalid source selection.
assert_always(); success = false;
assert_unhandled_case(vgt_draw_initiator.source_select);
} break; } break;
} }
auto viz_query = register_file_->Get<reg::PA_SC_VIZ_QUERY>(); // Skip to the next command, for example, if there are immediate indexes that
if (viz_query.viz_query_ena && viz_query.kill_pix_post_hi_z) { // we don't support yet.
// TODO(Triang3l): Don't drop the draw call completely if the vertex shader reader->AdvanceRead(count_remaining * sizeof(uint32_t));
// has memexport.
// TODO(Triang3l || JoelLinn): Handle this properly in the render backends. if (success) {
return true; auto viz_query = register_file_->Get<reg::PA_SC_VIZ_QUERY>();
if (!(viz_query.viz_query_ena && viz_query.kill_pix_post_hi_z)) {
// TODO(Triang3l): Don't drop the draw call completely if the vertex
// shader has memexport.
// TODO(Triang3l || JoelLinn): Handle this properly in the render
// backends.
success = IssueDraw(
vgt_draw_initiator.prim_type, vgt_draw_initiator.num_indices,
is_indexed ? &index_buffer_info : nullptr,
xenos::IsMajorModeExplicit(vgt_draw_initiator.major_mode,
vgt_draw_initiator.prim_type));
if (!success) {
XELOGE("{}({}, {}, {}): Failed in backend", opcode_name,
vgt_draw_initiator.num_indices,
uint32_t(vgt_draw_initiator.prim_type),
uint32_t(vgt_draw_initiator.source_select));
}
}
} }
bool success = return success;
IssueDraw(vgt_draw_initiator.prim_type, vgt_draw_initiator.num_indices, }
is_indexed ? &index_buffer_info : nullptr,
xenos::IsMajorModeExplicit(vgt_draw_initiator.major_mode,
vgt_draw_initiator.prim_type));
if (!success) {
XELOGE("PM4_DRAW_INDX({}, {}, {}): Failed in backend",
vgt_draw_initiator.num_indices,
uint32_t(vgt_draw_initiator.prim_type),
uint32_t(vgt_draw_initiator.source_select));
}
return true; bool CommandProcessor::ExecutePacketType3_DRAW_INDX(RingBuffer* reader,
uint32_t packet,
uint32_t count) {
// "initiate fetch of index buffer and draw"
// Generally used by Xbox 360 Direct3D 9 for kDMA and kAutoIndex sources.
// With a viz query token as the first one.
uint32_t count_remaining = count;
assert_not_zero(count_remaining);
if (!count_remaining) {
XELOGE("PM4_DRAW_INDX: Packet too small, can't read the viz query token");
return false;
}
uint32_t viz_query_condition = reader->ReadAndSwap<uint32_t>();
--count_remaining;
return ExecutePacketType3Draw(reader, packet, "PM4_DRAW_INDX",
viz_query_condition, count_remaining);
} }
bool CommandProcessor::ExecutePacketType3_DRAW_INDX_2(RingBuffer* reader, bool CommandProcessor::ExecutePacketType3_DRAW_INDX_2(RingBuffer* reader,
uint32_t packet, uint32_t packet,
uint32_t count) { uint32_t count) {
// draw using supplied indices in packet // "draw using supplied indices in packet"
reg::VGT_DRAW_INITIATOR vgt_draw_initiator; // Generally used by Xbox 360 Direct3D 9 for kAutoIndex source.
vgt_draw_initiator.value = reader->ReadAndSwap<uint32_t>(); // No viz query token.
WriteRegister(XE_GPU_REG_VGT_DRAW_INITIATOR, vgt_draw_initiator.value); return ExecutePacketType3Draw(reader, packet, "PM4_DRAW_INDX_2", 0, count);
assert_true(vgt_draw_initiator.source_select ==
xenos::SourceSelect::kAutoIndex);
// Index buffer unused as automatic.
// uint32_t indices_size =
// vgt_draw_initiator.num_indices *
// (vgt_draw_initiator.index_size == xenos::IndexFormat::kInt32 ? 4
// : 2);
// uint32_t index_ptr = reader->ptr();
// TODO(Triang3l): VGT_IMMED_DATA.
reader->AdvanceRead((count - 1) * sizeof(uint32_t));
auto viz_query = register_file_->Get<reg::PA_SC_VIZ_QUERY>();
if (viz_query.viz_query_ena && viz_query.kill_pix_post_hi_z) {
// TODO(Triang3l): Don't drop the draw call completely if the vertex shader
// has memexport.
// TODO(Triang3l || JoelLinn): Handle this properly in the render backends.
return true;
}
bool success = IssueDraw(
vgt_draw_initiator.prim_type, vgt_draw_initiator.num_indices, nullptr,
xenos::IsMajorModeExplicit(vgt_draw_initiator.major_mode,
vgt_draw_initiator.prim_type));
if (!success) {
XELOGE("PM4_DRAW_INDX_IMM({}, {}): Failed in backend",
vgt_draw_initiator.num_indices,
uint32_t(vgt_draw_initiator.prim_type));
}
return true;
} }
bool CommandProcessor::ExecutePacketType3_SET_CONSTANT(RingBuffer* reader, bool CommandProcessor::ExecutePacketType3_SET_CONSTANT(RingBuffer* reader,

View File

@ -218,6 +218,10 @@ class CommandProcessor {
uint32_t count); uint32_t count);
bool ExecutePacketType3_EVENT_WRITE_ZPD(RingBuffer* reader, uint32_t packet, bool ExecutePacketType3_EVENT_WRITE_ZPD(RingBuffer* reader, uint32_t packet,
uint32_t count); uint32_t count);
bool ExecutePacketType3Draw(RingBuffer* reader, uint32_t packet,
const char* opcode_name,
uint32_t viz_query_condition,
uint32_t count_remaining);
bool ExecutePacketType3_DRAW_INDX(RingBuffer* reader, uint32_t packet, bool ExecutePacketType3_DRAW_INDX(RingBuffer* reader, uint32_t packet,
uint32_t count); uint32_t count);
bool ExecutePacketType3_DRAW_INDX_2(RingBuffer* reader, uint32_t packet, bool ExecutePacketType3_DRAW_INDX_2(RingBuffer* reader, uint32_t packet,

View File

@ -81,7 +81,7 @@ void D3D12CommandProcessor::RequestFrameTrace(
void D3D12CommandProcessor::TracePlaybackWroteMemory(uint32_t base_ptr, void D3D12CommandProcessor::TracePlaybackWroteMemory(uint32_t base_ptr,
uint32_t length) { uint32_t length) {
shared_memory_->MemoryInvalidationCallback(base_ptr, length, true); shared_memory_->MemoryInvalidationCallback(base_ptr, length, true);
primitive_converter_->MemoryInvalidationCallback(base_ptr, length, true); primitive_processor_->MemoryInvalidationCallback(base_ptr, length, true);
} }
void D3D12CommandProcessor::RestoreEdramSnapshot(const void* snapshot) { void D3D12CommandProcessor::RestoreEdramSnapshot(const void* snapshot) {
@ -1194,6 +1194,13 @@ bool D3D12CommandProcessor::SetupContext() {
return false; return false;
} }
primitive_processor_ = std::make_unique<D3D12PrimitiveProcessor>(
*register_file_, *memory_, trace_writer_, *shared_memory_, *this);
if (!primitive_processor_->Initialize()) {
XELOGE("Failed to initialize the geometric primitive processor");
return false;
}
texture_cache_ = std::make_unique<TextureCache>( texture_cache_ = std::make_unique<TextureCache>(
*this, *register_file_, *shared_memory_, bindless_resources_used_, *this, *register_file_, *shared_memory_, bindless_resources_used_,
render_target_cache_->GetResolutionScale()); render_target_cache_->GetResolutionScale());
@ -1210,13 +1217,6 @@ bool D3D12CommandProcessor::SetupContext() {
return false; return false;
} }
primitive_converter_ = std::make_unique<PrimitiveConverter>(
*this, *register_file_, *memory_, trace_writer_);
if (!primitive_converter_->Initialize()) {
XELOGE("Failed to initialize the geometric primitive converter");
return false;
}
D3D12_HEAP_FLAGS heap_flag_create_not_zeroed = D3D12_HEAP_FLAGS heap_flag_create_not_zeroed =
provider.GetHeapFlagCreateNotZeroed(); provider.GetHeapFlagCreateNotZeroed();
@ -1529,12 +1529,12 @@ void D3D12CommandProcessor::ShutdownContext() {
ui::d3d12::util::ReleaseAndNull(gamma_ramp_upload_); ui::d3d12::util::ReleaseAndNull(gamma_ramp_upload_);
ui::d3d12::util::ReleaseAndNull(gamma_ramp_texture_); ui::d3d12::util::ReleaseAndNull(gamma_ramp_texture_);
primitive_converter_.reset();
pipeline_cache_.reset(); pipeline_cache_.reset();
texture_cache_.reset(); texture_cache_.reset();
primitive_processor_.reset();
shared_memory_.reset(); shared_memory_.reset();
// Shut down binding - bindless descriptors may be owned by subsystems like // Shut down binding - bindless descriptors may be owned by subsystems like
@ -1822,13 +1822,13 @@ bool D3D12CommandProcessor::IssueDraw(xenos::PrimitiveType primitive_type,
uint32_t index_count, uint32_t index_count,
IndexBufferInfo* index_buffer_info, IndexBufferInfo* index_buffer_info,
bool major_mode_explicit) { bool major_mode_explicit) {
auto device = GetD3D12Context().GetD3D12Provider().GetDevice();
auto& regs = *register_file_;
#if XE_UI_D3D12_FINE_GRAINED_DRAW_SCOPES #if XE_UI_D3D12_FINE_GRAINED_DRAW_SCOPES
SCOPE_profile_cpu_f("gpu"); SCOPE_profile_cpu_f("gpu");
#endif // XE_UI_D3D12_FINE_GRAINED_DRAW_SCOPES #endif // XE_UI_D3D12_FINE_GRAINED_DRAW_SCOPES
ID3D12Device* device = GetD3D12Context().GetD3D12Provider().GetDevice();
const RegisterFile& regs = *register_file_;
xenos::ModeControl edram_mode = regs.Get<reg::RB_MODECONTROL>().edram_mode; xenos::ModeControl edram_mode = regs.Get<reg::RB_MODECONTROL>().edram_mode;
if (edram_mode == xenos::ModeControl::kCopy) { if (edram_mode == xenos::ModeControl::kCopy) {
// Special copy handling. // Special copy handling.
@ -1842,7 +1842,7 @@ bool D3D12CommandProcessor::IssueDraw(xenos::PrimitiveType primitive_type,
return true; return true;
} }
// Vertex shader. // Vertex shader analysis.
auto vertex_shader = static_cast<D3D12Shader*>(active_vertex_shader()); auto vertex_shader = static_cast<D3D12Shader*>(active_vertex_shader());
if (!vertex_shader) { if (!vertex_shader) {
// Always need a vertex shader. // Always need a vertex shader.
@ -1850,16 +1850,9 @@ bool D3D12CommandProcessor::IssueDraw(xenos::PrimitiveType primitive_type,
} }
pipeline_cache_->AnalyzeShaderUcode(*vertex_shader); pipeline_cache_->AnalyzeShaderUcode(*vertex_shader);
bool memexport_used_vertex = vertex_shader->is_valid_memexport_used(); bool memexport_used_vertex = vertex_shader->is_valid_memexport_used();
DxbcShaderTranslator::Modification vertex_shader_modification;
pipeline_cache_->GetCurrentShaderModification(*vertex_shader,
vertex_shader_modification);
bool tessellated =
vertex_shader_modification.vertex.host_vertex_shader_type !=
Shader::HostVertexShaderType::kVertex;
bool primitive_polygonal =
xenos::IsPrimitivePolygonal(tessellated, primitive_type);
// Pixel shader. // Pixel shader analysis.
bool primitive_polygonal = draw_util::IsPrimitivePolygonal(regs);
bool is_rasterization_done = bool is_rasterization_done =
draw_util::IsRasterizationPotentiallyDone(regs, primitive_polygonal); draw_util::IsRasterizationPotentiallyDone(regs, primitive_polygonal);
D3D12Shader* pixel_shader = nullptr; D3D12Shader* pixel_shader = nullptr;
@ -1884,23 +1877,31 @@ bool D3D12CommandProcessor::IssueDraw(xenos::PrimitiveType primitive_type,
return true; return true;
} }
} }
bool memexport_used_pixel; bool memexport_used_pixel =
DxbcShaderTranslator::Modification pixel_shader_modification; pixel_shader && pixel_shader->is_valid_memexport_used();
if (pixel_shader) {
memexport_used_pixel = pixel_shader->is_valid_memexport_used();
if (!pipeline_cache_->GetCurrentShaderModification(
*pixel_shader, pixel_shader_modification)) {
return false;
}
} else {
memexport_used_pixel = false;
pixel_shader_modification = DxbcShaderTranslator::Modification(0);
}
bool memexport_used = memexport_used_vertex || memexport_used_pixel; bool memexport_used = memexport_used_vertex || memexport_used_pixel;
BeginSubmission(true); BeginSubmission(true);
// Process primitives.
PrimitiveProcessor::ProcessingResult primitive_processing_result;
if (!primitive_processor_->Process(primitive_processing_result)) {
return false;
}
if (!primitive_processing_result.host_draw_vertex_count) {
// Nothing to draw.
return true;
}
// Shader modifications.
DxbcShaderTranslator::Modification vertex_shader_modification =
pipeline_cache_->GetCurrentVertexShaderModification(
*vertex_shader, primitive_processing_result.host_vertex_shader_type);
DxbcShaderTranslator::Modification pixel_shader_modification =
pixel_shader
? pipeline_cache_->GetCurrentPixelShaderModification(*pixel_shader)
: DxbcShaderTranslator::Modification(0);
// Set up the render targets - this may perform dispatches and draws. // Set up the render targets - this may perform dispatches and draws.
uint32_t pixel_shader_writes_color_targets = uint32_t pixel_shader_writes_color_targets =
pixel_shader ? pixel_shader->writes_color_targets() : 0; pixel_shader ? pixel_shader->writes_color_targets() : 0;
@ -1909,66 +1910,6 @@ bool D3D12CommandProcessor::IssueDraw(xenos::PrimitiveType primitive_type,
return false; return false;
} }
// Set up primitive topology.
bool indexed = index_buffer_info != nullptr && index_buffer_info->guest_base;
xenos::PrimitiveType primitive_type_converted;
D3D_PRIMITIVE_TOPOLOGY primitive_topology;
if (tessellated) {
primitive_type_converted = primitive_type;
switch (primitive_type_converted) {
// TODO(Triang3l): Support all kinds of patches if found in games.
case xenos::PrimitiveType::kTriangleList:
case xenos::PrimitiveType::kTrianglePatch:
primitive_topology = D3D_PRIMITIVE_TOPOLOGY_3_CONTROL_POINT_PATCHLIST;
break;
case xenos::PrimitiveType::kQuadList:
case xenos::PrimitiveType::kQuadPatch:
primitive_topology = D3D_PRIMITIVE_TOPOLOGY_4_CONTROL_POINT_PATCHLIST;
break;
default:
return false;
}
} else {
primitive_type_converted =
PrimitiveConverter::GetReplacementPrimitiveType(primitive_type);
switch (primitive_type_converted) {
case xenos::PrimitiveType::kPointList:
primitive_topology = D3D_PRIMITIVE_TOPOLOGY_POINTLIST;
break;
case xenos::PrimitiveType::kLineList:
primitive_topology = D3D_PRIMITIVE_TOPOLOGY_LINELIST;
break;
case xenos::PrimitiveType::kLineStrip:
primitive_topology = D3D_PRIMITIVE_TOPOLOGY_LINESTRIP;
break;
case xenos::PrimitiveType::kTriangleList:
case xenos::PrimitiveType::kRectangleList:
primitive_topology = D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
break;
case xenos::PrimitiveType::kTriangleStrip:
primitive_topology = D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP;
break;
case xenos::PrimitiveType::kQuadList:
primitive_topology = D3D_PRIMITIVE_TOPOLOGY_LINELIST_ADJ;
break;
default:
return false;
}
}
SetPrimitiveTopology(primitive_topology);
uint32_t line_loop_closing_index;
if (primitive_type == xenos::PrimitiveType::kLineLoop && !indexed &&
index_count >= 3) {
// Add a vertex to close the loop, and make the vertex shader replace its
// index (before adding the offset) with 0 to fetch the first vertex again.
// For indexed line loops, the primitive converter will add the vertex.
line_loop_closing_index = index_count;
++index_count;
} else {
// Replace index 0 with 0 (do nothing) otherwise.
line_loop_closing_index = 0;
}
// Translate the shaders and create the pipeline if needed. // Translate the shaders and create the pipeline if needed.
D3D12Shader::D3D12Translation* vertex_shader_translation = D3D12Shader::D3D12Translation* vertex_shader_translation =
static_cast<D3D12Shader::D3D12Translation*>( static_cast<D3D12Shader::D3D12Translation*>(
@ -1987,6 +1928,7 @@ bool D3D12CommandProcessor::IssueDraw(xenos::PrimitiveType primitive_type,
if (host_render_targets_used) { if (host_render_targets_used) {
bound_depth_and_color_render_target_bits = bound_depth_and_color_render_target_bits =
render_target_cache_->GetLastUpdateBoundRenderTargets( render_target_cache_->GetLastUpdateBoundRenderTargets(
render_target_cache_->gamma_render_target_as_srgb(),
bound_depth_and_color_render_target_formats); bound_depth_and_color_render_target_formats);
} else { } else {
bound_depth_and_color_render_target_bits = 0; bound_depth_and_color_render_target_bits = 0;
@ -1995,9 +1937,7 @@ bool D3D12CommandProcessor::IssueDraw(xenos::PrimitiveType primitive_type,
ID3D12RootSignature* root_signature; ID3D12RootSignature* root_signature;
if (!pipeline_cache_->ConfigurePipeline( if (!pipeline_cache_->ConfigurePipeline(
vertex_shader_translation, pixel_shader_translation, vertex_shader_translation, pixel_shader_translation,
primitive_type_converted, primitive_processing_result, bound_depth_and_color_render_target_bits,
indexed ? index_buffer_info->format : xenos::IndexFormat::kInt16,
bound_depth_and_color_render_target_bits,
bound_depth_and_color_render_target_formats, &pipeline_handle, bound_depth_and_color_render_target_formats, &pipeline_handle,
&root_signature)) { &root_signature)) {
return false; return false;
@ -2048,9 +1988,10 @@ bool D3D12CommandProcessor::IssueDraw(xenos::PrimitiveType primitive_type,
// Update system constants before uploading them. // Update system constants before uploading them.
// TODO(Triang3l): With ROV, pass the disabled render target mask for safety. // TODO(Triang3l): With ROV, pass the disabled render target mask for safety.
UpdateSystemConstantValues( UpdateSystemConstantValues(
memexport_used, primitive_polygonal, line_loop_closing_index, memexport_used, primitive_polygonal,
indexed ? index_buffer_info->endianness : xenos::Endian::kNone, primitive_processing_result.line_loop_closing_index,
viewport_info, used_texture_mask, primitive_processing_result.host_index_endian, viewport_info,
used_texture_mask,
pixel_shader ? GetCurrentColorMask(pixel_shader->writes_color_targets()) pixel_shader ? GetCurrentColorMask(pixel_shader->writes_color_targets())
: 0); : 0);
@ -2206,112 +2147,139 @@ bool D3D12CommandProcessor::IssueDraw(xenos::PrimitiveType primitive_type,
} }
} }
// Actually draw. // Primitive topology.
if (indexed) { D3D_PRIMITIVE_TOPOLOGY primitive_topology;
uint32_t index_size = if (primitive_processing_result.IsTessellated()) {
index_buffer_info->format == xenos::IndexFormat::kInt32 switch (primitive_processing_result.host_primitive_type) {
? sizeof(uint32_t) // TODO(Triang3l): Support all primitive types.
: sizeof(uint16_t); case xenos::PrimitiveType::kTriangleList:
assert_false(index_buffer_info->guest_base & (index_size - 1)); case xenos::PrimitiveType::kTrianglePatch:
uint32_t index_base = primitive_topology = D3D_PRIMITIVE_TOPOLOGY_3_CONTROL_POINT_PATCHLIST;
index_buffer_info->guest_base & 0x1FFFFFFF & ~(index_size - 1); break;
D3D12_INDEX_BUFFER_VIEW index_buffer_view; case xenos::PrimitiveType::kQuadList:
index_buffer_view.Format = case xenos::PrimitiveType::kQuadPatch:
index_buffer_info->format == xenos::IndexFormat::kInt32 primitive_topology = D3D_PRIMITIVE_TOPOLOGY_4_CONTROL_POINT_PATCHLIST;
? DXGI_FORMAT_R32_UINT break;
: DXGI_FORMAT_R16_UINT; default:
PrimitiveConverter::ConversionResult conversion_result;
uint32_t converted_index_count;
if (tessellated) {
conversion_result =
PrimitiveConverter::ConversionResult::kConversionNotNeeded;
} else {
conversion_result = primitive_converter_->ConvertPrimitives(
primitive_type, index_buffer_info->guest_base, index_count,
index_buffer_info->format, index_buffer_info->endianness,
index_buffer_view.BufferLocation, converted_index_count);
if (conversion_result == PrimitiveConverter::ConversionResult::kFailed) {
return false;
}
if (conversion_result ==
PrimitiveConverter::ConversionResult::kPrimitiveEmpty) {
return true;
}
}
ID3D12Resource* scratch_index_buffer = nullptr;
if (conversion_result == PrimitiveConverter::ConversionResult::kConverted) {
index_buffer_view.SizeInBytes = converted_index_count * index_size;
index_count = converted_index_count;
} else {
uint32_t index_buffer_size = index_buffer_info->count * index_size;
if (!shared_memory_->RequestRange(index_base, index_buffer_size)) {
XELOGE( XELOGE(
"Failed to request index buffer at 0x{:08X} (size {}) in the " "Host tessellated primitive type {} returned by the primitive "
"shared memory", "processor is not supported by the Direct3D 12 command processor",
index_base, index_buffer_size); uint32_t(primitive_processing_result.host_primitive_type));
assert_unhandled_case(primitive_processing_result.host_primitive_type);
return false; return false;
}
if (memexport_used) {
// If the shared memory is a UAV, it can't be used as an index buffer
// (UAV is a read/write state, index buffer is a read-only state). Need
// to copy the indices to a buffer in the index buffer state.
scratch_index_buffer = RequestScratchGPUBuffer(
index_buffer_size, D3D12_RESOURCE_STATE_COPY_DEST);
if (scratch_index_buffer == nullptr) {
return false;
}
shared_memory_->UseAsCopySource();
SubmitBarriers();
deferred_command_list_.D3DCopyBufferRegion(
scratch_index_buffer, 0, shared_memory_->GetBuffer(), index_base,
index_buffer_size);
PushTransitionBarrier(scratch_index_buffer,
D3D12_RESOURCE_STATE_COPY_DEST,
D3D12_RESOURCE_STATE_INDEX_BUFFER);
index_buffer_view.BufferLocation =
scratch_index_buffer->GetGPUVirtualAddress();
} else {
index_buffer_view.BufferLocation =
shared_memory_->GetGPUAddress() + index_base;
}
index_buffer_view.SizeInBytes = index_buffer_size;
} }
} else {
switch (primitive_processing_result.host_primitive_type) {
case xenos::PrimitiveType::kPointList:
primitive_topology = D3D_PRIMITIVE_TOPOLOGY_POINTLIST;
break;
case xenos::PrimitiveType::kLineList:
primitive_topology = D3D_PRIMITIVE_TOPOLOGY_LINELIST;
break;
case xenos::PrimitiveType::kLineStrip:
primitive_topology = D3D_PRIMITIVE_TOPOLOGY_LINESTRIP;
break;
case xenos::PrimitiveType::kTriangleList:
case xenos::PrimitiveType::kRectangleList:
primitive_topology = D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
break;
case xenos::PrimitiveType::kTriangleStrip:
primitive_topology = D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP;
break;
case xenos::PrimitiveType::kQuadList:
primitive_topology = D3D_PRIMITIVE_TOPOLOGY_LINELIST_ADJ;
break;
default:
XELOGE(
"Host primitive type {} returned by the primitive processor is not "
"supported by the Direct3D 12 command processor",
uint32_t(primitive_processing_result.host_primitive_type));
assert_unhandled_case(primitive_processing_result.host_primitive_type);
return false;
}
}
SetPrimitiveTopology(primitive_topology);
// Must not call anything that may change the primitive topology from now on!
// Draw.
if (primitive_processing_result.index_buffer_type ==
PrimitiveProcessor::ProcessedIndexBufferType::kNone) {
if (memexport_used) { if (memexport_used) {
shared_memory_->UseForWriting(); shared_memory_->UseForWriting();
} else { } else {
shared_memory_->UseForReading(); shared_memory_->UseForReading();
} }
SubmitBarriers(); SubmitBarriers();
deferred_command_list_.D3DDrawInstanced(
primitive_processing_result.host_draw_vertex_count, 1, 0, 0);
} else {
D3D12_INDEX_BUFFER_VIEW index_buffer_view;
index_buffer_view.SizeInBytes =
primitive_processing_result.host_draw_vertex_count;
if (primitive_processing_result.host_index_format ==
xenos::IndexFormat::kInt16) {
index_buffer_view.SizeInBytes *= sizeof(uint16_t);
index_buffer_view.Format = DXGI_FORMAT_R16_UINT;
} else {
index_buffer_view.SizeInBytes *= sizeof(uint32_t);
index_buffer_view.Format = DXGI_FORMAT_R32_UINT;
}
ID3D12Resource* scratch_index_buffer = nullptr;
switch (primitive_processing_result.index_buffer_type) {
case PrimitiveProcessor::ProcessedIndexBufferType::kGuest: {
if (memexport_used) {
// If the shared memory is a UAV, it can't be used as an index buffer
// (UAV is a read/write state, index buffer is a read-only state).
// Need to copy the indices to a buffer in the index buffer state.
scratch_index_buffer = RequestScratchGPUBuffer(
index_buffer_view.SizeInBytes, D3D12_RESOURCE_STATE_COPY_DEST);
if (scratch_index_buffer == nullptr) {
return false;
}
shared_memory_->UseAsCopySource();
SubmitBarriers();
deferred_command_list_.D3DCopyBufferRegion(
scratch_index_buffer, 0, shared_memory_->GetBuffer(),
primitive_processing_result.guest_index_base,
index_buffer_view.SizeInBytes);
PushTransitionBarrier(scratch_index_buffer,
D3D12_RESOURCE_STATE_COPY_DEST,
D3D12_RESOURCE_STATE_INDEX_BUFFER);
index_buffer_view.BufferLocation =
scratch_index_buffer->GetGPUVirtualAddress();
} else {
index_buffer_view.BufferLocation =
shared_memory_->GetGPUAddress() +
primitive_processing_result.guest_index_base;
}
} break;
case PrimitiveProcessor::ProcessedIndexBufferType::kHostConverted:
index_buffer_view.BufferLocation =
primitive_processor_->GetConvertedIndexBufferGpuAddress(
primitive_processing_result.host_index_buffer_handle);
break;
case PrimitiveProcessor::ProcessedIndexBufferType::kHostBuiltin:
index_buffer_view.BufferLocation =
primitive_processor_->GetBuiltinIndexBufferGpuAddress(
primitive_processing_result.host_index_buffer_handle);
break;
default:
assert_unhandled_case(primitive_processing_result.index_buffer_type);
return false;
}
deferred_command_list_.D3DIASetIndexBuffer(&index_buffer_view); deferred_command_list_.D3DIASetIndexBuffer(&index_buffer_view);
deferred_command_list_.D3DDrawIndexedInstanced(index_count, 1, 0, 0, 0); if (memexport_used) {
shared_memory_->UseForWriting();
} else {
shared_memory_->UseForReading();
}
SubmitBarriers();
deferred_command_list_.D3DDrawIndexedInstanced(
primitive_processing_result.host_draw_vertex_count, 1, 0, 0, 0);
if (scratch_index_buffer != nullptr) { if (scratch_index_buffer != nullptr) {
ReleaseScratchGPUBuffer(scratch_index_buffer, ReleaseScratchGPUBuffer(scratch_index_buffer,
D3D12_RESOURCE_STATE_INDEX_BUFFER); D3D12_RESOURCE_STATE_INDEX_BUFFER);
} }
} else {
// Check if need to draw using a conversion index buffer.
uint32_t converted_index_count = 0;
D3D12_GPU_VIRTUAL_ADDRESS conversion_gpu_address =
tessellated ? 0
: primitive_converter_->GetStaticIndexBuffer(
primitive_type, index_count, converted_index_count);
if (memexport_used) {
shared_memory_->UseForWriting();
} else {
shared_memory_->UseForReading();
}
SubmitBarriers();
if (conversion_gpu_address) {
D3D12_INDEX_BUFFER_VIEW index_buffer_view;
index_buffer_view.BufferLocation = conversion_gpu_address;
index_buffer_view.SizeInBytes = converted_index_count * sizeof(uint16_t);
index_buffer_view.Format = DXGI_FORMAT_R16_UINT;
deferred_command_list_.D3DIASetIndexBuffer(&index_buffer_view);
deferred_command_list_.D3DDrawIndexedInstanced(converted_index_count, 1,
0, 0, 0);
} else {
deferred_command_list_.D3DDrawInstanced(index_count, 1, 0, 0);
}
} }
if (memexport_used) { if (memexport_used) {
@ -2526,9 +2494,9 @@ void D3D12CommandProcessor::CheckSubmissionFence(uint64_t await_submission) {
shared_memory_->CompletedSubmissionUpdated(); shared_memory_->CompletedSubmissionUpdated();
render_target_cache_->CompletedSubmissionUpdated(); primitive_processor_->CompletedSubmissionUpdated();
primitive_converter_->CompletedSubmissionUpdated(); render_target_cache_->CompletedSubmissionUpdated();
} }
void D3D12CommandProcessor::BeginSubmission(bool is_guest_command) { void D3D12CommandProcessor::BeginSubmission(bool is_guest_command) {
@ -2593,11 +2561,11 @@ void D3D12CommandProcessor::BeginSubmission(bool is_guest_command) {
} }
primitive_topology_ = D3D_PRIMITIVE_TOPOLOGY_UNDEFINED; primitive_topology_ = D3D_PRIMITIVE_TOPOLOGY_UNDEFINED;
primitive_processor_->BeginSubmission();
render_target_cache_->BeginSubmission(); render_target_cache_->BeginSubmission();
texture_cache_->BeginSubmission(); texture_cache_->BeginSubmission();
primitive_converter_->BeginSubmission();
} }
if (is_opening_frame) { if (is_opening_frame) {
@ -2645,9 +2613,9 @@ void D3D12CommandProcessor::BeginSubmission(bool is_guest_command) {
} }
} }
texture_cache_->BeginFrame(); primitive_processor_->BeginFrame();
primitive_converter_->BeginFrame(); texture_cache_->BeginFrame();
} }
} }
@ -2676,6 +2644,8 @@ bool D3D12CommandProcessor::EndSubmission(bool is_swap) {
if (is_closing_frame) { if (is_closing_frame) {
texture_cache_->EndFrame(); texture_cache_->EndFrame();
primitive_processor_->EndFrame();
} }
if (submission_open_) { if (submission_open_) {
@ -2762,8 +2732,6 @@ bool D3D12CommandProcessor::EndSubmission(bool is_swap) {
} }
constant_buffer_pool_->ClearCache(); constant_buffer_pool_->ClearCache();
primitive_converter_->ClearCache();
pipeline_cache_->ClearCache(); pipeline_cache_->ClearCache();
render_target_cache_->ClearCache(); render_target_cache_->ClearCache();
@ -2775,6 +2743,8 @@ bool D3D12CommandProcessor::EndSubmission(bool is_swap) {
} }
root_signatures_bindful_.clear(); root_signatures_bindful_.clear();
primitive_processor_->ClearCache();
shared_memory_->ClearCache(); shared_memory_->ClearCache();
} }
} }
@ -2897,7 +2867,9 @@ void D3D12CommandProcessor::UpdateSystemConstantValues(
auto rb_surface_info = regs.Get<reg::RB_SURFACE_INFO>(); auto rb_surface_info = regs.Get<reg::RB_SURFACE_INFO>();
auto sq_context_misc = regs.Get<reg::SQ_CONTEXT_MISC>(); auto sq_context_misc = regs.Get<reg::SQ_CONTEXT_MISC>();
auto sq_program_cntl = regs.Get<reg::SQ_PROGRAM_CNTL>(); auto sq_program_cntl = regs.Get<reg::SQ_PROGRAM_CNTL>();
int32_t vgt_indx_offset = int32_t(regs[XE_GPU_REG_VGT_INDX_OFFSET].u32); uint32_t vgt_indx_offset = regs.Get<reg::VGT_INDX_OFFSET>().indx_offset;
uint32_t vgt_max_vtx_indx = regs.Get<reg::VGT_MAX_VTX_INDX>().max_indx;
uint32_t vgt_min_vtx_indx = regs.Get<reg::VGT_MIN_VTX_INDX>().min_indx;
bool edram_rov_used = render_target_cache_->GetPath() == bool edram_rov_used = render_target_cache_->GetPath() ==
RenderTargetCache::Path::kPixelShaderInterlock; RenderTargetCache::Path::kPixelShaderInterlock;
@ -3050,8 +3022,14 @@ void D3D12CommandProcessor::UpdateSystemConstantValues(
system_constants_.vertex_index_endian = index_endian; system_constants_.vertex_index_endian = index_endian;
// Vertex index offset. // Vertex index offset.
dirty |= system_constants_.vertex_base_index != vgt_indx_offset; dirty |= system_constants_.vertex_index_offset != vgt_indx_offset;
system_constants_.vertex_base_index = vgt_indx_offset; system_constants_.vertex_index_offset = vgt_indx_offset;
// Vertex index range.
dirty |= system_constants_.vertex_index_min != vgt_min_vtx_indx;
dirty |= system_constants_.vertex_index_max != vgt_max_vtx_indx;
system_constants_.vertex_index_min = vgt_min_vtx_indx;
system_constants_.vertex_index_max = vgt_max_vtx_indx;
// User clip planes (UCP_ENA_#), when not CLIP_DISABLE. // User clip planes (UCP_ENA_#), when not CLIP_DISABLE.
if (!pa_cl_clip_cntl.clip_disable) { if (!pa_cl_clip_cntl.clip_disable) {
@ -3082,14 +3060,14 @@ void D3D12CommandProcessor::UpdateSystemConstantValues(
float point_size_y = float(pa_su_point_size.height) * 0.125f; float point_size_y = float(pa_su_point_size.height) * 0.125f;
float point_size_min = float(pa_su_point_minmax.min_size) * 0.125f; float point_size_min = float(pa_su_point_minmax.min_size) * 0.125f;
float point_size_max = float(pa_su_point_minmax.max_size) * 0.125f; float point_size_max = float(pa_su_point_minmax.max_size) * 0.125f;
dirty |= system_constants_.point_size[0] != point_size_x; dirty |= system_constants_.point_size_x != point_size_x;
dirty |= system_constants_.point_size[1] != point_size_y; dirty |= system_constants_.point_size_y != point_size_y;
dirty |= system_constants_.point_size_min_max[0] != point_size_min; dirty |= system_constants_.point_size_min != point_size_min;
dirty |= system_constants_.point_size_min_max[1] != point_size_max; dirty |= system_constants_.point_size_max != point_size_max;
system_constants_.point_size[0] = point_size_x; system_constants_.point_size_x = point_size_x;
system_constants_.point_size[1] = point_size_y; system_constants_.point_size_y = point_size_y;
system_constants_.point_size_min_max[0] = point_size_min; system_constants_.point_size_min = point_size_min;
system_constants_.point_size_min_max[1] = point_size_max; system_constants_.point_size_max = point_size_max;
float point_screen_to_ndc_x = float point_screen_to_ndc_x =
(/* 0.5f * 2.0f * */ float(resolution_scale)) / (/* 0.5f * 2.0f * */ float(resolution_scale)) /
std::max(viewport_info.xy_extent[0], uint32_t(1)); std::max(viewport_info.xy_extent[0], uint32_t(1));
@ -3236,12 +3214,6 @@ void D3D12CommandProcessor::UpdateSystemConstantValues(
dirty |= system_constants_.edram_depth_base_dwords != depth_base_dwords; dirty |= system_constants_.edram_depth_base_dwords != depth_base_dwords;
system_constants_.edram_depth_base_dwords = depth_base_dwords; system_constants_.edram_depth_base_dwords = depth_base_dwords;
float depth_range_scale = viewport_info.z_max - viewport_info.z_min;
dirty |= system_constants_.edram_depth_range_scale != depth_range_scale;
system_constants_.edram_depth_range_scale = depth_range_scale;
dirty |= system_constants_.edram_depth_range_offset != viewport_info.z_min;
system_constants_.edram_depth_range_offset = viewport_info.z_min;
// For non-polygons, front polygon offset is used, and it's enabled if // For non-polygons, front polygon offset is used, and it's enabled if
// POLY_OFFSET_PARA_ENABLED is set, for polygons, separate front and back // POLY_OFFSET_PARA_ENABLED is set, for polygons, separate front and back
// are used. // are used.
@ -3270,10 +3242,10 @@ void D3D12CommandProcessor::UpdateSystemConstantValues(
poly_offset_back_offset = poly_offset_front_offset; poly_offset_back_offset = poly_offset_front_offset;
} }
} }
// "slope computed in subpixels (1/12 or 1/16)" - R5xx Acceleration. Also: float poly_offset_scale_factor =
// https://github.com/mesa3d/mesa/blob/54ad9b444c8e73da498211870e785239ad3ff1aa/src/gallium/drivers/radeonsi/si_state.c#L943 xenos::kPolygonOffsetScaleSubpixelUnit * resolution_scale;
poly_offset_front_scale *= (1.0f / 16.0f) * resolution_scale; poly_offset_front_scale *= poly_offset_scale_factor;
poly_offset_back_scale *= (1.0f / 16.0f) * resolution_scale; poly_offset_back_scale *= poly_offset_scale_factor;
dirty |= system_constants_.edram_poly_offset_front_scale != dirty |= system_constants_.edram_poly_offset_front_scale !=
poly_offset_front_scale; poly_offset_front_scale;
system_constants_.edram_poly_offset_front_scale = poly_offset_front_scale; system_constants_.edram_poly_offset_front_scale = poly_offset_front_scale;

View File

@ -20,11 +20,11 @@
#include "xenia/base/assert.h" #include "xenia/base/assert.h"
#include "xenia/gpu/command_processor.h" #include "xenia/gpu/command_processor.h"
#include "xenia/gpu/d3d12/d3d12_graphics_system.h" #include "xenia/gpu/d3d12/d3d12_graphics_system.h"
#include "xenia/gpu/d3d12/d3d12_primitive_processor.h"
#include "xenia/gpu/d3d12/d3d12_render_target_cache.h" #include "xenia/gpu/d3d12/d3d12_render_target_cache.h"
#include "xenia/gpu/d3d12/d3d12_shared_memory.h" #include "xenia/gpu/d3d12/d3d12_shared_memory.h"
#include "xenia/gpu/d3d12/deferred_command_list.h" #include "xenia/gpu/d3d12/deferred_command_list.h"
#include "xenia/gpu/d3d12/pipeline_cache.h" #include "xenia/gpu/d3d12/pipeline_cache.h"
#include "xenia/gpu/d3d12/primitive_converter.h"
#include "xenia/gpu/d3d12/texture_cache.h" #include "xenia/gpu/d3d12/texture_cache.h"
#include "xenia/gpu/draw_util.h" #include "xenia/gpu/draw_util.h"
#include "xenia/gpu/dxbc_shader.h" #include "xenia/gpu/dxbc_shader.h"
@ -490,12 +490,12 @@ class D3D12CommandProcessor : public CommandProcessor {
std::unique_ptr<D3D12SharedMemory> shared_memory_; std::unique_ptr<D3D12SharedMemory> shared_memory_;
std::unique_ptr<D3D12PrimitiveProcessor> primitive_processor_;
std::unique_ptr<PipelineCache> pipeline_cache_; std::unique_ptr<PipelineCache> pipeline_cache_;
std::unique_ptr<TextureCache> texture_cache_; std::unique_ptr<TextureCache> texture_cache_;
std::unique_ptr<PrimitiveConverter> primitive_converter_;
// Mip 0 contains the normal gamma ramp (256 entries), mip 1 contains the PWL // Mip 0 contains the normal gamma ramp (256 entries), mip 1 contains the PWL
// ramp (128 entries). DXGI_FORMAT_R10G10B10A2_UNORM 1D. // ramp (128 entries). DXGI_FORMAT_R10G10B10A2_UNORM 1D.
ID3D12Resource* gamma_ramp_texture_ = nullptr; ID3D12Resource* gamma_ramp_texture_ = nullptr;

View File

@ -9,6 +9,8 @@
#include "xenia/gpu/d3d12/d3d12_graphics_system.h" #include "xenia/gpu/d3d12/d3d12_graphics_system.h"
#include <algorithm>
#include "xenia/base/logging.h" #include "xenia/base/logging.h"
#include "xenia/base/math.h" #include "xenia/base/math.h"
#include "xenia/gpu/d3d12/d3d12_command_processor.h" #include "xenia/gpu/d3d12/d3d12_command_processor.h"
@ -20,10 +22,12 @@ namespace xe {
namespace gpu { namespace gpu {
namespace d3d12 { namespace d3d12 {
// Generated with `xb buildhlsl`. // Generated with `xb buildshaders`.
#include "xenia/gpu/d3d12/shaders/dxbc/fullscreen_tc_vs.h" namespace shaders {
#include "xenia/gpu/d3d12/shaders/dxbc/stretch_gamma_ps.h" #include "xenia/gpu/shaders/bytecode/d3d12_5_1/fullscreen_tc_vs.h"
#include "xenia/gpu/d3d12/shaders/dxbc/stretch_ps.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() {}
@ -138,10 +142,10 @@ X_STATUS D3D12GraphicsSystem::Setup(cpu::Processor* processor,
// Create the stretch pipelines. // Create the stretch pipelines.
D3D12_GRAPHICS_PIPELINE_STATE_DESC stretch_pipeline_desc = {}; D3D12_GRAPHICS_PIPELINE_STATE_DESC stretch_pipeline_desc = {};
stretch_pipeline_desc.pRootSignature = stretch_root_signature_; stretch_pipeline_desc.pRootSignature = stretch_root_signature_;
stretch_pipeline_desc.VS.pShaderBytecode = fullscreen_tc_vs; stretch_pipeline_desc.VS.pShaderBytecode = shaders::fullscreen_tc_vs;
stretch_pipeline_desc.VS.BytecodeLength = sizeof(fullscreen_tc_vs); stretch_pipeline_desc.VS.BytecodeLength = sizeof(shaders::fullscreen_tc_vs);
stretch_pipeline_desc.PS.pShaderBytecode = stretch_ps; stretch_pipeline_desc.PS.pShaderBytecode = shaders::stretch_ps;
stretch_pipeline_desc.PS.BytecodeLength = sizeof(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. // The shader will set alpha to 1, don't use output-merger to preserve it.
stretch_pipeline_desc.BlendState.RenderTarget[0].RenderTargetWriteMask = stretch_pipeline_desc.BlendState.RenderTarget[0].RenderTargetWriteMask =
D3D12_COLOR_WRITE_ENABLE_ALL; D3D12_COLOR_WRITE_ENABLE_ALL;
@ -165,8 +169,8 @@ X_STATUS D3D12GraphicsSystem::Setup(cpu::Processor* processor,
return X_STATUS_UNSUCCESSFUL; return X_STATUS_UNSUCCESSFUL;
} }
stretch_pipeline_desc.pRootSignature = stretch_gamma_root_signature_; stretch_pipeline_desc.pRootSignature = stretch_gamma_root_signature_;
stretch_pipeline_desc.PS.pShaderBytecode = stretch_gamma_ps; stretch_pipeline_desc.PS.pShaderBytecode = shaders::stretch_gamma_ps;
stretch_pipeline_desc.PS.BytecodeLength = sizeof(stretch_gamma_ps); stretch_pipeline_desc.PS.BytecodeLength = sizeof(shaders::stretch_gamma_ps);
if (FAILED(device->CreateGraphicsPipelineState( if (FAILED(device->CreateGraphicsPipelineState(
&stretch_pipeline_desc, IID_PPV_ARGS(&stretch_gamma_pipeline_)))) { &stretch_pipeline_desc, IID_PPV_ARGS(&stretch_gamma_pipeline_)))) {
XELOGE( XELOGE(

View File

@ -0,0 +1,174 @@
/**
******************************************************************************
* 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. *
******************************************************************************
*/
#include "xenia/gpu/d3d12/d3d12_primitive_processor.h"
#include <algorithm>
#include <cstdint>
#include <memory>
#include <utility>
#include "xenia/base/assert.h"
#include "xenia/base/logging.h"
#include "xenia/gpu/d3d12/d3d12_command_processor.h"
#include "xenia/gpu/d3d12/deferred_command_list.h"
#include "xenia/ui/d3d12/d3d12_provider.h"
#include "xenia/ui/d3d12/d3d12_util.h"
namespace xe {
namespace gpu {
namespace d3d12 {
D3D12PrimitiveProcessor::~D3D12PrimitiveProcessor() { Shutdown(true); }
bool D3D12PrimitiveProcessor::Initialize() {
if (!InitializeCommon(true, false, false, true)) {
Shutdown();
return false;
}
frame_index_buffer_pool_ = std::make_unique<ui::d3d12::D3D12UploadBufferPool>(
command_processor_.GetD3D12Context().GetD3D12Provider(),
std::max(size_t(kMinRequiredConvertedIndexBufferSize),
ui::GraphicsUploadBufferPool::kDefaultPageSize));
return true;
}
void D3D12PrimitiveProcessor::Shutdown(bool from_destructor) {
frame_index_buffers_.clear();
frame_index_buffer_pool_.reset();
builtin_index_buffer_upload_.Reset();
builtin_index_buffer_gpu_address_ = 0;
builtin_index_buffer_.Reset();
if (!from_destructor) {
ShutdownCommon();
}
}
void D3D12PrimitiveProcessor::CompletedSubmissionUpdated() {
if (builtin_index_buffer_upload_ &&
command_processor_.GetCompletedSubmission() >=
builtin_index_buffer_upload_submission_) {
builtin_index_buffer_upload_.Reset();
}
}
void D3D12PrimitiveProcessor::BeginSubmission() {
if (builtin_index_buffer_upload_ &&
builtin_index_buffer_upload_submission_ == UINT64_MAX) {
// No need to submit deferred barriers - builtin_index_buffer_ has never
// been used yet, so it's in the initial state, and
// builtin_index_buffer_upload_ is in an upload heap, so it's GENERIC_READ.
command_processor_.GetDeferredCommandList().D3DCopyResource(
builtin_index_buffer_.Get(), builtin_index_buffer_upload_.Get());
command_processor_.PushTransitionBarrier(builtin_index_buffer_.Get(),
D3D12_RESOURCE_STATE_COPY_DEST,
D3D12_RESOURCE_STATE_INDEX_BUFFER);
builtin_index_buffer_upload_submission_ =
command_processor_.GetCurrentSubmission();
}
}
void D3D12PrimitiveProcessor::BeginFrame() {
frame_index_buffer_pool_->Reclaim(command_processor_.GetCompletedFrame());
}
void D3D12PrimitiveProcessor::EndFrame() {
ClearPerFrameCache();
frame_index_buffers_.clear();
}
bool D3D12PrimitiveProcessor::InitializeBuiltin16BitIndexBuffer(
uint32_t index_count, std::function<void(uint16_t*)> fill_callback) {
assert_not_zero(index_count);
assert_null(builtin_index_buffer_);
assert_null(builtin_index_buffer_upload_);
const ui::d3d12::D3D12Provider& provider =
command_processor_.GetD3D12Context().GetD3D12Provider();
ID3D12Device* device = provider.GetDevice();
D3D12_RESOURCE_DESC resource_desc;
ui::d3d12::util::FillBufferResourceDesc(
resource_desc, UINT64(sizeof(uint16_t) * index_count),
D3D12_RESOURCE_FLAG_NONE);
Microsoft::WRL::ComPtr<ID3D12Resource> draw_resource;
if (FAILED(device->CreateCommittedResource(
&ui::d3d12::util::kHeapPropertiesDefault,
provider.GetHeapFlagCreateNotZeroed(), &resource_desc,
D3D12_RESOURCE_STATE_COPY_DEST, nullptr,
IID_PPV_ARGS(&draw_resource)))) {
XELOGE(
"D3D12 primitive processor: Failed to create the built-in index "
"buffer GPU resource with {} 16-bit indices",
index_count);
return false;
}
Microsoft::WRL::ComPtr<ID3D12Resource> upload_resource;
if (FAILED(device->CreateCommittedResource(
&ui::d3d12::util::kHeapPropertiesUpload,
provider.GetHeapFlagCreateNotZeroed(), &resource_desc,
D3D12_RESOURCE_STATE_GENERIC_READ, nullptr,
IID_PPV_ARGS(&upload_resource)))) {
XELOGE(
"D3D12 primitive processor: Failed to create the built-in index "
"buffer upload resource with {} 16-bit indices",
index_count);
return false;
}
D3D12_RANGE upload_read_range = {};
void* mapping;
if (FAILED(upload_resource->Map(0, &upload_read_range, &mapping))) {
XELOGE(
"D3D12 primitive processor: Failed to map the built-in index buffer "
"upload resource with {} 16-bit indices",
index_count);
return false;
}
fill_callback(reinterpret_cast<uint16_t*>(mapping));
upload_resource->Unmap(0, nullptr);
// Successfully created the buffer and wrote the data to upload.
builtin_index_buffer_ = std::move(draw_resource);
builtin_index_buffer_gpu_address_ =
builtin_index_buffer_->GetGPUVirtualAddress();
builtin_index_buffer_upload_ = std::move(upload_resource);
// Schedule uploading in the first submission.
builtin_index_buffer_upload_submission_ = UINT64_MAX;
return true;
}
void* D3D12PrimitiveProcessor::RequestHostConvertedIndexBufferForCurrentFrame(
xenos::IndexFormat format, uint32_t index_count, bool coalign_for_simd,
uint32_t coalignment_original_address, size_t& backend_handle_out) {
size_t index_size = format == xenos::IndexFormat::kInt16 ? sizeof(uint16_t)
: sizeof(uint32_t);
D3D12_GPU_VIRTUAL_ADDRESS gpu_address;
uint8_t* mapping = frame_index_buffer_pool_->Request(
command_processor_.GetCurrentFrame(),
index_size * index_count +
(coalign_for_simd ? XE_GPU_PRIMITIVE_PROCESSOR_SIMD_SIZE : 0),
index_size, nullptr, nullptr, &gpu_address);
if (!mapping) {
return false;
}
if (coalign_for_simd) {
ptrdiff_t coalignment_offset =
GetSimdCoalignmentOffset(mapping, coalignment_original_address);
mapping += coalignment_offset;
gpu_address = D3D12_GPU_VIRTUAL_ADDRESS(gpu_address + coalignment_offset);
}
backend_handle_out = frame_index_buffers_.size();
frame_index_buffers_.push_back(gpu_address);
return mapping;
}
} // namespace d3d12
} // namespace gpu
} // namespace xe

View File

@ -0,0 +1,90 @@
/**
******************************************************************************
* 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_GPU_D3D12_D3D12_PRIMITIVE_PROCESSOR_H_
#define XENIA_GPU_D3D12_D3D12_PRIMITIVE_PROCESSOR_H_
#include <cstdint>
#include <deque>
#include <memory>
#include "xenia/base/assert.h"
#include "xenia/gpu/primitive_processor.h"
#include "xenia/ui/d3d12/d3d12_api.h"
#include "xenia/ui/d3d12/d3d12_upload_buffer_pool.h"
namespace xe {
namespace gpu {
namespace d3d12 {
class D3D12CommandProcessor;
class D3D12PrimitiveProcessor final : public PrimitiveProcessor {
public:
D3D12PrimitiveProcessor(const RegisterFile& register_file, Memory& memory,
TraceWriter& trace_writer,
SharedMemory& shared_memory,
D3D12CommandProcessor& command_processor)
: PrimitiveProcessor(register_file, memory, trace_writer, shared_memory),
command_processor_(command_processor) {}
~D3D12PrimitiveProcessor();
bool Initialize();
void Shutdown(bool from_destructor = false);
void ClearCache() { frame_index_buffer_pool_->ClearCache(); }
void CompletedSubmissionUpdated();
void BeginSubmission();
void BeginFrame();
void EndFrame();
D3D12_GPU_VIRTUAL_ADDRESS GetBuiltinIndexBufferGpuAddress(
size_t handle) const {
assert_not_null(builtin_index_buffer_);
return D3D12_GPU_VIRTUAL_ADDRESS(builtin_index_buffer_gpu_address_ +
GetBuiltinIndexBufferOffsetBytes(handle));
}
D3D12_GPU_VIRTUAL_ADDRESS GetConvertedIndexBufferGpuAddress(
size_t handle) const {
return frame_index_buffers_[handle];
}
protected:
bool InitializeBuiltin16BitIndexBuffer(
uint32_t index_count,
std::function<void(uint16_t*)> fill_callback) override;
void* RequestHostConvertedIndexBufferForCurrentFrame(
xenos::IndexFormat format, uint32_t index_count, bool coalign_for_simd,
uint32_t coalignment_original_address,
size_t& backend_handle_out) override;
private:
D3D12CommandProcessor& command_processor_;
Microsoft::WRL::ComPtr<ID3D12Resource> builtin_index_buffer_;
D3D12_GPU_VIRTUAL_ADDRESS builtin_index_buffer_gpu_address_ = 0;
// Temporary buffer copied in the beginning of the first submission for
// uploading to builtin_index_buffer_, destroyed when the submission when it
// was uploaded is completed.
Microsoft::WRL::ComPtr<ID3D12Resource> builtin_index_buffer_upload_;
// UINT64_MAX means not uploaded yet and needs uploading in the first
// submission (if the upload buffer exists at all).
uint64_t builtin_index_buffer_upload_submission_ = UINT64_MAX;
std::unique_ptr<ui::d3d12::D3D12UploadBufferPool> frame_index_buffer_pool_;
// Indexed by the backend handles.
std::deque<D3D12_GPU_VIRTUAL_ADDRESS> frame_index_buffers_;
};
} // namespace d3d12
} // namespace gpu
} // namespace xe
#endif // XENIA_GPU_D3D12_D3D12_PRIMITIVE_PROCESSOR_H_

View File

@ -74,91 +74,108 @@ namespace xe {
namespace gpu { namespace gpu {
namespace d3d12 { namespace d3d12 {
// Generated with `xb buildhlsl`. // Generated with `xb buildshaders`.
#include "xenia/gpu/d3d12/shaders/dxbc/clear_uint2_ps.h" namespace shaders {
#include "xenia/gpu/d3d12/shaders/dxbc/fullscreen_vs.h" #include "xenia/gpu/shaders/bytecode/d3d12_5_1/clear_uint2_ps.h"
#include "xenia/gpu/d3d12/shaders/dxbc/host_depth_store_1xmsaa_cs.h" #include "xenia/gpu/shaders/bytecode/d3d12_5_1/fullscreen_vs.h"
#include "xenia/gpu/d3d12/shaders/dxbc/host_depth_store_2xmsaa_cs.h" #include "xenia/gpu/shaders/bytecode/d3d12_5_1/host_depth_store_1xmsaa_cs.h"
#include "xenia/gpu/d3d12/shaders/dxbc/host_depth_store_4xmsaa_cs.h" #include "xenia/gpu/shaders/bytecode/d3d12_5_1/host_depth_store_2xmsaa_cs.h"
#include "xenia/gpu/d3d12/shaders/dxbc/passthrough_position_xy_vs.h" #include "xenia/gpu/shaders/bytecode/d3d12_5_1/host_depth_store_4xmsaa_cs.h"
#include "xenia/gpu/d3d12/shaders/dxbc/resolve_clear_32bpp_2xres_cs.h" #include "xenia/gpu/shaders/bytecode/d3d12_5_1/passthrough_position_xy_vs.h"
#include "xenia/gpu/d3d12/shaders/dxbc/resolve_clear_32bpp_3xres_cs.h" #include "xenia/gpu/shaders/bytecode/d3d12_5_1/resolve_clear_32bpp_2xres_cs.h"
#include "xenia/gpu/d3d12/shaders/dxbc/resolve_clear_32bpp_cs.h" #include "xenia/gpu/shaders/bytecode/d3d12_5_1/resolve_clear_32bpp_3xres_cs.h"
#include "xenia/gpu/d3d12/shaders/dxbc/resolve_clear_64bpp_2xres_cs.h" #include "xenia/gpu/shaders/bytecode/d3d12_5_1/resolve_clear_32bpp_cs.h"
#include "xenia/gpu/d3d12/shaders/dxbc/resolve_clear_64bpp_3xres_cs.h" #include "xenia/gpu/shaders/bytecode/d3d12_5_1/resolve_clear_64bpp_2xres_cs.h"
#include "xenia/gpu/d3d12/shaders/dxbc/resolve_clear_64bpp_cs.h" #include "xenia/gpu/shaders/bytecode/d3d12_5_1/resolve_clear_64bpp_3xres_cs.h"
#include "xenia/gpu/d3d12/shaders/dxbc/resolve_fast_32bpp_1x2xmsaa_cs.h" #include "xenia/gpu/shaders/bytecode/d3d12_5_1/resolve_clear_64bpp_cs.h"
#include "xenia/gpu/d3d12/shaders/dxbc/resolve_fast_32bpp_2xres_cs.h" #include "xenia/gpu/shaders/bytecode/d3d12_5_1/resolve_fast_32bpp_1x2xmsaa_cs.h"
#include "xenia/gpu/d3d12/shaders/dxbc/resolve_fast_32bpp_3xres_1x2xmsaa_cs.h" #include "xenia/gpu/shaders/bytecode/d3d12_5_1/resolve_fast_32bpp_2xres_cs.h"
#include "xenia/gpu/d3d12/shaders/dxbc/resolve_fast_32bpp_3xres_4xmsaa_cs.h" #include "xenia/gpu/shaders/bytecode/d3d12_5_1/resolve_fast_32bpp_3xres_1x2xmsaa_cs.h"
#include "xenia/gpu/d3d12/shaders/dxbc/resolve_fast_32bpp_4xmsaa_cs.h" #include "xenia/gpu/shaders/bytecode/d3d12_5_1/resolve_fast_32bpp_3xres_4xmsaa_cs.h"
#include "xenia/gpu/d3d12/shaders/dxbc/resolve_fast_64bpp_1x2xmsaa_cs.h" #include "xenia/gpu/shaders/bytecode/d3d12_5_1/resolve_fast_32bpp_4xmsaa_cs.h"
#include "xenia/gpu/d3d12/shaders/dxbc/resolve_fast_64bpp_2xres_cs.h" #include "xenia/gpu/shaders/bytecode/d3d12_5_1/resolve_fast_64bpp_1x2xmsaa_cs.h"
#include "xenia/gpu/d3d12/shaders/dxbc/resolve_fast_64bpp_3xres_cs.h" #include "xenia/gpu/shaders/bytecode/d3d12_5_1/resolve_fast_64bpp_2xres_cs.h"
#include "xenia/gpu/d3d12/shaders/dxbc/resolve_fast_64bpp_4xmsaa_cs.h" #include "xenia/gpu/shaders/bytecode/d3d12_5_1/resolve_fast_64bpp_3xres_cs.h"
#include "xenia/gpu/d3d12/shaders/dxbc/resolve_full_128bpp_2xres_cs.h" #include "xenia/gpu/shaders/bytecode/d3d12_5_1/resolve_fast_64bpp_4xmsaa_cs.h"
#include "xenia/gpu/d3d12/shaders/dxbc/resolve_full_128bpp_cs.h" #include "xenia/gpu/shaders/bytecode/d3d12_5_1/resolve_full_128bpp_2xres_cs.h"
#include "xenia/gpu/d3d12/shaders/dxbc/resolve_full_128bpp_from_32bpp_3xres_cs.h" #include "xenia/gpu/shaders/bytecode/d3d12_5_1/resolve_full_128bpp_cs.h"
#include "xenia/gpu/d3d12/shaders/dxbc/resolve_full_128bpp_from_64bpp_3xres_cs.h" #include "xenia/gpu/shaders/bytecode/d3d12_5_1/resolve_full_128bpp_from_32bpp_3xres_cs.h"
#include "xenia/gpu/d3d12/shaders/dxbc/resolve_full_16bpp_2xres_cs.h" #include "xenia/gpu/shaders/bytecode/d3d12_5_1/resolve_full_128bpp_from_64bpp_3xres_cs.h"
#include "xenia/gpu/d3d12/shaders/dxbc/resolve_full_16bpp_cs.h" #include "xenia/gpu/shaders/bytecode/d3d12_5_1/resolve_full_16bpp_2xres_cs.h"
#include "xenia/gpu/d3d12/shaders/dxbc/resolve_full_16bpp_from_32bpp_3xres_cs.h" #include "xenia/gpu/shaders/bytecode/d3d12_5_1/resolve_full_16bpp_cs.h"
#include "xenia/gpu/d3d12/shaders/dxbc/resolve_full_16bpp_from_64bpp_3xres_cs.h" #include "xenia/gpu/shaders/bytecode/d3d12_5_1/resolve_full_16bpp_from_32bpp_3xres_cs.h"
#include "xenia/gpu/d3d12/shaders/dxbc/resolve_full_32bpp_2xres_cs.h" #include "xenia/gpu/shaders/bytecode/d3d12_5_1/resolve_full_16bpp_from_64bpp_3xres_cs.h"
#include "xenia/gpu/d3d12/shaders/dxbc/resolve_full_32bpp_cs.h" #include "xenia/gpu/shaders/bytecode/d3d12_5_1/resolve_full_32bpp_2xres_cs.h"
#include "xenia/gpu/d3d12/shaders/dxbc/resolve_full_32bpp_from_32bpp_3xres_cs.h" #include "xenia/gpu/shaders/bytecode/d3d12_5_1/resolve_full_32bpp_cs.h"
#include "xenia/gpu/d3d12/shaders/dxbc/resolve_full_32bpp_from_64bpp_3xres_cs.h" #include "xenia/gpu/shaders/bytecode/d3d12_5_1/resolve_full_32bpp_from_32bpp_3xres_cs.h"
#include "xenia/gpu/d3d12/shaders/dxbc/resolve_full_64bpp_2xres_cs.h" #include "xenia/gpu/shaders/bytecode/d3d12_5_1/resolve_full_32bpp_from_64bpp_3xres_cs.h"
#include "xenia/gpu/d3d12/shaders/dxbc/resolve_full_64bpp_cs.h" #include "xenia/gpu/shaders/bytecode/d3d12_5_1/resolve_full_64bpp_2xres_cs.h"
#include "xenia/gpu/d3d12/shaders/dxbc/resolve_full_64bpp_from_32bpp_3xres_cs.h" #include "xenia/gpu/shaders/bytecode/d3d12_5_1/resolve_full_64bpp_cs.h"
#include "xenia/gpu/d3d12/shaders/dxbc/resolve_full_64bpp_from_64bpp_3xres_cs.h" #include "xenia/gpu/shaders/bytecode/d3d12_5_1/resolve_full_64bpp_from_32bpp_3xres_cs.h"
#include "xenia/gpu/d3d12/shaders/dxbc/resolve_full_8bpp_2xres_cs.h" #include "xenia/gpu/shaders/bytecode/d3d12_5_1/resolve_full_64bpp_from_64bpp_3xres_cs.h"
#include "xenia/gpu/d3d12/shaders/dxbc/resolve_full_8bpp_3xres_cs.h" #include "xenia/gpu/shaders/bytecode/d3d12_5_1/resolve_full_8bpp_2xres_cs.h"
#include "xenia/gpu/d3d12/shaders/dxbc/resolve_full_8bpp_cs.h" #include "xenia/gpu/shaders/bytecode/d3d12_5_1/resolve_full_8bpp_3xres_cs.h"
#include "xenia/gpu/shaders/bytecode/d3d12_5_1/resolve_full_8bpp_cs.h"
} // namespace shaders
const std::pair<const uint8_t*, size_t> const std::pair<const void*, size_t>
D3D12RenderTargetCache::kResolveCopyShaders[size_t( D3D12RenderTargetCache::kResolveCopyShaders[size_t(
draw_util::ResolveCopyShaderIndex::kCount)] = { draw_util::ResolveCopyShaderIndex::kCount)] = {
{resolve_fast_32bpp_1x2xmsaa_cs, {shaders::resolve_fast_32bpp_1x2xmsaa_cs,
sizeof(resolve_fast_32bpp_1x2xmsaa_cs)}, sizeof(shaders::resolve_fast_32bpp_1x2xmsaa_cs)},
{resolve_fast_32bpp_4xmsaa_cs, sizeof(resolve_fast_32bpp_4xmsaa_cs)}, {shaders::resolve_fast_32bpp_4xmsaa_cs,
{resolve_fast_32bpp_2xres_cs, sizeof(resolve_fast_32bpp_2xres_cs)}, sizeof(shaders::resolve_fast_32bpp_4xmsaa_cs)},
{resolve_fast_32bpp_3xres_1x2xmsaa_cs, {shaders::resolve_fast_32bpp_2xres_cs,
sizeof(resolve_fast_32bpp_3xres_1x2xmsaa_cs)}, sizeof(shaders::resolve_fast_32bpp_2xres_cs)},
{resolve_fast_32bpp_3xres_4xmsaa_cs, {shaders::resolve_fast_32bpp_3xres_1x2xmsaa_cs,
sizeof(resolve_fast_32bpp_3xres_4xmsaa_cs)}, sizeof(shaders::resolve_fast_32bpp_3xres_1x2xmsaa_cs)},
{resolve_fast_64bpp_1x2xmsaa_cs, {shaders::resolve_fast_32bpp_3xres_4xmsaa_cs,
sizeof(resolve_fast_64bpp_1x2xmsaa_cs)}, sizeof(shaders::resolve_fast_32bpp_3xres_4xmsaa_cs)},
{resolve_fast_64bpp_4xmsaa_cs, sizeof(resolve_fast_64bpp_4xmsaa_cs)}, {shaders::resolve_fast_64bpp_1x2xmsaa_cs,
{resolve_fast_64bpp_2xres_cs, sizeof(resolve_fast_64bpp_2xres_cs)}, sizeof(shaders::resolve_fast_64bpp_1x2xmsaa_cs)},
{resolve_fast_64bpp_3xres_cs, sizeof(resolve_fast_64bpp_3xres_cs)}, {shaders::resolve_fast_64bpp_4xmsaa_cs,
{resolve_full_8bpp_cs, sizeof(resolve_full_8bpp_cs)}, sizeof(shaders::resolve_fast_64bpp_4xmsaa_cs)},
{resolve_full_8bpp_2xres_cs, sizeof(resolve_full_8bpp_2xres_cs)}, {shaders::resolve_fast_64bpp_2xres_cs,
{resolve_full_8bpp_3xres_cs, sizeof(resolve_full_8bpp_3xres_cs)}, sizeof(shaders::resolve_fast_64bpp_2xres_cs)},
{resolve_full_16bpp_cs, sizeof(resolve_full_16bpp_cs)}, {shaders::resolve_fast_64bpp_3xres_cs,
{resolve_full_16bpp_2xres_cs, sizeof(resolve_full_16bpp_2xres_cs)}, sizeof(shaders::resolve_fast_64bpp_3xres_cs)},
{resolve_full_16bpp_from_32bpp_3xres_cs, {shaders::resolve_full_8bpp_cs, sizeof(shaders::resolve_full_8bpp_cs)},
sizeof(resolve_full_16bpp_from_32bpp_3xres_cs)}, {shaders::resolve_full_8bpp_2xres_cs,
{resolve_full_16bpp_from_64bpp_3xres_cs, sizeof(shaders::resolve_full_8bpp_2xres_cs)},
sizeof(resolve_full_16bpp_from_64bpp_3xres_cs)}, {shaders::resolve_full_8bpp_3xres_cs,
{resolve_full_32bpp_cs, sizeof(resolve_full_32bpp_cs)}, sizeof(shaders::resolve_full_8bpp_3xres_cs)},
{resolve_full_32bpp_2xres_cs, sizeof(resolve_full_32bpp_2xres_cs)}, {shaders::resolve_full_16bpp_cs,
{resolve_full_32bpp_from_32bpp_3xres_cs, sizeof(shaders::resolve_full_16bpp_cs)},
sizeof(resolve_full_32bpp_from_32bpp_3xres_cs)}, {shaders::resolve_full_16bpp_2xres_cs,
{resolve_full_32bpp_from_64bpp_3xres_cs, sizeof(shaders::resolve_full_16bpp_2xres_cs)},
sizeof(resolve_full_32bpp_from_64bpp_3xres_cs)}, {shaders::resolve_full_16bpp_from_32bpp_3xres_cs,
{resolve_full_64bpp_cs, sizeof(resolve_full_64bpp_cs)}, sizeof(shaders::resolve_full_16bpp_from_32bpp_3xres_cs)},
{resolve_full_64bpp_2xres_cs, sizeof(resolve_full_64bpp_2xres_cs)}, {shaders::resolve_full_16bpp_from_64bpp_3xres_cs,
{resolve_full_64bpp_from_32bpp_3xres_cs, sizeof(shaders::resolve_full_16bpp_from_64bpp_3xres_cs)},
sizeof(resolve_full_64bpp_from_32bpp_3xres_cs)}, {shaders::resolve_full_32bpp_cs,
{resolve_full_64bpp_from_64bpp_3xres_cs, sizeof(shaders::resolve_full_32bpp_cs)},
sizeof(resolve_full_64bpp_from_64bpp_3xres_cs)}, {shaders::resolve_full_32bpp_2xres_cs,
{resolve_full_128bpp_cs, sizeof(resolve_full_128bpp_cs)}, sizeof(shaders::resolve_full_32bpp_2xres_cs)},
{resolve_full_128bpp_2xres_cs, sizeof(resolve_full_128bpp_2xres_cs)}, {shaders::resolve_full_32bpp_from_32bpp_3xres_cs,
{resolve_full_128bpp_from_32bpp_3xres_cs, sizeof(shaders::resolve_full_32bpp_from_32bpp_3xres_cs)},
sizeof(resolve_full_128bpp_from_32bpp_3xres_cs)}, {shaders::resolve_full_32bpp_from_64bpp_3xres_cs,
{resolve_full_128bpp_from_64bpp_3xres_cs, sizeof(shaders::resolve_full_32bpp_from_64bpp_3xres_cs)},
sizeof(resolve_full_128bpp_from_64bpp_3xres_cs)}, {shaders::resolve_full_64bpp_cs,
sizeof(shaders::resolve_full_64bpp_cs)},
{shaders::resolve_full_64bpp_2xres_cs,
sizeof(shaders::resolve_full_64bpp_2xres_cs)},
{shaders::resolve_full_64bpp_from_32bpp_3xres_cs,
sizeof(shaders::resolve_full_64bpp_from_32bpp_3xres_cs)},
{shaders::resolve_full_64bpp_from_64bpp_3xres_cs,
sizeof(shaders::resolve_full_64bpp_from_64bpp_3xres_cs)},
{shaders::resolve_full_128bpp_cs,
sizeof(shaders::resolve_full_128bpp_cs)},
{shaders::resolve_full_128bpp_2xres_cs,
sizeof(shaders::resolve_full_128bpp_2xres_cs)},
{shaders::resolve_full_128bpp_from_32bpp_3xres_cs,
sizeof(shaders::resolve_full_128bpp_from_32bpp_3xres_cs)},
{shaders::resolve_full_128bpp_from_64bpp_3xres_cs,
sizeof(shaders::resolve_full_128bpp_from_64bpp_3xres_cs)},
}; };
const uint32_t D3D12RenderTargetCache::kTransferUsedRootParameters[size_t( const uint32_t D3D12RenderTargetCache::kTransferUsedRootParameters[size_t(
@ -231,16 +248,22 @@ const D3D12RenderTargetCache::TransferModeInfo
const std::pair<const void*, size_t> const std::pair<const void*, size_t>
D3D12RenderTargetCache::kResolveROVClear32bppShaders[3] = { D3D12RenderTargetCache::kResolveROVClear32bppShaders[3] = {
{resolve_clear_32bpp_cs, sizeof(resolve_clear_32bpp_cs)}, {shaders::resolve_clear_32bpp_cs,
{resolve_clear_32bpp_2xres_cs, sizeof(resolve_clear_32bpp_2xres_cs)}, sizeof(shaders::resolve_clear_32bpp_cs)},
{resolve_clear_32bpp_3xres_cs, sizeof(resolve_clear_32bpp_3xres_cs)}, {shaders::resolve_clear_32bpp_2xres_cs,
sizeof(shaders::resolve_clear_32bpp_2xres_cs)},
{shaders::resolve_clear_32bpp_3xres_cs,
sizeof(shaders::resolve_clear_32bpp_3xres_cs)},
}; };
const std::pair<const void*, size_t> const std::pair<const void*, size_t>
D3D12RenderTargetCache::kResolveROVClear64bppShaders[3] = { D3D12RenderTargetCache::kResolveROVClear64bppShaders[3] = {
{resolve_clear_64bpp_cs, sizeof(resolve_clear_64bpp_cs)}, {shaders::resolve_clear_64bpp_cs,
{resolve_clear_64bpp_2xres_cs, sizeof(resolve_clear_64bpp_2xres_cs)}, sizeof(shaders::resolve_clear_64bpp_cs)},
{resolve_clear_64bpp_3xres_cs, sizeof(resolve_clear_64bpp_3xres_cs)}, {shaders::resolve_clear_64bpp_2xres_cs,
sizeof(shaders::resolve_clear_64bpp_2xres_cs)},
{shaders::resolve_clear_64bpp_3xres_cs,
sizeof(shaders::resolve_clear_64bpp_3xres_cs)},
}; };
D3D12RenderTargetCache::~D3D12RenderTargetCache() { Shutdown(true); } D3D12RenderTargetCache::~D3D12RenderTargetCache() { Shutdown(true); }
@ -459,7 +482,7 @@ bool D3D12RenderTargetCache::Initialize() {
if (resolve_copy_shader_info.resolution_scale != resolution_scale_) { if (resolve_copy_shader_info.resolution_scale != resolution_scale_) {
continue; continue;
} }
const std::pair<const uint8_t*, size_t>& resolve_copy_shader = const std::pair<const void*, size_t>& resolve_copy_shader =
kResolveCopyShaders[i]; kResolveCopyShaders[i];
ID3D12PipelineState* resolve_copy_pipeline = ID3D12PipelineState* resolve_copy_pipeline =
ui::d3d12::util::CreateComputePipeline( ui::d3d12::util::CreateComputePipeline(
@ -655,8 +678,8 @@ bool D3D12RenderTargetCache::Initialize() {
// 1 sample. // 1 sample.
host_depth_store_pipelines_[size_t(xenos::MsaaSamples::k1X)] = host_depth_store_pipelines_[size_t(xenos::MsaaSamples::k1X)] =
ui::d3d12::util::CreateComputePipeline( ui::d3d12::util::CreateComputePipeline(
device, host_depth_store_1xmsaa_cs, device, shaders::host_depth_store_1xmsaa_cs,
sizeof(host_depth_store_1xmsaa_cs), sizeof(shaders::host_depth_store_1xmsaa_cs),
host_depth_store_root_signature_); host_depth_store_root_signature_);
if (!host_depth_store_pipelines_[size_t(xenos::MsaaSamples::k1X)]) { if (!host_depth_store_pipelines_[size_t(xenos::MsaaSamples::k1X)]) {
XELOGE( XELOGE(
@ -670,8 +693,8 @@ bool D3D12RenderTargetCache::Initialize() {
// 2 samples. // 2 samples.
host_depth_store_pipelines_[size_t(xenos::MsaaSamples::k2X)] = host_depth_store_pipelines_[size_t(xenos::MsaaSamples::k2X)] =
ui::d3d12::util::CreateComputePipeline( ui::d3d12::util::CreateComputePipeline(
device, host_depth_store_2xmsaa_cs, device, shaders::host_depth_store_2xmsaa_cs,
sizeof(host_depth_store_2xmsaa_cs), sizeof(shaders::host_depth_store_2xmsaa_cs),
host_depth_store_root_signature_); host_depth_store_root_signature_);
if (!host_depth_store_pipelines_[size_t(xenos::MsaaSamples::k2X)]) { if (!host_depth_store_pipelines_[size_t(xenos::MsaaSamples::k2X)]) {
XELOGE( XELOGE(
@ -685,8 +708,8 @@ bool D3D12RenderTargetCache::Initialize() {
// 4 samples. // 4 samples.
host_depth_store_pipelines_[size_t(xenos::MsaaSamples::k4X)] = host_depth_store_pipelines_[size_t(xenos::MsaaSamples::k4X)] =
ui::d3d12::util::CreateComputePipeline( ui::d3d12::util::CreateComputePipeline(
device, host_depth_store_4xmsaa_cs, device, shaders::host_depth_store_4xmsaa_cs,
sizeof(host_depth_store_4xmsaa_cs), sizeof(shaders::host_depth_store_4xmsaa_cs),
host_depth_store_root_signature_); host_depth_store_root_signature_);
if (!host_depth_store_pipelines_[size_t(xenos::MsaaSamples::k4X)]) { if (!host_depth_store_pipelines_[size_t(xenos::MsaaSamples::k4X)]) {
XELOGE( XELOGE(
@ -1001,10 +1024,12 @@ bool D3D12RenderTargetCache::Initialize() {
D3D12_GRAPHICS_PIPELINE_STATE_DESC uint32_rtv_clear_pipeline_desc = {}; D3D12_GRAPHICS_PIPELINE_STATE_DESC uint32_rtv_clear_pipeline_desc = {};
uint32_rtv_clear_pipeline_desc.pRootSignature = uint32_rtv_clear_pipeline_desc.pRootSignature =
uint32_rtv_clear_root_signature_; uint32_rtv_clear_root_signature_;
uint32_rtv_clear_pipeline_desc.VS.pShaderBytecode = fullscreen_vs; uint32_rtv_clear_pipeline_desc.VS.pShaderBytecode = shaders::fullscreen_vs;
uint32_rtv_clear_pipeline_desc.VS.BytecodeLength = sizeof(fullscreen_vs); uint32_rtv_clear_pipeline_desc.VS.BytecodeLength =
uint32_rtv_clear_pipeline_desc.PS.pShaderBytecode = clear_uint2_ps; sizeof(shaders::fullscreen_vs);
uint32_rtv_clear_pipeline_desc.PS.BytecodeLength = sizeof(clear_uint2_ps); uint32_rtv_clear_pipeline_desc.PS.pShaderBytecode = shaders::clear_uint2_ps;
uint32_rtv_clear_pipeline_desc.PS.BytecodeLength =
sizeof(shaders::clear_uint2_ps);
uint32_rtv_clear_pipeline_desc.BlendState.RenderTarget[0] uint32_rtv_clear_pipeline_desc.BlendState.RenderTarget[0]
.RenderTargetWriteMask = D3D12_COLOR_WRITE_ENABLE_ALL; .RenderTargetWriteMask = D3D12_COLOR_WRITE_ENABLE_ALL;
uint32_rtv_clear_pipeline_desc.RasterizerState.FillMode = uint32_rtv_clear_pipeline_desc.RasterizerState.FillMode =
@ -1833,9 +1858,10 @@ DXGI_FORMAT D3D12RenderTargetCache::GetColorDrawDXGIFormat(
xenos::ColorRenderTargetFormat format) const { xenos::ColorRenderTargetFormat format) const {
switch (format) { switch (format) {
case xenos::ColorRenderTargetFormat::k_8_8_8_8: case xenos::ColorRenderTargetFormat::k_8_8_8_8:
case xenos::ColorRenderTargetFormat::k_8_8_8_8_GAMMA:
// sRGB is handled in a different way, not via the RenderTargetKey format.
return DXGI_FORMAT_R8G8B8A8_UNORM; return DXGI_FORMAT_R8G8B8A8_UNORM;
case xenos::ColorRenderTargetFormat::k_8_8_8_8_GAMMA:
return gamma_render_target_as_srgb_ ? DXGI_FORMAT_R8G8B8A8_UNORM_SRGB
: DXGI_FORMAT_R8G8B8A8_UNORM;
case xenos::ColorRenderTargetFormat::k_16_16: case xenos::ColorRenderTargetFormat::k_16_16:
return DXGI_FORMAT_R16G16_SNORM; return DXGI_FORMAT_R16G16_SNORM;
case xenos::ColorRenderTargetFormat::k_16_16_16_16: case xenos::ColorRenderTargetFormat::k_16_16_16_16:
@ -1929,20 +1955,6 @@ DXGI_FORMAT D3D12RenderTargetCache::GetDepthSRVStencilDXGIFormat(
} }
} }
xenos::ColorRenderTargetFormat
D3D12RenderTargetCache::GetHostRelevantColorFormat(
xenos::ColorRenderTargetFormat format) const {
switch (format) {
case xenos::ColorRenderTargetFormat::k_8_8_8_8_GAMMA:
// Currently handled in the shader (with incorrect blending), but even if
// handling is changed (to true sRGB), it will still be able to alias it
// with R8G8B8A8_UNORM.
return xenos::ColorRenderTargetFormat::k_8_8_8_8;
default:
return format;
}
}
RenderTargetCache::RenderTarget* D3D12RenderTargetCache::CreateRenderTarget( RenderTargetCache::RenderTarget* D3D12RenderTargetCache::CreateRenderTarget(
RenderTargetKey key) { RenderTargetKey key) {
ID3D12Device* device = ID3D12Device* device =
@ -1965,7 +1977,7 @@ RenderTargetCache::RenderTarget* D3D12RenderTargetCache::CreateRenderTarget(
assert_true(resource_desc.Format != DXGI_FORMAT_UNKNOWN); assert_true(resource_desc.Format != DXGI_FORMAT_UNKNOWN);
if (resource_desc.Format == DXGI_FORMAT_UNKNOWN) { if (resource_desc.Format == DXGI_FORMAT_UNKNOWN) {
XELOGE("D3D12RenderTargetCache: Unknown {} render target format {}", XELOGE("D3D12RenderTargetCache: Unknown {} render target format {}",
key.is_depth ? "depth" : "color", key.host_relevant_format); key.is_depth ? "depth" : "color", key.resource_format);
return nullptr; return nullptr;
} }
if (key.msaa_samples == xenos::MsaaSamples::k2X && !msaa_2x_supported()) { if (key.msaa_samples == xenos::MsaaSamples::k2X && !msaa_2x_supported()) {
@ -2119,7 +2131,7 @@ RenderTargetCache::RenderTarget* D3D12RenderTargetCache::CreateRenderTarget(
descriptor_srv.GetHandle()); descriptor_srv.GetHandle());
return new D3D12RenderTarget( return new D3D12RenderTarget(
key, *this, resource.Get(), std::move(descriptor_draw), key, resource.Get(), std::move(descriptor_draw),
std::move(descriptor_draw_srgb), std::move(descriptor_load_separate), std::move(descriptor_draw_srgb), std::move(descriptor_load_separate),
std::move(descriptor_srv), std::move(descriptor_srv_stencil), std::move(descriptor_srv), std::move(descriptor_srv_stencil),
resource_state); resource_state);
@ -2203,16 +2215,16 @@ D3D12RenderTargetCache::GetOrCreateTransferPipelines(TransferShaderKey key) {
bool dest_is_color = (mode.output == TransferOutput::kColor); bool dest_is_color = (mode.output == TransferOutput::kColor);
xenos::ColorRenderTargetFormat dest_color_format = xenos::ColorRenderTargetFormat dest_color_format =
xenos::ColorRenderTargetFormat(key.dest_host_relevant_format); xenos::ColorRenderTargetFormat(key.dest_resource_format);
xenos::DepthRenderTargetFormat dest_depth_format = xenos::DepthRenderTargetFormat dest_depth_format =
xenos::DepthRenderTargetFormat(key.dest_host_relevant_format); xenos::DepthRenderTargetFormat(key.dest_resource_format);
bool dest_is_64bpp = bool dest_is_64bpp =
dest_is_color && xenos::IsColorRenderTargetFormat64bpp(dest_color_format); dest_is_color && xenos::IsColorRenderTargetFormat64bpp(dest_color_format);
xenos::ColorRenderTargetFormat source_color_format = xenos::ColorRenderTargetFormat source_color_format =
xenos::ColorRenderTargetFormat(key.source_host_relevant_format); xenos::ColorRenderTargetFormat(key.source_resource_format);
xenos::DepthRenderTargetFormat source_depth_format = xenos::DepthRenderTargetFormat source_depth_format =
xenos::DepthRenderTargetFormat(key.source_host_relevant_format); xenos::DepthRenderTargetFormat(key.source_resource_format);
// If not source_is_color, it's depth / stencil - 40-sample columns are // If not source_is_color, it's depth / stencil - 40-sample columns are
// swapped as opposed to color destination. // swapped as opposed to color destination.
bool source_is_color = (rs & kTransferUsedRootParameterColorSRVBit) != 0; bool source_is_color = (rs & kTransferUsedRootParameterColorSRVBit) != 0;
@ -3056,10 +3068,11 @@ D3D12RenderTargetCache::GetOrCreateTransferPipelines(TransferShaderKey key) {
uint32_t source_tile_pixel_x_reg = 0; uint32_t source_tile_pixel_x_reg = 0;
uint32_t source_tile_pixel_y_reg = 0; uint32_t source_tile_pixel_y_reg = 0;
// First sample bit at 4x - horizontal sample. // First sample bit at 4x in Direct3D 10.1+ - horizontal sample.
// Second sample bit at 4x - vertical sample. // Second sample bit at 4x in Direct3D 10.1+ - vertical sample.
// At 2x, the vertical sample is either the first or the second bit // At 2x:
// depending on whether 2x is emulated as 4x. // - Native 2x: top is 1 in Direct3D 10.1+, bottom is 0.
// - 2x as 4x: top is 0, bottom is 3.
if (!source_is_64bpp && dest_is_64bpp) { if (!source_is_64bpp && dest_is_64bpp) {
// 32bpp -> 64bpp, need two samples of the source. // 32bpp -> 64bpp, need two samples of the source.
@ -3088,14 +3101,15 @@ D3D12RenderTargetCache::GetOrCreateTransferPipelines(TransferShaderKey key) {
// D p0,0 s1 = S p0,0 s0,1 | S p0,0 s1,1 // D p0,0 s1 = S p0,0 s0,1 | S p0,0 s1,1
// D p1,0 s0 = S p1,0 s0,0 | S p1,0 s1,0 // D p1,0 s0 = S p1,0 s0,0 | S p1,0 s1,0
// D p1,0 s1 = S p1,0 s0,1 | S p1,0 s1,1 // D p1,0 s1 = S p1,0 s0,1 | S p1,0 s1,1
// Pixel index can be reused. Sample 0 should become samples 01, // Pixel index can be reused. Sample 1 (for native 2x) or 0 (for 2x as
// sample 1 or 3 should become samples 23. // 4x) should become samples 01, sample 0 or 3 should become samples 23.
source_sample = dxbc::Src::R(1, dxbc::Src::kZZZZ);
if (msaa_2x_supported_) { if (msaa_2x_supported_) {
a.OpIShL(dxbc::Dest::R(1, 0b0100), dest_sample, dxbc::Src::LU(1)); a.OpXOr(dxbc::Dest::R(1, 0b0100), dest_sample, dxbc::Src::LU(1));
a.OpIShL(dxbc::Dest::R(1, 0b0100), source_sample, dxbc::Src::LU(1));
} else { } else {
a.OpAnd(dxbc::Dest::R(1, 0b0100), dest_sample, dxbc::Src::LU(0b10)); a.OpAnd(dxbc::Dest::R(1, 0b0100), dest_sample, dxbc::Src::LU(0b10));
} }
source_sample = dxbc::Src::R(1, dxbc::Src::kZZZZ);
} else { } else {
// 32bpp -> 64bpp, 4x -> 1x. // 32bpp -> 64bpp, 4x -> 1x.
// 1 destination horizontal pixel = 2 source horizontal samples. // 1 destination horizontal pixel = 2 source horizontal samples.
@ -3176,11 +3190,14 @@ D3D12RenderTargetCache::GetOrCreateTransferPipelines(TransferShaderKey key) {
source_sample = dxbc::Src::R(1, dxbc::Src::kZZZZ); source_sample = dxbc::Src::R(1, dxbc::Src::kZZZZ);
if (key.dest_msaa_samples == xenos::MsaaSamples::k2X) { if (key.dest_msaa_samples == xenos::MsaaSamples::k2X) {
// 64bpp -> 32bpp, 4x -> 2x. // 64bpp -> 32bpp, 4x -> 2x.
// Destination vertical samples (first or second bit, depending on // Destination vertical samples (1/0 in the first bit for native 2x or
// support) = source vertical samples (second bit). // 0/1 in the second bit for 2x as 4x) = source vertical samples
// (second bit).
if (msaa_2x_supported_) { if (msaa_2x_supported_) {
a.OpBFI(dxbc::Dest::R(1, 0b0100), dxbc::Src::LU(1), a.OpBFI(dxbc::Dest::R(1, 0b0100), dxbc::Src::LU(1),
dxbc::Src::LU(1), dest_sample, source_sample); dxbc::Src::LU(1), dest_sample, source_sample);
a.OpXOr(dxbc::Dest::R(1, 0b0100), source_sample,
dxbc::Src::LU(1 << 1));
} else { } else {
a.OpBFI(dxbc::Dest::R(1, 0b0100), dxbc::Src::LU(1), a.OpBFI(dxbc::Dest::R(1, 0b0100), dxbc::Src::LU(1),
dxbc::Src::LU(0), source_sample, dest_sample); dxbc::Src::LU(0), source_sample, dest_sample);
@ -3219,18 +3236,21 @@ D3D12RenderTargetCache::GetOrCreateTransferPipelines(TransferShaderKey key) {
// Same BPP, 4x -> 1x/2x. // Same BPP, 4x -> 1x/2x.
if (key.dest_msaa_samples == xenos::MsaaSamples::k2X) { if (key.dest_msaa_samples == xenos::MsaaSamples::k2X) {
// Same BPP, 4x -> 2x. // Same BPP, 4x -> 2x.
// Horizontal pixels to samples. Vertical sample (first or second bit, // Horizontal pixels to samples. Vertical sample (1/0 in the first bit
// depending on support) to second sample bit. // for native 2x or 0/1 in the second bit for 2x as 4x) to second
// sample bit.
source_sample = dxbc::Src::R(1, dxbc::Src::kZZZZ);
if (msaa_2x_supported_) { if (msaa_2x_supported_) {
a.OpBFI(dxbc::Dest::R(1, 0b0100), dxbc::Src::LU(31), a.OpBFI(dxbc::Dest::R(1, 0b0100), dxbc::Src::LU(31),
dxbc::Src::LU(1), dest_sample, dxbc::Src::LU(1), dest_sample,
dxbc::Src::R(0, dxbc::Src::kXXXX)); dxbc::Src::R(0, dxbc::Src::kXXXX));
a.OpXOr(dxbc::Dest::R(1, 0b0100), source_sample,
dxbc::Src::LU(1 << 1));
} else { } else {
a.OpBFI(dxbc::Dest::R(1, 0b0100), dxbc::Src::LU(1), a.OpBFI(dxbc::Dest::R(1, 0b0100), dxbc::Src::LU(1),
dxbc::Src::LU(0), dxbc::Src::R(0, dxbc::Src::kXXXX), dxbc::Src::LU(0), dxbc::Src::R(0, dxbc::Src::kXXXX),
dest_sample); dest_sample);
} }
source_sample = dxbc::Src::R(1, dxbc::Src::kZZZZ);
a.OpUShR(dxbc::Dest::R(1, 0b0001), dxbc::Src::R(0, dxbc::Src::kXXXX), a.OpUShR(dxbc::Dest::R(1, 0b0001), dxbc::Src::R(0, dxbc::Src::kXXXX),
dxbc::Src::LU(1)); dxbc::Src::LU(1));
source_tile_pixel_x_reg = 1; source_tile_pixel_x_reg = 1;
@ -3267,10 +3287,12 @@ D3D12RenderTargetCache::GetOrCreateTransferPipelines(TransferShaderKey key) {
if (key.source_msaa_samples == xenos::MsaaSamples::k2X) { if (key.source_msaa_samples == xenos::MsaaSamples::k2X) {
// 2x -> 4x. // 2x -> 4x.
// Vertical samples (second bit) of 4x destination to vertical sample // Vertical samples (second bit) of 4x destination to vertical sample
// (01 or 03, depending on support) of 2x source. // (1, 0 for native 2x, or 0, 3 for 2x as 4x) of 2x source.
a.OpUShR(dxbc::Dest::R(1, 0b0100), dest_sample, dxbc::Src::LU(1)); a.OpUShR(dxbc::Dest::R(1, 0b0100), dest_sample, dxbc::Src::LU(1));
source_sample = dxbc::Src::R(1, dxbc::Src::kZZZZ); source_sample = dxbc::Src::R(1, dxbc::Src::kZZZZ);
if (!msaa_2x_supported_) { if (msaa_2x_supported_) {
a.OpXOr(dxbc::Dest::R(1, 0b0100), source_sample, dxbc::Src::LU(1));
} else {
a.OpBFI(dxbc::Dest::R(1, 0b0100), dxbc::Src::LU(1), dxbc::Src::LU(1), a.OpBFI(dxbc::Dest::R(1, 0b0100), dxbc::Src::LU(1), dxbc::Src::LU(1),
source_sample, source_sample); source_sample, source_sample);
} }
@ -3287,12 +3309,14 @@ D3D12RenderTargetCache::GetOrCreateTransferPipelines(TransferShaderKey key) {
// 1x/2x -> different 1x/2x. // 1x/2x -> different 1x/2x.
if (key.source_msaa_samples == xenos::MsaaSamples::k2X) { if (key.source_msaa_samples == xenos::MsaaSamples::k2X) {
// 2x -> 1x. // 2x -> 1x.
// Vertical pixels of 2x destination to vertical samples (01 or 03, // Vertical pixels of 2x destination to vertical samples (1, 0 for
// depending on support) of 1x source. // native 2x, or 0, 3 for 2x as 4x) of 1x source.
a.OpAnd(dxbc::Dest::R(1, 0b0100), dxbc::Src::R(0, dxbc::Src::kYYYY), a.OpAnd(dxbc::Dest::R(1, 0b0100), dxbc::Src::R(0, dxbc::Src::kYYYY),
dxbc::Src::LU(1)); dxbc::Src::LU(1));
source_sample = dxbc::Src::R(1, dxbc::Src::kZZZZ); source_sample = dxbc::Src::R(1, dxbc::Src::kZZZZ);
if (!msaa_2x_supported_) { if (msaa_2x_supported_) {
a.OpXOr(dxbc::Dest::R(1, 0b0100), source_sample, dxbc::Src::LU(1));
} else {
a.OpBFI(dxbc::Dest::R(1, 0b0100), dxbc::Src::LU(1), dxbc::Src::LU(1), a.OpBFI(dxbc::Dest::R(1, 0b0100), dxbc::Src::LU(1), dxbc::Src::LU(1),
source_sample, source_sample); source_sample, source_sample);
} }
@ -3301,15 +3325,20 @@ D3D12RenderTargetCache::GetOrCreateTransferPipelines(TransferShaderKey key) {
source_tile_pixel_y_reg = 1; source_tile_pixel_y_reg = 1;
} else { } else {
// 1x -> 2x. // 1x -> 2x.
// Vertical samples (first or second bit, depending on support) of 2x // Vertical samples (1/0 in the first bit for native 2x or 0/1 in the
// destination to vertical pixels of 1x source. // second bit for 2x as 4x) of 2x destination to vertical pixels of 1x
if (!msaa_2x_supported_) { // source.
if (msaa_2x_supported_) {
a.OpBFI(dxbc::Dest::R(1, 0b0010), dxbc::Src::LU(31), dxbc::Src::LU(1),
dxbc::Src::R(0, dxbc::Src::kYYYY), dest_sample);
a.OpXOr(dxbc::Dest::R(1, 0b0010), dxbc::Src::R(1, dxbc::Src::kYYYY),
dxbc::Src::LU(1));
} else {
a.OpUShR(dxbc::Dest::R(1, 0b0010), dest_sample, dxbc::Src::LU(1)); a.OpUShR(dxbc::Dest::R(1, 0b0010), dest_sample, dxbc::Src::LU(1));
a.OpBFI(dxbc::Dest::R(1, 0b0010), dxbc::Src::LU(31), dxbc::Src::LU(1),
dxbc::Src::R(0, dxbc::Src::kYYYY),
dxbc::Src::R(1, dxbc::Src::kYYYY));
} }
a.OpBFI(dxbc::Dest::R(1, 0b0010), dxbc::Src::LU(31), dxbc::Src::LU(1),
dxbc::Src::R(0, dxbc::Src::kYYYY),
msaa_2x_supported_ ? dest_sample
: dxbc::Src::R(1, dxbc::Src::kYYYY));
source_tile_pixel_y_reg = 1; source_tile_pixel_y_reg = 1;
} }
} }
@ -3910,13 +3939,15 @@ D3D12RenderTargetCache::GetOrCreateTransferPipelines(TransferShaderKey key) {
dxbc::Src::LU(1), dxbc::Src::R(0, dxbc::Src::kXXXX), dxbc::Src::LU(1), dxbc::Src::R(0, dxbc::Src::kXXXX),
dest_sample); dest_sample);
} }
// Vertical sample index in bit 0 for true 2x or in bit 1 for // Vertical sample index as 1 or 0 in bit 0 for true 2x or as 0
// 4x or for 2x emulated as 4x. // or 1 in bit 1 for 4x or for 2x emulated as 4x.
if (key.dest_msaa_samples == xenos::MsaaSamples::k2X && if (key.dest_msaa_samples == xenos::MsaaSamples::k2X &&
msaa_2x_supported_) { msaa_2x_supported_) {
a.OpBFI(dxbc::Dest::R(0, 0b0010), dxbc::Src::LU(31), a.OpBFI(dxbc::Dest::R(0, 0b0010), dxbc::Src::LU(31),
dxbc::Src::LU(1), dxbc::Src::R(0, dxbc::Src::kYYYY), dxbc::Src::LU(1), dxbc::Src::R(0, dxbc::Src::kYYYY),
dest_sample); dest_sample);
a.OpXOr(dxbc::Dest::R(0, 0b0010),
dxbc::Src::R(0, dxbc::Src::kYYYY), dxbc::Src::LU(1));
} else { } else {
// Using r0.w as a temporary. // Using r0.w as a temporary.
a.OpUShR(dxbc::Dest::R(0, 0b1000), dest_sample, a.OpUShR(dxbc::Dest::R(0, 0b1000), dest_sample,
@ -3968,19 +3999,22 @@ D3D12RenderTargetCache::GetOrCreateTransferPipelines(TransferShaderKey key) {
// 4x -> 1x/2x. // 4x -> 1x/2x.
if (key.dest_msaa_samples == xenos::MsaaSamples::k2X) { if (key.dest_msaa_samples == xenos::MsaaSamples::k2X) {
// 4x -> 2x. // 4x -> 2x.
// Horizontal pixels to samples. Vertical sample (first or // Horizontal pixels to samples. Vertical sample (1, 0 in
// second bit, depending on support) to second sample bit. // the first bit for native 2x or 0, 1 in the second bit for
// 2x as 4x) to second sample bit.
host_depth_source_sample =
dxbc::Src::R(0, dxbc::Src::kWWWW);
if (msaa_2x_supported_) { if (msaa_2x_supported_) {
a.OpBFI(dxbc::Dest::R(0, 0b1000), dxbc::Src::LU(31), a.OpBFI(dxbc::Dest::R(0, 0b1000), dxbc::Src::LU(31),
dxbc::Src::LU(1), dest_sample, dxbc::Src::LU(1), dest_sample,
dxbc::Src::R(0, dxbc::Src::kXXXX)); dxbc::Src::R(0, dxbc::Src::kXXXX));
a.OpXOr(dxbc::Dest::R(0, 0b1000),
host_depth_source_sample, dxbc::Src::LU(1 << 1));
} else { } else {
a.OpBFI(dxbc::Dest::R(0, 0b1000), dxbc::Src::LU(1), a.OpBFI(dxbc::Dest::R(0, 0b1000), dxbc::Src::LU(1),
dxbc::Src::LU(0), dxbc::Src::LU(0),
dxbc::Src::R(0, dxbc::Src::kXXXX), dest_sample); dxbc::Src::R(0, dxbc::Src::kXXXX), dest_sample);
} }
host_depth_source_sample =
dxbc::Src::R(0, dxbc::Src::kWWWW);
a.OpUShR(dxbc::Dest::R(0, 0b0001), a.OpUShR(dxbc::Dest::R(0, 0b0001),
dxbc::Src::R(0, dxbc::Src::kXXXX), dxbc::Src::R(0, dxbc::Src::kXXXX),
dxbc::Src::LU(1)); dxbc::Src::LU(1));
@ -4017,13 +4051,16 @@ D3D12RenderTargetCache::GetOrCreateTransferPipelines(TransferShaderKey key) {
xenos::MsaaSamples::k2X) { xenos::MsaaSamples::k2X) {
// 2x -> 4x. // 2x -> 4x.
// Vertical samples (second bit) of 4x destination to // Vertical samples (second bit) of 4x destination to
// vertical sample (01 or 03, depending on support) of 2x // vertical sample (1, 0 for native 2x, or 0, 3 for 2x as
// source. // 4x) of 2x source.
a.OpUShR(dxbc::Dest::R(0, 0b1000), dest_sample, a.OpUShR(dxbc::Dest::R(0, 0b1000), dest_sample,
dxbc::Src::LU(1)); dxbc::Src::LU(1));
host_depth_source_sample = host_depth_source_sample =
dxbc::Src::R(0, dxbc::Src::kWWWW); dxbc::Src::R(0, dxbc::Src::kWWWW);
if (!msaa_2x_supported_) { if (msaa_2x_supported_) {
a.OpXOr(dxbc::Dest::R(0, 0b1000),
host_depth_source_sample, dxbc::Src::LU(1));
} else {
a.OpBFI(dxbc::Dest::R(0, 0b1000), dxbc::Src::LU(1), a.OpBFI(dxbc::Dest::R(0, 0b1000), dxbc::Src::LU(1),
dxbc::Src::LU(1), host_depth_source_sample, dxbc::Src::LU(1), host_depth_source_sample,
host_depth_source_sample); host_depth_source_sample);
@ -4045,13 +4082,17 @@ D3D12RenderTargetCache::GetOrCreateTransferPipelines(TransferShaderKey key) {
xenos::MsaaSamples::k2X) { xenos::MsaaSamples::k2X) {
// 2x -> 1x. // 2x -> 1x.
// Vertical pixels of 2x destination to vertical samples // Vertical pixels of 2x destination to vertical samples
// (01 or 03, depending on support) of 1x source. // (1, 0 for native 2x, or 0, 3 for 2x as 4x) of 1x
// source.
a.OpAnd(dxbc::Dest::R(0, 0b1000), a.OpAnd(dxbc::Dest::R(0, 0b1000),
dxbc::Src::R(0, dxbc::Src::kYYYY), dxbc::Src::R(0, dxbc::Src::kYYYY),
dxbc::Src::LU(1)); dxbc::Src::LU(1));
host_depth_source_sample = host_depth_source_sample =
dxbc::Src::R(0, dxbc::Src::kWWWW); dxbc::Src::R(0, dxbc::Src::kWWWW);
if (!msaa_2x_supported_) { if (msaa_2x_supported_) {
a.OpXOr(dxbc::Dest::R(0, 0b1000),
host_depth_source_sample, dxbc::Src::LU(1));
} else {
a.OpBFI(dxbc::Dest::R(0, 0b1000), dxbc::Src::LU(1), a.OpBFI(dxbc::Dest::R(0, 0b1000), dxbc::Src::LU(1),
dxbc::Src::LU(1), host_depth_source_sample, dxbc::Src::LU(1), host_depth_source_sample,
host_depth_source_sample); host_depth_source_sample);
@ -4061,21 +4102,26 @@ D3D12RenderTargetCache::GetOrCreateTransferPipelines(TransferShaderKey key) {
dxbc::Src::LU(1)); dxbc::Src::LU(1));
} else { } else {
// 1x -> 2x. // 1x -> 2x.
// Vertical samples (first or second bit, depending on // Vertical samples (1, 0 in the first bit for native 2x
// support) of 2x destination to vertical pixels of 1x // or 0, 1 in the second bit for 2x as 4x) of 2x
// source. // destination to vertical pixels of 1x source.
// Using r0.w (not needed without source MSAA) as a // Using r0.w (not needed without source MSAA) as a
// temporary. // temporary.
if (!msaa_2x_supported_) { if (msaa_2x_supported_) {
a.OpBFI(dxbc::Dest::R(0, 0b0010), dxbc::Src::LU(31),
dxbc::Src::LU(1),
dxbc::Src::R(0, dxbc::Src::kYYYY), dest_sample);
a.OpXOr(dxbc::Dest::R(0, 0b0010),
dxbc::Src::R(0, dxbc::Src::kYYYY),
dxbc::Src::LU(1));
} else {
a.OpUShR(dxbc::Dest::R(0, 0b1000), dest_sample, a.OpUShR(dxbc::Dest::R(0, 0b1000), dest_sample,
dxbc::Src::LU(1)); dxbc::Src::LU(1));
a.OpBFI(dxbc::Dest::R(0, 0b0010), dxbc::Src::LU(31),
dxbc::Src::LU(1),
dxbc::Src::R(0, dxbc::Src::kYYYY),
dxbc::Src::R(0, dxbc::Src::kWWWW));
} }
a.OpBFI(dxbc::Dest::R(0, 0b0010), dxbc::Src::LU(31),
dxbc::Src::LU(1),
dxbc::Src::R(0, dxbc::Src::kYYYY),
msaa_2x_supported_
? dest_sample
: dxbc::Src::R(0, dxbc::Src::kWWWW));
} }
} }
} }
@ -4315,8 +4361,8 @@ D3D12RenderTargetCache::GetOrCreateTransferPipelines(TransferShaderKey key) {
pipeline_desc.pRootSignature = transfer_root_signatures_[size_t( pipeline_desc.pRootSignature = transfer_root_signatures_[size_t(
use_stencil_reference_output_ ? mode.root_signature_with_stencil_ref use_stencil_reference_output_ ? mode.root_signature_with_stencil_ref
: mode.root_signature_no_stencil_ref)]; : mode.root_signature_no_stencil_ref)];
pipeline_desc.VS.pShaderBytecode = passthrough_position_xy_vs; pipeline_desc.VS.pShaderBytecode = shaders::passthrough_position_xy_vs;
pipeline_desc.VS.BytecodeLength = sizeof(passthrough_position_xy_vs); pipeline_desc.VS.BytecodeLength = sizeof(shaders::passthrough_position_xy_vs);
pipeline_desc.PS.pShaderBytecode = built_shader_.data(); pipeline_desc.PS.pShaderBytecode = built_shader_.data();
pipeline_desc.PS.BytecodeLength = built_shader_size_bytes; pipeline_desc.PS.BytecodeLength = built_shader_size_bytes;
if (key.dest_msaa_samples == xenos::MsaaSamples::k2X && !msaa_2x_supported_) { if (key.dest_msaa_samples == xenos::MsaaSamples::k2X && !msaa_2x_supported_) {
@ -4558,11 +4604,8 @@ void D3D12RenderTargetCache::PerformTransfersAndResolveClears(
dest_rt_key.pitch_tiles_at_32bpp; dest_rt_key.pitch_tiles_at_32bpp;
host_depth_store_render_target_constant.resolution_scale = host_depth_store_render_target_constant.resolution_scale =
resolution_scale_; resolution_scale_;
host_depth_store_render_target_constant.second_sample_index = host_depth_store_render_target_constant.msaa_2x_supported =
(dest_rt_key.msaa_samples == xenos::MsaaSamples::k2X && uint32_t(msaa_2x_supported_);
!msaa_2x_supported_)
? 3
: 1;
command_list.D3DSetComputeRoot32BitConstants( command_list.D3DSetComputeRoot32BitConstants(
kHostDepthStoreRootParameterRenderTargetConstant, kHostDepthStoreRootParameterRenderTargetConstant,
sizeof(host_depth_store_render_target_constant) / sizeof(uint32_t), sizeof(host_depth_store_render_target_constant) / sizeof(uint32_t),
@ -4861,8 +4904,8 @@ void D3D12RenderTargetCache::PerformTransfersAndResolveClears(
uint32_t rt_sort_index = 0; uint32_t rt_sort_index = 0;
TransferShaderKey new_transfer_shader_key; TransferShaderKey new_transfer_shader_key;
new_transfer_shader_key.dest_msaa_samples = dest_rt_key.msaa_samples; new_transfer_shader_key.dest_msaa_samples = dest_rt_key.msaa_samples;
new_transfer_shader_key.dest_host_relevant_format = new_transfer_shader_key.dest_resource_format =
dest_rt_key.host_relevant_format; dest_rt_key.resource_format;
uint32_t stencil_clear_rectangle_count = 0; uint32_t stencil_clear_rectangle_count = 0;
for (uint32_t j = 0; j <= uint32_t(need_stencil_bit_draws); ++j) { for (uint32_t j = 0; j <= uint32_t(need_stencil_bit_draws); ++j) {
// j == 0 - color or depth. // j == 0 - color or depth.
@ -4899,8 +4942,8 @@ void D3D12RenderTargetCache::PerformTransfersAndResolveClears(
RenderTargetKey source_rt_key = source_d3d12_rt.key(); RenderTargetKey source_rt_key = source_d3d12_rt.key();
new_transfer_shader_key.source_msaa_samples = new_transfer_shader_key.source_msaa_samples =
source_rt_key.msaa_samples; source_rt_key.msaa_samples;
new_transfer_shader_key.source_host_relevant_format = new_transfer_shader_key.source_resource_format =
source_rt_key.host_relevant_format; source_rt_key.resource_format;
bool host_depth_source_is_copy = bool host_depth_source_is_copy =
host_depth_source_d3d12_rt == &dest_d3d12_rt; host_depth_source_d3d12_rt == &dest_d3d12_rt;
new_transfer_shader_key.host_depth_source_is_copy = new_transfer_shader_key.host_depth_source_is_copy =
@ -6013,7 +6056,7 @@ ID3D12PipelineState* D3D12RenderTargetCache::GetOrCreateDumpPipeline(
dxbc::Src::LU(xenos::kEdramTileWidthSamples >> dxbc::Src::LU(xenos::kEdramTileWidthSamples >>
uint32_t(format_is_64bpp)), uint32_t(format_is_64bpp)),
dxbc::Src::VThreadIDInGroup(dxbc::Src::kXXXX)); dxbc::Src::VThreadIDInGroup(dxbc::Src::kXXXX));
// r0.w for 4x MSAA = pixel Y in the group // r0.w for 2x MSAA = pixel Y in the group
a.OpUShR(dxbc::Dest::R(0, 0b1000), a.OpUShR(dxbc::Dest::R(0, 0b1000),
dxbc::Src::VThreadIDInGroup(dxbc::Src::kYYYY), dxbc::Src::LU(1)); dxbc::Src::VThreadIDInGroup(dxbc::Src::kYYYY), dxbc::Src::LU(1));
// r0.w = free // r0.w = free
@ -6042,7 +6085,7 @@ ID3D12PipelineState* D3D12RenderTargetCache::GetOrCreateDumpPipeline(
if (key.msaa_samples != xenos::MsaaSamples::k1X) { if (key.msaa_samples != xenos::MsaaSamples::k1X) {
// Sample index. // Sample index.
// For 4x, bit 0 for horizontal, bit 1 for vertical. // For 4x, bit 0 for horizontal, bit 1 for vertical.
// For 2x, only vertical - but 0 or 1 for true 2x MSAA or 0 or 3 for 2x MSAA // For 2x, only vertical - but 1 or 0 for true 2x MSAA or 0 or 3 for 2x MSAA
// via two samples of 4x. // via two samples of 4x.
// r0.w = vertical sample index // r0.w = vertical sample index
a.OpAnd(dxbc::Dest::R(0, 0b1000), a.OpAnd(dxbc::Dest::R(0, 0b1000),
@ -6052,11 +6095,17 @@ ID3D12PipelineState* D3D12RenderTargetCache::GetOrCreateDumpPipeline(
a.OpBFI(dxbc::Dest::R(0, 0b1000), dxbc::Src::LU(31), dxbc::Src::LU(1), a.OpBFI(dxbc::Dest::R(0, 0b1000), dxbc::Src::LU(31), dxbc::Src::LU(1),
dxbc::Src::R(0, dxbc::Src::kWWWW), dxbc::Src::R(0, dxbc::Src::kWWWW),
dxbc::Src::VThreadIDInGroup(dxbc::Src::kXXXX)); dxbc::Src::VThreadIDInGroup(dxbc::Src::kXXXX));
} else if (!msaa_2x_supported_) { } else {
// r0.w = source sample 0 or 3 for 2x MSAA emulated via 4x if (msaa_2x_supported_) {
a.OpBFI(dxbc::Dest::R(0, 0b1000), dxbc::Src::LU(1), dxbc::Src::LU(1), // r0.w = source sample 1 or 0 for native 2x MSAA
dxbc::Src::R(0, dxbc::Src::kWWWW), a.OpXOr(dxbc::Dest::R(0, 0b1000), dxbc::Src::R(0, dxbc::Src::kWWWW),
dxbc::Src::R(0, dxbc::Src::kWWWW)); dxbc::Src::LU(1));
} else {
// r0.w = source sample 0 or 3 for 2x MSAA emulated via 4x
a.OpBFI(dxbc::Dest::R(0, 0b1000), dxbc::Src::LU(1), dxbc::Src::LU(1),
dxbc::Src::R(0, dxbc::Src::kWWWW),
dxbc::Src::R(0, dxbc::Src::kWWWW));
}
} }
} }
@ -6427,7 +6476,7 @@ void D3D12RenderTargetCache::DumpRenderTargets(uint32_t dump_base,
any_sources_32bpp_64bpp[size_t(rt_key.Is64bpp())] = true; any_sources_32bpp_64bpp[size_t(rt_key.Is64bpp())] = true;
DumpPipelineKey pipeline_key; DumpPipelineKey pipeline_key;
pipeline_key.msaa_samples = rt_key.msaa_samples; pipeline_key.msaa_samples = rt_key.msaa_samples;
pipeline_key.host_relevant_format = rt_key.host_relevant_format; pipeline_key.resource_format = rt_key.resource_format;
pipeline_key.is_depth = rt_key.is_depth; pipeline_key.is_depth = rt_key.is_depth;
dump_invocations_.emplace_back(rectangle, pipeline_key); dump_invocations_.emplace_back(rectangle, pipeline_key);
} }

View File

@ -15,7 +15,9 @@
#include <cstddef> #include <cstddef>
#include <cstdint> #include <cstdint>
#include <deque> #include <deque>
#include <functional>
#include <memory> #include <memory>
#include <unordered_map>
#include <utility> #include <utility>
#include <vector> #include <vector>
@ -132,8 +134,7 @@ class D3D12RenderTargetCache final : public RenderTargetCache {
// floating-point formats, and to distinguish between two -1 representations // floating-point formats, and to distinguish between two -1 representations
// in snorm formats). // in snorm formats).
D3D12RenderTarget( D3D12RenderTarget(
RenderTargetKey key, D3D12RenderTargetCache& render_target_cache, RenderTargetKey key, ID3D12Resource* resource,
ID3D12Resource* resource,
ui::d3d12::D3D12CpuDescriptorPool::Descriptor&& descriptor_draw, ui::d3d12::D3D12CpuDescriptorPool::Descriptor&& descriptor_draw,
ui::d3d12::D3D12CpuDescriptorPool::Descriptor&& descriptor_draw_srgb, ui::d3d12::D3D12CpuDescriptorPool::Descriptor&& descriptor_draw_srgb,
ui::d3d12::D3D12CpuDescriptorPool::Descriptor&& ui::d3d12::D3D12CpuDescriptorPool::Descriptor&&
@ -142,7 +143,6 @@ class D3D12RenderTargetCache final : public RenderTargetCache {
ui::d3d12::D3D12CpuDescriptorPool::Descriptor&& descriptor_srv_stencil, ui::d3d12::D3D12CpuDescriptorPool::Descriptor&& descriptor_srv_stencil,
D3D12_RESOURCE_STATES resource_state) D3D12_RESOURCE_STATES resource_state)
: RenderTarget(key), : RenderTarget(key),
render_target_cache_(render_target_cache),
resource_(resource), resource_(resource),
descriptor_draw_(std::move(descriptor_draw)), descriptor_draw_(std::move(descriptor_draw)),
descriptor_draw_srgb_(std::move(descriptor_draw_srgb)), descriptor_draw_srgb_(std::move(descriptor_draw_srgb)),
@ -197,7 +197,6 @@ class D3D12RenderTargetCache final : public RenderTargetCache {
} }
private: private:
D3D12RenderTargetCache& render_target_cache_;
Microsoft::WRL::ComPtr<ID3D12Resource> resource_; Microsoft::WRL::ComPtr<ID3D12Resource> resource_;
ui::d3d12::D3D12CpuDescriptorPool::Descriptor descriptor_draw_; ui::d3d12::D3D12CpuDescriptorPool::Descriptor descriptor_draw_;
ui::d3d12::D3D12CpuDescriptorPool::Descriptor descriptor_draw_srgb_; ui::d3d12::D3D12CpuDescriptorPool::Descriptor descriptor_draw_srgb_;
@ -222,9 +221,6 @@ class D3D12RenderTargetCache final : public RenderTargetCache {
return D3D12_REQ_TEXTURE2D_U_OR_V_DIMENSION; return D3D12_REQ_TEXTURE2D_U_OR_V_DIMENSION;
} }
xenos::ColorRenderTargetFormat GetHostRelevantColorFormat(
xenos::ColorRenderTargetFormat format) const override;
RenderTarget* CreateRenderTarget(RenderTargetKey key) override; RenderTarget* CreateRenderTarget(RenderTargetKey key) override;
bool IsHostDepthEncodingDifferent( bool IsHostDepthEncodingDifferent(
@ -294,7 +290,7 @@ class D3D12RenderTargetCache final : public RenderTargetCache {
// Parameter 1 - destination (shared memory or a part of it). // Parameter 1 - destination (shared memory or a part of it).
// Parameter 2 - source (EDRAM). // Parameter 2 - source (EDRAM).
ID3D12RootSignature* resolve_copy_root_signature_ = nullptr; ID3D12RootSignature* resolve_copy_root_signature_ = nullptr;
static const std::pair<const uint8_t*, size_t> static const std::pair<const void*, size_t>
kResolveCopyShaders[size_t(draw_util::ResolveCopyShaderIndex::kCount)]; kResolveCopyShaders[size_t(draw_util::ResolveCopyShaderIndex::kCount)];
ID3D12PipelineState* resolve_copy_pipelines_[size_t( ID3D12PipelineState* resolve_copy_pipelines_[size_t(
draw_util::ResolveCopyShaderIndex::kCount)] = {}; draw_util::ResolveCopyShaderIndex::kCount)] = {};
@ -416,14 +412,14 @@ class D3D12RenderTargetCache final : public RenderTargetCache {
union TransferShaderKey { union TransferShaderKey {
struct { struct {
xenos::MsaaSamples dest_msaa_samples : xenos::kMsaaSamplesBits; xenos::MsaaSamples dest_msaa_samples : xenos::kMsaaSamplesBits;
uint32_t dest_host_relevant_format : xenos::kRenderTargetFormatBits; uint32_t dest_resource_format : xenos::kRenderTargetFormatBits;
xenos::MsaaSamples source_msaa_samples : xenos::kMsaaSamplesBits; xenos::MsaaSamples source_msaa_samples : xenos::kMsaaSamplesBits;
// Always 1x when host_depth_source_is_copy is true not to create the same // Always 1x when host_depth_source_is_copy is true not to create the same
// pipeline for different MSAA sample counts as it doesn't matter in this // pipeline for different MSAA sample counts as it doesn't matter in this
// case. // case.
xenos::MsaaSamples host_depth_source_msaa_samples xenos::MsaaSamples host_depth_source_msaa_samples
: xenos::kMsaaSamplesBits; : xenos::kMsaaSamplesBits;
uint32_t source_host_relevant_format : xenos::kRenderTargetFormatBits; uint32_t source_resource_format : xenos::kRenderTargetFormatBits;
// If host depth is also fetched, whether it's pre-copied to the EDRAM // If host depth is also fetched, whether it's pre-copied to the EDRAM
// buffer (but since it's just a scratch buffer, with tiles laid out // buffer (but since it's just a scratch buffer, with tiles laid out
// linearly with the same pitch as in the original render target; also no // linearly with the same pitch as in the original render target; also no
@ -537,8 +533,8 @@ class D3D12RenderTargetCache final : public RenderTargetCache {
uint32_t pitch_tiles : xenos::kEdramPitchTilesBits; uint32_t pitch_tiles : xenos::kEdramPitchTilesBits;
// 1 to 3. // 1 to 3.
uint32_t resolution_scale : 2; uint32_t resolution_scale : 2;
// For native 2x MSAA vs. 2x over 4x. // Whether 2x MSAA is supported natively rather than through 4x.
uint32_t second_sample_index : 2; uint32_t msaa_2x_supported : 1;
}; };
uint32_t constant = 0; uint32_t constant = 0;
}; };
@ -555,7 +551,7 @@ class D3D12RenderTargetCache final : public RenderTargetCache {
union DumpPipelineKey { union DumpPipelineKey {
struct { struct {
xenos::MsaaSamples msaa_samples : 2; xenos::MsaaSamples msaa_samples : 2;
uint32_t host_relevant_format : 4; uint32_t resource_format : 4;
// Last bit because this affects the root signature - after sorting, only // Last bit because this affects the root signature - after sorting, only
// change it at most once. Depth buffers have an additional stencil SRV. // change it at most once. Depth buffers have an additional stencil SRV.
uint32_t is_depth : 1; uint32_t is_depth : 1;
@ -578,11 +574,11 @@ class D3D12RenderTargetCache final : public RenderTargetCache {
xenos::ColorRenderTargetFormat GetColorFormat() const { xenos::ColorRenderTargetFormat GetColorFormat() const {
assert_false(is_depth); assert_false(is_depth);
return xenos::ColorRenderTargetFormat(host_relevant_format); return xenos::ColorRenderTargetFormat(resource_format);
} }
xenos::DepthRenderTargetFormat GetDepthFormat() const { xenos::DepthRenderTargetFormat GetDepthFormat() const {
assert_true(is_depth); assert_true(is_depth);
return xenos::DepthRenderTargetFormat(host_relevant_format); return xenos::DepthRenderTargetFormat(resource_format);
} }
}; };

View File

@ -36,6 +36,7 @@
#include "xenia/gpu/d3d12/d3d12_render_target_cache.h" #include "xenia/gpu/d3d12/d3d12_render_target_cache.h"
#include "xenia/gpu/draw_util.h" #include "xenia/gpu/draw_util.h"
#include "xenia/gpu/gpu_flags.h" #include "xenia/gpu/gpu_flags.h"
#include "xenia/gpu/xenos.h"
#include "xenia/ui/d3d12/d3d12_util.h" #include "xenia/ui/d3d12/d3d12_util.h"
DEFINE_bool(d3d12_dxbc_disasm, false, DEFINE_bool(d3d12_dxbc_disasm, false,
@ -61,20 +62,22 @@ namespace xe {
namespace gpu { namespace gpu {
namespace d3d12 { namespace d3d12 {
// Generated with `xb buildhlsl`. // Generated with `xb buildshaders`.
#include "xenia/gpu/d3d12/shaders/dxbc/adaptive_quad_hs.h" namespace shaders {
#include "xenia/gpu/d3d12/shaders/dxbc/adaptive_triangle_hs.h" #include "xenia/gpu/shaders/bytecode/d3d12_5_1/adaptive_quad_hs.h"
#include "xenia/gpu/d3d12/shaders/dxbc/continuous_quad_hs.h" #include "xenia/gpu/shaders/bytecode/d3d12_5_1/adaptive_triangle_hs.h"
#include "xenia/gpu/d3d12/shaders/dxbc/continuous_triangle_hs.h" #include "xenia/gpu/shaders/bytecode/d3d12_5_1/continuous_quad_hs.h"
#include "xenia/gpu/d3d12/shaders/dxbc/discrete_quad_hs.h" #include "xenia/gpu/shaders/bytecode/d3d12_5_1/continuous_triangle_hs.h"
#include "xenia/gpu/d3d12/shaders/dxbc/discrete_triangle_hs.h" #include "xenia/gpu/shaders/bytecode/d3d12_5_1/discrete_quad_hs.h"
#include "xenia/gpu/d3d12/shaders/dxbc/float24_round_ps.h" #include "xenia/gpu/shaders/bytecode/d3d12_5_1/discrete_triangle_hs.h"
#include "xenia/gpu/d3d12/shaders/dxbc/float24_truncate_ps.h" #include "xenia/gpu/shaders/bytecode/d3d12_5_1/float24_round_ps.h"
#include "xenia/gpu/d3d12/shaders/dxbc/primitive_point_list_gs.h" #include "xenia/gpu/shaders/bytecode/d3d12_5_1/float24_truncate_ps.h"
#include "xenia/gpu/d3d12/shaders/dxbc/primitive_quad_list_gs.h" #include "xenia/gpu/shaders/bytecode/d3d12_5_1/primitive_point_list_gs.h"
#include "xenia/gpu/d3d12/shaders/dxbc/primitive_rectangle_list_gs.h" #include "xenia/gpu/shaders/bytecode/d3d12_5_1/primitive_quad_list_gs.h"
#include "xenia/gpu/d3d12/shaders/dxbc/tessellation_adaptive_vs.h" #include "xenia/gpu/shaders/bytecode/d3d12_5_1/primitive_rectangle_list_gs.h"
#include "xenia/gpu/d3d12/shaders/dxbc/tessellation_indexed_vs.h" #include "xenia/gpu/shaders/bytecode/d3d12_5_1/tessellation_adaptive_vs.h"
#include "xenia/gpu/shaders/bytecode/d3d12_5_1/tessellation_indexed_vs.h"
} // namespace shaders
PipelineCache::PipelineCache(D3D12CommandProcessor& command_processor, PipelineCache::PipelineCache(D3D12CommandProcessor& command_processor,
const RegisterFile& register_file, const RegisterFile& register_file,
@ -864,146 +867,66 @@ D3D12Shader* PipelineCache::LoadShader(xenos::ShaderType shader_type,
return shader; return shader;
} }
bool PipelineCache::GetCurrentShaderModification( DxbcShaderTranslator::Modification
PipelineCache::GetCurrentVertexShaderModification(
const Shader& shader, const Shader& shader,
DxbcShaderTranslator::Modification& modification_out) const { Shader::HostVertexShaderType host_vertex_shader_type) const {
assert_true(shader.type() == xenos::ShaderType::kVertex);
assert_true(shader.is_ucode_analyzed()); assert_true(shader.is_ucode_analyzed());
const auto& regs = register_file_; const auto& regs = register_file_;
auto sq_program_cntl = regs.Get<reg::SQ_PROGRAM_CNTL>(); auto sq_program_cntl = regs.Get<reg::SQ_PROGRAM_CNTL>();
if (shader.type() == xenos::ShaderType::kVertex) { return DxbcShaderTranslator::Modification(
Shader::HostVertexShaderType host_vertex_shader_type = shader_translator_->GetDefaultVertexShaderModification(
GetCurrentHostVertexShaderTypeIfValid(); shader.GetDynamicAddressableRegisterCount(sq_program_cntl.vs_num_reg),
if (host_vertex_shader_type == Shader::HostVertexShaderType(-1)) { host_vertex_shader_type));
return false;
}
modification_out = DxbcShaderTranslator::Modification(
shader_translator_->GetDefaultVertexShaderModification(
shader.GetDynamicAddressableRegisterCount(
sq_program_cntl.vs_num_reg),
host_vertex_shader_type));
} else {
assert_true(shader.type() == xenos::ShaderType::kPixel);
DxbcShaderTranslator::Modification pixel_shader_modification(
shader_translator_->GetDefaultPixelShaderModification(
shader.GetDynamicAddressableRegisterCount(
sq_program_cntl.ps_num_reg)));
if (render_target_cache_.GetPath() ==
RenderTargetCache::Path::kHostRenderTargets) {
using DepthStencilMode =
DxbcShaderTranslator::Modification::DepthStencilMode;
RenderTargetCache::DepthFloat24Conversion depth_float24_conversion =
render_target_cache_.depth_float24_conversion();
if ((depth_float24_conversion ==
RenderTargetCache::DepthFloat24Conversion::kOnOutputTruncating ||
depth_float24_conversion ==
RenderTargetCache::DepthFloat24Conversion::kOnOutputRounding) &&
draw_util::GetDepthControlForCurrentEdramMode(regs).z_enable &&
regs.Get<reg::RB_DEPTH_INFO>().depth_format ==
xenos::DepthRenderTargetFormat::kD24FS8) {
pixel_shader_modification.pixel.depth_stencil_mode =
depth_float24_conversion ==
RenderTargetCache::DepthFloat24Conversion::
kOnOutputTruncating
? DepthStencilMode::kFloat24Truncating
: DepthStencilMode::kFloat24Rounding;
} else {
if (shader.implicit_early_z_write_allowed() &&
(!shader.writes_color_target(0) ||
!draw_util::DoesCoverageDependOnAlpha(
regs.Get<reg::RB_COLORCONTROL>()))) {
pixel_shader_modification.pixel.depth_stencil_mode =
DepthStencilMode::kEarlyHint;
} else {
pixel_shader_modification.pixel.depth_stencil_mode =
DepthStencilMode::kNoModifiers;
}
}
}
modification_out = pixel_shader_modification;
}
return true;
} }
Shader::HostVertexShaderType DxbcShaderTranslator::Modification
PipelineCache::GetCurrentHostVertexShaderTypeIfValid() const { PipelineCache::GetCurrentPixelShaderModification(const Shader& shader) const {
// If the values this functions returns are changed, INVALIDATE THE SHADER assert_true(shader.type() == xenos::ShaderType::kPixel);
// STORAGE (increase kVersion for BOTH shaders and pipelines)! The exception assert_true(shader.is_ucode_analyzed());
// is when the function originally returned "unsupported", but started to
// return a valid value (in this case the shader wouldn't be cached in the
// first place). Otherwise games will not be able to locate shaders for draws
// for which the host vertex shader type has changed!
const auto& regs = register_file_; const auto& regs = register_file_;
auto vgt_draw_initiator = regs.Get<reg::VGT_DRAW_INITIATOR>(); auto sq_program_cntl = regs.Get<reg::SQ_PROGRAM_CNTL>();
if (!xenos::IsMajorModeExplicit(vgt_draw_initiator.major_mode, DxbcShaderTranslator::Modification modification(
vgt_draw_initiator.prim_type)) { shader_translator_->GetDefaultPixelShaderModification(
// VGT_OUTPUT_PATH_CNTL and HOS registers are ignored in implicit major shader.GetDynamicAddressableRegisterCount(
// mode. sq_program_cntl.ps_num_reg)));
return Shader::HostVertexShaderType::kVertex; if (render_target_cache_.GetPath() ==
} RenderTargetCache::Path::kHostRenderTargets) {
if (regs.Get<reg::VGT_OUTPUT_PATH_CNTL>().path_select != using DepthStencilMode =
xenos::VGTOutputPath::kTessellationEnable) { DxbcShaderTranslator::Modification::DepthStencilMode;
return Shader::HostVertexShaderType::kVertex; RenderTargetCache::DepthFloat24Conversion depth_float24_conversion =
} render_target_cache_.depth_float24_conversion();
xenos::TessellationMode tessellation_mode = if ((depth_float24_conversion ==
regs.Get<reg::VGT_HOS_CNTL>().tess_mode; RenderTargetCache::DepthFloat24Conversion::kOnOutputTruncating ||
switch (vgt_draw_initiator.prim_type) { depth_float24_conversion ==
case xenos::PrimitiveType::kTriangleList: RenderTargetCache::DepthFloat24Conversion::kOnOutputRounding) &&
// Also supported by triangle strips and fans according to: draw_util::GetDepthControlForCurrentEdramMode(regs).z_enable &&
// https://www.khronos.org/registry/OpenGL/extensions/AMD/AMD_vertex_shader_tessellator.txt regs.Get<reg::RB_DEPTH_INFO>().depth_format ==
// Would need to convert those to triangle lists, but haven't seen any xenos::DepthRenderTargetFormat::kD24FS8) {
// games using tessellated strips/fans so far. modification.pixel.depth_stencil_mode =
switch (tessellation_mode) { depth_float24_conversion ==
case xenos::TessellationMode::kDiscrete: RenderTargetCache::DepthFloat24Conversion::kOnOutputTruncating
// - Call of Duty 3 - nets above barrels in the beginning of the ? DepthStencilMode::kFloat24Truncating
// first mission (turn right after the end of the intro) - : DepthStencilMode::kFloat24Rounding;
// kTriangleList. } else {
case xenos::TessellationMode::kContinuous: if (shader.implicit_early_z_write_allowed() &&
// - Viva Pinata - tree building with a beehive in the beginning (!shader.writes_color_target(0) ||
// (visible on the start screen behind the logo), waterfall in the !draw_util::DoesCoverageDependOnAlpha(
// beginning - kTriangleList. regs.Get<reg::RB_COLORCONTROL>()))) {
return Shader::HostVertexShaderType::kTriangleDomainCPIndexed; modification.pixel.depth_stencil_mode = DepthStencilMode::kEarlyHint;
default: } else {
break; modification.pixel.depth_stencil_mode = DepthStencilMode::kNoModifiers;
} }
break; }
case xenos::PrimitiveType::kQuadList:
switch (tessellation_mode) {
// Also supported by quad strips according to:
// https://www.khronos.org/registry/OpenGL/extensions/AMD/AMD_vertex_shader_tessellator.txt
// Would need to convert those to quad lists, but haven't seen any games
// using tessellated strips so far.
case xenos::TessellationMode::kDiscrete:
// Not seen in games so far.
case xenos::TessellationMode::kContinuous:
// - Defender - retro screen and beams in the main menu - kQuadList.
return Shader::HostVertexShaderType::kQuadDomainCPIndexed;
default:
break;
}
break;
case xenos::PrimitiveType::kTrianglePatch:
// - Banjo-Kazooie: Nuts & Bolts - water - adaptive.
// - Halo 3 - water - adaptive.
return Shader::HostVertexShaderType::kTriangleDomainPatchIndexed;
case xenos::PrimitiveType::kQuadPatch:
// - Fable II - continuous.
// - Viva Pinata - garden ground - adaptive.
return Shader::HostVertexShaderType::kQuadDomainPatchIndexed;
default:
// TODO(Triang3l): Support line patches.
break;
} }
XELOGE( return modification;
"Unsupported tessellation mode {} for primitive type {}. Report the game "
"to Xenia developers!",
uint32_t(tessellation_mode), uint32_t(vgt_draw_initiator.prim_type));
return Shader::HostVertexShaderType(-1);
} }
bool PipelineCache::ConfigurePipeline( bool PipelineCache::ConfigurePipeline(
D3D12Shader::D3D12Translation* vertex_shader, D3D12Shader::D3D12Translation* vertex_shader,
D3D12Shader::D3D12Translation* pixel_shader, D3D12Shader::D3D12Translation* pixel_shader,
xenos::PrimitiveType primitive_type, xenos::IndexFormat index_format, const PrimitiveProcessor::ProcessingResult& primitive_processing_result,
uint32_t bound_depth_and_color_render_target_bits, uint32_t bound_depth_and_color_render_target_bits,
const uint32_t* bound_depth_and_color_render_target_formats, const uint32_t* bound_depth_and_color_render_target_formats,
void** pipeline_handle_out, ID3D12RootSignature** root_signature_out) { void** pipeline_handle_out, ID3D12RootSignature** root_signature_out) {
@ -1074,7 +997,7 @@ bool PipelineCache::ConfigurePipeline(
PipelineRuntimeDescription runtime_description; PipelineRuntimeDescription runtime_description;
if (!GetCurrentStateDescription( if (!GetCurrentStateDescription(
vertex_shader, pixel_shader, primitive_type, index_format, vertex_shader, pixel_shader, primitive_processing_result,
bound_depth_and_color_render_target_bits, bound_depth_and_color_render_target_bits,
bound_depth_and_color_render_target_formats, runtime_description)) { bound_depth_and_color_render_target_formats, runtime_description)) {
return false; return false;
@ -1340,7 +1263,7 @@ bool PipelineCache::TranslateAnalyzedShader(
bool PipelineCache::GetCurrentStateDescription( bool PipelineCache::GetCurrentStateDescription(
D3D12Shader::D3D12Translation* vertex_shader, D3D12Shader::D3D12Translation* vertex_shader,
D3D12Shader::D3D12Translation* pixel_shader, D3D12Shader::D3D12Translation* pixel_shader,
xenos::PrimitiveType primitive_type, xenos::IndexFormat index_format, const PrimitiveProcessor::ProcessingResult& primitive_processing_result,
uint32_t bound_depth_and_color_render_target_bits, uint32_t bound_depth_and_color_render_target_bits,
const uint32_t* bound_depth_and_color_render_target_formats, const uint32_t* bound_depth_and_color_render_target_formats,
PipelineRuntimeDescription& runtime_description_out) { PipelineRuntimeDescription& runtime_description_out) {
@ -1357,12 +1280,11 @@ bool PipelineCache::GetCurrentStateDescription(
// Initialize all unused fields to zero for comparison/hashing. // Initialize all unused fields to zero for comparison/hashing.
std::memset(&runtime_description_out, 0, sizeof(runtime_description_out)); std::memset(&runtime_description_out, 0, sizeof(runtime_description_out));
bool tessellated = assert_true(DxbcShaderTranslator::Modification(vertex_shader->modification())
DxbcShaderTranslator::Modification(vertex_shader->modification()) .vertex.host_vertex_shader_type ==
.vertex.host_vertex_shader_type != primitive_processing_result.host_vertex_shader_type);
Shader::HostVertexShaderType::kVertex; bool tessellated = primitive_processing_result.IsTessellated();
bool primitive_polygonal = bool primitive_polygonal = draw_util::IsPrimitivePolygonal(regs);
xenos::IsPrimitivePolygonal(tessellated, primitive_type);
bool rasterization_enabled = bool rasterization_enabled =
draw_util::IsRasterizationPotentiallyDone(regs, primitive_polygonal); draw_util::IsRasterizationPotentiallyDone(regs, primitive_polygonal);
// In Direct3D, rasterization (along with pixel counting) is disabled by // In Direct3D, rasterization (along with pixel counting) is disabled by
@ -1397,12 +1319,12 @@ bool PipelineCache::GetCurrentStateDescription(
description_out.vertex_shader_modification = vertex_shader->modification(); description_out.vertex_shader_modification = vertex_shader->modification();
// Index buffer strip cut value. // Index buffer strip cut value.
if (pa_su_sc_mode_cntl.multi_prim_ib_ena) { if (primitive_processing_result.host_primitive_reset_enabled) {
// Not using 0xFFFF with 32-bit indices because in index buffers it will be description_out.strip_cut_index =
// 0xFFFF0000 anyway due to endianness. primitive_processing_result.host_index_format ==
description_out.strip_cut_index = index_format == xenos::IndexFormat::kInt32 xenos::IndexFormat::kInt16
? PipelineStripCutIndex::kFFFFFFFF ? PipelineStripCutIndex::kFFFF
: PipelineStripCutIndex::kFFFF; : PipelineStripCutIndex::kFFFFFFFF;
} else { } else {
description_out.strip_cut_index = PipelineStripCutIndex::kNone; description_out.strip_cut_index = PipelineStripCutIndex::kNone;
} }
@ -1410,16 +1332,15 @@ bool PipelineCache::GetCurrentStateDescription(
// Host vertex shader type and primitive topology. // Host vertex shader type and primitive topology.
if (tessellated) { if (tessellated) {
description_out.primitive_topology_type_or_tessellation_mode = description_out.primitive_topology_type_or_tessellation_mode =
uint32_t(regs.Get<reg::VGT_HOS_CNTL>().tess_mode); uint32_t(primitive_processing_result.tessellation_mode);
} else { } else {
switch (primitive_type) { switch (primitive_processing_result.host_primitive_type) {
case xenos::PrimitiveType::kPointList: case xenos::PrimitiveType::kPointList:
description_out.primitive_topology_type_or_tessellation_mode = description_out.primitive_topology_type_or_tessellation_mode =
uint32_t(PipelinePrimitiveTopologyType::kPoint); uint32_t(PipelinePrimitiveTopologyType::kPoint);
break; break;
case xenos::PrimitiveType::kLineList: case xenos::PrimitiveType::kLineList:
case xenos::PrimitiveType::kLineStrip: case xenos::PrimitiveType::kLineStrip:
case xenos::PrimitiveType::kLineLoop:
// Quads are emulated as line lists with adjacency. // Quads are emulated as line lists with adjacency.
case xenos::PrimitiveType::kQuadList: case xenos::PrimitiveType::kQuadList:
case xenos::PrimitiveType::k2DLineStrip: case xenos::PrimitiveType::k2DLineStrip:
@ -1431,7 +1352,7 @@ bool PipelineCache::GetCurrentStateDescription(
uint32_t(PipelinePrimitiveTopologyType::kTriangle); uint32_t(PipelinePrimitiveTopologyType::kTriangle);
break; break;
} }
switch (primitive_type) { switch (primitive_processing_result.host_primitive_type) {
case xenos::PrimitiveType::kPointList: case xenos::PrimitiveType::kPointList:
description_out.geometry_shader = PipelineGeometryShader::kPointList; description_out.geometry_shader = PipelineGeometryShader::kPointList;
break; break;
@ -1523,7 +1444,7 @@ bool PipelineCache::GetCurrentStateDescription(
poly_offset_scale = regs[XE_GPU_REG_PA_SU_POLY_OFFSET_BACK_SCALE].f32; poly_offset_scale = regs[XE_GPU_REG_PA_SU_POLY_OFFSET_BACK_SCALE].f32;
} }
} }
if (pa_su_sc_mode_cntl.poly_mode == xenos::PolygonModeEnable::kDisabled) { if (pa_su_sc_mode_cntl.poly_mode != xenos::PolygonModeEnable::kDualMode) {
description_out.fill_mode_wireframe = 0; description_out.fill_mode_wireframe = 0;
} }
} else { } else {
@ -1536,33 +1457,18 @@ bool PipelineCache::GetCurrentStateDescription(
} }
} }
if (!edram_rov_used) { if (!edram_rov_used) {
// Conversion based on the calculations in Call of Duty 4 and the values it float poly_offset_host_scale = draw_util::GetD3D10PolygonOffsetFactor(
// writes to the registers, and also on: regs.Get<reg::RB_DEPTH_INFO>().depth_format, true);
// https://github.com/mesa3d/mesa/blob/54ad9b444c8e73da498211870e785239ad3ff1aa/src/gallium/drivers/radeonsi/si_state.c#L943
// Dividing the scale by 2 - Call of Duty 4 sets the constant bias of
// 1/32768 for decals, however, it's done in two steps in separate places:
// first it's divided by 65536, and then it's multiplied by 2 (which is
// consistent with what si_create_rs_state does, which multiplies the offset
// by 2 if it comes from a non-D3D9 API for 24-bit depth buffers) - and
// multiplying by 2 to the number of significand bits. Tested mostly in Call
// of Duty 4 (vehicledamage map explosion decals) and Red Dead Redemption
// (shadows - 2^17 is not enough, 2^18 hasn't been tested, but 2^19
// eliminates the acne).
if (regs.Get<reg::RB_DEPTH_INFO>().depth_format ==
xenos::DepthRenderTargetFormat::kD24FS8) {
poly_offset *= float(1 << 19);
} else {
poly_offset *= float(1 << 23);
}
// Using ceil here just in case a game wants the offset but passes a value // Using ceil here just in case a game wants the offset but passes a value
// that is too small - it's better to apply more offset than to make depth // that is too small - it's better to apply more offset than to make depth
// fighting worse or to disable the offset completely (Direct3D 12 takes an // fighting worse or to disable the offset completely (Direct3D 12 takes an
// integer value). // integer value).
description_out.depth_bias = int32_t(std::ceil(std::abs(poly_offset))) * description_out.depth_bias =
(poly_offset < 0.0f ? -1 : 1); int32_t(std::ceil(std::abs(poly_offset * poly_offset_host_scale))) *
// "slope computed in subpixels (1/12 or 1/16)" - R5xx Acceleration. (poly_offset < 0.0f ? -1 : 1);
// "slope computed in subpixels ([...] 1/16)" - R5xx Acceleration.
description_out.depth_bias_slope_scaled = description_out.depth_bias_slope_scaled =
poly_offset_scale * (1.0f / 16.0f); poly_offset_scale * xenos::kPolygonOffsetScaleSubpixelUnit;
} }
if (tessellated && cvars::d3d12_tessellation_wireframe) { if (tessellated && cvars::d3d12_tessellation_wireframe) {
description_out.fill_mode_wireframe = 1; description_out.fill_mode_wireframe = 1;
@ -1827,16 +1733,17 @@ ID3D12PipelineState* PipelineCache::CreateD3D12Pipeline(
} }
switch (description.geometry_shader) { switch (description.geometry_shader) {
case PipelineGeometryShader::kPointList: case PipelineGeometryShader::kPointList:
state_desc.GS.pShaderBytecode = primitive_point_list_gs; state_desc.GS.pShaderBytecode = shaders::primitive_point_list_gs;
state_desc.GS.BytecodeLength = sizeof(primitive_point_list_gs); state_desc.GS.BytecodeLength = sizeof(shaders::primitive_point_list_gs);
break; break;
case PipelineGeometryShader::kRectangleList: case PipelineGeometryShader::kRectangleList:
state_desc.GS.pShaderBytecode = primitive_rectangle_list_gs; state_desc.GS.pShaderBytecode = shaders::primitive_rectangle_list_gs;
state_desc.GS.BytecodeLength = sizeof(primitive_rectangle_list_gs); state_desc.GS.BytecodeLength =
sizeof(shaders::primitive_rectangle_list_gs);
break; break;
case PipelineGeometryShader::kQuadList: case PipelineGeometryShader::kQuadList:
state_desc.GS.pShaderBytecode = primitive_quad_list_gs; state_desc.GS.pShaderBytecode = shaders::primitive_quad_list_gs;
state_desc.GS.BytecodeLength = sizeof(primitive_quad_list_gs); state_desc.GS.BytecodeLength = sizeof(shaders::primitive_quad_list_gs);
break; break;
default: default:
break; break;
@ -1846,24 +1753,25 @@ ID3D12PipelineState* PipelineCache::CreateD3D12Pipeline(
xenos::TessellationMode tessellation_mode = xenos::TessellationMode( xenos::TessellationMode tessellation_mode = xenos::TessellationMode(
description.primitive_topology_type_or_tessellation_mode); description.primitive_topology_type_or_tessellation_mode);
if (tessellation_mode == xenos::TessellationMode::kAdaptive) { if (tessellation_mode == xenos::TessellationMode::kAdaptive) {
state_desc.VS.pShaderBytecode = tessellation_adaptive_vs; state_desc.VS.pShaderBytecode = shaders::tessellation_adaptive_vs;
state_desc.VS.BytecodeLength = sizeof(tessellation_adaptive_vs); state_desc.VS.BytecodeLength = sizeof(shaders::tessellation_adaptive_vs);
} else { } else {
state_desc.VS.pShaderBytecode = tessellation_indexed_vs; state_desc.VS.pShaderBytecode = shaders::tessellation_indexed_vs;
state_desc.VS.BytecodeLength = sizeof(tessellation_indexed_vs); state_desc.VS.BytecodeLength = sizeof(shaders::tessellation_indexed_vs);
} }
switch (tessellation_mode) { switch (tessellation_mode) {
case xenos::TessellationMode::kDiscrete: case xenos::TessellationMode::kDiscrete:
switch (host_vertex_shader_type) { switch (host_vertex_shader_type) {
case Shader::HostVertexShaderType::kTriangleDomainCPIndexed: case Shader::HostVertexShaderType::kTriangleDomainCPIndexed:
case Shader::HostVertexShaderType::kTriangleDomainPatchIndexed: case Shader::HostVertexShaderType::kTriangleDomainPatchIndexed:
state_desc.HS.pShaderBytecode = discrete_triangle_hs; state_desc.HS.pShaderBytecode = shaders::discrete_triangle_hs;
state_desc.HS.BytecodeLength = sizeof(discrete_triangle_hs); state_desc.HS.BytecodeLength =
sizeof(shaders::discrete_triangle_hs);
break; break;
case Shader::HostVertexShaderType::kQuadDomainCPIndexed: case Shader::HostVertexShaderType::kQuadDomainCPIndexed:
case Shader::HostVertexShaderType::kQuadDomainPatchIndexed: case Shader::HostVertexShaderType::kQuadDomainPatchIndexed:
state_desc.HS.pShaderBytecode = discrete_quad_hs; state_desc.HS.pShaderBytecode = shaders::discrete_quad_hs;
state_desc.HS.BytecodeLength = sizeof(discrete_quad_hs); state_desc.HS.BytecodeLength = sizeof(shaders::discrete_quad_hs);
break; break;
default: default:
assert_unhandled_case(host_vertex_shader_type); assert_unhandled_case(host_vertex_shader_type);
@ -1874,13 +1782,14 @@ ID3D12PipelineState* PipelineCache::CreateD3D12Pipeline(
switch (host_vertex_shader_type) { switch (host_vertex_shader_type) {
case Shader::HostVertexShaderType::kTriangleDomainCPIndexed: case Shader::HostVertexShaderType::kTriangleDomainCPIndexed:
case Shader::HostVertexShaderType::kTriangleDomainPatchIndexed: case Shader::HostVertexShaderType::kTriangleDomainPatchIndexed:
state_desc.HS.pShaderBytecode = continuous_triangle_hs; state_desc.HS.pShaderBytecode = shaders::continuous_triangle_hs;
state_desc.HS.BytecodeLength = sizeof(continuous_triangle_hs); state_desc.HS.BytecodeLength =
sizeof(shaders::continuous_triangle_hs);
break; break;
case Shader::HostVertexShaderType::kQuadDomainCPIndexed: case Shader::HostVertexShaderType::kQuadDomainCPIndexed:
case Shader::HostVertexShaderType::kQuadDomainPatchIndexed: case Shader::HostVertexShaderType::kQuadDomainPatchIndexed:
state_desc.HS.pShaderBytecode = continuous_quad_hs; state_desc.HS.pShaderBytecode = shaders::continuous_quad_hs;
state_desc.HS.BytecodeLength = sizeof(continuous_quad_hs); state_desc.HS.BytecodeLength = sizeof(shaders::continuous_quad_hs);
break; break;
default: default:
assert_unhandled_case(host_vertex_shader_type); assert_unhandled_case(host_vertex_shader_type);
@ -1890,12 +1799,13 @@ ID3D12PipelineState* PipelineCache::CreateD3D12Pipeline(
case xenos::TessellationMode::kAdaptive: case xenos::TessellationMode::kAdaptive:
switch (host_vertex_shader_type) { switch (host_vertex_shader_type) {
case Shader::HostVertexShaderType::kTriangleDomainPatchIndexed: case Shader::HostVertexShaderType::kTriangleDomainPatchIndexed:
state_desc.HS.pShaderBytecode = adaptive_triangle_hs; state_desc.HS.pShaderBytecode = shaders::adaptive_triangle_hs;
state_desc.HS.BytecodeLength = sizeof(adaptive_triangle_hs); state_desc.HS.BytecodeLength =
sizeof(shaders::adaptive_triangle_hs);
break; break;
case Shader::HostVertexShaderType::kQuadDomainPatchIndexed: case Shader::HostVertexShaderType::kQuadDomainPatchIndexed:
state_desc.HS.pShaderBytecode = adaptive_quad_hs; state_desc.HS.pShaderBytecode = shaders::adaptive_quad_hs;
state_desc.HS.BytecodeLength = sizeof(adaptive_quad_hs); state_desc.HS.BytecodeLength = sizeof(shaders::adaptive_quad_hs);
break; break;
default: default:
assert_unhandled_case(host_vertex_shader_type); assert_unhandled_case(host_vertex_shader_type);
@ -1933,12 +1843,12 @@ ID3D12PipelineState* PipelineCache::CreateD3D12Pipeline(
description.depth_format == xenos::DepthRenderTargetFormat::kD24FS8) { description.depth_format == xenos::DepthRenderTargetFormat::kD24FS8) {
switch (render_target_cache_.depth_float24_conversion()) { switch (render_target_cache_.depth_float24_conversion()) {
case RenderTargetCache::DepthFloat24Conversion::kOnOutputTruncating: case RenderTargetCache::DepthFloat24Conversion::kOnOutputTruncating:
state_desc.PS.pShaderBytecode = float24_truncate_ps; state_desc.PS.pShaderBytecode = shaders::float24_truncate_ps;
state_desc.PS.BytecodeLength = sizeof(float24_truncate_ps); state_desc.PS.BytecodeLength = sizeof(shaders::float24_truncate_ps);
break; break;
case RenderTargetCache::DepthFloat24Conversion::kOnOutputRounding: case RenderTargetCache::DepthFloat24Conversion::kOnOutputRounding:
state_desc.PS.pShaderBytecode = float24_round_ps; state_desc.PS.pShaderBytecode = shaders::float24_round_ps;
state_desc.PS.BytecodeLength = sizeof(float24_round_ps); state_desc.PS.BytecodeLength = sizeof(shaders::float24_round_ps);
break; break;
default: default:
break; break;

View File

@ -29,6 +29,7 @@
#include "xenia/gpu/d3d12/d3d12_shader.h" #include "xenia/gpu/d3d12/d3d12_shader.h"
#include "xenia/gpu/dxbc_shader_translator.h" #include "xenia/gpu/dxbc_shader_translator.h"
#include "xenia/gpu/gpu_flags.h" #include "xenia/gpu/gpu_flags.h"
#include "xenia/gpu/primitive_processor.h"
#include "xenia/gpu/register_file.h" #include "xenia/gpu/register_file.h"
#include "xenia/gpu/xenos.h" #include "xenia/gpu/xenos.h"
#include "xenia/ui/d3d12/d3d12_api.h" #include "xenia/ui/d3d12/d3d12_api.h"
@ -67,18 +68,21 @@ class PipelineCache {
shader.AnalyzeUcode(ucode_disasm_buffer_); shader.AnalyzeUcode(ucode_disasm_buffer_);
} }
// Retrieves the shader modification for the current state, and returns // Retrieves the shader modification for the current state. The shader must
// whether it is valid. The shader must have microcode analyzed. // have microcode analyzed.
bool PipelineCache::GetCurrentShaderModification( DxbcShaderTranslator::Modification
PipelineCache::GetCurrentVertexShaderModification(
const Shader& shader, const Shader& shader,
DxbcShaderTranslator::Modification& modification_out) const; Shader::HostVertexShaderType host_vertex_shader_type) const;
DxbcShaderTranslator::Modification
PipelineCache::GetCurrentPixelShaderModification(const Shader& shader) const;
// If draw_util::IsRasterizationPotentiallyDone is false, the pixel shader // If draw_util::IsRasterizationPotentiallyDone is false, the pixel shader
// MUST be made nullptr BEFORE calling this! // MUST be made nullptr BEFORE calling this!
bool ConfigurePipeline( bool ConfigurePipeline(
D3D12Shader::D3D12Translation* vertex_shader, D3D12Shader::D3D12Translation* vertex_shader,
D3D12Shader::D3D12Translation* pixel_shader, D3D12Shader::D3D12Translation* pixel_shader,
xenos::PrimitiveType primitive_type, xenos::IndexFormat index_format, const PrimitiveProcessor::ProcessingResult& primitive_processing_result,
uint32_t bound_depth_and_color_render_target_bits, uint32_t bound_depth_and_color_render_target_bits,
const uint32_t* bound_depth_and_color_render_targets_formats, const uint32_t* bound_depth_and_color_render_targets_formats,
void** pipeline_handle_out, ID3D12RootSignature** root_signature_out); void** pipeline_handle_out, ID3D12RootSignature** root_signature_out);
@ -226,10 +230,6 @@ class PipelineCache {
PipelineDescription description; PipelineDescription description;
}; };
// Returns the host vertex shader type for the current draw if it's valid and
// supported, or Shader::HostVertexShaderType(-1) if not.
Shader::HostVertexShaderType GetCurrentHostVertexShaderTypeIfValid() const;
D3D12Shader* LoadShader(xenos::ShaderType shader_type, D3D12Shader* LoadShader(xenos::ShaderType shader_type,
const uint32_t* host_address, uint32_t dword_count, const uint32_t* host_address, uint32_t dword_count,
uint64_t data_hash); uint64_t data_hash);
@ -247,7 +247,7 @@ class PipelineCache {
bool GetCurrentStateDescription( bool GetCurrentStateDescription(
D3D12Shader::D3D12Translation* vertex_shader, D3D12Shader::D3D12Translation* vertex_shader,
D3D12Shader::D3D12Translation* pixel_shader, D3D12Shader::D3D12Translation* pixel_shader,
xenos::PrimitiveType primitive_type, xenos::IndexFormat index_format, const PrimitiveProcessor::ProcessingResult& primitive_processing_result,
uint32_t bound_depth_and_color_render_target_bits, uint32_t bound_depth_and_color_render_target_bits,
const uint32_t* bound_depth_and_color_render_target_formats, const uint32_t* bound_depth_and_color_render_target_formats,
PipelineRuntimeDescription& runtime_description_out); PipelineRuntimeDescription& runtime_description_out);

View File

@ -16,7 +16,7 @@ project("xenia-gpu-d3d12")
}) })
local_platform_files() local_platform_files()
files({ files({
"shaders/bin/*.h", "../shaders/bytecode/d3d12_5_1/*.h",
}) })
group("src") group("src")

View File

@ -1,762 +0,0 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2018 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/gpu/d3d12/primitive_converter.h"
#include <algorithm>
#include "xenia/base/assert.h"
#include "xenia/base/cvar.h"
#include "xenia/base/logging.h"
#include "xenia/base/math.h"
#include "xenia/base/memory.h"
#include "xenia/base/platform.h"
#include "xenia/base/profiling.h"
#include "xenia/gpu/d3d12/d3d12_command_processor.h"
#include "xenia/ui/d3d12/d3d12_util.h"
DEFINE_bool(d3d12_convert_quads_to_triangles, false,
"Convert quad lists to triangle lists on the CPU instead of using "
"a geometry shader. Not recommended for playing, for debugging "
"primarily (because PIX fails to display vertices when a geometry "
"shader is used), and this way quads can't be discarded correctly "
"when the game uses vertex kill functionality.",
"D3D12");
namespace xe {
namespace gpu {
namespace d3d12 {
PrimitiveConverter::PrimitiveConverter(D3D12CommandProcessor& command_processor,
const RegisterFile& register_file,
Memory& memory,
TraceWriter& trace_writer)
: command_processor_(command_processor),
register_file_(register_file),
memory_(memory),
trace_writer_(trace_writer) {
system_page_size_ = uint32_t(memory::page_size());
}
PrimitiveConverter::~PrimitiveConverter() { Shutdown(); }
bool PrimitiveConverter::Initialize() {
auto& provider = command_processor_.GetD3D12Context().GetD3D12Provider();
auto device = provider.GetDevice();
D3D12_HEAP_FLAGS heap_flag_create_not_zeroed =
provider.GetHeapFlagCreateNotZeroed();
// There can be at most 65535 indices in a Xenos draw call (16 bit index
// count), but they can be up to 4 bytes large, and conversion can add more
// indices (almost triple the count for triangle strips or fans, for
// instance).
buffer_pool_ = std::make_unique<ui::d3d12::D3D12UploadBufferPool>(
provider, std::max(sizeof(uint32_t) * 3 * 65535,
ui::d3d12::D3D12UploadBufferPool::kDefaultPageSize));
// Create the static index buffer for non-indexed drawing.
D3D12_RESOURCE_DESC static_ib_desc;
ui::d3d12::util::FillBufferResourceDesc(
static_ib_desc, kStaticIBTotalCount * sizeof(uint16_t),
D3D12_RESOURCE_FLAG_NONE);
if (FAILED(device->CreateCommittedResource(
&ui::d3d12::util::kHeapPropertiesUpload, heap_flag_create_not_zeroed,
&static_ib_desc, D3D12_RESOURCE_STATE_GENERIC_READ, nullptr,
IID_PPV_ARGS(&static_ib_upload_)))) {
XELOGE(
"Failed to create the upload buffer for the primitive conversion "
"static index buffer");
Shutdown();
return false;
}
D3D12_RANGE static_ib_read_range;
static_ib_read_range.Begin = 0;
static_ib_read_range.End = 0;
void* static_ib_mapping;
if (FAILED(static_ib_upload_->Map(0, &static_ib_read_range,
&static_ib_mapping))) {
XELOGE(
"Failed to map the upload buffer for the primitive conversion "
"static index buffer");
Shutdown();
return false;
}
uint16_t* static_ib_data = reinterpret_cast<uint16_t*>(static_ib_mapping);
// Triangle fans as triangle lists.
// https://docs.microsoft.com/en-us/windows/desktop/direct3d9/triangle-fans
// Ordered as (v1, v2, v0), (v2, v3, v0).
uint16_t* static_ib_data_pointer =
&static_ib_data[kStaticIBTriangleFanOffset];
for (uint32_t i = 2; i < kMaxNonIndexedVertices; ++i) {
*(static_ib_data_pointer++) = i - 1;
*(static_ib_data_pointer++) = i;
*(static_ib_data_pointer++) = 0;
}
static_ib_data_pointer = &static_ib_data[kStaticIBQuadOffset];
for (uint32_t i = 0; i < (kMaxNonIndexedVertices >> 2); ++i) {
uint32_t quad_index = i << 2;
*(static_ib_data_pointer++) = quad_index;
*(static_ib_data_pointer++) = quad_index + 1;
*(static_ib_data_pointer++) = quad_index + 2;
*(static_ib_data_pointer++) = quad_index;
*(static_ib_data_pointer++) = quad_index + 2;
*(static_ib_data_pointer++) = quad_index + 3;
}
static_ib_upload_->Unmap(0, nullptr);
// Not uploaded yet.
static_ib_upload_submission_ = UINT64_MAX;
if (FAILED(device->CreateCommittedResource(
&ui::d3d12::util::kHeapPropertiesDefault, heap_flag_create_not_zeroed,
&static_ib_desc, D3D12_RESOURCE_STATE_COPY_DEST, nullptr,
IID_PPV_ARGS(&static_ib_)))) {
XELOGE("Failed to create the primitive conversion static index buffer");
Shutdown();
return false;
}
static_ib_gpu_address_ = static_ib_->GetGPUVirtualAddress();
memory_regions_invalidated_.store(0ull, std::memory_order_relaxed);
memory_invalidation_callback_handle_ =
memory_.RegisterPhysicalMemoryInvalidationCallback(
MemoryInvalidationCallbackThunk, this);
return true;
}
void PrimitiveConverter::Shutdown() {
if (memory_invalidation_callback_handle_ != nullptr) {
memory_.UnregisterPhysicalMemoryInvalidationCallback(
memory_invalidation_callback_handle_);
memory_invalidation_callback_handle_ = nullptr;
}
ui::d3d12::util::ReleaseAndNull(static_ib_);
ui::d3d12::util::ReleaseAndNull(static_ib_upload_);
buffer_pool_.reset();
}
void PrimitiveConverter::ClearCache() { buffer_pool_->ClearCache(); }
void PrimitiveConverter::CompletedSubmissionUpdated() {
if (static_ib_upload_ && command_processor_.GetCompletedSubmission() >=
static_ib_upload_submission_) {
// Completely uploaded - release the upload buffer.
static_ib_upload_->Release();
static_ib_upload_ = nullptr;
}
}
void PrimitiveConverter::BeginSubmission() {
// Got a command list now - upload and transition the static index buffer if
// needed.
if (static_ib_upload_ && static_ib_upload_submission_ == UINT64_MAX) {
command_processor_.GetDeferredCommandList().D3DCopyResource(
static_ib_, static_ib_upload_);
command_processor_.PushTransitionBarrier(static_ib_,
D3D12_RESOURCE_STATE_COPY_DEST,
D3D12_RESOURCE_STATE_INDEX_BUFFER);
static_ib_upload_submission_ = command_processor_.GetCurrentSubmission();
}
}
void PrimitiveConverter::BeginFrame() {
buffer_pool_->Reclaim(command_processor_.GetCompletedFrame());
converted_indices_cache_.clear();
memory_regions_used_ = 0;
}
xenos::PrimitiveType PrimitiveConverter::GetReplacementPrimitiveType(
xenos::PrimitiveType type) {
switch (type) {
case xenos::PrimitiveType::kTriangleFan:
return xenos::PrimitiveType::kTriangleList;
case xenos::PrimitiveType::kLineLoop:
return xenos::PrimitiveType::kLineStrip;
case xenos::PrimitiveType::kQuadList:
if (cvars::d3d12_convert_quads_to_triangles) {
return xenos::PrimitiveType::kTriangleList;
}
break;
default:
break;
}
return type;
}
PrimitiveConverter::ConversionResult PrimitiveConverter::ConvertPrimitives(
xenos::PrimitiveType source_type, uint32_t address, uint32_t index_count,
xenos::IndexFormat index_format, xenos::Endian index_endianness,
D3D12_GPU_VIRTUAL_ADDRESS& gpu_address_out, uint32_t& index_count_out) {
bool index_32bit = index_format == xenos::IndexFormat::kInt32;
const auto& regs = register_file_;
bool reset = regs.Get<reg::PA_SU_SC_MODE_CNTL>().multi_prim_ib_ena;
// Swap the reset index because we will be comparing unswapped values to it.
uint32_t reset_index = xenos::GpuSwap(
regs[XE_GPU_REG_VGT_MULTI_PRIM_IB_RESET_INDX].u32, index_endianness);
// If the specified reset index is the same as the one used by Direct3D 12
// (0xFFFF or 0xFFFFFFFF - in the pipeline cache, we use the former for
// 16-bit and the latter for 32-bit indices), we can use the buffer directly.
uint32_t reset_index_host = index_32bit ? 0xFFFFFFFFu : 0xFFFFu;
// Degenerate line loops are just lines.
if (source_type == xenos::PrimitiveType::kLineLoop && index_count <= 2) {
source_type = xenos::PrimitiveType::kLineStrip;
}
// Check if need to convert at all.
if (source_type == xenos::PrimitiveType::kTriangleStrip ||
source_type == xenos::PrimitiveType::kLineStrip) {
if (!reset || reset_index == reset_index_host) {
return ConversionResult::kConversionNotNeeded;
}
} else if (source_type == xenos::PrimitiveType::kQuadList) {
if (!cvars::d3d12_convert_quads_to_triangles) {
return ConversionResult::kConversionNotNeeded;
}
} else if (source_type != xenos::PrimitiveType::kTriangleFan &&
source_type != xenos::PrimitiveType::kLineLoop) {
return ConversionResult::kConversionNotNeeded;
}
#if XE_UI_D3D12_FINE_GRAINED_DRAW_SCOPES
SCOPE_profile_cpu_f("gpu");
#endif // XE_UI_D3D12_FINE_GRAINED_DRAW_SCOPES
// Exit early for clearly empty draws, without even reading the memory.
uint32_t index_count_min;
if (source_type == xenos::PrimitiveType::kLineStrip ||
source_type == xenos::PrimitiveType::kLineLoop) {
index_count_min = 2;
} else if (source_type == xenos::PrimitiveType::kQuadList) {
index_count_min = 4;
} else {
index_count_min = 3;
}
if (index_count < index_count_min) {
return ConversionResult::kPrimitiveEmpty;
}
// Invalidate the cache if data behind any entry was modified.
if (memory_regions_invalidated_.exchange(0ull, std::memory_order_acquire) &
memory_regions_used_) {
converted_indices_cache_.clear();
memory_regions_used_ = 0;
}
address &= index_32bit ? 0x1FFFFFFC : 0x1FFFFFFE;
uint32_t index_size = index_32bit ? sizeof(uint32_t) : sizeof(uint16_t);
uint32_t index_buffer_size = index_size * index_count;
uint32_t address_last = address + index_size * (index_count - 1);
// Create the cache entry, currently only for the key.
ConvertedIndices converted_indices;
converted_indices.key.address = address;
converted_indices.key.source_type = source_type;
converted_indices.key.format = index_format;
converted_indices.key.count = index_count;
converted_indices.key.reset = reset ? 1 : 0;
converted_indices.reset_index = reset_index;
// Try to find the previously converted index buffer.
auto found_range =
converted_indices_cache_.equal_range(converted_indices.key.value);
for (auto iter = found_range.first; iter != found_range.second; ++iter) {
const ConvertedIndices& found_converted = iter->second;
if (reset && found_converted.reset_index != reset_index) {
continue;
}
if (found_converted.converted_index_count == 0) {
return ConversionResult::kPrimitiveEmpty;
}
if (!found_converted.gpu_address) {
return ConversionResult::kConversionNotNeeded;
}
gpu_address_out = found_converted.gpu_address;
index_count_out = found_converted.converted_index_count;
return ConversionResult::kConverted;
}
// Get the memory usage mask for cache invalidation.
// 1 bit = (512 / 64) MB = 8 MB.
uint64_t memory_regions_used_bits = ~((1ull << (address >> 23)) - 1);
if (address_last < (63 << 23)) {
memory_regions_used_bits = (1ull << ((address_last >> 23) + 1)) - 1;
}
union {
const void* source;
const uint8_t* source_8;
const uint16_t* source_16;
const uint32_t* source_32;
uintptr_t source_uintptr;
};
source = memory_.TranslatePhysical(address);
// Calculate the new index count, and also check if there's nothing to convert
// in the buffer (for instance, if not using actually primitive reset).
uint32_t converted_index_count = 0;
bool conversion_needed = false;
bool simd = false;
// Optimization specific to primitive types - if reset index not found in the
// source index buffer, can set this to false and use a faster way of copying.
bool reset_actually_used = reset;
if (source_type == xenos::PrimitiveType::kTriangleFan) {
// Triangle fans are not supported by Direct3D 12 at all.
conversion_needed = true;
trace_writer_.WriteMemoryRead(address, index_buffer_size);
if (reset) {
uint32_t current_fan_index_count = 0;
for (uint32_t i = 0; i < index_count; ++i) {
uint32_t index = index_format == xenos::IndexFormat::kInt32
? source_32[i]
: source_16[i];
if (index == reset_index) {
current_fan_index_count = 0;
continue;
}
if (++current_fan_index_count >= 3) {
converted_index_count += 3;
}
}
} else {
converted_index_count = 3 * (index_count - 2);
}
} else if (source_type == xenos::PrimitiveType::kTriangleStrip ||
source_type == xenos::PrimitiveType::kLineStrip) {
converted_index_count = index_count;
// Check if the restart index is used at all in this buffer because reading
// vertices from a default heap is faster than from an upload heap.
conversion_needed = false;
trace_writer_.WriteMemoryRead(address, index_buffer_size);
#if XE_ARCH_AMD64
// Will use SIMD to copy 16-byte blocks using _mm_or_si128.
simd = true;
union {
const void* check_source;
const uint32_t* check_source_16;
const uint32_t* check_source_32;
const __m128i* check_source_128;
uintptr_t check_source_uintptr;
};
check_source = source;
uint32_t check_indices_remaining = index_count;
alignas(16) uint64_t check_result[2];
if (index_format == xenos::IndexFormat::kInt32) {
while (check_indices_remaining != 0 && (check_source_uintptr & 15)) {
--check_indices_remaining;
if (*(check_source_32++) == reset_index) {
conversion_needed = true;
check_indices_remaining = 0;
}
}
__m128i check_reset_index_vector = _mm_set1_epi32(reset_index);
while (check_indices_remaining >= 4) {
check_indices_remaining -= 4;
_mm_store_si128(reinterpret_cast<__m128i*>(&check_result),
_mm_cmpeq_epi32(_mm_load_si128(check_source_128++),
check_reset_index_vector));
if (check_result[0] || check_result[1]) {
conversion_needed = true;
check_indices_remaining = 0;
}
}
while (check_indices_remaining != 0) {
--check_indices_remaining;
if (*(check_source_32++) == reset_index) {
conversion_needed = true;
check_indices_remaining = 0;
}
}
} else {
while (check_indices_remaining != 0 && (check_source_uintptr & 15)) {
--check_indices_remaining;
if (*(check_source_16++) == reset_index) {
conversion_needed = true;
check_indices_remaining = 0;
}
}
__m128i check_reset_index_vector = _mm_set1_epi16(reset_index);
while (check_indices_remaining >= 8) {
check_indices_remaining -= 8;
_mm_store_si128(reinterpret_cast<__m128i*>(&check_result),
_mm_cmpeq_epi16(_mm_load_si128(check_source_128++),
check_reset_index_vector));
if (check_result[0] || check_result[1]) {
conversion_needed = true;
check_indices_remaining = 0;
}
}
while (check_indices_remaining != 0) {
--check_indices_remaining;
if (*(check_source_16++) == reset_index) {
conversion_needed = true;
check_indices_remaining = 0;
}
}
}
#else
if (index_format == xenos::IndexFormat::kInt32) {
for (uint32_t i = 0; i < index_count; ++i) {
if (source_32[i] == reset_index) {
conversion_needed = true;
break;
}
}
} else {
for (uint32_t i = 0; i < index_count; ++i) {
if (source_16[i] == reset_index) {
conversion_needed = true;
break;
}
}
}
#endif // XE_ARCH_AMD64
} else if (source_type == xenos::PrimitiveType::kLineLoop) {
conversion_needed = true;
trace_writer_.WriteMemoryRead(address, index_buffer_size);
if (reset) {
reset_actually_used = false;
uint32_t current_strip_index_count = 0;
for (uint32_t i = 0; i < index_count; ++i) {
uint32_t index = index_format == xenos::IndexFormat::kInt32
? source_32[i]
: source_16[i];
if (index == reset_index) {
reset_actually_used = true;
// Loop strips with more than 2 vertices.
if (current_strip_index_count > 2) {
++converted_index_count;
}
current_strip_index_count = 0;
continue;
}
// Start a new strip if 2 vertices, add one vertex if more.
if (++current_strip_index_count >= 2) {
converted_index_count += current_strip_index_count == 2 ? 2 : 1;
}
}
} else {
converted_index_count = index_count + 1;
}
} else if (source_type == xenos::PrimitiveType::kQuadList) {
conversion_needed = true;
trace_writer_.WriteMemoryRead(address, index_buffer_size);
converted_index_count = (index_count >> 2) * 6;
}
converted_indices.converted_index_count = converted_index_count;
// If nothing to convert, store this result so the check won't be happening
// again and again and exit.
if (!conversion_needed || converted_index_count == 0) {
converted_indices.gpu_address = 0;
converted_indices_cache_.emplace(converted_indices.key.value,
converted_indices);
memory_regions_used_ |= memory_regions_used_bits;
return converted_index_count == 0 ? ConversionResult::kPrimitiveEmpty
: ConversionResult::kConversionNotNeeded;
}
// Convert.
D3D12_GPU_VIRTUAL_ADDRESS gpu_address;
void* target = AllocateIndices(index_format, converted_index_count,
simd ? address & 15 : 0, gpu_address);
if (target == nullptr) {
return ConversionResult::kFailed;
}
if (source_type == xenos::PrimitiveType::kTriangleFan) {
// https://docs.microsoft.com/en-us/windows/desktop/direct3d9/triangle-fans
// Ordered as (v1, v2, v0), (v2, v3, v0).
if (reset) {
uint32_t current_fan_index_count = 0;
uint32_t current_fan_first_index = 0;
if (index_format == xenos::IndexFormat::kInt32) {
uint32_t* target_32 = reinterpret_cast<uint32_t*>(target);
for (uint32_t i = 0; i < index_count; ++i) {
uint32_t index = source_32[i];
if (index == reset_index) {
current_fan_index_count = 0;
continue;
}
if (current_fan_index_count == 0) {
current_fan_first_index = index;
}
if (++current_fan_index_count >= 3) {
*(target_32++) = source_32[i - 1];
*(target_32++) = index;
*(target_32++) = current_fan_first_index;
}
}
} else {
uint16_t* target_16 = reinterpret_cast<uint16_t*>(target);
for (uint32_t i = 0; i < index_count; ++i) {
uint16_t index = source_16[i];
if (index == reset_index) {
current_fan_index_count = 0;
continue;
}
if (current_fan_index_count == 0) {
current_fan_first_index = index;
}
if (++current_fan_index_count >= 3) {
*(target_16++) = source_16[i - 1];
*(target_16++) = index;
*(target_16++) = uint16_t(current_fan_first_index);
}
}
}
} else {
if (index_format == xenos::IndexFormat::kInt32) {
uint32_t* target_32 = reinterpret_cast<uint32_t*>(target);
for (uint32_t i = 2; i < index_count; ++i) {
*(target_32++) = source_32[i - 1];
*(target_32++) = source_32[i];
*(target_32++) = source_32[0];
}
} else {
uint16_t* target_16 = reinterpret_cast<uint16_t*>(target);
for (uint32_t i = 2; i < index_count; ++i) {
*(target_16++) = source_16[i - 1];
*(target_16++) = source_16[i];
*(target_16++) = source_16[0];
}
}
}
} else if (source_type == xenos::PrimitiveType::kTriangleStrip ||
source_type == xenos::PrimitiveType::kLineStrip) {
#if XE_ARCH_AMD64
// Replace the reset index with the maximum representable value - vector OR
// gives 0 or 0xFFFF/0xFFFFFFFF, which is exactly what is needed.
// Allocations in the target index buffer are aligned with 16-byte
// granularity, and within 16-byte vectors, both the source and the target
// start at the same offset.
union {
const __m128i* source_aligned_128;
uintptr_t source_aligned_uintptr;
};
source_aligned_uintptr = source_uintptr & ~(uintptr_t(15));
union {
__m128i* target_aligned_128;
uintptr_t target_aligned_uintptr;
};
target_aligned_uintptr =
reinterpret_cast<uintptr_t>(target) & ~(uintptr_t(15));
uint32_t vector_count = (address_last >> 4) - (address >> 4) + 1;
if (index_format == xenos::IndexFormat::kInt32) {
__m128i reset_index_vector = _mm_set1_epi32(reset_index);
for (uint32_t i = 0; i < vector_count; ++i) {
__m128i indices_vector = _mm_load_si128(source_aligned_128++);
__m128i indices_are_reset_vector =
_mm_cmpeq_epi32(indices_vector, reset_index_vector);
_mm_store_si128(target_aligned_128++,
_mm_or_si128(indices_vector, indices_are_reset_vector));
}
} else {
__m128i reset_index_vector = _mm_set1_epi16(reset_index);
for (uint32_t i = 0; i < vector_count; ++i) {
__m128i indices_vector = _mm_load_si128(source_aligned_128++);
__m128i indices_are_reset_vector =
_mm_cmpeq_epi16(indices_vector, reset_index_vector);
_mm_store_si128(target_aligned_128++,
_mm_or_si128(indices_vector, indices_are_reset_vector));
}
}
#else
if (index_format == xenos::IndexFormat::kInt32) {
for (uint32_t i = 0; i < index_count; ++i) {
uint32_t index = source_32[i];
reinterpret_cast<uint32_t*>(target)[i] =
index == reset_index ? 0xFFFFFFFFu : index;
}
} else {
for (uint32_t i = 0; i < index_count; ++i) {
uint16_t index = source_16[i];
reinterpret_cast<uint16_t*>(target)[i] =
index == reset_index ? 0xFFFFu : index;
}
}
#endif // XE_ARCH_AMD64
} else if (source_type == xenos::PrimitiveType::kLineLoop) {
if (reset_actually_used) {
uint32_t current_strip_index_count = 0;
uint32_t current_strip_first_index = 0;
if (index_format == xenos::IndexFormat::kInt32) {
uint32_t* target_32 = reinterpret_cast<uint32_t*>(target);
for (uint32_t i = 0; i < index_count; ++i) {
uint32_t index = source_32[i];
if (index == reset_index) {
if (current_strip_index_count > 2) {
*(target_32++) = current_strip_first_index;
}
current_strip_index_count = 0;
continue;
}
if (current_strip_index_count == 0) {
current_strip_first_index = index;
}
++current_strip_index_count;
if (current_strip_index_count >= 2) {
if (current_strip_index_count == 2) {
*(target_32++) = current_strip_first_index;
}
*(target_32++) = index;
}
}
} else {
uint16_t* target_16 = reinterpret_cast<uint16_t*>(target);
for (uint32_t i = 0; i < index_count; ++i) {
uint16_t index = source_16[i];
if (index == reset_index) {
if (current_strip_index_count > 2) {
*(target_16++) = uint16_t(current_strip_first_index);
}
current_strip_index_count = 0;
continue;
}
if (current_strip_index_count == 0) {
current_strip_first_index = index;
}
++current_strip_index_count;
if (current_strip_index_count >= 2) {
if (current_strip_index_count == 2) {
*(target_16++) = uint16_t(current_strip_first_index);
}
*(target_16++) = index;
}
}
}
} else {
std::memcpy(target, source, index_count * index_size);
if (converted_index_count > index_count) {
if (index_format == xenos::IndexFormat::kInt32) {
reinterpret_cast<uint32_t*>(target)[index_count] = source_32[0];
} else {
reinterpret_cast<uint16_t*>(target)[index_count] = source_16[0];
}
}
}
} else if (source_type == xenos::PrimitiveType::kQuadList) {
uint32_t quad_count = index_count >> 4;
if (index_format == xenos::IndexFormat::kInt32) {
uint32_t* target_32 = reinterpret_cast<uint32_t*>(target);
for (uint32_t i = 0; i < quad_count; ++i) {
uint32_t quad_index = i << 2;
*(target_32++) = source_32[quad_index];
*(target_32++) = source_32[quad_index + 1];
*(target_32++) = source_32[quad_index + 2];
*(target_32++) = source_32[quad_index];
*(target_32++) = source_32[quad_index + 2];
*(target_32++) = source_32[quad_index + 3];
}
} else {
uint16_t* target_16 = reinterpret_cast<uint16_t*>(target);
for (uint32_t i = 0; i < quad_count; ++i) {
uint32_t quad_index = i << 2;
*(target_16++) = source_16[quad_index];
*(target_16++) = source_16[quad_index + 1];
*(target_16++) = source_16[quad_index + 2];
*(target_16++) = source_16[quad_index];
*(target_16++) = source_16[quad_index + 2];
*(target_16++) = source_16[quad_index + 3];
}
}
}
// Cache and return the indices.
converted_indices.gpu_address = gpu_address;
converted_indices_cache_.emplace(converted_indices.key.value,
converted_indices);
memory_regions_used_ |= memory_regions_used_bits;
gpu_address_out = gpu_address;
index_count_out = converted_index_count;
return ConversionResult::kConverted;
}
void* PrimitiveConverter::AllocateIndices(
xenos::IndexFormat format, uint32_t count, uint32_t simd_offset,
D3D12_GPU_VIRTUAL_ADDRESS& gpu_address_out) {
if (count == 0) {
return nullptr;
}
uint32_t size =
count * (format == xenos::IndexFormat::kInt32 ? sizeof(uint32_t)
: sizeof(uint16_t));
// 16-align all index data because SIMD is used to replace the reset index
// (without that, 4-alignment would be required anyway to mix 16-bit and
// 32-bit indices in one buffer page).
size = xe::align(size, uint32_t(16));
// Add some space to align SIMD register components the same way in the source
// and the buffer.
simd_offset &= 15;
if (simd_offset != 0) {
size += 16;
}
D3D12_GPU_VIRTUAL_ADDRESS gpu_address;
uint8_t* mapping =
buffer_pool_->Request(command_processor_.GetCurrentFrame(), size, 16,
nullptr, nullptr, &gpu_address);
if (mapping == nullptr) {
XELOGE("Failed to allocate space for {} converted {}-bit vertex indices",
count, format == xenos::IndexFormat::kInt32 ? 32 : 16);
return nullptr;
}
gpu_address_out = gpu_address + simd_offset;
return mapping + simd_offset;
}
std::pair<uint32_t, uint32_t> PrimitiveConverter::MemoryInvalidationCallback(
uint32_t physical_address_start, uint32_t length, bool exact_range) {
// 1 bit = (512 / 64) MB = 8 MB. Invalidate a region of this size.
uint32_t bit_index_first = physical_address_start >> 23;
uint32_t bit_index_last = (physical_address_start + length - 1) >> 23;
uint64_t bits = ~((1ull << bit_index_first) - 1);
if (bit_index_last < 63) {
bits &= (1ull << (bit_index_last + 1)) - 1;
}
memory_regions_invalidated_ |= bits;
return std::make_pair<uint32_t, uint32_t>(0, UINT32_MAX);
}
std::pair<uint32_t, uint32_t>
PrimitiveConverter::MemoryInvalidationCallbackThunk(
void* context_ptr, uint32_t physical_address_start, uint32_t length,
bool exact_range) {
return reinterpret_cast<PrimitiveConverter*>(context_ptr)
->MemoryInvalidationCallback(physical_address_start, length, exact_range);
}
D3D12_GPU_VIRTUAL_ADDRESS PrimitiveConverter::GetStaticIndexBuffer(
xenos::PrimitiveType source_type, uint32_t index_count,
uint32_t& index_count_out) const {
if (index_count > kMaxNonIndexedVertices) {
assert_always();
return D3D12_GPU_VIRTUAL_ADDRESS(0);
}
if (source_type == xenos::PrimitiveType::kTriangleFan) {
index_count_out = (std::max(index_count, uint32_t(2)) - 2) * 3;
return static_ib_gpu_address_ +
kStaticIBTriangleFanOffset * sizeof(uint16_t);
}
if (source_type == xenos::PrimitiveType::kQuadList &&
cvars::d3d12_convert_quads_to_triangles) {
index_count_out = (index_count >> 2) * 6;
return static_ib_gpu_address_ + kStaticIBQuadOffset * sizeof(uint16_t);
}
return D3D12_GPU_VIRTUAL_ADDRESS(0);
}
void PrimitiveConverter::InitializeTrace() {
// WriteMemoryRead must not be skipped.
converted_indices_cache_.clear();
memory_regions_used_ = 0;
}
} // namespace d3d12
} // namespace gpu
} // namespace xe

View File

@ -1,189 +0,0 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2018 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_GPU_D3D12_PRIMITIVE_CONVERTER_H_
#define XENIA_GPU_D3D12_PRIMITIVE_CONVERTER_H_
#include <atomic>
#include <memory>
#include <unordered_map>
#include "xenia/gpu/register_file.h"
#include "xenia/gpu/trace_writer.h"
#include "xenia/gpu/xenos.h"
#include "xenia/memory.h"
#include "xenia/ui/d3d12/d3d12_context.h"
#include "xenia/ui/d3d12/d3d12_upload_buffer_pool.h"
namespace xe {
namespace gpu {
namespace d3d12 {
class D3D12CommandProcessor;
// Index buffer cache for primitive types not natively supported by Direct3D 12:
// - Triangle and line strips with non-0xFFFF/0xFFFFFFFF reset index.
// - Triangle fans.
// - Line loops (only indexed ones - non-indexed are better handled in vertex
// shaders, otherwise a whole index buffer would have to be created for every
// vertex count value).
// - Quad lists (for debugging since geometry shaders break PIX - as an
// alternative to the geometry shader).
class PrimitiveConverter {
public:
PrimitiveConverter(D3D12CommandProcessor& command_processor,
const RegisterFile& register_file, Memory& memory,
TraceWriter& trace_writer);
~PrimitiveConverter();
bool Initialize();
void Shutdown();
void ClearCache();
void CompletedSubmissionUpdated();
void BeginSubmission();
void BeginFrame();
// Returns the primitive type that the original type will be converted to.
static xenos::PrimitiveType GetReplacementPrimitiveType(
xenos::PrimitiveType type);
enum class ConversionResult {
// Converted to a transient buffer.
kConverted,
// Conversion not required - use the index buffer in shared memory.
kConversionNotNeeded,
// No errors, but nothing to render.
kPrimitiveEmpty,
// Total failure of the draw call.
kFailed
};
// Converts an index buffer to the primitive type returned by
// GetReplacementPrimitiveType. If conversion has been performed, the returned
// buffer will be in the GENERIC_READ state (it's in an upload heap). Only
// writing to the outputs if returning kConverted. The restart index will be
// handled internally from the register values.
ConversionResult ConvertPrimitives(xenos::PrimitiveType source_type,
uint32_t address, uint32_t index_count,
xenos::IndexFormat index_format,
xenos::Endian index_endianness,
D3D12_GPU_VIRTUAL_ADDRESS& gpu_address_out,
uint32_t& index_count_out);
// Returns the 16-bit index buffer for drawing unsupported non-indexed
// primitives in INDEX_BUFFER state, for non-indexed drawing. Returns 0 if
// conversion is not available (can draw natively).
D3D12_GPU_VIRTUAL_ADDRESS GetStaticIndexBuffer(
xenos::PrimitiveType source_type, uint32_t index_count,
uint32_t& index_count_out) const;
// Callback for invalidating buffers mid-frame.
std::pair<uint32_t, uint32_t> MemoryInvalidationCallback(
uint32_t physical_address_start, uint32_t length, bool exact_range);
void InitializeTrace();
private:
// simd_offset is source address & 15 - if SIMD is used, the source and the
// target must have the same alignment within one register. 0 is optimal when
// not using SIMD.
void* AllocateIndices(xenos::IndexFormat format, uint32_t count,
uint32_t simd_offset,
D3D12_GPU_VIRTUAL_ADDRESS& gpu_address_out);
static std::pair<uint32_t, uint32_t> MemoryInvalidationCallbackThunk(
void* context_ptr, uint32_t physical_address_start, uint32_t length,
bool exact_range);
D3D12CommandProcessor& command_processor_;
const RegisterFile& register_file_;
Memory& memory_;
TraceWriter& trace_writer_;
std::unique_ptr<ui::d3d12::D3D12UploadBufferPool> buffer_pool_;
// Static index buffers for emulating unsupported primitive types when drawing
// without an index buffer.
// CPU-side, used only for uploading - destroyed once the copy commands have
// been completed.
ID3D12Resource* static_ib_upload_ = nullptr;
uint64_t static_ib_upload_submission_;
// GPU-side - used for drawing.
ID3D12Resource* static_ib_ = nullptr;
D3D12_GPU_VIRTUAL_ADDRESS static_ib_gpu_address_;
// In PM4 draw packets, 16 bits are used for the vertex count.
static constexpr uint32_t kMaxNonIndexedVertices = 65535;
static constexpr uint32_t kStaticIBTriangleFanOffset = 0;
static constexpr uint32_t kStaticIBTriangleFanCount =
(kMaxNonIndexedVertices - 2) * 3;
static constexpr uint32_t kStaticIBQuadOffset =
kStaticIBTriangleFanOffset + kStaticIBTriangleFanCount;
static constexpr uint32_t kStaticIBQuadCount =
(kMaxNonIndexedVertices >> 2) * 6;
static constexpr uint32_t kStaticIBTotalCount =
kStaticIBQuadOffset + kStaticIBQuadCount;
// Not identifying the index buffer uniquely - reset index must also be
// checked if reset is enabled.
union ConvertedIndicesKey {
uint64_t value;
struct {
uint32_t address; // 32
xenos::PrimitiveType source_type : 6; // 38
xenos::IndexFormat format : 1; // 39
uint32_t count : 16; // 55
uint32_t reset : 1; // 56
};
// Clearing the unused bits.
ConvertedIndicesKey() : value(0) {}
ConvertedIndicesKey(const ConvertedIndicesKey& key) : value(key.value) {}
ConvertedIndicesKey& operator=(const ConvertedIndicesKey& key) {
value = key.value;
return *this;
}
bool operator==(const ConvertedIndicesKey& key) const {
return value == key.value;
}
bool operator!=(const ConvertedIndicesKey& key) const {
return value != key.value;
}
};
struct ConvertedIndices {
ConvertedIndicesKey key;
// If reset is enabled, this also must be checked to find cached indices.
uint32_t reset_index;
// Zero GPU address if conversion not needed or the resulting index buffer
// is empty.
D3D12_GPU_VIRTUAL_ADDRESS gpu_address;
// When conversion is not needed, this must be equal to the original index
// count.
uint32_t converted_index_count;
};
// Cache for a single frame.
std::unordered_multimap<uint64_t, ConvertedIndices> converted_indices_cache_;
// Very coarse cache invalidation - if something is modified in a 8 MB portion
// of the physical memory and converted indices are also there, invalidate all
// the cache.
uint64_t memory_regions_used_;
std::atomic<uint64_t> memory_regions_invalidated_ = 0;
void* memory_invalidation_callback_handle_ = nullptr;
uint32_t system_page_size_;
};
} // namespace d3d12
} // namespace gpu
} // namespace xe
#endif // XENIA_GPU_D3D12_PRIMITIVE_CONVERTER_H_

View File

@ -1,104 +0,0 @@
// generated from `xb buildhlsl`
// source: adaptive_quad.hs.hlsl
const uint8_t adaptive_quad_hs[] = {
0x44, 0x58, 0x42, 0x43, 0x5B, 0x72, 0xC4, 0x5D, 0x23, 0xC3, 0xD2, 0x31,
0x9A, 0x46, 0xA8, 0xF4, 0x08, 0x84, 0x19, 0xF6, 0x01, 0x00, 0x00, 0x00,
0xB0, 0x04, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00,
0xA4, 0x00, 0x00, 0x00, 0xDC, 0x00, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00,
0xD4, 0x01, 0x00, 0x00, 0x14, 0x04, 0x00, 0x00, 0x52, 0x44, 0x45, 0x46,
0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x01, 0x05, 0x53, 0x48,
0x00, 0x05, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x13, 0x13, 0x44, 0x25,
0x3C, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00,
0x28, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x4D, 0x69, 0x63, 0x72, 0x6F, 0x73, 0x6F, 0x66,
0x74, 0x20, 0x28, 0x52, 0x29, 0x20, 0x48, 0x4C, 0x53, 0x4C, 0x20, 0x53,
0x68, 0x61, 0x64, 0x65, 0x72, 0x20, 0x43, 0x6F, 0x6D, 0x70, 0x69, 0x6C,
0x65, 0x72, 0x20, 0x31, 0x30, 0x2E, 0x31, 0x00, 0x49, 0x53, 0x47, 0x4E,
0x30, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
0x58, 0x45, 0x54, 0x45, 0x53, 0x53, 0x46, 0x41, 0x43, 0x54, 0x4F, 0x52,
0x00, 0xAB, 0xAB, 0xAB, 0x4F, 0x53, 0x47, 0x4E, 0x2C, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x01, 0x0E, 0x00, 0x00, 0x58, 0x45, 0x56, 0x45,
0x52, 0x54, 0x45, 0x58, 0x49, 0x44, 0x00, 0xAB, 0x50, 0x43, 0x53, 0x47,
0xBC, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
0x98, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00,
0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x0E, 0x00, 0x00,
0x98, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00,
0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x0E, 0x00, 0x00,
0x98, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00,
0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x0E, 0x00, 0x00,
0x98, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00,
0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x0E, 0x00, 0x00,
0xA6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00,
0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x0E, 0x00, 0x00,
0xA6, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00,
0x03, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x0E, 0x00, 0x00,
0x53, 0x56, 0x5F, 0x54, 0x65, 0x73, 0x73, 0x46, 0x61, 0x63, 0x74, 0x6F,
0x72, 0x00, 0x53, 0x56, 0x5F, 0x49, 0x6E, 0x73, 0x69, 0x64, 0x65, 0x54,
0x65, 0x73, 0x73, 0x46, 0x61, 0x63, 0x74, 0x6F, 0x72, 0x00, 0xAB, 0xAB,
0x53, 0x48, 0x45, 0x58, 0x38, 0x02, 0x00, 0x00, 0x51, 0x00, 0x03, 0x00,
0x8E, 0x00, 0x00, 0x00, 0x71, 0x00, 0x00, 0x01, 0x93, 0x20, 0x00, 0x01,
0x94, 0x20, 0x00, 0x01, 0x95, 0x18, 0x00, 0x01, 0x96, 0x20, 0x00, 0x01,
0x97, 0x18, 0x00, 0x01, 0x6A, 0x08, 0x00, 0x01, 0x72, 0x00, 0x00, 0x01,
0x65, 0x00, 0x00, 0x03, 0x12, 0x20, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00,
0x36, 0x00, 0x00, 0x05, 0x12, 0x20, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x01,
0x73, 0x00, 0x00, 0x01, 0x99, 0x00, 0x00, 0x02, 0x04, 0x00, 0x00, 0x00,
0x5F, 0x00, 0x00, 0x02, 0x00, 0x70, 0x01, 0x00, 0x5F, 0x00, 0x00, 0x04,
0x12, 0x90, 0x21, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x67, 0x00, 0x00, 0x04, 0x12, 0x20, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00,
0x0B, 0x00, 0x00, 0x00, 0x67, 0x00, 0x00, 0x04, 0x12, 0x20, 0x10, 0x00,
0x01, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x67, 0x00, 0x00, 0x04,
0x12, 0x20, 0x10, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00,
0x67, 0x00, 0x00, 0x04, 0x12, 0x20, 0x10, 0x00, 0x03, 0x00, 0x00, 0x00,
0x0E, 0x00, 0x00, 0x00, 0x68, 0x00, 0x00, 0x02, 0x01, 0x00, 0x00, 0x00,
0x5B, 0x00, 0x00, 0x04, 0x12, 0x20, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x06, 0x12, 0x00, 0x10, 0x00,
0x00, 0x00, 0x00, 0x00, 0x0A, 0x70, 0x01, 0x00, 0x01, 0x40, 0x00, 0x00,
0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x07, 0x12, 0x00, 0x10, 0x00,
0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01, 0x40, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x04,
0x22, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x70, 0x01, 0x00,
0x36, 0x00, 0x00, 0x08, 0x12, 0x20, 0x90, 0x00, 0x1A, 0x00, 0x10, 0x00,
0x00, 0x00, 0x00, 0x00, 0x0A, 0x90, 0xA1, 0x00, 0x0A, 0x00, 0x10, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x01,
0x73, 0x00, 0x00, 0x01, 0x99, 0x00, 0x00, 0x02, 0x02, 0x00, 0x00, 0x00,
0x5F, 0x00, 0x00, 0x02, 0x00, 0x70, 0x01, 0x00, 0x5F, 0x00, 0x00, 0x04,
0x12, 0x90, 0x21, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x67, 0x00, 0x00, 0x04, 0x12, 0x20, 0x10, 0x00, 0x04, 0x00, 0x00, 0x00,
0x0F, 0x00, 0x00, 0x00, 0x67, 0x00, 0x00, 0x04, 0x12, 0x20, 0x10, 0x00,
0x05, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x68, 0x00, 0x00, 0x02,
0x01, 0x00, 0x00, 0x00, 0x5B, 0x00, 0x00, 0x04, 0x12, 0x20, 0x10, 0x00,
0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x4F, 0x00, 0x00, 0x06,
0x12, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x70, 0x01, 0x00,
0x01, 0x40, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x09,
0x12, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x10, 0x00,
0x00, 0x00, 0x00, 0x00, 0x01, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01, 0x40, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x04,
0x22, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x70, 0x01, 0x00,
0x33, 0x00, 0x00, 0x0C, 0x12, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00,
0x0A, 0x90, 0xE1, 0x00, 0x02, 0x00, 0x00, 0x00, 0x1A, 0x00, 0x10, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x90, 0xA1, 0x00,
0x0A, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x36, 0x00, 0x00, 0x04, 0x22, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00,
0x0A, 0x70, 0x01, 0x00, 0x36, 0x00, 0x00, 0x07, 0x12, 0x20, 0xD0, 0x00,
0x04, 0x00, 0x00, 0x00, 0x1A, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00,
0x0A, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x01,
0x53, 0x54, 0x41, 0x54, 0x94, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};

View File

@ -1,81 +0,0 @@
//
// Generated by Microsoft (R) HLSL Shader Compiler 10.1
//
//
//
// Patch Constant signature:
//
// Name Index Mask Register SysValue Format Used
// -------------------- ----- ------ -------- -------- ------- ------
// SV_TessFactor 0 x 0 QUADEDGE float x
// SV_TessFactor 1 x 1 QUADEDGE float x
// SV_TessFactor 2 x 2 QUADEDGE float x
// SV_TessFactor 3 x 3 QUADEDGE float x
// SV_InsideTessFactor 0 x 4 QUADINT float x
// SV_InsideTessFactor 1 x 5 QUADINT float x
//
//
// Input signature:
//
// Name Index Mask Register SysValue Format Used
// -------------------- ----- ------ -------- -------- ------- ------
// XETESSFACTOR 0 x 0 NONE float x
//
//
// Output signature:
//
// Name Index Mask Register SysValue Format Used
// -------------------- ----- ------ -------- -------- ------- ------
// XEVERTEXID 0 x 0 NONE float x
//
// Tessellation Domain # of control points
// -------------------- --------------------
// Quadrilateral 4
//
// Tessellation Output Primitive Partitioning Type
// ------------------------------ ------------------
// Clockwise Triangles Even Fractional
//
hs_5_1
hs_decls
dcl_input_control_point_count 4
dcl_output_control_point_count 4
dcl_tessellator_domain domain_quad
dcl_tessellator_partitioning partitioning_fractional_even
dcl_tessellator_output_primitive output_triangle_cw
dcl_globalFlags refactoringAllowed
hs_control_point_phase
dcl_output o0.x
mov o0.x, l(0)
ret
hs_fork_phase
dcl_hs_fork_phase_instance_count 4
dcl_input vForkInstanceID
dcl_input vicp[4][0].x
dcl_output_siv o0.x, finalQuadUeq0EdgeTessFactor
dcl_output_siv o1.x, finalQuadVeq0EdgeTessFactor
dcl_output_siv o2.x, finalQuadUeq1EdgeTessFactor
dcl_output_siv o3.x, finalQuadVeq1EdgeTessFactor
dcl_temps 1
dcl_indexrange o0.x 4
iadd r0.x, vForkInstanceID.x, l(3)
and r0.x, r0.x, l(3)
mov r0.y, vForkInstanceID.x
mov o[r0.y + 0].x, vicp[r0.x + 0][0].x
ret
hs_fork_phase
dcl_hs_fork_phase_instance_count 2
dcl_input vForkInstanceID
dcl_input vicp[4][0].x
dcl_output_siv o4.x, finalQuadUInsideTessFactor
dcl_output_siv o5.x, finalQuadVInsideTessFactor
dcl_temps 1
dcl_indexrange o4.x 2
ult r0.x, vForkInstanceID.x, l(1)
movc r0.x, r0.x, l(0), l(3)
ineg r0.y, vForkInstanceID.x
min r0.x, vicp[r0.y + 2][0].x, vicp[r0.x + 0][0].x
mov r0.y, vForkInstanceID.x
mov o[r0.y + 4].x, r0.x
ret
// Approximately 14 instruction slots used

View File

@ -1,87 +0,0 @@
// generated from `xb buildhlsl`
// source: adaptive_triangle.hs.hlsl
const uint8_t adaptive_triangle_hs[] = {
0x44, 0x58, 0x42, 0x43, 0x0A, 0xA5, 0x75, 0xB0, 0x13, 0x0C, 0x82, 0x6C,
0xAB, 0x68, 0xC3, 0xA1, 0x34, 0xFB, 0x63, 0xC1, 0x01, 0x00, 0x00, 0x00,
0xE0, 0x03, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00,
0xA4, 0x00, 0x00, 0x00, 0xDC, 0x00, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00,
0xA4, 0x01, 0x00, 0x00, 0x44, 0x03, 0x00, 0x00, 0x52, 0x44, 0x45, 0x46,
0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x01, 0x05, 0x53, 0x48,
0x00, 0x05, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x13, 0x13, 0x44, 0x25,
0x3C, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00,
0x28, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x4D, 0x69, 0x63, 0x72, 0x6F, 0x73, 0x6F, 0x66,
0x74, 0x20, 0x28, 0x52, 0x29, 0x20, 0x48, 0x4C, 0x53, 0x4C, 0x20, 0x53,
0x68, 0x61, 0x64, 0x65, 0x72, 0x20, 0x43, 0x6F, 0x6D, 0x70, 0x69, 0x6C,
0x65, 0x72, 0x20, 0x31, 0x30, 0x2E, 0x31, 0x00, 0x49, 0x53, 0x47, 0x4E,
0x30, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
0x58, 0x45, 0x54, 0x45, 0x53, 0x53, 0x46, 0x41, 0x43, 0x54, 0x4F, 0x52,
0x00, 0xAB, 0xAB, 0xAB, 0x4F, 0x53, 0x47, 0x4E, 0x2C, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x01, 0x0E, 0x00, 0x00, 0x58, 0x45, 0x56, 0x45,
0x52, 0x54, 0x45, 0x58, 0x49, 0x44, 0x00, 0xAB, 0x50, 0x43, 0x53, 0x47,
0x8C, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00,
0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x0E, 0x00, 0x00,
0x68, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00,
0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x0E, 0x00, 0x00,
0x68, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00,
0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x0E, 0x00, 0x00,
0x76, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00,
0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x0E, 0x00, 0x00,
0x53, 0x56, 0x5F, 0x54, 0x65, 0x73, 0x73, 0x46, 0x61, 0x63, 0x74, 0x6F,
0x72, 0x00, 0x53, 0x56, 0x5F, 0x49, 0x6E, 0x73, 0x69, 0x64, 0x65, 0x54,
0x65, 0x73, 0x73, 0x46, 0x61, 0x63, 0x74, 0x6F, 0x72, 0x00, 0xAB, 0xAB,
0x53, 0x48, 0x45, 0x58, 0x98, 0x01, 0x00, 0x00, 0x51, 0x00, 0x03, 0x00,
0x66, 0x00, 0x00, 0x00, 0x71, 0x00, 0x00, 0x01, 0x93, 0x18, 0x00, 0x01,
0x94, 0x18, 0x00, 0x01, 0x95, 0x10, 0x00, 0x01, 0x96, 0x20, 0x00, 0x01,
0x97, 0x18, 0x00, 0x01, 0x6A, 0x08, 0x00, 0x01, 0x72, 0x00, 0x00, 0x01,
0x65, 0x00, 0x00, 0x03, 0x12, 0x20, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00,
0x36, 0x00, 0x00, 0x05, 0x12, 0x20, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x01,
0x73, 0x00, 0x00, 0x01, 0x99, 0x00, 0x00, 0x02, 0x03, 0x00, 0x00, 0x00,
0x5F, 0x00, 0x00, 0x02, 0x00, 0x70, 0x01, 0x00, 0x5F, 0x00, 0x00, 0x04,
0x12, 0x90, 0x21, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x67, 0x00, 0x00, 0x04, 0x12, 0x20, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00,
0x11, 0x00, 0x00, 0x00, 0x67, 0x00, 0x00, 0x04, 0x12, 0x20, 0x10, 0x00,
0x01, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x67, 0x00, 0x00, 0x04,
0x12, 0x20, 0x10, 0x00, 0x02, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00,
0x68, 0x00, 0x00, 0x02, 0x01, 0x00, 0x00, 0x00, 0x5B, 0x00, 0x00, 0x04,
0x12, 0x20, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
0x1E, 0x00, 0x00, 0x06, 0x12, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00,
0x0A, 0x70, 0x01, 0x00, 0x01, 0x40, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x4E, 0x00, 0x00, 0x08, 0x00, 0xD0, 0x00, 0x00, 0x12, 0x00, 0x10, 0x00,
0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01, 0x40, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x04,
0x22, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x70, 0x01, 0x00,
0x36, 0x00, 0x00, 0x08, 0x12, 0x20, 0x90, 0x00, 0x1A, 0x00, 0x10, 0x00,
0x00, 0x00, 0x00, 0x00, 0x0A, 0x90, 0xA1, 0x00, 0x0A, 0x00, 0x10, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x01,
0x73, 0x00, 0x00, 0x01, 0x5F, 0x00, 0x00, 0x04, 0x12, 0x90, 0x21, 0x00,
0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x67, 0x00, 0x00, 0x04,
0x12, 0x20, 0x10, 0x00, 0x03, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
0x68, 0x00, 0x00, 0x02, 0x01, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x09,
0x12, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x90, 0x21, 0x00,
0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x90, 0x21, 0x00,
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x08,
0x12, 0x20, 0x10, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x10, 0x00,
0x00, 0x00, 0x00, 0x00, 0x0A, 0x90, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x01, 0x53, 0x54, 0x41, 0x54,
0x94, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};

View File

@ -1,70 +0,0 @@
//
// Generated by Microsoft (R) HLSL Shader Compiler 10.1
//
//
//
// Patch Constant signature:
//
// Name Index Mask Register SysValue Format Used
// -------------------- ----- ------ -------- -------- ------- ------
// SV_TessFactor 0 x 0 TRIEDGE float x
// SV_TessFactor 1 x 1 TRIEDGE float x
// SV_TessFactor 2 x 2 TRIEDGE float x
// SV_InsideTessFactor 0 x 3 TRIINT float x
//
//
// Input signature:
//
// Name Index Mask Register SysValue Format Used
// -------------------- ----- ------ -------- -------- ------- ------
// XETESSFACTOR 0 x 0 NONE float x
//
//
// Output signature:
//
// Name Index Mask Register SysValue Format Used
// -------------------- ----- ------ -------- -------- ------- ------
// XEVERTEXID 0 x 0 NONE float x
//
// Tessellation Domain # of control points
// -------------------- --------------------
// Triangle 3
//
// Tessellation Output Primitive Partitioning Type
// ------------------------------ ------------------
// Clockwise Triangles Even Fractional
//
hs_5_1
hs_decls
dcl_input_control_point_count 3
dcl_output_control_point_count 3
dcl_tessellator_domain domain_tri
dcl_tessellator_partitioning partitioning_fractional_even
dcl_tessellator_output_primitive output_triangle_cw
dcl_globalFlags refactoringAllowed
hs_control_point_phase
dcl_output o0.x
mov o0.x, l(0)
ret
hs_fork_phase
dcl_hs_fork_phase_instance_count 3
dcl_input vForkInstanceID
dcl_input vicp[3][0].x
dcl_output_siv o0.x, finalTriUeq0EdgeTessFactor
dcl_output_siv o1.x, finalTriVeq0EdgeTessFactor
dcl_output_siv o2.x, finalTriWeq0EdgeTessFactor
dcl_temps 1
dcl_indexrange o0.x 3
iadd r0.x, vForkInstanceID.x, l(1)
udiv null, r0.x, r0.x, l(3)
mov r0.y, vForkInstanceID.x
mov o[r0.y + 0].x, vicp[r0.x + 0][0].x
ret
hs_fork_phase
dcl_input vicp[3][0].x
dcl_output_siv o3.x, finalTriInsideTessFactor
dcl_temps 1
min r0.x, vicp[2][0].x, vicp[1][0].x
min o3.x, r0.x, vicp[0][0].x
ret
// Approximately 10 instruction slots used

View File

@ -1,59 +0,0 @@
// generated from `xb buildhlsl`
// source: clear_uint2.ps.hlsl
const uint8_t clear_uint2_ps[] = {
0x44, 0x58, 0x42, 0x43, 0x5A, 0x4F, 0x43, 0x0F, 0x11, 0xAF, 0xD2, 0xAA,
0xBD, 0xDE, 0xD1, 0xE4, 0x3E, 0x1F, 0x99, 0xF6, 0x01, 0x00, 0x00, 0x00,
0x94, 0x02, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00,
0x58, 0x01, 0x00, 0x00, 0x68, 0x01, 0x00, 0x00, 0x9C, 0x01, 0x00, 0x00,
0xF8, 0x01, 0x00, 0x00, 0x52, 0x44, 0x45, 0x46, 0x1C, 0x01, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x3C, 0x00, 0x00, 0x00, 0x01, 0x05, 0xFF, 0xFF, 0x00, 0x05, 0x00, 0x00,
0xF4, 0x00, 0x00, 0x00, 0x13, 0x13, 0x44, 0x25, 0x3C, 0x00, 0x00, 0x00,
0x18, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00,
0x24, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x58, 0x65, 0x43, 0x6C, 0x65, 0x61, 0x72, 0x43,
0x6F, 0x6E, 0x73, 0x74, 0x61, 0x6E, 0x74, 0x73, 0x00, 0xAB, 0xAB, 0xAB,
0x64, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x90, 0x00, 0x00, 0x00,
0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xB8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
0x02, 0x00, 0x00, 0x00, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
0x00, 0x00, 0x00, 0x00, 0x78, 0x65, 0x5F, 0x63, 0x6C, 0x65, 0x61, 0x72,
0x5F, 0x76, 0x61, 0x6C, 0x75, 0x65, 0x00, 0x75, 0x69, 0x6E, 0x74, 0x32,
0x00, 0xAB, 0xAB, 0xAB, 0x01, 0x00, 0x13, 0x00, 0x01, 0x00, 0x02, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xC7, 0x00, 0x00, 0x00, 0x4D, 0x69, 0x63, 0x72, 0x6F, 0x73, 0x6F, 0x66,
0x74, 0x20, 0x28, 0x52, 0x29, 0x20, 0x48, 0x4C, 0x53, 0x4C, 0x20, 0x53,
0x68, 0x61, 0x64, 0x65, 0x72, 0x20, 0x43, 0x6F, 0x6D, 0x70, 0x69, 0x6C,
0x65, 0x72, 0x20, 0x31, 0x30, 0x2E, 0x31, 0x00, 0x49, 0x53, 0x47, 0x4E,
0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
0x4F, 0x53, 0x47, 0x4E, 0x2C, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x08, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x03, 0x0C, 0x00, 0x00, 0x53, 0x56, 0x5F, 0x54, 0x61, 0x72, 0x67, 0x65,
0x74, 0x00, 0xAB, 0xAB, 0x53, 0x48, 0x45, 0x58, 0x54, 0x00, 0x00, 0x00,
0x51, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x6A, 0x08, 0x00, 0x01,
0x59, 0x00, 0x00, 0x07, 0x46, 0x8E, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x65, 0x00, 0x00, 0x03, 0x32, 0x20, 0x10, 0x00,
0x00, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x07, 0x32, 0x20, 0x10, 0x00,
0x00, 0x00, 0x00, 0x00, 0x46, 0x80, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x01,
0x53, 0x54, 0x41, 0x54, 0x94, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};

View File

@ -1,41 +0,0 @@
//
// Generated by Microsoft (R) HLSL Shader Compiler 10.1
//
//
// Buffer Definitions:
//
// cbuffer XeClearConstants
// {
//
// uint2 xe_clear_value; // Offset: 0 Size: 8
//
// }
//
//
// Resource Bindings:
//
// Name Type Format Dim ID HLSL Bind Count
// ------------------------------ ---------- ------- ----------- ------- -------------- ------
// XeClearConstants cbuffer NA NA CB0 cb0 1
//
//
//
// Input signature:
//
// Name Index Mask Register SysValue Format Used
// -------------------- ----- ------ -------- -------- ------- ------
// no Input
//
// Output signature:
//
// Name Index Mask Register SysValue Format Used
// -------------------- ----- ------ -------- -------- ------- ------
// SV_Target 0 xy 0 TARGET uint xy
//
ps_5_1
dcl_globalFlags refactoringAllowed
dcl_constantbuffer CB0[0:0][1], immediateIndexed, space=0
dcl_output o0.xy
mov o0.xy, CB0[0][0].xyxx
ret
// Approximately 2 instruction slots used

View File

@ -1,298 +0,0 @@
// generated from `xb buildhlsl`
// source: continuous_quad.hs.hlsl
const uint8_t continuous_quad_hs[] = {
0x44, 0x58, 0x42, 0x43, 0xCE, 0xF4, 0xB8, 0x73, 0x74, 0x35, 0xD9, 0xE6,
0x42, 0x49, 0xF7, 0xF8, 0xDD, 0x91, 0xC9, 0xCD, 0x01, 0x00, 0x00, 0x00,
0xC0, 0x0D, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00,
0x8C, 0x0A, 0x00, 0x00, 0xC0, 0x0A, 0x00, 0x00, 0xF4, 0x0A, 0x00, 0x00,
0xB8, 0x0B, 0x00, 0x00, 0x24, 0x0D, 0x00, 0x00, 0x52, 0x44, 0x45, 0x46,
0x4C, 0x0A, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x01, 0x05, 0x53, 0x48,
0x00, 0x05, 0x00, 0x00, 0x22, 0x0A, 0x00, 0x00, 0x13, 0x13, 0x44, 0x25,
0x3C, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00,
0x28, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x65, 0x5F, 0x73,
0x79, 0x73, 0x74, 0x65, 0x6D, 0x5F, 0x63, 0x62, 0x75, 0x66, 0x66, 0x65,
0x72, 0x00, 0xAB, 0xAB, 0x64, 0x00, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x00,
0x90, 0x00, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x68, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x05, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x9C, 0x05, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
0xC0, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00,
0xE4, 0x05, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x78, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
0x00, 0x00, 0x00, 0x00, 0xFF, 0x05, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x05, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x16, 0x06, 0x00, 0x00,
0x14, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x30, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00,
0x54, 0x06, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xC0, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
0x00, 0x00, 0x00, 0x00, 0x62, 0x06, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x05, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x78, 0x06, 0x00, 0x00,
0x28, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xC0, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00,
0x8F, 0x06, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xAC, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
0x00, 0x00, 0x00, 0x00, 0xD0, 0x06, 0x00, 0x00, 0x90, 0x00, 0x00, 0x00,
0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE4, 0x06, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x08, 0x07, 0x00, 0x00,
0x9C, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x78, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00,
0x29, 0x07, 0x00, 0x00, 0xA0, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xE4, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
0x00, 0x00, 0x00, 0x00, 0x37, 0x07, 0x00, 0x00, 0xAC, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x05, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x47, 0x07, 0x00, 0x00,
0xB0, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x68, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00,
0x8C, 0x07, 0x00, 0x00, 0xD0, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x78, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
0x00, 0x00, 0x00, 0x00, 0xA1, 0x07, 0x00, 0x00, 0xD4, 0x00, 0x00, 0x00,
0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBC, 0x07, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x07, 0x00, 0x00,
0xDC, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00,
0x24, 0x08, 0x00, 0x00, 0xE0, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x38, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
0x00, 0x00, 0x00, 0x00, 0x5C, 0x08, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x05, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x6D, 0x08, 0x00, 0x00,
0xF4, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x78, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00,
0x82, 0x08, 0x00, 0x00, 0xF8, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xC0, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
0x00, 0x00, 0x00, 0x00, 0x97, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x05, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xB2, 0x08, 0x00, 0x00,
0x08, 0x01, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xC0, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00,
0xCC, 0x08, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x78, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
0x00, 0x00, 0x00, 0x00, 0xE7, 0x08, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00,
0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x08, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x09, 0x00, 0x00,
0x40, 0x01, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x3C, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00,
0x60, 0x09, 0x00, 0x00, 0x50, 0x01, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x3C, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
0x00, 0x00, 0x00, 0x00, 0x79, 0x09, 0x00, 0x00, 0x60, 0x01, 0x00, 0x00,
0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8C, 0x09, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xB0, 0x09, 0x00, 0x00,
0xA0, 0x01, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xC8, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00,
0xEC, 0x09, 0x00, 0x00, 0xC0, 0x01, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x3C, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
0x00, 0x00, 0x00, 0x00, 0x0A, 0x0A, 0x00, 0x00, 0xD0, 0x01, 0x00, 0x00,
0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x08, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x78, 0x65, 0x5F, 0x66,
0x6C, 0x61, 0x67, 0x73, 0x00, 0x64, 0x77, 0x6F, 0x72, 0x64, 0x00, 0xAB,
0x00, 0x00, 0x13, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x71, 0x05, 0x00, 0x00,
0x78, 0x65, 0x5F, 0x74, 0x65, 0x73, 0x73, 0x65, 0x6C, 0x6C, 0x61, 0x74,
0x69, 0x6F, 0x6E, 0x5F, 0x66, 0x61, 0x63, 0x74, 0x6F, 0x72, 0x5F, 0x72,
0x61, 0x6E, 0x67, 0x65, 0x00, 0x66, 0x6C, 0x6F, 0x61, 0x74, 0x32, 0x00,
0x01, 0x00, 0x03, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB9, 0x05, 0x00, 0x00,
0x78, 0x65, 0x5F, 0x6C, 0x69, 0x6E, 0x65, 0x5F, 0x6C, 0x6F, 0x6F, 0x70,
0x5F, 0x63, 0x6C, 0x6F, 0x73, 0x69, 0x6E, 0x67, 0x5F, 0x69, 0x6E, 0x64,
0x65, 0x78, 0x00, 0x78, 0x65, 0x5F, 0x76, 0x65, 0x72, 0x74, 0x65, 0x78,
0x5F, 0x69, 0x6E, 0x64, 0x65, 0x78, 0x5F, 0x65, 0x6E, 0x64, 0x69, 0x61,
0x6E, 0x00, 0x78, 0x65, 0x5F, 0x76, 0x65, 0x72, 0x74, 0x65, 0x78, 0x5F,
0x62, 0x61, 0x73, 0x65, 0x5F, 0x69, 0x6E, 0x64, 0x65, 0x78, 0x00, 0x69,
0x6E, 0x74, 0x00, 0xAB, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0x01, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x2B, 0x06, 0x00, 0x00, 0x78, 0x65, 0x5F, 0x70, 0x6F, 0x69, 0x6E, 0x74,
0x5F, 0x73, 0x69, 0x7A, 0x65, 0x00, 0x78, 0x65, 0x5F, 0x70, 0x6F, 0x69,
0x6E, 0x74, 0x5F, 0x73, 0x69, 0x7A, 0x65, 0x5F, 0x6D, 0x69, 0x6E, 0x5F,
0x6D, 0x61, 0x78, 0x00, 0x78, 0x65, 0x5F, 0x70, 0x6F, 0x69, 0x6E, 0x74,
0x5F, 0x73, 0x63, 0x72, 0x65, 0x65, 0x6E, 0x5F, 0x74, 0x6F, 0x5F, 0x6E,
0x64, 0x63, 0x00, 0x78, 0x65, 0x5F, 0x75, 0x73, 0x65, 0x72, 0x5F, 0x63,
0x6C, 0x69, 0x70, 0x5F, 0x70, 0x6C, 0x61, 0x6E, 0x65, 0x73, 0x00, 0x66,
0x6C, 0x6F, 0x61, 0x74, 0x34, 0x00, 0xAB, 0xAB, 0x01, 0x00, 0x03, 0x00,
0x01, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xA3, 0x06, 0x00, 0x00, 0x78, 0x65, 0x5F, 0x6E,
0x64, 0x63, 0x5F, 0x73, 0x63, 0x61, 0x6C, 0x65, 0x00, 0x66, 0x6C, 0x6F,
0x61, 0x74, 0x33, 0x00, 0x01, 0x00, 0x03, 0x00, 0x01, 0x00, 0x03, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xDD, 0x06, 0x00, 0x00, 0x78, 0x65, 0x5F, 0x69, 0x6E, 0x74, 0x65, 0x72,
0x70, 0x6F, 0x6C, 0x61, 0x74, 0x6F, 0x72, 0x5F, 0x73, 0x61, 0x6D, 0x70,
0x6C, 0x69, 0x6E, 0x67, 0x5F, 0x70, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6E,
0x00, 0x78, 0x65, 0x5F, 0x6E, 0x64, 0x63, 0x5F, 0x6F, 0x66, 0x66, 0x73,
0x65, 0x74, 0x00, 0x78, 0x65, 0x5F, 0x70, 0x73, 0x5F, 0x70, 0x61, 0x72,
0x61, 0x6D, 0x5F, 0x67, 0x65, 0x6E, 0x00, 0x78, 0x65, 0x5F, 0x74, 0x65,
0x78, 0x74, 0x75, 0x72, 0x65, 0x5F, 0x73, 0x77, 0x69, 0x7A, 0x7A, 0x6C,
0x65, 0x64, 0x5F, 0x73, 0x69, 0x67, 0x6E, 0x73, 0x00, 0x75, 0x69, 0x6E,
0x74, 0x34, 0x00, 0xAB, 0x01, 0x00, 0x13, 0x00, 0x01, 0x00, 0x04, 0x00,
0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x61, 0x07, 0x00, 0x00, 0x78, 0x65, 0x5F, 0x74, 0x65, 0x78, 0x74, 0x75,
0x72, 0x65, 0x73, 0x5F, 0x72, 0x65, 0x73, 0x6F, 0x6C, 0x76, 0x65, 0x64,
0x00, 0x78, 0x65, 0x5F, 0x73, 0x61, 0x6D, 0x70, 0x6C, 0x65, 0x5F, 0x63,
0x6F, 0x75, 0x6E, 0x74, 0x5F, 0x6C, 0x6F, 0x67, 0x32, 0x00, 0x75, 0x69,
0x6E, 0x74, 0x32, 0x00, 0x01, 0x00, 0x13, 0x00, 0x01, 0x00, 0x02, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xB6, 0x07, 0x00, 0x00, 0x78, 0x65, 0x5F, 0x61, 0x6C, 0x70, 0x68, 0x61,
0x5F, 0x74, 0x65, 0x73, 0x74, 0x5F, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65,
0x6E, 0x63, 0x65, 0x00, 0x66, 0x6C, 0x6F, 0x61, 0x74, 0x00, 0xAB, 0xAB,
0x00, 0x00, 0x03, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x07, 0x00, 0x00,
0x78, 0x65, 0x5F, 0x63, 0x6F, 0x6C, 0x6F, 0x72, 0x5F, 0x65, 0x78, 0x70,
0x5F, 0x62, 0x69, 0x61, 0x73, 0x00, 0xAB, 0xAB, 0x01, 0x00, 0x03, 0x00,
0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xA3, 0x06, 0x00, 0x00, 0x78, 0x65, 0x5F, 0x61,
0x6C, 0x70, 0x68, 0x61, 0x5F, 0x74, 0x6F, 0x5F, 0x6D, 0x61, 0x73, 0x6B,
0x00, 0x78, 0x65, 0x5F, 0x65, 0x64, 0x72, 0x61, 0x6D, 0x5F, 0x70, 0x69,
0x74, 0x63, 0x68, 0x5F, 0x74, 0x69, 0x6C, 0x65, 0x73, 0x00, 0x78, 0x65,
0x5F, 0x65, 0x64, 0x72, 0x61, 0x6D, 0x5F, 0x64, 0x65, 0x70, 0x74, 0x68,
0x5F, 0x72, 0x61, 0x6E, 0x67, 0x65, 0x00, 0x78, 0x65, 0x5F, 0x65, 0x64,
0x72, 0x61, 0x6D, 0x5F, 0x70, 0x6F, 0x6C, 0x79, 0x5F, 0x6F, 0x66, 0x66,
0x73, 0x65, 0x74, 0x5F, 0x66, 0x72, 0x6F, 0x6E, 0x74, 0x00, 0x78, 0x65,
0x5F, 0x65, 0x64, 0x72, 0x61, 0x6D, 0x5F, 0x70, 0x6F, 0x6C, 0x79, 0x5F,
0x6F, 0x66, 0x66, 0x73, 0x65, 0x74, 0x5F, 0x62, 0x61, 0x63, 0x6B, 0x00,
0x78, 0x65, 0x5F, 0x65, 0x64, 0x72, 0x61, 0x6D, 0x5F, 0x64, 0x65, 0x70,
0x74, 0x68, 0x5F, 0x62, 0x61, 0x73, 0x65, 0x5F, 0x64, 0x77, 0x6F, 0x72,
0x64, 0x73, 0x00, 0x78, 0x65, 0x5F, 0x65, 0x64, 0x72, 0x61, 0x6D, 0x5F,
0x73, 0x74, 0x65, 0x6E, 0x63, 0x69, 0x6C, 0x00, 0x01, 0x00, 0x13, 0x00,
0x01, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x61, 0x07, 0x00, 0x00, 0x78, 0x65, 0x5F, 0x65,
0x64, 0x72, 0x61, 0x6D, 0x5F, 0x72, 0x74, 0x5F, 0x62, 0x61, 0x73, 0x65,
0x5F, 0x64, 0x77, 0x6F, 0x72, 0x64, 0x73, 0x5F, 0x73, 0x63, 0x61, 0x6C,
0x65, 0x64, 0x00, 0xAB, 0x01, 0x00, 0x13, 0x00, 0x01, 0x00, 0x04, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x61, 0x07, 0x00, 0x00, 0x78, 0x65, 0x5F, 0x65, 0x64, 0x72, 0x61, 0x6D,
0x5F, 0x72, 0x74, 0x5F, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x5F, 0x66,
0x6C, 0x61, 0x67, 0x73, 0x00, 0x78, 0x65, 0x5F, 0x65, 0x64, 0x72, 0x61,
0x6D, 0x5F, 0x72, 0x74, 0x5F, 0x63, 0x6C, 0x61, 0x6D, 0x70, 0x00, 0xAB,
0x01, 0x00, 0x03, 0x00, 0x01, 0x00, 0x04, 0x00, 0x04, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA3, 0x06, 0x00, 0x00,
0x78, 0x65, 0x5F, 0x65, 0x64, 0x72, 0x61, 0x6D, 0x5F, 0x72, 0x74, 0x5F,
0x6B, 0x65, 0x65, 0x70, 0x5F, 0x6D, 0x61, 0x73, 0x6B, 0x00, 0xAB, 0xAB,
0x01, 0x00, 0x13, 0x00, 0x01, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x61, 0x07, 0x00, 0x00,
0x78, 0x65, 0x5F, 0x65, 0x64, 0x72, 0x61, 0x6D, 0x5F, 0x72, 0x74, 0x5F,
0x62, 0x6C, 0x65, 0x6E, 0x64, 0x5F, 0x66, 0x61, 0x63, 0x74, 0x6F, 0x72,
0x73, 0x5F, 0x6F, 0x70, 0x73, 0x00, 0x78, 0x65, 0x5F, 0x65, 0x64, 0x72,
0x61, 0x6D, 0x5F, 0x62, 0x6C, 0x65, 0x6E, 0x64, 0x5F, 0x63, 0x6F, 0x6E,
0x73, 0x74, 0x61, 0x6E, 0x74, 0x00, 0x4D, 0x69, 0x63, 0x72, 0x6F, 0x73,
0x6F, 0x66, 0x74, 0x20, 0x28, 0x52, 0x29, 0x20, 0x48, 0x4C, 0x53, 0x4C,
0x20, 0x53, 0x68, 0x61, 0x64, 0x65, 0x72, 0x20, 0x43, 0x6F, 0x6D, 0x70,
0x69, 0x6C, 0x65, 0x72, 0x20, 0x31, 0x30, 0x2E, 0x31, 0x00, 0xAB, 0xAB,
0x49, 0x53, 0x47, 0x4E, 0x2C, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x08, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01, 0x01, 0x00, 0x00, 0x58, 0x45, 0x56, 0x45, 0x52, 0x54, 0x45, 0x58,
0x49, 0x44, 0x00, 0xAB, 0x4F, 0x53, 0x47, 0x4E, 0x2C, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x01, 0x0E, 0x00, 0x00, 0x58, 0x45, 0x56, 0x45,
0x52, 0x54, 0x45, 0x58, 0x49, 0x44, 0x00, 0xAB, 0x50, 0x43, 0x53, 0x47,
0xBC, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
0x98, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00,
0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x0E, 0x00, 0x00,
0x98, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00,
0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x0E, 0x00, 0x00,
0x98, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00,
0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x0E, 0x00, 0x00,
0x98, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00,
0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x0E, 0x00, 0x00,
0xA6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00,
0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x0E, 0x00, 0x00,
0xA6, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00,
0x03, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x0E, 0x00, 0x00,
0x53, 0x56, 0x5F, 0x54, 0x65, 0x73, 0x73, 0x46, 0x61, 0x63, 0x74, 0x6F,
0x72, 0x00, 0x53, 0x56, 0x5F, 0x49, 0x6E, 0x73, 0x69, 0x64, 0x65, 0x54,
0x65, 0x73, 0x73, 0x46, 0x61, 0x63, 0x74, 0x6F, 0x72, 0x00, 0xAB, 0xAB,
0x53, 0x48, 0x45, 0x58, 0x64, 0x01, 0x00, 0x00, 0x51, 0x00, 0x03, 0x00,
0x59, 0x00, 0x00, 0x00, 0x71, 0x00, 0x00, 0x01, 0x93, 0x20, 0x00, 0x01,
0x94, 0x20, 0x00, 0x01, 0x95, 0x18, 0x00, 0x01, 0x96, 0x20, 0x00, 0x01,
0x97, 0x18, 0x00, 0x01, 0x6A, 0x08, 0x00, 0x01, 0x59, 0x00, 0x00, 0x07,
0x46, 0x8E, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x73, 0x00, 0x00, 0x01, 0x99, 0x00, 0x00, 0x02, 0x04, 0x00, 0x00, 0x00,
0x5F, 0x00, 0x00, 0x02, 0x00, 0x70, 0x01, 0x00, 0x67, 0x00, 0x00, 0x04,
0x12, 0x20, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00,
0x67, 0x00, 0x00, 0x04, 0x12, 0x20, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00,
0x0C, 0x00, 0x00, 0x00, 0x67, 0x00, 0x00, 0x04, 0x12, 0x20, 0x10, 0x00,
0x02, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x67, 0x00, 0x00, 0x04,
0x12, 0x20, 0x10, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00,
0x68, 0x00, 0x00, 0x02, 0x01, 0x00, 0x00, 0x00, 0x5B, 0x00, 0x00, 0x04,
0x12, 0x20, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
0x36, 0x00, 0x00, 0x04, 0x12, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00,
0x0A, 0x70, 0x01, 0x00, 0x36, 0x00, 0x00, 0x08, 0x12, 0x20, 0x90, 0x00,
0x0A, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0x80, 0x30, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x3E, 0x00, 0x00, 0x01, 0x73, 0x00, 0x00, 0x01, 0x99, 0x00, 0x00, 0x02,
0x02, 0x00, 0x00, 0x00, 0x5F, 0x00, 0x00, 0x02, 0x00, 0x70, 0x01, 0x00,
0x67, 0x00, 0x00, 0x04, 0x12, 0x20, 0x10, 0x00, 0x04, 0x00, 0x00, 0x00,
0x0F, 0x00, 0x00, 0x00, 0x67, 0x00, 0x00, 0x04, 0x12, 0x20, 0x10, 0x00,
0x05, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x68, 0x00, 0x00, 0x02,
0x01, 0x00, 0x00, 0x00, 0x5B, 0x00, 0x00, 0x04, 0x12, 0x20, 0x10, 0x00,
0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x04,
0x12, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x70, 0x01, 0x00,
0x36, 0x00, 0x00, 0x09, 0x12, 0x20, 0xD0, 0x00, 0x04, 0x00, 0x00, 0x00,
0x0A, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0x80, 0x30, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x3E, 0x00, 0x00, 0x01, 0x53, 0x54, 0x41, 0x54, 0x94, 0x00, 0x00, 0x00,
0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
};

View File

@ -1,117 +0,0 @@
//
// Generated by Microsoft (R) HLSL Shader Compiler 10.1
//
//
// Buffer Definitions:
//
// cbuffer xe_system_cbuffer
// {
//
// uint xe_flags; // Offset: 0 Size: 4 [unused]
// float2 xe_tessellation_factor_range;// Offset: 4 Size: 8
// uint xe_line_loop_closing_index; // Offset: 12 Size: 4 [unused]
// uint xe_vertex_index_endian; // Offset: 16 Size: 4 [unused]
// int xe_vertex_base_index; // Offset: 20 Size: 4 [unused]
// float2 xe_point_size; // Offset: 24 Size: 8 [unused]
// float2 xe_point_size_min_max; // Offset: 32 Size: 8 [unused]
// float2 xe_point_screen_to_ndc; // Offset: 40 Size: 8 [unused]
// float4 xe_user_clip_planes[6]; // Offset: 48 Size: 96 [unused]
// float3 xe_ndc_scale; // Offset: 144 Size: 12 [unused]
// uint xe_interpolator_sampling_pattern;// Offset: 156 Size: 4 [unused]
// float3 xe_ndc_offset; // Offset: 160 Size: 12 [unused]
// uint xe_ps_param_gen; // Offset: 172 Size: 4 [unused]
// uint4 xe_texture_swizzled_signs[2];// Offset: 176 Size: 32 [unused]
// uint xe_textures_resolved; // Offset: 208 Size: 4 [unused]
// uint2 xe_sample_count_log2; // Offset: 212 Size: 8 [unused]
// float xe_alpha_test_reference; // Offset: 220 Size: 4 [unused]
// float4 xe_color_exp_bias; // Offset: 224 Size: 16 [unused]
// uint xe_alpha_to_mask; // Offset: 240 Size: 4 [unused]
// uint xe_edram_pitch_tiles; // Offset: 244 Size: 4 [unused]
// float2 xe_edram_depth_range; // Offset: 248 Size: 8 [unused]
// float2 xe_edram_poly_offset_front; // Offset: 256 Size: 8 [unused]
// float2 xe_edram_poly_offset_back; // Offset: 264 Size: 8 [unused]
// uint xe_edram_depth_base_dwords; // Offset: 272 Size: 4 [unused]
// uint4 xe_edram_stencil[2]; // Offset: 288 Size: 32 [unused]
// uint4 xe_edram_rt_base_dwords_scaled;// Offset: 320 Size: 16 [unused]
// uint4 xe_edram_rt_format_flags; // Offset: 336 Size: 16 [unused]
// float4 xe_edram_rt_clamp[4]; // Offset: 352 Size: 64 [unused]
// uint4 xe_edram_rt_keep_mask[2]; // Offset: 416 Size: 32 [unused]
// uint4 xe_edram_rt_blend_factors_ops;// Offset: 448 Size: 16 [unused]
// float4 xe_edram_blend_constant; // Offset: 464 Size: 16 [unused]
//
// }
//
//
// Resource Bindings:
//
// Name Type Format Dim ID HLSL Bind Count
// ------------------------------ ---------- ------- ----------- ------- -------------- ------
// xe_system_cbuffer cbuffer NA NA CB0 cb0 1
//
//
//
// Patch Constant signature:
//
// Name Index Mask Register SysValue Format Used
// -------------------- ----- ------ -------- -------- ------- ------
// SV_TessFactor 0 x 0 QUADEDGE float x
// SV_TessFactor 1 x 1 QUADEDGE float x
// SV_TessFactor 2 x 2 QUADEDGE float x
// SV_TessFactor 3 x 3 QUADEDGE float x
// SV_InsideTessFactor 0 x 4 QUADINT float x
// SV_InsideTessFactor 1 x 5 QUADINT float x
//
//
// Input signature:
//
// Name Index Mask Register SysValue Format Used
// -------------------- ----- ------ -------- -------- ------- ------
// XEVERTEXID 0 x 0 NONE float x
//
//
// Output signature:
//
// Name Index Mask Register SysValue Format Used
// -------------------- ----- ------ -------- -------- ------- ------
// XEVERTEXID 0 x 0 NONE float x
//
// Tessellation Domain # of control points
// -------------------- --------------------
// Quadrilateral 4
//
// Tessellation Output Primitive Partitioning Type
// ------------------------------ ------------------
// Clockwise Triangles Even Fractional
//
hs_5_1
hs_decls
dcl_input_control_point_count 4
dcl_output_control_point_count 4
dcl_tessellator_domain domain_quad
dcl_tessellator_partitioning partitioning_fractional_even
dcl_tessellator_output_primitive output_triangle_cw
dcl_globalFlags refactoringAllowed
dcl_constantbuffer CB0[0:0][1], immediateIndexed, space=0
hs_fork_phase
dcl_hs_fork_phase_instance_count 4
dcl_input vForkInstanceID
dcl_output_siv o0.x, finalQuadUeq0EdgeTessFactor
dcl_output_siv o1.x, finalQuadVeq0EdgeTessFactor
dcl_output_siv o2.x, finalQuadUeq1EdgeTessFactor
dcl_output_siv o3.x, finalQuadVeq1EdgeTessFactor
dcl_temps 1
dcl_indexrange o0.x 4
mov r0.x, vForkInstanceID.x
mov o[r0.x + 0].x, CB0[0][0].z
ret
hs_fork_phase
dcl_hs_fork_phase_instance_count 2
dcl_input vForkInstanceID
dcl_output_siv o4.x, finalQuadUInsideTessFactor
dcl_output_siv o5.x, finalQuadVInsideTessFactor
dcl_temps 1
dcl_indexrange o4.x 2
mov r0.x, vForkInstanceID.x
mov o[r0.x + 4].x, CB0[0][0].z
ret
// Approximately 6 instruction slots used

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