mirror of
https://github.com/SSimco/Cemu.git
synced 2024-11-22 21:09:38 +00:00
Merge branch 'main' into android
This commit is contained in:
commit
aae6313fdb
41
.github/workflows/build.yml
vendored
41
.github/workflows/build.yml
vendored
@ -19,7 +19,7 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: "Checkout repo"
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: "recursive"
|
||||
fetch-depth: 0
|
||||
@ -28,7 +28,6 @@ jobs:
|
||||
run: |
|
||||
cd dependencies/vcpkg
|
||||
git fetch --unshallow
|
||||
git pull --all
|
||||
|
||||
- name: Setup release mode parameters (for deploy)
|
||||
if: ${{ inputs.deploymode == 'release' }}
|
||||
@ -55,6 +54,11 @@ jobs:
|
||||
sudo apt update -qq
|
||||
sudo apt install -y clang-15 cmake freeglut3-dev libgcrypt20-dev libglm-dev libgtk-3-dev libpulse-dev libsecret-1-dev libsystemd-dev libudev-dev nasm ninja-build
|
||||
|
||||
- name: "Setup cmake"
|
||||
uses: jwlawson/actions-setup-cmake@v2
|
||||
with:
|
||||
cmake-version: '3.29.0'
|
||||
|
||||
- name: "Bootstrap vcpkg"
|
||||
run: |
|
||||
bash ./dependencies/vcpkg/bootstrap-vcpkg.sh
|
||||
@ -75,7 +79,7 @@ jobs:
|
||||
|
||||
- name: "cmake"
|
||||
run: |
|
||||
cmake -S . -B build ${{ env.BUILD_FLAGS }} -DCMAKE_BUILD_TYPE=${{ env.BUILD_MODE }} -DPORTABLE=OFF -DCMAKE_C_COMPILER=/usr/bin/clang-15 -DCMAKE_CXX_COMPILER=/usr/bin/clang++-15 -G Ninja -DCMAKE_MAKE_PROGRAM=/usr/bin/ninja
|
||||
cmake -S . -B build ${{ env.BUILD_FLAGS }} -DCMAKE_BUILD_TYPE=${{ env.BUILD_MODE }} -DCMAKE_C_COMPILER=/usr/bin/clang-15 -DCMAKE_CXX_COMPILER=/usr/bin/clang++-15 -G Ninja -DCMAKE_MAKE_PROGRAM=/usr/bin/ninja
|
||||
|
||||
- name: "Build Cemu"
|
||||
run: |
|
||||
@ -86,7 +90,7 @@ jobs:
|
||||
run: mv bin/Cemu_release bin/Cemu
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
if: ${{ inputs.deploymode == 'release' }}
|
||||
with:
|
||||
name: cemu-bin-linux-x64
|
||||
@ -97,9 +101,9 @@ jobs:
|
||||
needs: build-ubuntu
|
||||
steps:
|
||||
- name: Checkout Upstream Repo
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/download-artifact@v3
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: cemu-bin-linux-x64
|
||||
path: bin
|
||||
@ -116,7 +120,7 @@ jobs:
|
||||
dist/linux/appimage.sh
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: cemu-appimage-x64
|
||||
path: artifacts
|
||||
@ -125,7 +129,7 @@ jobs:
|
||||
runs-on: windows-2022
|
||||
steps:
|
||||
- name: "Checkout repo"
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: "recursive"
|
||||
|
||||
@ -133,7 +137,6 @@ jobs:
|
||||
run: |
|
||||
cd dependencies/vcpkg
|
||||
git fetch --unshallow
|
||||
git pull --all
|
||||
|
||||
- name: Setup release mode parameters (for deploy)
|
||||
if: ${{ inputs.deploymode == 'release' }}
|
||||
@ -154,6 +157,11 @@ jobs:
|
||||
echo "[INFO] Experimental version ${{ inputs.experimentalversion }}"
|
||||
echo "BUILD_FLAGS=${{ env.BUILD_FLAGS }} -DEXPERIMENTAL_VERSION=${{ inputs.experimentalversion }}" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append
|
||||
|
||||
- name: "Setup cmake"
|
||||
uses: jwlawson/actions-setup-cmake@v2
|
||||
with:
|
||||
cmake-version: '3.29.0'
|
||||
|
||||
- name: "Bootstrap vcpkg"
|
||||
run: |
|
||||
./dependencies/vcpkg/bootstrap-vcpkg.bat
|
||||
@ -190,7 +198,7 @@ jobs:
|
||||
run: Rename-Item bin/Cemu_release.exe Cemu.exe
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
if: ${{ inputs.deploymode == 'release' }}
|
||||
with:
|
||||
name: cemu-bin-windows-x64
|
||||
@ -200,7 +208,7 @@ jobs:
|
||||
runs-on: macos-12
|
||||
steps:
|
||||
- name: "Checkout repo"
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: "recursive"
|
||||
|
||||
@ -208,7 +216,6 @@ jobs:
|
||||
run: |
|
||||
cd dependencies/vcpkg
|
||||
git fetch --unshallow
|
||||
git pull --all
|
||||
|
||||
- name: Setup release mode parameters (for deploy)
|
||||
if: ${{ inputs.deploymode == 'release' }}
|
||||
@ -234,6 +241,11 @@ jobs:
|
||||
brew update
|
||||
brew install llvm@15 ninja nasm molten-vk automake libtool
|
||||
|
||||
- name: "Setup cmake"
|
||||
uses: jwlawson/actions-setup-cmake@v2
|
||||
with:
|
||||
cmake-version: '3.29.0'
|
||||
|
||||
- name: "Bootstrap vcpkg"
|
||||
run: |
|
||||
bash ./dependencies/vcpkg/bootstrap-vcpkg.sh
|
||||
@ -258,7 +270,6 @@ jobs:
|
||||
cd build
|
||||
cmake .. ${{ env.BUILD_FLAGS }} \
|
||||
-DCMAKE_BUILD_TYPE=${{ env.BUILD_MODE }} \
|
||||
-DPORTABLE=OFF \
|
||||
-DMACOS_BUNDLE=ON \
|
||||
-DCMAKE_C_COMPILER=/usr/local/opt/llvm@15/bin/clang \
|
||||
-DCMAKE_CXX_COMPILER=/usr/local/opt/llvm@15/bin/clang++ \
|
||||
@ -275,14 +286,14 @@ jobs:
|
||||
mv bin/Cemu_release.app bin/Cemu_app/Cemu.app
|
||||
mv bin/Cemu_app/Cemu.app/Contents/MacOS/Cemu_release bin/Cemu_app/Cemu.app/Contents/MacOS/Cemu
|
||||
sed -i '' 's/Cemu_release/Cemu/g' bin/Cemu_app/Cemu.app/Contents/Info.plist
|
||||
chmod a+x bin/Cemu_app/Cemu.app/Contents/MacOS/Cemu
|
||||
chmod a+x bin/Cemu_app/Cemu.app/Contents/MacOS/{Cemu,update.sh}
|
||||
ln -s /Applications bin/Cemu_app/Applications
|
||||
hdiutil create ./bin/tmp.dmg -ov -volname "Cemu" -fs HFS+ -srcfolder "./bin/Cemu_app"
|
||||
hdiutil convert ./bin/tmp.dmg -format UDZO -o bin/Cemu.dmg
|
||||
rm bin/tmp.dmg
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
if: ${{ inputs.deploymode == 'release' }}
|
||||
with:
|
||||
name: cemu-bin-macos-x64
|
||||
|
@ -15,22 +15,22 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/download-artifact@v3
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: cemu-bin-linux-x64
|
||||
path: cemu-bin-linux-x64
|
||||
|
||||
- uses: actions/download-artifact@v3
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: cemu-appimage-x64
|
||||
path: cemu-appimage-x64
|
||||
|
||||
- uses: actions/download-artifact@v3
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: cemu-bin-windows-x64
|
||||
path: cemu-bin-windows-x64
|
||||
|
||||
- uses: actions/download-artifact@v3
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: cemu-bin-macos-x64
|
||||
path: cemu-bin-macos-x64
|
||||
|
8
.github/workflows/deploy_stable_release.yml
vendored
8
.github/workflows/deploy_stable_release.yml
vendored
@ -17,22 +17,22 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/download-artifact@v3
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: cemu-bin-linux-x64
|
||||
path: cemu-bin-linux-x64
|
||||
|
||||
- uses: actions/download-artifact@v3
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: cemu-appimage-x64
|
||||
path: cemu-appimage-x64
|
||||
|
||||
- uses: actions/download-artifact@v3
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: cemu-bin-windows-x64
|
||||
path: cemu-bin-windows-x64
|
||||
|
||||
- uses: actions/download-artifact@v3
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: cemu-bin-macos-x64
|
||||
path: cemu-bin-macos-x64
|
||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -39,6 +39,8 @@ bin/sdcard/*
|
||||
bin/screenshots/*
|
||||
bin/dump/*
|
||||
bin/cafeLibs/*
|
||||
bin/portable/*
|
||||
bin/keys.txt
|
||||
|
||||
!bin/shaderCache/info.txt
|
||||
bin/shaderCache/*
|
||||
|
155
BUILD.md
155
BUILD.md
@ -1,4 +1,26 @@
|
||||
# Build instructions
|
||||
# Build Instructions
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Windows](#windows)
|
||||
- [Linux](#linux)
|
||||
- [Dependencies](#dependencies)
|
||||
- [For Arch and derivatives:](#for-arch-and-derivatives)
|
||||
- [For Debian, Ubuntu and derivatives](#for-debian-ubuntu-and-derivatives)
|
||||
- [For Fedora and derivatives:](#for-fedora-and-derivatives)
|
||||
- [Build Cemu](#build-cemu)
|
||||
- [CMake and Clang](#cmake-and-clang)
|
||||
- [GCC](#gcc)
|
||||
- [Debug Build](#debug-build)
|
||||
- [Troubleshooting Steps](#troubleshooting-steps)
|
||||
- [Compiling Errors](#compiling-errors)
|
||||
- [Building Errors](#building-errors)
|
||||
- [macOS](#macos)
|
||||
- [On Apple Silicon Macs, Rosetta 2 and the x86_64 version of Homebrew must be used](#on-apple-silicon-macs-rosetta-2-and-the-x86_64-version-of-homebrew-must-be-used)
|
||||
- [Installing brew](#installing-brew)
|
||||
- [Installing Dependencies](#installing-dependencies)
|
||||
- [Build Cemu using CMake and Clang](#build-cemu-using-cmake-and-clang)
|
||||
- [Updating Cemu and source code](#updating-cemu-and-source-code)
|
||||
|
||||
## Windows
|
||||
|
||||
@ -19,57 +41,109 @@ Any other IDE should also work as long as it has CMake and MSVC support. CLion a
|
||||
|
||||
## Linux
|
||||
|
||||
To compile Cemu, a recent enough compiler and STL with C++20 support is required! clang-15 or higher is what we recommend.
|
||||
To compile Cemu, a recent enough compiler and STL with C++20 support is required! Clang-15 or higher is what we recommend.
|
||||
|
||||
### Installing dependencies
|
||||
|
||||
#### For Ubuntu and derivatives:
|
||||
`sudo apt install -y cmake curl clang-15 freeglut3-dev git libgcrypt20-dev libglm-dev libgtk-3-dev libpulse-dev libsecret-1-dev libsystemd-dev nasm ninja-build`
|
||||
|
||||
You may also need to install `libusb-1.0-0-dev` as a workaround for an issue with the vcpkg hidapi package.
|
||||
|
||||
At step 3 while building, use:
|
||||
`cmake -S . -B build -DCMAKE_BUILD_TYPE=release -DCMAKE_C_COMPILER=/usr/bin/clang-15 -DCMAKE_CXX_COMPILER=/usr/bin/clang++-15 -G Ninja -DCMAKE_MAKE_PROGRAM=/usr/bin/ninja`
|
||||
### Dependencies
|
||||
|
||||
#### For Arch and derivatives:
|
||||
`sudo pacman -S --needed base-devel clang cmake freeglut git glm gtk3 libgcrypt libpulse libsecret linux-headers llvm nasm ninja systemd unzip zip`
|
||||
|
||||
#### For Debian, Ubuntu and derivatives:
|
||||
`sudo apt install -y cmake curl clang-15 freeglut3-dev git libgcrypt20-dev libglm-dev libgtk-3-dev libpulse-dev libsecret-1-dev libsystemd-dev libtool nasm ninja-build`
|
||||
|
||||
You may also need to install `libusb-1.0-0-dev` as a workaround for an issue with the vcpkg hidapi package.
|
||||
|
||||
At Step 3 in [Build Cemu using cmake and clang](#build-cemu-using-cmake-and-clang), use the following command instead:
|
||||
`cmake -S . -B build -DCMAKE_BUILD_TYPE=release -DCMAKE_C_COMPILER=/usr/bin/clang-15 -DCMAKE_CXX_COMPILER=/usr/bin/clang++-15 -G Ninja -DCMAKE_MAKE_PROGRAM=/usr/bin/ninja`
|
||||
|
||||
#### For Fedora and derivatives:
|
||||
`sudo dnf install clang cmake cubeb-devel freeglut-devel git glm-devel gtk3-devel kernel-headers libgcrypt-devel libsecret-devel libtool libusb1-devel nasm ninja-build perl-core systemd-devel zlib-devel`
|
||||
`sudo dnf install clang cmake cubeb-devel freeglut-devel git glm-devel gtk3-devel kernel-headers libgcrypt-devel libsecret-devel libtool libusb1-devel llvm nasm ninja-build perl-core systemd-devel zlib-devel`
|
||||
|
||||
### Build Cemu using cmake and clang
|
||||
1. `git clone --recursive https://github.com/cemu-project/Cemu`
|
||||
2. `cd Cemu`
|
||||
3. `cmake -S . -B build -DCMAKE_BUILD_TYPE=release -DCMAKE_C_COMPILER=/usr/bin/clang -DCMAKE_CXX_COMPILER=/usr/bin/clang++ -G Ninja`
|
||||
4. `cmake --build build`
|
||||
5. You should now have a Cemu executable file in the /bin folder, which you can run using `./bin/Cemu_release`.
|
||||
### Build Cemu
|
||||
|
||||
#### Using GCC
|
||||
While we build and test Cemu using clang, using GCC might work better with your distro (they should be fairly similar performance/issues wise and should only be considered if compilation is the issue).
|
||||
#### CMake and Clang
|
||||
|
||||
You can use GCC by doing the following:
|
||||
- make sure you have g++ installed in your system
|
||||
- installation for Ubuntu and derivatives: `sudo apt install g++`
|
||||
- installation for Fedora and derivatives: `sudo dnf install gcc-c++`
|
||||
- replace the step 3 with the following:
|
||||
`cmake -S . -B build -DCMAKE_BUILD_TYPE=release -DCMAKE_C_COMPILER=/usr/bin/gcc -DCMAKE_CXX_COMPILER=/usr/bin/g++ -G Ninja`
|
||||
```
|
||||
git clone --recursive https://github.com/cemu-project/Cemu
|
||||
cd Cemu
|
||||
cmake -S . -B build -DCMAKE_BUILD_TYPE=release -DCMAKE_C_COMPILER=/usr/bin/clang -DCMAKE_CXX_COMPILER=/usr/bin/clang++ -G Ninja
|
||||
cmake --build build
|
||||
```
|
||||
|
||||
#### Troubleshooting steps
|
||||
- If step 3 gives you an error about not being able to find ninja, try appending `-DCMAKE_MAKE_PROGRAM=/usr/bin/ninja` to the command and running it again.
|
||||
- If step 3 fails while compiling the boost-build dependency, it means you don't have a working/good standard library installation. Check the integrity of your system headers and making sure that C++ related packages are installed and intact.
|
||||
- If step 3 gives a random error, read the `[package-name-and-platform]-out.log` and `[package-name-and-platform]-err.log` for the actual reason to see if you might be lacking the headers from a dependency.
|
||||
- If step 3 is still failing or if you're not able to find the cause, please make an issue on our Github about it!
|
||||
- If step 3 fails during rebuild after `git pull` with an error that mentions RPATH, add this to the end of step 3: `-DCMAKE_BUILD_WITH_INSTALL_RPATH=ON`
|
||||
- If step 4 gives you an error that contains something like `main.cpp.o: in function 'std::__cxx11::basic_string...`, you likely are experiencing a clang-14 issue. This can only be fixed by either lowering the clang version or using GCC, see below.
|
||||
- If step 4 gives you a different error, you could report it to this repo or try using GCC. Just make sure your standard library and compilers are updated since Cemu uses a lot of modern features!
|
||||
- If step 4 gives you undefined libdecor_xx, you are likely experiencing an issue with sdl2 package that comes with vcpkg. Delete sdl2 from vcpkg.json in source file and recompile.
|
||||
- If step 4 gives you `fatal error: 'span' file not found`, then you're either missing `libstdc++` or are using a version that's too old. Install at least v10 with your package manager, eg `sudo apt install libstdc++-10-dev`. See #644.
|
||||
#### GCC
|
||||
|
||||
If you are building using GCC, make sure you have g++ installed:
|
||||
- Installation for Arch and derivatives: `sudo pacman -S gcc`
|
||||
- Installation for Debian, Ubuntu and derivatives: `sudo apt install g++`
|
||||
- Installation for Fedora and derivatives: `sudo dnf install gcc-c++`
|
||||
|
||||
```
|
||||
git clone --recursive https://github.com/cemu-project/Cemu
|
||||
cd Cemu
|
||||
cmake -S . -B build -DCMAKE_BUILD_TYPE=release -DCMAKE_C_COMPILER=/usr/bin/gcc -DCMAKE_CXX_COMPILER=/usr/bin/g++ -G Ninja
|
||||
cmake --build build
|
||||
```
|
||||
|
||||
#### Debug Build
|
||||
|
||||
```
|
||||
git clone --recursive https://github.com/cemu-project/Cemu
|
||||
cd Cemu
|
||||
cmake -S . -B build -DCMAKE_BUILD_TYPE=debug -DCMAKE_C_COMPILER=/usr/bin/clang -DCMAKE_CXX_COMPILER=/usr/bin/clang++ -G Ninja
|
||||
cmake --build build
|
||||
```
|
||||
|
||||
If you are using GCC, replace `cmake -S . -B build -DCMAKE_BUILD_TYPE=debug -DCMAKE_C_COMPILER=/usr/bin/clang -DCMAKE_CXX_COMPILER=/usr/bin/clang++ -G Ninja` with `cmake -S . -B build -DCMAKE_BUILD_TYPE=debug -DCMAKE_C_COMPILER=/usr/bin/gcc -DCMAKE_CXX_COMPILER=/usr/bin/g++ -G Ninja`
|
||||
|
||||
#### Troubleshooting Steps
|
||||
|
||||
##### Compiling Errors
|
||||
|
||||
This section refers to running `cmake -S...` (truncated).
|
||||
|
||||
* `vcpkg install failed`
|
||||
* Run the following in the root directory and try running the command again (don't forget to change directories afterwards):
|
||||
* `cd dependencies/vcpkg && git fetch --unshallow`
|
||||
* `Please ensure you're using the latest port files with git pull and vcpkg update.`
|
||||
* Either:
|
||||
* Update vcpkg by running by the following command:
|
||||
* `git submodule update --remote dependencies/vcpkg`
|
||||
* If you are sure vcpkg is up to date, check the following logs:
|
||||
* `Cemu/dependencies/vcpkg/buildtrees/wxwidgets/config-x64-linux-out.log`
|
||||
* `Cemu/dependencies/vcpkg/buildtrees/libsystemd/config-x64-linux-dbg-meson-log.txt.log`
|
||||
* `Cemu/dependencies/vcpkg/buildtrees/libsystemd/config-x64-linux-dbg-out.log`
|
||||
* Not able to find Ninja.
|
||||
* Add the following and try running the command again:
|
||||
* `-DCMAKE_MAKE_PROGRAM=/usr/bin/ninja`
|
||||
* Compiling failed during the boost-build dependency.
|
||||
* It means you don't have a working/good standard library installation. Check the integrity of your system headers and making sure that C++ related packages are installed and intact.
|
||||
* Compiling failed during rebuild after `git pull` with an error that mentions RPATH
|
||||
* Add the following and try running the command again:
|
||||
* `-DCMAKE_BUILD_WITH_INSTALL_RPATH=ON`
|
||||
* If you are getting a random error, read the [package-name-and-platform]-out.log and [package-name-and-platform]-err.log for the actual reason to see if you might be lacking the headers from a dependency.
|
||||
|
||||
|
||||
If you are getting a different error than any of the errors listed above, you may either open an issue in this repo or try using [GCC](#gcc). Make sure your standard library and compilers are updated since Cemu uses a lot of modern features!
|
||||
|
||||
|
||||
##### Building Errors
|
||||
|
||||
This section refers to running `cmake --build build`.
|
||||
|
||||
* `main.cpp.o: in function 'std::__cxx11::basic_string...`
|
||||
* You likely are experiencing a clang-14 issue. This can only be fixed by either lowering the clang version or using GCC, see [GCC](#gcc).
|
||||
* `fatal error: 'span' file not found`
|
||||
* You're either missing `libstdc++` or are using a version that's too old. Install at least v10 with your package manager, eg `sudo apt install libstdc++-10-dev`. See [#644](https://github.com/cemu-project/Cemu/issues/644).
|
||||
* `undefined libdecor_xx`
|
||||
* You are likely experiencing an issue with sdl2 package that comes with vcpkg. Delete sdl2 from vcpkg.json in source file and recompile.
|
||||
|
||||
If you are getting a different error than any of the errors listed above, you may either open an issue in this repo or try using [GCC](#gcc). Make sure your standard library and compilers are updated since Cemu uses a lot of modern features!
|
||||
|
||||
## macOS
|
||||
|
||||
To compile Cemu, a recent enough compiler and STL with C++20 support is required! LLVM 13 and
|
||||
To compile Cemu, a recent enough compiler and STL with C++20 support is required! LLVM 13 and
|
||||
below, built in LLVM, and Xcode LLVM don't support the C++20 feature set required. The OpenGL graphics
|
||||
API isn't support on macOS, Vulkan must be used. Additionally Vulkan must be used through the
|
||||
API isn't support on macOS, Vulkan must be used. Additionally Vulkan must be used through the
|
||||
Molten-VK compatibility layer
|
||||
|
||||
### On Apple Silicon Macs, Rosetta 2 and the x86_64 version of Homebrew must be used
|
||||
@ -84,11 +158,11 @@ You can skip this section if you have an Intel Mac. Every time you compile, you
|
||||
1. `/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"`
|
||||
2. `eval "$(/usr/local/Homebrew/bin/brew shellenv)"` # set x86_64 brew env
|
||||
|
||||
### Installing dependencies
|
||||
### Installing Dependencies
|
||||
|
||||
`brew install boost git cmake llvm ninja nasm molten-vk automake libtool`
|
||||
|
||||
### Build Cemu using cmake and clang
|
||||
### Build Cemu using CMake and Clang
|
||||
1. `git clone --recursive https://github.com/cemu-project/Cemu`
|
||||
2. `cd Cemu`
|
||||
3. `cmake -S . -B build -DCMAKE_BUILD_TYPE=release -DCMAKE_C_COMPILER=/usr/local/opt/llvm/bin/clang -DCMAKE_CXX_COMPILER=/usr/local/opt/llvm/bin/clang++ -G Ninja`
|
||||
@ -104,3 +178,4 @@ You can skip this section if you have an Intel Mac. Every time you compile, you
|
||||
2. Then, you can rebuild Cemu using the steps listed above, according to whether you use Linux or Windows.
|
||||
|
||||
If CMake complains about Cemu already being compiled or another similar error, try deleting the `CMakeCache.txt` file inside the `build` folder and retry building.
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
cmake_minimum_required(VERSION 3.21.1)
|
||||
|
||||
option(ENABLE_VCPKG "Enable the vcpkg package manager" ON)
|
||||
option(PORTABLE "All data created and maintained by Cemu will be in the directory where the executable file is located" ON)
|
||||
option(MACOS_BUNDLE "The executable when built on macOS will be created as an application bundle" OFF)
|
||||
set(EXPERIMENTAL_VERSION "" CACHE STRING "") # used by CI script to set experimental version
|
||||
|
||||
@ -52,10 +51,6 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
|
||||
add_compile_definitions($<$<CONFIG:Debug>:CEMU_DEBUG_ASSERT>) # if build type is debug, set CEMU_DEBUG_ASSERT
|
||||
|
||||
if(PORTABLE)
|
||||
add_compile_definitions(PORTABLE)
|
||||
endif()
|
||||
|
||||
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
|
||||
|
||||
# enable link time optimization for release builds
|
||||
|
@ -48,7 +48,7 @@ Before submitting a pull request, please read and follow our code style guidelin
|
||||
|
||||
If coding isn't your thing, testing games and making detailed bug reports or updating the (usually outdated) compatibility wiki is also appreciated!
|
||||
|
||||
Questions about Cemu's software architecture can also be answered on Discord (through the Matrix bridge).
|
||||
Questions about Cemu's software architecture can also be answered on Discord (or through the Matrix bridge).
|
||||
|
||||
## License
|
||||
Cemu is licensed under [Mozilla Public License 2.0](/LICENSE.txt). Exempt from this are all files in the dependencies directory for which the licenses of the original code apply as well as some individual files in the src folder, as specified in those file headers respectively.
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
2
dependencies/vcpkg
vendored
2
dependencies/vcpkg
vendored
@ -1 +1 @@
|
||||
Subproject commit 53bef8994c541b6561884a8395ea35715ece75db
|
||||
Subproject commit a4275b7eee79fb24ec2e135481ef5fce8b41c339
|
1
dist/linux/appimage.sh
vendored
1
dist/linux/appimage.sh
vendored
@ -33,6 +33,7 @@ chmod +x AppDir/usr/bin/Cemu
|
||||
cp /usr/lib/x86_64-linux-gnu/{libsepol.so.1,libffi.so.7,libpcre.so.3,libGLU.so.1,libthai.so.0} AppDir/usr/lib
|
||||
|
||||
export UPD_INFO="gh-releases-zsync|cemu-project|Cemu|ci|Cemu.AppImage.zsync"
|
||||
export NO_STRIP=1
|
||||
./linuxdeploy-x86_64.AppImage --appimage-extract-and-run \
|
||||
--appdir="${GITHUB_WORKSPACE}"/AppDir/ \
|
||||
-d "${GITHUB_WORKSPACE}"/AppDir/info.cemu.Cemu.desktop \
|
||||
|
17
dist/network_services.xml
vendored
Normal file
17
dist/network_services.xml
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<content>
|
||||
<networkname>CustomExample</networkname>
|
||||
<disablesslverification>0</disablesslverification>
|
||||
<urls>
|
||||
<act>https://account.nintendo.net</act>
|
||||
<ecs>https://ecs.wup.shop.nintendo.net/ecs/services/ECommerceSOAP</ecs>
|
||||
<nus>https://nus.wup.shop.nintendo.net/nus/services/NetUpdateSOAP</nus>
|
||||
<ias>https://ias.wup.shop.nintendo.net/ias/services/IdentityAuthenticationSOAP</ias>
|
||||
<ccsu>https://ccs.wup.shop.nintendo.net/ccs/download</ccsu>
|
||||
<ccs>http://ccs.cdn.wup.shop.nintendo.net/ccs/download</ccs>
|
||||
<idbe>https://idbe-wup.cdn.nintendo.net/icondata</idbe>
|
||||
<boss>https://npts.app.nintendo.net/p01/tasksheet</boss>
|
||||
<tagaya>https://tagaya.wup.shop.nintendo.net/tagaya/versionlist</tagaya>
|
||||
<olv>https://discovery.olv.nintendo.net/v1/endpoint</olv>
|
||||
</urls>
|
||||
</content>
|
@ -68,6 +68,12 @@ else()
|
||||
mainLLE.cpp
|
||||
)
|
||||
|
||||
if(MSVC AND MSVC_VERSION EQUAL 1940)
|
||||
# workaround for an msvc issue on VS 17.10 where generated ILK files are too large
|
||||
# see https://developercommunity.visualstudio.com/t/After-updating-to-VS-1710-the-size-of-/10665511
|
||||
set_target_properties(CemuBin PROPERTIES LINK_FLAGS "/INCREMENTAL:NO")
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
target_sources(CemuBin PRIVATE
|
||||
resource/cemu.rc
|
||||
@ -110,8 +116,9 @@ if (MACOS_BUNDLE)
|
||||
add_custom_command (TARGET CemuBin POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} ARGS -E copy "/usr/local/lib/libMoltenVK.dylib" "${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/Frameworks/libMoltenVK.dylib"
|
||||
COMMAND ${CMAKE_COMMAND} ARGS -E copy "${CMAKE_BINARY_DIR}/vcpkg_installed/x64-osx/lib/libusb-1.0.0.dylib" "${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/Frameworks/libusb-1.0.0.dylib"
|
||||
COMMAND ${CMAKE_COMMAND} ARGS -E copy "${CMAKE_SOURCE_DIR}/src/resource/update.sh" "${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/MacOS/update.sh"
|
||||
COMMAND bash -c "install_name_tool -add_rpath @executable_path/../Frameworks ${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/MacOS/${OUTPUT_NAME}"
|
||||
COMMAND bash -c "install_name_tool -change /usr/local/opt/libusb/lib/libusb-1.0.0.dylib @executable_path/../Frameworks/libusb-1.0.0.dylib ${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/MacOS/${OUTPUT_NAME}")
|
||||
COMMAND bash -c "install_name_tool -change /Users/runner/work/Cemu/Cemu/build/vcpkg_installed/x64-osx/lib/libusb-1.0.0.dylib @executable_path/../Frameworks/libusb-1.0.0.dylib ${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/MacOS/${OUTPUT_NAME}")
|
||||
endif()
|
||||
|
||||
set_target_properties(CemuBin PROPERTIES
|
||||
|
@ -10,6 +10,7 @@ add_library(CemuCafe
|
||||
Filesystem/fscDeviceRedirect.cpp
|
||||
Filesystem/fscDeviceWua.cpp
|
||||
Filesystem/fscDeviceWud.cpp
|
||||
Filesystem/fscDeviceWuhb.cpp
|
||||
Filesystem/fsc.h
|
||||
Filesystem/FST/FST.cpp
|
||||
Filesystem/FST/FST.h
|
||||
@ -18,6 +19,9 @@ add_library(CemuCafe
|
||||
Filesystem/FST/KeyCache.h
|
||||
Filesystem/WUD/wud.cpp
|
||||
Filesystem/WUD/wud.h
|
||||
Filesystem/WUHB/RomFSStructs.h
|
||||
Filesystem/WUHB/WUHBReader.cpp
|
||||
Filesystem/WUHB/WUHBReader.h
|
||||
GamePatch.cpp
|
||||
GamePatch.h
|
||||
GameProfile/GameProfile.cpp
|
||||
@ -212,6 +216,8 @@ add_library(CemuCafe
|
||||
HW/SI/SI.cpp
|
||||
HW/SI/si.h
|
||||
HW/VI/VI.cpp
|
||||
IOSU/ccr_nfc/iosu_ccr_nfc.cpp
|
||||
IOSU/ccr_nfc/iosu_ccr_nfc.h
|
||||
IOSU/fsa/fsa_types.h
|
||||
IOSU/fsa/iosu_fsa.cpp
|
||||
IOSU/fsa/iosu_fsa.h
|
||||
@ -372,6 +378,16 @@ add_library(CemuCafe
|
||||
OS/libs/h264_avc/parser/H264Parser.h
|
||||
OS/libs/mic/mic.cpp
|
||||
OS/libs/mic/mic.h
|
||||
OS/libs/nfc/ndef.cpp
|
||||
OS/libs/nfc/ndef.h
|
||||
OS/libs/nfc/nfc.cpp
|
||||
OS/libs/nfc/nfc.h
|
||||
OS/libs/nfc/stream.cpp
|
||||
OS/libs/nfc/stream.h
|
||||
OS/libs/nfc/TagV0.cpp
|
||||
OS/libs/nfc/TagV0.h
|
||||
OS/libs/nfc/TLV.cpp
|
||||
OS/libs/nfc/TLV.h
|
||||
OS/libs/nlibcurl/nlibcurl.cpp
|
||||
OS/libs/nlibcurl/nlibcurlDebug.hpp
|
||||
OS/libs/nlibcurl/nlibcurl.h
|
||||
@ -402,6 +418,8 @@ add_library(CemuCafe
|
||||
OS/libs/nn_ndm/nn_ndm.h
|
||||
OS/libs/nn_spm/nn_spm.cpp
|
||||
OS/libs/nn_spm/nn_spm.h
|
||||
OS/libs/nn_sl/nn_sl.cpp
|
||||
OS/libs/nn_sl/nn_sl.h
|
||||
OS/libs/nn_nfp/AmiiboCrypto.h
|
||||
OS/libs/nn_nfp/nn_nfp.cpp
|
||||
OS/libs/nn_nfp/nn_nfp.h
|
||||
@ -437,14 +455,20 @@ add_library(CemuCafe
|
||||
OS/libs/nsyshid/AttachDefaultBackends.cpp
|
||||
OS/libs/nsyshid/Whitelist.cpp
|
||||
OS/libs/nsyshid/Whitelist.h
|
||||
OS/libs/nsyshid/BackendEmulated.cpp
|
||||
OS/libs/nsyshid/BackendEmulated.h
|
||||
OS/libs/nsyshid/BackendLibusb.cpp
|
||||
OS/libs/nsyshid/BackendLibusb.h
|
||||
OS/libs/nsyshid/BackendWindowsHID.cpp
|
||||
OS/libs/nsyshid/BackendWindowsHID.h
|
||||
OS/libs/nsyshid/Skylander.cpp
|
||||
OS/libs/nsyshid/Skylander.h
|
||||
OS/libs/nsyskbd/nsyskbd.cpp
|
||||
OS/libs/nsyskbd/nsyskbd.h
|
||||
OS/libs/nsysnet/nsysnet.cpp
|
||||
OS/libs/nsysnet/nsysnet.h
|
||||
OS/libs/ntag/ntag.cpp
|
||||
OS/libs/ntag/ntag.h
|
||||
OS/libs/padscore/padscore.cpp
|
||||
OS/libs/padscore/padscore.h
|
||||
OS/libs/proc_ui/proc_ui.cpp
|
||||
|
@ -34,6 +34,7 @@
|
||||
#include "Cafe/IOSU/legacy/iosu_boss.h"
|
||||
#include "Cafe/IOSU/legacy/iosu_nim.h"
|
||||
#include "Cafe/IOSU/PDM/iosu_pdm.h"
|
||||
#include "Cafe/IOSU/ccr_nfc/iosu_ccr_nfc.h"
|
||||
|
||||
// IOSU initializer functions
|
||||
#include "Cafe/IOSU/kernel/iosu_kernel.h"
|
||||
@ -50,6 +51,8 @@
|
||||
#include "Cafe/OS/libs/gx2/GX2.h"
|
||||
#include "Cafe/OS/libs/gx2/GX2_Misc.h"
|
||||
#include "Cafe/OS/libs/mic/mic.h"
|
||||
#include "Cafe/OS/libs/nfc/nfc.h"
|
||||
#include "Cafe/OS/libs/ntag/ntag.h"
|
||||
#include "Cafe/OS/libs/nn_aoc/nn_aoc.h"
|
||||
#include "Cafe/OS/libs/nn_pdm/nn_pdm.h"
|
||||
#include "Cafe/OS/libs/nn_cmpt/nn_cmpt.h"
|
||||
@ -544,8 +547,10 @@ namespace CafeSystem
|
||||
{
|
||||
// entries in this list are ordered by initialization order. Shutdown in reverse order
|
||||
iosu::kernel::GetModule(),
|
||||
iosu::acp::GetModule(),
|
||||
iosu::fpd::GetModule(),
|
||||
iosu::pdm::GetModule(),
|
||||
iosu::ccr_nfc::GetModule(),
|
||||
};
|
||||
|
||||
// initialize all subsystems which are persistent and don't depend on a game running
|
||||
@ -600,6 +605,8 @@ namespace CafeSystem
|
||||
H264::Initialize();
|
||||
snd_core::Initialize();
|
||||
mic::Initialize();
|
||||
nfc::Initialize();
|
||||
ntag::Initialize();
|
||||
// init hardware register interfaces
|
||||
HW_SI::Initialize();
|
||||
}
|
||||
@ -762,7 +769,6 @@ namespace CafeSystem
|
||||
}
|
||||
}
|
||||
LoadMainExecutable();
|
||||
gameProfile_load();
|
||||
return STATUS_CODE::SUCCESS;
|
||||
}
|
||||
|
||||
@ -791,6 +797,7 @@ namespace CafeSystem
|
||||
STATUS_CODE r = LoadAndMountForegroundTitle(titleId);
|
||||
if (r != STATUS_CODE::SUCCESS)
|
||||
return r;
|
||||
gameProfile_load();
|
||||
// setup memory space and PPC recompiler
|
||||
SetupMemorySpace();
|
||||
PPCRecompiler_init();
|
||||
@ -930,6 +937,27 @@ namespace CafeSystem
|
||||
return sGameInfo_ForegroundTitle.GetBase().GetArgStr();
|
||||
}
|
||||
|
||||
CosCapabilityBits GetForegroundTitleCosCapabilities(CosCapabilityGroup group)
|
||||
{
|
||||
if (sLaunchModeIsStandalone)
|
||||
return CosCapabilityBits::All;
|
||||
auto& update = sGameInfo_ForegroundTitle.GetUpdate();
|
||||
if (update.IsValid())
|
||||
{
|
||||
ParsedCosXml* cosXml = update.GetCosInfo();
|
||||
if (cosXml)
|
||||
return cosXml->GetCapabilityBits(group);
|
||||
}
|
||||
auto& base = sGameInfo_ForegroundTitle.GetBase();
|
||||
if(base.IsValid())
|
||||
{
|
||||
ParsedCosXml* cosXml = base.GetCosInfo();
|
||||
if (cosXml)
|
||||
return cosXml->GetCapabilityBits(group);
|
||||
}
|
||||
return CosCapabilityBits::All;
|
||||
}
|
||||
|
||||
// when switching titles custom parameters can be passed, returns true if override args are used
|
||||
bool GetOverrideArgStr(std::vector<std::string>& args)
|
||||
{
|
||||
|
@ -4,6 +4,9 @@
|
||||
#include "Cafe/TitleList/TitleId.h"
|
||||
#include "config/CemuConfig.h"
|
||||
|
||||
enum class CosCapabilityBits : uint64;
|
||||
enum class CosCapabilityGroup : uint32;
|
||||
|
||||
namespace CafeSystem
|
||||
{
|
||||
class SystemImplementation
|
||||
@ -52,6 +55,7 @@ namespace CafeSystem
|
||||
std::string GetForegroundTitleName();
|
||||
std::string GetForegroundTitleArgStr();
|
||||
uint32 GetForegroundTitleOlvAccesskey();
|
||||
CosCapabilityBits GetForegroundTitleCosCapabilities(CosCapabilityGroup group);
|
||||
|
||||
void ShutdownTitle();
|
||||
|
||||
|
40
src/Cafe/Filesystem/WUHB/RomFSStructs.h
Normal file
40
src/Cafe/Filesystem/WUHB/RomFSStructs.h
Normal file
@ -0,0 +1,40 @@
|
||||
#pragma once
|
||||
|
||||
struct romfs_header_t
|
||||
{
|
||||
uint32 header_magic;
|
||||
uint32be header_size;
|
||||
uint64be dir_hash_table_ofs;
|
||||
uint64be dir_hash_table_size;
|
||||
uint64be dir_table_ofs;
|
||||
uint64be dir_table_size;
|
||||
uint64be file_hash_table_ofs;
|
||||
uint64be file_hash_table_size;
|
||||
uint64be file_table_ofs;
|
||||
uint64be file_table_size;
|
||||
uint64be file_partition_ofs;
|
||||
};
|
||||
|
||||
struct romfs_direntry_t
|
||||
{
|
||||
uint32be parent;
|
||||
uint32be listNext; // offset to next directory entry in linked list of parent directory (aka "sibling")
|
||||
uint32be dirListHead; // offset to first entry in linked list of directory entries (aka "child")
|
||||
uint32be fileListHead; // offset to first entry in linked list of file entries (aka "file")
|
||||
uint32be hash;
|
||||
uint32be name_size;
|
||||
std::string name;
|
||||
};
|
||||
|
||||
struct romfs_fentry_t
|
||||
{
|
||||
uint32be parent;
|
||||
uint32be listNext; // offset to next file entry in linked list of parent directory (aka "sibling")
|
||||
uint64be offset;
|
||||
uint64be size;
|
||||
uint32be hash;
|
||||
uint32be name_size;
|
||||
std::string name;
|
||||
};
|
||||
|
||||
#define ROMFS_ENTRY_EMPTY 0xFFFFFFFF
|
224
src/Cafe/Filesystem/WUHB/WUHBReader.cpp
Normal file
224
src/Cafe/Filesystem/WUHB/WUHBReader.cpp
Normal file
@ -0,0 +1,224 @@
|
||||
#include "WUHBReader.h"
|
||||
WUHBReader* WUHBReader::FromPath(const fs::path& path)
|
||||
{
|
||||
FileStream* fileIn{FileStream::openFile2(path)};
|
||||
if (!fileIn)
|
||||
return nullptr;
|
||||
|
||||
WUHBReader* ret = new WUHBReader(fileIn);
|
||||
if (!ret->CheckMagicValue())
|
||||
{
|
||||
delete ret;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!ret->ReadHeader())
|
||||
{
|
||||
delete ret;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const romfs_direntry_t fallbackDirEntry{
|
||||
.parent = ROMFS_ENTRY_EMPTY,
|
||||
.listNext = ROMFS_ENTRY_EMPTY,
|
||||
.dirListHead = ROMFS_ENTRY_EMPTY,
|
||||
.fileListHead = ROMFS_ENTRY_EMPTY,
|
||||
.hash = ROMFS_ENTRY_EMPTY,
|
||||
.name_size = 0,
|
||||
.name = ""
|
||||
};
|
||||
static const romfs_fentry_t fallbackFileEntry{
|
||||
.parent = ROMFS_ENTRY_EMPTY,
|
||||
.listNext = ROMFS_ENTRY_EMPTY,
|
||||
.offset = 0,
|
||||
.size = 0,
|
||||
.hash = ROMFS_ENTRY_EMPTY,
|
||||
.name_size = 0,
|
||||
.name = ""
|
||||
};
|
||||
template<bool File>
|
||||
const WUHBReader::EntryType<File>& WUHBReader::GetFallback()
|
||||
{
|
||||
if constexpr (File)
|
||||
return fallbackFileEntry;
|
||||
else
|
||||
return fallbackDirEntry;
|
||||
}
|
||||
|
||||
template<bool File>
|
||||
WUHBReader::EntryType<File> WUHBReader::GetEntry(uint32 offset) const
|
||||
{
|
||||
auto fallback = GetFallback<File>();
|
||||
if(offset == ROMFS_ENTRY_EMPTY)
|
||||
return fallback;
|
||||
|
||||
const char* typeName = File ? "fentry" : "direntry";
|
||||
EntryType<File> ret;
|
||||
if (offset >= (File ? m_header.file_table_size : m_header.dir_table_size))
|
||||
{
|
||||
cemuLog_log(LogType::Force, "WUHB {} offset exceeds table size declared in header", typeName);
|
||||
return fallback;
|
||||
}
|
||||
|
||||
// read the entry
|
||||
m_fileIn->SetPosition((File ? m_header.file_table_ofs : m_header.dir_table_ofs) + offset);
|
||||
auto read = m_fileIn->readData(&ret, offsetof(EntryType<File>, name));
|
||||
if (read != offsetof(EntryType<File>, name))
|
||||
{
|
||||
cemuLog_log(LogType::Force, "failed to read WUHB {} at offset: {}", typeName, offset);
|
||||
return fallback;
|
||||
}
|
||||
|
||||
// read the name
|
||||
ret.name.resize(ret.name_size);
|
||||
read = m_fileIn->readData(ret.name.data(), ret.name_size);
|
||||
if (read != ret.name_size)
|
||||
{
|
||||
cemuLog_log(LogType::Force, "failed to read WUHB {} name", typeName);
|
||||
return fallback;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
romfs_direntry_t WUHBReader::GetDirEntry(uint32 offset) const
|
||||
{
|
||||
return GetEntry<false>(offset);
|
||||
}
|
||||
romfs_fentry_t WUHBReader::GetFileEntry(uint32 offset) const
|
||||
{
|
||||
return GetEntry<true>(offset);
|
||||
}
|
||||
|
||||
uint64 WUHBReader::GetFileSize(uint32 entryOffset) const
|
||||
{
|
||||
return GetFileEntry(entryOffset).size;
|
||||
}
|
||||
|
||||
uint64 WUHBReader::ReadFromFile(uint32 entryOffset, uint64 fileOffset, uint64 length, void* buffer) const
|
||||
{
|
||||
const auto fileEntry = GetFileEntry(entryOffset);
|
||||
if (fileOffset >= fileEntry.size)
|
||||
return 0;
|
||||
const uint64 readAmount = std::min(length, fileEntry.size - fileOffset);
|
||||
const uint64 wuhbOffset = m_header.file_partition_ofs + fileEntry.offset + fileOffset;
|
||||
m_fileIn->SetPosition(wuhbOffset);
|
||||
return m_fileIn->readData(buffer, readAmount);
|
||||
}
|
||||
|
||||
uint32 WUHBReader::GetHashTableEntryOffset(uint32 hash, bool isFile) const
|
||||
{
|
||||
const uint64 hash_table_size = (isFile ? m_header.file_hash_table_size : m_header.dir_hash_table_size);
|
||||
const uint64 hash_table_ofs = (isFile ? m_header.file_hash_table_ofs : m_header.dir_hash_table_ofs);
|
||||
|
||||
const uint64 hash_table_entry_count = hash_table_size / sizeof(uint32);
|
||||
const uint64 hash_table_entry_offset = hash_table_ofs + (hash % hash_table_entry_count) * sizeof(uint32);
|
||||
|
||||
m_fileIn->SetPosition(hash_table_entry_offset);
|
||||
uint32 tableOffset;
|
||||
if (!m_fileIn->readU32(tableOffset))
|
||||
{
|
||||
cemuLog_log(LogType::Force, "failed to read WUHB hash table entry at file offset: {}", hash_table_entry_offset);
|
||||
return ROMFS_ENTRY_EMPTY;
|
||||
}
|
||||
|
||||
return uint32be::from_bevalue(tableOffset);
|
||||
}
|
||||
|
||||
template<bool T>
|
||||
bool WUHBReader::SearchHashList(uint32& entryOffset, const fs::path& targetName) const
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
if (entryOffset == ROMFS_ENTRY_EMPTY)
|
||||
return false;
|
||||
auto entry = GetEntry<T>(entryOffset);
|
||||
|
||||
if (entry.name == targetName)
|
||||
return true;
|
||||
entryOffset = entry.hash;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32 WUHBReader::Lookup(const std::filesystem::path& path, bool isFile) const
|
||||
{
|
||||
uint32 currentEntryOffset = 0;
|
||||
auto look = [&](const fs::path& part, bool lookInFileHT) {
|
||||
const auto partString = part.string();
|
||||
currentEntryOffset = GetHashTableEntryOffset(CalcPathHash(currentEntryOffset, partString.c_str(), 0, partString.size()), lookInFileHT);
|
||||
if (lookInFileHT)
|
||||
return SearchHashList<true>(currentEntryOffset, part);
|
||||
else
|
||||
return SearchHashList<false>(currentEntryOffset, part);
|
||||
};
|
||||
// look for the root entry
|
||||
if (!look("", false))
|
||||
return ROMFS_ENTRY_EMPTY;
|
||||
|
||||
auto it = path.begin();
|
||||
while (it != path.end())
|
||||
{
|
||||
fs::path part = *it;
|
||||
++it;
|
||||
// no need to recurse after trailing forward slash (e.g. directory/)
|
||||
if (part.empty() && !isFile)
|
||||
break;
|
||||
// skip leading forward slash
|
||||
if (part == "/")
|
||||
continue;
|
||||
|
||||
// if the lookup target is a file and this is the last iteration, look in the file hash table instead.
|
||||
if (!look(part, it == path.end() && isFile))
|
||||
return ROMFS_ENTRY_EMPTY;
|
||||
}
|
||||
return currentEntryOffset;
|
||||
}
|
||||
bool WUHBReader::CheckMagicValue() const
|
||||
{
|
||||
uint8 magic[4];
|
||||
m_fileIn->SetPosition(0);
|
||||
int read = m_fileIn->readData(magic, 4);
|
||||
if (read != 4)
|
||||
{
|
||||
cemuLog_log(LogType::Force, "Failed to read WUHB magic numbers");
|
||||
return false;
|
||||
}
|
||||
static_assert(sizeof(magic) == s_headerMagicValue.size());
|
||||
return std::memcmp(&magic, s_headerMagicValue.data(), sizeof(magic)) == 0;
|
||||
}
|
||||
bool WUHBReader::ReadHeader()
|
||||
{
|
||||
m_fileIn->SetPosition(0);
|
||||
auto read = m_fileIn->readData(&m_header, sizeof(m_header));
|
||||
auto readSuccess = read == sizeof(m_header);
|
||||
if (!readSuccess)
|
||||
cemuLog_log(LogType::Force, "Failed to read WUHB header");
|
||||
return readSuccess;
|
||||
}
|
||||
unsigned char WUHBReader::NormalizeChar(unsigned char c)
|
||||
{
|
||||
if (c >= 'a' && c <= 'z')
|
||||
{
|
||||
return c + 'A' - 'a';
|
||||
}
|
||||
else
|
||||
{
|
||||
return c;
|
||||
}
|
||||
}
|
||||
uint32 WUHBReader::CalcPathHash(uint32 parent, const char* path, uint32 start, size_t path_len)
|
||||
{
|
||||
cemu_assert(path != nullptr || path_len == 0);
|
||||
uint32 hash = parent ^ 123456789;
|
||||
for (uint32 i = 0; i < path_len; i++)
|
||||
{
|
||||
hash = (hash >> 5) | (hash << 27);
|
||||
hash ^= NormalizeChar(path[start + i]);
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
45
src/Cafe/Filesystem/WUHB/WUHBReader.h
Normal file
45
src/Cafe/Filesystem/WUHB/WUHBReader.h
Normal file
@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
#include <Common/FileStream.h>
|
||||
#include "RomFSStructs.h"
|
||||
class WUHBReader
|
||||
{
|
||||
public:
|
||||
static WUHBReader* FromPath(const fs::path& path);
|
||||
|
||||
romfs_direntry_t GetDirEntry(uint32 offset) const;
|
||||
romfs_fentry_t GetFileEntry(uint32 offset) const;
|
||||
|
||||
uint64 GetFileSize(uint32 entryOffset) const;
|
||||
|
||||
uint64 ReadFromFile(uint32 entryOffset, uint64 fileOffset, uint64 length, void* buffer) const;
|
||||
|
||||
uint32 Lookup(const std::filesystem::path& path, bool isFile) const;
|
||||
|
||||
private:
|
||||
WUHBReader(FileStream* file)
|
||||
: m_fileIn(file)
|
||||
{
|
||||
cemu_assert_debug(file != nullptr);
|
||||
};
|
||||
WUHBReader() = delete;
|
||||
|
||||
romfs_header_t m_header;
|
||||
std::unique_ptr<FileStream> m_fileIn;
|
||||
constexpr static std::string_view s_headerMagicValue = "WUHB";
|
||||
bool ReadHeader();
|
||||
bool CheckMagicValue() const;
|
||||
|
||||
static inline unsigned char NormalizeChar(unsigned char c);
|
||||
static uint32 CalcPathHash(uint32 parent, const char* path, uint32 start, size_t path_len);
|
||||
|
||||
template<bool File>
|
||||
using EntryType = std::conditional_t<File, romfs_fentry_t, romfs_direntry_t>;
|
||||
template<bool File>
|
||||
static const EntryType<File>& GetFallback();
|
||||
template<bool File>
|
||||
EntryType<File> GetEntry(uint32 offset) const;
|
||||
|
||||
template<bool T>
|
||||
bool SearchHashList(uint32& entryOffset, const fs::path& targetName) const;
|
||||
uint32 GetHashTableEntryOffset(uint32 hash, bool isFile) const;
|
||||
};
|
@ -204,6 +204,9 @@ bool FSCDeviceWUD_Mount(std::string_view mountPath, std::string_view destination
|
||||
// wua device
|
||||
bool FSCDeviceWUA_Mount(std::string_view mountPath, std::string_view destinationBaseDir, class ZArchiveReader* archive, sint32 priority);
|
||||
|
||||
// wuhb device
|
||||
bool FSCDeviceWUHB_Mount(std::string_view mountPath, std::string_view destinationBaseDir, class WUHBReader* wuhbReader, sint32 priority);
|
||||
|
||||
// hostFS device
|
||||
bool FSCDeviceHostFS_Mount(std::string_view mountPath, std::string_view hostTargetPath, sint32 priority);
|
||||
|
||||
|
151
src/Cafe/Filesystem/fscDeviceWuhb.cpp
Normal file
151
src/Cafe/Filesystem/fscDeviceWuhb.cpp
Normal file
@ -0,0 +1,151 @@
|
||||
#include "Filesystem/WUHB/WUHBReader.h"
|
||||
#include "Cafe/Filesystem/fsc.h"
|
||||
#include "Cafe/Filesystem/FST/FST.h"
|
||||
|
||||
class FSCDeviceWuhbFileCtx : public FSCVirtualFile
|
||||
{
|
||||
public:
|
||||
FSCDeviceWuhbFileCtx(WUHBReader* reader, uint32 entryOffset, uint32 fscType)
|
||||
: m_wuhbReader(reader), m_entryOffset(entryOffset), m_fscType(fscType)
|
||||
{
|
||||
cemu_assert(entryOffset != ROMFS_ENTRY_EMPTY);
|
||||
if (fscType == FSC_TYPE_DIRECTORY)
|
||||
{
|
||||
romfs_direntry_t entry = reader->GetDirEntry(entryOffset);
|
||||
m_dirIterOffset = entry.dirListHead;
|
||||
m_fileIterOffset = entry.fileListHead;
|
||||
}
|
||||
}
|
||||
sint32 fscGetType() override
|
||||
{
|
||||
return m_fscType;
|
||||
}
|
||||
uint64 fscQueryValueU64(uint32 id) override
|
||||
{
|
||||
if (m_fscType == FSC_TYPE_FILE)
|
||||
{
|
||||
if (id == FSC_QUERY_SIZE)
|
||||
return m_wuhbReader->GetFileSize(m_entryOffset);
|
||||
else if (id == FSC_QUERY_WRITEABLE)
|
||||
return 0; // WUHB images are read-only
|
||||
else
|
||||
cemu_assert_error();
|
||||
}
|
||||
else
|
||||
{
|
||||
cemu_assert_unimplemented();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
uint32 fscWriteData(void* buffer, uint32 size) override
|
||||
{
|
||||
cemu_assert_error();
|
||||
return 0;
|
||||
}
|
||||
uint32 fscReadData(void* buffer, uint32 size) override
|
||||
{
|
||||
if (m_fscType != FSC_TYPE_FILE)
|
||||
return 0;
|
||||
auto read = m_wuhbReader->ReadFromFile(m_entryOffset, m_seek, size, buffer);
|
||||
m_seek += read;
|
||||
return read;
|
||||
}
|
||||
void fscSetSeek(uint64 seek) override
|
||||
{
|
||||
m_seek = seek;
|
||||
}
|
||||
uint64 fscGetSeek() override
|
||||
{
|
||||
if (m_fscType != FSC_TYPE_FILE)
|
||||
return 0;
|
||||
return m_seek;
|
||||
}
|
||||
void fscSetFileLength(uint64 endOffset) override
|
||||
{
|
||||
cemu_assert_error();
|
||||
}
|
||||
bool fscDirNext(FSCDirEntry* dirEntry) override
|
||||
{
|
||||
if (m_dirIterOffset != ROMFS_ENTRY_EMPTY)
|
||||
{
|
||||
romfs_direntry_t entry = m_wuhbReader->GetDirEntry(m_dirIterOffset);
|
||||
m_dirIterOffset = entry.listNext;
|
||||
if(entry.name_size > 0)
|
||||
{
|
||||
dirEntry->isDirectory = true;
|
||||
dirEntry->isFile = false;
|
||||
dirEntry->fileSize = 0;
|
||||
std::strncpy(dirEntry->path, entry.name.c_str(), FSC_MAX_DIR_NAME_LENGTH);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (m_fileIterOffset != ROMFS_ENTRY_EMPTY)
|
||||
{
|
||||
romfs_fentry_t entry = m_wuhbReader->GetFileEntry(m_fileIterOffset);
|
||||
m_fileIterOffset = entry.listNext;
|
||||
if(entry.name_size > 0)
|
||||
{
|
||||
dirEntry->isDirectory = false;
|
||||
dirEntry->isFile = true;
|
||||
dirEntry->fileSize = entry.size;
|
||||
std::strncpy(dirEntry->path, entry.name.c_str(), FSC_MAX_DIR_NAME_LENGTH);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
WUHBReader* m_wuhbReader{};
|
||||
uint32 m_fscType;
|
||||
uint32 m_entryOffset = ROMFS_ENTRY_EMPTY;
|
||||
uint32 m_dirIterOffset = ROMFS_ENTRY_EMPTY;
|
||||
uint32 m_fileIterOffset = ROMFS_ENTRY_EMPTY;
|
||||
uint64 m_seek = 0;
|
||||
};
|
||||
|
||||
class fscDeviceWUHB : public fscDeviceC
|
||||
{
|
||||
FSCVirtualFile* fscDeviceOpenByPath(std::string_view path, FSC_ACCESS_FLAG accessFlags, void* ctx, sint32* fscStatus) override
|
||||
{
|
||||
WUHBReader* reader = (WUHBReader*)ctx;
|
||||
cemu_assert_debug(!HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::WRITE_PERMISSION)); // writing to WUHB is not supported
|
||||
|
||||
bool isFile;
|
||||
uint32 table_offset = ROMFS_ENTRY_EMPTY;
|
||||
|
||||
if (table_offset == ROMFS_ENTRY_EMPTY && HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::OPEN_DIR))
|
||||
{
|
||||
table_offset = reader->Lookup(path, false);
|
||||
isFile = false;
|
||||
}
|
||||
if (table_offset == ROMFS_ENTRY_EMPTY && HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::OPEN_FILE))
|
||||
{
|
||||
table_offset = reader->Lookup(path, true);
|
||||
isFile = true;
|
||||
}
|
||||
|
||||
if (table_offset == ROMFS_ENTRY_EMPTY)
|
||||
{
|
||||
*fscStatus = FSC_STATUS_FILE_NOT_FOUND;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
*fscStatus = FSC_STATUS_OK;
|
||||
return new FSCDeviceWuhbFileCtx(reader, table_offset, isFile ? FSC_TYPE_FILE : FSC_TYPE_DIRECTORY);
|
||||
}
|
||||
|
||||
// singleton
|
||||
public:
|
||||
static fscDeviceWUHB& instance()
|
||||
{
|
||||
static fscDeviceWUHB _instance;
|
||||
return _instance;
|
||||
}
|
||||
};
|
||||
|
||||
bool FSCDeviceWUHB_Mount(std::string_view mountPath, std::string_view destinationBaseDir, WUHBReader* wuhbReader, sint32 priority)
|
||||
{
|
||||
return fsc_mount(mountPath, destinationBaseDir, &fscDeviceWUHB::instance(), wuhbReader, priority) == FSC_STATUS_OK;
|
||||
}
|
@ -280,6 +280,10 @@ GraphicPack2::GraphicPack2(fs::path rulesPath, IniParser& rules)
|
||||
m_enabled = m_default_enabled;
|
||||
}
|
||||
|
||||
auto option_allowRendertargetSizeOptimization = rules.FindOption("colorbufferOptimizationAware");
|
||||
if (option_allowRendertargetSizeOptimization)
|
||||
m_allowRendertargetSizeOptimization = boost::iequals(*option_allowRendertargetSizeOptimization, "true") || boost::iequals(*option_allowRendertargetSizeOptimization, "1");
|
||||
|
||||
auto option_vendorFilter = rules.FindOption("vendorFilter");
|
||||
if (option_vendorFilter)
|
||||
{
|
||||
@ -878,9 +882,6 @@ bool GraphicPack2::Activate()
|
||||
if (m_gfx_vendor.has_value())
|
||||
{
|
||||
auto vendor = g_renderer->GetVendor();
|
||||
if (vendor == GfxVendor::IntelLegacy || vendor == GfxVendor::IntelNoLegacy)
|
||||
vendor = GfxVendor::Intel;
|
||||
|
||||
if (m_gfx_vendor.value() != vendor)
|
||||
return false;
|
||||
}
|
||||
|
@ -113,6 +113,7 @@ public:
|
||||
const std::string& GetVirtualPath() const { return m_virtualPath; } // returns the path in the gfx tree hierarchy
|
||||
const std::string& GetDescription() const { return m_description; }
|
||||
bool IsDefaultEnabled() const { return m_default_enabled; }
|
||||
bool AllowRendertargetSizeOptimization() const { return m_allowRendertargetSizeOptimization; }
|
||||
|
||||
void SetEnabled(bool state) { m_enabled = state; }
|
||||
|
||||
@ -217,6 +218,8 @@ private:
|
||||
|
||||
bool m_default_enabled = false;
|
||||
|
||||
bool m_allowRendertargetSizeOptimization = false; // gfx pack supports framebuffers with non-padded sizes, which is an optional optimization introduced with Cemu 2.0-74
|
||||
|
||||
// filter
|
||||
std::optional<RendererAPI> m_renderer_api;
|
||||
std::optional<GfxVendor> m_gfx_vendor;
|
||||
|
@ -45,12 +45,17 @@ public:
|
||||
|
||||
static void ClearRange(MPTR address, uint32 length)
|
||||
{
|
||||
if (length == 0)
|
||||
return;
|
||||
s_lock.lock();
|
||||
while (length > 0)
|
||||
for (;;)
|
||||
{
|
||||
auto itr = s_typeStorage.find(address);
|
||||
if (itr != s_typeStorage.end())
|
||||
s_typeStorage.erase(itr);
|
||||
|
||||
if (length <= 4)
|
||||
break;
|
||||
address += 4;
|
||||
length -= 4;
|
||||
}
|
||||
@ -60,4 +65,4 @@ public:
|
||||
private:
|
||||
static FSpinlock s_lock;
|
||||
static std::unordered_map<MPTR, DEBUG_SYMBOL_TYPE> s_typeStorage;
|
||||
};
|
||||
};
|
||||
|
@ -297,7 +297,7 @@ bool GDBServer::Initialize()
|
||||
|
||||
void GDBServer::ThreadFunc()
|
||||
{
|
||||
SetThreadName("GDBServer::ThreadFunc");
|
||||
SetThreadName("GDBServer");
|
||||
|
||||
while (!m_stopRequested)
|
||||
{
|
||||
|
@ -98,7 +98,7 @@ uint8* PPCInterpreterGetStackPointer()
|
||||
return memory_getPointerFromVirtualOffset(PPCInterpreter_getCurrentInstance()->gpr[1]);
|
||||
}
|
||||
|
||||
uint8* PPCInterpreterGetAndModifyStackPointer(sint32 offset)
|
||||
uint8* PPCInterpreter_PushAndReturnStackPointer(sint32 offset)
|
||||
{
|
||||
PPCInterpreter_t* hCPU = PPCInterpreter_getCurrentInstance();
|
||||
uint8* result = memory_getPointerFromVirtualOffset(hCPU->gpr[1] - offset);
|
||||
|
@ -5,8 +5,28 @@ struct PPCCoreCallbackData_t
|
||||
{
|
||||
sint32 gprCount = 0;
|
||||
sint32 floatCount = 0;
|
||||
sint32 stackCount = 0;
|
||||
};
|
||||
|
||||
inline void _PPCCoreCallback_writeGPRArg(PPCCoreCallbackData_t& data, PPCInterpreter_t* hCPU, uint32 value)
|
||||
{
|
||||
if (data.gprCount < 8)
|
||||
{
|
||||
hCPU->gpr[3 + data.gprCount] = value;
|
||||
data.gprCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
uint32 stackOffset = 8 + data.stackCount * 4;
|
||||
|
||||
// PPCCore_executeCallbackInternal does -16*4 to save the current stack area
|
||||
stackOffset -= 16 * 4;
|
||||
|
||||
memory_writeU32(hCPU->gpr[1] + stackOffset, value);
|
||||
data.stackCount++;
|
||||
}
|
||||
}
|
||||
|
||||
// callback functions
|
||||
inline uint32 PPCCoreCallback(MPTR function, const PPCCoreCallbackData_t& data)
|
||||
{
|
||||
@ -16,23 +36,21 @@ inline uint32 PPCCoreCallback(MPTR function, const PPCCoreCallbackData_t& data)
|
||||
template <typename T, typename... TArgs>
|
||||
uint32 PPCCoreCallback(MPTR function, PPCCoreCallbackData_t& data, T currentArg, TArgs... args)
|
||||
{
|
||||
cemu_assert_debug(data.gprCount <= 8);
|
||||
cemu_assert_debug(data.floatCount <= 8);
|
||||
// TODO float arguments on stack
|
||||
cemu_assert_debug(data.floatCount < 8);
|
||||
|
||||
PPCInterpreter_t* hCPU = PPCInterpreter_getCurrentInstance();
|
||||
if constexpr (std::is_pointer_v<T>)
|
||||
{
|
||||
hCPU->gpr[3 + data.gprCount] = MEMPTR(currentArg).GetMPTR();
|
||||
data.gprCount++;
|
||||
_PPCCoreCallback_writeGPRArg(data, hCPU, MEMPTR(currentArg).GetMPTR());
|
||||
}
|
||||
else if constexpr (std::is_base_of_v<MEMPTRBase, std::remove_reference_t<T>>)
|
||||
{
|
||||
hCPU->gpr[3 + data.gprCount] = currentArg.GetMPTR();
|
||||
data.gprCount++;
|
||||
_PPCCoreCallback_writeGPRArg(data, hCPU, currentArg.GetMPTR());
|
||||
}
|
||||
else if constexpr (std::is_reference_v<T>)
|
||||
{
|
||||
hCPU->gpr[3 + data.gprCount] = MEMPTR(¤tArg).GetMPTR();
|
||||
data.gprCount++;
|
||||
_PPCCoreCallback_writeGPRArg(data, hCPU, MEMPTR(¤tArg).GetMPTR());
|
||||
}
|
||||
else if constexpr(std::is_enum_v<T>)
|
||||
{
|
||||
@ -53,8 +71,7 @@ uint32 PPCCoreCallback(MPTR function, PPCCoreCallbackData_t& data, T currentArg,
|
||||
}
|
||||
else
|
||||
{
|
||||
hCPU->gpr[3 + data.gprCount] = (uint32)currentArg;
|
||||
data.gprCount++;
|
||||
_PPCCoreCallback_writeGPRArg(data, hCPU, (uint32)currentArg);
|
||||
}
|
||||
|
||||
return PPCCoreCallback(function, data, args...);
|
||||
|
@ -216,7 +216,7 @@ void PPCTimer_start();
|
||||
// core info and control
|
||||
extern uint32 ppcThreadQuantum;
|
||||
|
||||
uint8* PPCInterpreterGetAndModifyStackPointer(sint32 offset);
|
||||
uint8* PPCInterpreter_PushAndReturnStackPointer(sint32 offset);
|
||||
uint8* PPCInterpreterGetStackPointer();
|
||||
void PPCInterpreterModifyStackPointer(sint32 offset);
|
||||
|
||||
|
@ -443,7 +443,7 @@ std::atomic_bool s_recompilerThreadStopSignal{false};
|
||||
|
||||
void PPCRecompiler_thread()
|
||||
{
|
||||
SetThreadName("PPCRecompiler_thread");
|
||||
SetThreadName("PPCRecompiler");
|
||||
while (true)
|
||||
{
|
||||
if(s_recompilerThreadStopSignal)
|
||||
|
@ -25,6 +25,8 @@ struct LatteGPUState_t
|
||||
// context control
|
||||
uint32 contextControl0;
|
||||
uint32 contextControl1;
|
||||
// optional features
|
||||
bool allowFramebufferSizeOptimization{false}; // allow using scissor box as size hint to determine non-padded rendertarget size
|
||||
// draw context
|
||||
struct
|
||||
{
|
||||
@ -98,7 +100,7 @@ void LatteRenderTarget_copyToBackbuffer(LatteTextureView* textureView, bool isPa
|
||||
void LatteRenderTarget_GetCurrentVirtualViewportSize(sint32* viewportWidth, sint32* viewportHeight);
|
||||
|
||||
void LatteRenderTarget_itHLESwapScanBuffer();
|
||||
void LatteRenderTarget_itHLEClearColorDepthStencil(uint32 clearMask, MPTR colorBufferMPTR, MPTR colorBufferFormat, Latte::E_HWTILEMODE colorBufferTilemode, uint32 colorBufferWidth, uint32 colorBufferHeight, uint32 colorBufferPitch, uint32 colorBufferViewFirstSlice, uint32 colorBufferViewNumSlice, MPTR depthBufferMPTR, MPTR depthBufferFormat, Latte::E_HWTILEMODE depthBufferTileMode, sint32 depthBufferWidth, sint32 depthBufferHeight, sint32 depthBufferPitch, sint32 depthBufferViewFirstSlice, sint32 depthBufferViewNumSlice, float r, float g, float b, float a, float clearDepth, uint32 clearStencil);
|
||||
void LatteRenderTarget_itHLEClearColorDepthStencil(uint32 clearMask, MPTR colorBufferMPTR, Latte::E_GX2SURFFMT colorBufferFormat, Latte::E_HWTILEMODE colorBufferTilemode, uint32 colorBufferWidth, uint32 colorBufferHeight, uint32 colorBufferPitch, uint32 colorBufferViewFirstSlice, uint32 colorBufferViewNumSlice, MPTR depthBufferMPTR, Latte::E_GX2SURFFMT depthBufferFormat, Latte::E_HWTILEMODE depthBufferTileMode, sint32 depthBufferWidth, sint32 depthBufferHeight, sint32 depthBufferPitch, sint32 depthBufferViewFirstSlice, sint32 depthBufferViewNumSlice, float r, float g, float b, float a, float clearDepth, uint32 clearStencil);
|
||||
void LatteRenderTarget_itHLECopyColorBufferToScanBuffer(MPTR colorBufferPtr, uint32 colorBufferWidth, uint32 colorBufferHeight, uint32 colorBufferSliceIndex, uint32 colorBufferFormat, uint32 colorBufferPitch, Latte::E_HWTILEMODE colorBufferTilemode, uint32 colorBufferSwizzle, uint32 renderTarget);
|
||||
|
||||
void LatteRenderTarget_unloadAll();
|
||||
|
@ -309,7 +309,7 @@ public:
|
||||
{
|
||||
if ((rangeBegin & 0xF))
|
||||
{
|
||||
cemuLog_logDebug(LogType::Force, "writeStreamout(): RangeBegin not aligned to 16. Begin {:08x} End {:08x}", rangeBegin, rangeEnd);
|
||||
cemuLog_logDebugOnce(LogType::Force, "writeStreamout(): RangeBegin not aligned to 16. Begin {:08x} End {:08x}", rangeBegin, rangeEnd);
|
||||
rangeBegin = (rangeBegin + 0xF) & ~0xF;
|
||||
rangeEnd = std::max(rangeBegin, rangeEnd);
|
||||
}
|
||||
|
@ -42,7 +42,7 @@ private:
|
||||
if(colorBuffer[i].texture == nullptr)
|
||||
continue;
|
||||
sint32 effectiveWidth, effectiveHeight;
|
||||
LatteTexture_getEffectiveSize(colorBuffer[i].texture->baseTexture, &effectiveWidth, &effectiveHeight, nullptr, colorBuffer[i].texture->firstMip);
|
||||
colorBuffer[i].texture->baseTexture->GetEffectiveSize(effectiveWidth, effectiveHeight, colorBuffer[i].texture->firstMip);
|
||||
if (rtEffectiveSize.x == 0 && rtEffectiveSize.y == 0)
|
||||
{
|
||||
rtEffectiveSize.x = effectiveWidth;
|
||||
@ -64,7 +64,7 @@ private:
|
||||
if (depthBuffer.texture)
|
||||
{
|
||||
sint32 effectiveWidth, effectiveHeight;
|
||||
LatteTexture_getEffectiveSize(depthBuffer.texture->baseTexture, &effectiveWidth, &effectiveHeight, nullptr, depthBuffer.texture->firstMip);
|
||||
depthBuffer.texture->baseTexture->GetEffectiveSize(effectiveWidth, effectiveHeight, depthBuffer.texture->firstMip);
|
||||
if (rtEffectiveSize.x == 0 && rtEffectiveSize.y == 0)
|
||||
{
|
||||
rtEffectiveSize.x = effectiveWidth;
|
||||
|
@ -483,18 +483,45 @@ LatteCMDPtr LatteCP_itWaitRegMem(LatteCMDPtr cmd, uint32 nWords)
|
||||
{
|
||||
uint32 fenceMemValue = _swapEndianU32(*fencePtr);
|
||||
fenceMemValue &= fenceMask;
|
||||
if (compareOp == GPU7_WAIT_MEM_OP_GEQUAL)
|
||||
if (compareOp == GPU7_WAIT_MEM_OP_LESS)
|
||||
{
|
||||
// greater or equal
|
||||
if (fenceMemValue >= fenceValue)
|
||||
if (fenceMemValue < fenceValue)
|
||||
break;
|
||||
}
|
||||
else if (compareOp == GPU7_WAIT_MEM_OP_LEQUAL)
|
||||
{
|
||||
if (fenceMemValue <= fenceValue)
|
||||
break;
|
||||
}
|
||||
else if (compareOp == GPU7_WAIT_MEM_OP_EQUAL)
|
||||
{
|
||||
// equal
|
||||
if (fenceMemValue == fenceValue)
|
||||
break;
|
||||
}
|
||||
else if (compareOp == GPU7_WAIT_MEM_OP_NOTEQUAL)
|
||||
{
|
||||
if (fenceMemValue != fenceValue)
|
||||
break;
|
||||
}
|
||||
else if (compareOp == GPU7_WAIT_MEM_OP_GEQUAL)
|
||||
{
|
||||
if (fenceMemValue >= fenceValue)
|
||||
break;
|
||||
}
|
||||
else if (compareOp == GPU7_WAIT_MEM_OP_GREATER)
|
||||
{
|
||||
if (fenceMemValue > fenceValue)
|
||||
break;
|
||||
}
|
||||
else if (compareOp == GPU7_WAIT_MEM_OP_ALWAYS)
|
||||
{
|
||||
break;
|
||||
}
|
||||
else if (compareOp == GPU7_WAIT_MEM_OP_NEVER)
|
||||
{
|
||||
cemuLog_logOnce(LogType::Force, "Latte: WAIT_MEM_OP_NEVER encountered");
|
||||
break;
|
||||
}
|
||||
else
|
||||
assert_dbg();
|
||||
if (!stalls)
|
||||
@ -875,8 +902,8 @@ LatteCMDPtr LatteCP_itHLEClearColorDepthStencil(LatteCMDPtr cmd, uint32 nWords)
|
||||
cemu_assert_debug(nWords == 23);
|
||||
uint32 clearMask = LatteReadCMD(); // color (1), depth (2), stencil (4)
|
||||
// color buffer
|
||||
MPTR colorBufferMPTR = LatteReadCMD(); // MPTR for color buffer (physical address)
|
||||
MPTR colorBufferFormat = LatteReadCMD(); // format for color buffer
|
||||
MPTR colorBufferMPTR = LatteReadCMD(); // physical address for color buffer
|
||||
Latte::E_GX2SURFFMT colorBufferFormat = (Latte::E_GX2SURFFMT)LatteReadCMD();
|
||||
Latte::E_HWTILEMODE colorBufferTilemode = (Latte::E_HWTILEMODE)LatteReadCMD();
|
||||
uint32 colorBufferWidth = LatteReadCMD();
|
||||
uint32 colorBufferHeight = LatteReadCMD();
|
||||
@ -884,8 +911,8 @@ LatteCMDPtr LatteCP_itHLEClearColorDepthStencil(LatteCMDPtr cmd, uint32 nWords)
|
||||
uint32 colorBufferViewFirstSlice = LatteReadCMD();
|
||||
uint32 colorBufferViewNumSlice = LatteReadCMD();
|
||||
// depth buffer
|
||||
MPTR depthBufferMPTR = LatteReadCMD(); // MPTR for depth buffer (physical address)
|
||||
MPTR depthBufferFormat = LatteReadCMD(); // format for depth buffer
|
||||
MPTR depthBufferMPTR = LatteReadCMD(); // physical address for depth buffer
|
||||
Latte::E_GX2SURFFMT depthBufferFormat = (Latte::E_GX2SURFFMT)LatteReadCMD();
|
||||
Latte::E_HWTILEMODE depthBufferTileMode = (Latte::E_HWTILEMODE)LatteReadCMD();
|
||||
uint32 depthBufferWidth = LatteReadCMD();
|
||||
uint32 depthBufferHeight = LatteReadCMD();
|
||||
@ -904,8 +931,8 @@ LatteCMDPtr LatteCP_itHLEClearColorDepthStencil(LatteCMDPtr cmd, uint32 nWords)
|
||||
|
||||
LatteRenderTarget_itHLEClearColorDepthStencil(
|
||||
clearMask,
|
||||
colorBufferMPTR, colorBufferFormat, colorBufferTilemode, colorBufferWidth, colorBufferHeight, colorBufferPitch, colorBufferViewFirstSlice, colorBufferViewNumSlice,
|
||||
depthBufferMPTR, depthBufferFormat, depthBufferTileMode, depthBufferWidth, depthBufferHeight, depthBufferPitch, depthBufferViewFirstSlice, depthBufferViewNumSlice,
|
||||
colorBufferMPTR, colorBufferFormat, colorBufferTilemode, colorBufferWidth, colorBufferHeight, colorBufferPitch, colorBufferViewFirstSlice, colorBufferViewNumSlice,
|
||||
depthBufferMPTR, depthBufferFormat, depthBufferTileMode, depthBufferWidth, depthBufferHeight, depthBufferPitch, depthBufferViewFirstSlice, depthBufferViewNumSlice,
|
||||
r, g, b, a,
|
||||
clearDepth, clearStencil);
|
||||
return cmd;
|
||||
|
@ -82,8 +82,6 @@
|
||||
#define GLVENDOR_UNKNOWN (0)
|
||||
#define GLVENDOR_AMD (1) // AMD/ATI
|
||||
#define GLVENDOR_NVIDIA (2)
|
||||
#define GLVENDOR_INTEL_LEGACY (3)
|
||||
#define GLVENDOR_INTEL_NOLEGACY (4)
|
||||
#define GLVENDOR_INTEL (5)
|
||||
#define GLVENDOR_APPLE (6)
|
||||
|
||||
|
@ -221,35 +221,9 @@ void LatteMRT::BindDepthBufferOnly(LatteTextureView* view)
|
||||
ApplyCurrentState();
|
||||
}
|
||||
|
||||
/***************************************************/
|
||||
|
||||
LatteTextureView* LatteMRT_FindColorBufferForClearing(MPTR colorBufferPtr, sint32 colorBufferWidth, sint32 colorBufferHeight, sint32 colorBufferPitch, uint32 format, sint32 sliceIndex, sint32* searchIndex)
|
||||
{
|
||||
LatteTextureView* view = LatteTC_LookupTextureByData(colorBufferPtr, colorBufferWidth, colorBufferHeight, colorBufferPitch, 0, 1, sliceIndex, 1, searchIndex);
|
||||
if (view == nullptr)
|
||||
return nullptr;
|
||||
return view;
|
||||
}
|
||||
|
||||
LatteTextureView* LatteMRT_CreateColorBuffer(MPTR colorBufferPhysMem, uint32 width, uint32 height, uint32 pitch, Latte::E_GX2SURFFMT format, Latte::E_HWTILEMODE tileMode, uint32 swizzle, uint32 viewSlice)
|
||||
{
|
||||
cemu_assert_debug(colorBufferPhysMem != MPTR_NULL);
|
||||
LatteTextureView* textureView;
|
||||
if(viewSlice != 0)
|
||||
textureView = LatteTexture_CreateMapping(colorBufferPhysMem, MPTR_NULL, width, height, viewSlice+1, pitch, tileMode, swizzle, 0, 1, viewSlice, 1, format, Latte::E_DIM::DIM_2D_ARRAY, Latte::E_DIM::DIM_2D, false);
|
||||
else
|
||||
textureView = LatteTexture_CreateMapping(colorBufferPhysMem, MPTR_NULL, width, height, 1, pitch, tileMode, swizzle, 0, 1, viewSlice, 1, format, Latte::E_DIM::DIM_2D, Latte::E_DIM::DIM_2D, false);
|
||||
return textureView;
|
||||
}
|
||||
|
||||
LatteTextureView* LatteMRT_CreateDepthBuffer(MPTR depthBufferPhysMem, uint32 width, uint32 height, uint32 pitch, Latte::E_HWTILEMODE tileMode, Latte::E_GX2SURFFMT format, uint32 swizzle, sint32 viewSlice)
|
||||
{
|
||||
LatteTextureView* textureView;
|
||||
if(viewSlice == 0)
|
||||
textureView = LatteTexture_CreateMapping(depthBufferPhysMem, MPTR_NULL, width, height, 1, pitch, tileMode, swizzle, 0, 1, viewSlice, 1, format, Latte::E_DIM::DIM_2D, Latte::E_DIM::DIM_2D, true);
|
||||
else
|
||||
textureView = LatteTexture_CreateMapping(depthBufferPhysMem, MPTR_NULL, width, height, viewSlice+1, pitch, tileMode, swizzle, 0, 1, viewSlice, 1, format, Latte::E_DIM::DIM_2D_ARRAY, Latte::E_DIM::DIM_2D, true);
|
||||
|
||||
LatteTextureView* textureView = LatteTexture_CreateMapping(depthBufferPhysMem, MPTR_NULL, width, height, viewSlice+1, pitch, tileMode, swizzle, 0, 1, viewSlice, 1, format, viewSlice > 0 ? Latte::E_DIM::DIM_2D_ARRAY : Latte::E_DIM::DIM_2D, Latte::E_DIM::DIM_2D, true);
|
||||
LatteMRT::SetDepthAndStencilAttachment(textureView, textureView->baseTexture->hasStencil);
|
||||
return textureView;
|
||||
}
|
||||
@ -293,14 +267,15 @@ LatteTextureView* LatteMRT::GetColorAttachmentTexture(uint32 index, bool createN
|
||||
|
||||
// colorbuffer width/height has to be padded to 8/32 alignment but the actual resolution might be smaller
|
||||
// use the scissor box as a clue to figure out the original resolution if possible
|
||||
#if 0
|
||||
uint32 scissorBoxWidth = LatteGPUState.contextNew.PA_SC_GENERIC_SCISSOR_BR.get_BR_X();
|
||||
uint32 scissorBoxHeight = LatteGPUState.contextNew.PA_SC_GENERIC_SCISSOR_BR.get_BR_Y();
|
||||
if (((scissorBoxWidth + 7) & ~7) == colorBufferWidth)
|
||||
colorBufferWidth = scissorBoxWidth;
|
||||
if (((colorBufferHeight + 31) & ~31) == colorBufferHeight)
|
||||
colorBufferHeight = scissorBoxHeight;
|
||||
#endif
|
||||
if(LatteGPUState.allowFramebufferSizeOptimization)
|
||||
{
|
||||
uint32 scissorBoxWidth = LatteGPUState.contextNew.PA_SC_GENERIC_SCISSOR_BR.get_BR_X();
|
||||
uint32 scissorBoxHeight = LatteGPUState.contextNew.PA_SC_GENERIC_SCISSOR_BR.get_BR_Y();
|
||||
if (((scissorBoxWidth + 7) & ~7) == colorBufferWidth)
|
||||
colorBufferWidth = scissorBoxWidth;
|
||||
if (((colorBufferHeight + 31) & ~31) == colorBufferHeight)
|
||||
colorBufferHeight = scissorBoxHeight;
|
||||
}
|
||||
|
||||
// log resolution changes if the above heuristic takes effect
|
||||
// this is useful to find resolutions which need to be updated in gfx pack texture rules
|
||||
@ -329,7 +304,7 @@ LatteTextureView* LatteMRT::GetColorAttachmentTexture(uint32 index, bool createN
|
||||
if (colorBufferView == nullptr)
|
||||
{
|
||||
// create color buffer view
|
||||
colorBufferView = LatteTexture_CreateMapping(colorBufferPhysMem, 0, colorBufferWidth, colorBufferHeight, (viewFirstSlice + viewNumSlices), colorBufferPitch, colorBufferTileMode, colorBufferSwizzle>>8, viewFirstMip, 1, viewFirstSlice, viewNumSlices, (Latte::E_GX2SURFFMT)colorBufferFormat, (viewFirstSlice + viewNumSlices)>1? Latte::E_DIM::DIM_2D_ARRAY: Latte::E_DIM::DIM_2D, Latte::E_DIM::DIM_2D, false);
|
||||
colorBufferView = LatteTexture_CreateMapping(colorBufferPhysMem, 0, colorBufferWidth, colorBufferHeight, (viewFirstSlice + viewNumSlices), colorBufferPitch, colorBufferTileMode, colorBufferSwizzle>>8, viewFirstMip, 1, viewFirstSlice, viewNumSlices, (Latte::E_GX2SURFFMT)colorBufferFormat, (viewFirstSlice + viewNumSlices)>1? Latte::E_DIM::DIM_2D_ARRAY: Latte::E_DIM::DIM_2D, Latte::E_DIM::DIM_2D, false, true);
|
||||
LatteGPUState.repeatTextureInitialization = true;
|
||||
checkForTextureChanges = false;
|
||||
}
|
||||
@ -365,7 +340,7 @@ uint8 LatteMRT::GetActiveColorBufferMask(const LatteDecompilerShader* pixelShade
|
||||
return 0;
|
||||
cemu_assert_debug(colorControlReg.get_DEGAMMA_ENABLE() == false); // not supported
|
||||
// combine color buffer mask with pixel output mask from pixel shader
|
||||
colorBufferMask &= pixelShader->pixelColorOutputMask;
|
||||
colorBufferMask &= (pixelShader ? pixelShader->pixelColorOutputMask : 0);
|
||||
// combine color buffer mask with color channel mask from mmCB_TARGET_MASK (disable render buffer if all colors are blocked)
|
||||
uint32 channelTargetMask = lcr.CB_TARGET_MASK.get_MASK();
|
||||
for (uint32 i = 0; i < 8; i++)
|
||||
@ -516,14 +491,12 @@ bool LatteMRT::UpdateCurrentFBO()
|
||||
sLatteRenderTargetState.rtUpdateList[sLatteRenderTargetState.rtUpdateListCount] = colorAttachmentView;
|
||||
sLatteRenderTargetState.rtUpdateListCount++;
|
||||
|
||||
sint32 colorAttachmentWidth;
|
||||
sint32 colorAttachmentHeight;
|
||||
|
||||
LatteTexture_getSize(colorAttachmentView->baseTexture, &colorAttachmentWidth, &colorAttachmentHeight, nullptr, colorAttachmentView->firstMip);
|
||||
sint32 colorAttachmentWidth, colorAttachmentHeight;
|
||||
colorAttachmentView->baseTexture->GetSize(colorAttachmentWidth, colorAttachmentHeight, colorAttachmentView->firstMip);
|
||||
|
||||
// set effective size
|
||||
sint32 effectiveWidth, effectiveHeight;
|
||||
LatteTexture_getEffectiveSize(colorAttachmentView->baseTexture, &effectiveWidth, &effectiveHeight, nullptr, colorAttachmentView->firstMip);
|
||||
colorAttachmentView->baseTexture->GetEffectiveSize(effectiveWidth, effectiveHeight, colorAttachmentView->firstMip);
|
||||
if (rtEffectiveSize->width == 0 && rtEffectiveSize->height == 0)
|
||||
{
|
||||
rtEffectiveSize->width = effectiveWidth;
|
||||
@ -531,9 +504,7 @@ bool LatteMRT::UpdateCurrentFBO()
|
||||
}
|
||||
else if (rtEffectiveSize->width != effectiveWidth && rtEffectiveSize->height != effectiveHeight)
|
||||
{
|
||||
#ifdef CEMU_DEBUG_ASSERT
|
||||
cemuLog_log(LogType::Force, "Color buffer size mismatch ({}x{}). Effective size: {}x{} Real size: {}x{} Mismatching texture: {:08x} {}x{} fmt {:04x}", rtEffectiveSize->width, rtEffectiveSize->height, effectiveWidth, effectiveHeight, colorAttachmentView->baseTexture->width, colorAttachmentView->baseTexture->height, colorAttachmentView->baseTexture->physAddress, colorAttachmentView->baseTexture->width, colorAttachmentView->baseTexture->height, (uint32)colorAttachmentView->baseTexture->format);
|
||||
#endif
|
||||
cemuLog_logDebug(LogType::Force, "Color buffer size mismatch ({}x{}). Effective size: {}x{} Real size: {}x{} Mismatching texture: {:08x} {}x{} fmt {:04x}", rtEffectiveSize->width, rtEffectiveSize->height, effectiveWidth, effectiveHeight, colorAttachmentView->baseTexture->width, colorAttachmentView->baseTexture->height, colorAttachmentView->baseTexture->physAddress, colorAttachmentView->baseTexture->width, colorAttachmentView->baseTexture->height, (uint32)colorAttachmentView->baseTexture->format);
|
||||
}
|
||||
// currently the first color attachment defines the size of the current render target
|
||||
if (rtRealSize->width == 0 && rtRealSize->height == 0)
|
||||
@ -608,15 +579,11 @@ bool LatteMRT::UpdateCurrentFBO()
|
||||
|
||||
if (depthBufferPhysMem != MPTR_NULL)
|
||||
{
|
||||
bool depthBufferWasFound = false;
|
||||
LatteTextureView* depthBufferView = LatteTextureViewLookupCache::lookupSliceEx(depthBufferPhysMem, depthBufferWidth, depthBufferHeight, depthBufferPitch, 0, depthBufferViewFirstSlice, depthBufferFormat, true);
|
||||
if (depthBufferView == nullptr)
|
||||
if (!depthBufferView)
|
||||
{
|
||||
// create depth buffer view
|
||||
if(depthBufferViewFirstSlice == 0)
|
||||
depthBufferView = LatteTexture_CreateMapping(depthBufferPhysMem, 0, depthBufferWidth, depthBufferHeight, 1, depthBufferPitch, depthBufferTileMode, depthBufferSwizzle, 0, 1, 0, 1, depthBufferFormat, Latte::E_DIM::DIM_2D, Latte::E_DIM::DIM_2D, true);
|
||||
else
|
||||
depthBufferView = LatteTexture_CreateMapping(depthBufferPhysMem, 0, depthBufferWidth, depthBufferHeight, depthBufferViewFirstSlice+1, depthBufferPitch, depthBufferTileMode, depthBufferSwizzle, 0, 1, depthBufferViewFirstSlice, 1, depthBufferFormat, Latte::E_DIM::DIM_2D_ARRAY, Latte::E_DIM::DIM_2D, true);
|
||||
// create new depth buffer view and if it doesn't exist then also create the texture
|
||||
depthBufferView = LatteTexture_CreateMapping(depthBufferPhysMem, 0, depthBufferWidth, depthBufferHeight, depthBufferViewFirstSlice+1, depthBufferPitch, depthBufferTileMode, depthBufferSwizzle, 0, 1, depthBufferViewFirstSlice, 1, depthBufferFormat, depthBufferViewFirstSlice > 0 ? Latte::E_DIM::DIM_2D_ARRAY : Latte::E_DIM::DIM_2D, Latte::E_DIM::DIM_2D, true, true);
|
||||
LatteGPUState.repeatTextureInitialization = true;
|
||||
}
|
||||
else
|
||||
@ -626,7 +593,7 @@ bool LatteMRT::UpdateCurrentFBO()
|
||||
}
|
||||
// set effective size
|
||||
sint32 effectiveWidth, effectiveHeight;
|
||||
LatteTexture_getEffectiveSize(depthBufferView->baseTexture, &effectiveWidth, &effectiveHeight, NULL);
|
||||
depthBufferView->baseTexture->GetEffectiveSize(effectiveWidth, effectiveHeight, depthBufferView->firstMip);
|
||||
if (rtEffectiveSize->width == 0 && rtEffectiveSize->height == 0)
|
||||
{
|
||||
rtEffectiveSize->width = effectiveWidth;
|
||||
@ -776,7 +743,10 @@ void LatteRenderTarget_applyTextureDepthClear(LatteTexture* texture, uint32 slic
|
||||
LatteTexture_MarkDynamicTextureAsChanged(texture->baseView, sliceIndex, mipIndex, eventCounter);
|
||||
}
|
||||
|
||||
void LatteRenderTarget_itHLEClearColorDepthStencil(uint32 clearMask, MPTR colorBufferMPTR, MPTR colorBufferFormat, Latte::E_HWTILEMODE colorBufferTilemode, uint32 colorBufferWidth, uint32 colorBufferHeight, uint32 colorBufferPitch, uint32 colorBufferViewFirstSlice, uint32 colorBufferViewNumSlice, MPTR depthBufferMPTR, MPTR depthBufferFormat, Latte::E_HWTILEMODE depthBufferTileMode, sint32 depthBufferWidth, sint32 depthBufferHeight, sint32 depthBufferPitch, sint32 depthBufferViewFirstSlice, sint32 depthBufferViewNumSlice, float r, float g, float b, float a, float clearDepth, uint32 clearStencil)
|
||||
void LatteRenderTarget_itHLEClearColorDepthStencil(uint32 clearMask,
|
||||
MPTR colorBufferMPTR, Latte::E_GX2SURFFMT colorBufferFormat, Latte::E_HWTILEMODE colorBufferTilemode, uint32 colorBufferWidth, uint32 colorBufferHeight, uint32 colorBufferPitch, uint32 colorBufferViewFirstSlice, uint32 colorBufferViewNumSlice,
|
||||
MPTR depthBufferMPTR, Latte::E_GX2SURFFMT depthBufferFormat, Latte::E_HWTILEMODE depthBufferTileMode, sint32 depthBufferWidth, sint32 depthBufferHeight, sint32 depthBufferPitch, sint32 depthBufferViewFirstSlice, sint32 depthBufferViewNumSlice,
|
||||
float r, float g, float b, float a, float clearDepth, uint32 clearStencil)
|
||||
{
|
||||
uint32 depthBufferMipIndex = 0; // todo
|
||||
uint32 colorBufferMipIndex = 0; // todo
|
||||
@ -811,13 +781,11 @@ void LatteRenderTarget_itHLEClearColorDepthStencil(uint32 clearMask, MPTR colorB
|
||||
bool targetFound = false;
|
||||
while (true)
|
||||
{
|
||||
LatteTextureView* colorView = LatteMRT_FindColorBufferForClearing(colorBufferMPTR, colorBufferWidth, colorBufferHeight, colorBufferPitch, colorBufferFormat, colorBufferViewFirstSlice, &searchIndex);
|
||||
LatteTextureView* colorView = LatteTC_LookupTextureByData(colorBufferMPTR, colorBufferWidth, colorBufferHeight, colorBufferPitch, 0, 1, colorBufferViewFirstSlice, 1, &searchIndex);
|
||||
if (!colorView)
|
||||
break;
|
||||
if (Latte::GetFormatBits((Latte::E_GX2SURFFMT)colorBufferFormat) != Latte::GetFormatBits(colorView->baseTexture->format))
|
||||
{
|
||||
if (Latte::GetFormatBits(colorBufferFormat) != Latte::GetFormatBits(colorView->baseTexture->format))
|
||||
continue;
|
||||
}
|
||||
|
||||
if (colorView->baseTexture->pitch == colorBufferPitch && colorView->baseTexture->height == colorBufferHeight)
|
||||
targetFound = true;
|
||||
@ -829,7 +797,7 @@ void LatteRenderTarget_itHLEClearColorDepthStencil(uint32 clearMask, MPTR colorB
|
||||
{
|
||||
// create new texture with matching format
|
||||
cemu_assert_debug(colorBufferViewNumSlice <= 1);
|
||||
LatteTextureView* newColorView = LatteMRT_CreateColorBuffer(colorBufferMPTR, colorBufferWidth, colorBufferHeight, colorBufferPitch, (Latte::E_GX2SURFFMT)colorBufferFormat, colorBufferTilemode, colorBufferSwizzle, colorBufferViewFirstSlice);
|
||||
LatteTextureView* newColorView = LatteTexture_CreateMapping(colorBufferMPTR, MPTR_NULL, colorBufferWidth, colorBufferHeight, colorBufferViewFirstSlice+1, colorBufferPitch, colorBufferTilemode, colorBufferSwizzle, 0, 1, colorBufferViewFirstSlice, 1, colorBufferFormat, colorBufferViewFirstSlice > 0 ? Latte::E_DIM::DIM_2D_ARRAY : Latte::E_DIM::DIM_2D, Latte::E_DIM::DIM_2D, false);
|
||||
LatteRenderTarget_applyTextureColorClear(newColorView->baseTexture, colorBufferViewFirstSlice, colorBufferMipIndex, r, g, b, a, eventCounter);
|
||||
}
|
||||
}
|
||||
@ -907,20 +875,13 @@ void LatteRenderTarget_getScreenImageArea(sint32* x, sint32* y, sint32* width, s
|
||||
|
||||
void LatteRenderTarget_copyToBackbuffer(LatteTextureView* textureView, bool isPadView)
|
||||
{
|
||||
if (g_renderer->GetType() == RendererAPI::Vulkan)
|
||||
{
|
||||
((VulkanRenderer*)g_renderer.get())->PreparePresentationFrame(!isPadView);
|
||||
}
|
||||
|
||||
// make sure texture is updated to latest data in cache
|
||||
LatteTexture_UpdateDataToLatest(textureView->baseTexture);
|
||||
// mark source texture as still in use
|
||||
LatteTC_MarkTextureStillInUse(textureView->baseTexture);
|
||||
|
||||
sint32 effectiveWidth;
|
||||
sint32 effectiveHeight;
|
||||
sint32 effectiveDepth;
|
||||
LatteTexture_getEffectiveSize(textureView->baseTexture, &effectiveWidth, &effectiveHeight, &effectiveDepth, 0);
|
||||
sint32 effectiveWidth, effectiveHeight;
|
||||
textureView->baseTexture->GetEffectiveSize(effectiveWidth, effectiveHeight, 0);
|
||||
_currentOutputImageWidth = effectiveWidth;
|
||||
_currentOutputImageHeight = effectiveHeight;
|
||||
|
||||
|
@ -652,7 +652,7 @@ LatteDecompilerShader* LatteShader_CreateShaderFromDecompilerOutput(LatteDecompi
|
||||
}
|
||||
else
|
||||
{
|
||||
shader->uniform.count_uniformRegister = decompilerOutput.uniformOffsetsVK.count_uniformRegister;
|
||||
shader->uniform.count_uniformRegister = decompilerOutput.uniformOffsetsGL.count_uniformRegister;
|
||||
}
|
||||
// calculate aux hash
|
||||
if (calculateAuxHash)
|
||||
|
@ -12,7 +12,7 @@
|
||||
#define GPU7_CF_INST_VTX (0x02) // used only in GS copy program?
|
||||
#define GPU7_CF_INST_LOOP_END (0x05)
|
||||
#define GPU7_CF_INST_LOOP_START_DX10 (0x06)
|
||||
#define GPU7_CF_INST_LOOP_START_NO_AL (0x07) // (Seen in Project Zero)
|
||||
#define GPU7_CF_INST_LOOP_START_NO_AL (0x07) // (Seen in Project Zero, Injustice: Gods Among Us)
|
||||
|
||||
#define GPU7_CF_INST_LOOP_BREAK (0x09)
|
||||
#define GPU7_CF_INST_JUMP (0x0A)
|
||||
|
@ -37,7 +37,7 @@ void LatteSurfaceCopy_copySurfaceNew(MPTR srcPhysAddr, MPTR srcMipAddr, uint32 s
|
||||
if (!destinationTexture)
|
||||
{
|
||||
LatteTexture* renderTargetConf = nullptr;
|
||||
destinationView = LatteTexture_CreateMapping(dstPhysAddr, dstMipAddr, dstWidth, dstHeight, dstDepth, dstPitch, dstTilemode, dstSwizzle, dstLevel, 1, dstSlice, 1, dstSurfaceFormat, dstDim, Latte::E_DIM::DIM_2D, false);
|
||||
destinationView = LatteTexture_CreateMapping(dstPhysAddr, dstMipAddr, dstWidth, dstHeight, dstDepth, dstPitch, dstTilemode, dstSwizzle, dstLevel, 1, dstSlice, 1, dstSurfaceFormat, dstDim, Latte::IsMSAA(dstDim) ? Latte::E_DIM::DIM_2D_MSAA : Latte::E_DIM::DIM_2D, false);
|
||||
destinationTexture = destinationView->baseTexture;
|
||||
}
|
||||
// copy texture
|
||||
|
@ -1,5 +1,4 @@
|
||||
#include "Cafe/HW/Latte/Core/Latte.h"
|
||||
#include "Cafe/HW/Latte/Core/LatteDraw.h"
|
||||
#include "Cafe/HW/Latte/Core/LatteShader.h"
|
||||
#include "Cafe/HW/Latte/Core/LattePerformanceMonitor.h"
|
||||
#include "Cafe/HW/Latte/Core/LatteTexture.h"
|
||||
@ -9,6 +8,8 @@
|
||||
|
||||
#include "Cafe/GraphicPack/GraphicPack2.h"
|
||||
|
||||
#include <boost/container/small_vector.hpp>
|
||||
|
||||
struct TexMemOccupancyEntry
|
||||
{
|
||||
uint32 addrStart;
|
||||
@ -234,6 +235,9 @@ void LatteTexture_InitSliceAndMipInfo(LatteTexture* texture)
|
||||
// if this function returns false, textures will not be synchronized even if their data overlaps
|
||||
bool LatteTexture_IsFormatViewCompatible(Latte::E_GX2SURFFMT formatA, Latte::E_GX2SURFFMT formatB)
|
||||
{
|
||||
if(formatA == formatB)
|
||||
return true; // if the format is identical then compatibility must be guaranteed (otherwise we can't create the necessary default view of a texture)
|
||||
|
||||
// todo - find a better way to handle this
|
||||
for (sint32 swap = 0; swap < 2; swap++)
|
||||
{
|
||||
@ -297,9 +301,9 @@ void LatteTexture_copyData(LatteTexture* srcTexture, LatteTexture* dstTexture, s
|
||||
else
|
||||
{
|
||||
sint32 effectiveWidth_dst, effectiveHeight_dst;
|
||||
LatteTexture_getEffectiveSize(srcTexture, &effectiveWidth_dst, &effectiveHeight_dst, NULL, 0);
|
||||
srcTexture->GetEffectiveSize(effectiveWidth_dst, effectiveHeight_dst, 0);
|
||||
sint32 effectiveWidth_src, effectiveHeight_src;
|
||||
LatteTexture_getEffectiveSize(dstTexture, &effectiveWidth_src, &effectiveHeight_src, NULL, 0);
|
||||
dstTexture->GetEffectiveSize(effectiveWidth_src, effectiveHeight_src, 0);
|
||||
|
||||
debug_printf("texture_copyData(): Effective size mismatch\n");
|
||||
cemuLog_logDebug(LogType::Force, "texture_copyData(): Effective size mismatch (due to texture rule)");
|
||||
@ -307,8 +311,6 @@ void LatteTexture_copyData(LatteTexture* srcTexture, LatteTexture* dstTexture, s
|
||||
cemuLog_logDebug(LogType::Force, "Source: origResolution {:04}x{:04} effectiveResolution {:04}x{:04} fmt {:04x} mipIndex {}", srcTexture->width, srcTexture->height, effectiveWidth_src, effectiveHeight_src, (uint32)srcTexture->format, 0);
|
||||
return;
|
||||
}
|
||||
catchOpenGLError();
|
||||
|
||||
for (sint32 mipIndex = 0; mipIndex < mipCount; mipIndex++)
|
||||
{
|
||||
sint32 sliceCopyWidth = std::max(effectiveCopyWidth >> mipIndex, 1);
|
||||
@ -323,9 +325,7 @@ void LatteTexture_copyData(LatteTexture* srcTexture, LatteTexture* dstTexture, s
|
||||
LatteTextureSliceMipInfo* dstTexSliceInfo = dstTexture->sliceMipInfo + dstTexture->GetSliceMipArrayIndex(sliceIndex, mipIndex);
|
||||
dstTexSliceInfo->lastDynamicUpdate = srcTexSliceInfo->lastDynamicUpdate;
|
||||
}
|
||||
catchOpenGLError();
|
||||
}
|
||||
catchOpenGLError();
|
||||
}
|
||||
|
||||
template<bool bothMustMatch>
|
||||
@ -438,6 +438,11 @@ void LatteTexture_SyncSlice(LatteTexture* srcTexture, sint32 srcSliceIndex, sint
|
||||
sint32 dstWidth = dstTexture->width;
|
||||
sint32 dstHeight = dstTexture->height;
|
||||
|
||||
if(srcTexture->overwriteInfo.hasFormatOverwrite != dstTexture->overwriteInfo.hasFormatOverwrite)
|
||||
return; // dont sync: format overwrite state needs to match. Not strictly necessary but it simplifies logic down the road
|
||||
else if(srcTexture->overwriteInfo.hasFormatOverwrite && srcTexture->overwriteInfo.format != dstTexture->overwriteInfo.format)
|
||||
return; // both are overwritten but with different formats
|
||||
|
||||
if (srcMipIndex == 0 && dstMipIndex == 0 && (srcTexture->tileMode == Latte::E_HWTILEMODE::TM_LINEAR_ALIGNED || srcTexture->tileMode == Latte::E_HWTILEMODE::TM_1D_TILED_THIN1) && srcTexture->height > dstTexture->height && (srcTexture->height % dstTexture->height) == 0)
|
||||
{
|
||||
bool isMatch = srcTexture->tileMode == Latte::E_HWTILEMODE::TM_LINEAR_ALIGNED;
|
||||
@ -790,87 +795,42 @@ enum VIEWCOMPATIBILITY
|
||||
VIEW_NOT_COMPATIBLE,
|
||||
};
|
||||
|
||||
bool IsDimensionCompatibleForView(Latte::E_DIM baseDim, Latte::E_DIM viewDim)
|
||||
bool IsDimensionCompatibleForGX2View(Latte::E_DIM baseDim, Latte::E_DIM viewDim)
|
||||
{
|
||||
bool incompatibleDim = false;
|
||||
if (baseDim == Latte::E_DIM::DIM_2D && viewDim == Latte::E_DIM::DIM_2D)
|
||||
;
|
||||
else if (baseDim == Latte::E_DIM::DIM_1D && viewDim == Latte::E_DIM::DIM_1D)
|
||||
;
|
||||
else if (baseDim == Latte::E_DIM::DIM_2D && viewDim == Latte::E_DIM::DIM_2D_ARRAY)
|
||||
;
|
||||
else if (baseDim == Latte::E_DIM::DIM_CUBEMAP && viewDim == Latte::E_DIM::DIM_CUBEMAP)
|
||||
;
|
||||
else if (baseDim == Latte::E_DIM::DIM_CUBEMAP && viewDim == Latte::E_DIM::DIM_2D_ARRAY)
|
||||
;
|
||||
else if (baseDim == Latte::E_DIM::DIM_2D_ARRAY && viewDim == Latte::E_DIM::DIM_2D_ARRAY)
|
||||
;
|
||||
else if (baseDim == Latte::E_DIM::DIM_2D_ARRAY && viewDim == Latte::E_DIM::DIM_2D)
|
||||
;
|
||||
else if (baseDim == Latte::E_DIM::DIM_2D_ARRAY && viewDim == Latte::E_DIM::DIM_CUBEMAP)
|
||||
;
|
||||
else if (baseDim == Latte::E_DIM::DIM_3D && viewDim == Latte::E_DIM::DIM_2D_ARRAY)
|
||||
;
|
||||
else if (baseDim == Latte::E_DIM::DIM_2D_MSAA && viewDim == Latte::E_DIM::DIM_2D_MSAA)
|
||||
;
|
||||
else if (baseDim == Latte::E_DIM::DIM_2D_ARRAY && viewDim == Latte::E_DIM::DIM_3D)
|
||||
{
|
||||
// not compatible on OpenGL
|
||||
incompatibleDim = true;
|
||||
}
|
||||
else if (baseDim == Latte::E_DIM::DIM_2D && viewDim == Latte::E_DIM::DIM_2D_MSAA)
|
||||
{
|
||||
// not compatible
|
||||
incompatibleDim = true;
|
||||
}
|
||||
else if (baseDim == Latte::E_DIM::DIM_2D && viewDim == Latte::E_DIM::DIM_1D)
|
||||
{
|
||||
// not compatible
|
||||
incompatibleDim = true;
|
||||
}
|
||||
else if (baseDim == Latte::E_DIM::DIM_2D && viewDim == Latte::E_DIM::DIM_3D)
|
||||
{
|
||||
// not compatible
|
||||
incompatibleDim = true;
|
||||
}
|
||||
else if (baseDim == Latte::E_DIM::DIM_3D && viewDim == Latte::E_DIM::DIM_2D)
|
||||
{
|
||||
// not compatible
|
||||
incompatibleDim = true;
|
||||
}
|
||||
else if (baseDim == Latte::E_DIM::DIM_3D && viewDim == Latte::E_DIM::DIM_3D)
|
||||
{
|
||||
// incompatible by default, but may be compatible if the view matches the depth of the base texture and starts at mip/slice 0
|
||||
incompatibleDim = true;
|
||||
}
|
||||
else if ((baseDim == Latte::E_DIM::DIM_2D && viewDim == Latte::E_DIM::DIM_CUBEMAP) ||
|
||||
(baseDim == Latte::E_DIM::DIM_CUBEMAP && viewDim == Latte::E_DIM::DIM_2D))
|
||||
{
|
||||
// not compatible
|
||||
incompatibleDim = true;
|
||||
}
|
||||
else if (baseDim == Latte::E_DIM::DIM_2D_MSAA && viewDim == Latte::E_DIM::DIM_2D)
|
||||
{
|
||||
// not compatible
|
||||
incompatibleDim = true;
|
||||
}
|
||||
else if (baseDim == Latte::E_DIM::DIM_1D && viewDim == Latte::E_DIM::DIM_2D)
|
||||
{
|
||||
// not compatible (probably?)
|
||||
incompatibleDim = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
cemu_assert_debug(false);
|
||||
incompatibleDim = true;
|
||||
}
|
||||
return !incompatibleDim;
|
||||
// Note that some combinations depend on the exact view/slice index and count which we currently ignore (like a 3D view of a 3D texture)
|
||||
bool isCompatible =
|
||||
(baseDim == viewDim) ||
|
||||
(baseDim == Latte::E_DIM::DIM_CUBEMAP && viewDim == Latte::E_DIM::DIM_2D) ||
|
||||
(baseDim == Latte::E_DIM::DIM_2D && viewDim == Latte::E_DIM::DIM_2D_ARRAY) ||
|
||||
(baseDim == Latte::E_DIM::DIM_2D_ARRAY && viewDim == Latte::E_DIM::DIM_2D) ||
|
||||
(baseDim == Latte::E_DIM::DIM_CUBEMAP && viewDim == Latte::E_DIM::DIM_2D_ARRAY) ||
|
||||
(baseDim == Latte::E_DIM::DIM_2D_ARRAY && viewDim == Latte::E_DIM::DIM_CUBEMAP) ||
|
||||
(baseDim == Latte::E_DIM::DIM_3D && viewDim == Latte::E_DIM::DIM_2D_ARRAY);
|
||||
if(isCompatible)
|
||||
return true;
|
||||
// these combinations have been seen in use by games and are considered incompatible:
|
||||
// (baseDim == Latte::E_DIM::DIM_2D_ARRAY && viewDim == Latte::E_DIM::DIM_3D) -> Not allowed on OpenGL
|
||||
// (baseDim == Latte::E_DIM::DIM_2D && viewDim == Latte::E_DIM::DIM_2D_MSAA)
|
||||
// (baseDim == Latte::E_DIM::DIM_2D && viewDim == Latte::E_DIM::DIM_1D)
|
||||
// (baseDim == Latte::E_DIM::DIM_2D && viewDim == Latte::E_DIM::DIM_3D)
|
||||
// (baseDim == Latte::E_DIM::DIM_3D && viewDim == Latte::E_DIM::DIM_2D)
|
||||
// (baseDim == Latte::E_DIM::DIM_3D && viewDim == Latte::E_DIM::DIM_3D) -> Only compatible if the same depth and shared at mip/slice 0
|
||||
// (baseDim == Latte::E_DIM::DIM_2D && viewDim == Latte::E_DIM::DIM_CUBEMAP)
|
||||
// (baseDim == Latte::E_DIM::DIM_2D_MSAA && viewDim == Latte::E_DIM::DIM_2D)
|
||||
// (baseDim == Latte::E_DIM::DIM_1D && viewDim == Latte::E_DIM::DIM_2D)
|
||||
return false;
|
||||
}
|
||||
|
||||
VIEWCOMPATIBILITY LatteTexture_CanTextureBeRepresentedAsView(LatteTexture* baseTexture, uint32 physAddr, sint32 width, sint32 height, sint32 pitch, Latte::E_DIM dimView, Latte::E_GX2SURFFMT format, bool isDepth, sint32 firstMip, sint32 numMip, sint32 firstSlice, sint32 numSlice, sint32& relativeMipIndex, sint32& relativeSliceIndex)
|
||||
{
|
||||
relativeMipIndex = 0;
|
||||
relativeSliceIndex = 0;
|
||||
if (baseTexture->overwriteInfo.hasFormatOverwrite)
|
||||
{
|
||||
// if the base format is overwritten, then we only allow aliasing if the view format matches the base format
|
||||
if (baseTexture->format != format)
|
||||
return VIEW_NOT_COMPATIBLE;
|
||||
}
|
||||
if (LatteTexture_IsFormatViewCompatible(baseTexture->format, format) == false)
|
||||
return VIEW_NOT_COMPATIBLE;
|
||||
if (baseTexture->physAddress == physAddr && baseTexture->pitch == pitch)
|
||||
@ -881,7 +841,7 @@ VIEWCOMPATIBILITY LatteTexture_CanTextureBeRepresentedAsView(LatteTexture* baseT
|
||||
return VIEW_NOT_COMPATIBLE;
|
||||
// 3D views are only compatible on Vulkan if they match the base texture in regards to mip and slice count
|
||||
bool isCompatible3DView = dimView == Latte::E_DIM::DIM_3D && baseTexture->dim == dimView && firstSlice == 0 && firstMip == 0 && baseTexture->mipLevels == numMip && baseTexture->depth == numSlice;
|
||||
if (!isCompatible3DView && !IsDimensionCompatibleForView(baseTexture->dim, dimView))
|
||||
if (!isCompatible3DView && !IsDimensionCompatibleForGX2View(baseTexture->dim, dimView))
|
||||
return VIEW_NOT_COMPATIBLE;
|
||||
if (baseTexture->isDepth && baseTexture->format != format)
|
||||
{
|
||||
@ -933,7 +893,7 @@ VIEWCOMPATIBILITY LatteTexture_CanTextureBeRepresentedAsView(LatteTexture* baseT
|
||||
if (!LatteTexture_IsTexelSizeCompatibleFormat(baseTexture->format, format) )
|
||||
return VIEW_NOT_COMPATIBLE;
|
||||
|
||||
if (!IsDimensionCompatibleForView(baseTexture->dim, dimView))
|
||||
if (!IsDimensionCompatibleForGX2View(baseTexture->dim, dimView))
|
||||
return VIEW_NOT_COMPATIBLE;
|
||||
if (baseTexture->isDepth && baseTexture->format != format)
|
||||
{
|
||||
@ -1007,7 +967,7 @@ void LatteTexture_RecreateTextureWithDifferentMipSliceCount(LatteTexture* textur
|
||||
}
|
||||
|
||||
// create new texture representation
|
||||
// if allowCreateNewDataTexture is true, a new texture will be created if necessary. If it is false, only existing textures may be used, except if a data-compatible version of the requested texture already exists and it's not view compatible
|
||||
// if allowCreateNewDataTexture is true, a new texture will be created if necessary. If it is false, only existing textures may be used, except if a data-compatible version of the requested texture already exists and it's not view compatible (todo - we should differentiate between Latte compatible views and renderer compatible)
|
||||
// the returned view will map to the provided mip and slice range within the created texture, this is to match the behavior of lookupSliceEx
|
||||
LatteTextureView* LatteTexture_CreateMapping(MPTR physAddr, MPTR physMipAddr, sint32 width, sint32 height, sint32 depth, sint32 pitch, Latte::E_HWTILEMODE tileMode, uint32 swizzle, sint32 firstMip, sint32 numMip, sint32 firstSlice, sint32 numSlice, Latte::E_GX2SURFFMT format, Latte::E_DIM dimBase, Latte::E_DIM dimView, bool isDepth, bool allowCreateNewDataTexture)
|
||||
{
|
||||
@ -1024,7 +984,7 @@ LatteTextureView* LatteTexture_CreateMapping(MPTR physAddr, MPTR physMipAddr, si
|
||||
// todo, depth and numSlice are redundant
|
||||
|
||||
sint32 sliceCount = firstSlice + numSlice;
|
||||
std::vector<LatteTexture*> list_overlappingTextures;
|
||||
boost::container::small_vector<LatteTexture*, 16> list_overlappingTextures;
|
||||
for (sint32 sliceIndex = 0; sliceIndex < sliceCount; sliceIndex++)
|
||||
{
|
||||
sint32 mipIndex = 0;
|
||||
@ -1243,6 +1203,15 @@ std::vector<LatteTexture*>& LatteTexture::GetAllTextures()
|
||||
return sAllTextures;
|
||||
}
|
||||
|
||||
bool LatteTexture_GX2FormatHasStencil(bool isDepth, Latte::E_GX2SURFFMT format)
|
||||
{
|
||||
if (!isDepth)
|
||||
return false;
|
||||
return format == Latte::E_GX2SURFFMT::D24_S8_UNORM ||
|
||||
format == Latte::E_GX2SURFFMT::D24_S8_FLOAT ||
|
||||
format == Latte::E_GX2SURFFMT::D32_S8_FLOAT;
|
||||
}
|
||||
|
||||
LatteTexture::LatteTexture(Latte::E_DIM dim, MPTR physAddress, MPTR physMipAddress, Latte::E_GX2SURFFMT format, uint32 width, uint32 height, uint32 depth, uint32 pitch, uint32 mipLevels, uint32 swizzle,
|
||||
Latte::E_HWTILEMODE tileMode, bool isDepth)
|
||||
{
|
||||
@ -1261,6 +1230,7 @@ LatteTexture::LatteTexture(Latte::E_DIM dim, MPTR physAddress, MPTR physMipAddre
|
||||
this->mipLevels = mipLevels;
|
||||
this->tileMode = tileMode;
|
||||
this->isDepth = isDepth;
|
||||
this->hasStencil = LatteTexture_GX2FormatHasStencil(isDepth, format);
|
||||
this->physMipAddress = physMipAddress;
|
||||
this->lastUpdateEventCounter = LatteTexture_getNextUpdateEventCounter();
|
||||
this->lastWriteEventCounter = LatteTexture_getNextUpdateEventCounter();
|
||||
|
@ -27,6 +27,8 @@ public:
|
||||
LatteTexture(Latte::E_DIM dim, MPTR physAddress, MPTR physMipAddress, Latte::E_GX2SURFFMT format, uint32 width, uint32 height, uint32 depth, uint32 pitch, uint32 mipLevels, uint32 swizzle, Latte::E_HWTILEMODE tileMode, bool isDepth);
|
||||
virtual ~LatteTexture();
|
||||
|
||||
virtual void AllocateOnHost() = 0;
|
||||
|
||||
LatteTextureView* GetOrCreateView(Latte::E_DIM dim, Latte::E_GX2SURFFMT format, sint32 firstMip, sint32 mipCount, sint32 firstSlice, sint32 sliceCount)
|
||||
{
|
||||
for (auto& itr : views)
|
||||
@ -55,6 +57,29 @@ public:
|
||||
|
||||
bool Is3DTexture() const { return dim == Latte::E_DIM::DIM_3D; };
|
||||
|
||||
void GetSize(sint32& width, sint32& height, sint32 mipLevel) const
|
||||
{
|
||||
width = std::max(1, this->width >> mipLevel);
|
||||
height = std::max(1, this->height >> mipLevel);
|
||||
}
|
||||
|
||||
// similar to GetSize, but returns the real size of the texture taking into account any resolution overwrite by gfx pack rules
|
||||
void GetEffectiveSize(sint32& effectiveWidth, sint32& effectiveHeight, sint32 mipLevel) const
|
||||
{
|
||||
if( overwriteInfo.hasResolutionOverwrite )
|
||||
{
|
||||
effectiveWidth = overwriteInfo.width;
|
||||
effectiveHeight = overwriteInfo.height;
|
||||
}
|
||||
else
|
||||
{
|
||||
effectiveWidth = this->width;
|
||||
effectiveHeight = this->height;
|
||||
}
|
||||
effectiveWidth = std::max(1, effectiveWidth >> mipLevel);
|
||||
effectiveHeight = std::max(1, effectiveHeight >> mipLevel);
|
||||
}
|
||||
|
||||
sint32 GetMipDepth(sint32 mipIndex)
|
||||
{
|
||||
cemu_assert_debug(mipIndex >= 0 && mipIndex < this->mipLevels);
|
||||
@ -310,8 +335,6 @@ void LatteTexture_Delete(LatteTexture* texture);
|
||||
|
||||
void LatteTextureLoader_writeReadbackTextureToMemory(LatteTextureDefinition* textureData, uint32 sliceIndex, uint32 mipIndex, uint8* linearPixelData);
|
||||
|
||||
void LatteTexture_getSize(LatteTexture* texture, sint32* width, sint32* height, sint32* depth, sint32 mipLevel);
|
||||
void LatteTexture_getEffectiveSize(LatteTexture* texture, sint32* effectiveWidth, sint32* effectiveHeight, sint32* effectiveDepth, sint32 mipLevel = 0);
|
||||
sint32 LatteTexture_getEffectiveWidth(LatteTexture* texture);
|
||||
bool LatteTexture_doesEffectiveRescaleRatioMatch(LatteTexture* texture1, sint32 mipLevel1, LatteTexture* texture2, sint32 mipLevel2);
|
||||
void LatteTexture_scaleToEffectiveSize(LatteTexture* texture, sint32* x, sint32* y, sint32 mipLevel);
|
||||
|
@ -316,7 +316,7 @@ void LatteTexture_Delete(LatteTexture* texture)
|
||||
delete[] texture->sliceMipInfo;
|
||||
texture->sliceMipInfo = nullptr;
|
||||
}
|
||||
g_renderer->texture_destroy(texture);
|
||||
delete texture;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -207,13 +207,13 @@ void LatteTexture_updateTexturesForStage(LatteDecompilerShader* shaderContext, u
|
||||
bool isDepthSampler = shaderContext->textureUsesDepthCompare[textureIndex];
|
||||
// look for already existing texture
|
||||
LatteTextureView* textureView;
|
||||
if (isDepthSampler == false)
|
||||
if (!isDepthSampler)
|
||||
textureView = LatteTextureViewLookupCache::lookup(physAddr, width, height, depth, pitch, viewFirstMip, viewNumMips, viewFirstSlice, viewNumSlices, format, dim);
|
||||
else
|
||||
textureView = LatteTextureViewLookupCache::lookup(physAddr, width, height, depth, pitch, viewFirstMip, viewNumMips, viewFirstSlice, viewNumSlices, format, dim, true);
|
||||
if (textureView == nullptr)
|
||||
textureView = LatteTextureViewLookupCache::lookupWithColorOrDepthType(physAddr, width, height, depth, pitch, viewFirstMip, viewNumMips, viewFirstSlice, viewNumSlices, format, dim, true);
|
||||
if (!textureView)
|
||||
{
|
||||
// create new mapping
|
||||
// view not found, create a new mapping which will also create a new texture if necessary
|
||||
textureView = LatteTexture_CreateMapping(physAddr, physMipAddr, width, height, depth, pitch, tileMode, swizzle, viewFirstMip, viewNumMips, viewFirstSlice, viewNumSlices, format, dim, dim, isDepthSampler);
|
||||
if (textureView == nullptr)
|
||||
continue;
|
||||
@ -229,21 +229,16 @@ void LatteTexture_updateTexturesForStage(LatteDecompilerShader* shaderContext, u
|
||||
// if this texture is bound multiple times then use alternative views
|
||||
if (textureView->lastTextureBindIndex == LatteGPUState.textureBindCounter)
|
||||
{
|
||||
// Intel driver has issues with textures that have multiple views bound and used by a shader, causes a softlock in BotW
|
||||
// therefore we disable this on Intel
|
||||
if (LatteGPUState.glVendor != GLVENDOR_INTEL_NOLEGACY)
|
||||
LatteTextureViewGL* textureViewGL = (LatteTextureViewGL*)textureView;
|
||||
// get next unused alternative texture view
|
||||
while (true)
|
||||
{
|
||||
LatteTextureViewGL* textureViewGL = (LatteTextureViewGL*)textureView;
|
||||
// get next unused alternative texture view
|
||||
while (true)
|
||||
{
|
||||
textureViewGL = textureViewGL->GetAlternativeView();
|
||||
if (textureViewGL->lastTextureBindIndex != LatteGPUState.textureBindCounter)
|
||||
break;
|
||||
}
|
||||
textureView = textureViewGL;
|
||||
textureViewGL = textureViewGL->GetAlternativeView();
|
||||
if (textureViewGL->lastTextureBindIndex != LatteGPUState.textureBindCounter)
|
||||
break;
|
||||
}
|
||||
}
|
||||
textureView = textureViewGL;
|
||||
}
|
||||
textureView->lastTextureBindIndex = LatteGPUState.textureBindCounter;
|
||||
rendererGL->renderstate_updateTextureSettingsGL(shaderContext, textureView, textureIndex + glBackendBaseTexUnit, word4, textureIndex, isDepthSampler);
|
||||
}
|
||||
@ -273,9 +268,7 @@ void LatteTexture_updateTexturesForStage(LatteDecompilerShader* shaderContext, u
|
||||
// check for changes
|
||||
if (LatteTC_HasTextureChanged(textureView->baseTexture) || swizzleChanged)
|
||||
{
|
||||
#ifdef CEMU_DEBUG_ASSERT
|
||||
debug_printf("Reload texture 0x%08x res %dx%d memRange %08x-%08x SwizzleChange: %s\n", textureView->baseTexture->physAddress, textureView->baseTexture->width, textureView->baseTexture->height, textureView->baseTexture->texDataPtrLow, textureView->baseTexture->texDataPtrHigh, swizzleChanged ? "yes" : "no");
|
||||
#endif
|
||||
// update swizzle / changed mip address
|
||||
if (swizzleChanged)
|
||||
{
|
||||
@ -338,44 +331,6 @@ void LatteTexture_updateTextures()
|
||||
LatteTexture_updateTexturesForStage(geometryShader, LATTE_CEMU_GS_TEX_UNIT_BASE, LatteGPUState.contextNew.SQ_TEX_START_GS);
|
||||
}
|
||||
|
||||
// returns the width, height, depth of the texture
|
||||
void LatteTexture_getSize(LatteTexture* texture, sint32* width, sint32* height, sint32* depth, sint32 mipLevel)
|
||||
{
|
||||
*width = texture->width;
|
||||
*height = texture->height;
|
||||
if (depth != NULL)
|
||||
*depth = texture->depth;
|
||||
// handle mip level
|
||||
*width = std::max(1, *width >> mipLevel);
|
||||
*height = std::max(1, *height >> mipLevel);
|
||||
if(texture->Is3DTexture() && depth)
|
||||
*depth = std::max(1, *depth >> mipLevel);
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the internally used width/height/depth of the texture
|
||||
* Usually this is the width/height/depth specified by the game,
|
||||
* unless the texture resolution was redefined via graphic pack texture rules
|
||||
*/
|
||||
void LatteTexture_getEffectiveSize(LatteTexture* texture, sint32* effectiveWidth, sint32* effectiveHeight, sint32* effectiveDepth, sint32 mipLevel)
|
||||
{
|
||||
*effectiveWidth = texture->width;
|
||||
*effectiveHeight = texture->height;
|
||||
if( effectiveDepth != NULL )
|
||||
*effectiveDepth = texture->depth;
|
||||
if( texture->overwriteInfo.hasResolutionOverwrite )
|
||||
{
|
||||
*effectiveWidth = texture->overwriteInfo.width;
|
||||
*effectiveHeight = texture->overwriteInfo.height;
|
||||
if( effectiveDepth != NULL )
|
||||
*effectiveDepth = texture->overwriteInfo.depth;
|
||||
}
|
||||
// handle mipLevel
|
||||
// todo: Mip-mapped 3D textures decrease in depth also?
|
||||
*effectiveWidth = std::max(1, *effectiveWidth >> mipLevel);
|
||||
*effectiveHeight = std::max(1, *effectiveHeight >> mipLevel);
|
||||
}
|
||||
|
||||
sint32 LatteTexture_getEffectiveWidth(LatteTexture* texture)
|
||||
{
|
||||
if (texture->overwriteInfo.hasResolutionOverwrite)
|
||||
|
@ -621,7 +621,7 @@ void LatteTextureLoader_UpdateTextureSliceData(LatteTexture* tex, uint32 sliceIn
|
||||
|
||||
if (tex->isDataDefined == false)
|
||||
{
|
||||
g_renderer->texture_reserveTextureOnGPU(tex);
|
||||
tex->AllocateOnHost();
|
||||
tex->isDataDefined = true;
|
||||
// if decoder is not set then clear texture
|
||||
// on Vulkan this is used to make sure the texture is no longer in UNDEFINED layout
|
||||
|
@ -143,7 +143,6 @@ void LatteTextureViewLookupCache::RemoveAll(LatteTextureView* view)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
LatteTextureView* LatteTextureViewLookupCache::lookup(MPTR physAddr, sint32 width, sint32 height, sint32 depth, sint32 pitch, sint32 firstMip, sint32 numMip, sint32 firstSlice, sint32 numSlice, Latte::E_GX2SURFFMT format, Latte::E_DIM dim)
|
||||
{
|
||||
// todo - add tileMode param to this and the other lookup functions?
|
||||
@ -163,7 +162,7 @@ LatteTextureView* LatteTextureViewLookupCache::lookup(MPTR physAddr, sint32 widt
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
LatteTextureView* LatteTextureViewLookupCache::lookup(MPTR physAddr, sint32 width, sint32 height, sint32 depth, sint32 pitch, sint32 firstMip, sint32 numMip, sint32 firstSlice, sint32 numSlice, Latte::E_GX2SURFFMT format, Latte::E_DIM dim, bool isDepth)
|
||||
LatteTextureView* LatteTextureViewLookupCache::lookupWithColorOrDepthType(MPTR physAddr, sint32 width, sint32 height, sint32 depth, sint32 pitch, sint32 firstMip, sint32 numMip, sint32 firstSlice, sint32 numSlice, Latte::E_GX2SURFFMT format, Latte::E_DIM dim, bool isDepth)
|
||||
{
|
||||
cemu_assert_debug(firstSlice == 0);
|
||||
uint32 key = _getViewBucketKey(physAddr, width, height, pitch);
|
||||
|
@ -41,7 +41,7 @@ public:
|
||||
static void RemoveAll(LatteTextureView* view);
|
||||
|
||||
static LatteTextureView* lookup(MPTR physAddr, sint32 width, sint32 height, sint32 depth, sint32 pitch, sint32 firstMip, sint32 numMip, sint32 firstSlice, sint32 numSlice, Latte::E_GX2SURFFMT format, Latte::E_DIM dim);
|
||||
static LatteTextureView* lookup(MPTR physAddr, sint32 width, sint32 height, sint32 depth, sint32 pitch, sint32 firstMip, sint32 numMip, sint32 firstSlice, sint32 numSlice, Latte::E_GX2SURFFMT format, Latte::E_DIM dim, bool isDepth);
|
||||
static LatteTextureView* lookupWithColorOrDepthType(MPTR physAddr, sint32 width, sint32 height, sint32 depth, sint32 pitch, sint32 firstMip, sint32 numMip, sint32 firstSlice, sint32 numSlice, Latte::E_GX2SURFFMT format, Latte::E_DIM dim, bool isDepth);
|
||||
static LatteTextureView* lookupSlice(MPTR physAddr, sint32 width, sint32 height, sint32 pitch, sint32 firstMip, sint32 firstSlice, Latte::E_GX2SURFFMT format);
|
||||
static LatteTextureView* lookupSliceMinSize(MPTR physAddr, sint32 minWidth, sint32 minHeight, sint32 pitch, sint32 firstMip, sint32 firstSlice, Latte::E_GX2SURFFMT format);
|
||||
static LatteTextureView* lookupSliceEx(MPTR physAddr, sint32 width, sint32 height, sint32 pitch, sint32 firstMip, sint32 firstSlice, Latte::E_GX2SURFFMT format, bool isDepth);
|
||||
|
@ -140,13 +140,7 @@ int Latte_ThreadEntry()
|
||||
case GfxVendor::AMD:
|
||||
LatteGPUState.glVendor = GLVENDOR_AMD;
|
||||
break;
|
||||
case GfxVendor::IntelLegacy:
|
||||
LatteGPUState.glVendor = GLVENDOR_INTEL_LEGACY;
|
||||
break;
|
||||
case GfxVendor::IntelNoLegacy:
|
||||
LatteGPUState.glVendor = GLVENDOR_INTEL_NOLEGACY;
|
||||
break;
|
||||
case GfxVendor::Intel:
|
||||
case GfxVendor::Intel:
|
||||
LatteGPUState.glVendor = GLVENDOR_INTEL;
|
||||
break;
|
||||
case GfxVendor::Nvidia:
|
||||
@ -180,6 +174,23 @@ int Latte_ThreadEntry()
|
||||
|
||||
// before doing anything with game specific shaders, we need to wait for graphic packs to finish loading
|
||||
GraphicPack2::WaitUntilReady();
|
||||
// if legacy packs are enabled we cannot use the colorbuffer resolution optimization
|
||||
LatteGPUState.allowFramebufferSizeOptimization = true;
|
||||
for(auto& pack : GraphicPack2::GetActiveGraphicPacks())
|
||||
{
|
||||
if(pack->AllowRendertargetSizeOptimization())
|
||||
continue;
|
||||
for(auto& rule : pack->GetTextureRules())
|
||||
{
|
||||
if(rule.filter_settings.width >= 0 || rule.filter_settings.height >= 0 || rule.filter_settings.depth >= 0 ||
|
||||
rule.overwrite_settings.width >= 0 || rule.overwrite_settings.height >= 0 || rule.overwrite_settings.depth >= 0)
|
||||
{
|
||||
LatteGPUState.allowFramebufferSizeOptimization = false;
|
||||
cemuLog_log(LogType::Force, "Graphic pack \"{}\" prevents rendertarget size optimization. This warning can be ignored and is intended for graphic pack developers", pack->GetName());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// load disk shader cache
|
||||
LatteShaderCache_Load();
|
||||
// init registers
|
||||
|
@ -345,6 +345,11 @@ namespace Latte
|
||||
return IsCompressedFormat((Latte::E_HWSURFFMT)((uint32)format & 0x3F));
|
||||
}
|
||||
|
||||
inline bool IsMSAA(Latte::E_DIM dim)
|
||||
{
|
||||
return dim == E_DIM::DIM_2D_MSAA || dim == E_DIM::DIM_2D_ARRAY_MSAA;
|
||||
}
|
||||
|
||||
enum GPU_LIMITS
|
||||
{
|
||||
NUM_VERTEX_BUFFERS = 16,
|
||||
|
@ -101,7 +101,8 @@ bool LatteDecompiler_ParseCFInstruction(LatteDecompilerShaderContext* shaderCont
|
||||
// ignored (we use ALU/IF/ELSE/PUSH/POP clauses to determine code flow)
|
||||
return true;
|
||||
}
|
||||
else if (cf_inst23_7 == GPU7_CF_INST_LOOP_START_DX10 || cf_inst23_7 == GPU7_CF_INST_LOOP_END)
|
||||
else if (cf_inst23_7 == GPU7_CF_INST_LOOP_START_DX10 || cf_inst23_7 == GPU7_CF_INST_LOOP_END ||
|
||||
cf_inst23_7 == GPU7_CF_INST_LOOP_START_NO_AL)
|
||||
{
|
||||
LatteDecompilerCFInstruction& cfInstruction = instructionList.emplace_back();
|
||||
// set type and address
|
||||
@ -966,7 +967,8 @@ void LatteDecompiler_ParseClauses(LatteDecompilerShaderContext* decompilerContex
|
||||
{
|
||||
// no sub-instructions
|
||||
}
|
||||
else if (cfInstruction.type == GPU7_CF_INST_LOOP_START_DX10 || cfInstruction.type == GPU7_CF_INST_LOOP_END)
|
||||
else if (cfInstruction.type == GPU7_CF_INST_LOOP_START_DX10 || cfInstruction.type == GPU7_CF_INST_LOOP_END ||
|
||||
cfInstruction.type == GPU7_CF_INST_LOOP_START_NO_AL)
|
||||
{
|
||||
// no sub-instructions
|
||||
}
|
||||
|
@ -441,7 +441,8 @@ void LatteDecompiler_analyzeSubroutine(LatteDecompilerShaderContext* shaderConte
|
||||
{
|
||||
shaderContext->analyzer.modifiesPixelActiveState = true;
|
||||
}
|
||||
else if (cfInstruction.type == GPU7_CF_INST_LOOP_START_DX10 || cfInstruction.type == GPU7_CF_INST_LOOP_END)
|
||||
else if (cfInstruction.type == GPU7_CF_INST_LOOP_START_DX10 || cfInstruction.type == GPU7_CF_INST_LOOP_END ||
|
||||
cfInstruction.type == GPU7_CF_INST_LOOP_START_NO_AL)
|
||||
{
|
||||
shaderContext->analyzer.modifiesPixelActiveState = true;
|
||||
}
|
||||
@ -685,7 +686,8 @@ void LatteDecompiler_analyze(LatteDecompilerShaderContext* shaderContext, LatteD
|
||||
{
|
||||
shaderContext->analyzer.modifiesPixelActiveState = true;
|
||||
}
|
||||
else if (cfInstruction.type == GPU7_CF_INST_LOOP_START_DX10 || cfInstruction.type == GPU7_CF_INST_LOOP_END)
|
||||
else if (cfInstruction.type == GPU7_CF_INST_LOOP_START_DX10 || cfInstruction.type == GPU7_CF_INST_LOOP_END ||
|
||||
cfInstruction.type == GPU7_CF_INST_LOOP_START_NO_AL)
|
||||
{
|
||||
shaderContext->analyzer.modifiesPixelActiveState = true;
|
||||
shaderContext->analyzer.hasLoops = true;
|
||||
@ -787,7 +789,7 @@ void LatteDecompiler_analyze(LatteDecompilerShaderContext* shaderContext, LatteD
|
||||
continue;
|
||||
LatteDecompilerShader::QuickBufferEntry entry;
|
||||
entry.index = i;
|
||||
entry.size = shaderContext->analyzer.uniformBufferAccessTracker[i].DetermineSize(LATTE_GLSL_DYNAMIC_UNIFORM_BLOCK_SIZE) * 16;
|
||||
entry.size = shaderContext->analyzer.uniformBufferAccessTracker[i].DetermineSize(shaderContext->shaderBaseHash, LATTE_GLSL_DYNAMIC_UNIFORM_BLOCK_SIZE) * 16;
|
||||
shader->list_quickBufferList.push_back(entry);
|
||||
}
|
||||
// get dimension of each used texture
|
||||
@ -929,7 +931,8 @@ void LatteDecompiler_analyze(LatteDecompilerShaderContext* shaderContext, LatteD
|
||||
if (cfCurrentStackDepth < 0)
|
||||
debugBreakpoint();
|
||||
}
|
||||
else if (cfInstruction.type == GPU7_CF_INST_LOOP_START_DX10 || cfInstruction.type == GPU7_CF_INST_LOOP_END)
|
||||
else if (cfInstruction.type == GPU7_CF_INST_LOOP_START_DX10 || cfInstruction.type == GPU7_CF_INST_LOOP_END ||
|
||||
cfInstruction.type == GPU7_CF_INST_LOOP_START_NO_AL)
|
||||
{
|
||||
// no effect on stack depth
|
||||
cfInstruction.activeStackDepth = cfCurrentStackDepth;
|
||||
|
@ -973,7 +973,7 @@ void _emitOperandInputCode(LatteDecompilerShaderContext* shaderContext, LatteDec
|
||||
}
|
||||
else
|
||||
{
|
||||
cemuLog_log(LogType::Force, "Unsupported shader ALU operand sel 0x%x\n", aluInstruction->sourceOperand[operandIndex].sel);
|
||||
cemuLog_log(LogType::Force, "Unsupported shader ALU operand sel {:#x}\n", aluInstruction->sourceOperand[operandIndex].sel);
|
||||
debugBreakpoint();
|
||||
}
|
||||
|
||||
@ -3662,7 +3662,8 @@ void LatteDecompiler_emitClauseCode(LatteDecompilerShaderContext* shaderContext,
|
||||
{
|
||||
src->addFmt("{} = {} == true && {} == true;" _CRLF, _getActiveMaskCVarName(shaderContext, cfInstruction->activeStackDepth + 1 - cfInstruction->popCount), _getActiveMaskVarName(shaderContext, cfInstruction->activeStackDepth - cfInstruction->popCount), _getActiveMaskCVarName(shaderContext, cfInstruction->activeStackDepth - cfInstruction->popCount));
|
||||
}
|
||||
else if( cfInstruction->type == GPU7_CF_INST_LOOP_START_DX10 )
|
||||
else if( cfInstruction->type == GPU7_CF_INST_LOOP_START_DX10 ||
|
||||
cfInstruction->type == GPU7_CF_INST_LOOP_START_NO_AL)
|
||||
{
|
||||
// start of loop
|
||||
// if pixel is disabled, then skip loop
|
||||
|
@ -241,6 +241,16 @@ void LatteDecompiler_emitAttributeDecodeGLSL(LatteDecompilerShader* shaderContex
|
||||
src->add("attrDecoder.z = floatBitsToUint(max(float(int(attrDecoder.z))/32767.0,-1.0));" _CRLF);
|
||||
src->add("attrDecoder.w = floatBitsToUint(max(float(int(attrDecoder.w))/32767.0,-1.0));" _CRLF);
|
||||
}
|
||||
else if( attrib->format == FMT_16_16_16_16 && attrib->nfa == 2 && attrib->isSigned == 1 )
|
||||
{
|
||||
// seen in Rabbids Land
|
||||
_readLittleEndianAttributeU16x4(shaderContext, src, attributeInputIndex);
|
||||
src->add("if( (attrDecoder.x&0x8000) != 0 ) attrDecoder.x |= 0xFFFF0000;" _CRLF);
|
||||
src->add("if( (attrDecoder.y&0x8000) != 0 ) attrDecoder.y |= 0xFFFF0000;" _CRLF);
|
||||
src->add("if( (attrDecoder.z&0x8000) != 0 ) attrDecoder.z |= 0xFFFF0000;" _CRLF);
|
||||
src->add("if( (attrDecoder.w&0x8000) != 0 ) attrDecoder.w |= 0xFFFF0000;" _CRLF);
|
||||
src->add("attrDecoder.xyzw = floatBitsToUint(vec4(ivec4(attrDecoder)));" _CRLF);
|
||||
}
|
||||
else if (attrib->format == FMT_16_16_16_16_FLOAT && attrib->nfa == 2)
|
||||
{
|
||||
// seen in Giana Sisters: Twisted Dreams
|
||||
@ -496,3 +506,5 @@ void LatteDecompiler_emitAttributeDecodeGLSL(LatteDecompilerShader* shaderContex
|
||||
cemu_assert_debug(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -37,7 +37,7 @@ namespace LatteDecompiler
|
||||
}
|
||||
else if (decompilerContext->shader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_FULL_CFILE)
|
||||
{
|
||||
uint32 cfileSize = decompilerContext->analyzer.uniformRegisterAccessTracker.DetermineSize(256);
|
||||
uint32 cfileSize = decompilerContext->analyzer.uniformRegisterAccessTracker.DetermineSize(decompilerContext->shaderBaseHash, 256);
|
||||
// full or partial uniform register file has to be present
|
||||
if (shaderType == LatteConst::ShaderType::Vertex)
|
||||
shaderSrc->addFmt("uniform ivec4 uf_uniformRegisterVS[{}];" _CRLF, cfileSize);
|
||||
@ -156,7 +156,7 @@ namespace LatteDecompiler
|
||||
|
||||
shaderSrc->addFmt("uniform {}{}" _CRLF, _getShaderUniformBlockInterfaceName(decompilerContext->shaderType), i);
|
||||
shaderSrc->add("{" _CRLF);
|
||||
shaderSrc->addFmt("vec4 {}{}[{}];" _CRLF, _getShaderUniformBlockVariableName(decompilerContext->shaderType), i, decompilerContext->analyzer.uniformBufferAccessTracker[i].DetermineSize(LATTE_GLSL_DYNAMIC_UNIFORM_BLOCK_SIZE));
|
||||
shaderSrc->addFmt("vec4 {}{}[{}];" _CRLF, _getShaderUniformBlockVariableName(decompilerContext->shaderType), i, decompilerContext->analyzer.uniformBufferAccessTracker[i].DetermineSize(decompilerContext->shaderBaseHash, LATTE_GLSL_DYNAMIC_UNIFORM_BLOCK_SIZE));
|
||||
shaderSrc->add("};" _CRLF _CRLF);
|
||||
shaderSrc->add(_CRLF);
|
||||
}
|
||||
|
@ -157,19 +157,23 @@ struct LatteDecompilerBufferAccessTracker
|
||||
}
|
||||
}
|
||||
|
||||
sint32 DetermineSize(sint32 maximumSize) const
|
||||
sint32 DetermineSize(uint64 shaderBaseHash, sint32 maximumSize) const
|
||||
{
|
||||
// here we try to predict the accessed range so we dont have to upload the whole buffer
|
||||
// potential risky optimization: assume that if there is a fixed-index access on an index higher than any other non-zero relative accesses, it bounds the prior relative access
|
||||
// here we try to predict the accessed byte range so we dont have to upload the whole buffer
|
||||
// if no bound can be determined then return maximumSize
|
||||
// for some known shaders we use hand-tuned values instead of the maximumSize fallback value that those shaders would normally use
|
||||
if(shaderBaseHash == 0x8ff56afdf1a2f837) // XCX text rendering
|
||||
return 24;
|
||||
if(shaderBaseHash == 0x37b9100c1310d3bb) // BotW UI backdrops 1
|
||||
return 24;
|
||||
if(shaderBaseHash == 0xf7ba548c1fefe24a) // BotW UI backdrops 2
|
||||
return 30;
|
||||
|
||||
sint32 highestAccessIndex = -1;
|
||||
if(hasStaticIndexAccess)
|
||||
{
|
||||
highestAccessIndex = highestAccessStaticIndex;
|
||||
}
|
||||
if(hasDynamicIndexAccess)
|
||||
{
|
||||
return maximumSize; // dynamic index exists and no bound can be determined
|
||||
}
|
||||
if (highestAccessIndex < 0)
|
||||
return 1; // no access at all? But avoid zero as a size
|
||||
return highestAccessIndex + 1;
|
||||
|
@ -5,20 +5,6 @@
|
||||
|
||||
#include "config/LaunchSettings.h"
|
||||
|
||||
GLuint texIdPool[64];
|
||||
sint32 texIdPoolIndex = 64;
|
||||
|
||||
static GLuint _genTextureHandleGL()
|
||||
{
|
||||
if (texIdPoolIndex == 64)
|
||||
{
|
||||
glGenTextures(64, texIdPool);
|
||||
texIdPoolIndex = 0;
|
||||
}
|
||||
texIdPoolIndex++;
|
||||
return texIdPool[texIdPoolIndex - 1];
|
||||
}
|
||||
|
||||
LatteTextureGL::LatteTextureGL(Latte::E_DIM dim, MPTR physAddress, MPTR physMipAddress, Latte::E_GX2SURFFMT format, uint32 width, uint32 height, uint32 depth, uint32 pitch, uint32 mipLevels, uint32 swizzle,
|
||||
Latte::E_HWTILEMODE tileMode, bool isDepth)
|
||||
: LatteTexture(dim, physAddress, physMipAddress, format, width, height, depth, pitch, mipLevels, swizzle, tileMode, isDepth)
|
||||
@ -26,10 +12,9 @@ LatteTextureGL::LatteTextureGL(Latte::E_DIM dim, MPTR physAddress, MPTR physMipA
|
||||
GenerateEmptyTextureFromGX2Dim(dim, this->glId_texture, this->glTexTarget, true);
|
||||
// set format info
|
||||
FormatInfoGL glFormatInfo;
|
||||
GetOpenGLFormatInfo(isDepth, format, dim, &glFormatInfo);
|
||||
GetOpenGLFormatInfo(isDepth, overwriteInfo.hasFormatOverwrite ? (Latte::E_GX2SURFFMT)overwriteInfo.format : format, dim, &glFormatInfo);
|
||||
this->glInternalFormat = glFormatInfo.glInternalFormat;
|
||||
this->isAlternativeFormat = glFormatInfo.isUsingAlternativeFormat;
|
||||
this->hasStencil = glFormatInfo.hasStencil; // todo - should get this from the GX2 format?
|
||||
// set debug name
|
||||
bool useGLDebugNames = false;
|
||||
#ifdef CEMU_DEBUG_ASSERT
|
||||
@ -88,34 +73,34 @@ void LatteTextureGL::GetOpenGLFormatInfo(bool isDepth, Latte::E_GX2SURFFMT forma
|
||||
{
|
||||
if (format == Latte::E_GX2SURFFMT::D24_S8_UNORM)
|
||||
{
|
||||
formatInfoOut->setDepthFormat(GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, true);
|
||||
formatInfoOut->setFormat(GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8);
|
||||
return;
|
||||
}
|
||||
else if (format == Latte::E_GX2SURFFMT::D24_S8_FLOAT)
|
||||
{
|
||||
formatInfoOut->setDepthFormat(GL_DEPTH32F_STENCIL8, GL_DEPTH_STENCIL, GL_FLOAT_32_UNSIGNED_INT_24_8_REV, true);
|
||||
formatInfoOut->setFormat(GL_DEPTH32F_STENCIL8, GL_DEPTH_STENCIL, GL_FLOAT_32_UNSIGNED_INT_24_8_REV);
|
||||
formatInfoOut->markAsAlternativeFormat();
|
||||
return;
|
||||
}
|
||||
else if (format == Latte::E_GX2SURFFMT::D32_S8_FLOAT)
|
||||
{
|
||||
formatInfoOut->setDepthFormat(GL_DEPTH32F_STENCIL8, GL_DEPTH_STENCIL, GL_FLOAT_32_UNSIGNED_INT_24_8_REV, true);
|
||||
formatInfoOut->setFormat(GL_DEPTH32F_STENCIL8, GL_DEPTH_STENCIL, GL_FLOAT_32_UNSIGNED_INT_24_8_REV);
|
||||
return;
|
||||
}
|
||||
else if (format == Latte::E_GX2SURFFMT::D32_FLOAT)
|
||||
{
|
||||
formatInfoOut->setDepthFormat(GL_DEPTH_COMPONENT32F, GL_DEPTH_COMPONENT, GL_FLOAT, false);
|
||||
formatInfoOut->setFormat(GL_DEPTH_COMPONENT32F, GL_DEPTH_COMPONENT, GL_FLOAT);
|
||||
return;
|
||||
}
|
||||
else if (format == Latte::E_GX2SURFFMT::D16_UNORM)
|
||||
{
|
||||
formatInfoOut->setDepthFormat(GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT, false);
|
||||
formatInfoOut->setFormat(GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT);
|
||||
return;
|
||||
}
|
||||
// unsupported depth format
|
||||
cemuLog_log(LogType::Force, "OpenGL: Unsupported texture depth format 0x{:04x}", (uint32)format);
|
||||
// use placeholder format
|
||||
formatInfoOut->setDepthFormat(GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT, false);
|
||||
formatInfoOut->setFormat(GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT);
|
||||
formatInfoOut->markAsAlternativeFormat();
|
||||
return;
|
||||
}
|
||||
@ -125,10 +110,6 @@ void LatteTextureGL::GetOpenGLFormatInfo(bool isDepth, Latte::E_GX2SURFFMT forma
|
||||
sint32 glInternalFormat;
|
||||
sint32 glSuppliedFormat;
|
||||
sint32 glSuppliedFormatType;
|
||||
// check if compressed textures should be used
|
||||
bool allowCompressedGLFormat = true;
|
||||
if (LatteGPUState.glVendor == GLVENDOR_INTEL_LEGACY)
|
||||
allowCompressedGLFormat = false; // compressed formats seem to cause more harm than good on Intel
|
||||
// get format information
|
||||
if (format == Latte::E_GX2SURFFMT::R4_G4_UNORM)
|
||||
{
|
||||
@ -164,20 +145,11 @@ void LatteTextureGL::GetOpenGLFormatInfo(bool isDepth, Latte::E_GX2SURFFMT forma
|
||||
else if (format == Latte::E_GX2SURFFMT::BC1_UNORM ||
|
||||
format == Latte::E_GX2SURFFMT::BC1_SRGB)
|
||||
{
|
||||
if (allowCompressedGLFormat)
|
||||
{
|
||||
if (format == Latte::E_GX2SURFFMT::BC1_SRGB)
|
||||
formatInfoOut->setCompressed(GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT, -1, -1);
|
||||
else
|
||||
formatInfoOut->setCompressed(GL_COMPRESSED_RGBA_S3TC_DXT1_EXT, -1, -1);
|
||||
return;
|
||||
}
|
||||
if (format == Latte::E_GX2SURFFMT::BC1_SRGB)
|
||||
formatInfoOut->setCompressed(GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT, -1, -1);
|
||||
else
|
||||
{
|
||||
formatInfoOut->setFormat(GL_RGBA16F, GL_RGBA, GL_FLOAT);
|
||||
formatInfoOut->markAsAlternativeFormat();
|
||||
return;
|
||||
}
|
||||
formatInfoOut->setCompressed(GL_COMPRESSED_RGBA_S3TC_DXT1_EXT, -1, -1);
|
||||
return;
|
||||
}
|
||||
else if (format == Latte::E_GX2SURFFMT::BC2_UNORM || format == Latte::E_GX2SURFFMT::BC2_SRGB)
|
||||
{
|
||||
@ -188,28 +160,18 @@ void LatteTextureGL::GetOpenGLFormatInfo(bool isDepth, Latte::E_GX2SURFFMT forma
|
||||
}
|
||||
else if (format == Latte::E_GX2SURFFMT::BC3_UNORM || format == Latte::E_GX2SURFFMT::BC3_SRGB)
|
||||
{
|
||||
if (allowCompressedGLFormat)
|
||||
{
|
||||
if (format == Latte::E_GX2SURFFMT::BC3_SRGB)
|
||||
formatInfoOut->setCompressed(GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT, -1, -1);
|
||||
else
|
||||
formatInfoOut->setCompressed(GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, -1, -1);
|
||||
return;
|
||||
}
|
||||
if (format == Latte::E_GX2SURFFMT::BC3_SRGB)
|
||||
formatInfoOut->setCompressed(GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT, -1, -1);
|
||||
else
|
||||
{
|
||||
// todo: SRGB support
|
||||
formatInfoOut->setFormat(GL_RGBA16F, GL_RGBA, GL_FLOAT);
|
||||
formatInfoOut->markAsAlternativeFormat();
|
||||
return;
|
||||
}
|
||||
formatInfoOut->setCompressed(GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, -1, -1);
|
||||
return;
|
||||
}
|
||||
else if (format == Latte::E_GX2SURFFMT::BC4_UNORM || format == Latte::E_GX2SURFFMT::BC4_SNORM)
|
||||
{
|
||||
bool allowCompressed = true;
|
||||
if (dim != Latte::E_DIM::DIM_2D && dim != Latte::E_DIM::DIM_2D_ARRAY)
|
||||
allowCompressedGLFormat = false; // RGTC1 does not support non-2D textures
|
||||
|
||||
if (allowCompressedGLFormat)
|
||||
allowCompressed = false; // RGTC1 does not support non-2D textures
|
||||
if (allowCompressed)
|
||||
{
|
||||
if (format == Latte::E_GX2SURFFMT::BC4_UNORM)
|
||||
formatInfoOut->setCompressed(GL_COMPRESSED_RED_RGTC1, -1, -1);
|
||||
@ -226,20 +188,11 @@ void LatteTextureGL::GetOpenGLFormatInfo(bool isDepth, Latte::E_GX2SURFFMT forma
|
||||
}
|
||||
else if (format == Latte::E_GX2SURFFMT::BC5_UNORM || format == Latte::E_GX2SURFFMT::BC5_SNORM)
|
||||
{
|
||||
if (allowCompressedGLFormat)
|
||||
{
|
||||
if (format == Latte::E_GX2SURFFMT::BC5_SNORM)
|
||||
formatInfoOut->setCompressed(GL_COMPRESSED_SIGNED_RG_RGTC2, -1, -1);
|
||||
else
|
||||
formatInfoOut->setCompressed(GL_COMPRESSED_RG_RGTC2, -1, -1);
|
||||
return;
|
||||
}
|
||||
if (format == Latte::E_GX2SURFFMT::BC5_SNORM)
|
||||
formatInfoOut->setCompressed(GL_COMPRESSED_SIGNED_RG_RGTC2, -1, -1);
|
||||
else
|
||||
{
|
||||
formatInfoOut->setFormat(GL_RG16F, GL_RG, GL_FLOAT);
|
||||
formatInfoOut->markAsAlternativeFormat();
|
||||
return;
|
||||
}
|
||||
formatInfoOut->setCompressed(GL_COMPRESSED_RG_RGTC2, -1, -1);
|
||||
return;
|
||||
}
|
||||
else if (format == Latte::E_GX2SURFFMT::R32_FLOAT)
|
||||
{
|
||||
@ -496,3 +449,49 @@ void LatteTextureGL::GetOpenGLFormatInfo(bool isDepth, Latte::E_GX2SURFFMT forma
|
||||
formatInfoOut->glIsCompressed = glIsCompressed;
|
||||
formatInfoOut->isUsingAlternativeFormat = isUsingAlternativeFormat;
|
||||
}
|
||||
|
||||
void LatteTextureGL::AllocateOnHost()
|
||||
{
|
||||
auto hostTexture = this;
|
||||
cemu_assert_debug(hostTexture->isDataDefined == false);
|
||||
sint32 effectiveBaseWidth = hostTexture->width;
|
||||
sint32 effectiveBaseHeight = hostTexture->height;
|
||||
sint32 effectiveBaseDepth = hostTexture->depth;
|
||||
if (hostTexture->overwriteInfo.hasResolutionOverwrite)
|
||||
{
|
||||
effectiveBaseWidth = hostTexture->overwriteInfo.width;
|
||||
effectiveBaseHeight = hostTexture->overwriteInfo.height;
|
||||
effectiveBaseDepth = hostTexture->overwriteInfo.depth;
|
||||
}
|
||||
// calculate mip count
|
||||
sint32 mipLevels = std::min(hostTexture->mipLevels, hostTexture->maxPossibleMipLevels);
|
||||
mipLevels = std::max(mipLevels, 1);
|
||||
// create immutable storage
|
||||
if (hostTexture->dim == Latte::E_DIM::DIM_2D || hostTexture->dim == Latte::E_DIM::DIM_2D_MSAA)
|
||||
{
|
||||
cemu_assert_debug(effectiveBaseDepth == 1);
|
||||
glTextureStorage2DWrapper(GL_TEXTURE_2D, hostTexture->glId_texture, mipLevels, hostTexture->glInternalFormat, effectiveBaseWidth, effectiveBaseHeight);
|
||||
}
|
||||
else if (hostTexture->dim == Latte::E_DIM::DIM_1D)
|
||||
{
|
||||
cemu_assert_debug(effectiveBaseHeight == 1);
|
||||
cemu_assert_debug(effectiveBaseDepth == 1);
|
||||
glTextureStorage1DWrapper(GL_TEXTURE_1D, hostTexture->glId_texture, mipLevels, hostTexture->glInternalFormat, effectiveBaseWidth);
|
||||
}
|
||||
else if (hostTexture->dim == Latte::E_DIM::DIM_2D_ARRAY || hostTexture->dim == Latte::E_DIM::DIM_2D_ARRAY_MSAA)
|
||||
{
|
||||
glTextureStorage3DWrapper(GL_TEXTURE_2D_ARRAY, hostTexture->glId_texture, mipLevels, hostTexture->glInternalFormat, effectiveBaseWidth, effectiveBaseHeight, std::max(1, effectiveBaseDepth));
|
||||
}
|
||||
else if (hostTexture->dim == Latte::E_DIM::DIM_3D)
|
||||
{
|
||||
glTextureStorage3DWrapper(GL_TEXTURE_3D, hostTexture->glId_texture, mipLevels, hostTexture->glInternalFormat, effectiveBaseWidth, effectiveBaseHeight, std::max(1, effectiveBaseDepth));
|
||||
}
|
||||
else if (hostTexture->dim == Latte::E_DIM::DIM_CUBEMAP)
|
||||
{
|
||||
glTextureStorage3DWrapper(GL_TEXTURE_CUBE_MAP_ARRAY, hostTexture->glId_texture, mipLevels, hostTexture->glInternalFormat, effectiveBaseWidth, effectiveBaseHeight, effectiveBaseDepth);
|
||||
}
|
||||
else
|
||||
{
|
||||
cemu_assert_unimplemented();
|
||||
}
|
||||
}
|
@ -11,6 +11,8 @@ public:
|
||||
|
||||
~LatteTextureGL();
|
||||
|
||||
void AllocateOnHost() override;
|
||||
|
||||
static void GenerateEmptyTextureFromGX2Dim(Latte::E_DIM dim, GLuint& texId, GLint& texTarget, bool createForTargetType);
|
||||
|
||||
protected:
|
||||
@ -23,7 +25,6 @@ public:
|
||||
sint32 glSuppliedFormat;
|
||||
sint32 glSuppliedFormatType;
|
||||
bool glIsCompressed;
|
||||
bool hasStencil{};
|
||||
bool isUsingAlternativeFormat{};
|
||||
|
||||
void setFormat(sint32 glInternalFormat, sint32 glSuppliedFormat, sint32 glSuppliedFormatType)
|
||||
@ -34,15 +35,6 @@ public:
|
||||
this->glIsCompressed = false;
|
||||
}
|
||||
|
||||
void setDepthFormat(sint32 glInternalFormat, sint32 glSuppliedFormat, sint32 glSuppliedFormatType, bool hasStencil)
|
||||
{
|
||||
this->glInternalFormat = glInternalFormat;
|
||||
this->glSuppliedFormat = glSuppliedFormat;
|
||||
this->glSuppliedFormatType = glSuppliedFormatType;
|
||||
this->glIsCompressed = false;
|
||||
this->hasStencil = hasStencil;
|
||||
}
|
||||
|
||||
void setCompressed(sint32 glInternalFormat, sint32 glSuppliedFormat, sint32 glSuppliedFormatType)
|
||||
{
|
||||
setFormat(glInternalFormat, glSuppliedFormat, glSuppliedFormatType);
|
||||
|
@ -55,12 +55,16 @@ LatteTextureViewGL::~LatteTextureViewGL()
|
||||
void LatteTextureViewGL::InitAliasView()
|
||||
{
|
||||
const auto texture = (LatteTextureGL*)baseTexture;
|
||||
// get internal format
|
||||
if (baseTexture->isDepth)
|
||||
// compute internal format
|
||||
if(texture->overwriteInfo.hasFormatOverwrite)
|
||||
{
|
||||
cemu_assert_debug(format == texture->format);
|
||||
glInternalFormat = texture->glInternalFormat; // for format overwrite no aliasing is allowed and thus we always inherit the internal format of the base texture
|
||||
}
|
||||
else if (baseTexture->isDepth)
|
||||
{
|
||||
// depth is handled differently
|
||||
cemuLog_logDebug(LogType::Force, "Creating depth view");
|
||||
cemu_assert(format == texture->format); // todo
|
||||
cemu_assert(format == texture->format); // is depth alias with different format intended?
|
||||
glInternalFormat = texture->glInternalFormat;
|
||||
}
|
||||
else
|
||||
@ -73,7 +77,7 @@ void LatteTextureViewGL::InitAliasView()
|
||||
catchOpenGLError();
|
||||
if (firstMip >= texture->maxPossibleMipLevels)
|
||||
{
|
||||
cemuLog_logDebug(LogType::Force, "_createNewView: Out of bounds mip level requested");
|
||||
cemuLog_logDebug(LogType::Force, "InitAliasView(): Out of bounds mip level requested");
|
||||
glTextureView(glTexId, glTexTarget, texture->glId_texture, glInternalFormat, texture->maxPossibleMipLevels - 1, numMip, firstSlice, this->numSlice);
|
||||
}
|
||||
else
|
||||
|
@ -25,11 +25,14 @@
|
||||
#define STRINGIFY2(X) #X
|
||||
#define STRINGIFY(X) STRINGIFY2(X)
|
||||
|
||||
namespace CemuGL
|
||||
{
|
||||
#define GLFUNC(__type, __name) __type __name;
|
||||
#define EGLFUNC(__type, __name) __type __name;
|
||||
#include "Common/GLInclude/glFunctions.h"
|
||||
#undef GLFUNC
|
||||
#undef EGLFUNC
|
||||
}
|
||||
|
||||
#include "config/ActiveSettings.h"
|
||||
#include "config/LaunchSettings.h"
|
||||
@ -247,6 +250,17 @@ void LoadOpenGLImports()
|
||||
#undef GLFUNC
|
||||
#undef EGLFUNC
|
||||
}
|
||||
|
||||
#if BOOST_OS_LINUX
|
||||
// dummy function for all code that is statically linked with cemu and attempts to use eglSwapInterval
|
||||
// used to suppress wxWidgets calls to eglSwapInterval
|
||||
extern "C"
|
||||
EGLAPI EGLBoolean EGLAPIENTRY eglSwapInterval(EGLDisplay dpy, EGLint interval)
|
||||
{
|
||||
return EGL_TRUE;
|
||||
}
|
||||
#endif
|
||||
|
||||
#elif BOOST_OS_MACOS
|
||||
void LoadOpenGLImports()
|
||||
{
|
||||
@ -322,13 +336,14 @@ void OpenGLRenderer::Initialize()
|
||||
lock.unlock();
|
||||
|
||||
// create framebuffer for fast clearing (avoid glClearTexSubImage on Nvidia)
|
||||
if (this->m_vendor == GfxVendor::Nvidia || glClearTexSubImage == nullptr)
|
||||
if (glCreateFramebuffers)
|
||||
glCreateFramebuffers(1, &glRendererState.clearFBO);
|
||||
else
|
||||
{
|
||||
// generate framebuffer
|
||||
if (glCreateFramebuffers && false)
|
||||
glCreateFramebuffers(1, &glRendererState.clearFBO);
|
||||
else
|
||||
glGenFramebuffers(1, &glRendererState.clearFBO);
|
||||
glGenFramebuffers(1, &glRendererState.clearFBO);
|
||||
// bind to initialize
|
||||
glBindFramebuffer(GL_FRAMEBUFFER_EXT, glRendererState.clearFBO);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER_EXT, 0);
|
||||
}
|
||||
|
||||
draw_init();
|
||||
@ -386,7 +401,7 @@ void OpenGLRenderer::GetVendorInformation()
|
||||
cemuLog_log(LogType::Force, "GL_RENDERER: {}", glRendererString ? glRendererString : "unknown");
|
||||
cemuLog_log(LogType::Force, "GL_VERSION: {}", glVersionString ? glVersionString : "unknown");
|
||||
|
||||
if(boost::icontains(glVersionString, "Mesa"))
|
||||
if(glVersionString && boost::icontains(glVersionString, "Mesa"))
|
||||
{
|
||||
m_vendor = GfxVendor::Mesa;
|
||||
return;
|
||||
@ -407,10 +422,7 @@ void OpenGLRenderer::GetVendorInformation()
|
||||
}
|
||||
else if (memcmp(glVendorString, "Intel", 5) == 0)
|
||||
{
|
||||
if (LaunchSettings::ForceIntelLegacyEnabled())
|
||||
m_vendor = GfxVendor::IntelLegacy;
|
||||
else
|
||||
m_vendor = GfxVendor::IntelNoLegacy;
|
||||
m_vendor = GfxVendor::Intel;
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -426,9 +438,12 @@ void _glDebugCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GL
|
||||
return;
|
||||
if (LatteGPUState.glVendor == GLVENDOR_NVIDIA && strstr(message, "Dithering is enabled"))
|
||||
return;
|
||||
|
||||
if (LatteGPUState.glVendor == GLVENDOR_NVIDIA && strstr(message, "Blending is enabled, but is not supported for integer framebuffers"))
|
||||
return;
|
||||
if (LatteGPUState.glVendor == GLVENDOR_NVIDIA && strstr(message, "does not have a defined base level"))
|
||||
return;
|
||||
if(LatteGPUState.glVendor == GLVENDOR_NVIDIA && strstr(message, "has depth comparisons disabled, with a texture object"))
|
||||
return;
|
||||
|
||||
cemuLog_log(LogType::Force, "GLDEBUG: {}", message);
|
||||
|
||||
@ -570,10 +585,8 @@ void OpenGLRenderer::DrawBackbufferQuad(LatteTextureView* texView, RendererOutpu
|
||||
g_renderer->ClearColorbuffer(padView);
|
||||
}
|
||||
|
||||
// calculate effective size
|
||||
sint32 effectiveWidth;
|
||||
sint32 effectiveHeight;
|
||||
LatteTexture_getEffectiveSize(texView->baseTexture, &effectiveWidth, &effectiveHeight, nullptr, 0);
|
||||
sint32 effectiveWidth, effectiveHeight;
|
||||
texView->baseTexture->GetEffectiveSize(effectiveWidth, effectiveHeight, 0);
|
||||
|
||||
shader_unbind(RendererShader::ShaderType::kGeometry);
|
||||
shader_bind(shader->GetVertexShader());
|
||||
@ -673,7 +686,10 @@ void OpenGLRenderer::rendertarget_deleteCachedFBO(LatteCachedFBO* cfbo)
|
||||
{
|
||||
auto cfboGL = (CachedFBOGL*)cfbo;
|
||||
if (prevBoundFBO == cfboGL->glId_fbo)
|
||||
prevBoundFBO = -1;
|
||||
{
|
||||
glBindFramebuffer(GL_FRAMEBUFFER_EXT, 0);
|
||||
prevBoundFBO = 0;
|
||||
}
|
||||
glDeleteFramebuffers(1, &cfboGL->glId_fbo);
|
||||
}
|
||||
|
||||
@ -845,45 +861,6 @@ TextureDecoder* OpenGLRenderer::texture_chooseDecodedFormat(Latte::E_GX2SURFFMT
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (LatteGPUState.glVendor == GLVENDOR_INTEL_LEGACY)
|
||||
{
|
||||
if (format == Latte::E_GX2SURFFMT::BC1_UNORM)
|
||||
{
|
||||
texDecoder = TextureDecoder_BC1_UNORM_uncompress::getInstance();
|
||||
}
|
||||
else if (format == Latte::E_GX2SURFFMT::BC1_SRGB)
|
||||
{
|
||||
texDecoder = TextureDecoder_BC1_SRGB_uncompress::getInstance();
|
||||
}
|
||||
else if (format == Latte::E_GX2SURFFMT::BC3_UNORM)
|
||||
{
|
||||
texDecoder = TextureDecoder_BC3_UNORM_uncompress::getInstance();
|
||||
}
|
||||
else if (format == Latte::E_GX2SURFFMT::BC3_SRGB)
|
||||
{
|
||||
texDecoder = TextureDecoder_BC3_SRGB_uncompress::getInstance();
|
||||
}
|
||||
else if (format == Latte::E_GX2SURFFMT::BC4_UNORM)
|
||||
{
|
||||
texDecoder = TextureDecoder_BC4_UNORM_uncompress::getInstance();
|
||||
}
|
||||
else if (format == Latte::E_GX2SURFFMT::BC4_SNORM)
|
||||
{
|
||||
cemu_assert_debug(false); // todo
|
||||
}
|
||||
else if (format == Latte::E_GX2SURFFMT::BC5_UNORM)
|
||||
{
|
||||
texDecoder = TextureDecoder_BC5_UNORM_uncompress::getInstance();
|
||||
}
|
||||
else if (format == Latte::E_GX2SURFFMT::BC5_SNORM)
|
||||
{
|
||||
texDecoder = TextureDecoder_BC5_SNORM_uncompress::getInstance();
|
||||
}
|
||||
if (texDecoder)
|
||||
return texDecoder;
|
||||
}
|
||||
|
||||
if (format == Latte::E_GX2SURFFMT::R4_G4_UNORM)
|
||||
texDecoder = TextureDecoder_R4_G4_UNORM_To_RGBA4::getInstance();
|
||||
else if (format == Latte::E_GX2SURFFMT::R4_G4_B4_A4_UNORM)
|
||||
@ -998,60 +975,6 @@ TextureDecoder* OpenGLRenderer::texture_chooseDecodedFormat(Latte::E_GX2SURFFMT
|
||||
return texDecoder;
|
||||
}
|
||||
|
||||
void OpenGLRenderer::texture_destroy(LatteTexture* hostTexture)
|
||||
{
|
||||
delete hostTexture;
|
||||
}
|
||||
|
||||
void OpenGLRenderer::texture_reserveTextureOnGPU(LatteTexture* hostTextureGeneric)
|
||||
{
|
||||
auto hostTexture = (LatteTextureGL*)hostTextureGeneric;
|
||||
cemu_assert_debug(hostTexture->isDataDefined == false);
|
||||
sint32 effectiveBaseWidth = hostTexture->width;
|
||||
sint32 effectiveBaseHeight = hostTexture->height;
|
||||
sint32 effectiveBaseDepth = hostTexture->depth;
|
||||
if (hostTexture->overwriteInfo.hasResolutionOverwrite)
|
||||
{
|
||||
effectiveBaseWidth = hostTexture->overwriteInfo.width;
|
||||
effectiveBaseHeight = hostTexture->overwriteInfo.height;
|
||||
effectiveBaseDepth = hostTexture->overwriteInfo.depth;
|
||||
}
|
||||
// get format info
|
||||
LatteTextureGL::FormatInfoGL glFormatInfo;
|
||||
LatteTextureGL::GetOpenGLFormatInfo(hostTexture->isDepth, hostTexture->overwriteInfo.hasFormatOverwrite ? (Latte::E_GX2SURFFMT)hostTexture->overwriteInfo.format : hostTexture->format, hostTexture->dim, &glFormatInfo);
|
||||
// calculate mip count
|
||||
sint32 mipLevels = std::min(hostTexture->mipLevels, hostTexture->maxPossibleMipLevels);
|
||||
mipLevels = std::max(mipLevels, 1);
|
||||
// create immutable storage
|
||||
if (hostTexture->dim == Latte::E_DIM::DIM_2D || hostTexture->dim == Latte::E_DIM::DIM_2D_MSAA)
|
||||
{
|
||||
cemu_assert_debug(effectiveBaseDepth == 1);
|
||||
glTextureStorage2DWrapper(GL_TEXTURE_2D, hostTexture->glId_texture, mipLevels, glFormatInfo.glInternalFormat, effectiveBaseWidth, effectiveBaseHeight);
|
||||
}
|
||||
else if (hostTexture->dim == Latte::E_DIM::DIM_1D)
|
||||
{
|
||||
cemu_assert_debug(effectiveBaseHeight == 1);
|
||||
cemu_assert_debug(effectiveBaseDepth == 1);
|
||||
glTextureStorage1DWrapper(GL_TEXTURE_1D, hostTexture->glId_texture, mipLevels, glFormatInfo.glInternalFormat, effectiveBaseWidth);
|
||||
}
|
||||
else if (hostTexture->dim == Latte::E_DIM::DIM_2D_ARRAY || hostTexture->dim == Latte::E_DIM::DIM_2D_ARRAY_MSAA)
|
||||
{
|
||||
glTextureStorage3DWrapper(GL_TEXTURE_2D_ARRAY, hostTexture->glId_texture, mipLevels, glFormatInfo.glInternalFormat, effectiveBaseWidth, effectiveBaseHeight, std::max(1, effectiveBaseDepth));
|
||||
}
|
||||
else if (hostTexture->dim == Latte::E_DIM::DIM_3D)
|
||||
{
|
||||
glTextureStorage3DWrapper(GL_TEXTURE_3D, hostTexture->glId_texture, mipLevels, glFormatInfo.glInternalFormat, effectiveBaseWidth, effectiveBaseHeight, std::max(1, effectiveBaseDepth));
|
||||
}
|
||||
else if (hostTexture->dim == Latte::E_DIM::DIM_CUBEMAP)
|
||||
{
|
||||
glTextureStorage3DWrapper(GL_TEXTURE_CUBE_MAP_ARRAY, hostTexture->glId_texture, mipLevels, glFormatInfo.glInternalFormat, effectiveBaseWidth, effectiveBaseHeight, effectiveBaseDepth);
|
||||
}
|
||||
else
|
||||
{
|
||||
cemu_assert_unimplemented();
|
||||
}
|
||||
}
|
||||
|
||||
// use standard API to upload texture data
|
||||
void OpenGLRenderer_texture_loadSlice_normal(LatteTexture* hostTextureGeneric, sint32 width, sint32 height, sint32 depth, void* pixelData, sint32 sliceIndex, sint32 mipIndex, uint32 imageSize)
|
||||
{
|
||||
@ -1128,8 +1051,8 @@ void OpenGLRenderer::texture_clearColorSlice(LatteTexture* hostTexture, sint32 s
|
||||
LatteTextureGL* texGL = (LatteTextureGL*)hostTexture;
|
||||
cemu_assert_debug(!texGL->isDepth);
|
||||
|
||||
sint32 eWidth, eHeight, eDepth;
|
||||
LatteTexture_getEffectiveSize(hostTexture, &eWidth, &eHeight, &eDepth, mipIndex);
|
||||
sint32 eWidth, eHeight;
|
||||
hostTexture->GetEffectiveSize(eWidth, eHeight, mipIndex);
|
||||
renderstate_resetColorControl();
|
||||
renderTarget_setViewport(0, 0, eWidth, eHeight, 0.0f, 1.0f);
|
||||
LatteMRT::BindColorBufferOnly(hostTexture->GetOrCreateView(mipIndex, 1, sliceIndex, 1));
|
||||
@ -1142,8 +1065,8 @@ void OpenGLRenderer::texture_clearDepthSlice(LatteTexture* hostTexture, uint32 s
|
||||
LatteTextureGL* texGL = (LatteTextureGL*)hostTexture;
|
||||
cemu_assert_debug(texGL->isDepth);
|
||||
|
||||
sint32 eWidth, eHeight, eDepth;
|
||||
LatteTexture_getEffectiveSize(hostTexture, &eWidth, &eHeight, &eDepth, mipIndex);
|
||||
sint32 eWidth, eHeight;
|
||||
hostTexture->GetEffectiveSize(eWidth, eHeight, mipIndex);
|
||||
renderstate_resetColorControl();
|
||||
renderstate_resetDepthControl();
|
||||
renderTarget_setViewport(0, 0, eWidth, eHeight, 0.0f, 1.0f);
|
||||
@ -1171,13 +1094,12 @@ void OpenGLRenderer::texture_clearSlice(LatteTexture* hostTextureGeneric, sint32
|
||||
LatteTextureGL::FormatInfoGL formatInfoGL;
|
||||
LatteTextureGL::GetOpenGLFormatInfo(hostTexture->isDepth, hostTexture->format, hostTexture->dim, &formatInfoGL);
|
||||
// get effective size of mip
|
||||
sint32 effectiveWidth;
|
||||
sint32 effectiveHeight;
|
||||
LatteTexture_getEffectiveSize(hostTexture, &effectiveWidth, &effectiveHeight, nullptr, mipIndex);
|
||||
sint32 effectiveWidth, effectiveHeight;
|
||||
hostTexture->GetEffectiveSize(effectiveWidth, effectiveHeight, mipIndex);
|
||||
|
||||
// on Nvidia glClearTexImage and glClearTexSubImage has bad performance (clearing a 4K texture takes up to 50ms)
|
||||
// clearing with glTextureSubImage2D from a CPU RAM buffer is only slightly slower
|
||||
// clearing with glTextureSubImage2D from a OpenGL buffer is 10-20% faster than glClearTexImage)
|
||||
// clearing with glTextureSubImage2D from a OpenGL buffer is 10-20% faster than glClearTexImage
|
||||
// clearing with FBO and glClear is orders of magnitude faster than the other methods
|
||||
// (these are results from 2018, may be different now)
|
||||
|
||||
@ -1208,7 +1130,6 @@ void OpenGLRenderer::texture_clearSlice(LatteTexture* hostTextureGeneric, sint32
|
||||
}
|
||||
if (glClearTexSubImage == nullptr)
|
||||
return;
|
||||
// clear
|
||||
glClearTexSubImage(hostTexture->glId_texture, mipIndex, 0, 0, sliceIndex, effectiveWidth, effectiveHeight, 1, formatInfoGL.glSuppliedFormat, formatInfoGL.glSuppliedFormatType, NULL);
|
||||
}
|
||||
|
||||
@ -1216,7 +1137,6 @@ LatteTexture* OpenGLRenderer::texture_createTextureEx(Latte::E_DIM dim, MPTR phy
|
||||
uint32 swizzle, Latte::E_HWTILEMODE tileMode, bool isDepth)
|
||||
{
|
||||
return new LatteTextureGL(dim, physAddress, physMipAddress, format, width, height, depth, pitch, mipLevels, swizzle, tileMode, isDepth);
|
||||
|
||||
}
|
||||
|
||||
void OpenGLRenderer::texture_setActiveTextureUnit(sint32 index)
|
||||
@ -1285,7 +1205,6 @@ void OpenGLRenderer::texture_copyImageSubData(LatteTexture* src, sint32 srcMip,
|
||||
{
|
||||
auto srcGL = (LatteTextureGL*)src;
|
||||
auto dstGL = (LatteTextureGL*)dst;
|
||||
|
||||
if ((srcGL->isAlternativeFormat || dstGL->isAlternativeFormat) && (srcGL->glInternalFormat != dstGL->glInternalFormat))
|
||||
{
|
||||
if (srcGL->format == Latte::E_GX2SURFFMT::R16_G16_B16_A16_UINT && dstGL->format == Latte::E_GX2SURFFMT::BC4_UNORM)
|
||||
|
@ -30,8 +30,6 @@ public:
|
||||
void Flush(bool waitIdle = false) override;
|
||||
void NotifyLatteCommandProcessorIdle() override;
|
||||
|
||||
void UpdateVSyncState();
|
||||
|
||||
void EnableDebugMode() override;
|
||||
void SwapBuffers(bool swapTV = true, bool swapDRC = true) override;
|
||||
|
||||
@ -66,14 +64,11 @@ public:
|
||||
void renderstate_updateTextureSettingsGL(LatteDecompilerShader* shaderContext, LatteTextureView* _hostTextureView, uint32 hostTextureUnit, const Latte::LATTE_SQ_TEX_RESOURCE_WORD4_N texUnitWord4, uint32 texUnitIndex, bool isDepthSampler);
|
||||
|
||||
// texture functions
|
||||
void texture_destroy(LatteTexture* hostTexture) override;
|
||||
|
||||
void* texture_acquireTextureUploadBuffer(uint32 size) override;
|
||||
void texture_releaseTextureUploadBuffer(uint8* mem) override;
|
||||
|
||||
TextureDecoder* texture_chooseDecodedFormat(Latte::E_GX2SURFFMT format, bool isDepth, Latte::E_DIM dim, uint32 width, uint32 height) override;
|
||||
|
||||
void texture_reserveTextureOnGPU(LatteTexture* hostTexture) override;
|
||||
void texture_clearSlice(LatteTexture* hostTexture, sint32 sliceIndex, sint32 mipIndex) override;
|
||||
void texture_loadSlice(LatteTexture* hostTexture, sint32 width, sint32 height, sint32 depth, void* pixelData, sint32 sliceIndex, sint32 mipIndex, uint32 compressedImageSize) override;
|
||||
void texture_clearColorSlice(LatteTexture* hostTexture, sint32 sliceIndex, sint32 mipIndex, float r, float g, float b, float a) override;
|
||||
@ -209,7 +204,7 @@ private:
|
||||
GLuint glStreamoutCacheRingBuffer;
|
||||
|
||||
// cfbo
|
||||
GLuint prevBoundFBO = -1;
|
||||
GLuint prevBoundFBO = 0;
|
||||
GLuint glId_fbo = 0;
|
||||
|
||||
// renderstate
|
||||
|
@ -950,7 +950,7 @@ void OpenGLRenderer::draw_genericDrawHandler(uint32 baseVertex, uint32 baseInsta
|
||||
bool streamoutEnable = LatteGPUState.contextRegister[mmVGT_STRMOUT_EN] != 0;
|
||||
if (streamoutEnable)
|
||||
{
|
||||
if (glBeginTransformFeedback == nullptr || LatteGPUState.glVendor == GLVENDOR_INTEL_NOLEGACY)
|
||||
if (glBeginTransformFeedback == nullptr)
|
||||
{
|
||||
cemu_assert_debug(false);
|
||||
return; // transform feedback not supported
|
||||
|
@ -30,9 +30,8 @@ void OpenGLRenderer::surfaceCopy_copySurfaceWithFormatConversion(LatteTexture* s
|
||||
sint32 effectiveCopyWidth = width;
|
||||
sint32 effectiveCopyHeight = height;
|
||||
LatteTexture_scaleToEffectiveSize(sourceTexture, &effectiveCopyWidth, &effectiveCopyHeight, 0);
|
||||
sint32 sourceEffectiveWidth;
|
||||
sint32 sourceEffectiveHeight;
|
||||
LatteTexture_getEffectiveSize(sourceTexture, &sourceEffectiveWidth, &sourceEffectiveHeight, nullptr, srcMip);
|
||||
sint32 sourceEffectiveWidth, sourceEffectiveHeight;
|
||||
sourceTexture->GetEffectiveSize(sourceEffectiveWidth, sourceEffectiveHeight, srcMip);
|
||||
// reset everything
|
||||
renderstate_resetColorControl();
|
||||
renderstate_resetDepthControl();
|
||||
|
@ -23,16 +23,13 @@ bool RendererShaderGL::loadBinary()
|
||||
cemu_assert_debug(m_baseHash != 0);
|
||||
uint64 h1, h2;
|
||||
GenerateShaderPrecompiledCacheFilename(m_type, m_baseHash, m_auxHash, h1, h2);
|
||||
sint32 fileSize = 0;
|
||||
std::vector<uint8> cacheFileData;
|
||||
if (!s_programBinaryCache->GetFile({h1, h2 }, cacheFileData))
|
||||
return false;
|
||||
if (fileSize < sizeof(uint32))
|
||||
{
|
||||
if (cacheFileData.size() <= sizeof(uint32))
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32 shaderBinFormat = *(uint32*)(cacheFileData.data() + 0);
|
||||
uint32 shaderBinFormat = *(uint32*)(cacheFileData.data());
|
||||
|
||||
m_program = glCreateProgram();
|
||||
glProgramBinary(m_program, shaderBinFormat, cacheFileData.data()+4, cacheFileData.size()-4);
|
||||
|
@ -21,8 +21,6 @@ enum class GfxVendor
|
||||
Generic,
|
||||
|
||||
AMD,
|
||||
IntelLegacy,
|
||||
IntelNoLegacy,
|
||||
Intel,
|
||||
Nvidia,
|
||||
Apple,
|
||||
@ -100,14 +98,11 @@ public:
|
||||
virtual void rendertarget_bindFramebufferObject(LatteCachedFBO* cfbo) = 0;
|
||||
|
||||
// texture functions
|
||||
virtual void texture_destroy(LatteTexture* hostTexture) = 0;
|
||||
|
||||
virtual void* texture_acquireTextureUploadBuffer(uint32 size) = 0;
|
||||
virtual void texture_releaseTextureUploadBuffer(uint8* mem) = 0;
|
||||
|
||||
virtual TextureDecoder* texture_chooseDecodedFormat(Latte::E_GX2SURFFMT format, bool isDepth, Latte::E_DIM dim, uint32 width, uint32 height) = 0;
|
||||
|
||||
virtual void texture_reserveTextureOnGPU(LatteTexture* hostTexture) = 0;
|
||||
virtual void texture_clearSlice(LatteTexture* hostTexture, sint32 sliceIndex, sint32 mipIndex) = 0;
|
||||
virtual void texture_loadSlice(LatteTexture* hostTexture, sint32 width, sint32 height, sint32 depth, void* pixelData, sint32 sliceIndex, sint32 mipIndex, uint32 compressedImageSize) = 0;
|
||||
virtual void texture_clearColorSlice(LatteTexture* hostTexture, sint32 sliceIndex, sint32 mipIndex, float r, float g, float b, float a) = 0;
|
||||
|
@ -44,9 +44,9 @@ CachedFBOVk::~CachedFBOVk()
|
||||
while (!m_usedByPipelines.empty())
|
||||
delete m_usedByPipelines[0];
|
||||
auto vkr = VulkanRenderer::GetInstance();
|
||||
vkr->releaseDestructibleObject(m_vkrObjFramebuffer);
|
||||
vkr->ReleaseDestructibleObject(m_vkrObjFramebuffer);
|
||||
m_vkrObjFramebuffer = nullptr;
|
||||
vkr->releaseDestructibleObject(m_vkrObjRenderPass);
|
||||
vkr->ReleaseDestructibleObject(m_vkrObjRenderPass);
|
||||
m_vkrObjRenderPass = nullptr;
|
||||
}
|
||||
|
||||
|
@ -57,7 +57,12 @@ uint32 LatteTextureVk_AdjustTextureCompSel(Latte::E_GX2SURFFMT format, uint32 co
|
||||
LatteTextureViewVk::LatteTextureViewVk(VkDevice device, LatteTextureVk* texture, Latte::E_DIM dim, Latte::E_GX2SURFFMT format, sint32 firstMip, sint32 mipCount, sint32 firstSlice, sint32 sliceCount)
|
||||
: LatteTextureView(texture, firstMip, mipCount, firstSlice, sliceCount, dim, format), m_device(device)
|
||||
{
|
||||
if (dim != texture->dim || format != texture->format)
|
||||
if(texture->overwriteInfo.hasFormatOverwrite)
|
||||
{
|
||||
cemu_assert_debug(format == texture->format); // if format overwrite is used, the texture is no longer taking part in aliasing and the format of any view has to match
|
||||
m_format = texture->GetFormat();
|
||||
}
|
||||
else if (dim != texture->dim || format != texture->format)
|
||||
{
|
||||
VulkanRenderer::FormatInfoVK texFormatInfo;
|
||||
VulkanRenderer::GetInstance()->GetTextureFormatInfoVK(format, texture->isDepth, dim, 0, 0, &texFormatInfo);
|
||||
@ -74,14 +79,14 @@ LatteTextureViewVk::~LatteTextureViewVk()
|
||||
delete list_descriptorSets[0];
|
||||
|
||||
if (m_smallCacheView0)
|
||||
VulkanRenderer::GetInstance()->releaseDestructibleObject(m_smallCacheView0);
|
||||
VulkanRenderer::GetInstance()->ReleaseDestructibleObject(m_smallCacheView0);
|
||||
if (m_smallCacheView1)
|
||||
VulkanRenderer::GetInstance()->releaseDestructibleObject(m_smallCacheView1);
|
||||
VulkanRenderer::GetInstance()->ReleaseDestructibleObject(m_smallCacheView1);
|
||||
|
||||
if (m_fallbackCache)
|
||||
{
|
||||
for (auto& itr : *m_fallbackCache)
|
||||
VulkanRenderer::GetInstance()->releaseDestructibleObject(itr.second);
|
||||
VulkanRenderer::GetInstance()->ReleaseDestructibleObject(itr.second);
|
||||
delete m_fallbackCache;
|
||||
m_fallbackCache = nullptr;
|
||||
}
|
||||
|
@ -46,7 +46,7 @@ LatteTextureVk::LatteTextureVk(class VulkanRenderer* vkRenderer, Latte::E_DIM di
|
||||
|
||||
VulkanRenderer::FormatInfoVK texFormatInfo;
|
||||
vkRenderer->GetTextureFormatInfoVK(format, isDepth, dim, effectiveBaseWidth, effectiveBaseHeight, &texFormatInfo);
|
||||
hasStencil = (texFormatInfo.vkImageAspect & VK_IMAGE_ASPECT_STENCIL_BIT) != 0;
|
||||
cemu_assert_debug(hasStencil == ((texFormatInfo.vkImageAspect & VK_IMAGE_ASPECT_STENCIL_BIT) != 0));
|
||||
imageInfo.format = texFormatInfo.vkImageFormat;
|
||||
vkObjTex->m_imageAspect = texFormatInfo.vkImageAspect;
|
||||
|
||||
@ -117,7 +117,7 @@ LatteTextureVk::~LatteTextureVk()
|
||||
|
||||
m_vkr->surfaceCopy_notifyTextureRelease(this);
|
||||
|
||||
VulkanRenderer::GetInstance()->releaseDestructibleObject(vkObjTex);
|
||||
VulkanRenderer::GetInstance()->ReleaseDestructibleObject(vkObjTex);
|
||||
vkObjTex = nullptr;
|
||||
}
|
||||
|
||||
@ -130,12 +130,8 @@ LatteTextureView* LatteTextureVk::CreateView(Latte::E_DIM dim, Latte::E_GX2SURFF
|
||||
return new LatteTextureViewVk(m_vkr->GetLogicalDevice(), this, dim, format, firstMip, mipCount, firstSlice, sliceCount);
|
||||
}
|
||||
|
||||
void LatteTextureVk::setAllocation(struct VkImageMemAllocation* memAllocation)
|
||||
void LatteTextureVk::AllocateOnHost()
|
||||
{
|
||||
vkObjTex->m_allocation = memAllocation;
|
||||
}
|
||||
|
||||
struct VkImageMemAllocation* LatteTextureVk::getAllocation() const
|
||||
{
|
||||
return vkObjTex->m_allocation;
|
||||
auto allocationInfo = VulkanRenderer::GetInstance()->GetMemoryManager()->imageMemoryAllocate(GetImageObj()->m_image);
|
||||
vkObjTex->m_allocation = allocationInfo;
|
||||
}
|
||||
|
@ -14,14 +14,13 @@ public:
|
||||
|
||||
~LatteTextureVk();
|
||||
|
||||
void AllocateOnHost() override;
|
||||
|
||||
VKRObjectTexture* GetImageObj() const { return vkObjTex; };
|
||||
|
||||
VkFormat GetFormat() const { return vkObjTex->m_format; }
|
||||
VkImageAspectFlags GetImageAspect() const { return vkObjTex->m_imageAspect; }
|
||||
|
||||
void setAllocation(struct VkImageMemAllocation* memAllocation);
|
||||
struct VkImageMemAllocation* getAllocation() const;
|
||||
|
||||
VkImageLayout GetImageLayout(VkImageSubresource& subresource)
|
||||
{
|
||||
cemu_assert_debug(subresource.mipLevel < m_layoutsMips);
|
||||
|
@ -8,6 +8,7 @@
|
||||
|
||||
#include <glslang/Public/ShaderLang.h>
|
||||
#include <glslang/SPIRV/GlslangToSpv.h>
|
||||
#include <util/helpers/helpers.h>
|
||||
|
||||
bool s_isLoadingShadersVk{ false };
|
||||
class FileCache* s_spirvCache{nullptr};
|
||||
@ -129,19 +130,18 @@ class _ShaderVkThreadPool
|
||||
public:
|
||||
void StartThreads()
|
||||
{
|
||||
if (s_threads.empty())
|
||||
{
|
||||
// create thread pool
|
||||
m_shutdownThread.store(false);
|
||||
const uint32 threadCount = 2;
|
||||
for (uint32 i = 0; i < threadCount; ++i)
|
||||
s_threads.emplace_back(&_ShaderVkThreadPool::CompilerThreadFunc, this);
|
||||
}
|
||||
if (m_threadsActive.exchange(true))
|
||||
return;
|
||||
// create thread pool
|
||||
const uint32 threadCount = 2;
|
||||
for (uint32 i = 0; i < threadCount; ++i)
|
||||
s_threads.emplace_back(&_ShaderVkThreadPool::CompilerThreadFunc, this);
|
||||
}
|
||||
|
||||
void StopThreads()
|
||||
{
|
||||
m_shutdownThread.store(true);
|
||||
if (!m_threadsActive.exchange(false))
|
||||
return;
|
||||
for (uint32 i = 0; i < s_threads.size(); ++i)
|
||||
s_compilationQueueCount.increment();
|
||||
for (auto& it : s_threads)
|
||||
@ -156,7 +156,8 @@ public:
|
||||
|
||||
void CompilerThreadFunc()
|
||||
{
|
||||
while (!m_shutdownThread.load(std::memory_order::relaxed))
|
||||
SetThreadName("vkShaderComp");
|
||||
while (m_threadsActive.load(std::memory_order::relaxed))
|
||||
{
|
||||
s_compilationQueueCount.decrementWithWait();
|
||||
s_compilationQueueMutex.lock();
|
||||
@ -181,7 +182,7 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
bool HasThreadsRunning() const { return !m_shutdownThread; }
|
||||
bool HasThreadsRunning() const { return m_threadsActive; }
|
||||
|
||||
public:
|
||||
std::vector<std::thread> s_threads;
|
||||
@ -191,7 +192,7 @@ public:
|
||||
std::mutex s_compilationQueueMutex;
|
||||
|
||||
private:
|
||||
std::atomic<bool> m_shutdownThread;
|
||||
std::atomic<bool> m_threadsActive;
|
||||
}ShaderVkThreadPool;
|
||||
|
||||
RendererShaderVk::RendererShaderVk(ShaderType type, uint64 baseHash, uint64 auxHash, bool isGameShader, bool isGfxPackShader, const std::string& glslCode)
|
||||
@ -208,7 +209,8 @@ RendererShaderVk::RendererShaderVk(ShaderType type, uint64 baseHash, uint64 auxH
|
||||
|
||||
RendererShaderVk::~RendererShaderVk()
|
||||
{
|
||||
VulkanRenderer::GetInstance()->destroyShader(this);
|
||||
while (!list_pipelineInfo.empty())
|
||||
delete list_pipelineInfo[0];
|
||||
}
|
||||
|
||||
void RendererShaderVk::Init()
|
||||
|
@ -1,15 +1,34 @@
|
||||
#include "SwapchainInfoVk.h"
|
||||
|
||||
#include "Cemu/GuiSystem/GuiSystem.h"
|
||||
#include "config/CemuConfig.h"
|
||||
#include "Cafe/HW/Latte/Core/Latte.h"
|
||||
#include "Cafe/HW/Latte/Core/LatteTiming.h"
|
||||
#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h"
|
||||
#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h"
|
||||
|
||||
void SwapchainInfoVk::Create(VkPhysicalDevice physicalDevice, VkDevice logicalDevice)
|
||||
SwapchainInfoVk::SwapchainInfoVk(bool mainWindow, Vector2i size) : mainWindow(mainWindow), m_desiredExtent(size)
|
||||
{
|
||||
m_physicalDevice = physicalDevice;
|
||||
m_logicalDevice = logicalDevice;
|
||||
const auto details = QuerySwapchainSupport(surface, physicalDevice);
|
||||
auto& windowHandleInfo = mainWindow ? GuiSystem::getWindowInfo().canvas_main : GuiSystem::getWindowInfo().canvas_pad;
|
||||
auto renderer = VulkanRenderer::GetInstance();
|
||||
m_instance = renderer->GetVkInstance();
|
||||
m_logicalDevice = renderer->GetLogicalDevice();
|
||||
m_physicalDevice = renderer->GetPhysicalDevice();
|
||||
|
||||
m_surface = renderer->CreateFramebufferSurface(m_instance, windowHandleInfo);
|
||||
}
|
||||
|
||||
|
||||
SwapchainInfoVk::~SwapchainInfoVk()
|
||||
{
|
||||
Cleanup();
|
||||
if(m_surface != VK_NULL_HANDLE)
|
||||
vkDestroySurfaceKHR(m_instance, m_surface, nullptr);
|
||||
}
|
||||
|
||||
void SwapchainInfoVk::Create()
|
||||
{
|
||||
const auto details = QuerySwapchainSupport(m_surface, m_physicalDevice);
|
||||
m_surfaceFormat = ChooseSurfaceFormat(details.formats);
|
||||
m_actualExtent = ChooseSwapExtent(details.capabilities);
|
||||
|
||||
@ -20,28 +39,28 @@ void SwapchainInfoVk::Create(VkPhysicalDevice physicalDevice, VkDevice logicalDe
|
||||
if(image_count < 2)
|
||||
cemuLog_log(LogType::Force, "Vulkan: Swapchain image count less than 2 may cause problems");
|
||||
|
||||
VkSwapchainCreateInfoKHR create_info = CreateSwapchainCreateInfo(surface, details, m_surfaceFormat, image_count, m_actualExtent);
|
||||
VkSwapchainCreateInfoKHR create_info = CreateSwapchainCreateInfo(m_surface, details, m_surfaceFormat, image_count, m_actualExtent);
|
||||
create_info.oldSwapchain = nullptr;
|
||||
create_info.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
|
||||
|
||||
VkResult result = vkCreateSwapchainKHR(logicalDevice, &create_info, nullptr, &swapchain);
|
||||
VkResult result = vkCreateSwapchainKHR(m_logicalDevice, &create_info, nullptr, &m_swapchain);
|
||||
if (result != VK_SUCCESS)
|
||||
UnrecoverableError("Error attempting to create a swapchain");
|
||||
|
||||
result = vkGetSwapchainImagesKHR(logicalDevice, swapchain, &image_count, nullptr);
|
||||
result = vkGetSwapchainImagesKHR(m_logicalDevice, m_swapchain, &image_count, nullptr);
|
||||
if (result != VK_SUCCESS)
|
||||
UnrecoverableError("Error attempting to retrieve the count of swapchain images");
|
||||
|
||||
|
||||
m_swapchainImages.resize(image_count);
|
||||
result = vkGetSwapchainImagesKHR(logicalDevice, swapchain, &image_count, m_swapchainImages.data());
|
||||
result = vkGetSwapchainImagesKHR(m_logicalDevice, m_swapchain, &image_count, m_swapchainImages.data());
|
||||
if (result != VK_SUCCESS)
|
||||
UnrecoverableError("Error attempting to retrieve swapchain images");
|
||||
// create default renderpass
|
||||
VkAttachmentDescription colorAttachment = {};
|
||||
colorAttachment.format = m_surfaceFormat.format;
|
||||
colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
||||
colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
|
||||
colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
|
||||
colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
||||
colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
||||
@ -62,7 +81,7 @@ void SwapchainInfoVk::Create(VkPhysicalDevice physicalDevice, VkDevice logicalDe
|
||||
renderPassInfo.pAttachments = &colorAttachment;
|
||||
renderPassInfo.subpassCount = 1;
|
||||
renderPassInfo.pSubpasses = &subpass;
|
||||
result = vkCreateRenderPass(logicalDevice, &renderPassInfo, nullptr, &m_swapchainRenderPass);
|
||||
result = vkCreateRenderPass(m_logicalDevice, &renderPassInfo, nullptr, &m_swapchainRenderPass);
|
||||
if (result != VK_SUCCESS)
|
||||
UnrecoverableError("Failed to create renderpass for swapchain");
|
||||
|
||||
@ -84,7 +103,7 @@ void SwapchainInfoVk::Create(VkPhysicalDevice physicalDevice, VkDevice logicalDe
|
||||
createInfo.subresourceRange.levelCount = 1;
|
||||
createInfo.subresourceRange.baseArrayLayer = 0;
|
||||
createInfo.subresourceRange.layerCount = 1;
|
||||
result = vkCreateImageView(logicalDevice, &createInfo, nullptr, &m_swapchainImageViews[i]);
|
||||
result = vkCreateImageView(m_logicalDevice, &createInfo, nullptr, &m_swapchainImageViews[i]);
|
||||
if (result != VK_SUCCESS)
|
||||
UnrecoverableError("Failed to create imageviews for swapchain");
|
||||
}
|
||||
@ -104,7 +123,7 @@ void SwapchainInfoVk::Create(VkPhysicalDevice physicalDevice, VkDevice logicalDe
|
||||
framebufferInfo.width = m_actualExtent.width;
|
||||
framebufferInfo.height = m_actualExtent.height;
|
||||
framebufferInfo.layers = 1;
|
||||
result = vkCreateFramebuffer(logicalDevice, &framebufferInfo, nullptr, &m_swapchainFramebuffers[i]);
|
||||
result = vkCreateFramebuffer(m_logicalDevice, &framebufferInfo, nullptr, &m_swapchainFramebuffers[i]);
|
||||
if (result != VK_SUCCESS)
|
||||
UnrecoverableError("Failed to create framebuffer for swapchain");
|
||||
}
|
||||
@ -114,7 +133,7 @@ void SwapchainInfoVk::Create(VkPhysicalDevice physicalDevice, VkDevice logicalDe
|
||||
VkSemaphoreCreateInfo info = {};
|
||||
info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
|
||||
for (auto& semaphore : m_presentSemaphores){
|
||||
if (vkCreateSemaphore(logicalDevice, &info, nullptr, &semaphore) != VK_SUCCESS)
|
||||
if (vkCreateSemaphore(m_logicalDevice, &info, nullptr, &semaphore) != VK_SUCCESS)
|
||||
UnrecoverableError("Failed to create semaphore for swapchain present");
|
||||
}
|
||||
|
||||
@ -123,17 +142,10 @@ void SwapchainInfoVk::Create(VkPhysicalDevice physicalDevice, VkDevice logicalDe
|
||||
info = {};
|
||||
info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
|
||||
for (auto& semaphore : m_acquireSemaphores){
|
||||
if (vkCreateSemaphore(logicalDevice, &info, nullptr, &semaphore) != VK_SUCCESS)
|
||||
if (vkCreateSemaphore(m_logicalDevice, &info, nullptr, &semaphore) != VK_SUCCESS)
|
||||
UnrecoverableError("Failed to create semaphore for swapchain acquire");
|
||||
}
|
||||
|
||||
VkFenceCreateInfo fenceInfo = {};
|
||||
fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
|
||||
fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;
|
||||
result = vkCreateFence(logicalDevice, &fenceInfo, nullptr, &m_imageAvailableFence);
|
||||
if (result != VK_SUCCESS)
|
||||
UnrecoverableError("Failed to create fence for swapchain");
|
||||
|
||||
m_acquireIndex = 0;
|
||||
hasDefinedSwapchainImage = false;
|
||||
}
|
||||
@ -165,33 +177,16 @@ void SwapchainInfoVk::Cleanup()
|
||||
m_swapchainFramebuffers.clear();
|
||||
|
||||
|
||||
if (m_imageAvailableFence)
|
||||
if (m_swapchain)
|
||||
{
|
||||
vkDestroyFence(m_logicalDevice, m_imageAvailableFence, nullptr);
|
||||
m_imageAvailableFence = nullptr;
|
||||
}
|
||||
if (swapchain)
|
||||
{
|
||||
vkDestroySwapchainKHR(m_logicalDevice, swapchain, nullptr);
|
||||
swapchain = VK_NULL_HANDLE;
|
||||
vkDestroySwapchainKHR(m_logicalDevice, m_swapchain, nullptr);
|
||||
m_swapchain = VK_NULL_HANDLE;
|
||||
}
|
||||
}
|
||||
|
||||
bool SwapchainInfoVk::IsValid() const
|
||||
{
|
||||
return swapchain && !m_acquireSemaphores.empty();
|
||||
}
|
||||
|
||||
void SwapchainInfoVk::WaitAvailableFence()
|
||||
{
|
||||
if(m_awaitableFence != VK_NULL_HANDLE)
|
||||
vkWaitForFences(m_logicalDevice, 1, &m_awaitableFence, VK_TRUE, UINT64_MAX);
|
||||
m_awaitableFence = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
void SwapchainInfoVk::ResetAvailableFence() const
|
||||
{
|
||||
vkResetFences(m_logicalDevice, 1, &m_imageAvailableFence);
|
||||
return m_swapchain && !m_acquireSemaphores.empty();
|
||||
}
|
||||
|
||||
VkSemaphore SwapchainInfoVk::ConsumeAcquireSemaphore()
|
||||
@ -201,15 +196,18 @@ VkSemaphore SwapchainInfoVk::ConsumeAcquireSemaphore()
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool SwapchainInfoVk::AcquireImage(uint64 timeout)
|
||||
bool SwapchainInfoVk::AcquireImage()
|
||||
{
|
||||
WaitAvailableFence();
|
||||
ResetAvailableFence();
|
||||
|
||||
VkSemaphore acquireSemaphore = m_acquireSemaphores[m_acquireIndex];
|
||||
VkResult result = vkAcquireNextImageKHR(m_logicalDevice, swapchain, timeout, acquireSemaphore, m_imageAvailableFence, &swapchainImageIndex);
|
||||
VkResult result = vkAcquireNextImageKHR(m_logicalDevice, m_swapchain, 1'000'000'000, acquireSemaphore, nullptr, &swapchainImageIndex);
|
||||
if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR)
|
||||
m_shouldRecreate = true;
|
||||
if (result == VK_TIMEOUT)
|
||||
{
|
||||
swapchainImageIndex = -1;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (result < 0)
|
||||
{
|
||||
swapchainImageIndex = -1;
|
||||
@ -218,7 +216,6 @@ bool SwapchainInfoVk::AcquireImage(uint64 timeout)
|
||||
return false;
|
||||
}
|
||||
m_currentSemaphore = acquireSemaphore;
|
||||
m_awaitableFence = m_imageAvailableFence;
|
||||
m_acquireIndex = (m_acquireIndex + 1) % m_swapchainImages.size();
|
||||
|
||||
return true;
|
||||
@ -231,35 +228,6 @@ void SwapchainInfoVk::UnrecoverableError(const char* errMsg)
|
||||
throw std::runtime_error(errMsg);
|
||||
}
|
||||
|
||||
SwapchainInfoVk::QueueFamilyIndices SwapchainInfoVk::FindQueueFamilies(VkSurfaceKHR surface, VkPhysicalDevice device)
|
||||
{
|
||||
uint32_t queueFamilyCount = 0;
|
||||
vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr);
|
||||
|
||||
std::vector<VkQueueFamilyProperties> queueFamilies(queueFamilyCount);
|
||||
vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data());
|
||||
|
||||
QueueFamilyIndices indices;
|
||||
for (int i = 0; i < (int)queueFamilies.size(); ++i)
|
||||
{
|
||||
const auto& queueFamily = queueFamilies[i];
|
||||
if (queueFamily.queueCount > 0 && queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT)
|
||||
indices.graphicsFamily = i;
|
||||
|
||||
VkBool32 presentSupport = false;
|
||||
const VkResult result = vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport);
|
||||
if (result != VK_SUCCESS)
|
||||
throw std::runtime_error(fmt::format("Error while attempting to check if a surface supports presentation: {}", result));
|
||||
|
||||
if (queueFamily.queueCount > 0 && presentSupport)
|
||||
indices.presentFamily = i;
|
||||
|
||||
if (indices.IsComplete())
|
||||
break;
|
||||
}
|
||||
|
||||
return indices;
|
||||
}
|
||||
|
||||
SwapchainInfoVk::SwapchainSupportDetails SwapchainInfoVk::QuerySwapchainSupport(VkSurfaceKHR surface, const VkPhysicalDevice& device)
|
||||
{
|
||||
@ -391,7 +359,7 @@ VkSwapchainCreateInfoKHR SwapchainInfoVk::CreateSwapchainCreateInfo(VkSurfaceKHR
|
||||
createInfo.imageArrayLayers = 1;
|
||||
createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
|
||||
|
||||
const QueueFamilyIndices indices = FindQueueFamilies(surface, m_physicalDevice);
|
||||
const VulkanRenderer::QueueFamilyIndices indices = VulkanRenderer::GetInstance()->FindQueueFamilies(surface, m_physicalDevice);
|
||||
m_swapchainQueueFamilyIndices = { (uint32)indices.graphicsFamily, (uint32)indices.presentFamily };
|
||||
if (indices.graphicsFamily != indices.presentFamily)
|
||||
{
|
||||
|
@ -14,14 +14,6 @@ struct SwapchainInfoVk
|
||||
SYNC_AND_LIMIT = 3, // synchronize emulated vsync events to monitor vsync. But skip events if rate higher than virtual vsync period
|
||||
};
|
||||
|
||||
struct QueueFamilyIndices
|
||||
{
|
||||
int32_t graphicsFamily = -1;
|
||||
int32_t presentFamily = -1;
|
||||
|
||||
bool IsComplete() const { return graphicsFamily >= 0 && presentFamily >= 0; }
|
||||
};
|
||||
|
||||
struct SwapchainSupportDetails
|
||||
{
|
||||
VkSurfaceCapabilitiesKHR capabilities;
|
||||
@ -30,14 +22,11 @@ struct SwapchainInfoVk
|
||||
};
|
||||
|
||||
void Cleanup();
|
||||
void Create(VkPhysicalDevice physicalDevice, VkDevice logicalDevice);
|
||||
void Create();
|
||||
|
||||
bool IsValid() const;
|
||||
|
||||
void WaitAvailableFence();
|
||||
void ResetAvailableFence() const;
|
||||
|
||||
bool AcquireImage(uint64 timeout);
|
||||
bool AcquireImage();
|
||||
// retrieve semaphore of last acquire for submitting a wait operation
|
||||
// only one wait operation must be submitted per acquire (which submits a single signal operation)
|
||||
// therefore subsequent calls will return a NULL handle
|
||||
@ -45,8 +34,6 @@ struct SwapchainInfoVk
|
||||
|
||||
static void UnrecoverableError(const char* errMsg);
|
||||
|
||||
// todo: move this function somewhere more sensible. Not directly swapchain related
|
||||
static QueueFamilyIndices FindQueueFamilies(VkSurfaceKHR surface, VkPhysicalDevice device);
|
||||
static SwapchainSupportDetails QuerySwapchainSupport(VkSurfaceKHR surface, const VkPhysicalDevice& device);
|
||||
|
||||
VkPresentModeKHR ChoosePresentMode(const std::vector<VkPresentModeKHR>& modes);
|
||||
@ -61,14 +48,10 @@ struct SwapchainInfoVk
|
||||
return m_actualExtent;
|
||||
}
|
||||
|
||||
SwapchainInfoVk(VkSurfaceKHR surface, bool mainWindow)
|
||||
: surface(surface), mainWindow(mainWindow) {}
|
||||
SwapchainInfoVk(bool mainWindow, Vector2i size);
|
||||
SwapchainInfoVk(const SwapchainInfoVk&) = delete;
|
||||
SwapchainInfoVk(SwapchainInfoVk&&) noexcept = default;
|
||||
~SwapchainInfoVk()
|
||||
{
|
||||
Cleanup();
|
||||
}
|
||||
~SwapchainInfoVk();
|
||||
|
||||
bool mainWindow{};
|
||||
|
||||
@ -76,11 +59,12 @@ struct SwapchainInfoVk
|
||||
bool m_usesSRGB = false;
|
||||
VSync m_vsyncState = VSync::Immediate;
|
||||
bool hasDefinedSwapchainImage{}; // indicates if the swapchain image is in a defined state
|
||||
VkInstance m_instance{};
|
||||
VkPhysicalDevice m_physicalDevice{};
|
||||
VkDevice m_logicalDevice{};
|
||||
VkSurfaceKHR surface{};
|
||||
VkSurfaceKHR m_surface{};
|
||||
VkSurfaceFormatKHR m_surfaceFormat{};
|
||||
VkSwapchainKHR swapchain{};
|
||||
VkSwapchainKHR m_swapchain{};
|
||||
Vector2i m_desiredExtent{};
|
||||
uint32 swapchainImageIndex = (uint32)-1;
|
||||
|
||||
@ -96,9 +80,7 @@ struct SwapchainInfoVk
|
||||
private:
|
||||
uint32 m_acquireIndex = 0;
|
||||
std::vector<VkSemaphore> m_acquireSemaphores; // indexed by m_acquireIndex
|
||||
VkFence m_imageAvailableFence{};
|
||||
VkSemaphore m_currentSemaphore = VK_NULL_HANDLE;
|
||||
VkFence m_awaitableFence = VK_NULL_HANDLE;
|
||||
|
||||
std::array<uint32, 2> m_swapchainQueueFamilyIndices;
|
||||
VkExtent2D m_actualExtent{};
|
||||
|
@ -84,7 +84,7 @@ PipelineInfo::~PipelineInfo()
|
||||
// queue pipeline for destruction
|
||||
if (m_vkrObjPipeline)
|
||||
{
|
||||
VulkanRenderer::GetInstance()->releaseDestructibleObject(m_vkrObjPipeline);
|
||||
VulkanRenderer::GetInstance()->ReleaseDestructibleObject(m_vkrObjPipeline);
|
||||
m_vkrObjPipeline = nullptr;
|
||||
}
|
||||
|
||||
|
@ -300,7 +300,7 @@ void VulkanPipelineStableCache::LoadPipelineFromCache(std::span<uint8> fileData)
|
||||
delete pipelineInfo;
|
||||
delete lcr;
|
||||
delete cachedPipeline;
|
||||
VulkanRenderer::GetInstance()->releaseDestructibleObject(renderPass);
|
||||
VulkanRenderer::GetInstance()->ReleaseDestructibleObject(renderPass);
|
||||
s_spinlockSharedInternal.unlock();
|
||||
}
|
||||
|
||||
@ -408,6 +408,7 @@ bool VulkanPipelineStableCache::DeserializePipeline(MemStreamReader& memReader,
|
||||
|
||||
int VulkanPipelineStableCache::CompilerThread()
|
||||
{
|
||||
SetThreadName("plCacheCompiler");
|
||||
while (m_numCompilationThreads != 0)
|
||||
{
|
||||
std::vector<uint8> pipelineData = m_compilationQueue.pop();
|
||||
@ -421,6 +422,7 @@ int VulkanPipelineStableCache::CompilerThread()
|
||||
|
||||
void VulkanPipelineStableCache::WorkerThread()
|
||||
{
|
||||
SetThreadName("plCacheWriter");
|
||||
while (true)
|
||||
{
|
||||
CachedPipeline* job;
|
||||
|
@ -171,6 +171,7 @@ std::vector<VulkanRenderer::DeviceInfo> VulkanRenderer::GetDevices()
|
||||
result.emplace_back(physDeviceProps.properties.deviceName, physDeviceIDProps.deviceUUID);
|
||||
}
|
||||
}
|
||||
vkDestroySurfaceKHR(instance, surface, nullptr);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
@ -441,7 +442,7 @@ VulkanRenderer::VulkanRenderer()
|
||||
}
|
||||
|
||||
// create logical device
|
||||
m_indices = SwapchainInfoVk::FindQueueFamilies(surface, m_physicalDevice);
|
||||
m_indices = FindQueueFamilies(surface, m_physicalDevice);
|
||||
std::set<int> uniqueQueueFamilies = { m_indices.graphicsFamily, m_indices.presentFamily };
|
||||
std::vector<VkDeviceQueueCreateInfo> queueCreateInfos = CreateQueueCreateInfos(uniqueQueueFamilies);
|
||||
VkPhysicalDeviceFeatures deviceFeatures = {};
|
||||
@ -510,7 +511,7 @@ VulkanRenderer::VulkanRenderer()
|
||||
PFN_vkCreateDebugUtilsMessengerEXT vkCreateDebugUtilsMessengerEXT = reinterpret_cast<PFN_vkCreateDebugUtilsMessengerEXT>(vkGetInstanceProcAddr(m_instance, "vkCreateDebugUtilsMessengerEXT"));
|
||||
|
||||
VkDebugUtilsMessengerCreateInfoEXT debugCallback{};
|
||||
debugCallback.sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CREATE_INFO_EXT;
|
||||
debugCallback.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
|
||||
debugCallback.pNext = nullptr;
|
||||
debugCallback.flags = 0;
|
||||
debugCallback.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT;
|
||||
@ -531,7 +532,6 @@ VulkanRenderer::VulkanRenderer()
|
||||
|
||||
QueryMemoryInfo();
|
||||
QueryAvailableFormats();
|
||||
CreateBackbufferIndexBuffer();
|
||||
CreateCommandPool();
|
||||
CreateCommandBuffers();
|
||||
CreateDescriptorPool();
|
||||
@ -578,20 +578,6 @@ VulkanRenderer::VulkanRenderer()
|
||||
for (sint32 i = 0; i < OCCLUSION_QUERY_POOL_SIZE; i++)
|
||||
m_occlusionQueries.list_availableQueryIndices.emplace_back(i);
|
||||
|
||||
// enable surface copies via buffer if we have plenty of memory available (otherwise use drawcalls)
|
||||
size_t availableSurfaceCopyBufferMem = memoryManager->GetTotalMemoryForBufferType(VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
|
||||
//m_featureControl.mode.useBufferSurfaceCopies = availableSurfaceCopyBufferMem >= 2000ull * 1024ull * 1024ull; // enable if at least 2000MB VRAM
|
||||
m_featureControl.mode.useBufferSurfaceCopies = false;
|
||||
|
||||
if (m_featureControl.mode.useBufferSurfaceCopies)
|
||||
{
|
||||
//cemuLog_log(LogType::Force, "Enable surface copies via buffer");
|
||||
}
|
||||
else
|
||||
{
|
||||
//cemuLog_log(LogType::Force, "Disable surface copies via buffer (Requires 2GB. Has only {}MB available)", availableSurfaceCopyBufferMem / 1024ull / 1024ull);
|
||||
}
|
||||
|
||||
// start compilation threads
|
||||
RendererShaderVk::Init();
|
||||
}
|
||||
@ -601,7 +587,7 @@ VulkanRenderer::~VulkanRenderer()
|
||||
SubmitCommandBuffer();
|
||||
WaitDeviceIdle();
|
||||
WaitCommandBufferFinished(GetCurrentCommandBufferId());
|
||||
// shut down compilation threads
|
||||
// make sure compilation threads have been shut down
|
||||
RendererShaderVk::Shutdown();
|
||||
// shut down pipeline save thread
|
||||
m_destructionRequested = true;
|
||||
@ -615,7 +601,6 @@ VulkanRenderer::~VulkanRenderer()
|
||||
DeleteNullObjects();
|
||||
|
||||
// delete buffers
|
||||
memoryManager->DeleteBuffer(m_indexBuffer, m_indexBufferMemory);
|
||||
memoryManager->DeleteBuffer(m_uniformVarBuffer, m_uniformVarBufferMemory);
|
||||
memoryManager->DeleteBuffer(m_textureReadbackBuffer, m_textureReadbackBufferMemory);
|
||||
memoryManager->DeleteBuffer(m_xfbRingBuffer, m_xfbRingBufferMemory);
|
||||
@ -691,22 +676,19 @@ void VulkanRenderer::InitializeSurface(const Vector2i& size, bool mainWindow)
|
||||
{
|
||||
auto& windowHandleInfo = mainWindow ? GuiSystem::getWindowInfo().canvas_main : GuiSystem::getWindowInfo().canvas_pad;
|
||||
|
||||
const auto surface = CreateFramebufferSurface(m_instance, windowHandleInfo);
|
||||
if (mainWindow)
|
||||
{
|
||||
m_mainSwapchainInfo = std::make_unique<SwapchainInfoVk>(surface, mainWindow);
|
||||
m_mainSwapchainInfo->m_desiredExtent = size;
|
||||
m_mainSwapchainInfo->Create(m_physicalDevice, m_logicalDevice);
|
||||
m_mainSwapchainInfo = std::make_unique<SwapchainInfoVk>(mainWindow, size);
|
||||
m_mainSwapchainInfo->Create();
|
||||
|
||||
// aquire first command buffer
|
||||
InitFirstCommandBuffer();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_padSwapchainInfo = std::make_unique<SwapchainInfoVk>(surface, mainWindow);
|
||||
m_padSwapchainInfo->m_desiredExtent = size;
|
||||
m_padSwapchainInfo = std::make_unique<SwapchainInfoVk>(mainWindow, size);
|
||||
// todo: figure out a way to exclusively create swapchain on main LatteThread
|
||||
m_padSwapchainInfo->Create(m_physicalDevice, m_logicalDevice);
|
||||
m_padSwapchainInfo->Create();
|
||||
}
|
||||
}
|
||||
|
||||
@ -722,8 +704,8 @@ SwapchainInfoVk& VulkanRenderer::GetChainInfo(bool mainWindow) const
|
||||
|
||||
void VulkanRenderer::StopUsingPadAndWait()
|
||||
{
|
||||
m_destroyPadSwapchainNextAcquire = true;
|
||||
m_padCloseReadySemaphore.wait();
|
||||
m_destroyPadSwapchainNextAcquire.test_and_set();
|
||||
m_destroyPadSwapchainNextAcquire.wait(true);
|
||||
}
|
||||
|
||||
bool VulkanRenderer::IsPadWindowActive()
|
||||
@ -766,7 +748,7 @@ void VulkanRenderer::HandleScreenshotRequest(LatteTextureView* texView, bool pad
|
||||
//dumpImage->flagForCurrentCommandBuffer();
|
||||
|
||||
int width, height;
|
||||
LatteTexture_getEffectiveSize(baseImageTex, &width, &height, nullptr, 0);
|
||||
baseImageTex->GetEffectiveSize(width, height, 0);
|
||||
|
||||
VkImage image = nullptr;
|
||||
VkDeviceMemory imageMemory = nullptr;;
|
||||
@ -1090,6 +1072,36 @@ RendererShader* VulkanRenderer::shader_create(RendererShader::ShaderType type, u
|
||||
return new RendererShaderVk(type, baseHash, auxHash, isGameShader, isGfxPackShader, source);
|
||||
}
|
||||
|
||||
VulkanRenderer::QueueFamilyIndices VulkanRenderer::FindQueueFamilies(VkSurfaceKHR surface, VkPhysicalDevice device)
|
||||
{
|
||||
uint32_t queueFamilyCount = 0;
|
||||
vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr);
|
||||
|
||||
std::vector<VkQueueFamilyProperties> queueFamilies(queueFamilyCount);
|
||||
vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data());
|
||||
|
||||
QueueFamilyIndices indices;
|
||||
for (int i = 0; i < (int)queueFamilies.size(); ++i)
|
||||
{
|
||||
const auto& queueFamily = queueFamilies[i];
|
||||
if (queueFamily.queueCount > 0 && queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT)
|
||||
indices.graphicsFamily = i;
|
||||
|
||||
VkBool32 presentSupport = false;
|
||||
const VkResult result = vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport);
|
||||
if (result != VK_SUCCESS)
|
||||
throw std::runtime_error(fmt::format("Error while attempting to check if a surface supports presentation: {}", result));
|
||||
|
||||
if (queueFamily.queueCount > 0 && presentSupport)
|
||||
indices.presentFamily = i;
|
||||
|
||||
if (indices.IsComplete())
|
||||
break;
|
||||
}
|
||||
|
||||
return indices;
|
||||
}
|
||||
|
||||
bool VulkanRenderer::CheckDeviceExtensionSupport(const VkPhysicalDevice device, FeatureControl& info)
|
||||
{
|
||||
std::vector<VkExtensionProperties> availableDeviceExtensions;
|
||||
@ -1235,7 +1247,7 @@ std::vector<const char*> VulkanRenderer::CheckInstanceExtensionSupport(FeatureCo
|
||||
|
||||
bool VulkanRenderer::IsDeviceSuitable(VkSurfaceKHR surface, const VkPhysicalDevice& device)
|
||||
{
|
||||
if (!SwapchainInfoVk::FindQueueFamilies(surface, device).IsComplete())
|
||||
if (!FindQueueFamilies(surface, device).IsComplete())
|
||||
return false;
|
||||
|
||||
// check API version (using Vulkan 1.0 way of querying properties)
|
||||
@ -1433,8 +1445,7 @@ bool VulkanRenderer::IsSwapchainInfoValid(bool mainWindow) const
|
||||
|
||||
void VulkanRenderer::CreateNullTexture(NullTexture& nullTex, VkImageType imageType)
|
||||
{
|
||||
// these are used when the game requests NULL ptr textures or buffers
|
||||
// texture
|
||||
// these are used when the game requests NULL ptr textures
|
||||
VkImageCreateInfo imageInfo{};
|
||||
imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
|
||||
if (imageType == VK_IMAGE_TYPE_1D)
|
||||
@ -1589,12 +1600,12 @@ void VulkanRenderer::Shutdown()
|
||||
Renderer::Shutdown();
|
||||
SubmitCommandBuffer();
|
||||
WaitDeviceIdle();
|
||||
|
||||
if (m_imguiRenderPass != VK_NULL_HANDLE)
|
||||
{
|
||||
vkDestroyRenderPass(m_logicalDevice, m_imguiRenderPass, nullptr);
|
||||
m_imguiRenderPass = VK_NULL_HANDLE;
|
||||
}
|
||||
RendererShaderVk::Shutdown();
|
||||
}
|
||||
|
||||
void VulkanRenderer::UnrecoverableError(const char* errMsg) const
|
||||
@ -1843,49 +1854,6 @@ void VulkanRenderer::DrawEmptyFrame(bool mainWindow)
|
||||
SwapBuffers(mainWindow, !mainWindow);
|
||||
}
|
||||
|
||||
void VulkanRenderer::PreparePresentationFrame(bool mainWindow)
|
||||
{
|
||||
AcquireNextSwapchainImage(mainWindow);
|
||||
}
|
||||
|
||||
void VulkanRenderer::ProcessDestructionQueues(size_t commandBufferIndex)
|
||||
{
|
||||
auto& current_descriptor_cache = m_destructionQueues.m_cmd_descriptor_set_objects[commandBufferIndex];
|
||||
if (!current_descriptor_cache.empty())
|
||||
{
|
||||
assert_dbg();
|
||||
//for (const auto& descriptor : current_descriptor_cache)
|
||||
//{
|
||||
// vkFreeDescriptorSets(m_logicalDevice, m_descriptorPool, 1, &descriptor);
|
||||
// performanceMonitor.vk.numDescriptorSets.decrement();
|
||||
//}
|
||||
|
||||
current_descriptor_cache.clear();
|
||||
}
|
||||
|
||||
// destroy buffers
|
||||
for (auto& itr : m_destructionQueues.m_buffers[commandBufferIndex])
|
||||
vkDestroyBuffer(m_logicalDevice, itr, nullptr);
|
||||
m_destructionQueues.m_buffers[commandBufferIndex].clear();
|
||||
|
||||
// destroy device memory objects
|
||||
for (auto& itr : m_destructionQueues.m_memory[commandBufferIndex])
|
||||
vkFreeMemory(m_logicalDevice, itr, nullptr);
|
||||
m_destructionQueues.m_memory[commandBufferIndex].clear();
|
||||
|
||||
// destroy image views
|
||||
for (auto& itr : m_destructionQueues.m_cmd_image_views[commandBufferIndex])
|
||||
vkDestroyImageView(m_logicalDevice, itr, nullptr);
|
||||
m_destructionQueues.m_cmd_image_views[commandBufferIndex].clear();
|
||||
|
||||
// destroy host textures
|
||||
for (auto itr : m_destructionQueues.m_host_textures[commandBufferIndex])
|
||||
delete itr;
|
||||
m_destructionQueues.m_host_textures[commandBufferIndex].clear();
|
||||
|
||||
ProcessDestructionQueue2();
|
||||
}
|
||||
|
||||
void VulkanRenderer::InitFirstCommandBuffer()
|
||||
{
|
||||
cemu_assert_debug(m_state.currentCommandBuffer == nullptr);
|
||||
@ -1914,7 +1882,7 @@ void VulkanRenderer::ProcessFinishedCommandBuffers()
|
||||
VkResult fenceStatus = vkGetFenceStatus(m_logicalDevice, m_cmd_buffer_fences[m_commandBufferSyncIndex]);
|
||||
if (fenceStatus == VK_SUCCESS)
|
||||
{
|
||||
ProcessDestructionQueues(m_commandBufferSyncIndex);
|
||||
ProcessDestructionQueue();
|
||||
m_commandBufferSyncIndex = (m_commandBufferSyncIndex + 1) % m_commandBuffers.size();
|
||||
memoryManager->cleanupBuffers(m_countCommandBufferFinished);
|
||||
m_countCommandBufferFinished++;
|
||||
@ -2069,6 +2037,7 @@ void VulkanRenderer::WaitCommandBufferFinished(uint64 commandBufferId)
|
||||
|
||||
void VulkanRenderer::PipelineCacheSaveThread(size_t cache_size)
|
||||
{
|
||||
SetThreadName("vkDriverPlCache");
|
||||
const auto dir = ActiveSettings::GetCachePath("shaderCache/driver/vk");
|
||||
if (!fs::exists(dir))
|
||||
{
|
||||
@ -2639,11 +2608,11 @@ bool VulkanRenderer::AcquireNextSwapchainImage(bool mainWindow)
|
||||
if(!IsSwapchainInfoValid(mainWindow))
|
||||
return false;
|
||||
|
||||
if(!mainWindow && m_destroyPadSwapchainNextAcquire)
|
||||
if(!mainWindow && m_destroyPadSwapchainNextAcquire.test())
|
||||
{
|
||||
RecreateSwapchain(mainWindow, true);
|
||||
m_destroyPadSwapchainNextAcquire = false;
|
||||
m_padCloseReadySemaphore.notify();
|
||||
m_destroyPadSwapchainNextAcquire.clear();
|
||||
m_destroyPadSwapchainNextAcquire.notify_all();
|
||||
return false;
|
||||
}
|
||||
auto& chainInfo = GetChainInfo(mainWindow);
|
||||
@ -2654,7 +2623,7 @@ bool VulkanRenderer::AcquireNextSwapchainImage(bool mainWindow)
|
||||
if (!UpdateSwapchainProperties(mainWindow))
|
||||
return false;
|
||||
|
||||
bool result = chainInfo.AcquireImage(UINT64_MAX);
|
||||
bool result = chainInfo.AcquireImage();
|
||||
if (!result)
|
||||
return false;
|
||||
|
||||
@ -2667,8 +2636,6 @@ void VulkanRenderer::RecreateSwapchain(bool mainWindow, bool skipCreate)
|
||||
SubmitCommandBuffer();
|
||||
WaitDeviceIdle();
|
||||
auto& chainInfo = GetChainInfo(mainWindow);
|
||||
// make sure fence has no signal operation submitted
|
||||
chainInfo.WaitAvailableFence();
|
||||
|
||||
Vector2i size;
|
||||
if (mainWindow)
|
||||
@ -2686,7 +2653,7 @@ void VulkanRenderer::RecreateSwapchain(bool mainWindow, bool skipCreate)
|
||||
chainInfo.m_desiredExtent = size;
|
||||
if(!skipCreate)
|
||||
{
|
||||
chainInfo.Create(m_physicalDevice, m_logicalDevice);
|
||||
chainInfo.Create();
|
||||
}
|
||||
|
||||
if (mainWindow)
|
||||
@ -2756,7 +2723,7 @@ void VulkanRenderer::SwapBuffer(bool mainWindow)
|
||||
VkPresentInfoKHR presentInfo = {};
|
||||
presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
|
||||
presentInfo.swapchainCount = 1;
|
||||
presentInfo.pSwapchains = &chainInfo.swapchain;
|
||||
presentInfo.pSwapchains = &chainInfo.m_swapchain;
|
||||
presentInfo.pImageIndices = &chainInfo.swapchainImageIndex;
|
||||
// wait on command buffer semaphore
|
||||
presentInfo.waitSemaphoreCount = 1;
|
||||
@ -2843,6 +2810,35 @@ void VulkanRenderer::ClearColorImageRaw(VkImage image, uint32 sliceIndex, uint32
|
||||
|
||||
void VulkanRenderer::ClearColorImage(LatteTextureVk* vkTexture, uint32 sliceIndex, uint32 mipIndex, const VkClearColorValue& color, VkImageLayout outputLayout)
|
||||
{
|
||||
if(vkTexture->isDepth)
|
||||
{
|
||||
cemu_assert_suspicious();
|
||||
return;
|
||||
}
|
||||
if (vkTexture->IsCompressedFormat())
|
||||
{
|
||||
// vkCmdClearColorImage cannot be called on compressed formats
|
||||
// for now we ignore affected clears but still transition the image to the correct layout
|
||||
auto imageObj = vkTexture->GetImageObj();
|
||||
imageObj->flagForCurrentCommandBuffer();
|
||||
VkImageSubresourceLayers subresourceRange{};
|
||||
subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
subresourceRange.mipLevel = mipIndex;
|
||||
subresourceRange.baseArrayLayer = sliceIndex;
|
||||
subresourceRange.layerCount = 1;
|
||||
barrier_image<ANY_TRANSFER | IMAGE_READ, ANY_TRANSFER | IMAGE_READ | IMAGE_WRITE>(vkTexture, subresourceRange, outputLayout);
|
||||
if(color.float32[0] == 0.0f && color.float32[1] == 0.0f && color.float32[2] == 0.0f && color.float32[3] == 0.0f)
|
||||
{
|
||||
static bool dbgMsgPrinted = false;
|
||||
if(!dbgMsgPrinted)
|
||||
{
|
||||
cemuLog_logDebug(LogType::Force, "Unsupported compressed texture clear to zero");
|
||||
dbgMsgPrinted = true;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
VkImageSubresourceRange subresourceRange;
|
||||
|
||||
subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
@ -2859,18 +2855,6 @@ void VulkanRenderer::ClearColorImage(LatteTextureVk* vkTexture, uint32 sliceInde
|
||||
vkTexture->SetImageLayout(subresourceRange, outputLayout);
|
||||
}
|
||||
|
||||
void VulkanRenderer::CreateBackbufferIndexBuffer()
|
||||
{
|
||||
const VkDeviceSize bufferSize = sizeof(uint16) * 6;
|
||||
memoryManager->CreateBuffer(bufferSize, VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, m_indexBuffer, m_indexBufferMemory);
|
||||
|
||||
uint16* data;
|
||||
vkMapMemory(m_logicalDevice, m_indexBufferMemory, 0, bufferSize, 0, (void**)&data);
|
||||
const uint16 tmp[] = { 0, 1, 2, 3, 4, 5 };
|
||||
std::copy(std::begin(tmp), std::end(tmp), data);
|
||||
vkUnmapMemory(m_logicalDevice, m_indexBufferMemory);
|
||||
}
|
||||
|
||||
void VulkanRenderer::DrawBackbufferQuad(LatteTextureView* texView, RendererOutputShader* shader, bool useLinearTexFilter, sint32 imageX, sint32 imageY, sint32 imageWidth, sint32 imageHeight, bool padView, bool clearBackground)
|
||||
{
|
||||
if(!AcquireNextSwapchainImage(!padView))
|
||||
@ -2921,11 +2905,9 @@ void VulkanRenderer::DrawBackbufferQuad(LatteTextureView* texView, RendererOutpu
|
||||
vkCmdBindPipeline(m_state.currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
|
||||
m_state.currentPipeline = pipeline;
|
||||
|
||||
vkCmdBindIndexBuffer(m_state.currentCommandBuffer, m_indexBuffer, 0, VK_INDEX_TYPE_UINT16);
|
||||
|
||||
vkCmdBindDescriptorSets(m_state.currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipelineLayout, 0, 1, &descriptSet, 0, nullptr);
|
||||
|
||||
vkCmdDrawIndexed(m_state.currentCommandBuffer, 6, 1, 0, 0, 0);
|
||||
vkCmdDraw(m_state.currentCommandBuffer, 6, 1, 0, 0);
|
||||
|
||||
vkCmdEndRenderPass(m_state.currentCommandBuffer);
|
||||
|
||||
@ -3068,48 +3050,7 @@ TextureDecoder* VulkanRenderer::texture_chooseDecodedFormat(Latte::E_GX2SURFFMT
|
||||
return texFormatInfo.decoder;
|
||||
}
|
||||
|
||||
void VulkanRenderer::texture_reserveTextureOnGPU(LatteTexture* hostTexture)
|
||||
{
|
||||
LatteTextureVk* vkTexture = (LatteTextureVk*)hostTexture;
|
||||
auto allocationInfo = memoryManager->imageMemoryAllocate(vkTexture->GetImageObj()->m_image);
|
||||
vkTexture->setAllocation(allocationInfo);
|
||||
}
|
||||
|
||||
void VulkanRenderer::texture_destroy(LatteTexture* hostTexture)
|
||||
{
|
||||
LatteTextureVk* texVk = (LatteTextureVk*)hostTexture;
|
||||
delete texVk;
|
||||
}
|
||||
|
||||
void VulkanRenderer::destroyViewDepr(VkImageView imageView)
|
||||
{
|
||||
cemu_assert_debug(false);
|
||||
|
||||
m_destructionQueues.m_cmd_image_views[m_commandBufferIndex].emplace_back(imageView);
|
||||
}
|
||||
|
||||
void VulkanRenderer::destroyBuffer(VkBuffer buffer)
|
||||
{
|
||||
m_destructionQueues.m_buffers[m_commandBufferIndex].emplace_back(buffer);
|
||||
}
|
||||
|
||||
void VulkanRenderer::destroyDeviceMemory(VkDeviceMemory mem)
|
||||
{
|
||||
m_destructionQueues.m_memory[m_commandBufferIndex].emplace_back(mem);
|
||||
}
|
||||
|
||||
void VulkanRenderer::destroyPipelineInfo(PipelineInfo* pipelineInfo)
|
||||
{
|
||||
cemu_assert_debug(false);
|
||||
}
|
||||
|
||||
void VulkanRenderer::destroyShader(RendererShaderVk* shader)
|
||||
{
|
||||
while (!shader->list_pipelineInfo.empty())
|
||||
delete shader->list_pipelineInfo[0];
|
||||
}
|
||||
|
||||
void VulkanRenderer::releaseDestructibleObject(VKRDestructibleObject* destructibleObject)
|
||||
void VulkanRenderer::ReleaseDestructibleObject(VKRDestructibleObject* destructibleObject)
|
||||
{
|
||||
// destroy immediately if possible
|
||||
if (destructibleObject->canDestroy())
|
||||
@ -3123,7 +3064,7 @@ void VulkanRenderer::releaseDestructibleObject(VKRDestructibleObject* destructib
|
||||
m_spinlockDestructionQueue.unlock();
|
||||
}
|
||||
|
||||
void VulkanRenderer::ProcessDestructionQueue2()
|
||||
void VulkanRenderer::ProcessDestructionQueue()
|
||||
{
|
||||
m_spinlockDestructionQueue.lock();
|
||||
for (auto it = m_destructionQueue.begin(); it != m_destructionQueue.end();)
|
||||
@ -3172,7 +3113,7 @@ VkDescriptorSetInfo::~VkDescriptorSetInfo()
|
||||
performanceMonitor.vk.numDescriptorDynUniformBuffers.decrement(statsNumDynUniformBuffers);
|
||||
performanceMonitor.vk.numDescriptorStorageBuffers.decrement(statsNumStorageBuffers);
|
||||
|
||||
VulkanRenderer::GetInstance()->releaseDestructibleObject(m_vkObjDescriptorSet);
|
||||
VulkanRenderer::GetInstance()->ReleaseDestructibleObject(m_vkObjDescriptorSet);
|
||||
m_vkObjDescriptorSet = nullptr;
|
||||
}
|
||||
|
||||
@ -3185,32 +3126,18 @@ void VulkanRenderer::texture_clearSlice(LatteTexture* hostTexture, sint32 sliceI
|
||||
else
|
||||
{
|
||||
cemu_assert_debug(vkTexture->dim != Latte::E_DIM::DIM_3D);
|
||||
if (hostTexture->IsCompressedFormat())
|
||||
{
|
||||
auto imageObj = vkTexture->GetImageObj();
|
||||
imageObj->flagForCurrentCommandBuffer();
|
||||
|
||||
cemuLog_logDebug(LogType::Force, "Compressed texture ({}/{} fmt {:04x}) unsupported clear", vkTexture->width, vkTexture->height, (uint32)vkTexture->format);
|
||||
|
||||
VkImageSubresourceLayers subresourceRange{};
|
||||
subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
subresourceRange.mipLevel = mipIndex;
|
||||
subresourceRange.baseArrayLayer = sliceIndex;
|
||||
subresourceRange.layerCount = 1;
|
||||
barrier_image<ANY_TRANSFER | IMAGE_READ, ANY_TRANSFER | IMAGE_READ | IMAGE_WRITE>(vkTexture, subresourceRange, VK_IMAGE_LAYOUT_GENERAL);
|
||||
}
|
||||
else
|
||||
{
|
||||
ClearColorImage(vkTexture, sliceIndex, mipIndex, { 0,0,0,0 }, VK_IMAGE_LAYOUT_GENERAL);
|
||||
}
|
||||
ClearColorImage(vkTexture, sliceIndex, mipIndex, { 0,0,0,0 }, VK_IMAGE_LAYOUT_GENERAL);
|
||||
}
|
||||
}
|
||||
|
||||
void VulkanRenderer::texture_clearColorSlice(LatteTexture* hostTexture, sint32 sliceIndex, sint32 mipIndex, float r, float g, float b, float a)
|
||||
{
|
||||
auto vkTexture = (LatteTextureVk*)hostTexture;
|
||||
cemu_assert_debug(vkTexture->dim != Latte::E_DIM::DIM_3D);
|
||||
ClearColorImage(vkTexture, sliceIndex, mipIndex, { r,g,b,a }, VK_IMAGE_LAYOUT_GENERAL);
|
||||
if(vkTexture->dim == Latte::E_DIM::DIM_3D)
|
||||
{
|
||||
cemu_assert_unimplemented();
|
||||
}
|
||||
ClearColorImage(vkTexture, sliceIndex, mipIndex, {r, g, b, a}, VK_IMAGE_LAYOUT_GENERAL);
|
||||
}
|
||||
|
||||
void VulkanRenderer::texture_clearDepthSlice(LatteTexture* hostTexture, uint32 sliceIndex, sint32 mipIndex, bool clearDepth, bool clearStencil, float depthValue, uint32 stencilValue)
|
||||
|
@ -128,10 +128,8 @@ class VulkanRenderer : public Renderer
|
||||
friend class PipelineCompiler;
|
||||
|
||||
using VSync = SwapchainInfoVk::VSync;
|
||||
using QueueFamilyIndices = SwapchainInfoVk::QueueFamilyIndices;
|
||||
|
||||
static const inline int UNIFORMVAR_RINGBUFFER_SIZE = 1024 * 1024 * 16; // 16MB
|
||||
static const inline int INDEX_STREAM_BUFFER_SIZE = 16 * 1024 * 1024; // 16 MB
|
||||
|
||||
static const inline int TEXTURE_READBACK_SIZE = 32 * 1024 * 1024; // 32 MB
|
||||
|
||||
@ -235,9 +233,7 @@ public:
|
||||
uint64 GenUniqueId(); // return unique id (uses incrementing counter)
|
||||
|
||||
void DrawEmptyFrame(bool mainWindow) override;
|
||||
void PreparePresentationFrame(bool mainWindow);
|
||||
|
||||
void ProcessDestructionQueues(size_t commandBufferIndex);
|
||||
void InitFirstCommandBuffer();
|
||||
void ProcessFinishedCommandBuffers();
|
||||
void WaitForNextFinishedCommandBuffer();
|
||||
@ -250,15 +246,9 @@ public:
|
||||
bool HasCommandBufferFinished(uint64 commandBufferId) const;
|
||||
void WaitCommandBufferFinished(uint64 commandBufferId);
|
||||
|
||||
// clean up (deprecated)
|
||||
void destroyViewDepr(VkImageView imageView);
|
||||
void destroyBuffer(VkBuffer buffer);
|
||||
void destroyDeviceMemory(VkDeviceMemory mem);
|
||||
void destroyPipelineInfo(PipelineInfo* pipelineInfo);
|
||||
void destroyShader(RendererShaderVk* shader);
|
||||
// clean up (new)
|
||||
void releaseDestructibleObject(VKRDestructibleObject* destructibleObject);
|
||||
void ProcessDestructionQueue2();
|
||||
// resource destruction queue
|
||||
void ReleaseDestructibleObject(VKRDestructibleObject* destructibleObject);
|
||||
void ProcessDestructionQueue();
|
||||
|
||||
FSpinlock m_spinlockDestructionQueue;
|
||||
std::vector<VKRDestructibleObject*> m_destructionQueue;
|
||||
@ -296,9 +286,6 @@ public:
|
||||
|
||||
TextureDecoder* texture_chooseDecodedFormat(Latte::E_GX2SURFFMT format, bool isDepth, Latte::E_DIM dim, uint32 width, uint32 height) override;
|
||||
|
||||
void texture_reserveTextureOnGPU(LatteTexture* hostTexture) override;
|
||||
void texture_destroy(LatteTexture* hostTexture) override;
|
||||
|
||||
void texture_clearSlice(LatteTexture* hostTexture, sint32 sliceIndex, sint32 mipIndex) override;
|
||||
void texture_clearColorSlice(LatteTexture* hostTexture, sint32 sliceIndex, sint32 mipIndex, float r, float g, float b, float a) override;
|
||||
void texture_clearDepthSlice(LatteTexture* hostTexture, uint32 sliceIndex, sint32 mipIndex, bool clearDepth, bool clearStencil, float depthValue, uint32 stencilValue) override;
|
||||
@ -317,7 +304,6 @@ public:
|
||||
void surfaceCopy_notifyTextureRelease(LatteTextureVk* hostTexture);
|
||||
|
||||
private:
|
||||
void surfaceCopy_viaBuffer(LatteTextureVk* srcTextureVk, sint32 texSrcMip, sint32 texSrcLevel, LatteTextureVk* dstTextureVk, sint32 texDstMip, sint32 texDstLevel, sint32 effectiveCopyWidth, sint32 effectiveCopyHeight);
|
||||
void surfaceCopy_viaDrawcall(LatteTextureVk* srcTextureVk, sint32 texSrcMip, sint32 texSrcSlice, LatteTextureVk* dstTextureVk, sint32 texDstMip, sint32 texDstSlice, sint32 effectiveCopyWidth, sint32 effectiveCopyHeight);
|
||||
|
||||
void surfaceCopy_cleanup();
|
||||
@ -334,10 +320,6 @@ private:
|
||||
|
||||
std::unordered_map<uint64, struct CopySurfacePipelineInfo*> m_copySurfacePipelineCache;
|
||||
|
||||
VkBuffer m_surfaceCopyBuffer = VK_NULL_HANDLE;
|
||||
VkDeviceMemory m_surfaceCopyBufferMemory = VK_NULL_HANDLE;
|
||||
size_t m_surfaceCopyBufferSize{};
|
||||
|
||||
public:
|
||||
// renderer interface
|
||||
void bufferCache_init(const sint32 bufferSize) override;
|
||||
@ -435,14 +417,25 @@ private:
|
||||
}m_state;
|
||||
|
||||
std::unique_ptr<SwapchainInfoVk> m_mainSwapchainInfo{}, m_padSwapchainInfo{};
|
||||
Semaphore m_padCloseReadySemaphore;
|
||||
bool m_destroyPadSwapchainNextAcquire = false;
|
||||
std::atomic_flag m_destroyPadSwapchainNextAcquire{};
|
||||
bool IsSwapchainInfoValid(bool mainWindow) const;
|
||||
|
||||
VkRenderPass m_imguiRenderPass = VK_NULL_HANDLE;
|
||||
|
||||
VkDescriptorPool m_descriptorPool;
|
||||
|
||||
public:
|
||||
struct QueueFamilyIndices
|
||||
{
|
||||
int32_t graphicsFamily = -1;
|
||||
int32_t presentFamily = -1;
|
||||
|
||||
bool IsComplete() const { return graphicsFamily >= 0 && presentFamily >= 0; }
|
||||
};
|
||||
static QueueFamilyIndices FindQueueFamilies(VkSurfaceKHR surface, VkPhysicalDevice device);
|
||||
|
||||
private:
|
||||
|
||||
struct FeatureControl
|
||||
{
|
||||
struct
|
||||
@ -476,7 +469,6 @@ private:
|
||||
|
||||
struct
|
||||
{
|
||||
bool useBufferSurfaceCopies; // if GPU has enough VRAM to spare, allow to use a buffer to copy surfaces (instead of drawcalls)
|
||||
bool useTFEmulationViaSSBO = true; // emulate transform feedback via shader writes to a storage buffer
|
||||
}mode;
|
||||
|
||||
@ -553,15 +545,11 @@ private:
|
||||
void sync_RenderPassStoreTextures(CachedFBOVk* fboVk);
|
||||
|
||||
// command buffer
|
||||
|
||||
VkCommandBuffer getCurrentCommandBuffer() const { return m_state.currentCommandBuffer; }
|
||||
|
||||
// uniform
|
||||
void uniformData_updateUniformVars(uint32 shaderStageIndex, LatteDecompilerShader* shader);
|
||||
|
||||
// indices
|
||||
void CreateBackbufferIndexBuffer();
|
||||
|
||||
// misc
|
||||
void CreatePipelineCache();
|
||||
VkPipelineShaderStageCreateInfo CreatePipelineShaderStageCreateInfo(VkShaderStageFlagBits stage, VkShaderModule& module, const char* entryName) const;
|
||||
@ -585,9 +573,6 @@ private:
|
||||
void occlusionQuery_notifyBeginCommandBuffer();
|
||||
|
||||
private:
|
||||
VkBuffer m_indexBuffer = VK_NULL_HANDLE;
|
||||
VkDeviceMemory m_indexBufferMemory = VK_NULL_HANDLE;
|
||||
|
||||
std::vector<const char*> m_layerNames;
|
||||
VkInstance m_instance = VK_NULL_HANDLE;
|
||||
VkPhysicalDevice m_physicalDevice = VK_NULL_HANDLE;
|
||||
@ -653,15 +638,6 @@ private:
|
||||
// command buffer, garbage collection, synchronization
|
||||
static constexpr uint32 kCommandBufferPoolSize = 128;
|
||||
|
||||
struct
|
||||
{
|
||||
std::array<std::vector<VkDescriptorSet>, kCommandBufferPoolSize> m_cmd_descriptor_set_objects;
|
||||
std::array<std::vector<VkImageView>, kCommandBufferPoolSize> m_cmd_image_views;
|
||||
std::array<std::vector<LatteTextureVk*>, kCommandBufferPoolSize> m_host_textures;
|
||||
std::array<std::vector<VkBuffer>, kCommandBufferPoolSize> m_buffers;
|
||||
std::array<std::vector<VkDeviceMemory>, kCommandBufferPoolSize> m_memory;
|
||||
}m_destructionQueues;
|
||||
|
||||
size_t m_commandBufferIndex = 0; // current buffer being filled
|
||||
size_t m_commandBufferSyncIndex = 0; // latest buffer that finished execution (updated on submit)
|
||||
std::array<VkFence, kCommandBufferPoolSize> m_cmd_buffer_fences;
|
||||
|
@ -190,6 +190,7 @@ std::queue<PipelineCompiler*> g_compilePipelineRequests;
|
||||
|
||||
void compilePipeline_thread(sint32 threadIndex)
|
||||
{
|
||||
SetThreadName("compilePl");
|
||||
#ifdef _WIN32
|
||||
// one thread runs at normal priority while the others run at lower priority
|
||||
if(threadIndex != 0)
|
||||
@ -1284,9 +1285,9 @@ void VulkanRenderer::draw_beginSequence()
|
||||
|
||||
// update shader state
|
||||
LatteSHRC_UpdateActiveShaders();
|
||||
if (m_state.drawSequenceSkip)
|
||||
if (LatteGPUState.activeShaderHasError)
|
||||
{
|
||||
debug_printf("Skipping drawcalls due to shader error\n");
|
||||
cemuLog_logDebugOnce(LogType::Force, "Skipping drawcalls due to shader error");
|
||||
m_state.drawSequenceSkip = true;
|
||||
cemu_assert_debug(false);
|
||||
return;
|
||||
|
@ -464,9 +464,8 @@ VKRObjectFramebuffer* VulkanRenderer::surfaceCopy_getOrCreateFramebuffer(VkCopyS
|
||||
VKRObjectTextureView* vkObjTextureView = surfaceCopy_createImageView(state.destinationTexture, state.dstSlice, state.dstMip);
|
||||
|
||||
// create new framebuffer
|
||||
sint32 effectiveWidth = 0;
|
||||
sint32 effectiveHeight = 0;
|
||||
LatteTexture_getEffectiveSize(state.destinationTexture, &effectiveWidth, &effectiveHeight, nullptr, state.dstMip);
|
||||
sint32 effectiveWidth, effectiveHeight;
|
||||
state.destinationTexture->GetEffectiveSize(effectiveWidth, effectiveHeight, state.dstMip);
|
||||
|
||||
std::array<VKRObjectTextureView*, 1> fbAttachments;
|
||||
fbAttachments[0] = vkObjTextureView;
|
||||
@ -595,15 +594,11 @@ void VulkanRenderer::surfaceCopy_viaDrawcall(LatteTextureVk* srcTextureVk, sint3
|
||||
// get descriptor set
|
||||
VKRObjectDescriptorSet* vkObjDescriptorSet = surfaceCopy_getOrCreateDescriptorSet(copySurfaceState, copySurfacePipelineInfo);
|
||||
|
||||
// get extend
|
||||
sint32 effectiveWidth = 0;
|
||||
sint32 effectiveHeight = 0;
|
||||
LatteTexture_getEffectiveSize(dstTextureVk, &effectiveWidth, &effectiveHeight, nullptr, texDstMip);
|
||||
sint32 dstEffectiveWidth, dstEffectiveHeight;
|
||||
dstTextureVk->GetEffectiveSize(dstEffectiveWidth, dstEffectiveHeight, texDstMip);
|
||||
|
||||
// get extend
|
||||
sint32 srcEffectiveWidth = 0;
|
||||
sint32 srcEffectiveHeight = 0;
|
||||
LatteTexture_getEffectiveSize(srcTextureVk, &srcEffectiveWidth, &srcEffectiveHeight, nullptr, texSrcMip);
|
||||
sint32 srcEffectiveWidth, srcEffectiveHeight;
|
||||
srcTextureVk->GetEffectiveSize(srcEffectiveWidth, srcEffectiveHeight, texSrcMip);
|
||||
|
||||
CopyShaderPushConstantData_t pushConstantData;
|
||||
|
||||
@ -768,119 +763,14 @@ bool vkIsBitCompatibleColorDepthFormat(VkFormat format1, VkFormat format2)
|
||||
return false;
|
||||
}
|
||||
|
||||
void VulkanRenderer::surfaceCopy_viaBuffer(LatteTextureVk* srcTextureVk, sint32 texSrcMip, sint32 texSrcSlice, LatteTextureVk* dstTextureVk, sint32 texDstMip, sint32 texDstSlice, sint32 effectiveCopyWidth, sint32 effectiveCopyHeight)
|
||||
{
|
||||
cemu_assert_debug(false); // not used currently
|
||||
|
||||
cemu_assert_debug(m_featureControl.mode.useBufferSurfaceCopies);
|
||||
|
||||
if (srcTextureVk->dim == Latte::E_DIM::DIM_3D)
|
||||
{
|
||||
cemu_assert_debug(false);
|
||||
return;
|
||||
}
|
||||
if (dstTextureVk->dim == Latte::E_DIM::DIM_3D)
|
||||
{
|
||||
cemu_assert_debug(false);
|
||||
return;
|
||||
}
|
||||
|
||||
draw_endRenderPass();
|
||||
|
||||
// calculate buffer size required for copy
|
||||
VkDeviceSize copySize = std::max(srcTextureVk->getAllocation()->getAllocationSize(), dstTextureVk->getAllocation()->getAllocationSize());
|
||||
|
||||
// make sure allocated buffer is large enough
|
||||
if (m_surfaceCopyBuffer == VK_NULL_HANDLE || copySize > m_surfaceCopyBufferSize)
|
||||
{
|
||||
if (m_surfaceCopyBuffer != VK_NULL_HANDLE)
|
||||
{
|
||||
// free existing buffer
|
||||
destroyDeviceMemory(m_surfaceCopyBufferMemory);
|
||||
m_surfaceCopyBufferMemory = VK_NULL_HANDLE;
|
||||
destroyBuffer(m_surfaceCopyBuffer);
|
||||
m_surfaceCopyBuffer = VK_NULL_HANDLE;
|
||||
}
|
||||
VkDeviceSize allocSize = (copySize + 1024ull * 1024ull - 1ull) & ~(1024ull * 1024ull - 1ull); // align to whole MB
|
||||
m_surfaceCopyBufferSize = allocSize;
|
||||
memoryManager->CreateBuffer(m_surfaceCopyBufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, m_surfaceCopyBuffer, m_surfaceCopyBufferMemory);
|
||||
if (m_surfaceCopyBuffer == VK_NULL_HANDLE)
|
||||
{
|
||||
cemuLog_log(LogType::Force, "Vulkan: Failed to allocate surface copy buffer with size {}", allocSize);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (m_surfaceCopyBuffer == VK_NULL_HANDLE)
|
||||
return;
|
||||
|
||||
auto vkObjSrcTexture = srcTextureVk->GetImageObj();
|
||||
auto vkObjDstTexture = dstTextureVk->GetImageObj();
|
||||
vkObjSrcTexture->flagForCurrentCommandBuffer();
|
||||
vkObjDstTexture->flagForCurrentCommandBuffer();
|
||||
|
||||
VkBufferImageCopy region{};
|
||||
region.bufferOffset = 0;
|
||||
region.bufferRowLength = effectiveCopyWidth;
|
||||
region.bufferImageHeight = effectiveCopyHeight;
|
||||
|
||||
if (srcTextureVk->isDepth)
|
||||
region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
|
||||
else
|
||||
region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
region.imageSubresource.baseArrayLayer = texSrcSlice;
|
||||
region.imageSubresource.layerCount = 1;
|
||||
region.imageSubresource.mipLevel = texSrcMip;
|
||||
|
||||
region.imageOffset = { 0,0,0 };
|
||||
region.imageExtent = { (uint32)effectiveCopyWidth, (uint32)effectiveCopyHeight, 1 };
|
||||
|
||||
// make sure all write operations to the src image have finished
|
||||
barrier_image<SYNC_OP::IMAGE_WRITE | SYNC_OP::ANY_TRANSFER, SYNC_OP::ANY_TRANSFER>(srcTextureVk, region.imageSubresource, VK_IMAGE_LAYOUT_GENERAL);
|
||||
|
||||
vkCmdCopyImageToBuffer(getCurrentCommandBuffer(), vkObjSrcTexture->m_image, VK_IMAGE_LAYOUT_GENERAL, m_surfaceCopyBuffer, 1, ®ion);
|
||||
|
||||
// copy buffer to image
|
||||
|
||||
VkBufferImageCopy imageRegion[2]{};
|
||||
sint32 imageRegionCount = 0;
|
||||
|
||||
// color or depth only copy
|
||||
imageRegion[0].bufferOffset = 0;
|
||||
imageRegion[0].imageExtent.width = effectiveCopyWidth;
|
||||
imageRegion[0].imageExtent.height = effectiveCopyHeight;
|
||||
imageRegion[0].imageExtent.depth = 1;
|
||||
|
||||
imageRegion[0].imageSubresource.mipLevel = texDstMip;
|
||||
if (dstTextureVk->isDepth)
|
||||
imageRegion[0].imageSubresource.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
|
||||
else
|
||||
imageRegion[0].imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
imageRegion[0].imageSubresource.baseArrayLayer = texDstSlice;
|
||||
imageRegion[0].imageSubresource.layerCount = 1;
|
||||
|
||||
imageRegionCount = 1;
|
||||
|
||||
// make sure the transfer to the buffer finished
|
||||
barrier_bufferRange<SYNC_OP::ANY_TRANSFER, SYNC_OP::ANY_TRANSFER>(m_surfaceCopyBuffer, 0, VK_WHOLE_SIZE);
|
||||
|
||||
// make sure all read and write operations to the dst image have finished
|
||||
barrier_image<SYNC_OP::IMAGE_READ | SYNC_OP::IMAGE_WRITE | SYNC_OP::ANY_TRANSFER, SYNC_OP::ANY_TRANSFER>(dstTextureVk, imageRegion[0].imageSubresource, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
|
||||
|
||||
vkCmdCopyBufferToImage(m_state.currentCommandBuffer, m_surfaceCopyBuffer, vkObjDstTexture->m_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, imageRegionCount, imageRegion);
|
||||
|
||||
// make sure transfer has finished before any other operation
|
||||
barrier_image<SYNC_OP::ANY_TRANSFER, SYNC_OP::ANY_TRANSFER | SYNC_OP::IMAGE_READ | SYNC_OP::IMAGE_WRITE>(dstTextureVk, imageRegion[0].imageSubresource, VK_IMAGE_LAYOUT_GENERAL);
|
||||
}
|
||||
|
||||
void VulkanRenderer::surfaceCopy_copySurfaceWithFormatConversion(LatteTexture* sourceTexture, sint32 srcMip, sint32 srcSlice, LatteTexture* destinationTexture, sint32 dstMip, sint32 dstSlice, sint32 width, sint32 height)
|
||||
{
|
||||
// scale copy size to effective size
|
||||
sint32 effectiveCopyWidth = width;
|
||||
sint32 effectiveCopyHeight = height;
|
||||
LatteTexture_scaleToEffectiveSize(sourceTexture, &effectiveCopyWidth, &effectiveCopyHeight, 0);
|
||||
sint32 sourceEffectiveWidth;
|
||||
sint32 sourceEffectiveHeight;
|
||||
LatteTexture_getEffectiveSize(sourceTexture, &sourceEffectiveWidth, &sourceEffectiveHeight, nullptr, srcMip);
|
||||
sint32 sourceEffectiveWidth, sourceEffectiveHeight;
|
||||
sourceTexture->GetEffectiveSize(sourceEffectiveWidth, sourceEffectiveHeight, srcMip);
|
||||
|
||||
sint32 texSrcMip = srcMip;
|
||||
sint32 texSrcSlice = srcSlice;
|
||||
@ -905,28 +795,7 @@ void VulkanRenderer::surfaceCopy_copySurfaceWithFormatConversion(LatteTexture* s
|
||||
return;
|
||||
}
|
||||
|
||||
VkFormat srcFormatVk = srcTextureVk->GetFormat();
|
||||
VkFormat dstFormatVk = dstTextureVk->GetFormat();
|
||||
|
||||
if ((srcTextureVk->isDepth && !dstTextureVk->isDepth) ||
|
||||
!srcTextureVk->isDepth && dstTextureVk->isDepth)
|
||||
{
|
||||
// depth to color or
|
||||
// color to depth
|
||||
if (m_featureControl.mode.useBufferSurfaceCopies && vkIsBitCompatibleColorDepthFormat(srcFormatVk, dstFormatVk))
|
||||
surfaceCopy_viaBuffer(srcTextureVk, texSrcMip, texSrcSlice, dstTextureVk, texDstMip, texDstSlice, effectiveCopyWidth, effectiveCopyHeight);
|
||||
else
|
||||
surfaceCopy_viaDrawcall(srcTextureVk, texSrcMip, texSrcSlice, dstTextureVk, texDstMip, texDstSlice, effectiveCopyWidth, effectiveCopyHeight);
|
||||
}
|
||||
else
|
||||
{
|
||||
// depth to depth or
|
||||
// color to color
|
||||
if (m_featureControl.mode.useBufferSurfaceCopies && srcFormatVk == dstFormatVk)
|
||||
surfaceCopy_viaBuffer(srcTextureVk, texSrcMip, texSrcSlice, dstTextureVk, texDstMip, texDstSlice, effectiveCopyWidth, effectiveCopyHeight);
|
||||
else
|
||||
surfaceCopy_viaDrawcall(srcTextureVk, texSrcMip, texSrcSlice, dstTextureVk, texDstMip, texDstSlice, effectiveCopyWidth, effectiveCopyHeight);
|
||||
}
|
||||
surfaceCopy_viaDrawcall(srcTextureVk, texSrcMip, texSrcSlice, dstTextureVk, texDstMip, texDstSlice, effectiveCopyWidth, effectiveCopyHeight);
|
||||
}
|
||||
|
||||
// called whenever a texture is destroyed
|
||||
@ -944,9 +813,9 @@ void VulkanRenderer::surfaceCopy_notifyTextureRelease(LatteTextureVk* hostTextur
|
||||
{
|
||||
if (p)
|
||||
{
|
||||
VulkanRenderer::GetInstance()->releaseDestructibleObject(p->vkObjDescriptorSet);
|
||||
VulkanRenderer::GetInstance()->ReleaseDestructibleObject(p->vkObjDescriptorSet);
|
||||
p->vkObjDescriptorSet = nullptr;
|
||||
VulkanRenderer::GetInstance()->releaseDestructibleObject(p->vkObjImageView);
|
||||
VulkanRenderer::GetInstance()->ReleaseDestructibleObject(p->vkObjImageView);
|
||||
p->vkObjImageView = nullptr;
|
||||
}
|
||||
}
|
||||
@ -960,9 +829,9 @@ void VulkanRenderer::surfaceCopy_notifyTextureRelease(LatteTextureVk* hostTextur
|
||||
{
|
||||
if (p)
|
||||
{
|
||||
VulkanRenderer::GetInstance()->releaseDestructibleObject(p->vkObjFramebuffer);
|
||||
VulkanRenderer::GetInstance()->ReleaseDestructibleObject(p->vkObjFramebuffer);
|
||||
p->vkObjFramebuffer = nullptr;
|
||||
VulkanRenderer::GetInstance()->releaseDestructibleObject(p->vkObjImageView);
|
||||
VulkanRenderer::GetInstance()->ReleaseDestructibleObject(p->vkObjImageView);
|
||||
p->vkObjImageView = nullptr;
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
#include <util/helpers/helpers.h>
|
||||
#include "iosu_odm.h"
|
||||
#include "config/ActiveSettings.h"
|
||||
#include "Common/FileStream.h"
|
||||
@ -79,6 +80,7 @@ namespace iosu
|
||||
|
||||
void ODMServiceThread()
|
||||
{
|
||||
SetThreadName("ODMService");
|
||||
s_msgQueueId = IOS_CreateMessageQueue(_s_msgBuffer.GetPtr(), _s_msgBuffer.GetCount());
|
||||
cemu_assert(!IOS_ResultIsError((IOS_ERROR)s_msgQueueId));
|
||||
IOS_ERROR r = IOS_RegisterResourceManager(s_devicePath.c_str(), s_msgQueueId);
|
||||
|
@ -1,3 +1,4 @@
|
||||
#include <util/helpers/helpers.h>
|
||||
#include "iosu_pdm.h"
|
||||
#include "Cafe/CafeSystem.h"
|
||||
#include "config/ActiveSettings.h"
|
||||
@ -387,6 +388,7 @@ namespace iosu
|
||||
|
||||
void TimeTrackingThread(uint64 titleId)
|
||||
{
|
||||
SetThreadName("PlayDiaryThread");
|
||||
PlayStatsEntry* playStatsEntry = PlayStats_BeginNewTracking(titleId);
|
||||
|
||||
auto startTime = std::chrono::steady_clock::now();
|
||||
|
406
src/Cafe/IOSU/ccr_nfc/iosu_ccr_nfc.cpp
Normal file
406
src/Cafe/IOSU/ccr_nfc/iosu_ccr_nfc.cpp
Normal file
@ -0,0 +1,406 @@
|
||||
#include "iosu_ccr_nfc.h"
|
||||
#include "Cafe/IOSU/kernel/iosu_kernel.h"
|
||||
#include "util/crypto/aes128.h"
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/hmac.h>
|
||||
|
||||
namespace iosu
|
||||
{
|
||||
namespace ccr_nfc
|
||||
{
|
||||
IOSMsgQueueId sCCRNFCMsgQueue;
|
||||
SysAllocator<iosu::kernel::IOSMessage, 0x20> sCCRNFCMsgQueueMsgBuffer;
|
||||
std::thread sCCRNFCThread;
|
||||
|
||||
constexpr uint8 sNfcKey[] = { 0xC1, 0x2B, 0x07, 0x10, 0xD7, 0x2C, 0xEB, 0x5D, 0x43, 0x49, 0xB7, 0x43, 0xE3, 0xCA, 0xD2, 0x24 };
|
||||
constexpr uint8 sNfcKeyIV[] = { 0x4F, 0xD3, 0x9A, 0x6E, 0x79, 0xFC, 0xEA, 0xAD, 0x99, 0x90, 0x4D, 0xB8, 0xEE, 0x38, 0xE9, 0xDB };
|
||||
|
||||
constexpr uint8 sUnfixedInfosMagicBytes[] = { 0x00, 0x00, 0xDB, 0x4B, 0x9E, 0x3F, 0x45, 0x27, 0x8F, 0x39, 0x7E, 0xFF, 0x9B, 0x4F, 0xB9, 0x93 };
|
||||
constexpr uint8 sLockedSecretMagicBytes[] = { 0xFD, 0xC8, 0xA0, 0x76, 0x94, 0xB8, 0x9E, 0x4C, 0x47, 0xD3, 0x7D, 0xE8, 0xCE, 0x5C, 0x74, 0xC1 };
|
||||
constexpr uint8 sUnfixedInfosString[] = { 0x75, 0x6E, 0x66, 0x69, 0x78, 0x65, 0x64, 0x20, 0x69, 0x6E, 0x66, 0x6F, 0x73, 0x00, 0x00, 0x00 };
|
||||
constexpr uint8 sLockedSecretString[] = { 0x6C, 0x6F, 0x63, 0x6B, 0x65, 0x64, 0x20, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x00, 0x00, 0x00 };
|
||||
|
||||
constexpr uint8 sLockedSecretHmacKey[] = { 0x7F, 0x75, 0x2D, 0x28, 0x73, 0xA2, 0x00, 0x17, 0xFE, 0xF8, 0x5C, 0x05, 0x75, 0x90, 0x4B, 0x6D };
|
||||
constexpr uint8 sUnfixedInfosHmacKey[] = { 0x1D, 0x16, 0x4B, 0x37, 0x5B, 0x72, 0xA5, 0x57, 0x28, 0xB9, 0x1D, 0x64, 0xB6, 0xA3, 0xC2, 0x05 };
|
||||
|
||||
uint8 sLockedSecretInternalKey[0x10];
|
||||
uint8 sLockedSecretInternalNonce[0x10];
|
||||
uint8 sLockedSecretInternalHmacKey[0x10];
|
||||
|
||||
uint8 sUnfixedInfosInternalKey[0x10];
|
||||
uint8 sUnfixedInfosInternalNonce[0x10];
|
||||
uint8 sUnfixedInfosInternalHmacKey[0x10];
|
||||
|
||||
sint32 __CCRNFCValidateCryptData(CCRNFCCryptData* data, uint32 size, bool validateOffsets)
|
||||
{
|
||||
if (!data)
|
||||
{
|
||||
return CCR_NFC_ERROR;
|
||||
}
|
||||
|
||||
if (size != sizeof(CCRNFCCryptData))
|
||||
{
|
||||
return CCR_NFC_ERROR;
|
||||
}
|
||||
|
||||
if (!validateOffsets)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Make sure all offsets are within bounds
|
||||
if (data->version == 0)
|
||||
{
|
||||
if (data->unfixedInfosHmacOffset < 0x1C9 && data->unfixedInfosOffset < 0x1C9 &&
|
||||
data->lockedSecretHmacOffset < 0x1C9 && data->lockedSecretOffset < 0x1C9 &&
|
||||
data->lockedSecretSize < 0x1C9 && data->unfixedInfosSize < 0x1C9)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
else if (data->version == 2)
|
||||
{
|
||||
if (data->unfixedInfosHmacOffset < 0x21D && data->unfixedInfosOffset < 0x21D &&
|
||||
data->lockedSecretHmacOffset < 0x21D && data->lockedSecretOffset < 0x21D &&
|
||||
data->lockedSecretSize < 0x21D && data->unfixedInfosSize < 0x21D)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return CCR_NFC_ERROR;
|
||||
}
|
||||
|
||||
sint32 CCRNFCAESCTRCrypt(const uint8* key, const void* ivNonce, const void* inData, uint32 inSize, void* outData, uint32 outSize)
|
||||
{
|
||||
uint8 tmpIv[0x10];
|
||||
memcpy(tmpIv, ivNonce, sizeof(tmpIv));
|
||||
|
||||
memcpy(outData, inData, inSize);
|
||||
AES128CTR_transform((uint8*)outData, outSize, (uint8*)key, tmpIv);
|
||||
return 0;
|
||||
}
|
||||
|
||||
sint32 __CCRNFCGenerateKey(const uint8* hmacKey, uint32 hmacKeySize, const uint8* name, uint32 nameSize, const uint8* inData, uint32 inSize, uint8* outData, uint32 outSize)
|
||||
{
|
||||
if (nameSize != 0xe || outSize != 0x40)
|
||||
{
|
||||
return CCR_NFC_ERROR;
|
||||
}
|
||||
|
||||
// Create a buffer containing 2 counter bytes, the key name, and the key data
|
||||
uint8 buffer[0x50];
|
||||
buffer[0] = 0;
|
||||
buffer[1] = 0;
|
||||
memcpy(buffer + 2, name, nameSize);
|
||||
memcpy(buffer + nameSize + 2, inData, inSize);
|
||||
|
||||
uint16 counter = 0;
|
||||
while (outSize > 0)
|
||||
{
|
||||
// Set counter bytes and increment counter
|
||||
buffer[0] = (counter >> 8) & 0xFF;
|
||||
buffer[1] = counter & 0xFF;
|
||||
counter++;
|
||||
|
||||
uint32 dataSize = outSize;
|
||||
if (!HMAC(EVP_sha256(), hmacKey, hmacKeySize, buffer, sizeof(buffer), outData, &dataSize))
|
||||
{
|
||||
return CCR_NFC_ERROR;
|
||||
}
|
||||
|
||||
outSize -= 0x20;
|
||||
outData += 0x20;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
sint32 __CCRNFCGenerateInternalKeys(const CCRNFCCryptData* in, const uint8* keyGenSalt)
|
||||
{
|
||||
uint8 lockedSecretBuffer[0x40] = { 0 };
|
||||
uint8 unfixedInfosBuffer[0x40] = { 0 };
|
||||
uint8 outBuffer[0x40] = { 0 };
|
||||
|
||||
// Fill the locked secret buffer
|
||||
memcpy(lockedSecretBuffer, sLockedSecretMagicBytes, sizeof(sLockedSecretMagicBytes));
|
||||
if (in->version == 0)
|
||||
{
|
||||
// For Version 0 this is the 16-byte Format Info
|
||||
memcpy(lockedSecretBuffer + 0x10, in->data + in->uuidOffset, 0x10);
|
||||
}
|
||||
else if (in->version == 2)
|
||||
{
|
||||
// For Version 2 this is 2 times the 7-byte UID + 1 check byte
|
||||
memcpy(lockedSecretBuffer + 0x10, in->data + in->uuidOffset, 8);
|
||||
memcpy(lockedSecretBuffer + 0x18, in->data + in->uuidOffset, 8);
|
||||
}
|
||||
else
|
||||
{
|
||||
return CCR_NFC_ERROR;
|
||||
}
|
||||
// Append key generation salt
|
||||
memcpy(lockedSecretBuffer + 0x20, keyGenSalt, 0x20);
|
||||
|
||||
// Generate the key output
|
||||
sint32 res = __CCRNFCGenerateKey(sLockedSecretHmacKey, sizeof(sLockedSecretHmacKey), sLockedSecretString, 0xe, lockedSecretBuffer, sizeof(lockedSecretBuffer), outBuffer, sizeof(outBuffer));
|
||||
if (res != 0)
|
||||
{
|
||||
return res;
|
||||
}
|
||||
|
||||
// Unpack the key buffer
|
||||
memcpy(sLockedSecretInternalKey, outBuffer, 0x10);
|
||||
memcpy(sLockedSecretInternalNonce, outBuffer + 0x10, 0x10);
|
||||
memcpy(sLockedSecretInternalHmacKey, outBuffer + 0x20, 0x10);
|
||||
|
||||
// Fill the unfixed infos buffer
|
||||
memcpy(unfixedInfosBuffer, in->data + in->seedOffset, 2);
|
||||
memcpy(unfixedInfosBuffer + 2, sUnfixedInfosMagicBytes + 2, 0xe);
|
||||
if (in->version == 0)
|
||||
{
|
||||
// For Version 0 this is the 16-byte Format Info
|
||||
memcpy(unfixedInfosBuffer + 0x10, in->data + in->uuidOffset, 0x10);
|
||||
}
|
||||
else if (in->version == 2)
|
||||
{
|
||||
// For Version 2 this is 2 times the 7-byte UID + 1 check byte
|
||||
memcpy(unfixedInfosBuffer + 0x10, in->data + in->uuidOffset, 8);
|
||||
memcpy(unfixedInfosBuffer + 0x18, in->data + in->uuidOffset, 8);
|
||||
}
|
||||
else
|
||||
{
|
||||
return CCR_NFC_ERROR;
|
||||
}
|
||||
// Append key generation salt
|
||||
memcpy(unfixedInfosBuffer + 0x20, keyGenSalt, 0x20);
|
||||
|
||||
// Generate the key output
|
||||
res = __CCRNFCGenerateKey(sUnfixedInfosHmacKey, sizeof(sUnfixedInfosHmacKey), sUnfixedInfosString, 0xe, unfixedInfosBuffer, sizeof(unfixedInfosBuffer), outBuffer, sizeof(outBuffer));
|
||||
if (res != 0)
|
||||
{
|
||||
return res;
|
||||
}
|
||||
|
||||
// Unpack the key buffer
|
||||
memcpy(sUnfixedInfosInternalKey, outBuffer, 0x10);
|
||||
memcpy(sUnfixedInfosInternalNonce, outBuffer + 0x10, 0x10);
|
||||
memcpy(sUnfixedInfosInternalHmacKey, outBuffer + 0x20, 0x10);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
sint32 __CCRNFCCryptData(const CCRNFCCryptData* in, CCRNFCCryptData* out, bool decrypt)
|
||||
{
|
||||
// Decrypt key generation salt
|
||||
uint8 keyGenSalt[0x20];
|
||||
sint32 res = CCRNFCAESCTRCrypt(sNfcKey, sNfcKeyIV, in->data + in->keyGenSaltOffset, 0x20, keyGenSalt, sizeof(keyGenSalt));
|
||||
if (res != 0)
|
||||
{
|
||||
return res;
|
||||
}
|
||||
|
||||
// Prepare internal keys
|
||||
res = __CCRNFCGenerateInternalKeys(in, keyGenSalt);
|
||||
if (res != 0)
|
||||
{
|
||||
return res;
|
||||
}
|
||||
|
||||
if (decrypt)
|
||||
{
|
||||
// Only version 0 tags have an encrypted locked secret area
|
||||
if (in->version == 0)
|
||||
{
|
||||
res = CCRNFCAESCTRCrypt(sLockedSecretInternalKey, sLockedSecretInternalNonce, in->data + in->lockedSecretOffset, in->lockedSecretSize, out->data + in->lockedSecretOffset, in->lockedSecretSize);
|
||||
if (res != 0)
|
||||
{
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
// Decrypt unfxied infos
|
||||
res = CCRNFCAESCTRCrypt(sUnfixedInfosInternalKey, sUnfixedInfosInternalNonce, in->data + in->unfixedInfosOffset, in->unfixedInfosSize, out->data + in->unfixedInfosOffset, in->unfixedInfosSize);
|
||||
if (res != 0)
|
||||
{
|
||||
return res;
|
||||
}
|
||||
|
||||
// Verify HMACs
|
||||
uint8 hmacBuffer[0x20];
|
||||
uint32 hmacLen = sizeof(hmacBuffer);
|
||||
|
||||
if (!HMAC(EVP_sha256(), sLockedSecretInternalHmacKey, sizeof(sLockedSecretInternalHmacKey), out->data + in->lockedSecretHmacOffset + 0x20, (in->dataSize - in->lockedSecretHmacOffset) - 0x20, hmacBuffer, &hmacLen))
|
||||
{
|
||||
return CCR_NFC_ERROR;
|
||||
}
|
||||
|
||||
if (memcmp(in->data + in->lockedSecretHmacOffset, hmacBuffer, 0x20) != 0)
|
||||
{
|
||||
return CCR_NFC_INVALID_LOCKED_SECRET;
|
||||
}
|
||||
|
||||
if (in->version == 0)
|
||||
{
|
||||
hmacLen = sizeof(hmacBuffer);
|
||||
res = HMAC(EVP_sha256(), sUnfixedInfosInternalHmacKey, sizeof(sUnfixedInfosInternalHmacKey), out->data + in->unfixedInfosHmacOffset + 0x20, (in->dataSize - in->unfixedInfosHmacOffset) - 0x20, hmacBuffer, &hmacLen) ? 0 : CCR_NFC_ERROR;
|
||||
}
|
||||
else
|
||||
{
|
||||
hmacLen = sizeof(hmacBuffer);
|
||||
res = HMAC(EVP_sha256(), sUnfixedInfosInternalHmacKey, sizeof(sUnfixedInfosInternalHmacKey), out->data + in->unfixedInfosHmacOffset + 0x21, (in->dataSize - in->unfixedInfosHmacOffset) - 0x21, hmacBuffer, &hmacLen) ? 0 : CCR_NFC_ERROR;
|
||||
}
|
||||
|
||||
if (memcmp(in->data + in->unfixedInfosHmacOffset, hmacBuffer, 0x20) != 0)
|
||||
{
|
||||
return CCR_NFC_INVALID_UNFIXED_INFOS;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
uint8 hmacBuffer[0x20];
|
||||
uint32 hmacLen = sizeof(hmacBuffer);
|
||||
|
||||
if (!HMAC(EVP_sha256(), sLockedSecretInternalHmacKey, sizeof(sLockedSecretInternalHmacKey), out->data + in->lockedSecretHmacOffset + 0x20, (in->dataSize - in->lockedSecretHmacOffset) - 0x20, hmacBuffer, &hmacLen))
|
||||
{
|
||||
return CCR_NFC_ERROR;
|
||||
}
|
||||
|
||||
if (memcmp(in->data + in->lockedSecretHmacOffset, hmacBuffer, 0x20) != 0)
|
||||
{
|
||||
return CCR_NFC_INVALID_LOCKED_SECRET;
|
||||
}
|
||||
|
||||
// Only version 0 tags have an encrypted locked secret area
|
||||
if (in->version == 0)
|
||||
{
|
||||
uint32 hmacLen = 0x20;
|
||||
if (!HMAC(EVP_sha256(), sUnfixedInfosInternalHmacKey, sizeof(sUnfixedInfosInternalHmacKey), out->data + in->unfixedInfosHmacOffset + 0x20, (in->dataSize - in->unfixedInfosHmacOffset) - 0x20, out->data + in->unfixedInfosHmacOffset, &hmacLen))
|
||||
{
|
||||
return CCR_NFC_ERROR;
|
||||
}
|
||||
|
||||
res = CCRNFCAESCTRCrypt(sLockedSecretInternalKey, sLockedSecretInternalNonce, in->data + in->lockedSecretOffset, in->lockedSecretSize, out->data + in->lockedSecretOffset, in->lockedSecretSize);
|
||||
if (res != 0)
|
||||
{
|
||||
return res;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
uint32 hmacLen = 0x20;
|
||||
if (!HMAC(EVP_sha256(), sUnfixedInfosInternalHmacKey, sizeof(sUnfixedInfosInternalHmacKey), out->data + in->unfixedInfosHmacOffset + 0x21, (in->dataSize - in->unfixedInfosHmacOffset) - 0x21, out->data + in->unfixedInfosHmacOffset, &hmacLen))
|
||||
{
|
||||
return CCR_NFC_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
res = CCRNFCAESCTRCrypt(sUnfixedInfosInternalKey, sUnfixedInfosInternalNonce, in->data + in->unfixedInfosOffset, in->unfixedInfosSize, out->data + in->unfixedInfosOffset, in->unfixedInfosSize);
|
||||
if (res != 0)
|
||||
{
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
void CCRNFCThread()
|
||||
{
|
||||
iosu::kernel::IOSMessage msg;
|
||||
while (true)
|
||||
{
|
||||
IOS_ERROR error = iosu::kernel::IOS_ReceiveMessage(sCCRNFCMsgQueue, &msg, 0);
|
||||
cemu_assert(!IOS_ResultIsError(error));
|
||||
|
||||
// Check for system exit
|
||||
if (msg == 0xf00dd00d)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
IPCCommandBody* cmd = MEMPTR<IPCCommandBody>(msg).GetPtr();
|
||||
if (cmd->cmdId == IPCCommandId::IOS_OPEN)
|
||||
{
|
||||
iosu::kernel::IOS_ResourceReply(cmd, IOS_ERROR_OK);
|
||||
}
|
||||
else if (cmd->cmdId == IPCCommandId::IOS_CLOSE)
|
||||
{
|
||||
iosu::kernel::IOS_ResourceReply(cmd, IOS_ERROR_OK);
|
||||
}
|
||||
else if (cmd->cmdId == IPCCommandId::IOS_IOCTL)
|
||||
{
|
||||
sint32 result;
|
||||
uint32 requestId = cmd->args[0];
|
||||
void* ptrIn = MEMPTR<void>(cmd->args[1]);
|
||||
uint32 sizeIn = cmd->args[2];
|
||||
void* ptrOut = MEMPTR<void>(cmd->args[3]);
|
||||
uint32 sizeOut = cmd->args[4];
|
||||
|
||||
if ((result = __CCRNFCValidateCryptData(static_cast<CCRNFCCryptData*>(ptrIn), sizeIn, true)) == 0 &&
|
||||
(result = __CCRNFCValidateCryptData(static_cast<CCRNFCCryptData*>(ptrOut), sizeOut, false)) == 0)
|
||||
{
|
||||
// Initialize outData with inData
|
||||
memcpy(ptrOut, ptrIn, sizeIn);
|
||||
|
||||
switch (requestId)
|
||||
{
|
||||
case 1: // encrypt
|
||||
result = __CCRNFCCryptData(static_cast<CCRNFCCryptData*>(ptrIn), static_cast<CCRNFCCryptData*>(ptrOut), false);
|
||||
break;
|
||||
case 2: // decrypt
|
||||
result = __CCRNFCCryptData(static_cast<CCRNFCCryptData*>(ptrIn), static_cast<CCRNFCCryptData*>(ptrOut), true);
|
||||
break;
|
||||
default:
|
||||
cemuLog_log(LogType::Force, "/dev/ccr_nfc: Unsupported IOCTL requestId");
|
||||
cemu_assert_suspicious();
|
||||
result = IOS_ERROR_INVALID;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
iosu::kernel::IOS_ResourceReply(cmd, static_cast<IOS_ERROR>(result));
|
||||
}
|
||||
else
|
||||
{
|
||||
cemuLog_log(LogType::Force, "/dev/ccr_nfc: Unsupported IPC cmdId");
|
||||
cemu_assert_suspicious();
|
||||
iosu::kernel::IOS_ResourceReply(cmd, IOS_ERROR_INVALID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class : public ::IOSUModule
|
||||
{
|
||||
void SystemLaunch() override
|
||||
{
|
||||
sCCRNFCMsgQueue = iosu::kernel::IOS_CreateMessageQueue(sCCRNFCMsgQueueMsgBuffer.GetPtr(), sCCRNFCMsgQueueMsgBuffer.GetCount());
|
||||
cemu_assert(!IOS_ResultIsError(static_cast<IOS_ERROR>(sCCRNFCMsgQueue)));
|
||||
|
||||
IOS_ERROR error = iosu::kernel::IOS_RegisterResourceManager("/dev/ccr_nfc", sCCRNFCMsgQueue);
|
||||
cemu_assert(!IOS_ResultIsError(error));
|
||||
|
||||
sCCRNFCThread = std::thread(CCRNFCThread);
|
||||
}
|
||||
|
||||
void SystemExit() override
|
||||
{
|
||||
if (sCCRNFCMsgQueue < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
iosu::kernel::IOS_SendMessage(sCCRNFCMsgQueue, 0xf00dd00d, 0);
|
||||
sCCRNFCThread.join();
|
||||
|
||||
iosu::kernel::IOS_DestroyMessageQueue(sCCRNFCMsgQueue);
|
||||
sCCRNFCMsgQueue = -1;
|
||||
}
|
||||
} sIOSUModuleCCRNFC;
|
||||
|
||||
IOSUModule* GetModule()
|
||||
{
|
||||
return &sIOSUModuleCCRNFC;
|
||||
}
|
||||
}
|
||||
}
|
31
src/Cafe/IOSU/ccr_nfc/iosu_ccr_nfc.h
Normal file
31
src/Cafe/IOSU/ccr_nfc/iosu_ccr_nfc.h
Normal file
@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
#include "Cafe/IOSU/iosu_types_common.h"
|
||||
|
||||
#define CCR_NFC_ERROR (-0x2F001E)
|
||||
#define CCR_NFC_INVALID_LOCKED_SECRET (-0x2F0029)
|
||||
#define CCR_NFC_INVALID_UNFIXED_INFOS (-0x2F002A)
|
||||
|
||||
namespace iosu
|
||||
{
|
||||
namespace ccr_nfc
|
||||
{
|
||||
struct CCRNFCCryptData
|
||||
{
|
||||
uint32 version;
|
||||
uint32 dataSize;
|
||||
uint32 seedOffset;
|
||||
uint32 keyGenSaltOffset;
|
||||
uint32 uuidOffset;
|
||||
uint32 unfixedInfosOffset;
|
||||
uint32 unfixedInfosSize;
|
||||
uint32 lockedSecretOffset;
|
||||
uint32 lockedSecretSize;
|
||||
uint32 unfixedInfosHmacOffset;
|
||||
uint32 lockedSecretHmacOffset;
|
||||
uint8 data[540];
|
||||
};
|
||||
static_assert(sizeof(CCRNFCCryptData) == 0x248);
|
||||
|
||||
IOSUModule* GetModule();
|
||||
}
|
||||
}
|
@ -8,10 +8,19 @@
|
||||
#include "Cafe/OS/libs/nn_acp/nn_acp.h"
|
||||
#include "Cafe/OS/libs/coreinit/coreinit_FS.h"
|
||||
#include "Cafe/Filesystem/fsc.h"
|
||||
#include "Cafe/HW/Espresso/PPCState.h"
|
||||
//#include "Cafe/HW/Espresso/PPCState.h"
|
||||
|
||||
#include "Cafe/IOSU/iosu_types_common.h"
|
||||
#include "Cafe/IOSU/nn/iosu_nn_service.h"
|
||||
|
||||
#include "Cafe/IOSU/legacy/iosu_act.h"
|
||||
#include "Cafe/CafeSystem.h"
|
||||
#include "config/ActiveSettings.h"
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
using ACPDeviceType = iosu::acp::ACPDeviceType;
|
||||
|
||||
static_assert(sizeof(acpMetaXml_t) == 0x3440);
|
||||
static_assert(offsetof(acpMetaXml_t, title_id) == 0x0000);
|
||||
static_assert(offsetof(acpMetaXml_t, boss_id) == 0x0008);
|
||||
@ -506,48 +515,6 @@ namespace iosu
|
||||
return 0;
|
||||
}
|
||||
|
||||
sint32 ACPCreateSaveDirEx(uint8 accountSlot, uint64 titleId)
|
||||
{
|
||||
uint32 persistentId = 0;
|
||||
nn::save::GetPersistentIdEx(accountSlot, &persistentId);
|
||||
|
||||
uint32 high = GetTitleIdHigh(titleId) & (~0xC);
|
||||
uint32 low = GetTitleIdLow(titleId);
|
||||
|
||||
sint32 fscStatus = FSC_STATUS_FILE_NOT_FOUND;
|
||||
char path[256];
|
||||
|
||||
sprintf(path, "%susr/boss/", "/vol/storage_mlc01/");
|
||||
fsc_createDir(path, &fscStatus);
|
||||
sprintf(path, "%susr/boss/%08x/", "/vol/storage_mlc01/", high);
|
||||
fsc_createDir(path, &fscStatus);
|
||||
sprintf(path, "%susr/boss/%08x/%08x/", "/vol/storage_mlc01/", high, low);
|
||||
fsc_createDir(path, &fscStatus);
|
||||
sprintf(path, "%susr/boss/%08x/%08x/user/", "/vol/storage_mlc01/", high, low);
|
||||
fsc_createDir(path, &fscStatus);
|
||||
sprintf(path, "%susr/boss/%08x/%08x/user/common", "/vol/storage_mlc01/", high, low);
|
||||
fsc_createDir(path, &fscStatus);
|
||||
sprintf(path, "%susr/boss/%08x/%08x/user/%08x/", "/vol/storage_mlc01/", high, low, persistentId == 0 ? 0x80000001 : persistentId);
|
||||
fsc_createDir(path, &fscStatus);
|
||||
|
||||
sprintf(path, "%susr/save/%08x/", "/vol/storage_mlc01/", high);
|
||||
fsc_createDir(path, &fscStatus);
|
||||
sprintf(path, "%susr/save/%08x/%08x/", "/vol/storage_mlc01/", high, low);
|
||||
fsc_createDir(path, &fscStatus);
|
||||
sprintf(path, "%susr/save/%08x/%08x/meta/", "/vol/storage_mlc01/", high, low);
|
||||
fsc_createDir(path, &fscStatus);
|
||||
sprintf(path, "%susr/save/%08x/%08x/user/", "/vol/storage_mlc01/", high, low);
|
||||
fsc_createDir(path, &fscStatus);
|
||||
sprintf(path, "%susr/save/%08x/%08x/user/common", "/vol/storage_mlc01/", high, low);
|
||||
fsc_createDir(path, &fscStatus);
|
||||
sprintf(path, "%susr/save/%08x/%08x/user/%08x", "/vol/storage_mlc01/", high, low, persistentId == 0 ? 0x80000001 : persistentId);
|
||||
fsc_createDir(path, &fscStatus);
|
||||
|
||||
// copy xml meta files
|
||||
nn::acp::CreateSaveMetaFiles(persistentId, titleId);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int iosuAcp_thread()
|
||||
{
|
||||
SetThreadName("iosuAcp_thread");
|
||||
@ -584,7 +551,7 @@ namespace iosu
|
||||
}
|
||||
else if (acpCemuRequest->requestCode == IOSU_ACP_CREATE_SAVE_DIR_EX)
|
||||
{
|
||||
acpCemuRequest->returnCode = ACPCreateSaveDirEx(acpCemuRequest->accountSlot, acpCemuRequest->titleId);
|
||||
acpCemuRequest->returnCode = acp::ACPCreateSaveDirEx(acpCemuRequest->accountSlot, acpCemuRequest->titleId);
|
||||
}
|
||||
else
|
||||
cemu_assert_unimplemented();
|
||||
@ -610,5 +577,237 @@ namespace iosu
|
||||
return iosuAcp.isInitialized;
|
||||
}
|
||||
|
||||
/* Above is the legacy implementation. Below is the new style implementation which also matches the official IPC protocol and works with the real nn_acp.rpl */
|
||||
|
||||
}
|
||||
namespace acp
|
||||
{
|
||||
|
||||
uint64 _ACPGetTimestamp()
|
||||
{
|
||||
return coreinit::coreinit_getOSTime() / ESPRESSO_TIMER_CLOCK;
|
||||
}
|
||||
|
||||
nnResult ACPUpdateSaveTimeStamp(uint32 persistentId, uint64 titleId, ACPDeviceType deviceType)
|
||||
{
|
||||
if (deviceType == ACPDeviceType::UnknownType)
|
||||
{
|
||||
return (nnResult)0xA030FB80;
|
||||
}
|
||||
|
||||
// create or modify the saveinfo
|
||||
const auto saveinfoPath = ActiveSettings::GetMlcPath("usr/save/{:08x}/{:08x}/meta/saveinfo.xml", GetTitleIdHigh(titleId), GetTitleIdLow(titleId));
|
||||
auto saveinfoData = FileStream::LoadIntoMemory(saveinfoPath);
|
||||
if (saveinfoData && !saveinfoData->empty())
|
||||
{
|
||||
namespace xml = tinyxml2;
|
||||
xml::XMLDocument doc;
|
||||
tinyxml2::XMLError xmlError = doc.Parse((const char*)saveinfoData->data(), saveinfoData->size());
|
||||
if (xmlError == xml::XML_SUCCESS || xmlError == xml::XML_ERROR_EMPTY_DOCUMENT)
|
||||
{
|
||||
xml::XMLNode* child = doc.FirstChild();
|
||||
// check for declaration -> <?xml version="1.0" encoding="utf-8"?>
|
||||
if (!child || !child->ToDeclaration())
|
||||
{
|
||||
xml::XMLDeclaration* decl = doc.NewDeclaration();
|
||||
doc.InsertFirstChild(decl);
|
||||
}
|
||||
|
||||
xml::XMLElement* info = doc.FirstChildElement("info");
|
||||
if (!info)
|
||||
{
|
||||
info = doc.NewElement("info");
|
||||
doc.InsertEndChild(info);
|
||||
}
|
||||
|
||||
// find node with persistentId
|
||||
char tmp[64];
|
||||
sprintf(tmp, "%08x", persistentId);
|
||||
bool foundNode = false;
|
||||
for (xml::XMLElement* account = info->FirstChildElement("account"); account; account = account->NextSiblingElement("account"))
|
||||
{
|
||||
if (account->Attribute("persistentId", tmp))
|
||||
{
|
||||
// found the entry! -> update timestamp
|
||||
xml::XMLElement* timestamp = account->FirstChildElement("timestamp");
|
||||
sprintf(tmp, "%" PRIx64, _ACPGetTimestamp());
|
||||
if (timestamp)
|
||||
timestamp->SetText(tmp);
|
||||
else
|
||||
{
|
||||
timestamp = doc.NewElement("timestamp");
|
||||
account->InsertFirstChild(timestamp);
|
||||
}
|
||||
|
||||
foundNode = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundNode)
|
||||
{
|
||||
tinyxml2::XMLElement* account = doc.NewElement("account");
|
||||
{
|
||||
sprintf(tmp, "%08x", persistentId);
|
||||
account->SetAttribute("persistentId", tmp);
|
||||
|
||||
tinyxml2::XMLElement* timestamp = doc.NewElement("timestamp");
|
||||
{
|
||||
sprintf(tmp, "%" PRIx64, _ACPGetTimestamp());
|
||||
timestamp->SetText(tmp);
|
||||
}
|
||||
|
||||
account->InsertFirstChild(timestamp);
|
||||
}
|
||||
|
||||
info->InsertFirstChild(account);
|
||||
}
|
||||
|
||||
// update file
|
||||
tinyxml2::XMLPrinter printer;
|
||||
doc.Print(&printer);
|
||||
FileStream* fs = FileStream::createFile2(saveinfoPath);
|
||||
if (fs)
|
||||
{
|
||||
fs->writeString(printer.CStr());
|
||||
delete fs;
|
||||
}
|
||||
}
|
||||
}
|
||||
return NN_RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
void CreateSaveMetaFiles(uint32 persistentId, uint64 titleId)
|
||||
{
|
||||
std::string titlePath = CafeSystem::GetMlcStoragePath(CafeSystem::GetForegroundTitleId());
|
||||
|
||||
sint32 fscStatus;
|
||||
FSCVirtualFile* fscFile = fsc_open((titlePath + "/meta/meta.xml").c_str(), FSC_ACCESS_FLAG::OPEN_FILE | FSC_ACCESS_FLAG::READ_PERMISSION, &fscStatus);
|
||||
if (fscFile)
|
||||
{
|
||||
sint32 fileSize = fsc_getFileSize(fscFile);
|
||||
|
||||
std::unique_ptr<uint8[]> fileContent = std::make_unique<uint8[]>(fileSize);
|
||||
fsc_readFile(fscFile, fileContent.get(), fileSize);
|
||||
fsc_close(fscFile);
|
||||
|
||||
const auto outPath = ActiveSettings::GetMlcPath("usr/save/{:08x}/{:08x}/meta/meta.xml", GetTitleIdHigh(titleId), GetTitleIdLow(titleId));
|
||||
|
||||
std::ofstream myFile(outPath, std::ios::out | std::ios::binary);
|
||||
myFile.write((char*)fileContent.get(), fileSize);
|
||||
myFile.close();
|
||||
}
|
||||
|
||||
fscFile = fsc_open((titlePath + "/meta/iconTex.tga").c_str(), FSC_ACCESS_FLAG::OPEN_FILE | FSC_ACCESS_FLAG::READ_PERMISSION, &fscStatus);
|
||||
if (fscFile)
|
||||
{
|
||||
sint32 fileSize = fsc_getFileSize(fscFile);
|
||||
|
||||
std::unique_ptr<uint8[]> fileContent = std::make_unique<uint8[]>(fileSize);
|
||||
fsc_readFile(fscFile, fileContent.get(), fileSize);
|
||||
fsc_close(fscFile);
|
||||
|
||||
const auto outPath = ActiveSettings::GetMlcPath("usr/save/{:08x}/{:08x}/meta/iconTex.tga", GetTitleIdHigh(titleId), GetTitleIdLow(titleId));
|
||||
|
||||
std::ofstream myFile(outPath, std::ios::out | std::ios::binary);
|
||||
myFile.write((char*)fileContent.get(), fileSize);
|
||||
myFile.close();
|
||||
}
|
||||
|
||||
ACPUpdateSaveTimeStamp(persistentId, titleId, iosu::acp::ACPDeviceType::InternalDeviceType);
|
||||
}
|
||||
|
||||
|
||||
sint32 _ACPCreateSaveDir(uint32 persistentId, uint64 titleId, ACPDeviceType type)
|
||||
{
|
||||
uint32 high = GetTitleIdHigh(titleId) & (~0xC);
|
||||
uint32 low = GetTitleIdLow(titleId);
|
||||
|
||||
sint32 fscStatus = FSC_STATUS_FILE_NOT_FOUND;
|
||||
char path[256];
|
||||
|
||||
sprintf(path, "%susr/boss/", "/vol/storage_mlc01/");
|
||||
fsc_createDir(path, &fscStatus);
|
||||
sprintf(path, "%susr/boss/%08x/", "/vol/storage_mlc01/", high);
|
||||
fsc_createDir(path, &fscStatus);
|
||||
sprintf(path, "%susr/boss/%08x/%08x/", "/vol/storage_mlc01/", high, low);
|
||||
fsc_createDir(path, &fscStatus);
|
||||
sprintf(path, "%susr/boss/%08x/%08x/user/", "/vol/storage_mlc01/", high, low);
|
||||
fsc_createDir(path, &fscStatus);
|
||||
sprintf(path, "%susr/boss/%08x/%08x/user/common", "/vol/storage_mlc01/", high, low);
|
||||
fsc_createDir(path, &fscStatus);
|
||||
sprintf(path, "%susr/boss/%08x/%08x/user/%08x/", "/vol/storage_mlc01/", high, low, persistentId == 0 ? 0x80000001 : persistentId);
|
||||
fsc_createDir(path, &fscStatus);
|
||||
|
||||
sprintf(path, "%susr/save/%08x/", "/vol/storage_mlc01/", high);
|
||||
fsc_createDir(path, &fscStatus);
|
||||
sprintf(path, "%susr/save/%08x/%08x/", "/vol/storage_mlc01/", high, low);
|
||||
fsc_createDir(path, &fscStatus);
|
||||
sprintf(path, "%susr/save/%08x/%08x/meta/", "/vol/storage_mlc01/", high, low);
|
||||
fsc_createDir(path, &fscStatus);
|
||||
sprintf(path, "%susr/save/%08x/%08x/user/", "/vol/storage_mlc01/", high, low);
|
||||
fsc_createDir(path, &fscStatus);
|
||||
sprintf(path, "%susr/save/%08x/%08x/user/common", "/vol/storage_mlc01/", high, low);
|
||||
fsc_createDir(path, &fscStatus);
|
||||
sprintf(path, "%susr/save/%08x/%08x/user/%08x", "/vol/storage_mlc01/", high, low, persistentId == 0 ? 0x80000001 : persistentId);
|
||||
fsc_createDir(path, &fscStatus);
|
||||
|
||||
// copy xml meta files
|
||||
CreateSaveMetaFiles(persistentId, titleId);
|
||||
return 0;
|
||||
}
|
||||
|
||||
nnResult ACPCreateSaveDir(uint32 persistentId, ACPDeviceType type)
|
||||
{
|
||||
uint64 titleId = CafeSystem::GetForegroundTitleId();
|
||||
return _ACPCreateSaveDir(persistentId, titleId, type);
|
||||
}
|
||||
|
||||
sint32 ACPCreateSaveDirEx(uint8 accountSlot, uint64 titleId)
|
||||
{
|
||||
uint32 persistentId = 0;
|
||||
cemu_assert_debug(accountSlot >= 1 && accountSlot <= 13); // outside valid slot range?
|
||||
bool r = iosu::act::GetPersistentId(accountSlot, &persistentId);
|
||||
cemu_assert_debug(r);
|
||||
return _ACPCreateSaveDir(persistentId, titleId, ACPDeviceType::InternalDeviceType);
|
||||
}
|
||||
|
||||
nnResult ACPGetOlvAccesskey(uint32be* accessKey)
|
||||
{
|
||||
*accessKey = CafeSystem::GetForegroundTitleOlvAccesskey();
|
||||
return 0;
|
||||
}
|
||||
|
||||
class AcpMainService : public iosu::nn::IPCService
|
||||
{
|
||||
public:
|
||||
AcpMainService() : iosu::nn::IPCService("/dev/acp_main") {}
|
||||
|
||||
nnResult ServiceCall(uint32 serviceId, void* request, void* response) override
|
||||
{
|
||||
cemuLog_log(LogType::Force, "Unsupported service call to /dev/acp_main");
|
||||
cemu_assert_unimplemented();
|
||||
return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_ACP, 0);
|
||||
}
|
||||
};
|
||||
|
||||
AcpMainService gACPMainService;
|
||||
|
||||
class : public ::IOSUModule
|
||||
{
|
||||
void TitleStart() override
|
||||
{
|
||||
gACPMainService.Start();
|
||||
// gACPMainService.SetTimerUpdate(1000); // call TimerUpdate() once a second
|
||||
}
|
||||
void TitleStop() override
|
||||
{
|
||||
gACPMainService.Stop();
|
||||
}
|
||||
}sIOSUModuleNNACP;
|
||||
|
||||
IOSUModule* GetModule()
|
||||
{
|
||||
return static_cast<IOSUModule*>(&sIOSUModuleNNACP);
|
||||
}
|
||||
} // namespace acp
|
||||
} // namespace iosu
|
||||
|
@ -1,5 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include "Cafe/IOSU/iosu_types_common.h"
|
||||
#include "Cafe/OS/libs/nn_common.h" // for nnResult
|
||||
|
||||
typedef struct
|
||||
{
|
||||
/* +0x0000 */ uint64 title_id; // parsed via GetHex64
|
||||
@ -192,4 +195,24 @@ typedef struct
|
||||
namespace iosu
|
||||
{
|
||||
void iosuAcp_init();
|
||||
|
||||
namespace acp
|
||||
{
|
||||
enum ACPDeviceType
|
||||
{
|
||||
UnknownType = 0,
|
||||
InternalDeviceType = 1,
|
||||
USBDeviceType = 3,
|
||||
};
|
||||
|
||||
class IOSUModule* GetModule();
|
||||
|
||||
void CreateSaveMetaFiles(uint32 persistentId, uint64 titleId);
|
||||
nnResult ACPUpdateSaveTimeStamp(uint32 persistentId, uint64 titleId, ACPDeviceType deviceType);
|
||||
|
||||
nnResult ACPCreateSaveDir(uint32 persistentId, ACPDeviceType type);
|
||||
sint32 ACPCreateSaveDirEx(uint8 accountSlot, uint64 titleId);
|
||||
nnResult ACPGetOlvAccesskey(uint32be* accessKey);
|
||||
}
|
||||
|
||||
}
|
@ -20,14 +20,18 @@
|
||||
|
||||
using namespace iosu::kernel;
|
||||
|
||||
using NexToken = NAPI::ACTNexToken;
|
||||
static_assert(sizeof(NexToken) == 0x25C);
|
||||
|
||||
struct
|
||||
{
|
||||
bool isInitialized;
|
||||
std::mutex actMutex;
|
||||
}iosuAct = { };
|
||||
|
||||
// account manager
|
||||
|
||||
typedef struct
|
||||
struct actAccountData_t
|
||||
{
|
||||
bool isValid;
|
||||
// options
|
||||
@ -48,7 +52,12 @@ typedef struct
|
||||
// Mii
|
||||
FFLData_t miiData;
|
||||
uint16le miiNickname[ACT_NICKNAME_LENGTH];
|
||||
}actAccountData_t;
|
||||
|
||||
bool IsNetworkAccount() const
|
||||
{
|
||||
return isNetworkAccount; // todo - IOSU only checks if accountId is not empty?
|
||||
}
|
||||
};
|
||||
|
||||
#define IOSU_ACT_ACCOUNT_MAX_COUNT (0xC)
|
||||
|
||||
@ -158,149 +167,11 @@ uint32 iosuAct_getAccountIdOfCurrentAccount()
|
||||
|
||||
// IOSU act API interface
|
||||
|
||||
namespace iosu
|
||||
{
|
||||
namespace act
|
||||
{
|
||||
uint8 getCurrentAccountSlot()
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
bool getPrincipalId(uint8 slot, uint32* principalId)
|
||||
{
|
||||
sint32 accountIndex = iosuAct_getAccountIndexBySlot(slot);
|
||||
if (_actAccountData[accountIndex].isValid == false)
|
||||
{
|
||||
*principalId = 0;
|
||||
return false;
|
||||
}
|
||||
*principalId = _actAccountData[accountIndex].principalId;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool getAccountId(uint8 slot, char* accountId)
|
||||
{
|
||||
sint32 accountIndex = iosuAct_getAccountIndexBySlot(slot);
|
||||
if (_actAccountData[accountIndex].isValid == false)
|
||||
{
|
||||
*accountId = '\0';
|
||||
return false;
|
||||
}
|
||||
strcpy(accountId, _actAccountData[accountIndex].accountId);
|
||||
return true;
|
||||
}
|
||||
|
||||
// returns empty string if invalid
|
||||
std::string getAccountId2(uint8 slot)
|
||||
{
|
||||
sint32 accountIndex = iosuAct_getAccountIndexBySlot(slot);
|
||||
if (_actAccountData[accountIndex].isValid == false)
|
||||
return {};
|
||||
return {_actAccountData[accountIndex].accountId};
|
||||
}
|
||||
|
||||
bool getMii(uint8 slot, FFLData_t* fflData)
|
||||
{
|
||||
sint32 accountIndex = iosuAct_getAccountIndexBySlot(slot);
|
||||
if (_actAccountData[accountIndex].isValid == false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
memcpy(fflData, &_actAccountData[accountIndex].miiData, sizeof(FFLData_t));
|
||||
return true;
|
||||
}
|
||||
|
||||
// return screenname in little-endian wide characters
|
||||
bool getScreenname(uint8 slot, uint16 screenname[ACT_NICKNAME_LENGTH])
|
||||
{
|
||||
sint32 accountIndex = iosuAct_getAccountIndexBySlot(slot);
|
||||
if (_actAccountData[accountIndex].isValid == false)
|
||||
{
|
||||
screenname[0] = '\0';
|
||||
return false;
|
||||
}
|
||||
for (sint32 i = 0; i < ACT_NICKNAME_LENGTH; i++)
|
||||
{
|
||||
screenname[i] = (uint16)_actAccountData[accountIndex].miiNickname[i];
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool getCountryIndex(uint8 slot, uint32* countryIndex)
|
||||
{
|
||||
sint32 accountIndex = iosuAct_getAccountIndexBySlot(slot);
|
||||
if (_actAccountData[accountIndex].isValid == false)
|
||||
{
|
||||
*countryIndex = 0;
|
||||
return false;
|
||||
}
|
||||
*countryIndex = _actAccountData[accountIndex].countryIndex;
|
||||
return true;
|
||||
}
|
||||
|
||||
class ActService : public iosu::nn::IPCService
|
||||
{
|
||||
public:
|
||||
ActService() : iosu::nn::IPCService("/dev/act") {}
|
||||
|
||||
nnResult ServiceCall(uint32 serviceId, void* request, void* response) override
|
||||
{
|
||||
cemuLog_log(LogType::Force, "Unsupported service call to /dev/act");
|
||||
cemu_assert_unimplemented();
|
||||
return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_ACT, 0);
|
||||
}
|
||||
};
|
||||
|
||||
ActService gActService;
|
||||
|
||||
void Initialize()
|
||||
{
|
||||
gActService.Start();
|
||||
}
|
||||
|
||||
void Stop()
|
||||
{
|
||||
gActService.Stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// IOSU act IO
|
||||
|
||||
typedef struct
|
||||
{
|
||||
/* +0x00 */ uint32be ukn00;
|
||||
/* +0x04 */ uint32be ukn04;
|
||||
/* +0x08 */ uint32be ukn08;
|
||||
/* +0x0C */ uint32be subcommandCode;
|
||||
/* +0x10 */ uint8 ukn10;
|
||||
/* +0x11 */ uint8 ukn11;
|
||||
/* +0x12 */ uint8 ukn12;
|
||||
/* +0x13 */ uint8 accountSlot;
|
||||
/* +0x14 */ uint32be unique; // is this command specific?
|
||||
}cmdActRequest00_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint32be returnCode;
|
||||
uint8 transferableIdBase[8];
|
||||
}cmdActGetTransferableIDResult_t;
|
||||
|
||||
#define ACT_SUBCMD_GET_TRANSFERABLE_ID 4
|
||||
#define ACT_SUBCMD_INITIALIZE 0x14
|
||||
|
||||
#define _cancelIfAccountDoesNotExist() \
|
||||
if (_actAccountData[accountIndex].isValid == false) \
|
||||
{ \
|
||||
/* account does not exist*/ \
|
||||
ioctlReturnValue = 0; \
|
||||
actCemuRequest->setACTReturnCode(BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_ACT, NN_ACT_RESULT_ACCOUNT_DOES_NOT_EXIST)); /* 0xA071F480 */ \
|
||||
actCemuRequest->resultU64.u64 = 0; \
|
||||
iosuIoctl_completeRequest(ioQueueEntry, ioctlReturnValue); \
|
||||
continue; \
|
||||
}
|
||||
static const auto ACTResult_Ok = 0;
|
||||
static const auto ACTResult_InvalidValue = BUILD_NN_RESULT(NN_RESULT_LEVEL_LVL6, NN_RESULT_MODULE_NN_ACT, 0x12F00); // 0xC0712F00
|
||||
static const auto ACTResult_OutOfRange = BUILD_NN_RESULT(NN_RESULT_LEVEL_LVL6, NN_RESULT_MODULE_NN_ACT, 0x12D80); // 0xC0712D80
|
||||
static const auto ACTResult_AccountDoesNotExist = BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_ACT, NN_ACT_RESULT_ACCOUNT_DOES_NOT_EXIST); // 0xA071F480
|
||||
static const auto ACTResult_NotANetworkAccount = BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_ACT, 0x1FE80); // 0xA071FE80
|
||||
|
||||
nnResult ServerActErrorCodeToNNResult(NAPI::ACT_ERROR_CODE ec)
|
||||
{
|
||||
@ -505,6 +376,291 @@ nnResult ServerActErrorCodeToNNResult(NAPI::ACT_ERROR_CODE ec)
|
||||
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, NN_ERROR_CODE::ACT_UNKNOWN_SERVER_ERROR);
|
||||
}
|
||||
|
||||
namespace iosu
|
||||
{
|
||||
namespace act
|
||||
{
|
||||
uint8 getCurrentAccountSlot()
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
actAccountData_t* GetAccountBySlotNo(uint8 slotNo)
|
||||
{
|
||||
// only call this while holding actMutex
|
||||
uint8 accIndex;
|
||||
if(slotNo == iosu::act::ACT_SLOT_CURRENT)
|
||||
{
|
||||
accIndex = getCurrentAccountSlot() - 1;
|
||||
cemu_assert_debug(accIndex >= 0 && accIndex < IOSU_ACT_ACCOUNT_MAX_COUNT);
|
||||
}
|
||||
else if(slotNo > 0 && slotNo <= IOSU_ACT_ACCOUNT_MAX_COUNT)
|
||||
accIndex = slotNo - 1;
|
||||
else
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
if(!_actAccountData[accIndex].isValid)
|
||||
return nullptr;
|
||||
return &_actAccountData[accIndex];
|
||||
}
|
||||
|
||||
// has ownership of account data
|
||||
// while any thread has a LockedAccount in non-null state no other thread can access the account data
|
||||
class LockedAccount
|
||||
{
|
||||
public:
|
||||
LockedAccount(uint8 slotNo)
|
||||
{
|
||||
iosuAct.actMutex.lock();
|
||||
m_account = GetAccountBySlotNo(slotNo);
|
||||
if(!m_account)
|
||||
iosuAct.actMutex.unlock();
|
||||
}
|
||||
|
||||
~LockedAccount()
|
||||
{
|
||||
if(m_account)
|
||||
iosuAct.actMutex.unlock();
|
||||
}
|
||||
|
||||
void Release()
|
||||
{
|
||||
if(m_account)
|
||||
iosuAct.actMutex.unlock();
|
||||
m_account = nullptr;
|
||||
}
|
||||
|
||||
actAccountData_t* operator->()
|
||||
{
|
||||
return m_account;
|
||||
}
|
||||
|
||||
actAccountData_t& operator*()
|
||||
{
|
||||
return *m_account;
|
||||
}
|
||||
|
||||
LockedAccount(const LockedAccount&) = delete;
|
||||
LockedAccount& operator=(const LockedAccount&) = delete;
|
||||
|
||||
operator bool() const { return m_account != nullptr; }
|
||||
|
||||
private:
|
||||
actAccountData_t* m_account{nullptr};
|
||||
};
|
||||
|
||||
bool getPrincipalId(uint8 slot, uint32* principalId)
|
||||
{
|
||||
sint32 accountIndex = iosuAct_getAccountIndexBySlot(slot);
|
||||
if (_actAccountData[accountIndex].isValid == false)
|
||||
{
|
||||
*principalId = 0;
|
||||
return false;
|
||||
}
|
||||
*principalId = _actAccountData[accountIndex].principalId;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool getAccountId(uint8 slot, char* accountId)
|
||||
{
|
||||
sint32 accountIndex = iosuAct_getAccountIndexBySlot(slot);
|
||||
if (_actAccountData[accountIndex].isValid == false)
|
||||
{
|
||||
*accountId = '\0';
|
||||
return false;
|
||||
}
|
||||
strcpy(accountId, _actAccountData[accountIndex].accountId);
|
||||
return true;
|
||||
}
|
||||
|
||||
// returns empty string if invalid
|
||||
std::string getAccountId2(uint8 slot)
|
||||
{
|
||||
sint32 accountIndex = iosuAct_getAccountIndexBySlot(slot);
|
||||
if (_actAccountData[accountIndex].isValid == false)
|
||||
return {};
|
||||
return {_actAccountData[accountIndex].accountId};
|
||||
}
|
||||
|
||||
bool getMii(uint8 slot, FFLData_t* fflData)
|
||||
{
|
||||
sint32 accountIndex = iosuAct_getAccountIndexBySlot(slot);
|
||||
if (_actAccountData[accountIndex].isValid == false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
memcpy(fflData, &_actAccountData[accountIndex].miiData, sizeof(FFLData_t));
|
||||
return true;
|
||||
}
|
||||
|
||||
// return screenname in little-endian wide characters
|
||||
bool getScreenname(uint8 slot, uint16 screenname[ACT_NICKNAME_LENGTH])
|
||||
{
|
||||
sint32 accountIndex = iosuAct_getAccountIndexBySlot(slot);
|
||||
if (_actAccountData[accountIndex].isValid == false)
|
||||
{
|
||||
screenname[0] = '\0';
|
||||
return false;
|
||||
}
|
||||
for (sint32 i = 0; i < ACT_NICKNAME_LENGTH; i++)
|
||||
{
|
||||
screenname[i] = (uint16)_actAccountData[accountIndex].miiNickname[i];
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool getCountryIndex(uint8 slot, uint32* countryIndex)
|
||||
{
|
||||
sint32 accountIndex = iosuAct_getAccountIndexBySlot(slot);
|
||||
if (_actAccountData[accountIndex].isValid == false)
|
||||
{
|
||||
*countryIndex = 0;
|
||||
return false;
|
||||
}
|
||||
*countryIndex = _actAccountData[accountIndex].countryIndex;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GetPersistentId(uint8 slot, uint32* persistentId)
|
||||
{
|
||||
sint32 accountIndex = iosuAct_getAccountIndexBySlot(slot);
|
||||
if(!_actAccountData[accountIndex].isValid)
|
||||
{
|
||||
*persistentId = 0;
|
||||
return false;
|
||||
}
|
||||
*persistentId = _actAccountData[accountIndex].persistentId;
|
||||
return true;
|
||||
}
|
||||
|
||||
nnResult AcquireNexToken(uint8 accountSlot, uint64 titleId, uint16 titleVersion, uint32 serverId, uint8* tokenOut, uint32 tokenLen)
|
||||
{
|
||||
if (accountSlot != ACT_SLOT_CURRENT)
|
||||
return ACTResult_InvalidValue;
|
||||
LockedAccount account(accountSlot);
|
||||
if (!account)
|
||||
return ACTResult_AccountDoesNotExist;
|
||||
if (!account->IsNetworkAccount())
|
||||
return ACTResult_NotANetworkAccount;
|
||||
cemu_assert_debug(ActiveSettings::IsOnlineEnabled());
|
||||
if (tokenLen != sizeof(NexToken))
|
||||
return ACTResult_OutOfRange;
|
||||
|
||||
NAPI::AuthInfo authInfo;
|
||||
NAPI::NAPI_MakeAuthInfoFromCurrentAccount(authInfo);
|
||||
NAPI::ACTGetNexTokenResult nexTokenResult = NAPI::ACT_GetNexToken_WithCache(authInfo, titleId, titleVersion, serverId);
|
||||
if (nexTokenResult.isValid())
|
||||
{
|
||||
memcpy(tokenOut, &nexTokenResult.nexToken, sizeof(NexToken));
|
||||
return ACTResult_Ok;
|
||||
}
|
||||
else if (nexTokenResult.apiError == NAPI_RESULT::SERVICE_ERROR)
|
||||
{
|
||||
nnResult returnCode = ServerActErrorCodeToNNResult(nexTokenResult.serviceError);
|
||||
cemu_assert_debug((returnCode&0x80000000) != 0);
|
||||
return returnCode;
|
||||
}
|
||||
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, NN_ERROR_CODE::ACT_UNKNOWN_SERVER_ERROR);
|
||||
}
|
||||
|
||||
nnResult AcquireIndependentServiceToken(uint8 accountSlot, uint64 titleId, uint16 titleVersion, std::string_view clientId, uint8* tokenOut, uint32 tokenLen)
|
||||
{
|
||||
static constexpr size_t IndependentTokenMaxLength = 512+1; // 512 bytes + null terminator
|
||||
if(accountSlot != ACT_SLOT_CURRENT)
|
||||
return ACTResult_InvalidValue;
|
||||
LockedAccount account(accountSlot);
|
||||
if (!account)
|
||||
return ACTResult_AccountDoesNotExist;
|
||||
if (!account->IsNetworkAccount())
|
||||
return ACTResult_NotANetworkAccount;
|
||||
cemu_assert_debug(ActiveSettings::IsOnlineEnabled());
|
||||
if (tokenLen < IndependentTokenMaxLength)
|
||||
return ACTResult_OutOfRange;
|
||||
NAPI::AuthInfo authInfo;
|
||||
NAPI::NAPI_MakeAuthInfoFromCurrentAccount(authInfo);
|
||||
account.Release();
|
||||
NAPI::ACTGetIndependentTokenResult tokenResult = NAPI::ACT_GetIndependentToken_WithCache(authInfo, titleId, titleVersion, clientId);
|
||||
uint32 returnCode = 0;
|
||||
if (tokenResult.isValid())
|
||||
{
|
||||
for (size_t i = 0; i < std::min(tokenResult.token.size(), (size_t)IndependentTokenMaxLength); i++)
|
||||
{
|
||||
tokenOut[i] = tokenResult.token[i];
|
||||
tokenOut[i + 1] = '\0';
|
||||
}
|
||||
returnCode = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
returnCode = 0x80000000; // todo - proper error codes
|
||||
}
|
||||
return returnCode;
|
||||
}
|
||||
|
||||
class ActService : public iosu::nn::IPCService
|
||||
{
|
||||
public:
|
||||
ActService() : iosu::nn::IPCService("/dev/act") {}
|
||||
|
||||
nnResult ServiceCall(uint32 serviceId, void* request, void* response) override
|
||||
{
|
||||
cemuLog_log(LogType::Force, "Unsupported service call to /dev/act");
|
||||
cemu_assert_unimplemented();
|
||||
return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_ACT, 0);
|
||||
}
|
||||
};
|
||||
|
||||
ActService gActService;
|
||||
|
||||
void Initialize()
|
||||
{
|
||||
gActService.Start();
|
||||
}
|
||||
|
||||
void Stop()
|
||||
{
|
||||
gActService.Stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// IOSU act IO
|
||||
|
||||
typedef struct
|
||||
{
|
||||
/* +0x00 */ uint32be ukn00;
|
||||
/* +0x04 */ uint32be ukn04;
|
||||
/* +0x08 */ uint32be ukn08;
|
||||
/* +0x0C */ uint32be subcommandCode;
|
||||
/* +0x10 */ uint8 ukn10;
|
||||
/* +0x11 */ uint8 ukn11;
|
||||
/* +0x12 */ uint8 ukn12;
|
||||
/* +0x13 */ uint8 accountSlot;
|
||||
/* +0x14 */ uint32be unique; // is this command specific?
|
||||
}cmdActRequest00_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint32be returnCode;
|
||||
uint8 transferableIdBase[8];
|
||||
}cmdActGetTransferableIDResult_t;
|
||||
|
||||
#define ACT_SUBCMD_GET_TRANSFERABLE_ID 4
|
||||
#define ACT_SUBCMD_INITIALIZE 0x14
|
||||
|
||||
#define _cancelIfAccountDoesNotExist() \
|
||||
if (_actAccountData[accountIndex].isValid == false) \
|
||||
{ \
|
||||
/* account does not exist*/ \
|
||||
ioctlReturnValue = 0; \
|
||||
actCemuRequest->setACTReturnCode(BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_ACT, NN_ACT_RESULT_ACCOUNT_DOES_NOT_EXIST)); /* 0xA071F480 */ \
|
||||
actCemuRequest->resultU64.u64 = 0; \
|
||||
iosuIoctl_completeRequest(ioQueueEntry, ioctlReturnValue); \
|
||||
continue; \
|
||||
}
|
||||
|
||||
int iosuAct_thread()
|
||||
{
|
||||
SetThreadName("iosuAct_thread");
|
||||
@ -661,47 +817,13 @@ int iosuAct_thread()
|
||||
}
|
||||
else if (actCemuRequest->requestCode == IOSU_ARC_ACQUIRENEXTOKEN)
|
||||
{
|
||||
NAPI::AuthInfo authInfo;
|
||||
NAPI::NAPI_MakeAuthInfoFromCurrentAccount(authInfo);
|
||||
NAPI::ACTGetNexTokenResult nexTokenResult = NAPI::ACT_GetNexToken_WithCache(authInfo, actCemuRequest->titleId, actCemuRequest->titleVersion, actCemuRequest->serverId);
|
||||
uint32 returnCode = 0;
|
||||
if (nexTokenResult.isValid())
|
||||
{
|
||||
*(NAPI::ACTNexToken*)actCemuRequest->resultBinary.binBuffer = nexTokenResult.nexToken;
|
||||
returnCode = NN_RESULT_SUCCESS;
|
||||
}
|
||||
else if (nexTokenResult.apiError == NAPI_RESULT::SERVICE_ERROR)
|
||||
{
|
||||
returnCode = ServerActErrorCodeToNNResult(nexTokenResult.serviceError);
|
||||
cemu_assert_debug((returnCode&0x80000000) != 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
returnCode = nnResultStatus(NN_RESULT_MODULE_NN_ACT, NN_ERROR_CODE::ACT_UNKNOWN_SERVER_ERROR);
|
||||
}
|
||||
actCemuRequest->setACTReturnCode(returnCode);
|
||||
nnResult r = iosu::act::AcquireNexToken(actCemuRequest->accountSlot, actCemuRequest->titleId, actCemuRequest->titleVersion, actCemuRequest->serverId, actCemuRequest->resultBinary.binBuffer, sizeof(NexToken));
|
||||
actCemuRequest->setACTReturnCode(r);
|
||||
}
|
||||
else if (actCemuRequest->requestCode == IOSU_ARC_ACQUIREINDEPENDENTTOKEN)
|
||||
{
|
||||
NAPI::AuthInfo authInfo;
|
||||
NAPI::NAPI_MakeAuthInfoFromCurrentAccount(authInfo);
|
||||
NAPI::ACTGetIndependentTokenResult tokenResult = NAPI::ACT_GetIndependentToken_WithCache(authInfo, actCemuRequest->titleId, actCemuRequest->titleVersion, actCemuRequest->clientId);
|
||||
|
||||
uint32 returnCode = 0;
|
||||
if (tokenResult.isValid())
|
||||
{
|
||||
for (size_t i = 0; i < std::min(tokenResult.token.size(), (size_t)200); i++)
|
||||
{
|
||||
actCemuRequest->resultBinary.binBuffer[i] = tokenResult.token[i];
|
||||
actCemuRequest->resultBinary.binBuffer[i + 1] = '\0';
|
||||
}
|
||||
returnCode = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
returnCode = 0x80000000; // todo - proper error codes
|
||||
}
|
||||
actCemuRequest->setACTReturnCode(returnCode);
|
||||
nnResult r = iosu::act::AcquireIndependentServiceToken(actCemuRequest->accountSlot, actCemuRequest->titleId, actCemuRequest->titleVersion, actCemuRequest->clientId, actCemuRequest->resultBinary.binBuffer, sizeof(actCemuRequest->resultBinary.binBuffer));
|
||||
actCemuRequest->setACTReturnCode(r);
|
||||
}
|
||||
else if (actCemuRequest->requestCode == IOSU_ARC_ACQUIREPIDBYNNID)
|
||||
{
|
||||
|
@ -49,10 +49,11 @@ namespace iosu
|
||||
bool getMii(uint8 slot, FFLData_t* fflData);
|
||||
bool getScreenname(uint8 slot, uint16 screenname[ACT_NICKNAME_LENGTH]);
|
||||
bool getCountryIndex(uint8 slot, uint32* countryIndex);
|
||||
bool GetPersistentId(uint8 slot, uint32* persistentId);
|
||||
|
||||
std::string getAccountId2(uint8 slot);
|
||||
|
||||
const uint8 ACT_SLOT_CURRENT = 0xFE;
|
||||
static constexpr uint8 ACT_SLOT_CURRENT = 0xFE;
|
||||
|
||||
void Initialize();
|
||||
void Stop();
|
||||
|
@ -498,7 +498,7 @@ namespace iosu
|
||||
curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, task_header_callback);
|
||||
curl_easy_setopt(curl, CURLOPT_HEADERDATA, &(*it));
|
||||
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 0x3C);
|
||||
if (GetNetworkConfig().disablesslver.GetValue() && ActiveSettings::GetNetworkService() == NetworkService::Custom || ActiveSettings::GetNetworkService() == NetworkService::Pretendo) // remove Pretendo Function once SSL is in the Service
|
||||
if (IsNetworkServiceSSLDisabled(ActiveSettings::GetNetworkService()))
|
||||
{
|
||||
curl_easy_setopt(curl,CURLOPT_SSL_VERIFYPEER,0L);
|
||||
}
|
||||
|
@ -292,16 +292,6 @@ void iosuCrypto_generateDeviceCertificate()
|
||||
BN_CTX_free(context);
|
||||
}
|
||||
|
||||
bool iosuCrypto_hasAllDataForLogin()
|
||||
{
|
||||
if (hasOtpMem == false)
|
||||
return false;
|
||||
if (hasSeepromMem == false)
|
||||
return false;
|
||||
// todo - check if certificates are available
|
||||
return true;
|
||||
}
|
||||
|
||||
sint32 iosuCrypto_getDeviceCertificateBase64Encoded(char* output)
|
||||
{
|
||||
iosuCrypto_base64Encode((uint8*)&g_wiiuDeviceCert, sizeof(g_wiiuDeviceCert), output);
|
||||
|
@ -2,7 +2,6 @@
|
||||
|
||||
void iosuCrypto_init();
|
||||
|
||||
bool iosuCrypto_hasAllDataForLogin();
|
||||
bool iosuCrypto_getDeviceId(uint32* deviceId);
|
||||
void iosuCrypto_getDeviceSerialString(char* serialString);
|
||||
|
||||
|
@ -214,6 +214,12 @@ namespace iosu
|
||||
friendData->friendExtraData.gameKey.ukn08 = frd->presence.gameKey.ukn;
|
||||
NexPresenceToGameMode(&frd->presence, &friendData->friendExtraData.gameMode);
|
||||
|
||||
auto fixed_presence_msg = '\0' + frd->presence.msg; // avoid first character of comment from being cut off
|
||||
friendData->friendExtraData.gameModeDescription.assignFromUTF8(fixed_presence_msg);
|
||||
|
||||
auto fixed_comment = '\0' + frd->comment.commentString; // avoid first character of comment from being cut off
|
||||
friendData->friendExtraData.comment.assignFromUTF8(fixed_comment);
|
||||
|
||||
// set valid dates
|
||||
friendData->uknDate.year = 2018;
|
||||
friendData->uknDate.day = 1;
|
||||
@ -750,9 +756,18 @@ namespace iosu
|
||||
{
|
||||
if(numVecIn != 0 || numVecOut != 1)
|
||||
return FPResult_InvalidIPCParam;
|
||||
SelfPlayingGame selfPlayingGame{0};
|
||||
cemuLog_log(LogType::Force, "GetMyPlayingGame is todo");
|
||||
return WriteValueOutput<SelfPlayingGame>(vecOut, selfPlayingGame);
|
||||
GameKey selfPlayingGame
|
||||
{
|
||||
CafeSystem::GetForegroundTitleId(),
|
||||
CafeSystem::GetForegroundTitleVersion(),
|
||||
{0,0,0,0,0,0}
|
||||
};
|
||||
if (GetTitleIdHigh(CafeSystem::GetForegroundTitleId()) != 0x00050000)
|
||||
{
|
||||
selfPlayingGame.titleId = 0;
|
||||
selfPlayingGame.ukn08 = 0;
|
||||
}
|
||||
return WriteValueOutput<GameKey>(vecOut, selfPlayingGame);
|
||||
}
|
||||
|
||||
nnResult CallHandler_GetFriendAccountId(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut)
|
||||
@ -1410,8 +1425,16 @@ namespace iosu
|
||||
act::getCountryIndex(currentSlot, &countryCode);
|
||||
// init presence
|
||||
g_fpd.myPresence.isOnline = 1;
|
||||
g_fpd.myPresence.gameKey.titleId = CafeSystem::GetForegroundTitleId();
|
||||
g_fpd.myPresence.gameKey.ukn = CafeSystem::GetForegroundTitleVersion();
|
||||
if (GetTitleIdHigh(CafeSystem::GetForegroundTitleId()) == 0x00050000)
|
||||
{
|
||||
g_fpd.myPresence.gameKey.titleId = CafeSystem::GetForegroundTitleId();
|
||||
g_fpd.myPresence.gameKey.ukn = CafeSystem::GetForegroundTitleVersion();
|
||||
}
|
||||
else
|
||||
{
|
||||
g_fpd.myPresence.gameKey.titleId = 0; // icon will not be ??? or invalid to others
|
||||
g_fpd.myPresence.gameKey.ukn = 0;
|
||||
}
|
||||
// resolve potential domain to IP address
|
||||
struct addrinfo hints = {0}, *addrs;
|
||||
hints.ai_family = AF_INET;
|
||||
|
@ -94,7 +94,7 @@ namespace iosu
|
||||
/* +0x1EC */ uint8 isOnline;
|
||||
/* +0x1ED */ uint8 _padding1ED[3];
|
||||
// some other sub struct?
|
||||
/* +0x1F0 */ char comment[36]; // pops up every few seconds in friend list
|
||||
/* +0x1F0 */ CafeWideString<0x12> comment; // pops up every few seconds in friend list
|
||||
/* +0x214 */ uint32be _padding214;
|
||||
/* +0x218 */ FPDDate approvalTime;
|
||||
/* +0x220 */ FPDDate lastOnline;
|
||||
@ -263,4 +263,4 @@ namespace iosu
|
||||
|
||||
IOSUModule* GetModule();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -228,7 +228,7 @@ namespace iosu
|
||||
}
|
||||
}
|
||||
|
||||
auto result = NAPI::IDBE_Request(titleId);
|
||||
auto result = NAPI::IDBE_Request(ActiveSettings::GetNetworkService(), titleId);
|
||||
if (!result)
|
||||
{
|
||||
memset(idbeIconOutput, 0, sizeof(NAPI::IDBEIconDataV0));
|
||||
|
@ -155,6 +155,9 @@ namespace iosu
|
||||
|
||||
void IPCService::ServiceThread()
|
||||
{
|
||||
std::string serviceName = m_devicePath.substr(m_devicePath.find_last_of('/') == std::string::npos ? 0 : m_devicePath.find_last_of('/') + 1);
|
||||
serviceName.insert(0, "NNsvc_");
|
||||
SetThreadName(serviceName.c_str());
|
||||
m_msgQueueId = IOS_CreateMessageQueue(_m_msgBuffer.GetPtr(), _m_msgBuffer.GetCount());
|
||||
cemu_assert(!IOS_ResultIsError((IOS_ERROR)m_msgQueueId));
|
||||
IOS_ERROR r = IOS_RegisterResourceManager(m_devicePath.c_str(), m_msgQueueId);
|
||||
@ -207,6 +210,7 @@ namespace iosu
|
||||
IOS_ResourceReply(cmd, IOS_ERROR_INVALID);
|
||||
}
|
||||
}
|
||||
IOS_DestroyMessageQueue(m_msgQueueId);
|
||||
m_threadInitialized = false;
|
||||
}
|
||||
};
|
||||
|
@ -8,7 +8,7 @@ namespace iosu
|
||||
{
|
||||
namespace nn
|
||||
{
|
||||
// a simple service interface which wraps handle management and Ioctlv/IoctlvAsync
|
||||
// a simple service interface which wraps handle management and Ioctlv/IoctlvAsync (used by /dev/fpd and others are still to be determined)
|
||||
class IPCSimpleService
|
||||
{
|
||||
public:
|
||||
@ -88,7 +88,7 @@ namespace iosu
|
||||
uint32be nnResultCode;
|
||||
};
|
||||
|
||||
// a complex service interface which wraps Ioctlv and adds an additional service channel, used by /dev/act, ?
|
||||
// a complex service interface which wraps Ioctlv and adds an additional service channel, used by /dev/act, /dev/acp_main, ?
|
||||
class IPCService
|
||||
{
|
||||
public:
|
||||
|
@ -50,6 +50,10 @@ typedef struct
|
||||
|
||||
static_assert(sizeof(elfSectionEntry_t) == 0x28, "");
|
||||
|
||||
#define PF_X (1 << 0) /* Segment is executable */
|
||||
#define PF_W (1 << 1) /* Segment is writable */
|
||||
#define PF_R (1 << 2) /* Segment is readable */
|
||||
|
||||
// Map elf into memory
|
||||
uint32 ELF_LoadFromMemory(uint8* elfData, sint32 size, const char* name)
|
||||
{
|
||||
@ -68,6 +72,7 @@ uint32 ELF_LoadFromMemory(uint8* elfData, sint32 size, const char* name)
|
||||
uint32 shSize = (uint32)sectionTable[i].shSize;
|
||||
uint32 shOffset = (uint32)sectionTable[i].shOffset;
|
||||
uint32 shType = (uint32)sectionTable[i].shType;
|
||||
uint32 shFlags = (uint32)sectionTable[i].shFlags;
|
||||
|
||||
if (shOffset > (uint32)size)
|
||||
{
|
||||
@ -89,6 +94,8 @@ uint32 ELF_LoadFromMemory(uint8* elfData, sint32 size, const char* name)
|
||||
}
|
||||
// SHT_NOBITS
|
||||
}
|
||||
if((shFlags & PF_X) > 0)
|
||||
PPCRecompiler_allocateRange(shAddr, shSize);
|
||||
}
|
||||
return header->entrypoint;
|
||||
}
|
||||
|
@ -13,6 +13,7 @@
|
||||
#include "Cafe/OS/libs/nn_spm/nn_spm.h"
|
||||
#include "Cafe/OS/libs/nn_ec/nn_ec.h"
|
||||
#include "Cafe/OS/libs/nn_boss/nn_boss.h"
|
||||
#include "Cafe/OS/libs/nn_sl/nn_sl.h"
|
||||
#include "Cafe/OS/libs/nn_fp/nn_fp.h"
|
||||
#include "Cafe/OS/libs/nn_olv/nn_olv.h"
|
||||
#include "Cafe/OS/libs/nn_idbe/nn_idbe.h"
|
||||
@ -208,6 +209,7 @@ void osLib_load()
|
||||
nn::ndm::load();
|
||||
nn::spm::load();
|
||||
nn::save::load();
|
||||
nnSL_load();
|
||||
nsysnet_load();
|
||||
nn::fp::load();
|
||||
nn::olv::load();
|
||||
@ -219,5 +221,5 @@ void osLib_load()
|
||||
nsyskbd::nsyskbd_load();
|
||||
swkbd::load();
|
||||
camera::load();
|
||||
procui_load();
|
||||
proc_ui::load();
|
||||
}
|
||||
|
@ -140,6 +140,17 @@ static std::tuple<Args...> cafeExportBuildArgTuple(PPCInterpreter_t* hCPU, R(fn)
|
||||
return std::tuple<Args...>{ cafeExportGetParamWrapper<Args>(hCPU, gprIndex, fprIndex)... };
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T cafeExportGetFormatParamWrapper(PPCInterpreter_t* hCPU, int& gprIndex, int& fprIndex)
|
||||
{
|
||||
T v;
|
||||
cafeExportParamWrapper::getParamWrapper(hCPU, gprIndex, fprIndex, v);
|
||||
// if T is char* or const char*, return "null" instead of nullptr since newer fmtlib would throw otherwise
|
||||
if constexpr (std::is_same_v<T, char*> || std::is_same_v<T, const char*>)
|
||||
return v ? v : (T)"null";
|
||||
return v;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
using _CAFE_FORMAT_ARG = std::conditional_t<std::is_pointer_v<T>,
|
||||
std::conditional_t<std::is_same_v<T, char*> || std::is_same_v<T, const char*>, T, MEMPTR<T>>, T>;
|
||||
@ -150,7 +161,7 @@ static auto cafeExportBuildFormatTuple(PPCInterpreter_t* hCPU, R(fn)(Args...))
|
||||
int gprIndex = 0;
|
||||
int fprIndex = 0;
|
||||
return std::tuple<_CAFE_FORMAT_ARG<Args>...>{
|
||||
cafeExportGetParamWrapper<_CAFE_FORMAT_ARG<Args>>(hCPU, gprIndex, fprIndex)...
|
||||
cafeExportGetFormatParamWrapper<_CAFE_FORMAT_ARG<Args>>(hCPU, gprIndex, fprIndex)...
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -35,12 +35,12 @@
|
||||
#include "Cafe/OS/libs/coreinit/coreinit_MEM_BlockHeap.h"
|
||||
#include "Cafe/OS/libs/coreinit/coreinit_MEM_ExpHeap.h"
|
||||
|
||||
CoreinitSharedData* gCoreinitData = NULL;
|
||||
CoreinitSharedData* gCoreinitData = nullptr;
|
||||
|
||||
sint32 ScoreStackTrace(OSThread_t* thread, MPTR sp)
|
||||
{
|
||||
uint32 stackMinAddr = _swapEndianU32(thread->stackEnd);
|
||||
uint32 stackMaxAddr = _swapEndianU32(thread->stackBase);
|
||||
uint32 stackMinAddr = thread->stackEnd.GetMPTR();
|
||||
uint32 stackMaxAddr = thread->stackBase.GetMPTR();
|
||||
|
||||
sint32 score = 0;
|
||||
uint32 currentStackPtr = sp;
|
||||
@ -95,8 +95,8 @@ void DebugLogStackTrace(OSThread_t* thread, MPTR sp)
|
||||
|
||||
// print stack trace
|
||||
uint32 currentStackPtr = highestScoreSP;
|
||||
uint32 stackMinAddr = _swapEndianU32(thread->stackEnd);
|
||||
uint32 stackMaxAddr = _swapEndianU32(thread->stackBase);
|
||||
uint32 stackMinAddr = thread->stackEnd.GetMPTR();
|
||||
uint32 stackMaxAddr = thread->stackBase.GetMPTR();
|
||||
for (sint32 i = 0; i < 20; i++)
|
||||
{
|
||||
uint32 nextStackPtr = memory_readU32(currentStackPtr);
|
||||
@ -179,27 +179,6 @@ void coreinitExport_OSGetSharedData(PPCInterpreter_t* hCPU)
|
||||
osLib_returnFromFunction(hCPU, 1);
|
||||
}
|
||||
|
||||
typedef struct
|
||||
{
|
||||
MPTR getDriverName;
|
||||
MPTR ukn04;
|
||||
MPTR onAcquiredForeground;
|
||||
MPTR onReleaseForeground;
|
||||
MPTR ukn10;
|
||||
}OSDriverCallbacks_t;
|
||||
|
||||
void coreinitExport_OSDriver_Register(PPCInterpreter_t* hCPU)
|
||||
{
|
||||
#ifdef CEMU_DEBUG_ASSERT
|
||||
cemuLog_log(LogType::Force, "OSDriver_Register(0x{:08x},0x{:08x},0x{:08x},0x{:08x},0x{:08x},0x{:08x})", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6], hCPU->gpr[7], hCPU->gpr[8]);
|
||||
#endif
|
||||
OSDriverCallbacks_t* driverCallbacks = (OSDriverCallbacks_t*)memory_getPointerFromVirtualOffset(hCPU->gpr[5]);
|
||||
|
||||
// todo
|
||||
|
||||
osLib_returnFromFunction(hCPU, 0);
|
||||
}
|
||||
|
||||
namespace coreinit
|
||||
{
|
||||
sint32 OSGetCoreId()
|
||||
@ -379,7 +358,6 @@ void coreinit_load()
|
||||
coreinit::miscInit();
|
||||
osLib_addFunction("coreinit", "OSGetSharedData", coreinitExport_OSGetSharedData);
|
||||
osLib_addFunction("coreinit", "UCReadSysConfig", coreinitExport_UCReadSysConfig);
|
||||
osLib_addFunction("coreinit", "OSDriver_Register", coreinitExport_OSDriver_Register);
|
||||
|
||||
// async callbacks
|
||||
InitializeAsyncCallback();
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user