mirror of
https://github.com/cemu-project/Cemu.git
synced 2025-07-07 15:31:18 +12:00
Merge branch 'main' into camera
This commit is contained in:
commit
7a53a5cac7
153 changed files with 5380 additions and 2445 deletions
4
.github/workflows/build.yml
vendored
4
.github/workflows/build.yml
vendored
|
@ -39,7 +39,7 @@ jobs:
|
||||||
- name: "Install system dependencies"
|
- name: "Install system dependencies"
|
||||||
run: |
|
run: |
|
||||||
sudo apt update -qq
|
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
|
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 libbluetooth-dev
|
||||||
|
|
||||||
- name: "Setup cmake"
|
- name: "Setup cmake"
|
||||||
uses: jwlawson/actions-setup-cmake@v2
|
uses: jwlawson/actions-setup-cmake@v2
|
||||||
|
@ -96,7 +96,7 @@ jobs:
|
||||||
- name: "Install system dependencies"
|
- name: "Install system dependencies"
|
||||||
run: |
|
run: |
|
||||||
sudo apt update -qq
|
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 nasm ninja-build appstream
|
sudo apt install -y clang-15 cmake freeglut3-dev libgcrypt20-dev libglm-dev libgtk-3-dev libpulse-dev libsecret-1-dev libsystemd-dev nasm ninja-build appstream libbluetooth-dev
|
||||||
|
|
||||||
- name: "Build AppImage"
|
- name: "Build AppImage"
|
||||||
run: |
|
run: |
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
name: Deploy experimental release
|
name: Deploy release
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
|
@ -54,7 +54,7 @@ jobs:
|
||||||
next_version_major: ${{ needs.calculate-version.outputs.next_version_major }}
|
next_version_major: ${{ needs.calculate-version.outputs.next_version_major }}
|
||||||
next_version_minor: ${{ needs.calculate-version.outputs.next_version_minor }}
|
next_version_minor: ${{ needs.calculate-version.outputs.next_version_minor }}
|
||||||
deploy:
|
deploy:
|
||||||
name: Deploy experimental release
|
name: Deploy release
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
needs: [call-release-build, calculate-version]
|
needs: [call-release-build, calculate-version]
|
||||||
steps:
|
steps:
|
85
.github/workflows/deploy_stable_release.yml
vendored
85
.github/workflows/deploy_stable_release.yml
vendored
|
@ -1,85 +0,0 @@
|
||||||
name: Create new release
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
PlaceholderInput:
|
|
||||||
description: PlaceholderInput
|
|
||||||
required: false
|
|
||||||
jobs:
|
|
||||||
call-release-build:
|
|
||||||
uses: ./.github/workflows/build.yml
|
|
||||||
with:
|
|
||||||
deploymode: release
|
|
||||||
deploy:
|
|
||||||
name: Deploy release
|
|
||||||
runs-on: ubuntu-20.04
|
|
||||||
needs: call-release-build
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
name: cemu-bin-linux-x64
|
|
||||||
path: cemu-bin-linux-x64
|
|
||||||
|
|
||||||
- uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
name: cemu-appimage-x64
|
|
||||||
path: cemu-appimage-x64
|
|
||||||
|
|
||||||
- uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
name: cemu-bin-windows-x64
|
|
||||||
path: cemu-bin-windows-x64
|
|
||||||
|
|
||||||
- uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
name: cemu-bin-macos-x64
|
|
||||||
path: cemu-bin-macos-x64
|
|
||||||
|
|
||||||
- name: Initialize
|
|
||||||
run: |
|
|
||||||
mkdir upload
|
|
||||||
sudo apt update -qq
|
|
||||||
sudo apt install -y zip
|
|
||||||
|
|
||||||
- name: Get Cemu release version
|
|
||||||
run: |
|
|
||||||
gcc -o getversion .github/getversion.cpp
|
|
||||||
echo "Cemu CI version: $(./getversion)"
|
|
||||||
echo "CEMU_FOLDER_NAME=Cemu_$(./getversion)" >> $GITHUB_ENV
|
|
||||||
echo "CEMU_VERSION=$(./getversion)" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Create release from windows-bin
|
|
||||||
run: |
|
|
||||||
ls ./
|
|
||||||
ls ./bin/
|
|
||||||
cp -R ./bin ./${{ env.CEMU_FOLDER_NAME }}
|
|
||||||
mv cemu-bin-windows-x64/Cemu.exe ./${{ env.CEMU_FOLDER_NAME }}/Cemu.exe
|
|
||||||
zip -9 -r upload/cemu-${{ env.CEMU_VERSION }}-windows-x64.zip ${{ env.CEMU_FOLDER_NAME }}
|
|
||||||
rm -r ./${{ env.CEMU_FOLDER_NAME }}
|
|
||||||
|
|
||||||
- name: Create appimage
|
|
||||||
run: |
|
|
||||||
VERSION=${{ env.CEMU_VERSION }}
|
|
||||||
echo "Cemu Version is $VERSION"
|
|
||||||
ls cemu-appimage-x64
|
|
||||||
mv cemu-appimage-x64/Cemu-*-x86_64.AppImage upload/Cemu-$VERSION-x86_64.AppImage
|
|
||||||
|
|
||||||
- name: Create release from ubuntu-bin
|
|
||||||
run: |
|
|
||||||
ls ./
|
|
||||||
ls ./bin/
|
|
||||||
cp -R ./bin ./${{ env.CEMU_FOLDER_NAME }}
|
|
||||||
mv cemu-bin-linux-x64/Cemu ./${{ env.CEMU_FOLDER_NAME }}/Cemu
|
|
||||||
zip -9 -r upload/cemu-${{ env.CEMU_VERSION }}-ubuntu-20.04-x64.zip ${{ env.CEMU_FOLDER_NAME }}
|
|
||||||
rm -r ./${{ env.CEMU_FOLDER_NAME }}
|
|
||||||
|
|
||||||
- name: Create release from macos-bin
|
|
||||||
run: cp cemu-bin-macos-x64/Cemu.dmg upload/cemu-${{ env.CEMU_VERSION }}-macos-12-x64.dmg
|
|
||||||
|
|
||||||
- name: Create release
|
|
||||||
run: |
|
|
||||||
wget -O ghr.tar.gz https://github.com/tcnksm/ghr/releases/download/v0.15.0/ghr_v0.15.0_linux_amd64.tar.gz
|
|
||||||
tar xvzf ghr.tar.gz; rm ghr.tar.gz
|
|
||||||
ghr_v0.15.0_linux_amd64/ghr -t ${{ secrets.GITHUB_TOKEN }} -n "Cemu ${{ env.CEMU_VERSION }}" -b "Changelog:" v${{ env.CEMU_VERSION }} ./upload
|
|
2
.github/workflows/generate_pot.yml
vendored
2
.github/workflows/generate_pot.yml
vendored
|
@ -35,7 +35,7 @@ jobs:
|
||||||
-o cemu.pot
|
-o cemu.pot
|
||||||
|
|
||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: POT file
|
name: POT file
|
||||||
path: ./cemu.pot
|
path: ./cemu.pot
|
||||||
|
|
47
BUILD.md
47
BUILD.md
|
@ -46,10 +46,10 @@ To compile Cemu, a recent enough compiler and STL with C++20 support is required
|
||||||
### Dependencies
|
### Dependencies
|
||||||
|
|
||||||
#### For Arch and derivatives:
|
#### 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`
|
`sudo pacman -S --needed base-devel bluez-libs clang cmake freeglut git glm gtk3 libgcrypt libpulse libsecret linux-headers llvm nasm ninja systemd unzip zip`
|
||||||
|
|
||||||
#### For Debian, Ubuntu and derivatives:
|
#### 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`
|
`sudo apt install -y cmake curl clang-15 freeglut3-dev git libbluetooth-dev 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.
|
You may also need to install `libusb-1.0-0-dev` as a workaround for an issue with the vcpkg hidapi package.
|
||||||
|
|
||||||
|
@ -57,7 +57,7 @@ At Step 3 in [Build Cemu using cmake and clang](#build-cemu-using-cmake-and-clan
|
||||||
`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`
|
`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:
|
#### 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 llvm nasm ninja-build perl-core systemd-devel zlib-devel zlib-static`
|
`sudo dnf install bluez-libs-devel 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 wayland-protocols-devel zlib-devel zlib-static`
|
||||||
|
|
||||||
### Build Cemu
|
### Build Cemu
|
||||||
|
|
||||||
|
@ -120,6 +120,9 @@ This section refers to running `cmake -S...` (truncated).
|
||||||
* Compiling failed during rebuild after `git pull` with an error that mentions RPATH
|
* Compiling failed during rebuild after `git pull` with an error that mentions RPATH
|
||||||
* Add the following and try running the command again:
|
* Add the following and try running the command again:
|
||||||
* `-DCMAKE_BUILD_WITH_INSTALL_RPATH=ON`
|
* `-DCMAKE_BUILD_WITH_INSTALL_RPATH=ON`
|
||||||
|
* Environment variable `VCPKG_FORCE_SYSTEM_BINARIES` must be set.
|
||||||
|
* Execute the folowing and then try running the command again:
|
||||||
|
* `export VCPKG_FORCE_SYSTEM_BINARIES=1`
|
||||||
* 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 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.
|
||||||
|
|
||||||
|
|
||||||
|
@ -189,3 +192,41 @@ Then install the dependencies:
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
|
## CMake configure flags
|
||||||
|
Some flags can be passed during CMake configure to customise which features are enabled on build.
|
||||||
|
|
||||||
|
Example usage: `cmake -S . -B build -DCMAKE_BUILD_TYPE=release -DENABLE_SDL=ON -DENABLE_VULKAN=OFF`
|
||||||
|
|
||||||
|
### All platforms
|
||||||
|
| Flag | | Description | Default | Note |
|
||||||
|
|--------------------|:--|-----------------------------------------------------------------------------|---------|--------------------|
|
||||||
|
| ALLOW_PORTABLE | | Allow Cemu to use the `portable` directory to store configs and data | ON | |
|
||||||
|
| CEMU_CXX_FLAGS | | Flags passed straight to the compiler, e.g. `-march=native`, `-Wall`, `/W3` | "" | |
|
||||||
|
| ENABLE_CUBEB | | Enable cubeb audio backend | ON | |
|
||||||
|
| ENABLE_DISCORD_RPC | | Enable Discord Rich presence support | ON | |
|
||||||
|
| ENABLE_OPENGL | | Enable OpenGL graphics backend | ON | Currently required |
|
||||||
|
| ENABLE_HIDAPI | | Enable HIDAPI (used for Wiimote controller API) | ON | |
|
||||||
|
| ENABLE_SDL | | Enable SDLController controller API | ON | Currently required |
|
||||||
|
| ENABLE_VCPKG | | Use VCPKG package manager to obtain dependencies | ON | |
|
||||||
|
| ENABLE_VULKAN | | Enable the Vulkan graphics backend | ON | |
|
||||||
|
| ENABLE_WXWIDGETS | | Enable wxWidgets UI | ON | Currently required |
|
||||||
|
|
||||||
|
### Windows
|
||||||
|
| Flag | Description | Default | Note |
|
||||||
|
|--------------------|-----------------------------------|---------|--------------------|
|
||||||
|
| ENABLE_DIRECTAUDIO | Enable DirectAudio audio backend | ON | Currently required |
|
||||||
|
| ENABLE_DIRECTINPUT | Enable DirectInput controller API | ON | Currently required |
|
||||||
|
| ENABLE_XAUDIO | Enable XAudio audio backend | ON | |
|
||||||
|
| ENABLE_XINPUT | Enable XInput controller API | ON | |
|
||||||
|
|
||||||
|
### Linux
|
||||||
|
| Flag | Description | Default |
|
||||||
|
|-----------------------|----------------------------------------------------|---------|
|
||||||
|
| ENABLE_BLUEZ | Build with Bluez (used for Wiimote controller API) | ON |
|
||||||
|
| ENABLE_FERAL_GAMEMODE | Enable Feral Interactive GameMode support | ON |
|
||||||
|
| ENABLE_WAYLAND | Enable Wayland support | ON |
|
||||||
|
|
||||||
|
### macOS
|
||||||
|
| Flag | Description | Default |
|
||||||
|
|--------------|------------------------------------------------|---------|
|
||||||
|
| MACOS_BUNDLE | MacOS executable will be an application bundle | OFF |
|
||||||
|
|
|
@ -2,6 +2,7 @@ cmake_minimum_required(VERSION 3.21.1)
|
||||||
|
|
||||||
option(ENABLE_VCPKG "Enable the vcpkg package manager" ON)
|
option(ENABLE_VCPKG "Enable the vcpkg package manager" ON)
|
||||||
option(MACOS_BUNDLE "The executable when built on macOS will be created as an application bundle" OFF)
|
option(MACOS_BUNDLE "The executable when built on macOS will be created as an application bundle" OFF)
|
||||||
|
option(ALLOW_PORTABLE "Allow Cemu to be run in portable mode" ON)
|
||||||
|
|
||||||
# used by CI script to set version:
|
# used by CI script to set version:
|
||||||
set(EMULATOR_VERSION_MAJOR "0" CACHE STRING "")
|
set(EMULATOR_VERSION_MAJOR "0" CACHE STRING "")
|
||||||
|
@ -98,6 +99,7 @@ endif()
|
||||||
if (UNIX AND NOT APPLE)
|
if (UNIX AND NOT APPLE)
|
||||||
option(ENABLE_WAYLAND "Build with Wayland support" ON)
|
option(ENABLE_WAYLAND "Build with Wayland support" ON)
|
||||||
option(ENABLE_FERAL_GAMEMODE "Enables Feral Interactive GameMode Support" ON)
|
option(ENABLE_FERAL_GAMEMODE "Enables Feral Interactive GameMode Support" ON)
|
||||||
|
option(ENABLE_BLUEZ "Build with Bluez support" ON)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
option(ENABLE_OPENGL "Enables the OpenGL backend" ON)
|
option(ENABLE_OPENGL "Enables the OpenGL backend" ON)
|
||||||
|
@ -122,23 +124,6 @@ if (WIN32)
|
||||||
endif()
|
endif()
|
||||||
option(ENABLE_CUBEB "Enabled cubeb backend" ON)
|
option(ENABLE_CUBEB "Enabled cubeb backend" ON)
|
||||||
|
|
||||||
# usb hid backends
|
|
||||||
if (WIN32)
|
|
||||||
option(ENABLE_NSYSHID_WINDOWS_HID "Enables the native Windows HID backend for nsyshid" ON)
|
|
||||||
endif ()
|
|
||||||
# libusb and windows hid backends shouldn't be active at the same time; otherwise we'd see all devices twice!
|
|
||||||
if (NOT ENABLE_NSYSHID_WINDOWS_HID)
|
|
||||||
option(ENABLE_NSYSHID_LIBUSB "Enables the libusb backend for nsyshid" ON)
|
|
||||||
else ()
|
|
||||||
set(ENABLE_NSYSHID_LIBUSB OFF CACHE BOOL "" FORCE)
|
|
||||||
endif ()
|
|
||||||
if (ENABLE_NSYSHID_WINDOWS_HID)
|
|
||||||
add_compile_definitions(NSYSHID_ENABLE_BACKEND_WINDOWS_HID)
|
|
||||||
endif ()
|
|
||||||
if (ENABLE_NSYSHID_LIBUSB)
|
|
||||||
add_compile_definitions(NSYSHID_ENABLE_BACKEND_LIBUSB)
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
option(ENABLE_WXWIDGETS "Build with wxWidgets UI (Currently required)" ON)
|
option(ENABLE_WXWIDGETS "Build with wxWidgets UI (Currently required)" ON)
|
||||||
|
|
||||||
set(THREADS_PREFER_PTHREAD_FLAG true)
|
set(THREADS_PREFER_PTHREAD_FLAG true)
|
||||||
|
@ -179,6 +164,12 @@ if (UNIX AND NOT APPLE)
|
||||||
endif()
|
endif()
|
||||||
find_package(GTK3 REQUIRED)
|
find_package(GTK3 REQUIRED)
|
||||||
|
|
||||||
|
if(ENABLE_BLUEZ)
|
||||||
|
find_package(bluez REQUIRED)
|
||||||
|
set(ENABLE_WIIMOTE ON)
|
||||||
|
add_compile_definitions(HAS_BLUEZ)
|
||||||
|
endif()
|
||||||
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (ENABLE_VULKAN)
|
if (ENABLE_VULKAN)
|
||||||
|
|
BIN
bin/resources/ar/cemu.mo
Normal file
BIN
bin/resources/ar/cemu.mo
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
20
cmake/Findbluez.cmake
Normal file
20
cmake/Findbluez.cmake
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# SPDX-FileCopyrightText: 2022 Andrea Pappacoda <andrea@pappacoda.it>
|
||||||
|
# SPDX-License-Identifier: ISC
|
||||||
|
|
||||||
|
find_package(bluez CONFIG)
|
||||||
|
if (NOT bluez_FOUND)
|
||||||
|
find_package(PkgConfig)
|
||||||
|
if (PKG_CONFIG_FOUND)
|
||||||
|
pkg_search_module(bluez IMPORTED_TARGET GLOBAL bluez-1.0 bluez)
|
||||||
|
if (bluez_FOUND)
|
||||||
|
add_library(bluez::bluez ALIAS PkgConfig::bluez)
|
||||||
|
endif ()
|
||||||
|
endif ()
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
find_package_handle_standard_args(bluez
|
||||||
|
REQUIRED_VARS
|
||||||
|
bluez_LINK_LIBRARIES
|
||||||
|
bluez_FOUND
|
||||||
|
VERSION_VAR bluez_VERSION
|
||||||
|
)
|
|
@ -83,8 +83,8 @@ if (MACOS_BUNDLE)
|
||||||
set(MACOSX_BUNDLE_ICON_FILE "cemu.icns")
|
set(MACOSX_BUNDLE_ICON_FILE "cemu.icns")
|
||||||
set(MACOSX_BUNDLE_GUI_IDENTIFIER "info.cemu.Cemu")
|
set(MACOSX_BUNDLE_GUI_IDENTIFIER "info.cemu.Cemu")
|
||||||
set(MACOSX_BUNDLE_BUNDLE_NAME "Cemu")
|
set(MACOSX_BUNDLE_BUNDLE_NAME "Cemu")
|
||||||
set(MACOSX_BUNDLE_SHORT_VERSION_STRING ${CMAKE_PROJECT_VERSION})
|
set(MACOSX_BUNDLE_SHORT_VERSION_STRING "${EMULATOR_VERSION_MAJOR}.${EMULATOR_VERSION_MINOR}.${EMULATOR_VERSION_PATCH}")
|
||||||
set(MACOSX_BUNDLE_BUNDLE_VERSION ${CMAKE_PROJECT_VERSION})
|
set(MACOSX_BUNDLE_BUNDLE_VERSION "${EMULATOR_VERSION_MAJOR}.${EMULATOR_VERSION_MINOR}.${EMULATOR_VERSION_PATCH}")
|
||||||
set(MACOSX_BUNDLE_COPYRIGHT "Copyright © 2024 Cemu Project")
|
set(MACOSX_BUNDLE_COPYRIGHT "Copyright © 2024 Cemu Project")
|
||||||
|
|
||||||
set(MACOSX_BUNDLE_CATEGORY "public.app-category.games")
|
set(MACOSX_BUNDLE_CATEGORY "public.app-category.games")
|
||||||
|
@ -102,12 +102,18 @@ if (MACOS_BUNDLE)
|
||||||
COMMAND ${CMAKE_COMMAND} ARGS -E copy_directory "${CMAKE_SOURCE_DIR}/bin/${folder}" "${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/SharedSupport/${folder}")
|
COMMAND ${CMAKE_COMMAND} ARGS -E copy_directory "${CMAKE_SOURCE_DIR}/bin/${folder}" "${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/SharedSupport/${folder}")
|
||||||
endforeach(folder)
|
endforeach(folder)
|
||||||
|
|
||||||
|
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||||
|
set(LIBUSB_PATH "${CMAKE_BINARY_DIR}/vcpkg_installed/x64-osx/debug/lib/libusb-1.0.0.dylib")
|
||||||
|
else()
|
||||||
|
set(LIBUSB_PATH "${CMAKE_BINARY_DIR}/vcpkg_installed/x64-osx/lib/libusb-1.0.0.dylib")
|
||||||
|
endif()
|
||||||
|
|
||||||
add_custom_command (TARGET CemuBin POST_BUILD
|
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 "/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 "${LIBUSB_PATH}" "${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 ${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 -add_rpath @executable_path/../Frameworks ${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}")
|
COMMAND bash -c "install_name_tool -change ${LIBUSB_PATH} @executable_path/../Frameworks/libusb-1.0.0.dylib ${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/MacOS/${OUTPUT_NAME}")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
set_target_properties(CemuBin PROPERTIES
|
set_target_properties(CemuBin PROPERTIES
|
||||||
|
|
|
@ -463,8 +463,8 @@ add_library(CemuCafe
|
||||||
OS/libs/nsyshid/BackendEmulated.h
|
OS/libs/nsyshid/BackendEmulated.h
|
||||||
OS/libs/nsyshid/BackendLibusb.cpp
|
OS/libs/nsyshid/BackendLibusb.cpp
|
||||||
OS/libs/nsyshid/BackendLibusb.h
|
OS/libs/nsyshid/BackendLibusb.h
|
||||||
OS/libs/nsyshid/BackendWindowsHID.cpp
|
OS/libs/nsyshid/Dimensions.cpp
|
||||||
OS/libs/nsyshid/BackendWindowsHID.h
|
OS/libs/nsyshid/Dimensions.h
|
||||||
OS/libs/nsyshid/Infinity.cpp
|
OS/libs/nsyshid/Infinity.cpp
|
||||||
OS/libs/nsyshid/Infinity.h
|
OS/libs/nsyshid/Infinity.h
|
||||||
OS/libs/nsyshid/Skylander.cpp
|
OS/libs/nsyshid/Skylander.cpp
|
||||||
|
@ -530,6 +530,12 @@ set_property(TARGET CemuCafe PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CON
|
||||||
|
|
||||||
target_include_directories(CemuCafe PUBLIC "../")
|
target_include_directories(CemuCafe PUBLIC "../")
|
||||||
|
|
||||||
|
if (glslang_VERSION VERSION_LESS "15.0.0")
|
||||||
|
set(glslang_target "glslang::SPIRV")
|
||||||
|
else()
|
||||||
|
set(glslang_target "glslang")
|
||||||
|
endif()
|
||||||
|
|
||||||
target_link_libraries(CemuCafe PRIVATE
|
target_link_libraries(CemuCafe PRIVATE
|
||||||
CemuAsm
|
CemuAsm
|
||||||
CemuAudio
|
CemuAudio
|
||||||
|
@ -546,7 +552,7 @@ target_link_libraries(CemuCafe PRIVATE
|
||||||
Boost::nowide
|
Boost::nowide
|
||||||
CURL::libcurl
|
CURL::libcurl
|
||||||
fmt::fmt
|
fmt::fmt
|
||||||
glslang::SPIRV
|
${glslang_target}
|
||||||
ih264d
|
ih264d
|
||||||
OpenSSL::Crypto
|
OpenSSL::Crypto
|
||||||
OpenSSL::SSL
|
OpenSSL::SSL
|
||||||
|
@ -562,15 +568,16 @@ if (ENABLE_WAYLAND)
|
||||||
target_link_libraries(CemuCafe PUBLIC Wayland::Client)
|
target_link_libraries(CemuCafe PUBLIC Wayland::Client)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (ENABLE_NSYSHID_LIBUSB)
|
if (ENABLE_VCPKG)
|
||||||
if (ENABLE_VCPKG)
|
if(WIN32)
|
||||||
find_package(PkgConfig REQUIRED)
|
set(PKG_CONFIG_EXECUTABLE "${VCPKG_INSTALLED_DIR}/x64-windows/tools/pkgconf/pkgconf.exe")
|
||||||
pkg_check_modules(libusb REQUIRED IMPORTED_TARGET libusb-1.0)
|
endif()
|
||||||
target_link_libraries(CemuCafe PRIVATE PkgConfig::libusb)
|
find_package(PkgConfig REQUIRED)
|
||||||
else ()
|
pkg_check_modules(libusb REQUIRED IMPORTED_TARGET libusb-1.0)
|
||||||
find_package(libusb MODULE REQUIRED)
|
target_link_libraries(CemuCafe PRIVATE PkgConfig::libusb)
|
||||||
target_link_libraries(CemuCafe PRIVATE libusb::libusb)
|
else ()
|
||||||
endif ()
|
find_package(libusb MODULE REQUIRED)
|
||||||
|
target_link_libraries(CemuCafe PRIVATE libusb::libusb)
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
if (ENABLE_WXWIDGETS)
|
if (ENABLE_WXWIDGETS)
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
#include "audio/IAudioAPI.h"
|
#include "audio/IAudioAPI.h"
|
||||||
#include "audio/IAudioInputAPI.h"
|
#include "audio/IAudioInputAPI.h"
|
||||||
#include "config/ActiveSettings.h"
|
#include "config/ActiveSettings.h"
|
||||||
|
#include "config/LaunchSettings.h"
|
||||||
#include "Cafe/TitleList/GameInfo.h"
|
#include "Cafe/TitleList/GameInfo.h"
|
||||||
#include "Cafe/GraphicPack/GraphicPack2.h"
|
#include "Cafe/GraphicPack/GraphicPack2.h"
|
||||||
#include "util/helpers/SystemException.h"
|
#include "util/helpers/SystemException.h"
|
||||||
|
@ -396,7 +397,7 @@ void cemu_initForGame()
|
||||||
// replace any known function signatures with our HLE implementations and patch bugs in the games
|
// replace any known function signatures with our HLE implementations and patch bugs in the games
|
||||||
GamePatch_scan();
|
GamePatch_scan();
|
||||||
}
|
}
|
||||||
LatteGPUState.alwaysDisplayDRC = ActiveSettings::DisplayDRCEnabled();
|
LatteGPUState.isDRCPrimary = ActiveSettings::DisplayDRCEnabled();
|
||||||
InfoLog_PrintActiveSettings();
|
InfoLog_PrintActiveSettings();
|
||||||
Latte_Start();
|
Latte_Start();
|
||||||
// check for debugger entrypoint bp
|
// check for debugger entrypoint bp
|
||||||
|
@ -637,40 +638,40 @@ namespace CafeSystem
|
||||||
fsc_unmount("/cemuBossStorage/", FSC_PRIORITY_BASE);
|
fsc_unmount("/cemuBossStorage/", FSC_PRIORITY_BASE);
|
||||||
}
|
}
|
||||||
|
|
||||||
STATUS_CODE LoadAndMountForegroundTitle(TitleId titleId)
|
PREPARE_STATUS_CODE LoadAndMountForegroundTitle(TitleId titleId)
|
||||||
{
|
{
|
||||||
cemuLog_log(LogType::Force, "Mounting title {:016x}", (uint64)titleId);
|
cemuLog_log(LogType::Force, "Mounting title {:016x}", (uint64)titleId);
|
||||||
sGameInfo_ForegroundTitle = CafeTitleList::GetGameInfo(titleId);
|
sGameInfo_ForegroundTitle = CafeTitleList::GetGameInfo(titleId);
|
||||||
if (!sGameInfo_ForegroundTitle.IsValid())
|
if (!sGameInfo_ForegroundTitle.IsValid())
|
||||||
{
|
{
|
||||||
cemuLog_log(LogType::Force, "Mounting failed: Game meta information is either missing, inaccessible or not valid (missing or invalid .xml files in code and meta folder)");
|
cemuLog_log(LogType::Force, "Mounting failed: Game meta information is either missing, inaccessible or not valid (missing or invalid .xml files in code and meta folder)");
|
||||||
return STATUS_CODE::UNABLE_TO_MOUNT;
|
return PREPARE_STATUS_CODE::UNABLE_TO_MOUNT;
|
||||||
}
|
}
|
||||||
// check base
|
// check base
|
||||||
TitleInfo& titleBase = sGameInfo_ForegroundTitle.GetBase();
|
TitleInfo& titleBase = sGameInfo_ForegroundTitle.GetBase();
|
||||||
if (!titleBase.IsValid())
|
if (!titleBase.IsValid())
|
||||||
return STATUS_CODE::UNABLE_TO_MOUNT;
|
return PREPARE_STATUS_CODE::UNABLE_TO_MOUNT;
|
||||||
if(!titleBase.ParseXmlInfo())
|
if(!titleBase.ParseXmlInfo())
|
||||||
return STATUS_CODE::UNABLE_TO_MOUNT;
|
return PREPARE_STATUS_CODE::UNABLE_TO_MOUNT;
|
||||||
cemuLog_log(LogType::Force, "Base: {}", titleBase.GetPrintPath());
|
cemuLog_log(LogType::Force, "Base: {}", titleBase.GetPrintPath());
|
||||||
// mount base
|
// mount base
|
||||||
if (!titleBase.Mount("/vol/content", "content", FSC_PRIORITY_BASE) || !titleBase.Mount(GetInternalVirtualCodeFolder(), "code", FSC_PRIORITY_BASE))
|
if (!titleBase.Mount("/vol/content", "content", FSC_PRIORITY_BASE) || !titleBase.Mount(GetInternalVirtualCodeFolder(), "code", FSC_PRIORITY_BASE))
|
||||||
{
|
{
|
||||||
cemuLog_log(LogType::Force, "Mounting failed");
|
cemuLog_log(LogType::Force, "Mounting failed");
|
||||||
return STATUS_CODE::UNABLE_TO_MOUNT;
|
return PREPARE_STATUS_CODE::UNABLE_TO_MOUNT;
|
||||||
}
|
}
|
||||||
// check update
|
// check update
|
||||||
TitleInfo& titleUpdate = sGameInfo_ForegroundTitle.GetUpdate();
|
TitleInfo& titleUpdate = sGameInfo_ForegroundTitle.GetUpdate();
|
||||||
if (titleUpdate.IsValid())
|
if (titleUpdate.IsValid())
|
||||||
{
|
{
|
||||||
if (!titleUpdate.ParseXmlInfo())
|
if (!titleUpdate.ParseXmlInfo())
|
||||||
return STATUS_CODE::UNABLE_TO_MOUNT;
|
return PREPARE_STATUS_CODE::UNABLE_TO_MOUNT;
|
||||||
cemuLog_log(LogType::Force, "Update: {}", titleUpdate.GetPrintPath());
|
cemuLog_log(LogType::Force, "Update: {}", titleUpdate.GetPrintPath());
|
||||||
// mount update
|
// mount update
|
||||||
if (!titleUpdate.Mount("/vol/content", "content", FSC_PRIORITY_PATCH) || !titleUpdate.Mount(GetInternalVirtualCodeFolder(), "code", FSC_PRIORITY_PATCH))
|
if (!titleUpdate.Mount("/vol/content", "content", FSC_PRIORITY_PATCH) || !titleUpdate.Mount(GetInternalVirtualCodeFolder(), "code", FSC_PRIORITY_PATCH))
|
||||||
{
|
{
|
||||||
cemuLog_log(LogType::Force, "Mounting failed");
|
cemuLog_log(LogType::Force, "Mounting failed");
|
||||||
return STATUS_CODE::UNABLE_TO_MOUNT;
|
return PREPARE_STATUS_CODE::UNABLE_TO_MOUNT;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -682,20 +683,20 @@ namespace CafeSystem
|
||||||
// todo - support for multi-title AOC
|
// todo - support for multi-title AOC
|
||||||
TitleInfo& titleAOC = aocList[0];
|
TitleInfo& titleAOC = aocList[0];
|
||||||
if (!titleAOC.ParseXmlInfo())
|
if (!titleAOC.ParseXmlInfo())
|
||||||
return STATUS_CODE::UNABLE_TO_MOUNT;
|
return PREPARE_STATUS_CODE::UNABLE_TO_MOUNT;
|
||||||
cemu_assert_debug(titleAOC.IsValid());
|
cemu_assert_debug(titleAOC.IsValid());
|
||||||
cemuLog_log(LogType::Force, "DLC: {}", titleAOC.GetPrintPath());
|
cemuLog_log(LogType::Force, "DLC: {}", titleAOC.GetPrintPath());
|
||||||
// mount AOC
|
// mount AOC
|
||||||
if (!titleAOC.Mount(fmt::format("/vol/aoc{:016x}", titleAOC.GetAppTitleId()), "content", FSC_PRIORITY_PATCH))
|
if (!titleAOC.Mount(fmt::format("/vol/aoc{:016x}", titleAOC.GetAppTitleId()), "content", FSC_PRIORITY_PATCH))
|
||||||
{
|
{
|
||||||
cemuLog_log(LogType::Force, "Mounting failed");
|
cemuLog_log(LogType::Force, "Mounting failed");
|
||||||
return STATUS_CODE::UNABLE_TO_MOUNT;
|
return PREPARE_STATUS_CODE::UNABLE_TO_MOUNT;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
cemuLog_log(LogType::Force, "DLC: Not present");
|
cemuLog_log(LogType::Force, "DLC: Not present");
|
||||||
sForegroundTitleId = titleId;
|
sForegroundTitleId = titleId;
|
||||||
return STATUS_CODE::SUCCESS;
|
return PREPARE_STATUS_CODE::SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
void UnmountForegroundTitle()
|
void UnmountForegroundTitle()
|
||||||
|
@ -723,7 +724,7 @@ namespace CafeSystem
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
STATUS_CODE SetupExecutable()
|
PREPARE_STATUS_CODE SetupExecutable()
|
||||||
{
|
{
|
||||||
// set rpx path from cos.xml if available
|
// set rpx path from cos.xml if available
|
||||||
_pathToBaseExecutable = _pathToExecutable;
|
_pathToBaseExecutable = _pathToExecutable;
|
||||||
|
@ -755,7 +756,7 @@ namespace CafeSystem
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
LoadMainExecutable();
|
LoadMainExecutable();
|
||||||
return STATUS_CODE::SUCCESS;
|
return PREPARE_STATUS_CODE::SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetupMemorySpace()
|
void SetupMemorySpace()
|
||||||
|
@ -769,7 +770,7 @@ namespace CafeSystem
|
||||||
memory_unmapForCurrentTitle();
|
memory_unmapForCurrentTitle();
|
||||||
}
|
}
|
||||||
|
|
||||||
STATUS_CODE PrepareForegroundTitle(TitleId titleId)
|
PREPARE_STATUS_CODE PrepareForegroundTitle(TitleId titleId)
|
||||||
{
|
{
|
||||||
CafeTitleList::WaitForMandatoryScan();
|
CafeTitleList::WaitForMandatoryScan();
|
||||||
sLaunchModeIsStandalone = false;
|
sLaunchModeIsStandalone = false;
|
||||||
|
@ -780,21 +781,21 @@ namespace CafeSystem
|
||||||
// mount mlc storage
|
// mount mlc storage
|
||||||
MountBaseDirectories();
|
MountBaseDirectories();
|
||||||
// mount title folders
|
// mount title folders
|
||||||
STATUS_CODE r = LoadAndMountForegroundTitle(titleId);
|
PREPARE_STATUS_CODE r = LoadAndMountForegroundTitle(titleId);
|
||||||
if (r != STATUS_CODE::SUCCESS)
|
if (r != PREPARE_STATUS_CODE::SUCCESS)
|
||||||
return r;
|
return r;
|
||||||
gameProfile_load();
|
gameProfile_load();
|
||||||
// setup memory space and PPC recompiler
|
// setup memory space and PPC recompiler
|
||||||
SetupMemorySpace();
|
SetupMemorySpace();
|
||||||
PPCRecompiler_init();
|
PPCRecompiler_init();
|
||||||
r = SetupExecutable(); // load RPX
|
r = SetupExecutable(); // load RPX
|
||||||
if (r != STATUS_CODE::SUCCESS)
|
if (r != PREPARE_STATUS_CODE::SUCCESS)
|
||||||
return r;
|
return r;
|
||||||
InitVirtualMlcStorage();
|
InitVirtualMlcStorage();
|
||||||
return STATUS_CODE::SUCCESS;
|
return PREPARE_STATUS_CODE::SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
STATUS_CODE PrepareForegroundTitleFromStandaloneRPX(const fs::path& path)
|
PREPARE_STATUS_CODE PrepareForegroundTitleFromStandaloneRPX(const fs::path& path)
|
||||||
{
|
{
|
||||||
sLaunchModeIsStandalone = true;
|
sLaunchModeIsStandalone = true;
|
||||||
cemuLog_log(LogType::Force, "Launching executable in standalone mode due to incorrect layout or missing meta files");
|
cemuLog_log(LogType::Force, "Launching executable in standalone mode due to incorrect layout or missing meta files");
|
||||||
|
@ -812,7 +813,7 @@ namespace CafeSystem
|
||||||
if (!r)
|
if (!r)
|
||||||
{
|
{
|
||||||
cemuLog_log(LogType::Force, "Failed to mount {}", _pathToUtf8(contentPath));
|
cemuLog_log(LogType::Force, "Failed to mount {}", _pathToUtf8(contentPath));
|
||||||
return STATUS_CODE::UNABLE_TO_MOUNT;
|
return PREPARE_STATUS_CODE::UNABLE_TO_MOUNT;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -824,7 +825,7 @@ namespace CafeSystem
|
||||||
// since a lot of systems (including save folder location) rely on a TitleId, we derive a placeholder id from the executable hash
|
// since a lot of systems (including save folder location) rely on a TitleId, we derive a placeholder id from the executable hash
|
||||||
auto execData = fsc_extractFile(_pathToExecutable.c_str());
|
auto execData = fsc_extractFile(_pathToExecutable.c_str());
|
||||||
if (!execData)
|
if (!execData)
|
||||||
return STATUS_CODE::INVALID_RPX;
|
return PREPARE_STATUS_CODE::INVALID_RPX;
|
||||||
uint32 h = generateHashFromRawRPXData(execData->data(), execData->size());
|
uint32 h = generateHashFromRawRPXData(execData->data(), execData->size());
|
||||||
sForegroundTitleId = 0xFFFFFFFF00000000ULL | (uint64)h;
|
sForegroundTitleId = 0xFFFFFFFF00000000ULL | (uint64)h;
|
||||||
cemuLog_log(LogType::Force, "Generated placeholder TitleId: {:016x}", sForegroundTitleId);
|
cemuLog_log(LogType::Force, "Generated placeholder TitleId: {:016x}", sForegroundTitleId);
|
||||||
|
@ -834,7 +835,7 @@ namespace CafeSystem
|
||||||
// load executable
|
// load executable
|
||||||
SetupExecutable();
|
SetupExecutable();
|
||||||
InitVirtualMlcStorage();
|
InitVirtualMlcStorage();
|
||||||
return STATUS_CODE::SUCCESS;
|
return PREPARE_STATUS_CODE::SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _LaunchTitleThread()
|
void _LaunchTitleThread()
|
||||||
|
@ -843,7 +844,7 @@ namespace CafeSystem
|
||||||
module->TitleStart();
|
module->TitleStart();
|
||||||
cemu_initForGame();
|
cemu_initForGame();
|
||||||
// enter scheduler
|
// enter scheduler
|
||||||
if (ActiveSettings::GetCPUMode() == CPUMode::MulticoreRecompiler)
|
if (ActiveSettings::GetCPUMode() == CPUMode::MulticoreRecompiler && !LaunchSettings::ForceInterpreter())
|
||||||
coreinit::OSSchedulerBegin(3);
|
coreinit::OSSchedulerBegin(3);
|
||||||
else
|
else
|
||||||
coreinit::OSSchedulerBegin(1);
|
coreinit::OSSchedulerBegin(1);
|
||||||
|
|
|
@ -15,20 +15,19 @@ namespace CafeSystem
|
||||||
virtual void CafeRecreateCanvas() = 0;
|
virtual void CafeRecreateCanvas() = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class STATUS_CODE
|
enum class PREPARE_STATUS_CODE
|
||||||
{
|
{
|
||||||
SUCCESS,
|
SUCCESS,
|
||||||
INVALID_RPX,
|
INVALID_RPX,
|
||||||
UNABLE_TO_MOUNT, // failed to mount through TitleInfo (most likely caused by an invalid or outdated path)
|
UNABLE_TO_MOUNT, // failed to mount through TitleInfo (most likely caused by an invalid or outdated path)
|
||||||
//BAD_META_DATA, - the title list only stores titles with valid meta, so this error code is impossible
|
|
||||||
};
|
};
|
||||||
|
|
||||||
void Initialize();
|
void Initialize();
|
||||||
void SetImplementation(SystemImplementation* impl);
|
void SetImplementation(SystemImplementation* impl);
|
||||||
void Shutdown();
|
void Shutdown();
|
||||||
|
|
||||||
STATUS_CODE PrepareForegroundTitle(TitleId titleId);
|
PREPARE_STATUS_CODE PrepareForegroundTitle(TitleId titleId);
|
||||||
STATUS_CODE PrepareForegroundTitleFromStandaloneRPX(const fs::path& path);
|
PREPARE_STATUS_CODE PrepareForegroundTitleFromStandaloneRPX(const fs::path& path);
|
||||||
void LaunchForegroundTitle();
|
void LaunchForegroundTitle();
|
||||||
bool IsTitleRunning();
|
bool IsTitleRunning();
|
||||||
|
|
||||||
|
|
|
@ -3,8 +3,7 @@
|
||||||
#include "Cemu/ncrypto/ncrypto.h"
|
#include "Cemu/ncrypto/ncrypto.h"
|
||||||
#include "Cafe/Filesystem/WUD/wud.h"
|
#include "Cafe/Filesystem/WUD/wud.h"
|
||||||
#include "util/crypto/aes128.h"
|
#include "util/crypto/aes128.h"
|
||||||
#include "openssl/evp.h" /* EVP_Digest */
|
#include "openssl/sha.h" /* SHA1 / SHA256 */
|
||||||
#include "openssl/sha.h" /* SHA1 / SHA256_DIGEST_LENGTH */
|
|
||||||
#include "fstUtil.h"
|
#include "fstUtil.h"
|
||||||
|
|
||||||
#include "FST.h"
|
#include "FST.h"
|
||||||
|
@ -141,7 +140,7 @@ struct DiscPartitionTableHeader
|
||||||
static constexpr uint32 MAGIC_VALUE = 0xCCA6E67B;
|
static constexpr uint32 MAGIC_VALUE = 0xCCA6E67B;
|
||||||
|
|
||||||
/* +0x00 */ uint32be magic;
|
/* +0x00 */ uint32be magic;
|
||||||
/* +0x04 */ uint32be sectorSize; // must be 0x8000?
|
/* +0x04 */ uint32be blockSize; // must be 0x8000?
|
||||||
/* +0x08 */ uint8 partitionTableHash[20]; // hash of the data range at +0x800 to end of sector (0x8000)
|
/* +0x08 */ uint8 partitionTableHash[20]; // hash of the data range at +0x800 to end of sector (0x8000)
|
||||||
/* +0x1C */ uint32be numPartitions;
|
/* +0x1C */ uint32be numPartitions;
|
||||||
};
|
};
|
||||||
|
@ -164,10 +163,10 @@ struct DiscPartitionHeader
|
||||||
static constexpr uint32 MAGIC_VALUE = 0xCC93A4F5;
|
static constexpr uint32 MAGIC_VALUE = 0xCC93A4F5;
|
||||||
|
|
||||||
/* +0x00 */ uint32be magic;
|
/* +0x00 */ uint32be magic;
|
||||||
/* +0x04 */ uint32be sectorSize; // must match DISC_SECTOR_SIZE
|
/* +0x04 */ uint32be sectorSize; // must match DISC_SECTOR_SIZE for hashed blocks
|
||||||
|
|
||||||
/* +0x08 */ uint32be ukn008;
|
/* +0x08 */ uint32be ukn008;
|
||||||
/* +0x0C */ uint32be ukn00C;
|
/* +0x0C */ uint32be ukn00C; // h3 array size?
|
||||||
/* +0x10 */ uint32be h3HashNum;
|
/* +0x10 */ uint32be h3HashNum;
|
||||||
/* +0x14 */ uint32be fstSize; // in bytes
|
/* +0x14 */ uint32be fstSize; // in bytes
|
||||||
/* +0x18 */ uint32be fstSector; // relative to partition start
|
/* +0x18 */ uint32be fstSector; // relative to partition start
|
||||||
|
@ -178,13 +177,15 @@ struct DiscPartitionHeader
|
||||||
/* +0x24 */ uint8 fstHashType;
|
/* +0x24 */ uint8 fstHashType;
|
||||||
/* +0x25 */ uint8 fstEncryptionType; // purpose of this isn't really understood. Maybe it controls which key is being used? (1 -> disc key, 2 -> partition key)
|
/* +0x25 */ uint8 fstEncryptionType; // purpose of this isn't really understood. Maybe it controls which key is being used? (1 -> disc key, 2 -> partition key)
|
||||||
|
|
||||||
/* +0x26 */ uint8 versionA;
|
/* +0x26 */ uint8be versionA;
|
||||||
/* +0x27 */ uint8 ukn027; // also a version field?
|
/* +0x27 */ uint8be ukn027; // also a version field?
|
||||||
|
|
||||||
// there is an array at +0x40 ? Related to H3 list. Also related to value at +0x0C and h3HashNum
|
// there is an array at +0x40 ? Related to H3 list. Also related to value at +0x0C and h3HashNum
|
||||||
|
/* +0x28 */ uint8be _uknOrPadding028[0x18];
|
||||||
|
/* +0x40 */ uint8be h3HashArray[32]; // dynamic size. Only present if fstHashType != 0
|
||||||
};
|
};
|
||||||
|
|
||||||
static_assert(sizeof(DiscPartitionHeader) == 0x28);
|
static_assert(sizeof(DiscPartitionHeader) == 0x40+0x20);
|
||||||
|
|
||||||
bool FSTVolume::FindDiscKey(const fs::path& path, NCrypto::AesKey& discTitleKey)
|
bool FSTVolume::FindDiscKey(const fs::path& path, NCrypto::AesKey& discTitleKey)
|
||||||
{
|
{
|
||||||
|
@ -269,7 +270,7 @@ FSTVolume* FSTVolume::OpenFromDiscImage(const fs::path& path, NCrypto::AesKey& d
|
||||||
cemuLog_log(LogType::Force, "Disc image rejected because decryption failed");
|
cemuLog_log(LogType::Force, "Disc image rejected because decryption failed");
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
if (partitionHeader->sectorSize != DISC_SECTOR_SIZE)
|
if (partitionHeader->blockSize != DISC_SECTOR_SIZE)
|
||||||
{
|
{
|
||||||
cemuLog_log(LogType::Force, "Disc image rejected because partition sector size is invalid");
|
cemuLog_log(LogType::Force, "Disc image rejected because partition sector size is invalid");
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
@ -336,6 +337,9 @@ FSTVolume* FSTVolume::OpenFromDiscImage(const fs::path& path, NCrypto::AesKey& d
|
||||||
cemu_assert_debug(partitionHeaderSI.fstEncryptionType == 1);
|
cemu_assert_debug(partitionHeaderSI.fstEncryptionType == 1);
|
||||||
// todo - check other fields?
|
// todo - check other fields?
|
||||||
|
|
||||||
|
if(partitionHeaderSI.fstHashType == 0 && partitionHeaderSI.h3HashNum != 0)
|
||||||
|
cemuLog_log(LogType::Force, "FST: Partition uses unhashed blocks but stores a non-zero amount of H3 hashes");
|
||||||
|
|
||||||
// GM partition
|
// GM partition
|
||||||
DiscPartitionHeader partitionHeaderGM{};
|
DiscPartitionHeader partitionHeaderGM{};
|
||||||
if (!readPartitionHeader(partitionHeaderGM, gmPartitionIndex))
|
if (!readPartitionHeader(partitionHeaderGM, gmPartitionIndex))
|
||||||
|
@ -349,9 +353,10 @@ FSTVolume* FSTVolume::OpenFromDiscImage(const fs::path& path, NCrypto::AesKey& d
|
||||||
// if decryption is necessary
|
// if decryption is necessary
|
||||||
// load SI FST
|
// load SI FST
|
||||||
dataSource->SetBaseOffset((uint64)partitionArray[siPartitionIndex].partitionAddress * DISC_SECTOR_SIZE);
|
dataSource->SetBaseOffset((uint64)partitionArray[siPartitionIndex].partitionAddress * DISC_SECTOR_SIZE);
|
||||||
auto siFST = OpenFST(dataSource.get(), (uint64)partitionHeaderSI.fstSector * DISC_SECTOR_SIZE, partitionHeaderSI.fstSize, &discTitleKey, static_cast<FSTVolume::ClusterHashMode>(partitionHeaderSI.fstHashType));
|
auto siFST = OpenFST(dataSource.get(), (uint64)partitionHeaderSI.fstSector * DISC_SECTOR_SIZE, partitionHeaderSI.fstSize, &discTitleKey, static_cast<FSTVolume::ClusterHashMode>(partitionHeaderSI.fstHashType), nullptr);
|
||||||
if (!siFST)
|
if (!siFST)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
cemu_assert_debug(!(siFST->HashIsDisabled() && partitionHeaderSI.h3HashNum != 0)); // if hash is disabled, no H3 data may be present
|
||||||
// load ticket file for partition that we want to decrypt
|
// load ticket file for partition that we want to decrypt
|
||||||
NCrypto::ETicketParser ticketParser;
|
NCrypto::ETicketParser ticketParser;
|
||||||
std::vector<uint8> ticketData = siFST->ExtractFile(fmt::format("{:02x}/title.tik", gmPartitionIndex));
|
std::vector<uint8> ticketData = siFST->ExtractFile(fmt::format("{:02x}/title.tik", gmPartitionIndex));
|
||||||
|
@ -360,16 +365,32 @@ FSTVolume* FSTVolume::OpenFromDiscImage(const fs::path& path, NCrypto::AesKey& d
|
||||||
cemuLog_log(LogType::Force, "Disc image ticket file is invalid");
|
cemuLog_log(LogType::Force, "Disc image ticket file is invalid");
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
#if 0
|
||||||
|
// each SI partition seems to contain a title.tmd that we could parse and which should have information about the associated GM partition
|
||||||
|
// but the console seems to ignore this file for disc images, at least when mounting, so we shouldn't rely on it either
|
||||||
|
std::vector<uint8> tmdData = siFST->ExtractFile(fmt::format("{:02x}/title.tmd", gmPartitionIndex));
|
||||||
|
if (tmdData.empty())
|
||||||
|
{
|
||||||
|
cemuLog_log(LogType::Force, "Disc image TMD file is missing");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
// parse TMD
|
||||||
|
NCrypto::TMDParser tmdParser;
|
||||||
|
if (!tmdParser.parse(tmdData.data(), tmdData.size()))
|
||||||
|
{
|
||||||
|
cemuLog_log(LogType::Force, "Disc image TMD file is invalid");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
delete siFST;
|
delete siFST;
|
||||||
|
|
||||||
NCrypto::AesKey gmTitleKey;
|
NCrypto::AesKey gmTitleKey;
|
||||||
ticketParser.GetTitleKey(gmTitleKey);
|
ticketParser.GetTitleKey(gmTitleKey);
|
||||||
|
|
||||||
// load GM partition
|
// load GM partition
|
||||||
dataSource->SetBaseOffset((uint64)partitionArray[gmPartitionIndex].partitionAddress * DISC_SECTOR_SIZE);
|
dataSource->SetBaseOffset((uint64)partitionArray[gmPartitionIndex].partitionAddress * DISC_SECTOR_SIZE);
|
||||||
FSTVolume* r = OpenFST(std::move(dataSource), (uint64)partitionHeaderGM.fstSector * DISC_SECTOR_SIZE, partitionHeaderGM.fstSize, &gmTitleKey, static_cast<FSTVolume::ClusterHashMode>(partitionHeaderGM.fstHashType));
|
FSTVolume* r = OpenFST(std::move(dataSource), (uint64)partitionHeaderGM.fstSector * DISC_SECTOR_SIZE, partitionHeaderGM.fstSize, &gmTitleKey, static_cast<FSTVolume::ClusterHashMode>(partitionHeaderGM.fstHashType), nullptr);
|
||||||
if (r)
|
if (r)
|
||||||
SET_FST_ERROR(OK);
|
SET_FST_ERROR(OK);
|
||||||
|
cemu_assert_debug(!(r->HashIsDisabled() && partitionHeaderGM.h3HashNum != 0)); // if hash is disabled, no H3 data may be present
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -426,15 +447,15 @@ FSTVolume* FSTVolume::OpenFromContentFolder(fs::path folderPath, ErrorCode* erro
|
||||||
}
|
}
|
||||||
// load FST
|
// load FST
|
||||||
// fstSize = size of first cluster?
|
// fstSize = size of first cluster?
|
||||||
FSTVolume* fstVolume = FSTVolume::OpenFST(std::move(dataSource), 0, fstSize, &titleKey, fstHashMode);
|
FSTVolume* fstVolume = FSTVolume::OpenFST(std::move(dataSource), 0, fstSize, &titleKey, fstHashMode, &tmdParser);
|
||||||
if (fstVolume)
|
if (fstVolume)
|
||||||
SET_FST_ERROR(OK);
|
SET_FST_ERROR(OK);
|
||||||
return fstVolume;
|
return fstVolume;
|
||||||
}
|
}
|
||||||
|
|
||||||
FSTVolume* FSTVolume::OpenFST(FSTDataSource* dataSource, uint64 fstOffset, uint32 fstSize, NCrypto::AesKey* partitionTitleKey, ClusterHashMode fstHashMode)
|
FSTVolume* FSTVolume::OpenFST(FSTDataSource* dataSource, uint64 fstOffset, uint32 fstSize, NCrypto::AesKey* partitionTitleKey, ClusterHashMode fstHashMode, NCrypto::TMDParser* optionalTMD)
|
||||||
{
|
{
|
||||||
cemu_assert_debug(fstHashMode != ClusterHashMode::RAW || fstHashMode != ClusterHashMode::RAW2);
|
cemu_assert_debug(fstHashMode != ClusterHashMode::RAW || fstHashMode != ClusterHashMode::RAW_STREAM);
|
||||||
if (fstSize < sizeof(FSTHeader))
|
if (fstSize < sizeof(FSTHeader))
|
||||||
return nullptr;
|
return nullptr;
|
||||||
constexpr uint64 FST_CLUSTER_OFFSET = 0;
|
constexpr uint64 FST_CLUSTER_OFFSET = 0;
|
||||||
|
@ -465,6 +486,34 @@ FSTVolume* FSTVolume::OpenFST(FSTDataSource* dataSource, uint64 fstOffset, uint3
|
||||||
clusterTable[i].offset = clusterDataTable[i].offset;
|
clusterTable[i].offset = clusterDataTable[i].offset;
|
||||||
clusterTable[i].size = clusterDataTable[i].size;
|
clusterTable[i].size = clusterDataTable[i].size;
|
||||||
clusterTable[i].hashMode = static_cast<FSTVolume::ClusterHashMode>((uint8)clusterDataTable[i].hashMode);
|
clusterTable[i].hashMode = static_cast<FSTVolume::ClusterHashMode>((uint8)clusterDataTable[i].hashMode);
|
||||||
|
clusterTable[i].hasContentHash = false; // from the TMD file (H4?)
|
||||||
|
}
|
||||||
|
// if the TMD is available (when opening .app files) we can use the extra info from it to validate unhashed clusters
|
||||||
|
// each content entry in the TMD corresponds to one cluster used by the FST
|
||||||
|
if(optionalTMD)
|
||||||
|
{
|
||||||
|
if(numCluster != optionalTMD->GetContentList().size())
|
||||||
|
{
|
||||||
|
cemuLog_log(LogType::Force, "FST: Number of clusters does not match TMD content list");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
auto& contentList = optionalTMD->GetContentList();
|
||||||
|
for(size_t i=0; i<contentList.size(); i++)
|
||||||
|
{
|
||||||
|
auto& cluster = clusterTable[i];
|
||||||
|
auto& content = contentList[i];
|
||||||
|
cluster.hasContentHash = true;
|
||||||
|
cluster.contentHashIsSHA1 = HAS_FLAG(contentList[i].contentFlags, NCrypto::TMDParser::TMDContentFlags::FLAG_SHA1);
|
||||||
|
cluster.contentSize = content.size;
|
||||||
|
static_assert(sizeof(content.hash32) == sizeof(cluster.contentHash32));
|
||||||
|
memcpy(cluster.contentHash32, content.hash32, sizeof(cluster.contentHash32));
|
||||||
|
// if unhashed mode, then initialize the hash context
|
||||||
|
if(cluster.hashMode == ClusterHashMode::RAW || cluster.hashMode == ClusterHashMode::RAW_STREAM)
|
||||||
|
{
|
||||||
|
cluster.singleHashCtx.reset(EVP_MD_CTX_new());
|
||||||
|
EVP_DigestInit_ex(cluster.singleHashCtx.get(), cluster.contentHashIsSHA1 ? EVP_sha1() : EVP_sha256(), nullptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// preprocess FST table
|
// preprocess FST table
|
||||||
FSTHeader_FileEntry* fileTable = (FSTHeader_FileEntry*)(clusterDataTable + numCluster);
|
FSTHeader_FileEntry* fileTable = (FSTHeader_FileEntry*)(clusterDataTable + numCluster);
|
||||||
|
@ -491,16 +540,17 @@ FSTVolume* FSTVolume::OpenFST(FSTDataSource* dataSource, uint64 fstOffset, uint3
|
||||||
fstVolume->m_offsetFactor = fstHeader->offsetFactor;
|
fstVolume->m_offsetFactor = fstHeader->offsetFactor;
|
||||||
fstVolume->m_sectorSize = DISC_SECTOR_SIZE;
|
fstVolume->m_sectorSize = DISC_SECTOR_SIZE;
|
||||||
fstVolume->m_partitionTitlekey = *partitionTitleKey;
|
fstVolume->m_partitionTitlekey = *partitionTitleKey;
|
||||||
std::swap(fstVolume->m_cluster, clusterTable);
|
fstVolume->m_hashIsDisabled = fstHeader->hashIsDisabled != 0;
|
||||||
std::swap(fstVolume->m_entries, fstEntries);
|
fstVolume->m_cluster = std::move(clusterTable);
|
||||||
std::swap(fstVolume->m_nameStringTable, nameStringTable);
|
fstVolume->m_entries = std::move(fstEntries);
|
||||||
|
fstVolume->m_nameStringTable = std::move(nameStringTable);
|
||||||
return fstVolume;
|
return fstVolume;
|
||||||
}
|
}
|
||||||
|
|
||||||
FSTVolume* FSTVolume::OpenFST(std::unique_ptr<FSTDataSource> dataSource, uint64 fstOffset, uint32 fstSize, NCrypto::AesKey* partitionTitleKey, ClusterHashMode fstHashMode)
|
FSTVolume* FSTVolume::OpenFST(std::unique_ptr<FSTDataSource> dataSource, uint64 fstOffset, uint32 fstSize, NCrypto::AesKey* partitionTitleKey, ClusterHashMode fstHashMode, NCrypto::TMDParser* optionalTMD)
|
||||||
{
|
{
|
||||||
FSTDataSource* ds = dataSource.release();
|
FSTDataSource* ds = dataSource.release();
|
||||||
FSTVolume* fstVolume = OpenFST(ds, fstOffset, fstSize, partitionTitleKey, fstHashMode);
|
FSTVolume* fstVolume = OpenFST(ds, fstOffset, fstSize, partitionTitleKey, fstHashMode, optionalTMD);
|
||||||
if (!fstVolume)
|
if (!fstVolume)
|
||||||
{
|
{
|
||||||
delete ds;
|
delete ds;
|
||||||
|
@ -757,7 +807,7 @@ uint32 FSTVolume::ReadFile(FSTFileHandle& fileHandle, uint32 offset, uint32 size
|
||||||
return 0;
|
return 0;
|
||||||
cemu_assert_debug(!HAS_FLAG(entry.GetFlags(), FSTEntry::FLAGS::FLAG_LINK));
|
cemu_assert_debug(!HAS_FLAG(entry.GetFlags(), FSTEntry::FLAGS::FLAG_LINK));
|
||||||
FSTCluster& cluster = m_cluster[entry.fileInfo.clusterIndex];
|
FSTCluster& cluster = m_cluster[entry.fileInfo.clusterIndex];
|
||||||
if (cluster.hashMode == ClusterHashMode::RAW || cluster.hashMode == ClusterHashMode::RAW2)
|
if (cluster.hashMode == ClusterHashMode::RAW || cluster.hashMode == ClusterHashMode::RAW_STREAM)
|
||||||
return ReadFile_HashModeRaw(entry.fileInfo.clusterIndex, entry, offset, size, dataOut);
|
return ReadFile_HashModeRaw(entry.fileInfo.clusterIndex, entry, offset, size, dataOut);
|
||||||
else if (cluster.hashMode == ClusterHashMode::HASH_INTERLEAVED)
|
else if (cluster.hashMode == ClusterHashMode::HASH_INTERLEAVED)
|
||||||
return ReadFile_HashModeHashed(entry.fileInfo.clusterIndex, entry, offset, size, dataOut);
|
return ReadFile_HashModeHashed(entry.fileInfo.clusterIndex, entry, offset, size, dataOut);
|
||||||
|
@ -765,87 +815,15 @@ uint32 FSTVolume::ReadFile(FSTFileHandle& fileHandle, uint32 offset, uint32 size
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32 FSTVolume::ReadFile_HashModeRaw(uint32 clusterIndex, FSTEntry& entry, uint32 readOffset, uint32 readSize, void* dataOut)
|
|
||||||
{
|
|
||||||
const uint32 readSizeInput = readSize;
|
|
||||||
uint8* dataOutU8 = (uint8*)dataOut;
|
|
||||||
if (readOffset >= entry.fileInfo.fileSize)
|
|
||||||
return 0;
|
|
||||||
else if ((readOffset + readSize) >= entry.fileInfo.fileSize)
|
|
||||||
readSize = (entry.fileInfo.fileSize - readOffset);
|
|
||||||
|
|
||||||
const FSTCluster& cluster = m_cluster[clusterIndex];
|
|
||||||
uint64 clusterOffset = (uint64)cluster.offset * m_sectorSize;
|
|
||||||
uint64 absFileOffset = entry.fileInfo.fileOffset * m_offsetFactor + readOffset;
|
|
||||||
|
|
||||||
// make sure the raw range we read is aligned to AES block size (16)
|
|
||||||
uint64 readAddrStart = absFileOffset & ~0xF;
|
|
||||||
uint64 readAddrEnd = (absFileOffset + readSize + 0xF) & ~0xF;
|
|
||||||
|
|
||||||
bool usesInitialIV = readOffset < 16;
|
|
||||||
if (!usesInitialIV)
|
|
||||||
readAddrStart -= 16; // read previous AES block since we require it for the IV
|
|
||||||
uint32 prePadding = (uint32)(absFileOffset - readAddrStart); // number of extra bytes we read before readOffset (for AES alignment and IV calculation)
|
|
||||||
uint32 postPadding = (uint32)(readAddrEnd - (absFileOffset + readSize));
|
|
||||||
|
|
||||||
uint8 readBuffer[64 * 1024];
|
|
||||||
// read first chunk
|
|
||||||
// if file read offset (readOffset) is within the first AES-block then use initial IV calculated from cluster index
|
|
||||||
// otherwise read previous AES-block is the IV (AES-CBC)
|
|
||||||
uint64 readAddrCurrent = readAddrStart;
|
|
||||||
uint32 rawBytesToRead = (uint32)std::min((readAddrEnd - readAddrStart), (uint64)sizeof(readBuffer));
|
|
||||||
if (m_dataSource->readData(clusterIndex, clusterOffset, readAddrCurrent, readBuffer, rawBytesToRead) != rawBytesToRead)
|
|
||||||
{
|
|
||||||
cemuLog_log(LogType::Force, "FST read error in raw content");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
readAddrCurrent += rawBytesToRead;
|
|
||||||
|
|
||||||
uint8 iv[16]{};
|
|
||||||
if (usesInitialIV)
|
|
||||||
{
|
|
||||||
// for the first AES block, the IV is initialized from cluster index
|
|
||||||
iv[0] = (uint8)(clusterIndex >> 8);
|
|
||||||
iv[1] = (uint8)(clusterIndex >> 0);
|
|
||||||
AES128_CBC_decrypt_updateIV(readBuffer, readBuffer, rawBytesToRead, m_partitionTitlekey.b, iv);
|
|
||||||
std::memcpy(dataOutU8, readBuffer + prePadding, rawBytesToRead - prePadding - postPadding);
|
|
||||||
dataOutU8 += (rawBytesToRead - prePadding - postPadding);
|
|
||||||
readSize -= (rawBytesToRead - prePadding - postPadding);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// IV is initialized from previous AES block (AES-CBC)
|
|
||||||
std::memcpy(iv, readBuffer, 16);
|
|
||||||
AES128_CBC_decrypt_updateIV(readBuffer + 16, readBuffer + 16, rawBytesToRead - 16, m_partitionTitlekey.b, iv);
|
|
||||||
std::memcpy(dataOutU8, readBuffer + prePadding, rawBytesToRead - prePadding - postPadding);
|
|
||||||
dataOutU8 += (rawBytesToRead - prePadding - postPadding);
|
|
||||||
readSize -= (rawBytesToRead - prePadding - postPadding);
|
|
||||||
}
|
|
||||||
|
|
||||||
// read remaining chunks
|
|
||||||
while (readSize > 0)
|
|
||||||
{
|
|
||||||
uint32 bytesToRead = (uint32)std::min((uint32)sizeof(readBuffer), readSize);
|
|
||||||
uint32 alignedBytesToRead = (bytesToRead + 15) & ~0xF;
|
|
||||||
if (m_dataSource->readData(clusterIndex, clusterOffset, readAddrCurrent, readBuffer, alignedBytesToRead) != alignedBytesToRead)
|
|
||||||
{
|
|
||||||
cemuLog_log(LogType::Force, "FST read error in raw content");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
AES128_CBC_decrypt_updateIV(readBuffer, readBuffer, alignedBytesToRead, m_partitionTitlekey.b, iv);
|
|
||||||
std::memcpy(dataOutU8, readBuffer, bytesToRead);
|
|
||||||
dataOutU8 += bytesToRead;
|
|
||||||
readSize -= bytesToRead;
|
|
||||||
readAddrCurrent += alignedBytesToRead;
|
|
||||||
}
|
|
||||||
|
|
||||||
return readSizeInput - readSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr size_t BLOCK_SIZE = 0x10000;
|
constexpr size_t BLOCK_SIZE = 0x10000;
|
||||||
constexpr size_t BLOCK_HASH_SIZE = 0x0400;
|
constexpr size_t BLOCK_HASH_SIZE = 0x0400;
|
||||||
constexpr size_t BLOCK_FILE_SIZE = 0xFC00;
|
constexpr size_t BLOCK_FILE_SIZE = 0xFC00;
|
||||||
|
|
||||||
|
struct FSTRawBlock
|
||||||
|
{
|
||||||
|
std::vector<uint8> rawData; // unhashed block size depends on sector size field in partition header
|
||||||
|
};
|
||||||
|
|
||||||
struct FSTHashedBlock
|
struct FSTHashedBlock
|
||||||
{
|
{
|
||||||
uint8 rawData[BLOCK_SIZE];
|
uint8 rawData[BLOCK_SIZE];
|
||||||
|
@ -887,12 +865,160 @@ struct FSTHashedBlock
|
||||||
|
|
||||||
static_assert(sizeof(FSTHashedBlock) == BLOCK_SIZE);
|
static_assert(sizeof(FSTHashedBlock) == BLOCK_SIZE);
|
||||||
|
|
||||||
|
struct FSTCachedRawBlock
|
||||||
|
{
|
||||||
|
FSTRawBlock blockData;
|
||||||
|
uint8 ivForNextBlock[16];
|
||||||
|
uint64 lastAccess;
|
||||||
|
};
|
||||||
|
|
||||||
struct FSTCachedHashedBlock
|
struct FSTCachedHashedBlock
|
||||||
{
|
{
|
||||||
FSTHashedBlock blockData;
|
FSTHashedBlock blockData;
|
||||||
uint64 lastAccess;
|
uint64 lastAccess;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Checks cache fill state and if necessary drops least recently accessed block from the cache. Optionally allows to recycle the released cache entry to cut down cost of memory allocation and clearing
|
||||||
|
void FSTVolume::TrimCacheIfRequired(FSTCachedRawBlock** droppedRawBlock, FSTCachedHashedBlock** droppedHashedBlock)
|
||||||
|
{
|
||||||
|
// calculate size used by cache
|
||||||
|
size_t cacheSize = 0;
|
||||||
|
for (auto& itr : m_cacheDecryptedRawBlocks)
|
||||||
|
cacheSize += itr.second->blockData.rawData.size();
|
||||||
|
for (auto& itr : m_cacheDecryptedHashedBlocks)
|
||||||
|
cacheSize += sizeof(FSTCachedHashedBlock) + sizeof(FSTHashedBlock);
|
||||||
|
// only trim if cache is full (larger than 2MB)
|
||||||
|
if (cacheSize < 2*1024*1024) // 2MB
|
||||||
|
return;
|
||||||
|
// scan both cache lists to find least recently accessed block to drop
|
||||||
|
auto dropRawItr = std::min_element(m_cacheDecryptedRawBlocks.begin(), m_cacheDecryptedRawBlocks.end(), [](const auto& a, const auto& b) -> bool
|
||||||
|
{ return a.second->lastAccess < b.second->lastAccess; });
|
||||||
|
auto dropHashedItr = std::min_element(m_cacheDecryptedHashedBlocks.begin(), m_cacheDecryptedHashedBlocks.end(), [](const auto& a, const auto& b) -> bool
|
||||||
|
{ return a.second->lastAccess < b.second->lastAccess; });
|
||||||
|
uint64 lastAccess = std::numeric_limits<uint64>::max();
|
||||||
|
if(dropRawItr != m_cacheDecryptedRawBlocks.end())
|
||||||
|
lastAccess = dropRawItr->second->lastAccess;
|
||||||
|
if(dropHashedItr != m_cacheDecryptedHashedBlocks.end())
|
||||||
|
lastAccess = std::min<uint64>(lastAccess, dropHashedItr->second->lastAccess);
|
||||||
|
if(dropRawItr != m_cacheDecryptedRawBlocks.end() && dropRawItr->second->lastAccess == lastAccess)
|
||||||
|
{
|
||||||
|
if (droppedRawBlock)
|
||||||
|
*droppedRawBlock = dropRawItr->second;
|
||||||
|
else
|
||||||
|
delete dropRawItr->second;
|
||||||
|
m_cacheDecryptedRawBlocks.erase(dropRawItr);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if(dropHashedItr != m_cacheDecryptedHashedBlocks.end() && dropHashedItr->second->lastAccess == lastAccess)
|
||||||
|
{
|
||||||
|
if (droppedHashedBlock)
|
||||||
|
*droppedHashedBlock = dropHashedItr->second;
|
||||||
|
else
|
||||||
|
delete dropHashedItr->second;
|
||||||
|
m_cacheDecryptedHashedBlocks.erase(dropHashedItr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FSTVolume::DetermineUnhashedBlockIV(uint32 clusterIndex, uint32 blockIndex, uint8 ivOut[16])
|
||||||
|
{
|
||||||
|
memset(ivOut, 0, sizeof(ivOut));
|
||||||
|
if(blockIndex == 0)
|
||||||
|
{
|
||||||
|
ivOut[0] = (uint8)(clusterIndex >> 8);
|
||||||
|
ivOut[1] = (uint8)(clusterIndex >> 0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// the last 16 encrypted bytes of the previous block are the IV (AES CBC)
|
||||||
|
// if the previous block is cached we can grab the IV from there. Otherwise we have to read the 16 bytes from the data source
|
||||||
|
uint32 prevBlockIndex = blockIndex - 1;
|
||||||
|
uint64 cacheBlockId = ((uint64)clusterIndex << (64 - 16)) | (uint64)prevBlockIndex;
|
||||||
|
auto itr = m_cacheDecryptedRawBlocks.find(cacheBlockId);
|
||||||
|
if (itr != m_cacheDecryptedRawBlocks.end())
|
||||||
|
{
|
||||||
|
memcpy(ivOut, itr->second->ivForNextBlock, 16);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cemu_assert(m_sectorSize >= 16);
|
||||||
|
uint64 clusterOffset = (uint64)m_cluster[clusterIndex].offset * m_sectorSize;
|
||||||
|
uint8 prevIV[16];
|
||||||
|
if (m_dataSource->readData(clusterIndex, clusterOffset, blockIndex * m_sectorSize - 16, prevIV, 16) != 16)
|
||||||
|
{
|
||||||
|
cemuLog_log(LogType::Force, "Failed to read IV for raw FST block");
|
||||||
|
m_detectedCorruption = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
memcpy(ivOut, prevIV, 16);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FSTCachedRawBlock* FSTVolume::GetDecryptedRawBlock(uint32 clusterIndex, uint32 blockIndex)
|
||||||
|
{
|
||||||
|
FSTCluster& cluster = m_cluster[clusterIndex];
|
||||||
|
uint64 clusterOffset = (uint64)cluster.offset * m_sectorSize;
|
||||||
|
// generate id for cache
|
||||||
|
uint64 cacheBlockId = ((uint64)clusterIndex << (64 - 16)) | (uint64)blockIndex;
|
||||||
|
// lookup block in cache
|
||||||
|
FSTCachedRawBlock* block = nullptr;
|
||||||
|
auto itr = m_cacheDecryptedRawBlocks.find(cacheBlockId);
|
||||||
|
if (itr != m_cacheDecryptedRawBlocks.end())
|
||||||
|
{
|
||||||
|
block = itr->second;
|
||||||
|
block->lastAccess = ++m_cacheAccessCounter;
|
||||||
|
return block;
|
||||||
|
}
|
||||||
|
// if cache already full, drop least recently accessed block and recycle FSTCachedRawBlock object if possible
|
||||||
|
TrimCacheIfRequired(&block, nullptr);
|
||||||
|
if (!block)
|
||||||
|
block = new FSTCachedRawBlock();
|
||||||
|
block->blockData.rawData.resize(m_sectorSize);
|
||||||
|
// block not cached, read new
|
||||||
|
block->lastAccess = ++m_cacheAccessCounter;
|
||||||
|
if (m_dataSource->readData(clusterIndex, clusterOffset, blockIndex * m_sectorSize, block->blockData.rawData.data(), m_sectorSize) != m_sectorSize)
|
||||||
|
{
|
||||||
|
cemuLog_log(LogType::Force, "Failed to read raw FST block");
|
||||||
|
delete block;
|
||||||
|
m_detectedCorruption = true;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
// decrypt hash data
|
||||||
|
uint8 iv[16]{};
|
||||||
|
DetermineUnhashedBlockIV(clusterIndex, blockIndex, iv);
|
||||||
|
memcpy(block->ivForNextBlock, block->blockData.rawData.data() + m_sectorSize - 16, 16);
|
||||||
|
AES128_CBC_decrypt(block->blockData.rawData.data(), block->blockData.rawData.data(), m_sectorSize, m_partitionTitlekey.b, iv);
|
||||||
|
// if this is the next block, then hash it
|
||||||
|
if(cluster.hasContentHash)
|
||||||
|
{
|
||||||
|
if(cluster.singleHashNumBlocksHashed == blockIndex)
|
||||||
|
{
|
||||||
|
cemu_assert_debug(!(cluster.contentSize % m_sectorSize)); // size should be multiple of sector size? Regardless, the hashing code below can handle non-aligned sizes
|
||||||
|
bool isLastBlock = blockIndex == (std::max<uint32>(cluster.contentSize / m_sectorSize, 1) - 1);
|
||||||
|
uint32 hashSize = m_sectorSize;
|
||||||
|
if(isLastBlock)
|
||||||
|
hashSize = cluster.contentSize - (uint64)blockIndex*m_sectorSize;
|
||||||
|
EVP_DigestUpdate(cluster.singleHashCtx.get(), block->blockData.rawData.data(), hashSize);
|
||||||
|
cluster.singleHashNumBlocksHashed++;
|
||||||
|
if(isLastBlock)
|
||||||
|
{
|
||||||
|
uint8 hash[32];
|
||||||
|
EVP_DigestFinal_ex(cluster.singleHashCtx.get(), hash, nullptr);
|
||||||
|
if(memcmp(hash, cluster.contentHash32, cluster.contentHashIsSHA1 ? 20 : 32) != 0)
|
||||||
|
{
|
||||||
|
cemuLog_log(LogType::Force, "FST: Raw section hash mismatch");
|
||||||
|
delete block;
|
||||||
|
m_detectedCorruption = true;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// register in cache
|
||||||
|
m_cacheDecryptedRawBlocks.emplace(cacheBlockId, block);
|
||||||
|
return block;
|
||||||
|
}
|
||||||
|
|
||||||
FSTCachedHashedBlock* FSTVolume::GetDecryptedHashedBlock(uint32 clusterIndex, uint32 blockIndex)
|
FSTCachedHashedBlock* FSTVolume::GetDecryptedHashedBlock(uint32 clusterIndex, uint32 blockIndex)
|
||||||
{
|
{
|
||||||
const FSTCluster& cluster = m_cluster[clusterIndex];
|
const FSTCluster& cluster = m_cluster[clusterIndex];
|
||||||
|
@ -908,22 +1034,17 @@ FSTCachedHashedBlock* FSTVolume::GetDecryptedHashedBlock(uint32 clusterIndex, ui
|
||||||
block->lastAccess = ++m_cacheAccessCounter;
|
block->lastAccess = ++m_cacheAccessCounter;
|
||||||
return block;
|
return block;
|
||||||
}
|
}
|
||||||
// if cache already full, drop least recently accessed block (but recycle the FSTHashedBlock* object)
|
// if cache already full, drop least recently accessed block and recycle FSTCachedHashedBlock object if possible
|
||||||
if (m_cacheDecryptedHashedBlocks.size() >= 16)
|
TrimCacheIfRequired(nullptr, &block);
|
||||||
{
|
if (!block)
|
||||||
auto dropItr = std::min_element(m_cacheDecryptedHashedBlocks.begin(), m_cacheDecryptedHashedBlocks.end(), [](const auto& a, const auto& b) -> bool
|
|
||||||
{ return a.second->lastAccess < b.second->lastAccess; });
|
|
||||||
block = dropItr->second;
|
|
||||||
m_cacheDecryptedHashedBlocks.erase(dropItr);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
block = new FSTCachedHashedBlock();
|
block = new FSTCachedHashedBlock();
|
||||||
// block not cached, read new
|
// block not cached, read new
|
||||||
block->lastAccess = ++m_cacheAccessCounter;
|
block->lastAccess = ++m_cacheAccessCounter;
|
||||||
if (m_dataSource->readData(clusterIndex, clusterOffset, blockIndex * BLOCK_SIZE, block->blockData.rawData, BLOCK_SIZE) != BLOCK_SIZE)
|
if (m_dataSource->readData(clusterIndex, clusterOffset, blockIndex * BLOCK_SIZE, block->blockData.rawData, BLOCK_SIZE) != BLOCK_SIZE)
|
||||||
{
|
{
|
||||||
cemuLog_log(LogType::Force, "Failed to read FST block");
|
cemuLog_log(LogType::Force, "Failed to read hashed FST block");
|
||||||
delete block;
|
delete block;
|
||||||
|
m_detectedCorruption = true;
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
// decrypt hash data
|
// decrypt hash data
|
||||||
|
@ -931,11 +1052,46 @@ FSTCachedHashedBlock* FSTVolume::GetDecryptedHashedBlock(uint32 clusterIndex, ui
|
||||||
AES128_CBC_decrypt(block->blockData.getHashData(), block->blockData.getHashData(), BLOCK_HASH_SIZE, m_partitionTitlekey.b, iv);
|
AES128_CBC_decrypt(block->blockData.getHashData(), block->blockData.getHashData(), BLOCK_HASH_SIZE, m_partitionTitlekey.b, iv);
|
||||||
// decrypt file data
|
// decrypt file data
|
||||||
AES128_CBC_decrypt(block->blockData.getFileData(), block->blockData.getFileData(), BLOCK_FILE_SIZE, m_partitionTitlekey.b, block->blockData.getH0Hash(blockIndex%16));
|
AES128_CBC_decrypt(block->blockData.getFileData(), block->blockData.getFileData(), BLOCK_FILE_SIZE, m_partitionTitlekey.b, block->blockData.getH0Hash(blockIndex%16));
|
||||||
|
// compare with H0 to verify data integrity
|
||||||
|
NCrypto::CHash160 h0;
|
||||||
|
SHA1(block->blockData.getFileData(), BLOCK_FILE_SIZE, h0.b);
|
||||||
|
uint32 h0Index = (blockIndex % 4096);
|
||||||
|
if (memcmp(h0.b, block->blockData.getH0Hash(h0Index & 0xF), sizeof(h0.b)) != 0)
|
||||||
|
{
|
||||||
|
cemuLog_log(LogType::Force, "FST: Hash H0 mismatch in hashed block (section {} index {})", clusterIndex, blockIndex);
|
||||||
|
delete block;
|
||||||
|
m_detectedCorruption = true;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
// register in cache
|
// register in cache
|
||||||
m_cacheDecryptedHashedBlocks.emplace(cacheBlockId, block);
|
m_cacheDecryptedHashedBlocks.emplace(cacheBlockId, block);
|
||||||
return block;
|
return block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint32 FSTVolume::ReadFile_HashModeRaw(uint32 clusterIndex, FSTEntry& entry, uint32 readOffset, uint32 readSize, void* dataOut)
|
||||||
|
{
|
||||||
|
uint8* dataOutU8 = (uint8*)dataOut;
|
||||||
|
if (readOffset >= entry.fileInfo.fileSize)
|
||||||
|
return 0;
|
||||||
|
else if ((readOffset + readSize) >= entry.fileInfo.fileSize)
|
||||||
|
readSize = (entry.fileInfo.fileSize - readOffset);
|
||||||
|
uint64 absFileOffset = entry.fileInfo.fileOffset * m_offsetFactor + readOffset;
|
||||||
|
uint32 remainingReadSize = readSize;
|
||||||
|
while (remainingReadSize > 0)
|
||||||
|
{
|
||||||
|
const FSTCachedRawBlock* rawBlock = this->GetDecryptedRawBlock(clusterIndex, absFileOffset/m_sectorSize);
|
||||||
|
if (!rawBlock)
|
||||||
|
break;
|
||||||
|
uint32 blockOffset = (uint32)(absFileOffset % m_sectorSize);
|
||||||
|
uint32 bytesToRead = std::min<uint32>(remainingReadSize, m_sectorSize - blockOffset);
|
||||||
|
std::memcpy(dataOutU8, rawBlock->blockData.rawData.data() + blockOffset, bytesToRead);
|
||||||
|
dataOutU8 += bytesToRead;
|
||||||
|
remainingReadSize -= bytesToRead;
|
||||||
|
absFileOffset += bytesToRead;
|
||||||
|
}
|
||||||
|
return readSize - remainingReadSize;
|
||||||
|
}
|
||||||
|
|
||||||
uint32 FSTVolume::ReadFile_HashModeHashed(uint32 clusterIndex, FSTEntry& entry, uint32 readOffset, uint32 readSize, void* dataOut)
|
uint32 FSTVolume::ReadFile_HashModeHashed(uint32 clusterIndex, FSTEntry& entry, uint32 readOffset, uint32 readSize, void* dataOut)
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
|
@ -966,7 +1122,6 @@ uint32 FSTVolume::ReadFile_HashModeHashed(uint32 clusterIndex, FSTEntry& entry,
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const FSTCluster& cluster = m_cluster[clusterIndex];
|
const FSTCluster& cluster = m_cluster[clusterIndex];
|
||||||
uint64 clusterBaseOffset = (uint64)cluster.offset * m_sectorSize;
|
|
||||||
uint64 fileReadOffset = entry.fileInfo.fileOffset * m_offsetFactor + readOffset;
|
uint64 fileReadOffset = entry.fileInfo.fileOffset * m_offsetFactor + readOffset;
|
||||||
uint32 blockIndex = (uint32)(fileReadOffset / BLOCK_FILE_SIZE);
|
uint32 blockIndex = (uint32)(fileReadOffset / BLOCK_FILE_SIZE);
|
||||||
uint32 bytesRemaining = readSize;
|
uint32 bytesRemaining = readSize;
|
||||||
|
@ -1019,6 +1174,8 @@ bool FSTVolume::Next(FSTDirectoryIterator& directoryIterator, FSTFileHandle& fil
|
||||||
|
|
||||||
FSTVolume::~FSTVolume()
|
FSTVolume::~FSTVolume()
|
||||||
{
|
{
|
||||||
|
for (auto& itr : m_cacheDecryptedRawBlocks)
|
||||||
|
delete itr.second;
|
||||||
for (auto& itr : m_cacheDecryptedHashedBlocks)
|
for (auto& itr : m_cacheDecryptedHashedBlocks)
|
||||||
delete itr.second;
|
delete itr.second;
|
||||||
if (m_sourceIsOwned)
|
if (m_sourceIsOwned)
|
||||||
|
@ -1115,4 +1272,4 @@ bool FSTVerifier::VerifyHashedContentFile(FileStream* fileContent, const NCrypto
|
||||||
void FSTVolumeTest()
|
void FSTVolumeTest()
|
||||||
{
|
{
|
||||||
FSTPathUnitTest();
|
FSTPathUnitTest();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "Cemu/ncrypto/ncrypto.h"
|
#include "Cemu/ncrypto/ncrypto.h"
|
||||||
|
#include "openssl/evp.h"
|
||||||
|
|
||||||
struct FSTFileHandle
|
struct FSTFileHandle
|
||||||
{
|
{
|
||||||
|
@ -45,6 +46,7 @@ public:
|
||||||
~FSTVolume();
|
~FSTVolume();
|
||||||
|
|
||||||
uint32 GetFileCount() const;
|
uint32 GetFileCount() const;
|
||||||
|
bool HasCorruption() const { return m_detectedCorruption; }
|
||||||
|
|
||||||
bool OpenFile(std::string_view path, FSTFileHandle& fileHandleOut, bool openOnlyFiles = false);
|
bool OpenFile(std::string_view path, FSTFileHandle& fileHandleOut, bool openOnlyFiles = false);
|
||||||
|
|
||||||
|
@ -86,15 +88,25 @@ private:
|
||||||
enum class ClusterHashMode : uint8
|
enum class ClusterHashMode : uint8
|
||||||
{
|
{
|
||||||
RAW = 0, // raw data + encryption, no hashing?
|
RAW = 0, // raw data + encryption, no hashing?
|
||||||
RAW2 = 1, // raw data + encryption, with hash stored in tmd?
|
RAW_STREAM = 1, // raw data + encryption, with hash stored in tmd?
|
||||||
HASH_INTERLEAVED = 2, // hashes + raw interleaved in 0x10000 blocks (0x400 bytes of hashes at the beginning, followed by 0xFC00 bytes of data)
|
HASH_INTERLEAVED = 2, // hashes + raw interleaved in 0x10000 blocks (0x400 bytes of hashes at the beginning, followed by 0xFC00 bytes of data)
|
||||||
};
|
};
|
||||||
|
|
||||||
struct FSTCluster
|
struct FSTCluster
|
||||||
{
|
{
|
||||||
|
FSTCluster() : singleHashCtx(nullptr, &EVP_MD_CTX_free) {}
|
||||||
|
|
||||||
uint32 offset;
|
uint32 offset;
|
||||||
uint32 size;
|
uint32 size;
|
||||||
ClusterHashMode hashMode;
|
ClusterHashMode hashMode;
|
||||||
|
// extra data if TMD is available
|
||||||
|
bool hasContentHash;
|
||||||
|
uint8 contentHash32[32];
|
||||||
|
bool contentHashIsSHA1; // if true then it's SHA1 (with extra bytes zeroed out), otherwise it's SHA256
|
||||||
|
uint64 contentSize; // size of the content (in blocks)
|
||||||
|
// hash context for single hash mode (content hash must be available)
|
||||||
|
std::unique_ptr<EVP_MD_CTX, decltype(&EVP_MD_CTX_free)> singleHashCtx; // unique_ptr to make this move-only
|
||||||
|
uint32 singleHashNumBlocksHashed{0};
|
||||||
};
|
};
|
||||||
|
|
||||||
struct FSTEntry
|
struct FSTEntry
|
||||||
|
@ -164,17 +176,30 @@ private:
|
||||||
bool m_sourceIsOwned{};
|
bool m_sourceIsOwned{};
|
||||||
uint32 m_sectorSize{}; // for cluster offsets
|
uint32 m_sectorSize{}; // for cluster offsets
|
||||||
uint32 m_offsetFactor{}; // for file offsets
|
uint32 m_offsetFactor{}; // for file offsets
|
||||||
|
bool m_hashIsDisabled{}; // disables hash verification (for all clusters of this volume?)
|
||||||
std::vector<FSTCluster> m_cluster;
|
std::vector<FSTCluster> m_cluster;
|
||||||
std::vector<FSTEntry> m_entries;
|
std::vector<FSTEntry> m_entries;
|
||||||
std::vector<char> m_nameStringTable;
|
std::vector<char> m_nameStringTable;
|
||||||
NCrypto::AesKey m_partitionTitlekey;
|
NCrypto::AesKey m_partitionTitlekey;
|
||||||
|
bool m_detectedCorruption{false};
|
||||||
|
|
||||||
/* Cache for decrypted hashed blocks */
|
bool HashIsDisabled() const
|
||||||
|
{
|
||||||
|
return m_hashIsDisabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Cache for decrypted raw and hashed blocks */
|
||||||
|
std::unordered_map<uint64, struct FSTCachedRawBlock*> m_cacheDecryptedRawBlocks;
|
||||||
std::unordered_map<uint64, struct FSTCachedHashedBlock*> m_cacheDecryptedHashedBlocks;
|
std::unordered_map<uint64, struct FSTCachedHashedBlock*> m_cacheDecryptedHashedBlocks;
|
||||||
uint64 m_cacheAccessCounter{};
|
uint64 m_cacheAccessCounter{};
|
||||||
|
|
||||||
|
void DetermineUnhashedBlockIV(uint32 clusterIndex, uint32 blockIndex, uint8 ivOut[16]);
|
||||||
|
|
||||||
|
struct FSTCachedRawBlock* GetDecryptedRawBlock(uint32 clusterIndex, uint32 blockIndex);
|
||||||
struct FSTCachedHashedBlock* GetDecryptedHashedBlock(uint32 clusterIndex, uint32 blockIndex);
|
struct FSTCachedHashedBlock* GetDecryptedHashedBlock(uint32 clusterIndex, uint32 blockIndex);
|
||||||
|
|
||||||
|
void TrimCacheIfRequired(struct FSTCachedRawBlock** droppedRawBlock, struct FSTCachedHashedBlock** droppedHashedBlock);
|
||||||
|
|
||||||
/* File reading */
|
/* File reading */
|
||||||
uint32 ReadFile_HashModeRaw(uint32 clusterIndex, FSTEntry& entry, uint32 readOffset, uint32 readSize, void* dataOut);
|
uint32 ReadFile_HashModeRaw(uint32 clusterIndex, FSTEntry& entry, uint32 readOffset, uint32 readSize, void* dataOut);
|
||||||
uint32 ReadFile_HashModeHashed(uint32 clusterIndex, FSTEntry& entry, uint32 readOffset, uint32 readSize, void* dataOut);
|
uint32 ReadFile_HashModeHashed(uint32 clusterIndex, FSTEntry& entry, uint32 readOffset, uint32 readSize, void* dataOut);
|
||||||
|
@ -185,7 +210,10 @@ private:
|
||||||
/* +0x00 */ uint32be magic;
|
/* +0x00 */ uint32be magic;
|
||||||
/* +0x04 */ uint32be offsetFactor;
|
/* +0x04 */ uint32be offsetFactor;
|
||||||
/* +0x08 */ uint32be numCluster;
|
/* +0x08 */ uint32be numCluster;
|
||||||
/* +0x0C */ uint32be ukn0C;
|
/* +0x0C */ uint8be hashIsDisabled;
|
||||||
|
/* +0x0D */ uint8be ukn0D;
|
||||||
|
/* +0x0E */ uint8be ukn0E;
|
||||||
|
/* +0x0F */ uint8be ukn0F;
|
||||||
/* +0x10 */ uint32be ukn10;
|
/* +0x10 */ uint32be ukn10;
|
||||||
/* +0x14 */ uint32be ukn14;
|
/* +0x14 */ uint32be ukn14;
|
||||||
/* +0x18 */ uint32be ukn18;
|
/* +0x18 */ uint32be ukn18;
|
||||||
|
@ -262,8 +290,8 @@ private:
|
||||||
|
|
||||||
static_assert(sizeof(FSTHeader_FileEntry) == 0x10);
|
static_assert(sizeof(FSTHeader_FileEntry) == 0x10);
|
||||||
|
|
||||||
static FSTVolume* OpenFST(FSTDataSource* dataSource, uint64 fstOffset, uint32 fstSize, NCrypto::AesKey* partitionTitleKey, ClusterHashMode fstHashMode);
|
static FSTVolume* OpenFST(FSTDataSource* dataSource, uint64 fstOffset, uint32 fstSize, NCrypto::AesKey* partitionTitleKey, ClusterHashMode fstHashMode, NCrypto::TMDParser* optionalTMD);
|
||||||
static FSTVolume* OpenFST(std::unique_ptr<FSTDataSource> dataSource, uint64 fstOffset, uint32 fstSize, NCrypto::AesKey* partitionTitleKey, ClusterHashMode fstHashMode);
|
static FSTVolume* OpenFST(std::unique_ptr<FSTDataSource> dataSource, uint64 fstOffset, uint32 fstSize, NCrypto::AesKey* partitionTitleKey, ClusterHashMode fstHashMode, NCrypto::TMDParser* optionalTMD);
|
||||||
static bool ProcessFST(FSTHeader_FileEntry* fileTable, uint32 numFileEntries, uint32 numCluster, std::vector<char>& nameStringTable, std::vector<FSTEntry>& fstEntries);
|
static bool ProcessFST(FSTHeader_FileEntry* fileTable, uint32 numFileEntries, uint32 numCluster, std::vector<char>& nameStringTable, std::vector<FSTEntry>& fstEntries);
|
||||||
|
|
||||||
bool MatchFSTEntryName(FSTEntry& entry, std::string_view comparedName)
|
bool MatchFSTEntryName(FSTEntry& entry, std::string_view comparedName)
|
||||||
|
|
|
@ -140,7 +140,7 @@ bool gameProfile_loadEnumOption(IniParser& iniParser, const char* optionName, T&
|
||||||
for(const T& v : T())
|
for(const T& v : T())
|
||||||
{
|
{
|
||||||
// test integer option
|
// test integer option
|
||||||
if (boost::iequals(fmt::format("{}", static_cast<typename std::underlying_type<T>::type>(v)), *option_value))
|
if (boost::iequals(fmt::format("{}", fmt::underlying(v)), *option_value))
|
||||||
{
|
{
|
||||||
option = v;
|
option = v;
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -345,7 +345,7 @@ GraphicPack2::GraphicPack2(fs::path rulesPath, IniParser& rules)
|
||||||
const auto preset_name = rules.FindOption("name");
|
const auto preset_name = rules.FindOption("name");
|
||||||
if (!preset_name)
|
if (!preset_name)
|
||||||
{
|
{
|
||||||
cemuLog_log(LogType::Force, "Graphic pack \"{}\": Preset in line {} skipped because it has no name option defined", m_name, rules.GetCurrentSectionLineNumber());
|
cemuLog_log(LogType::Force, "Graphic pack \"{}\": Preset in line {} skipped because it has no name option defined", GetNormalizedPathString(), rules.GetCurrentSectionLineNumber());
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -369,7 +369,7 @@ GraphicPack2::GraphicPack2(fs::path rulesPath, IniParser& rules)
|
||||||
}
|
}
|
||||||
catch (const std::exception & ex)
|
catch (const std::exception & ex)
|
||||||
{
|
{
|
||||||
cemuLog_log(LogType::Force, "Graphic pack \"{}\": Can't parse preset \"{}\": {}", m_name, *preset_name, ex.what());
|
cemuLog_log(LogType::Force, "Graphic pack \"{}\": Can't parse preset \"{}\": {}", GetNormalizedPathString(), *preset_name, ex.what());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (boost::iequals(currentSectionName, "RAM"))
|
else if (boost::iequals(currentSectionName, "RAM"))
|
||||||
|
@ -383,7 +383,7 @@ GraphicPack2::GraphicPack2(fs::path rulesPath, IniParser& rules)
|
||||||
{
|
{
|
||||||
if (m_version <= 5)
|
if (m_version <= 5)
|
||||||
{
|
{
|
||||||
cemuLog_log(LogType::Force, "Graphic pack \"{}\": [RAM] options are only available for graphic pack version 6 or higher", m_name, optionNameBuf);
|
cemuLog_log(LogType::Force, "Graphic pack \"{}\": [RAM] options are only available for graphic pack version 6 or higher", GetNormalizedPathString(), optionNameBuf);
|
||||||
throw std::exception();
|
throw std::exception();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -393,12 +393,12 @@ GraphicPack2::GraphicPack2(fs::path rulesPath, IniParser& rules)
|
||||||
{
|
{
|
||||||
if (addrEnd <= addrStart)
|
if (addrEnd <= addrStart)
|
||||||
{
|
{
|
||||||
cemuLog_log(LogType::Force, "Graphic pack \"{}\": start address (0x{:08x}) must be greater than end address (0x{:08x}) for {}", m_name, addrStart, addrEnd, optionNameBuf);
|
cemuLog_log(LogType::Force, "Graphic pack \"{}\": start address (0x{:08x}) must be greater than end address (0x{:08x}) for {}", GetNormalizedPathString(), addrStart, addrEnd, optionNameBuf);
|
||||||
throw std::exception();
|
throw std::exception();
|
||||||
}
|
}
|
||||||
else if ((addrStart & 0xFFF) != 0 || (addrEnd & 0xFFF) != 0)
|
else if ((addrStart & 0xFFF) != 0 || (addrEnd & 0xFFF) != 0)
|
||||||
{
|
{
|
||||||
cemuLog_log(LogType::Force, "Graphic pack \"{}\": addresses for %s are not aligned to 0x1000", m_name, optionNameBuf);
|
cemuLog_log(LogType::Force, "Graphic pack \"{}\": addresses for %s are not aligned to 0x1000", GetNormalizedPathString(), optionNameBuf);
|
||||||
throw std::exception();
|
throw std::exception();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -408,7 +408,7 @@ GraphicPack2::GraphicPack2(fs::path rulesPath, IniParser& rules)
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
cemuLog_log(LogType::Force, "Graphic pack \"{}\": has invalid syntax for option {}", m_name, optionNameBuf);
|
cemuLog_log(LogType::Force, "Graphic pack \"{}\": has invalid syntax for option {}", GetNormalizedPathString(), optionNameBuf);
|
||||||
throw std::exception();
|
throw std::exception();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -422,24 +422,32 @@ GraphicPack2::GraphicPack2(fs::path rulesPath, IniParser& rules)
|
||||||
std::unordered_map<std::string, std::vector<PresetPtr>> tmp_map;
|
std::unordered_map<std::string, std::vector<PresetPtr>> tmp_map;
|
||||||
|
|
||||||
// all vars must be defined in the default preset vars before
|
// all vars must be defined in the default preset vars before
|
||||||
for (const auto& entry : m_presets)
|
std::vector<std::pair<std::string, std::string>> mismatchingPresetVars;
|
||||||
|
for (const auto& presetEntry : m_presets)
|
||||||
{
|
{
|
||||||
tmp_map[entry->category].emplace_back(entry);
|
tmp_map[presetEntry->category].emplace_back(presetEntry);
|
||||||
|
|
||||||
for (auto& kv : entry->variables)
|
for (auto& presetVar : presetEntry->variables)
|
||||||
{
|
{
|
||||||
const auto it = m_preset_vars.find(kv.first);
|
const auto it = m_preset_vars.find(presetVar.first);
|
||||||
if (it == m_preset_vars.cend())
|
if (it == m_preset_vars.cend())
|
||||||
{
|
{
|
||||||
cemuLog_log(LogType::Force, "Graphic pack: \"{}\" contains preset variables which are not defined in the default section", m_name);
|
mismatchingPresetVars.emplace_back(presetEntry->name, presetVar.first);
|
||||||
throw std::exception();
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// overwrite var type with default var type
|
// overwrite var type with default var type
|
||||||
kv.second.first = it->second.first;
|
presetVar.second.first = it->second.first;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(!mismatchingPresetVars.empty())
|
||||||
|
{
|
||||||
|
cemuLog_log(LogType::Force, "Graphic pack \"{}\" contains preset variables which are not defined in the [Default] section:", GetNormalizedPathString());
|
||||||
|
for (const auto& [presetName, varName] : mismatchingPresetVars)
|
||||||
|
cemuLog_log(LogType::Force, "Preset: {} Variable: {}", presetName, varName);
|
||||||
|
throw std::exception();
|
||||||
|
}
|
||||||
|
|
||||||
// have first entry be default active for every category if no default= is set
|
// have first entry be default active for every category if no default= is set
|
||||||
for(auto entry : get_values(tmp_map))
|
for(auto entry : get_values(tmp_map))
|
||||||
{
|
{
|
||||||
|
@ -469,7 +477,7 @@ GraphicPack2::GraphicPack2(fs::path rulesPath, IniParser& rules)
|
||||||
auto& p2 = kv.second[i + 1];
|
auto& p2 = kv.second[i + 1];
|
||||||
if (p1->variables.size() != p2->variables.size())
|
if (p1->variables.size() != p2->variables.size())
|
||||||
{
|
{
|
||||||
cemuLog_log(LogType::Force, "Graphic pack: \"{}\" contains inconsistent preset variables", m_name);
|
cemuLog_log(LogType::Force, "Graphic pack: \"{}\" contains inconsistent preset variables", GetNormalizedPathString());
|
||||||
throw std::exception();
|
throw std::exception();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -477,14 +485,14 @@ GraphicPack2::GraphicPack2(fs::path rulesPath, IniParser& rules)
|
||||||
std::set<std::string> keys2(get_keys(p2->variables).begin(), get_keys(p2->variables).end());
|
std::set<std::string> keys2(get_keys(p2->variables).begin(), get_keys(p2->variables).end());
|
||||||
if (keys1 != keys2)
|
if (keys1 != keys2)
|
||||||
{
|
{
|
||||||
cemuLog_log(LogType::Force, "Graphic pack: \"{}\" contains inconsistent preset variables", m_name);
|
cemuLog_log(LogType::Force, "Graphic pack: \"{}\" contains inconsistent preset variables", GetNormalizedPathString());
|
||||||
throw std::exception();
|
throw std::exception();
|
||||||
}
|
}
|
||||||
|
|
||||||
if(p1->is_default)
|
if(p1->is_default)
|
||||||
{
|
{
|
||||||
if(has_default)
|
if(has_default)
|
||||||
cemuLog_log(LogType::Force, "Graphic pack: \"{}\" has more than one preset with the default key set for the same category \"{}\"", m_name, p1->name);
|
cemuLog_log(LogType::Force, "Graphic pack: \"{}\" has more than one preset with the default key set for the same category \"{}\"", GetNormalizedPathString(), p1->name);
|
||||||
p1->active = true;
|
p1->active = true;
|
||||||
has_default = true;
|
has_default = true;
|
||||||
}
|
}
|
||||||
|
@ -960,7 +968,7 @@ bool GraphicPack2::Activate()
|
||||||
auto option_upscale = rules.FindOption("upscaleMagFilter");
|
auto option_upscale = rules.FindOption("upscaleMagFilter");
|
||||||
if(option_upscale && boost::iequals(*option_upscale, "NearestNeighbor"))
|
if(option_upscale && boost::iequals(*option_upscale, "NearestNeighbor"))
|
||||||
m_output_settings.upscale_filter = LatteTextureView::MagFilter::kNearestNeighbor;
|
m_output_settings.upscale_filter = LatteTextureView::MagFilter::kNearestNeighbor;
|
||||||
auto option_downscale = rules.FindOption("NearestNeighbor");
|
auto option_downscale = rules.FindOption("downscaleMinFilter");
|
||||||
if (option_downscale && boost::iequals(*option_downscale, "NearestNeighbor"))
|
if (option_downscale && boost::iequals(*option_downscale, "NearestNeighbor"))
|
||||||
m_output_settings.downscale_filter = LatteTextureView::MagFilter::kNearestNeighbor;
|
m_output_settings.downscale_filter = LatteTextureView::MagFilter::kNearestNeighbor;
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
#include "gui/debugger/DebuggerWindow2.h"
|
#include "gui/debugger/DebuggerWindow2.h"
|
||||||
|
|
||||||
#include "Cafe/OS/libs/coreinit/coreinit.h"
|
#include "Cafe/OS/libs/coreinit/coreinit.h"
|
||||||
|
#include "util/helpers/helpers.h"
|
||||||
|
|
||||||
#if BOOST_OS_WINDOWS
|
#if BOOST_OS_WINDOWS
|
||||||
#include <Windows.h>
|
#include <Windows.h>
|
||||||
|
@ -136,11 +137,6 @@ void debugger_createCodeBreakpoint(uint32 address, uint8 bpType)
|
||||||
debugger_updateExecutionBreakpoint(address);
|
debugger_updateExecutionBreakpoint(address);
|
||||||
}
|
}
|
||||||
|
|
||||||
void debugger_createExecuteBreakpoint(uint32 address)
|
|
||||||
{
|
|
||||||
debugger_createCodeBreakpoint(address, DEBUGGER_BP_T_NORMAL);
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace coreinit
|
namespace coreinit
|
||||||
{
|
{
|
||||||
std::vector<std::thread::native_handle_type>& OSGetSchedulerThreads();
|
std::vector<std::thread::native_handle_type>& OSGetSchedulerThreads();
|
||||||
|
@ -294,8 +290,23 @@ void debugger_toggleExecuteBreakpoint(uint32 address)
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// create new breakpoint
|
// create new execution breakpoint
|
||||||
debugger_createExecuteBreakpoint(address);
|
debugger_createCodeBreakpoint(address, DEBUGGER_BP_T_NORMAL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void debugger_toggleLoggingBreakpoint(uint32 address)
|
||||||
|
{
|
||||||
|
auto existingBP = debugger_getFirstBP(address, DEBUGGER_BP_T_LOGGING);
|
||||||
|
if (existingBP)
|
||||||
|
{
|
||||||
|
// delete existing breakpoint
|
||||||
|
debugger_deleteBreakpoint(existingBP);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// create new logging breakpoint
|
||||||
|
debugger_createCodeBreakpoint(address, DEBUGGER_BP_T_LOGGING);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -447,6 +458,34 @@ bool debugger_hasPatch(uint32 address)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void debugger_removePatch(uint32 address)
|
||||||
|
{
|
||||||
|
for (sint32 i = 0; i < debuggerState.patches.size(); i++)
|
||||||
|
{
|
||||||
|
auto& patch = debuggerState.patches[i];
|
||||||
|
if (address < patch->address || address >= (patch->address + patch->length))
|
||||||
|
continue;
|
||||||
|
MPTR startAddress = patch->address;
|
||||||
|
MPTR endAddress = patch->address + patch->length;
|
||||||
|
// remove any breakpoints overlapping with the patch
|
||||||
|
for (auto& bp : debuggerState.breakpoints)
|
||||||
|
{
|
||||||
|
if (bp->address + 4 > startAddress && bp->address < endAddress)
|
||||||
|
{
|
||||||
|
bp->enabled = false;
|
||||||
|
debugger_updateExecutionBreakpoint(bp->address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// restore original data
|
||||||
|
memcpy(MEMPTR<void>(startAddress).GetPtr(), patch->origData.data(), patch->length);
|
||||||
|
PPCRecompiler_invalidateRange(startAddress, endAddress);
|
||||||
|
// remove patch
|
||||||
|
delete patch;
|
||||||
|
debuggerState.patches.erase(debuggerState.patches.begin() + i);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void debugger_stepInto(PPCInterpreter_t* hCPU, bool updateDebuggerWindow = true)
|
void debugger_stepInto(PPCInterpreter_t* hCPU, bool updateDebuggerWindow = true)
|
||||||
{
|
{
|
||||||
bool isRecEnabled = ppcRecompilerEnabled;
|
bool isRecEnabled = ppcRecompilerEnabled;
|
||||||
|
@ -510,7 +549,48 @@ void debugger_enterTW(PPCInterpreter_t* hCPU)
|
||||||
{
|
{
|
||||||
if (bp->bpType == DEBUGGER_BP_T_LOGGING && bp->enabled)
|
if (bp->bpType == DEBUGGER_BP_T_LOGGING && bp->enabled)
|
||||||
{
|
{
|
||||||
std::string logName = !bp->comment.empty() ? "Breakpoint '"+boost::nowide::narrow(bp->comment)+"'" : fmt::format("Breakpoint at 0x{:08X} (no comment)", bp->address);
|
std::string comment = !bp->comment.empty() ? boost::nowide::narrow(bp->comment) : fmt::format("Breakpoint at 0x{:08X} (no comment)", bp->address);
|
||||||
|
|
||||||
|
auto replacePlaceholders = [&](const std::string& prefix, const auto& formatFunc)
|
||||||
|
{
|
||||||
|
size_t pos = 0;
|
||||||
|
while ((pos = comment.find(prefix, pos)) != std::string::npos)
|
||||||
|
{
|
||||||
|
size_t endPos = comment.find('}', pos);
|
||||||
|
if (endPos == std::string::npos)
|
||||||
|
break;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (int regNum = ConvertString<int>(comment.substr(pos + prefix.length(), endPos - pos - prefix.length())); regNum >= 0 && regNum < 32)
|
||||||
|
{
|
||||||
|
std::string replacement = formatFunc(regNum);
|
||||||
|
comment.replace(pos, endPos - pos + 1, replacement);
|
||||||
|
pos += replacement.length();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pos = endPos + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (...)
|
||||||
|
{
|
||||||
|
pos = endPos + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Replace integer register placeholders {rX}
|
||||||
|
replacePlaceholders("{r", [&](int regNum) {
|
||||||
|
return fmt::format("0x{:08X}", hCPU->gpr[regNum]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Replace floating point register placeholders {fX}
|
||||||
|
replacePlaceholders("{f", [&](int regNum) {
|
||||||
|
return fmt::format("{}", hCPU->fpr[regNum].fpr);
|
||||||
|
});
|
||||||
|
|
||||||
|
std::string logName = "Breakpoint '" + comment + "'";
|
||||||
std::string logContext = fmt::format("Thread: {:08x} LR: 0x{:08x}", MEMPTR<OSThread_t>(coreinit::OSGetCurrentThread()).GetMPTR(), hCPU->spr.LR, cemuLog_advancedPPCLoggingEnabled() ? " Stack Trace:" : "");
|
std::string logContext = fmt::format("Thread: {:08x} LR: 0x{:08x}", MEMPTR<OSThread_t>(coreinit::OSGetCurrentThread()).GetMPTR(), hCPU->spr.LR, cemuLog_advancedPPCLoggingEnabled() ? " Stack Trace:" : "");
|
||||||
cemuLog_log(LogType::Force, "[Debugger] {} was executed! {}", logName, logContext);
|
cemuLog_log(LogType::Force, "[Debugger] {} was executed! {}", logName, logContext);
|
||||||
if (cemuLog_advancedPPCLoggingEnabled())
|
if (cemuLog_advancedPPCLoggingEnabled())
|
||||||
|
@ -547,7 +627,7 @@ void debugger_enterTW(PPCInterpreter_t* hCPU)
|
||||||
debuggerState.debugSession.stepInto = false;
|
debuggerState.debugSession.stepInto = false;
|
||||||
debuggerState.debugSession.stepOver = false;
|
debuggerState.debugSession.stepOver = false;
|
||||||
debuggerState.debugSession.run = false;
|
debuggerState.debugSession.run = false;
|
||||||
while (true)
|
while (debuggerState.debugSession.isTrapped)
|
||||||
{
|
{
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||||
// check for step commands
|
// check for step commands
|
||||||
|
|
|
@ -100,8 +100,8 @@ extern debuggerState_t debuggerState;
|
||||||
// new API
|
// new API
|
||||||
DebuggerBreakpoint* debugger_getFirstBP(uint32 address);
|
DebuggerBreakpoint* debugger_getFirstBP(uint32 address);
|
||||||
void debugger_createCodeBreakpoint(uint32 address, uint8 bpType);
|
void debugger_createCodeBreakpoint(uint32 address, uint8 bpType);
|
||||||
void debugger_createExecuteBreakpoint(uint32 address);
|
|
||||||
void debugger_toggleExecuteBreakpoint(uint32 address); // create/remove execute breakpoint
|
void debugger_toggleExecuteBreakpoint(uint32 address); // create/remove execute breakpoint
|
||||||
|
void debugger_toggleLoggingBreakpoint(uint32 address); // create/remove logging breakpoint
|
||||||
void debugger_toggleBreakpoint(uint32 address, bool state, DebuggerBreakpoint* bp);
|
void debugger_toggleBreakpoint(uint32 address, bool state, DebuggerBreakpoint* bp);
|
||||||
|
|
||||||
void debugger_createMemoryBreakpoint(uint32 address, bool onRead, bool onWrite);
|
void debugger_createMemoryBreakpoint(uint32 address, bool onRead, bool onWrite);
|
||||||
|
@ -114,6 +114,7 @@ void debugger_updateExecutionBreakpoint(uint32 address, bool forceRestore = fals
|
||||||
|
|
||||||
void debugger_createPatch(uint32 address, std::span<uint8> patchData);
|
void debugger_createPatch(uint32 address, std::span<uint8> patchData);
|
||||||
bool debugger_hasPatch(uint32 address);
|
bool debugger_hasPatch(uint32 address);
|
||||||
|
void debugger_removePatch(uint32 address);
|
||||||
|
|
||||||
void debugger_forceBreak(); // force breakpoint at the next possible instruction
|
void debugger_forceBreak(); // force breakpoint at the next possible instruction
|
||||||
bool debugger_isTrapped();
|
bool debugger_isTrapped();
|
||||||
|
|
|
@ -114,13 +114,13 @@ void* ATTR_MS_ABI PPCRecompiler_virtualHLE(PPCInterpreter_t* hCPU, uint32 hleFun
|
||||||
|
|
||||||
void ATTR_MS_ABI PPCRecompiler_getTBL(PPCInterpreter_t* hCPU, uint32 gprIndex)
|
void ATTR_MS_ABI PPCRecompiler_getTBL(PPCInterpreter_t* hCPU, uint32 gprIndex)
|
||||||
{
|
{
|
||||||
uint64 coreTime = coreinit::coreinit_getTimerTick();
|
uint64 coreTime = coreinit::OSGetSystemTime();
|
||||||
hCPU->gpr[gprIndex] = (uint32)(coreTime&0xFFFFFFFF);
|
hCPU->gpr[gprIndex] = (uint32)(coreTime&0xFFFFFFFF);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ATTR_MS_ABI PPCRecompiler_getTBU(PPCInterpreter_t* hCPU, uint32 gprIndex)
|
void ATTR_MS_ABI PPCRecompiler_getTBU(PPCInterpreter_t* hCPU, uint32 gprIndex)
|
||||||
{
|
{
|
||||||
uint64 coreTime = coreinit::coreinit_getTimerTick();
|
uint64 coreTime = coreinit::OSGetSystemTime();
|
||||||
hCPU->gpr[gprIndex] = (uint32)((coreTime>>32)&0xFFFFFFFF);
|
hCPU->gpr[gprIndex] = (uint32)((coreTime>>32)&0xFFFFFFFF);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -52,7 +52,7 @@ struct LatteGPUState_t
|
||||||
uint32 gx2InitCalled; // incremented every time GX2Init() is called
|
uint32 gx2InitCalled; // incremented every time GX2Init() is called
|
||||||
// OpenGL control
|
// OpenGL control
|
||||||
uint32 glVendor; // GLVENDOR_*
|
uint32 glVendor; // GLVENDOR_*
|
||||||
bool alwaysDisplayDRC = false;
|
bool isDRCPrimary = false;
|
||||||
// temporary (replace with proper solution later)
|
// temporary (replace with proper solution later)
|
||||||
bool tvBufferUsesSRGB;
|
bool tvBufferUsesSRGB;
|
||||||
bool drcBufferUsesSRGB;
|
bool drcBufferUsesSRGB;
|
||||||
|
|
|
@ -141,6 +141,14 @@ private:
|
||||||
|
|
||||||
void LatteCP_processCommandBuffer(DrawPassContext& drawPassCtx);
|
void LatteCP_processCommandBuffer(DrawPassContext& drawPassCtx);
|
||||||
|
|
||||||
|
// called whenever the GPU runs out of commands or hits a wait condition (semaphores, HLE waits)
|
||||||
|
void LatteCP_signalEnterWait()
|
||||||
|
{
|
||||||
|
// based on the assumption that games won't do a rugpull and swap out buffer data in the middle of an uninterrupted sequence of drawcalls,
|
||||||
|
// we only flush caches when the GPU goes idle or has to wait for any operation
|
||||||
|
LatteIndices_invalidateAll();
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Read a U32 from the command buffer
|
* Read a U32 from the command buffer
|
||||||
* If no data is available then wait in a busy loop
|
* If no data is available then wait in a busy loop
|
||||||
|
@ -466,6 +474,8 @@ LatteCMDPtr LatteCP_itWaitRegMem(LatteCMDPtr cmd, uint32 nWords)
|
||||||
const uint32 GPU7_WAIT_MEM_OP_GREATER = 6;
|
const uint32 GPU7_WAIT_MEM_OP_GREATER = 6;
|
||||||
const uint32 GPU7_WAIT_MEM_OP_NEVER = 7;
|
const uint32 GPU7_WAIT_MEM_OP_NEVER = 7;
|
||||||
|
|
||||||
|
LatteCP_signalEnterWait();
|
||||||
|
|
||||||
bool stalls = false;
|
bool stalls = false;
|
||||||
if ((word0 & 0x10) != 0)
|
if ((word0 & 0x10) != 0)
|
||||||
{
|
{
|
||||||
|
@ -594,6 +604,7 @@ LatteCMDPtr LatteCP_itMemSemaphore(LatteCMDPtr cmd, uint32 nWords)
|
||||||
else if(SEM_SIGNAL == 7)
|
else if(SEM_SIGNAL == 7)
|
||||||
{
|
{
|
||||||
// wait
|
// wait
|
||||||
|
LatteCP_signalEnterWait();
|
||||||
size_t loopCount = 0;
|
size_t loopCount = 0;
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
|
@ -788,7 +799,7 @@ LatteCMDPtr LatteCP_itHLESampleTimer(LatteCMDPtr cmd, uint32 nWords)
|
||||||
{
|
{
|
||||||
cemu_assert_debug(nWords == 1);
|
cemu_assert_debug(nWords == 1);
|
||||||
MPTR timerMPTR = (MPTR)LatteReadCMD();
|
MPTR timerMPTR = (MPTR)LatteReadCMD();
|
||||||
memory_writeU64(timerMPTR, coreinit::coreinit_getTimerTick());
|
memory_writeU64(timerMPTR, coreinit::OSGetSystemTime());
|
||||||
return cmd;
|
return cmd;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1305,11 +1316,13 @@ void LatteCP_processCommandBuffer(DrawPassContext& drawPassCtx)
|
||||||
}
|
}
|
||||||
case IT_HLE_TRIGGER_SCANBUFFER_SWAP:
|
case IT_HLE_TRIGGER_SCANBUFFER_SWAP:
|
||||||
{
|
{
|
||||||
|
LatteCP_signalEnterWait();
|
||||||
LatteCP_itHLESwapScanBuffer(cmdData, nWords);
|
LatteCP_itHLESwapScanBuffer(cmdData, nWords);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case IT_HLE_WAIT_FOR_FLIP:
|
case IT_HLE_WAIT_FOR_FLIP:
|
||||||
{
|
{
|
||||||
|
LatteCP_signalEnterWait();
|
||||||
LatteCP_itHLEWaitForFlip(cmdData, nWords);
|
LatteCP_itHLEWaitForFlip(cmdData, nWords);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -1594,12 +1607,14 @@ void LatteCP_ProcessRingbuffer()
|
||||||
}
|
}
|
||||||
case IT_HLE_TRIGGER_SCANBUFFER_SWAP:
|
case IT_HLE_TRIGGER_SCANBUFFER_SWAP:
|
||||||
{
|
{
|
||||||
|
LatteCP_signalEnterWait();
|
||||||
LatteCP_itHLESwapScanBuffer(cmd, nWords);
|
LatteCP_itHLESwapScanBuffer(cmd, nWords);
|
||||||
timerRecheck += CP_TIMER_RECHECK / 64;
|
timerRecheck += CP_TIMER_RECHECK / 64;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case IT_HLE_WAIT_FOR_FLIP:
|
case IT_HLE_WAIT_FOR_FLIP:
|
||||||
{
|
{
|
||||||
|
LatteCP_signalEnterWait();
|
||||||
LatteCP_itHLEWaitForFlip(cmd, nWords);
|
LatteCP_itHLEWaitForFlip(cmd, nWords);
|
||||||
timerRecheck += CP_TIMER_RECHECK / 1;
|
timerRecheck += CP_TIMER_RECHECK / 1;
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#include "Cafe/HW/Latte/Core/LatteConst.h"
|
#include "Cafe/HW/Latte/Core/LatteConst.h"
|
||||||
#include "Cafe/HW/Latte/Renderer/Renderer.h"
|
#include "Cafe/HW/Latte/Renderer/Renderer.h"
|
||||||
#include "Cafe/HW/Latte/ISA/RegDefines.h"
|
#include "Cafe/HW/Latte/ISA/RegDefines.h"
|
||||||
|
#include "Cafe/HW/Latte/Core/LattePerformanceMonitor.h"
|
||||||
#include "Common/cpu_features.h"
|
#include "Common/cpu_features.h"
|
||||||
|
|
||||||
#if defined(ARCH_X86_64) && defined(__GNUC__)
|
#if defined(ARCH_X86_64) && defined(__GNUC__)
|
||||||
|
@ -9,32 +10,53 @@
|
||||||
|
|
||||||
struct
|
struct
|
||||||
{
|
{
|
||||||
const void* lastPtr;
|
struct CacheEntry
|
||||||
uint32 lastCount;
|
{
|
||||||
LattePrimitiveMode lastPrimitiveMode;
|
// input data
|
||||||
LatteIndexType lastIndexType;
|
const void* lastPtr;
|
||||||
// output
|
uint32 lastCount;
|
||||||
uint32 indexMin;
|
LattePrimitiveMode lastPrimitiveMode;
|
||||||
uint32 indexMax;
|
LatteIndexType lastIndexType;
|
||||||
Renderer::INDEX_TYPE renderIndexType;
|
uint64 lastUsed;
|
||||||
uint32 outputCount;
|
// output
|
||||||
uint32 indexBufferOffset;
|
uint32 indexMin;
|
||||||
uint32 indexBufferIndex;
|
uint32 indexMax;
|
||||||
|
Renderer::INDEX_TYPE renderIndexType;
|
||||||
|
uint32 outputCount;
|
||||||
|
Renderer::IndexAllocation indexAllocation;
|
||||||
|
};
|
||||||
|
std::array<CacheEntry, 8> entry;
|
||||||
|
uint64 currentUsageCounter{0};
|
||||||
}LatteIndexCache{};
|
}LatteIndexCache{};
|
||||||
|
|
||||||
void LatteIndices_invalidate(const void* memPtr, uint32 size)
|
void LatteIndices_invalidate(const void* memPtr, uint32 size)
|
||||||
{
|
{
|
||||||
if (LatteIndexCache.lastPtr >= memPtr && (LatteIndexCache.lastPtr < ((uint8*)memPtr + size)) )
|
for(auto& entry : LatteIndexCache.entry)
|
||||||
{
|
{
|
||||||
LatteIndexCache.lastPtr = nullptr;
|
if (entry.lastPtr >= memPtr && (entry.lastPtr < ((uint8*)memPtr + size)) )
|
||||||
LatteIndexCache.lastCount = 0;
|
{
|
||||||
|
if(entry.lastPtr != nullptr)
|
||||||
|
g_renderer->indexData_releaseIndexMemory(entry.indexAllocation);
|
||||||
|
entry.lastPtr = nullptr;
|
||||||
|
entry.lastCount = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void LatteIndices_invalidateAll()
|
void LatteIndices_invalidateAll()
|
||||||
{
|
{
|
||||||
LatteIndexCache.lastPtr = nullptr;
|
for(auto& entry : LatteIndexCache.entry)
|
||||||
LatteIndexCache.lastCount = 0;
|
{
|
||||||
|
if (entry.lastPtr != nullptr)
|
||||||
|
g_renderer->indexData_releaseIndexMemory(entry.indexAllocation);
|
||||||
|
entry.lastPtr = nullptr;
|
||||||
|
entry.lastCount = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64 LatteIndices_GetNextUsageIndex()
|
||||||
|
{
|
||||||
|
return LatteIndexCache.currentUsageCounter++;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32 LatteIndices_calculateIndexOutputSize(LattePrimitiveMode primitiveMode, LatteIndexType indexType, uint32 count)
|
uint32 LatteIndices_calculateIndexOutputSize(LattePrimitiveMode primitiveMode, LatteIndexType indexType, uint32 count)
|
||||||
|
@ -532,7 +554,7 @@ void LatteIndices_alternativeCalculateIndexMinMax(const void* indexData, LatteIn
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void LatteIndices_decode(const void* indexData, LatteIndexType indexType, uint32 count, LattePrimitiveMode primitiveMode, uint32& indexMin, uint32& indexMax, Renderer::INDEX_TYPE& renderIndexType, uint32& outputCount, uint32& indexBufferOffset, uint32& indexBufferIndex)
|
void LatteIndices_decode(const void* indexData, LatteIndexType indexType, uint32 count, LattePrimitiveMode primitiveMode, uint32& indexMin, uint32& indexMax, Renderer::INDEX_TYPE& renderIndexType, uint32& outputCount, Renderer::IndexAllocation& indexAllocation)
|
||||||
{
|
{
|
||||||
// what this should do:
|
// what this should do:
|
||||||
// [x] use fast SIMD-based index decoding
|
// [x] use fast SIMD-based index decoding
|
||||||
|
@ -542,17 +564,18 @@ void LatteIndices_decode(const void* indexData, LatteIndexType indexType, uint32
|
||||||
// [ ] better cache implementation, allow to cache across frames
|
// [ ] better cache implementation, allow to cache across frames
|
||||||
|
|
||||||
// reuse from cache if data didn't change
|
// reuse from cache if data didn't change
|
||||||
if (LatteIndexCache.lastPtr == indexData &&
|
auto cacheEntry = std::find_if(LatteIndexCache.entry.begin(), LatteIndexCache.entry.end(), [indexData, count, primitiveMode, indexType](const auto& entry)
|
||||||
LatteIndexCache.lastCount == count &&
|
|
||||||
LatteIndexCache.lastPrimitiveMode == primitiveMode &&
|
|
||||||
LatteIndexCache.lastIndexType == indexType)
|
|
||||||
{
|
{
|
||||||
indexMin = LatteIndexCache.indexMin;
|
return entry.lastPtr == indexData && entry.lastCount == count && entry.lastPrimitiveMode == primitiveMode && entry.lastIndexType == indexType;
|
||||||
indexMax = LatteIndexCache.indexMax;
|
});
|
||||||
renderIndexType = LatteIndexCache.renderIndexType;
|
if (cacheEntry != LatteIndexCache.entry.end())
|
||||||
outputCount = LatteIndexCache.outputCount;
|
{
|
||||||
indexBufferOffset = LatteIndexCache.indexBufferOffset;
|
indexMin = cacheEntry->indexMin;
|
||||||
indexBufferIndex = LatteIndexCache.indexBufferIndex;
|
indexMax = cacheEntry->indexMax;
|
||||||
|
renderIndexType = cacheEntry->renderIndexType;
|
||||||
|
outputCount = cacheEntry->outputCount;
|
||||||
|
indexAllocation = cacheEntry->indexAllocation;
|
||||||
|
cacheEntry->lastUsed = LatteIndices_GetNextUsageIndex();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -576,10 +599,12 @@ void LatteIndices_decode(const void* indexData, LatteIndexType indexType, uint32
|
||||||
indexMin = 0;
|
indexMin = 0;
|
||||||
indexMax = std::max(count, 1u)-1;
|
indexMax = std::max(count, 1u)-1;
|
||||||
renderIndexType = Renderer::INDEX_TYPE::NONE;
|
renderIndexType = Renderer::INDEX_TYPE::NONE;
|
||||||
|
indexAllocation = {};
|
||||||
return; // no indices
|
return; // no indices
|
||||||
}
|
}
|
||||||
// query index buffer from renderer
|
// query index buffer from renderer
|
||||||
void* indexOutputPtr = g_renderer->indexData_reserveIndexMemory(indexOutputSize, indexBufferOffset, indexBufferIndex);
|
indexAllocation = g_renderer->indexData_reserveIndexMemory(indexOutputSize);
|
||||||
|
void* indexOutputPtr = indexAllocation.mem;
|
||||||
|
|
||||||
// decode indices
|
// decode indices
|
||||||
indexMin = std::numeric_limits<uint32>::max();
|
indexMin = std::numeric_limits<uint32>::max();
|
||||||
|
@ -704,16 +729,25 @@ void LatteIndices_decode(const void* indexData, LatteIndexType indexType, uint32
|
||||||
// recalculate index range but filter out primitive restart index
|
// recalculate index range but filter out primitive restart index
|
||||||
LatteIndices_alternativeCalculateIndexMinMax(indexData, indexType, count, indexMin, indexMax);
|
LatteIndices_alternativeCalculateIndexMinMax(indexData, indexType, count, indexMin, indexMax);
|
||||||
}
|
}
|
||||||
g_renderer->indexData_uploadIndexMemory(indexBufferOffset, indexOutputSize);
|
g_renderer->indexData_uploadIndexMemory(indexAllocation);
|
||||||
|
performanceMonitor.cycle[performanceMonitor.cycleIndex].indexDataUploaded += indexOutputSize;
|
||||||
|
// get least recently used cache entry
|
||||||
|
auto lruEntry = std::min_element(LatteIndexCache.entry.begin(), LatteIndexCache.entry.end(), [](const auto& a, const auto& b)
|
||||||
|
{
|
||||||
|
return a.lastUsed < b.lastUsed;
|
||||||
|
});
|
||||||
|
// invalidate previous allocation
|
||||||
|
if(lruEntry->lastPtr != nullptr)
|
||||||
|
g_renderer->indexData_releaseIndexMemory(lruEntry->indexAllocation);
|
||||||
// update cache
|
// update cache
|
||||||
LatteIndexCache.lastPtr = indexData;
|
lruEntry->lastPtr = indexData;
|
||||||
LatteIndexCache.lastCount = count;
|
lruEntry->lastCount = count;
|
||||||
LatteIndexCache.lastPrimitiveMode = primitiveMode;
|
lruEntry->lastPrimitiveMode = primitiveMode;
|
||||||
LatteIndexCache.lastIndexType = indexType;
|
lruEntry->lastIndexType = indexType;
|
||||||
LatteIndexCache.indexMin = indexMin;
|
lruEntry->indexMin = indexMin;
|
||||||
LatteIndexCache.indexMax = indexMax;
|
lruEntry->indexMax = indexMax;
|
||||||
LatteIndexCache.renderIndexType = renderIndexType;
|
lruEntry->renderIndexType = renderIndexType;
|
||||||
LatteIndexCache.outputCount = outputCount;
|
lruEntry->outputCount = outputCount;
|
||||||
LatteIndexCache.indexBufferOffset = indexBufferOffset;
|
lruEntry->indexAllocation = indexAllocation;
|
||||||
LatteIndexCache.indexBufferIndex = indexBufferIndex;
|
lruEntry->lastUsed = LatteIndices_GetNextUsageIndex();
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,4 +4,4 @@
|
||||||
|
|
||||||
void LatteIndices_invalidate(const void* memPtr, uint32 size);
|
void LatteIndices_invalidate(const void* memPtr, uint32 size);
|
||||||
void LatteIndices_invalidateAll();
|
void LatteIndices_invalidateAll();
|
||||||
void LatteIndices_decode(const void* indexData, LatteIndexType indexType, uint32 count, LattePrimitiveMode primitiveMode, uint32& indexMin, uint32& indexMax, Renderer::INDEX_TYPE& renderIndexType, uint32& outputCount, uint32& indexBufferOffset, uint32& indexBufferIndex);
|
void LatteIndices_decode(const void* indexData, LatteIndexType indexType, uint32 count, LattePrimitiveMode primitiveMode, uint32& indexMin, uint32& indexMax, Renderer::INDEX_TYPE& renderIndexType, uint32& outputCount, Renderer::IndexAllocation& indexAllocation);
|
|
@ -107,7 +107,13 @@ void LatteOverlay_renderOverlay(ImVec2& position, ImVec2& pivot, sint32 directio
|
||||||
ImGui::Text("VRAM: %dMB / %dMB", g_state.vramUsage, g_state.vramTotal);
|
ImGui::Text("VRAM: %dMB / %dMB", g_state.vramUsage, g_state.vramTotal);
|
||||||
|
|
||||||
if (config.overlay.debug)
|
if (config.overlay.debug)
|
||||||
|
{
|
||||||
|
// general debug info
|
||||||
|
ImGui::Text("--- Debug info ---");
|
||||||
|
ImGui::Text("IndexUploadPerFrame: %dKB", (performanceMonitor.stats.indexDataUploadPerFrame+1023)/1024);
|
||||||
|
// backend specific info
|
||||||
g_renderer->AppendOverlayDebugInfo();
|
g_renderer->AppendOverlayDebugInfo();
|
||||||
|
}
|
||||||
|
|
||||||
position.y += (ImGui::GetWindowSize().y + 10.0f) * direction;
|
position.y += (ImGui::GetWindowSize().y + 10.0f) * direction;
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,7 +74,6 @@ void LattePerformanceMonitor_frameEnd()
|
||||||
uniformBankDataUploadedPerFrame /= 1024ULL;
|
uniformBankDataUploadedPerFrame /= 1024ULL;
|
||||||
uint32 uniformBankCountUploadedPerFrame = (uint32)(uniformBankUploadedCount / (uint64)elapsedFrames);
|
uint32 uniformBankCountUploadedPerFrame = (uint32)(uniformBankUploadedCount / (uint64)elapsedFrames);
|
||||||
uint64 indexDataUploadPerFrame = (indexDataUploaded / (uint64)elapsedFrames);
|
uint64 indexDataUploadPerFrame = (indexDataUploaded / (uint64)elapsedFrames);
|
||||||
indexDataUploadPerFrame /= 1024ULL;
|
|
||||||
|
|
||||||
double fps = (double)elapsedFrames2S * 1000.0 / (double)totalElapsedTimeFPS;
|
double fps = (double)elapsedFrames2S * 1000.0 / (double)totalElapsedTimeFPS;
|
||||||
uint32 shaderBindsPerFrame = shaderBindCounter / elapsedFrames;
|
uint32 shaderBindsPerFrame = shaderBindCounter / elapsedFrames;
|
||||||
|
@ -82,7 +81,7 @@ void LattePerformanceMonitor_frameEnd()
|
||||||
uint32 rlps = (uint32)((uint64)recompilerLeaveCount * 1000ULL / (uint64)totalElapsedTime);
|
uint32 rlps = (uint32)((uint64)recompilerLeaveCount * 1000ULL / (uint64)totalElapsedTime);
|
||||||
uint32 tlps = (uint32)((uint64)threadLeaveCount * 1000ULL / (uint64)totalElapsedTime);
|
uint32 tlps = (uint32)((uint64)threadLeaveCount * 1000ULL / (uint64)totalElapsedTime);
|
||||||
// set stats
|
// set stats
|
||||||
|
performanceMonitor.stats.indexDataUploadPerFrame = indexDataUploadPerFrame;
|
||||||
// next counter cycle
|
// next counter cycle
|
||||||
sint32 nextCycleIndex = (performanceMonitor.cycleIndex + 1) % PERFORMANCE_MONITOR_TRACK_CYCLES;
|
sint32 nextCycleIndex = (performanceMonitor.cycleIndex + 1) % PERFORMANCE_MONITOR_TRACK_CYCLES;
|
||||||
performanceMonitor.cycle[nextCycleIndex].drawCallCounter = 0;
|
performanceMonitor.cycle[nextCycleIndex].drawCallCounter = 0;
|
||||||
|
|
|
@ -124,6 +124,7 @@ typedef struct
|
||||||
LattePerfStatCounter numGraphicPipelines;
|
LattePerfStatCounter numGraphicPipelines;
|
||||||
LattePerfStatCounter numImages;
|
LattePerfStatCounter numImages;
|
||||||
LattePerfStatCounter numImageViews;
|
LattePerfStatCounter numImageViews;
|
||||||
|
LattePerfStatCounter numSamplers;
|
||||||
LattePerfStatCounter numRenderPass;
|
LattePerfStatCounter numRenderPass;
|
||||||
LattePerfStatCounter numFramebuffer;
|
LattePerfStatCounter numFramebuffer;
|
||||||
|
|
||||||
|
@ -131,6 +132,12 @@ typedef struct
|
||||||
LattePerfStatCounter numDrawBarriersPerFrame;
|
LattePerfStatCounter numDrawBarriersPerFrame;
|
||||||
LattePerfStatCounter numBeginRenderpassPerFrame;
|
LattePerfStatCounter numBeginRenderpassPerFrame;
|
||||||
}vk;
|
}vk;
|
||||||
|
|
||||||
|
// calculated stats (per frame)
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
uint32 indexDataUploadPerFrame;
|
||||||
|
}stats;
|
||||||
}performanceMonitor_t;
|
}performanceMonitor_t;
|
||||||
|
|
||||||
extern performanceMonitor_t performanceMonitor;
|
extern performanceMonitor_t performanceMonitor;
|
||||||
|
|
|
@ -11,7 +11,6 @@
|
||||||
#include "Cafe/HW/Latte/Core/LattePerformanceMonitor.h"
|
#include "Cafe/HW/Latte/Core/LattePerformanceMonitor.h"
|
||||||
#include "Cafe/GraphicPack/GraphicPack2.h"
|
#include "Cafe/GraphicPack/GraphicPack2.h"
|
||||||
#include "config/ActiveSettings.h"
|
#include "config/ActiveSettings.h"
|
||||||
#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h"
|
|
||||||
#include "gui/guiWrapper.h"
|
#include "gui/guiWrapper.h"
|
||||||
#include "Cafe/OS/libs/erreula/erreula.h"
|
#include "Cafe/OS/libs/erreula/erreula.h"
|
||||||
#include "input/InputManager.h"
|
#include "input/InputManager.h"
|
||||||
|
@ -933,13 +932,6 @@ void LatteRenderTarget_copyToBackbuffer(LatteTextureView* textureView, bool isPa
|
||||||
if (shader == nullptr)
|
if (shader == nullptr)
|
||||||
{
|
{
|
||||||
sint32 scaling_filter = downscaling ? GetConfig().downscale_filter : GetConfig().upscale_filter;
|
sint32 scaling_filter = downscaling ? GetConfig().downscale_filter : GetConfig().upscale_filter;
|
||||||
|
|
||||||
if (g_renderer->GetType() == RendererAPI::Vulkan)
|
|
||||||
{
|
|
||||||
// force linear or nearest neighbor filter
|
|
||||||
if(scaling_filter != kLinearFilter && scaling_filter != kNearestNeighborFilter)
|
|
||||||
scaling_filter = kLinearFilter;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (scaling_filter == kLinearFilter)
|
if (scaling_filter == kLinearFilter)
|
||||||
{
|
{
|
||||||
|
@ -957,7 +949,7 @@ void LatteRenderTarget_copyToBackbuffer(LatteTextureView* textureView, bool isPa
|
||||||
else
|
else
|
||||||
shader = RendererOutputShader::s_bicubic_shader;
|
shader = RendererOutputShader::s_bicubic_shader;
|
||||||
|
|
||||||
filter = LatteTextureView::MagFilter::kNearestNeighbor;
|
filter = LatteTextureView::MagFilter::kLinear;
|
||||||
}
|
}
|
||||||
else if (scaling_filter == kBicubicHermiteFilter)
|
else if (scaling_filter == kBicubicHermiteFilter)
|
||||||
{
|
{
|
||||||
|
@ -989,8 +981,6 @@ void LatteRenderTarget_copyToBackbuffer(LatteTextureView* textureView, bool isPa
|
||||||
g_renderer->ImguiEnd();
|
g_renderer->ImguiEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ctrlTabHotkeyPressed = false;
|
|
||||||
|
|
||||||
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_itHLECopyColorBufferToScanBuffer(MPTR colorBufferPtr, uint32 colorBufferWidth, uint32 colorBufferHeight, uint32 colorBufferSliceIndex, uint32 colorBufferFormat, uint32 colorBufferPitch, Latte::E_HWTILEMODE colorBufferTilemode, uint32 colorBufferSwizzle, uint32 renderTarget)
|
||||||
{
|
{
|
||||||
cemu_assert_debug(colorBufferSliceIndex == 0); // todo - support for non-zero slice
|
cemu_assert_debug(colorBufferSliceIndex == 0); // todo - support for non-zero slice
|
||||||
|
@ -1000,38 +990,31 @@ void LatteRenderTarget_itHLECopyColorBufferToScanBuffer(MPTR colorBufferPtr, uin
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto getVPADScreenActive = [](size_t n) -> std::pair<bool, bool> {
|
||||||
|
auto controller = InputManager::instance().get_vpad_controller(n);
|
||||||
|
if (!controller)
|
||||||
|
return {false,false};
|
||||||
|
auto pressed = controller->is_screen_active();
|
||||||
|
auto toggle = controller->is_screen_active_toggle();
|
||||||
|
return {pressed && !toggle, pressed && toggle};
|
||||||
|
};
|
||||||
|
|
||||||
const bool tabPressed = gui_isKeyDown(PlatformKeyCodes::TAB);
|
const bool tabPressed = gui_isKeyDown(PlatformKeyCodes::TAB);
|
||||||
const bool ctrlPressed = gui_isKeyDown(PlatformKeyCodes::LCONTROL);
|
const bool ctrlPressed = gui_isKeyDown(PlatformKeyCodes::LCONTROL);
|
||||||
|
const auto [vpad0Active, vpad0Toggle] = getVPADScreenActive(0);
|
||||||
|
const auto [vpad1Active, vpad1Toggle] = getVPADScreenActive(1);
|
||||||
|
|
||||||
bool showDRC = swkbd_hasKeyboardInputHook() == false && tabPressed;
|
const bool altScreenRequested = (!ctrlPressed && tabPressed) || vpad0Active || vpad1Active;
|
||||||
bool& alwaysDisplayDRC = LatteGPUState.alwaysDisplayDRC;
|
const bool togglePressed = (ctrlPressed && tabPressed) || vpad0Toggle || vpad1Toggle;
|
||||||
|
static bool togglePressedLast = false;
|
||||||
|
|
||||||
if (ctrlPressed && tabPressed)
|
bool& isDRCPrimary = LatteGPUState.isDRCPrimary;
|
||||||
{
|
|
||||||
if (ctrlTabHotkeyPressed == false)
|
|
||||||
{
|
|
||||||
alwaysDisplayDRC = !alwaysDisplayDRC;
|
|
||||||
ctrlTabHotkeyPressed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
ctrlTabHotkeyPressed = false;
|
|
||||||
|
|
||||||
if (alwaysDisplayDRC)
|
if(togglePressed && !togglePressedLast)
|
||||||
showDRC = !tabPressed;
|
isDRCPrimary = !isDRCPrimary;
|
||||||
|
togglePressedLast = togglePressed;
|
||||||
|
|
||||||
if (!showDRC)
|
bool showDRC = swkbd_hasKeyboardInputHook() == false && (isDRCPrimary ^ altScreenRequested);
|
||||||
{
|
|
||||||
auto controller = InputManager::instance().get_vpad_controller(0);
|
|
||||||
if (controller && controller->is_screen_active())
|
|
||||||
showDRC = true;
|
|
||||||
if (!showDRC)
|
|
||||||
{
|
|
||||||
controller = InputManager::instance().get_vpad_controller(1);
|
|
||||||
if (controller && controller->is_screen_active())
|
|
||||||
showDRC = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((renderTarget & RENDER_TARGET_DRC) && g_renderer->IsPadWindowActive())
|
if ((renderTarget & RENDER_TARGET_DRC) && g_renderer->IsPadWindowActive())
|
||||||
LatteRenderTarget_copyToBackbuffer(texView, true);
|
LatteRenderTarget_copyToBackbuffer(texView, true);
|
||||||
|
|
|
@ -451,9 +451,8 @@ void LatteShader_DumpShader(uint64 baseHash, uint64 auxHash, LatteDecompilerShad
|
||||||
suffix = "gs";
|
suffix = "gs";
|
||||||
else if (shader->shaderType == LatteConst::ShaderType::Pixel)
|
else if (shader->shaderType == LatteConst::ShaderType::Pixel)
|
||||||
suffix = "ps";
|
suffix = "ps";
|
||||||
fs::path dumpPath = "dump/shaders";
|
|
||||||
dumpPath /= fmt::format("{:016x}_{:016x}_{}.txt", baseHash, auxHash, suffix);
|
FileStream* fs = FileStream::createFile2(ActiveSettings::GetUserDataPath("dump/shaders/{:016x}_{:016x}_{}.txt", baseHash, auxHash, suffix));
|
||||||
FileStream* fs = FileStream::createFile2(dumpPath);
|
|
||||||
if (fs)
|
if (fs)
|
||||||
{
|
{
|
||||||
if (shader->strBuf_shaderSource)
|
if (shader->strBuf_shaderSource)
|
||||||
|
@ -479,9 +478,8 @@ void LatteShader_DumpRawShader(uint64 baseHash, uint64 auxHash, uint32 type, uin
|
||||||
suffix = "copy";
|
suffix = "copy";
|
||||||
else if (type == SHADER_DUMP_TYPE_COMPUTE)
|
else if (type == SHADER_DUMP_TYPE_COMPUTE)
|
||||||
suffix = "compute";
|
suffix = "compute";
|
||||||
fs::path dumpPath = "dump/shaders";
|
|
||||||
dumpPath /= fmt::format("{:016x}_{:016x}_{}.bin", baseHash, auxHash, suffix);
|
FileStream* fs = FileStream::createFile2(ActiveSettings::GetUserDataPath("dump/shaders/{:016x}_{:016x}_{}.bin", baseHash, auxHash, suffix));
|
||||||
FileStream* fs = FileStream::createFile2(dumpPath);
|
|
||||||
if (fs)
|
if (fs)
|
||||||
{
|
{
|
||||||
fs->writeData(programCode, programLen);
|
fs->writeData(programCode, programLen);
|
||||||
|
|
|
@ -25,6 +25,9 @@
|
||||||
#include "util/helpers/Serializer.h"
|
#include "util/helpers/Serializer.h"
|
||||||
|
|
||||||
#include <wx/msgdlg.h>
|
#include <wx/msgdlg.h>
|
||||||
|
#include <audio/IAudioAPI.h>
|
||||||
|
#include <util/bootSound/BootSoundReader.h>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
#if BOOST_OS_WINDOWS
|
#if BOOST_OS_WINDOWS
|
||||||
#include <psapi.h>
|
#include <psapi.h>
|
||||||
|
@ -155,6 +158,118 @@ bool LoadTGAFile(const std::vector<uint8>& buffer, TGAFILE *tgaFile)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class BootSoundPlayer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
BootSoundPlayer() = default;
|
||||||
|
~BootSoundPlayer()
|
||||||
|
{
|
||||||
|
m_stopRequested = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void StartSound()
|
||||||
|
{
|
||||||
|
if (!m_bootSndPlayThread.joinable())
|
||||||
|
{
|
||||||
|
m_fadeOutRequested = false;
|
||||||
|
m_stopRequested = false;
|
||||||
|
m_bootSndPlayThread = std::thread{[this]() {
|
||||||
|
StreamBootSound();
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FadeOutSound()
|
||||||
|
{
|
||||||
|
m_fadeOutRequested = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ApplyFadeOutEffect(std::span<sint16> samples, uint64& fadeOutSample, uint64 fadeOutDuration)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < samples.size(); i += 2)
|
||||||
|
{
|
||||||
|
const float decibel = (float)fadeOutSample / fadeOutDuration * -60.0f;
|
||||||
|
const float volumeFactor = pow(10, decibel / 20);
|
||||||
|
samples[i] *= volumeFactor;
|
||||||
|
samples[i + 1] *= volumeFactor;
|
||||||
|
fadeOutSample++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void StreamBootSound()
|
||||||
|
{
|
||||||
|
SetThreadName("bootsnd");
|
||||||
|
constexpr sint32 sampleRate = 48'000;
|
||||||
|
constexpr sint32 bitsPerSample = 16;
|
||||||
|
constexpr sint32 samplesPerBlock = sampleRate / 10; // block is 1/10th of a second
|
||||||
|
constexpr sint32 nChannels = 2;
|
||||||
|
static_assert(bitsPerSample % 8 == 0, "bits per sample is not a multiple of 8");
|
||||||
|
|
||||||
|
AudioAPIPtr bootSndAudioDev;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
bootSndAudioDev = IAudioAPI::CreateDeviceFromConfig(true, sampleRate, nChannels, samplesPerBlock, bitsPerSample);
|
||||||
|
if(!bootSndAudioDev)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
catch (const std::runtime_error& ex)
|
||||||
|
{
|
||||||
|
cemuLog_log(LogType::Force, "Failed to initialise audio device for bootup sound");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
bootSndAudioDev->SetAudioDelayOverride(4);
|
||||||
|
bootSndAudioDev->Play();
|
||||||
|
|
||||||
|
std::string sndPath = fmt::format("{}/meta/{}", CafeSystem::GetMlcStoragePath(CafeSystem::GetForegroundTitleId()), "bootSound.btsnd");
|
||||||
|
sint32 fscStatus = FSC_STATUS_UNDEFINED;
|
||||||
|
|
||||||
|
if(!fsc_doesFileExist(sndPath.c_str()))
|
||||||
|
return;
|
||||||
|
|
||||||
|
FSCVirtualFile* bootSndFileHandle = fsc_open(sndPath.c_str(), FSC_ACCESS_FLAG::OPEN_FILE | FSC_ACCESS_FLAG::READ_PERMISSION, &fscStatus);
|
||||||
|
if(!bootSndFileHandle)
|
||||||
|
{
|
||||||
|
cemuLog_log(LogType::Force, "failed to open bootSound.btsnd");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr sint32 audioBlockSize = samplesPerBlock * (bitsPerSample/8) * nChannels;
|
||||||
|
BootSoundReader bootSndFileReader(bootSndFileHandle, audioBlockSize);
|
||||||
|
|
||||||
|
uint64 fadeOutSample = 0; // track how far into the fadeout
|
||||||
|
constexpr uint64 fadeOutDuration = sampleRate * 2; // fadeout should last 2 seconds
|
||||||
|
while(fadeOutSample < fadeOutDuration && !m_stopRequested)
|
||||||
|
{
|
||||||
|
while (bootSndAudioDev->NeedAdditionalBlocks())
|
||||||
|
{
|
||||||
|
sint16* data = bootSndFileReader.getSamples();
|
||||||
|
if(data == nullptr)
|
||||||
|
{
|
||||||
|
// break outer loop
|
||||||
|
m_stopRequested = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if(m_fadeOutRequested)
|
||||||
|
ApplyFadeOutEffect({data, samplesPerBlock * nChannels}, fadeOutSample, fadeOutDuration);
|
||||||
|
|
||||||
|
bootSndAudioDev->FeedBlock(data);
|
||||||
|
}
|
||||||
|
// sleep for the duration of a single block
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(samplesPerBlock / (sampleRate/ 1'000)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(bootSndFileHandle)
|
||||||
|
fsc_close(bootSndFileHandle);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::thread m_bootSndPlayThread;
|
||||||
|
std::atomic_bool m_fadeOutRequested = false;
|
||||||
|
std::atomic_bool m_stopRequested = false;
|
||||||
|
};
|
||||||
|
static BootSoundPlayer g_bootSndPlayer;
|
||||||
|
|
||||||
void LatteShaderCache_finish()
|
void LatteShaderCache_finish()
|
||||||
{
|
{
|
||||||
if (g_renderer->GetType() == RendererAPI::Vulkan)
|
if (g_renderer->GetType() == RendererAPI::Vulkan)
|
||||||
|
@ -299,6 +414,9 @@ void LatteShaderCache_Load()
|
||||||
loadBackgroundTexture(true, g_shaderCacheLoaderState.textureTVId);
|
loadBackgroundTexture(true, g_shaderCacheLoaderState.textureTVId);
|
||||||
loadBackgroundTexture(false, g_shaderCacheLoaderState.textureDRCId);
|
loadBackgroundTexture(false, g_shaderCacheLoaderState.textureDRCId);
|
||||||
|
|
||||||
|
if(GetConfig().play_boot_sound)
|
||||||
|
g_bootSndPlayer.StartSound();
|
||||||
|
|
||||||
sint32 numLoadedShaders = 0;
|
sint32 numLoadedShaders = 0;
|
||||||
uint32 loadIndex = 0;
|
uint32 loadIndex = 0;
|
||||||
|
|
||||||
|
@ -365,6 +483,11 @@ void LatteShaderCache_Load()
|
||||||
g_renderer->DeleteTexture(g_shaderCacheLoaderState.textureTVId);
|
g_renderer->DeleteTexture(g_shaderCacheLoaderState.textureTVId);
|
||||||
if (g_shaderCacheLoaderState.textureDRCId)
|
if (g_shaderCacheLoaderState.textureDRCId)
|
||||||
g_renderer->DeleteTexture(g_shaderCacheLoaderState.textureDRCId);
|
g_renderer->DeleteTexture(g_shaderCacheLoaderState.textureDRCId);
|
||||||
|
|
||||||
|
g_bootSndPlayer.FadeOutSound();
|
||||||
|
|
||||||
|
if(Latte_GetStopSignal())
|
||||||
|
LatteThread_Exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
void LatteShaderCache_ShowProgress(const std::function <bool(void)>& loadUpdateFunc, bool isPipelines)
|
void LatteShaderCache_ShowProgress(const std::function <bool(void)>& loadUpdateFunc, bool isPipelines)
|
||||||
|
@ -505,8 +628,6 @@ void LatteShaderCache_LoadVulkanPipelineCache(uint64 cacheTitleId)
|
||||||
g_shaderCacheLoaderState.loadedPipelines = 0;
|
g_shaderCacheLoaderState.loadedPipelines = 0;
|
||||||
LatteShaderCache_ShowProgress(LatteShaderCache_updatePipelineLoadingProgress, true);
|
LatteShaderCache_ShowProgress(LatteShaderCache_updatePipelineLoadingProgress, true);
|
||||||
pipelineCache.EndLoading();
|
pipelineCache.EndLoading();
|
||||||
if(Latte_GetStopSignal())
|
|
||||||
LatteThread_Exit();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool LatteShaderCache_updatePipelineLoadingProgress()
|
bool LatteShaderCache_updatePipelineLoadingProgress()
|
||||||
|
@ -805,4 +926,4 @@ void LatteShaderCache_handleDeprecatedCacheFiles(fs::path pathGeneric, fs::path
|
||||||
fs::remove(pathGenericPre1_25_0, ec);
|
fs::remove(pathGenericPre1_25_0, ec);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -257,6 +257,7 @@ void LatteThread_Exit()
|
||||||
LatteSHRC_UnloadAll();
|
LatteSHRC_UnloadAll();
|
||||||
// close disk cache
|
// close disk cache
|
||||||
LatteShaderCache_Close();
|
LatteShaderCache_Close();
|
||||||
|
RendererOutputShader::ShutdownStatic();
|
||||||
// destroy renderer but make sure that g_renderer remains valid until the destructor has finished
|
// destroy renderer but make sure that g_renderer remains valid until the destructor has finished
|
||||||
if (g_renderer)
|
if (g_renderer)
|
||||||
{
|
{
|
||||||
|
|
|
@ -370,6 +370,8 @@ bool LatteDecompiler_IsALUTransInstruction(bool isOP3, uint32 opcode)
|
||||||
opcode == ALU_OP2_INST_LSHR_INT ||
|
opcode == ALU_OP2_INST_LSHR_INT ||
|
||||||
opcode == ALU_OP2_INST_MAX_INT ||
|
opcode == ALU_OP2_INST_MAX_INT ||
|
||||||
opcode == ALU_OP2_INST_MIN_INT ||
|
opcode == ALU_OP2_INST_MIN_INT ||
|
||||||
|
opcode == ALU_OP2_INST_MAX_UINT ||
|
||||||
|
opcode == ALU_OP2_INST_MIN_UINT ||
|
||||||
opcode == ALU_OP2_INST_MOVA_FLOOR ||
|
opcode == ALU_OP2_INST_MOVA_FLOOR ||
|
||||||
opcode == ALU_OP2_INST_MOVA_INT ||
|
opcode == ALU_OP2_INST_MOVA_INT ||
|
||||||
opcode == ALU_OP2_INST_SETE_DX10 ||
|
opcode == ALU_OP2_INST_SETE_DX10 ||
|
||||||
|
|
|
@ -140,6 +140,8 @@ bool _isIntegerInstruction(const LatteDecompilerALUInstruction& aluInstruction)
|
||||||
case ALU_OP2_INST_SUB_INT:
|
case ALU_OP2_INST_SUB_INT:
|
||||||
case ALU_OP2_INST_MAX_INT:
|
case ALU_OP2_INST_MAX_INT:
|
||||||
case ALU_OP2_INST_MIN_INT:
|
case ALU_OP2_INST_MIN_INT:
|
||||||
|
case ALU_OP2_INST_MAX_UINT:
|
||||||
|
case ALU_OP2_INST_MIN_UINT:
|
||||||
case ALU_OP2_INST_SETE_INT:
|
case ALU_OP2_INST_SETE_INT:
|
||||||
case ALU_OP2_INST_SETGT_INT:
|
case ALU_OP2_INST_SETGT_INT:
|
||||||
case ALU_OP2_INST_SETGE_INT:
|
case ALU_OP2_INST_SETGE_INT:
|
||||||
|
|
|
@ -1415,19 +1415,23 @@ void _emitALUOP2InstructionCode(LatteDecompilerShaderContext* shaderContext, Lat
|
||||||
}
|
}
|
||||||
else if( aluInstruction->opcode == ALU_OP2_INST_ADD_INT )
|
else if( aluInstruction->opcode == ALU_OP2_INST_ADD_INT )
|
||||||
_emitALUOperationBinary<LATTE_DECOMPILER_DTYPE_SIGNED_INT>(shaderContext, aluInstruction, " + ");
|
_emitALUOperationBinary<LATTE_DECOMPILER_DTYPE_SIGNED_INT>(shaderContext, aluInstruction, " + ");
|
||||||
else if( aluInstruction->opcode == ALU_OP2_INST_MAX_INT || aluInstruction->opcode == ALU_OP2_INST_MIN_INT )
|
else if( aluInstruction->opcode == ALU_OP2_INST_MAX_INT || aluInstruction->opcode == ALU_OP2_INST_MIN_INT ||
|
||||||
|
aluInstruction->opcode == ALU_OP2_INST_MAX_UINT || aluInstruction->opcode == ALU_OP2_INST_MIN_UINT)
|
||||||
{
|
{
|
||||||
// not verified
|
// not verified
|
||||||
|
bool isUnsigned = aluInstruction->opcode == ALU_OP2_INST_MAX_UINT || aluInstruction->opcode == ALU_OP2_INST_MIN_UINT;
|
||||||
|
auto opType = isUnsigned ? LATTE_DECOMPILER_DTYPE_UNSIGNED_INT : LATTE_DECOMPILER_DTYPE_SIGNED_INT;
|
||||||
_emitInstructionOutputVariableName(shaderContext, aluInstruction);
|
_emitInstructionOutputVariableName(shaderContext, aluInstruction);
|
||||||
if( aluInstruction->opcode == ALU_OP2_INST_MAX_INT )
|
src->add(" = ");
|
||||||
src->add(" = max(");
|
_emitTypeConversionPrefix(shaderContext, opType, outputType);
|
||||||
|
if( aluInstruction->opcode == ALU_OP2_INST_MAX_INT || aluInstruction->opcode == ALU_OP2_INST_MAX_UINT )
|
||||||
|
src->add("max(");
|
||||||
else
|
else
|
||||||
src->add(" = min(");
|
src->add("min(");
|
||||||
_emitTypeConversionPrefix(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, outputType);
|
_emitOperandInputCode(shaderContext, aluInstruction, 0, opType);
|
||||||
_emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_SIGNED_INT);
|
|
||||||
src->add(", ");
|
src->add(", ");
|
||||||
_emitOperandInputCode(shaderContext, aluInstruction, 1, LATTE_DECOMPILER_DTYPE_SIGNED_INT);
|
_emitOperandInputCode(shaderContext, aluInstruction, 1, opType);
|
||||||
_emitTypeConversionSuffix(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, outputType);
|
_emitTypeConversionSuffix(shaderContext, opType, outputType);
|
||||||
src->add(");" _CRLF);
|
src->add(");" _CRLF);
|
||||||
}
|
}
|
||||||
else if( aluInstruction->opcode == ALU_OP2_INST_SUB_INT )
|
else if( aluInstruction->opcode == ALU_OP2_INST_SUB_INT )
|
||||||
|
|
|
@ -60,6 +60,8 @@
|
||||||
#define ALU_OP2_INST_SUB_INT (0x035) // integer instruction
|
#define ALU_OP2_INST_SUB_INT (0x035) // integer instruction
|
||||||
#define ALU_OP2_INST_MAX_INT (0x036) // integer instruction
|
#define ALU_OP2_INST_MAX_INT (0x036) // integer instruction
|
||||||
#define ALU_OP2_INST_MIN_INT (0x037) // integer instruction
|
#define ALU_OP2_INST_MIN_INT (0x037) // integer instruction
|
||||||
|
#define ALU_OP2_INST_MAX_UINT (0x038) // integer instruction
|
||||||
|
#define ALU_OP2_INST_MIN_UINT (0x039) // integer instruction
|
||||||
#define ALU_OP2_INST_SETE_INT (0x03A) // integer instruction
|
#define ALU_OP2_INST_SETE_INT (0x03A) // integer instruction
|
||||||
#define ALU_OP2_INST_SETGT_INT (0x03B) // integer instruction
|
#define ALU_OP2_INST_SETGT_INT (0x03B) // integer instruction
|
||||||
#define ALU_OP2_INST_SETGE_INT (0x03C) // integer instruction
|
#define ALU_OP2_INST_SETGE_INT (0x03C) // integer instruction
|
||||||
|
|
|
@ -570,13 +570,10 @@ void OpenGLRenderer::DrawBackbufferQuad(LatteTextureView* texView, RendererOutpu
|
||||||
g_renderer->ClearColorbuffer(padView);
|
g_renderer->ClearColorbuffer(padView);
|
||||||
}
|
}
|
||||||
|
|
||||||
sint32 effectiveWidth, effectiveHeight;
|
|
||||||
texView->baseTexture->GetEffectiveSize(effectiveWidth, effectiveHeight, 0);
|
|
||||||
|
|
||||||
shader_unbind(RendererShader::ShaderType::kGeometry);
|
shader_unbind(RendererShader::ShaderType::kGeometry);
|
||||||
shader_bind(shader->GetVertexShader());
|
shader_bind(shader->GetVertexShader());
|
||||||
shader_bind(shader->GetFragmentShader());
|
shader_bind(shader->GetFragmentShader());
|
||||||
shader->SetUniformParameters(*texView, { effectiveWidth, effectiveHeight }, { imageWidth, imageHeight });
|
shader->SetUniformParameters(*texView, {imageWidth, imageHeight});
|
||||||
|
|
||||||
// set viewport
|
// set viewport
|
||||||
glViewportIndexedf(0, imageX, imageY, imageWidth, imageHeight);
|
glViewportIndexedf(0, imageX, imageY, imageWidth, imageHeight);
|
||||||
|
@ -584,6 +581,12 @@ void OpenGLRenderer::DrawBackbufferQuad(LatteTextureView* texView, RendererOutpu
|
||||||
LatteTextureViewGL* texViewGL = (LatteTextureViewGL*)texView;
|
LatteTextureViewGL* texViewGL = (LatteTextureViewGL*)texView;
|
||||||
texture_bindAndActivate(texView, 0);
|
texture_bindAndActivate(texView, 0);
|
||||||
|
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||||
|
texViewGL->samplerState.clampS = texViewGL->samplerState.clampT = 0xFF;
|
||||||
|
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, useLinearTexFilter ? GL_LINEAR : GL_NEAREST);
|
||||||
|
texViewGL->samplerState.filterMin = 0xFFFFFFFF;
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, useLinearTexFilter ? GL_LINEAR : GL_NEAREST);
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, useLinearTexFilter ? GL_LINEAR : GL_NEAREST);
|
||||||
texViewGL->samplerState.filterMag = 0xFFFFFFFF;
|
texViewGL->samplerState.filterMag = 0xFFFFFFFF;
|
||||||
|
|
||||||
|
|
|
@ -102,16 +102,21 @@ public:
|
||||||
static void SetAttributeArrayState(uint32 index, bool isEnabled, sint32 aluDivisor);
|
static void SetAttributeArrayState(uint32 index, bool isEnabled, sint32 aluDivisor);
|
||||||
static void SetArrayElementBuffer(GLuint arrayElementBuffer);
|
static void SetArrayElementBuffer(GLuint arrayElementBuffer);
|
||||||
|
|
||||||
// index
|
// index (not used by OpenGL renderer yet)
|
||||||
void* indexData_reserveIndexMemory(uint32 size, uint32& offset, uint32& bufferIndex) override
|
IndexAllocation indexData_reserveIndexMemory(uint32 size) override
|
||||||
{
|
{
|
||||||
assert_dbg();
|
cemu_assert_unimplemented();
|
||||||
return nullptr;
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
void indexData_uploadIndexMemory(uint32 offset, uint32 size) override
|
void indexData_releaseIndexMemory(IndexAllocation& allocation) override
|
||||||
{
|
{
|
||||||
assert_dbg();
|
cemu_assert_unimplemented();
|
||||||
|
}
|
||||||
|
|
||||||
|
void indexData_uploadIndexMemory(IndexAllocation& allocation) override
|
||||||
|
{
|
||||||
|
cemu_assert_unimplemented();
|
||||||
}
|
}
|
||||||
|
|
||||||
// uniform
|
// uniform
|
||||||
|
|
|
@ -138,8 +138,15 @@ public:
|
||||||
virtual void draw_endSequence() = 0;
|
virtual void draw_endSequence() = 0;
|
||||||
|
|
||||||
// index
|
// index
|
||||||
virtual void* indexData_reserveIndexMemory(uint32 size, uint32& offset, uint32& bufferIndex) = 0;
|
struct IndexAllocation
|
||||||
virtual void indexData_uploadIndexMemory(uint32 offset, uint32 size) = 0;
|
{
|
||||||
|
void* mem; // pointer to index data inside buffer
|
||||||
|
void* rendererInternal; // for renderer use
|
||||||
|
};
|
||||||
|
|
||||||
|
virtual IndexAllocation indexData_reserveIndexMemory(uint32 size) = 0;
|
||||||
|
virtual void indexData_releaseIndexMemory(IndexAllocation& allocation) = 0;
|
||||||
|
virtual void indexData_uploadIndexMemory(IndexAllocation& allocation) = 0;
|
||||||
|
|
||||||
// occlusion queries
|
// occlusion queries
|
||||||
virtual LatteQueryObject* occlusionQuery_create() = 0;
|
virtual LatteQueryObject* occlusionQuery_create() = 0;
|
||||||
|
|
|
@ -2,18 +2,7 @@
|
||||||
#include "Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h"
|
#include "Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h"
|
||||||
|
|
||||||
const std::string RendererOutputShader::s_copy_shader_source =
|
const std::string RendererOutputShader::s_copy_shader_source =
|
||||||
R"(#version 420
|
R"(
|
||||||
|
|
||||||
#ifdef VULKAN
|
|
||||||
layout(location = 0) in vec2 passUV;
|
|
||||||
layout(binding = 0) uniform sampler2D textureSrc;
|
|
||||||
layout(location = 0) out vec4 colorOut0;
|
|
||||||
#else
|
|
||||||
in vec2 passUV;
|
|
||||||
layout(binding=0) uniform sampler2D textureSrc;
|
|
||||||
layout(location = 0) out vec4 colorOut0;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
void main()
|
void main()
|
||||||
{
|
{
|
||||||
colorOut0 = vec4(texture(textureSrc, passUV).rgb,1.0);
|
colorOut0 = vec4(texture(textureSrc, passUV).rgb,1.0);
|
||||||
|
@ -22,20 +11,6 @@ void main()
|
||||||
|
|
||||||
const std::string RendererOutputShader::s_bicubic_shader_source =
|
const std::string RendererOutputShader::s_bicubic_shader_source =
|
||||||
R"(
|
R"(
|
||||||
#version 420
|
|
||||||
|
|
||||||
#ifdef VULKAN
|
|
||||||
layout(location = 0) in vec2 passUV;
|
|
||||||
layout(binding = 0) uniform sampler2D textureSrc;
|
|
||||||
layout(binding = 1) uniform vec2 textureSrcResolution;
|
|
||||||
layout(location = 0) out vec4 colorOut0;
|
|
||||||
#else
|
|
||||||
in vec2 passUV;
|
|
||||||
layout(binding=0) uniform sampler2D textureSrc;
|
|
||||||
uniform vec2 textureSrcResolution;
|
|
||||||
layout(location = 0) out vec4 colorOut0;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
vec4 cubic(float x)
|
vec4 cubic(float x)
|
||||||
{
|
{
|
||||||
float x2 = x * x;
|
float x2 = x * x;
|
||||||
|
@ -48,24 +23,23 @@ vec4 cubic(float x)
|
||||||
return w / 6.0;
|
return w / 6.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
vec4 bcFilter(vec2 texcoord, vec2 texscale)
|
vec4 bcFilter(vec2 uv, vec4 texelSize)
|
||||||
{
|
{
|
||||||
float fx = fract(texcoord.x);
|
vec2 pixel = uv*texelSize.zw - 0.5;
|
||||||
float fy = fract(texcoord.y);
|
vec2 pixelFrac = fract(pixel);
|
||||||
texcoord.x -= fx;
|
vec2 pixelInt = pixel - pixelFrac;
|
||||||
texcoord.y -= fy;
|
|
||||||
|
|
||||||
vec4 xcubic = cubic(fx);
|
vec4 xcubic = cubic(pixelFrac.x);
|
||||||
vec4 ycubic = cubic(fy);
|
vec4 ycubic = cubic(pixelFrac.y);
|
||||||
|
|
||||||
vec4 c = vec4(texcoord.x - 0.5, texcoord.x + 1.5, texcoord.y - 0.5, texcoord.y + 1.5);
|
vec4 c = vec4(pixelInt.x - 0.5, pixelInt.x + 1.5, pixelInt.y - 0.5, pixelInt.y + 1.5);
|
||||||
vec4 s = vec4(xcubic.x + xcubic.y, xcubic.z + xcubic.w, ycubic.x + ycubic.y, ycubic.z + ycubic.w);
|
vec4 s = vec4(xcubic.x + xcubic.y, xcubic.z + xcubic.w, ycubic.x + ycubic.y, ycubic.z + ycubic.w);
|
||||||
vec4 offset = c + vec4(xcubic.y, xcubic.w, ycubic.y, ycubic.w) / s;
|
vec4 offset = c + vec4(xcubic.y, xcubic.w, ycubic.y, ycubic.w) / s;
|
||||||
|
|
||||||
vec4 sample0 = texture(textureSrc, vec2(offset.x, offset.z) * texscale);
|
vec4 sample0 = texture(textureSrc, vec2(offset.x, offset.z) * texelSize.xy);
|
||||||
vec4 sample1 = texture(textureSrc, vec2(offset.y, offset.z) * texscale);
|
vec4 sample1 = texture(textureSrc, vec2(offset.y, offset.z) * texelSize.xy);
|
||||||
vec4 sample2 = texture(textureSrc, vec2(offset.x, offset.w) * texscale);
|
vec4 sample2 = texture(textureSrc, vec2(offset.x, offset.w) * texelSize.xy);
|
||||||
vec4 sample3 = texture(textureSrc, vec2(offset.y, offset.w) * texscale);
|
vec4 sample3 = texture(textureSrc, vec2(offset.y, offset.w) * texelSize.xy);
|
||||||
|
|
||||||
float sx = s.x / (s.x + s.y);
|
float sx = s.x / (s.x + s.y);
|
||||||
float sy = s.z / (s.z + s.w);
|
float sy = s.z / (s.z + s.w);
|
||||||
|
@ -76,20 +50,13 @@ vec4 bcFilter(vec2 texcoord, vec2 texscale)
|
||||||
}
|
}
|
||||||
|
|
||||||
void main(){
|
void main(){
|
||||||
colorOut0 = vec4(bcFilter(passUV*textureSrcResolution, vec2(1.0,1.0)/textureSrcResolution).rgb,1.0);
|
vec4 texelSize = vec4( 1.0 / textureSrcResolution.xy, textureSrcResolution.xy);
|
||||||
|
colorOut0 = vec4(bcFilter(passUV, texelSize).rgb,1.0);
|
||||||
}
|
}
|
||||||
)";
|
)";
|
||||||
|
|
||||||
const std::string RendererOutputShader::s_hermite_shader_source =
|
const std::string RendererOutputShader::s_hermite_shader_source =
|
||||||
R"(#version 420
|
R"(
|
||||||
|
|
||||||
in vec4 gl_FragCoord;
|
|
||||||
in vec2 passUV;
|
|
||||||
layout(binding=0) uniform sampler2D textureSrc;
|
|
||||||
uniform vec2 textureSrcResolution;
|
|
||||||
uniform vec2 outputResolution;
|
|
||||||
layout(location = 0) out vec4 colorOut0;
|
|
||||||
|
|
||||||
// https://www.shadertoy.com/view/MllSzX
|
// https://www.shadertoy.com/view/MllSzX
|
||||||
|
|
||||||
vec3 CubicHermite (vec3 A, vec3 B, vec3 C, vec3 D, float t)
|
vec3 CubicHermite (vec3 A, vec3 B, vec3 C, vec3 D, float t)
|
||||||
|
@ -111,7 +78,7 @@ vec3 BicubicHermiteTexture(vec2 uv, vec4 texelSize)
|
||||||
vec2 frac = fract(pixel);
|
vec2 frac = fract(pixel);
|
||||||
pixel = floor(pixel) / texelSize.zw - vec2(texelSize.xy/2.0);
|
pixel = floor(pixel) / texelSize.zw - vec2(texelSize.xy/2.0);
|
||||||
|
|
||||||
vec4 doubleSize = texelSize*texelSize;
|
vec4 doubleSize = texelSize*2.0;
|
||||||
|
|
||||||
vec3 C00 = texture(textureSrc, pixel + vec2(-texelSize.x ,-texelSize.y)).rgb;
|
vec3 C00 = texture(textureSrc, pixel + vec2(-texelSize.x ,-texelSize.y)).rgb;
|
||||||
vec3 C10 = texture(textureSrc, pixel + vec2( 0.0 ,-texelSize.y)).rgb;
|
vec3 C10 = texture(textureSrc, pixel + vec2( 0.0 ,-texelSize.y)).rgb;
|
||||||
|
@ -142,15 +109,17 @@ vec3 BicubicHermiteTexture(vec2 uv, vec4 texelSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
void main(){
|
void main(){
|
||||||
vec4 texelSize = vec4( 1.0 / outputResolution.xy, outputResolution.xy);
|
vec4 texelSize = vec4( 1.0 / textureSrcResolution.xy, textureSrcResolution.xy);
|
||||||
colorOut0 = vec4(BicubicHermiteTexture(passUV, texelSize), 1.0);
|
colorOut0 = vec4(BicubicHermiteTexture(passUV, texelSize), 1.0);
|
||||||
}
|
}
|
||||||
)";
|
)";
|
||||||
|
|
||||||
RendererOutputShader::RendererOutputShader(const std::string& vertex_source, const std::string& fragment_source)
|
RendererOutputShader::RendererOutputShader(const std::string& vertex_source, const std::string& fragment_source)
|
||||||
{
|
{
|
||||||
m_vertex_shader = g_renderer->shader_create(RendererShader::ShaderType::kVertex, 0, 0, vertex_source, false, false);
|
auto finalFragmentSrc = PrependFragmentPreamble(fragment_source);
|
||||||
m_fragment_shader = g_renderer->shader_create(RendererShader::ShaderType::kFragment, 0, 0, fragment_source, false, false);
|
|
||||||
|
m_vertex_shader.reset(g_renderer->shader_create(RendererShader::ShaderType::kVertex, 0, 0, vertex_source, false, false));
|
||||||
|
m_fragment_shader.reset(g_renderer->shader_create(RendererShader::ShaderType::kFragment, 0, 0, finalFragmentSrc, false, false));
|
||||||
|
|
||||||
m_vertex_shader->PreponeCompilation(true);
|
m_vertex_shader->PreponeCompilation(true);
|
||||||
m_fragment_shader->PreponeCompilation(true);
|
m_fragment_shader->PreponeCompilation(true);
|
||||||
|
@ -163,74 +132,45 @@ RendererOutputShader::RendererOutputShader(const std::string& vertex_source, con
|
||||||
|
|
||||||
if (g_renderer->GetType() == RendererAPI::OpenGL)
|
if (g_renderer->GetType() == RendererAPI::OpenGL)
|
||||||
{
|
{
|
||||||
m_attributes[0].m_loc_texture_src_resolution = m_vertex_shader->GetUniformLocation("textureSrcResolution");
|
m_uniformLocations[0].m_loc_textureSrcResolution = m_vertex_shader->GetUniformLocation("textureSrcResolution");
|
||||||
m_attributes[0].m_loc_input_resolution = m_vertex_shader->GetUniformLocation("inputResolution");
|
m_uniformLocations[0].m_loc_nativeResolution = m_vertex_shader->GetUniformLocation("nativeResolution");
|
||||||
m_attributes[0].m_loc_output_resolution = m_vertex_shader->GetUniformLocation("outputResolution");
|
m_uniformLocations[0].m_loc_outputResolution = m_vertex_shader->GetUniformLocation("outputResolution");
|
||||||
|
|
||||||
m_attributes[1].m_loc_texture_src_resolution = m_fragment_shader->GetUniformLocation("textureSrcResolution");
|
m_uniformLocations[1].m_loc_textureSrcResolution = m_fragment_shader->GetUniformLocation("textureSrcResolution");
|
||||||
m_attributes[1].m_loc_input_resolution = m_fragment_shader->GetUniformLocation("inputResolution");
|
m_uniformLocations[1].m_loc_nativeResolution = m_fragment_shader->GetUniformLocation("nativeResolution");
|
||||||
m_attributes[1].m_loc_output_resolution = m_fragment_shader->GetUniformLocation("outputResolution");
|
m_uniformLocations[1].m_loc_outputResolution = m_fragment_shader->GetUniformLocation("outputResolution");
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
cemuLog_logDebug(LogType::Force, "RendererOutputShader() - todo for Vulkan");
|
|
||||||
m_attributes[0].m_loc_texture_src_resolution = -1;
|
|
||||||
m_attributes[0].m_loc_input_resolution = -1;
|
|
||||||
m_attributes[0].m_loc_output_resolution = -1;
|
|
||||||
|
|
||||||
m_attributes[1].m_loc_texture_src_resolution = -1;
|
|
||||||
m_attributes[1].m_loc_input_resolution = -1;
|
|
||||||
m_attributes[1].m_loc_output_resolution = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void RendererOutputShader::SetUniformParameters(const LatteTextureView& texture_view, const Vector2i& input_res, const Vector2i& output_res) const
|
void RendererOutputShader::SetUniformParameters(const LatteTextureView& texture_view, const Vector2i& output_res) const
|
||||||
{
|
{
|
||||||
float res[2];
|
sint32 effectiveWidth, effectiveHeight;
|
||||||
// vertex shader
|
texture_view.baseTexture->GetEffectiveSize(effectiveWidth, effectiveHeight, 0);
|
||||||
if (m_attributes[0].m_loc_texture_src_resolution != -1)
|
auto setUniforms = [&](RendererShader* shader, const UniformLocations& locations){
|
||||||
{
|
float res[2];
|
||||||
res[0] = (float)texture_view.baseTexture->width;
|
if (locations.m_loc_textureSrcResolution != -1)
|
||||||
res[1] = (float)texture_view.baseTexture->height;
|
{
|
||||||
m_vertex_shader->SetUniform2fv(m_attributes[0].m_loc_texture_src_resolution, res, 1);
|
res[0] = (float)effectiveWidth;
|
||||||
}
|
res[1] = (float)effectiveHeight;
|
||||||
|
shader->SetUniform2fv(locations.m_loc_textureSrcResolution, res, 1);
|
||||||
|
}
|
||||||
|
|
||||||
if (m_attributes[0].m_loc_input_resolution != -1)
|
if (locations.m_loc_nativeResolution != -1)
|
||||||
{
|
{
|
||||||
res[0] = (float)input_res.x;
|
res[0] = (float)texture_view.baseTexture->width;
|
||||||
res[1] = (float)input_res.y;
|
res[1] = (float)texture_view.baseTexture->height;
|
||||||
m_vertex_shader->SetUniform2fv(m_attributes[0].m_loc_input_resolution, res, 1);
|
shader->SetUniform2fv(locations.m_loc_nativeResolution, res, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_attributes[0].m_loc_output_resolution != -1)
|
if (locations.m_loc_outputResolution != -1)
|
||||||
{
|
{
|
||||||
res[0] = (float)output_res.x;
|
res[0] = (float)output_res.x;
|
||||||
res[1] = (float)output_res.y;
|
res[1] = (float)output_res.y;
|
||||||
m_vertex_shader->SetUniform2fv(m_attributes[0].m_loc_output_resolution, res, 1);
|
shader->SetUniform2fv(locations.m_loc_outputResolution, res, 1);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
// fragment shader
|
setUniforms(m_vertex_shader.get(), m_uniformLocations[0]);
|
||||||
if (m_attributes[1].m_loc_texture_src_resolution != -1)
|
setUniforms(m_fragment_shader.get(), m_uniformLocations[1]);
|
||||||
{
|
|
||||||
res[0] = (float)texture_view.baseTexture->width;
|
|
||||||
res[1] = (float)texture_view.baseTexture->height;
|
|
||||||
m_fragment_shader->SetUniform2fv(m_attributes[1].m_loc_texture_src_resolution, res, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_attributes[1].m_loc_input_resolution != -1)
|
|
||||||
{
|
|
||||||
res[0] = (float)input_res.x;
|
|
||||||
res[1] = (float)input_res.y;
|
|
||||||
m_fragment_shader->SetUniform2fv(m_attributes[1].m_loc_input_resolution, res, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_attributes[1].m_loc_output_resolution != -1)
|
|
||||||
{
|
|
||||||
res[0] = (float)output_res.x;
|
|
||||||
res[1] = (float)output_res.y;
|
|
||||||
m_fragment_shader->SetUniform2fv(m_attributes[1].m_loc_output_resolution, res, 1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
RendererOutputShader* RendererOutputShader::s_copy_shader;
|
RendererOutputShader* RendererOutputShader::s_copy_shader;
|
||||||
|
@ -247,8 +187,8 @@ std::string RendererOutputShader::GetOpenGlVertexSource(bool render_upside_down)
|
||||||
// vertex shader
|
// vertex shader
|
||||||
std::ostringstream vertex_source;
|
std::ostringstream vertex_source;
|
||||||
vertex_source <<
|
vertex_source <<
|
||||||
R"(#version 400
|
R"(#version 420
|
||||||
out vec2 passUV;
|
layout(location = 0) smooth out vec2 passUV;
|
||||||
|
|
||||||
out gl_PerVertex
|
out gl_PerVertex
|
||||||
{
|
{
|
||||||
|
@ -341,6 +281,27 @@ void main(){
|
||||||
)";
|
)";
|
||||||
return vertex_source.str();
|
return vertex_source.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string RendererOutputShader::PrependFragmentPreamble(const std::string& shaderSrc)
|
||||||
|
{
|
||||||
|
return R"(#version 430
|
||||||
|
#ifdef VULKAN
|
||||||
|
layout(push_constant) uniform pc {
|
||||||
|
vec2 textureSrcResolution;
|
||||||
|
vec2 nativeResolution;
|
||||||
|
vec2 outputResolution;
|
||||||
|
};
|
||||||
|
#else
|
||||||
|
uniform vec2 textureSrcResolution;
|
||||||
|
uniform vec2 nativeResolution;
|
||||||
|
uniform vec2 outputResolution;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
layout(location = 0) smooth in vec2 passUV;
|
||||||
|
layout(binding = 0) uniform sampler2D textureSrc;
|
||||||
|
layout(location = 0) out vec4 colorOut0;
|
||||||
|
)" + shaderSrc;
|
||||||
|
}
|
||||||
void RendererOutputShader::InitializeStatic()
|
void RendererOutputShader::InitializeStatic()
|
||||||
{
|
{
|
||||||
std::string vertex_source, vertex_source_ud;
|
std::string vertex_source, vertex_source_ud;
|
||||||
|
@ -349,28 +310,30 @@ void RendererOutputShader::InitializeStatic()
|
||||||
{
|
{
|
||||||
vertex_source = GetOpenGlVertexSource(false);
|
vertex_source = GetOpenGlVertexSource(false);
|
||||||
vertex_source_ud = GetOpenGlVertexSource(true);
|
vertex_source_ud = GetOpenGlVertexSource(true);
|
||||||
|
|
||||||
s_copy_shader = new RendererOutputShader(vertex_source, s_copy_shader_source);
|
|
||||||
s_copy_shader_ud = new RendererOutputShader(vertex_source_ud, s_copy_shader_source);
|
|
||||||
|
|
||||||
s_bicubic_shader = new RendererOutputShader(vertex_source, s_bicubic_shader_source);
|
|
||||||
s_bicubic_shader_ud = new RendererOutputShader(vertex_source_ud, s_bicubic_shader_source);
|
|
||||||
|
|
||||||
s_hermit_shader = new RendererOutputShader(vertex_source, s_hermite_shader_source);
|
|
||||||
s_hermit_shader_ud = new RendererOutputShader(vertex_source_ud, s_hermite_shader_source);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
vertex_source = GetVulkanVertexSource(false);
|
vertex_source = GetVulkanVertexSource(false);
|
||||||
vertex_source_ud = GetVulkanVertexSource(true);
|
vertex_source_ud = GetVulkanVertexSource(true);
|
||||||
|
|
||||||
s_copy_shader = new RendererOutputShader(vertex_source, s_copy_shader_source);
|
|
||||||
s_copy_shader_ud = new RendererOutputShader(vertex_source_ud, s_copy_shader_source);
|
|
||||||
|
|
||||||
/* s_bicubic_shader = new RendererOutputShader(vertex_source, s_bicubic_shader_source); TODO
|
|
||||||
s_bicubic_shader_ud = new RendererOutputShader(vertex_source_ud, s_bicubic_shader_source);
|
|
||||||
|
|
||||||
s_hermit_shader = new RendererOutputShader(vertex_source, s_hermite_shader_source);
|
|
||||||
s_hermit_shader_ud = new RendererOutputShader(vertex_source_ud, s_hermite_shader_source);*/
|
|
||||||
}
|
}
|
||||||
|
s_copy_shader = new RendererOutputShader(vertex_source, s_copy_shader_source);
|
||||||
|
s_copy_shader_ud = new RendererOutputShader(vertex_source_ud, s_copy_shader_source);
|
||||||
|
|
||||||
|
s_bicubic_shader = new RendererOutputShader(vertex_source, s_bicubic_shader_source);
|
||||||
|
s_bicubic_shader_ud = new RendererOutputShader(vertex_source_ud, s_bicubic_shader_source);
|
||||||
|
|
||||||
|
s_hermit_shader = new RendererOutputShader(vertex_source, s_hermite_shader_source);
|
||||||
|
s_hermit_shader_ud = new RendererOutputShader(vertex_source_ud, s_hermite_shader_source);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RendererOutputShader::ShutdownStatic()
|
||||||
|
{
|
||||||
|
delete s_copy_shader;
|
||||||
|
delete s_copy_shader_ud;
|
||||||
|
|
||||||
|
delete s_bicubic_shader;
|
||||||
|
delete s_bicubic_shader_ud;
|
||||||
|
|
||||||
|
delete s_hermit_shader;
|
||||||
|
delete s_hermit_shader_ud;
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,19 +17,20 @@ public:
|
||||||
RendererOutputShader(const std::string& vertex_source, const std::string& fragment_source);
|
RendererOutputShader(const std::string& vertex_source, const std::string& fragment_source);
|
||||||
virtual ~RendererOutputShader() = default;
|
virtual ~RendererOutputShader() = default;
|
||||||
|
|
||||||
void SetUniformParameters(const LatteTextureView& texture_view, const Vector2i& input_res, const Vector2i& output_res) const;
|
void SetUniformParameters(const LatteTextureView& texture_view, const Vector2i& output_res) const;
|
||||||
|
|
||||||
RendererShader* GetVertexShader() const
|
RendererShader* GetVertexShader() const
|
||||||
{
|
{
|
||||||
return m_vertex_shader;
|
return m_vertex_shader.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
RendererShader* GetFragmentShader() const
|
RendererShader* GetFragmentShader() const
|
||||||
{
|
{
|
||||||
return m_fragment_shader;
|
return m_fragment_shader.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
static void InitializeStatic();
|
static void InitializeStatic();
|
||||||
|
static void ShutdownStatic();
|
||||||
|
|
||||||
static RendererOutputShader* s_copy_shader;
|
static RendererOutputShader* s_copy_shader;
|
||||||
static RendererOutputShader* s_copy_shader_ud;
|
static RendererOutputShader* s_copy_shader_ud;
|
||||||
|
@ -43,16 +44,18 @@ public:
|
||||||
static std::string GetVulkanVertexSource(bool render_upside_down);
|
static std::string GetVulkanVertexSource(bool render_upside_down);
|
||||||
static std::string GetOpenGlVertexSource(bool render_upside_down);
|
static std::string GetOpenGlVertexSource(bool render_upside_down);
|
||||||
|
|
||||||
protected:
|
static std::string PrependFragmentPreamble(const std::string& shaderSrc);
|
||||||
RendererShader* m_vertex_shader;
|
|
||||||
RendererShader* m_fragment_shader;
|
|
||||||
|
|
||||||
struct
|
protected:
|
||||||
|
std::unique_ptr<RendererShader> m_vertex_shader;
|
||||||
|
std::unique_ptr<RendererShader> m_fragment_shader;
|
||||||
|
|
||||||
|
struct UniformLocations
|
||||||
{
|
{
|
||||||
sint32 m_loc_texture_src_resolution = -1;
|
sint32 m_loc_textureSrcResolution = -1;
|
||||||
sint32 m_loc_input_resolution = -1;
|
sint32 m_loc_nativeResolution = -1;
|
||||||
sint32 m_loc_output_resolution = -1;
|
sint32 m_loc_outputResolution = -1;
|
||||||
} m_attributes[2]{};
|
} m_uniformLocations[2]{};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static const std::string s_copy_shader_source;
|
static const std::string s_copy_shader_source;
|
||||||
|
|
|
@ -202,6 +202,13 @@ VkSampler LatteTextureViewVk::GetDefaultTextureSampler(bool useLinearTexFilter)
|
||||||
VkSamplerCreateInfo samplerInfo{};
|
VkSamplerCreateInfo samplerInfo{};
|
||||||
samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
|
samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
|
||||||
|
|
||||||
|
// emulate OpenGL minFilters
|
||||||
|
// see note under: https://docs.vulkan.org/spec/latest/chapters/samplers.html#VkSamplerCreateInfo
|
||||||
|
// if maxLod = 0 then magnification is always performed
|
||||||
|
samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST;
|
||||||
|
samplerInfo.minLod = 0.0f;
|
||||||
|
samplerInfo.maxLod = 0.25f;
|
||||||
|
|
||||||
if (useLinearTexFilter)
|
if (useLinearTexFilter)
|
||||||
{
|
{
|
||||||
samplerInfo.magFilter = VK_FILTER_LINEAR;
|
samplerInfo.magFilter = VK_FILTER_LINEAR;
|
||||||
|
@ -212,6 +219,9 @@ VkSampler LatteTextureViewVk::GetDefaultTextureSampler(bool useLinearTexFilter)
|
||||||
samplerInfo.magFilter = VK_FILTER_NEAREST;
|
samplerInfo.magFilter = VK_FILTER_NEAREST;
|
||||||
samplerInfo.minFilter = VK_FILTER_NEAREST;
|
samplerInfo.minFilter = VK_FILTER_NEAREST;
|
||||||
}
|
}
|
||||||
|
samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
|
||||||
|
samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
|
||||||
|
samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
|
||||||
|
|
||||||
if (vkCreateSampler(m_device, &samplerInfo, nullptr, &sampler) != VK_SUCCESS)
|
if (vkCreateSampler(m_device, &samplerInfo, nullptr, &sampler) != VK_SUCCESS)
|
||||||
{
|
{
|
||||||
|
|
|
@ -211,6 +211,9 @@ RendererShaderVk::~RendererShaderVk()
|
||||||
{
|
{
|
||||||
while (!list_pipelineInfo.empty())
|
while (!list_pipelineInfo.empty())
|
||||||
delete list_pipelineInfo[0];
|
delete list_pipelineInfo[0];
|
||||||
|
|
||||||
|
VkDevice vkDev = VulkanRenderer::GetInstance()->GetLogicalDevice();
|
||||||
|
vkDestroyShaderModule(vkDev, m_shader_module, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
void RendererShaderVk::Init()
|
void RendererShaderVk::Init()
|
||||||
|
|
|
@ -60,7 +60,7 @@ void SwapchainInfoVk::Create()
|
||||||
VkAttachmentDescription colorAttachment = {};
|
VkAttachmentDescription colorAttachment = {};
|
||||||
colorAttachment.format = m_surfaceFormat.format;
|
colorAttachment.format = m_surfaceFormat.format;
|
||||||
colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
|
colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
|
||||||
colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
|
colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
||||||
colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
|
colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
|
||||||
colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
||||||
colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
||||||
|
|
|
@ -70,6 +70,7 @@ struct SwapchainInfoVk
|
||||||
VkSurfaceFormatKHR m_surfaceFormat{};
|
VkSurfaceFormatKHR m_surfaceFormat{};
|
||||||
VkSwapchainKHR m_swapchain{};
|
VkSwapchainKHR m_swapchain{};
|
||||||
Vector2i m_desiredExtent{};
|
Vector2i m_desiredExtent{};
|
||||||
|
VkExtent2D m_actualExtent{};
|
||||||
uint32 swapchainImageIndex = (uint32)-1;
|
uint32 swapchainImageIndex = (uint32)-1;
|
||||||
uint64 m_presentId = 1;
|
uint64 m_presentId = 1;
|
||||||
uint64 m_queueDepth = 0; // number of frames with pending presentation requests
|
uint64 m_queueDepth = 0; // number of frames with pending presentation requests
|
||||||
|
@ -92,5 +93,4 @@ private:
|
||||||
VkSemaphore m_currentSemaphore = VK_NULL_HANDLE;
|
VkSemaphore m_currentSemaphore = VK_NULL_HANDLE;
|
||||||
|
|
||||||
std::array<uint32, 2> m_swapchainQueueFamilyIndices;
|
std::array<uint32, 2> m_swapchainQueueFamilyIndices;
|
||||||
VkExtent2D m_actualExtent{};
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -22,7 +22,7 @@ uint32 LatteTextureReadbackInfoVk::GetImageSize(LatteTextureView* textureView)
|
||||||
cemu_assert(textureFormat == VK_FORMAT_R8G8B8A8_UNORM);
|
cemu_assert(textureFormat == VK_FORMAT_R8G8B8A8_UNORM);
|
||||||
return baseTexture->width * baseTexture->height * 4;
|
return baseTexture->width * baseTexture->height * 4;
|
||||||
}
|
}
|
||||||
else if (textureView->format == Latte::E_GX2SURFFMT::R8_UNORM)
|
else if (textureView->format == Latte::E_GX2SURFFMT::R8_UNORM )
|
||||||
{
|
{
|
||||||
cemu_assert(textureFormat == VK_FORMAT_R8_UNORM);
|
cemu_assert(textureFormat == VK_FORMAT_R8_UNORM);
|
||||||
return baseTexture->width * baseTexture->height * 1;
|
return baseTexture->width * baseTexture->height * 1;
|
||||||
|
@ -79,6 +79,13 @@ uint32 LatteTextureReadbackInfoVk::GetImageSize(LatteTextureView* textureView)
|
||||||
// todo - if driver does not support VK_FORMAT_D24_UNORM_S8_UINT this is represented as VK_FORMAT_D32_SFLOAT_S8_UINT which is 8 bytes
|
// todo - if driver does not support VK_FORMAT_D24_UNORM_S8_UINT this is represented as VK_FORMAT_D32_SFLOAT_S8_UINT which is 8 bytes
|
||||||
return baseTexture->width * baseTexture->height * 4;
|
return baseTexture->width * baseTexture->height * 4;
|
||||||
}
|
}
|
||||||
|
else if (textureView->format == Latte::E_GX2SURFFMT::R5_G6_B5_UNORM )
|
||||||
|
{
|
||||||
|
if(textureFormat == VK_FORMAT_R5G6B5_UNORM_PACK16){
|
||||||
|
return baseTexture->width * baseTexture->height * 2;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
cemuLog_log(LogType::Force, "Unsupported texture readback format {:04x}", (uint32)textureView->format);
|
cemuLog_log(LogType::Force, "Unsupported texture readback format {:04x}", (uint32)textureView->format);
|
||||||
|
|
|
@ -19,7 +19,7 @@ public:
|
||||||
|
|
||||||
virtual ~VKRMoveableRefCounter()
|
virtual ~VKRMoveableRefCounter()
|
||||||
{
|
{
|
||||||
cemu_assert_debug(refCount == 0);
|
cemu_assert_debug(m_refCount == 0);
|
||||||
|
|
||||||
// remove references
|
// remove references
|
||||||
#ifdef CEMU_DEBUG_ASSERT
|
#ifdef CEMU_DEBUG_ASSERT
|
||||||
|
@ -30,7 +30,11 @@ public:
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
for (auto itr : refs)
|
for (auto itr : refs)
|
||||||
itr->ref->refCount--;
|
{
|
||||||
|
itr->ref->m_refCount--;
|
||||||
|
if (itr->ref->m_refCount == 0)
|
||||||
|
itr->ref->RefCountReachedZero();
|
||||||
|
}
|
||||||
refs.clear();
|
refs.clear();
|
||||||
delete selfRef;
|
delete selfRef;
|
||||||
selfRef = nullptr;
|
selfRef = nullptr;
|
||||||
|
@ -41,8 +45,8 @@ public:
|
||||||
VKRMoveableRefCounter(VKRMoveableRefCounter&& rhs) noexcept
|
VKRMoveableRefCounter(VKRMoveableRefCounter&& rhs) noexcept
|
||||||
{
|
{
|
||||||
this->refs = std::move(rhs.refs);
|
this->refs = std::move(rhs.refs);
|
||||||
this->refCount = rhs.refCount;
|
this->m_refCount = rhs.m_refCount;
|
||||||
rhs.refCount = 0;
|
rhs.m_refCount = 0;
|
||||||
this->selfRef = rhs.selfRef;
|
this->selfRef = rhs.selfRef;
|
||||||
rhs.selfRef = nullptr;
|
rhs.selfRef = nullptr;
|
||||||
this->selfRef->ref = this;
|
this->selfRef->ref = this;
|
||||||
|
@ -57,7 +61,7 @@ public:
|
||||||
void addRef(VKRMoveableRefCounter* refTarget)
|
void addRef(VKRMoveableRefCounter* refTarget)
|
||||||
{
|
{
|
||||||
this->refs.emplace_back(refTarget->selfRef);
|
this->refs.emplace_back(refTarget->selfRef);
|
||||||
refTarget->refCount++;
|
refTarget->m_refCount++;
|
||||||
|
|
||||||
#ifdef CEMU_DEBUG_ASSERT
|
#ifdef CEMU_DEBUG_ASSERT
|
||||||
// add reverse ref
|
// add reverse ref
|
||||||
|
@ -68,16 +72,23 @@ public:
|
||||||
// methods to directly increment/decrement ref counter (for situations where no external object is available)
|
// methods to directly increment/decrement ref counter (for situations where no external object is available)
|
||||||
void incRef()
|
void incRef()
|
||||||
{
|
{
|
||||||
this->refCount++;
|
m_refCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
void decRef()
|
void decRef()
|
||||||
{
|
{
|
||||||
this->refCount--;
|
m_refCount--;
|
||||||
|
if (m_refCount == 0)
|
||||||
|
RefCountReachedZero();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
int refCount{};
|
virtual void RefCountReachedZero()
|
||||||
|
{
|
||||||
|
// does nothing by default
|
||||||
|
}
|
||||||
|
|
||||||
|
int m_refCount{};
|
||||||
private:
|
private:
|
||||||
VKRMoveableRefCounterRef* selfRef;
|
VKRMoveableRefCounterRef* selfRef;
|
||||||
std::vector<VKRMoveableRefCounterRef*> refs;
|
std::vector<VKRMoveableRefCounterRef*> refs;
|
||||||
|
@ -88,7 +99,7 @@ private:
|
||||||
void moveObj(VKRMoveableRefCounter&& rhs)
|
void moveObj(VKRMoveableRefCounter&& rhs)
|
||||||
{
|
{
|
||||||
this->refs = std::move(rhs.refs);
|
this->refs = std::move(rhs.refs);
|
||||||
this->refCount = rhs.refCount;
|
this->m_refCount = rhs.m_refCount;
|
||||||
this->selfRef = rhs.selfRef;
|
this->selfRef = rhs.selfRef;
|
||||||
this->selfRef->ref = this;
|
this->selfRef->ref = this;
|
||||||
}
|
}
|
||||||
|
@ -131,6 +142,25 @@ public:
|
||||||
VkSampler m_textureDefaultSampler[2] = { VK_NULL_HANDLE, VK_NULL_HANDLE }; // relict from LatteTextureViewVk, get rid of it eventually
|
VkSampler m_textureDefaultSampler[2] = { VK_NULL_HANDLE, VK_NULL_HANDLE }; // relict from LatteTextureViewVk, get rid of it eventually
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class VKRObjectSampler : public VKRDestructibleObject
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
VKRObjectSampler(VkSamplerCreateInfo* samplerInfo);
|
||||||
|
~VKRObjectSampler() override;
|
||||||
|
|
||||||
|
static VKRObjectSampler* GetOrCreateSampler(VkSamplerCreateInfo* samplerInfo);
|
||||||
|
static void DestroyCache();
|
||||||
|
|
||||||
|
void RefCountReachedZero() override; // sampler objects are destroyed when not referenced anymore
|
||||||
|
|
||||||
|
VkSampler GetSampler() const { return m_sampler; }
|
||||||
|
private:
|
||||||
|
static std::unordered_map<uint64, VKRObjectSampler*> s_samplerCache;
|
||||||
|
VkSampler m_sampler{ VK_NULL_HANDLE };
|
||||||
|
uint64 m_hash;
|
||||||
|
};
|
||||||
|
|
||||||
class VKRObjectRenderPass : public VKRDestructibleObject
|
class VKRObjectRenderPass : public VKRDestructibleObject
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
|
@ -4,6 +4,14 @@
|
||||||
|
|
||||||
/* VKRSynchronizedMemoryBuffer */
|
/* VKRSynchronizedMemoryBuffer */
|
||||||
|
|
||||||
|
VKRSynchronizedRingAllocator::~VKRSynchronizedRingAllocator()
|
||||||
|
{
|
||||||
|
for(auto& buf : m_buffers)
|
||||||
|
{
|
||||||
|
m_vkrMemMgr->DeleteBuffer(buf.vk_buffer, buf.vk_mem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void VKRSynchronizedRingAllocator::addUploadBufferSyncPoint(AllocatorBuffer_t& buffer, uint32 offset)
|
void VKRSynchronizedRingAllocator::addUploadBufferSyncPoint(AllocatorBuffer_t& buffer, uint32 offset)
|
||||||
{
|
{
|
||||||
auto cmdBufferId = m_vkr->GetCurrentCommandBufferId();
|
auto cmdBufferId = m_vkr->GetCurrentCommandBufferId();
|
||||||
|
@ -23,11 +31,11 @@ void VKRSynchronizedRingAllocator::allocateAdditionalUploadBuffer(uint32 sizeReq
|
||||||
AllocatorBuffer_t newBuffer{};
|
AllocatorBuffer_t newBuffer{};
|
||||||
newBuffer.writeIndex = 0;
|
newBuffer.writeIndex = 0;
|
||||||
newBuffer.basePtr = nullptr;
|
newBuffer.basePtr = nullptr;
|
||||||
if (m_bufferType == BUFFER_TYPE::STAGING)
|
if (m_bufferType == VKR_BUFFER_TYPE::STAGING)
|
||||||
m_vkrMemMgr->CreateBuffer(bufferAllocSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, newBuffer.vk_buffer, newBuffer.vk_mem);
|
m_vkrMemMgr->CreateBuffer(bufferAllocSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, newBuffer.vk_buffer, newBuffer.vk_mem);
|
||||||
else if (m_bufferType == BUFFER_TYPE::INDEX)
|
else if (m_bufferType == VKR_BUFFER_TYPE::INDEX)
|
||||||
m_vkrMemMgr->CreateBuffer(bufferAllocSize, VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, newBuffer.vk_buffer, newBuffer.vk_mem);
|
m_vkrMemMgr->CreateBuffer(bufferAllocSize, VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, newBuffer.vk_buffer, newBuffer.vk_mem);
|
||||||
else if (m_bufferType == BUFFER_TYPE::STRIDE)
|
else if (m_bufferType == VKR_BUFFER_TYPE::STRIDE)
|
||||||
m_vkrMemMgr->CreateBuffer(bufferAllocSize, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, newBuffer.vk_buffer, newBuffer.vk_mem);
|
m_vkrMemMgr->CreateBuffer(bufferAllocSize, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, newBuffer.vk_buffer, newBuffer.vk_mem);
|
||||||
else
|
else
|
||||||
cemu_assert_debug(false);
|
cemu_assert_debug(false);
|
||||||
|
@ -53,7 +61,7 @@ VKRSynchronizedRingAllocator::AllocatorReservation_t VKRSynchronizedRingAllocato
|
||||||
uint32 distanceToSyncPoint;
|
uint32 distanceToSyncPoint;
|
||||||
if (!itr.queue_syncPoints.empty())
|
if (!itr.queue_syncPoints.empty())
|
||||||
{
|
{
|
||||||
if(itr.queue_syncPoints.front().offset < itr.writeIndex)
|
if (itr.queue_syncPoints.front().offset < itr.writeIndex)
|
||||||
distanceToSyncPoint = 0xFFFFFFFF;
|
distanceToSyncPoint = 0xFFFFFFFF;
|
||||||
else
|
else
|
||||||
distanceToSyncPoint = itr.queue_syncPoints.front().offset - itr.writeIndex;
|
distanceToSyncPoint = itr.queue_syncPoints.front().offset - itr.writeIndex;
|
||||||
|
@ -100,7 +108,7 @@ VKRSynchronizedRingAllocator::AllocatorReservation_t VKRSynchronizedRingAllocato
|
||||||
|
|
||||||
void VKRSynchronizedRingAllocator::FlushReservation(AllocatorReservation_t& uploadReservation)
|
void VKRSynchronizedRingAllocator::FlushReservation(AllocatorReservation_t& uploadReservation)
|
||||||
{
|
{
|
||||||
cemu_assert_debug(m_bufferType == BUFFER_TYPE::STAGING); // only the staging buffer isn't coherent
|
cemu_assert_debug(m_bufferType == VKR_BUFFER_TYPE::STAGING); // only the staging buffer isn't coherent
|
||||||
// todo - use nonCoherentAtomSize for flush size (instead of hardcoded constant)
|
// todo - use nonCoherentAtomSize for flush size (instead of hardcoded constant)
|
||||||
VkMappedMemoryRange flushedRange{};
|
VkMappedMemoryRange flushedRange{};
|
||||||
flushedRange.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE;
|
flushedRange.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE;
|
||||||
|
@ -167,15 +175,88 @@ void VKRSynchronizedRingAllocator::GetStats(uint32& numBuffers, size_t& totalBuf
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* VKRSynchronizedHeapAllocator */
|
||||||
|
|
||||||
|
VKRSynchronizedHeapAllocator::VKRSynchronizedHeapAllocator(class VKRMemoryManager* vkMemoryManager, VKR_BUFFER_TYPE bufferType, size_t minimumBufferAllocSize)
|
||||||
|
: m_vkrMemMgr(vkMemoryManager), m_chunkedHeap(bufferType, minimumBufferAllocSize) {};
|
||||||
|
|
||||||
|
VKRSynchronizedHeapAllocator::AllocatorReservation* VKRSynchronizedHeapAllocator::AllocateBufferMemory(uint32 size, uint32 alignment)
|
||||||
|
{
|
||||||
|
CHAddr addr = m_chunkedHeap.alloc(size, alignment);
|
||||||
|
m_activeAllocations.emplace_back(addr);
|
||||||
|
AllocatorReservation* res = m_poolAllocatorReservation.allocObj();
|
||||||
|
res->bufferIndex = addr.chunkIndex;
|
||||||
|
res->bufferOffset = addr.offset;
|
||||||
|
res->size = size;
|
||||||
|
res->memPtr = m_chunkedHeap.GetChunkPtr(addr.chunkIndex) + addr.offset;
|
||||||
|
m_chunkedHeap.GetChunkVkMemInfo(addr.chunkIndex, res->vkBuffer, res->vkMem);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VKRSynchronizedHeapAllocator::FreeReservation(AllocatorReservation* uploadReservation)
|
||||||
|
{
|
||||||
|
// put the allocation on a delayed release queue for the current command buffer
|
||||||
|
uint64 currentCommandBufferId = VulkanRenderer::GetInstance()->GetCurrentCommandBufferId();
|
||||||
|
auto it = std::find_if(m_activeAllocations.begin(), m_activeAllocations.end(), [&uploadReservation](const TrackedAllocation& allocation) { return allocation.allocation.chunkIndex == uploadReservation->bufferIndex && allocation.allocation.offset == uploadReservation->bufferOffset; });
|
||||||
|
cemu_assert_debug(it != m_activeAllocations.end());
|
||||||
|
m_releaseQueue[currentCommandBufferId].emplace_back(it->allocation);
|
||||||
|
m_activeAllocations.erase(it);
|
||||||
|
m_poolAllocatorReservation.freeObj(uploadReservation);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VKRSynchronizedHeapAllocator::FlushReservation(AllocatorReservation* uploadReservation)
|
||||||
|
{
|
||||||
|
if (m_chunkedHeap.RequiresFlush(uploadReservation->bufferIndex))
|
||||||
|
{
|
||||||
|
VkMappedMemoryRange flushedRange{};
|
||||||
|
flushedRange.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE;
|
||||||
|
flushedRange.memory = uploadReservation->vkMem;
|
||||||
|
flushedRange.offset = uploadReservation->bufferOffset;
|
||||||
|
flushedRange.size = uploadReservation->size;
|
||||||
|
vkFlushMappedMemoryRanges(VulkanRenderer::GetInstance()->GetLogicalDevice(), 1, &flushedRange);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VKRSynchronizedHeapAllocator::CleanupBuffer(uint64 latestFinishedCommandBufferId)
|
||||||
|
{
|
||||||
|
auto it = m_releaseQueue.begin();
|
||||||
|
while (it != m_releaseQueue.end())
|
||||||
|
{
|
||||||
|
if (it->first <= latestFinishedCommandBufferId)
|
||||||
|
{
|
||||||
|
// release allocations
|
||||||
|
for(auto& addr : it->second)
|
||||||
|
m_chunkedHeap.free(addr);
|
||||||
|
it = m_releaseQueue.erase(it);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
it++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VKRSynchronizedHeapAllocator::GetStats(uint32& numBuffers, size_t& totalBufferSize, size_t& freeBufferSize) const
|
||||||
|
{
|
||||||
|
m_chunkedHeap.GetStats(numBuffers, totalBufferSize, freeBufferSize);
|
||||||
|
}
|
||||||
|
|
||||||
/* VkTextureChunkedHeap */
|
/* VkTextureChunkedHeap */
|
||||||
|
|
||||||
|
VkTextureChunkedHeap::~VkTextureChunkedHeap()
|
||||||
|
{
|
||||||
|
VkDevice device = VulkanRenderer::GetInstance()->GetLogicalDevice();
|
||||||
|
for (auto& i : m_list_chunkInfo)
|
||||||
|
{
|
||||||
|
vkFreeMemory(device, i.mem, nullptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
uint32 VkTextureChunkedHeap::allocateNewChunk(uint32 chunkIndex, uint32 minimumAllocationSize)
|
uint32 VkTextureChunkedHeap::allocateNewChunk(uint32 chunkIndex, uint32 minimumAllocationSize)
|
||||||
{
|
{
|
||||||
cemu_assert_debug(m_list_chunkInfo.size() == chunkIndex);
|
cemu_assert_debug(m_list_chunkInfo.size() == chunkIndex);
|
||||||
m_list_chunkInfo.resize(m_list_chunkInfo.size() + 1);
|
m_list_chunkInfo.resize(m_list_chunkInfo.size() + 1);
|
||||||
|
|
||||||
// pad minimumAllocationSize to 32KB alignment
|
// pad minimumAllocationSize to 32KB alignment
|
||||||
minimumAllocationSize = (minimumAllocationSize + (32*1024-1)) & ~(32 * 1024 - 1);
|
minimumAllocationSize = (minimumAllocationSize + (32 * 1024 - 1)) & ~(32 * 1024 - 1);
|
||||||
|
|
||||||
uint32 allocationSize = 1024 * 1024 * 128;
|
uint32 allocationSize = 1024 * 1024 * 128;
|
||||||
if (chunkIndex == 0)
|
if (chunkIndex == 0)
|
||||||
|
@ -189,8 +270,7 @@ uint32 VkTextureChunkedHeap::allocateNewChunk(uint32 chunkIndex, uint32 minimumA
|
||||||
std::vector<uint32> deviceLocalMemoryTypeIndices = m_vkrMemoryManager->FindMemoryTypes(m_typeFilter, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
|
std::vector<uint32> deviceLocalMemoryTypeIndices = m_vkrMemoryManager->FindMemoryTypes(m_typeFilter, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
|
||||||
std::vector<uint32> hostLocalMemoryTypeIndices = m_vkrMemoryManager->FindMemoryTypes(m_typeFilter, 0);
|
std::vector<uint32> hostLocalMemoryTypeIndices = m_vkrMemoryManager->FindMemoryTypes(m_typeFilter, 0);
|
||||||
// remove device local memory types from host local vector
|
// remove device local memory types from host local vector
|
||||||
auto pred = [&deviceLocalMemoryTypeIndices](const uint32& v) ->bool
|
auto pred = [&deviceLocalMemoryTypeIndices](const uint32& v) -> bool {
|
||||||
{
|
|
||||||
return std::find(deviceLocalMemoryTypeIndices.begin(), deviceLocalMemoryTypeIndices.end(), v) != deviceLocalMemoryTypeIndices.end();
|
return std::find(deviceLocalMemoryTypeIndices.begin(), deviceLocalMemoryTypeIndices.end(), v) != deviceLocalMemoryTypeIndices.end();
|
||||||
};
|
};
|
||||||
hostLocalMemoryTypeIndices.erase(std::remove_if(hostLocalMemoryTypeIndices.begin(), hostLocalMemoryTypeIndices.end(), pred), hostLocalMemoryTypeIndices.end());
|
hostLocalMemoryTypeIndices.erase(std::remove_if(hostLocalMemoryTypeIndices.begin(), hostLocalMemoryTypeIndices.end(), pred), hostLocalMemoryTypeIndices.end());
|
||||||
|
@ -206,7 +286,7 @@ uint32 VkTextureChunkedHeap::allocateNewChunk(uint32 chunkIndex, uint32 minimumA
|
||||||
allocInfo.memoryTypeIndex = memType;
|
allocInfo.memoryTypeIndex = memType;
|
||||||
|
|
||||||
VkDeviceMemory imageMemory;
|
VkDeviceMemory imageMemory;
|
||||||
VkResult r = vkAllocateMemory(m_device, &allocInfo, nullptr, &imageMemory);
|
VkResult r = vkAllocateMemory(VulkanRenderer::GetInstance()->GetLogicalDevice(), &allocInfo, nullptr, &imageMemory);
|
||||||
if (r != VK_SUCCESS)
|
if (r != VK_SUCCESS)
|
||||||
continue;
|
continue;
|
||||||
m_list_chunkInfo[chunkIndex].mem = imageMemory;
|
m_list_chunkInfo[chunkIndex].mem = imageMemory;
|
||||||
|
@ -221,7 +301,7 @@ uint32 VkTextureChunkedHeap::allocateNewChunk(uint32 chunkIndex, uint32 minimumA
|
||||||
allocInfo.memoryTypeIndex = memType;
|
allocInfo.memoryTypeIndex = memType;
|
||||||
|
|
||||||
VkDeviceMemory imageMemory;
|
VkDeviceMemory imageMemory;
|
||||||
VkResult r = vkAllocateMemory(m_device, &allocInfo, nullptr, &imageMemory);
|
VkResult r = vkAllocateMemory(VulkanRenderer::GetInstance()->GetLogicalDevice(), &allocInfo, nullptr, &imageMemory);
|
||||||
if (r != VK_SUCCESS)
|
if (r != VK_SUCCESS)
|
||||||
continue;
|
continue;
|
||||||
m_list_chunkInfo[chunkIndex].mem = imageMemory;
|
m_list_chunkInfo[chunkIndex].mem = imageMemory;
|
||||||
|
@ -238,28 +318,76 @@ uint32 VkTextureChunkedHeap::allocateNewChunk(uint32 chunkIndex, uint32 minimumA
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t VKRMemoryManager::FindMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) const
|
/* VkBufferChunkedHeap */
|
||||||
{
|
|
||||||
VkPhysicalDeviceMemoryProperties memProperties;
|
|
||||||
vkGetPhysicalDeviceMemoryProperties(m_vkr->GetPhysicalDevice(), &memProperties);
|
|
||||||
|
|
||||||
for (uint32 i = 0; i < memProperties.memoryTypeCount; i++)
|
VKRBuffer* VKRBuffer::Create(VKR_BUFFER_TYPE bufferType, size_t bufferSize, VkMemoryPropertyFlags properties)
|
||||||
|
{
|
||||||
|
auto* memMgr = VulkanRenderer::GetInstance()->GetMemoryManager();
|
||||||
|
VkBuffer buffer;
|
||||||
|
VkDeviceMemory bufferMemory;
|
||||||
|
bool allocSuccess;
|
||||||
|
if (bufferType == VKR_BUFFER_TYPE::STAGING)
|
||||||
|
allocSuccess = memMgr->CreateBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, properties, buffer, bufferMemory);
|
||||||
|
else if (bufferType == VKR_BUFFER_TYPE::INDEX)
|
||||||
|
allocSuccess = memMgr->CreateBuffer(bufferSize, VK_BUFFER_USAGE_INDEX_BUFFER_BIT, properties, buffer, bufferMemory);
|
||||||
|
else if (bufferType == VKR_BUFFER_TYPE::STRIDE)
|
||||||
|
allocSuccess = memMgr->CreateBuffer(bufferSize, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, properties, buffer, bufferMemory);
|
||||||
|
else
|
||||||
|
cemu_assert_debug(false);
|
||||||
|
if (!allocSuccess)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
VKRBuffer* bufferObj = new VKRBuffer(buffer, bufferMemory);
|
||||||
|
// if host visible, then map buffer
|
||||||
|
void* data = nullptr;
|
||||||
|
if (properties & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT)
|
||||||
{
|
{
|
||||||
if ((typeFilter & (1 << i)) != 0 && (memProperties.memoryTypes[i].propertyFlags & properties) == properties)
|
vkMapMemory(VulkanRenderer::GetInstance()->GetLogicalDevice(), bufferMemory, 0, bufferSize, 0, &data);
|
||||||
return i;
|
bufferObj->m_requiresFlush = !HAS_FLAG(properties, VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
|
||||||
}
|
}
|
||||||
m_vkr->UnrecoverableError(fmt::format("failed to find suitable memory type ({0:#08x} {1:#08x})", typeFilter, properties).c_str());
|
bufferObj->m_mappedMemory = (uint8*)data;
|
||||||
return 0;
|
return bufferObj;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool VKRMemoryManager::FindMemoryType2(uint32 typeFilter, VkMemoryPropertyFlags properties, uint32& memoryIndex) const
|
VKRBuffer::~VKRBuffer()
|
||||||
|
{
|
||||||
|
if (m_mappedMemory)
|
||||||
|
vkUnmapMemory(VulkanRenderer::GetInstance()->GetLogicalDevice(), m_bufferMemory);
|
||||||
|
if (m_bufferMemory != VK_NULL_HANDLE)
|
||||||
|
vkFreeMemory(VulkanRenderer::GetInstance()->GetLogicalDevice(), m_bufferMemory, nullptr);
|
||||||
|
if (m_buffer != VK_NULL_HANDLE)
|
||||||
|
vkDestroyBuffer(VulkanRenderer::GetInstance()->GetLogicalDevice(), m_buffer, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
VkBufferChunkedHeap::~VkBufferChunkedHeap()
|
||||||
|
{
|
||||||
|
for (auto& chunk : m_chunkBuffers)
|
||||||
|
delete chunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32 VkBufferChunkedHeap::allocateNewChunk(uint32 chunkIndex, uint32 minimumAllocationSize)
|
||||||
|
{
|
||||||
|
size_t allocationSize = std::max<size_t>(m_minimumBufferAllocationSize, minimumAllocationSize);
|
||||||
|
VKRBuffer* buffer = VKRBuffer::Create(m_bufferType, allocationSize, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
|
||||||
|
if(!buffer)
|
||||||
|
buffer = VKRBuffer::Create(m_bufferType, allocationSize, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT);
|
||||||
|
if(!buffer)
|
||||||
|
VulkanRenderer::GetInstance()->UnrecoverableError("Failed to allocate buffer memory for VkBufferChunkedHeap");
|
||||||
|
cemu_assert_debug(buffer);
|
||||||
|
cemu_assert_debug(m_chunkBuffers.size() == chunkIndex);
|
||||||
|
m_chunkBuffers.emplace_back(buffer);
|
||||||
|
// todo - VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT might be worth it?
|
||||||
|
return allocationSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VKRMemoryManager::FindMemoryType(uint32 typeFilter, VkMemoryPropertyFlags properties, uint32& memoryIndex) const
|
||||||
{
|
{
|
||||||
VkPhysicalDeviceMemoryProperties memProperties;
|
VkPhysicalDeviceMemoryProperties memProperties;
|
||||||
vkGetPhysicalDeviceMemoryProperties(m_vkr->GetPhysicalDevice(), &memProperties);
|
vkGetPhysicalDeviceMemoryProperties(m_vkr->GetPhysicalDevice(), &memProperties);
|
||||||
|
|
||||||
for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++)
|
for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++)
|
||||||
{
|
{
|
||||||
if (typeFilter & (1 << i) && memProperties.memoryTypes[i].propertyFlags == properties)
|
if (typeFilter & (1 << i) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties)
|
||||||
{
|
{
|
||||||
memoryIndex = i;
|
memoryIndex = i;
|
||||||
return true;
|
return true;
|
||||||
|
@ -330,31 +458,7 @@ size_t VKRMemoryManager::GetTotalMemoryForBufferType(VkBufferUsageFlags usage, V
|
||||||
return total;
|
return total;
|
||||||
}
|
}
|
||||||
|
|
||||||
void VKRMemoryManager::CreateBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory) const
|
bool VKRMemoryManager::CreateBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory) const
|
||||||
{
|
|
||||||
VkBufferCreateInfo bufferInfo{};
|
|
||||||
bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
|
|
||||||
bufferInfo.usage = usage;
|
|
||||||
bufferInfo.size = size;
|
|
||||||
bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
|
||||||
if (vkCreateBuffer(m_vkr->GetLogicalDevice(), &bufferInfo, nullptr, &buffer) != VK_SUCCESS)
|
|
||||||
m_vkr->UnrecoverableError("Failed to create buffer");
|
|
||||||
|
|
||||||
VkMemoryRequirements memRequirements;
|
|
||||||
vkGetBufferMemoryRequirements(m_vkr->GetLogicalDevice(), buffer, &memRequirements);
|
|
||||||
|
|
||||||
VkMemoryAllocateInfo allocInfo{};
|
|
||||||
allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
|
|
||||||
allocInfo.allocationSize = memRequirements.size;
|
|
||||||
allocInfo.memoryTypeIndex = FindMemoryType(memRequirements.memoryTypeBits, properties);
|
|
||||||
|
|
||||||
if (vkAllocateMemory(m_vkr->GetLogicalDevice(), &allocInfo, nullptr, &bufferMemory) != VK_SUCCESS)
|
|
||||||
m_vkr->UnrecoverableError("Failed to allocate buffer memory");
|
|
||||||
if (vkBindBufferMemory(m_vkr->GetLogicalDevice(), buffer, bufferMemory, 0) != VK_SUCCESS)
|
|
||||||
m_vkr->UnrecoverableError("Failed to bind buffer memory");
|
|
||||||
}
|
|
||||||
|
|
||||||
bool VKRMemoryManager::CreateBuffer2(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory) const
|
|
||||||
{
|
{
|
||||||
VkBufferCreateInfo bufferInfo{};
|
VkBufferCreateInfo bufferInfo{};
|
||||||
bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
|
bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
|
||||||
|
@ -363,7 +467,7 @@ bool VKRMemoryManager::CreateBuffer2(VkDeviceSize size, VkBufferUsageFlags usage
|
||||||
bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||||
if (vkCreateBuffer(m_vkr->GetLogicalDevice(), &bufferInfo, nullptr, &buffer) != VK_SUCCESS)
|
if (vkCreateBuffer(m_vkr->GetLogicalDevice(), &bufferInfo, nullptr, &buffer) != VK_SUCCESS)
|
||||||
{
|
{
|
||||||
cemuLog_log(LogType::Force, "Failed to create buffer (CreateBuffer2)");
|
cemuLog_log(LogType::Force, "Failed to create buffer (CreateBuffer)");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -373,7 +477,7 @@ bool VKRMemoryManager::CreateBuffer2(VkDeviceSize size, VkBufferUsageFlags usage
|
||||||
VkMemoryAllocateInfo allocInfo{};
|
VkMemoryAllocateInfo allocInfo{};
|
||||||
allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
|
allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
|
||||||
allocInfo.allocationSize = memRequirements.size;
|
allocInfo.allocationSize = memRequirements.size;
|
||||||
if (!FindMemoryType2(memRequirements.memoryTypeBits, properties, allocInfo.memoryTypeIndex))
|
if (!FindMemoryType(memRequirements.memoryTypeBits, properties, allocInfo.memoryTypeIndex))
|
||||||
{
|
{
|
||||||
vkDestroyBuffer(m_vkr->GetLogicalDevice(), buffer, nullptr);
|
vkDestroyBuffer(m_vkr->GetLogicalDevice(), buffer, nullptr);
|
||||||
return false;
|
return false;
|
||||||
|
@ -386,7 +490,7 @@ bool VKRMemoryManager::CreateBuffer2(VkDeviceSize size, VkBufferUsageFlags usage
|
||||||
if (vkBindBufferMemory(m_vkr->GetLogicalDevice(), buffer, bufferMemory, 0) != VK_SUCCESS)
|
if (vkBindBufferMemory(m_vkr->GetLogicalDevice(), buffer, bufferMemory, 0) != VK_SUCCESS)
|
||||||
{
|
{
|
||||||
vkDestroyBuffer(m_vkr->GetLogicalDevice(), buffer, nullptr);
|
vkDestroyBuffer(m_vkr->GetLogicalDevice(), buffer, nullptr);
|
||||||
cemuLog_log(LogType::Force, "Failed to bind buffer (CreateBuffer2)");
|
cemuLog_log(LogType::Force, "Failed to bind buffer (CreateBuffer)");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
@ -408,7 +512,7 @@ bool VKRMemoryManager::CreateBufferFromHostMemory(void* hostPointer, VkDeviceSiz
|
||||||
|
|
||||||
if (vkCreateBuffer(m_vkr->GetLogicalDevice(), &bufferInfo, nullptr, &buffer) != VK_SUCCESS)
|
if (vkCreateBuffer(m_vkr->GetLogicalDevice(), &bufferInfo, nullptr, &buffer) != VK_SUCCESS)
|
||||||
{
|
{
|
||||||
cemuLog_log(LogType::Force, "Failed to create buffer (CreateBuffer2)");
|
cemuLog_log(LogType::Force, "Failed to create buffer (CreateBuffer)");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -423,13 +527,13 @@ bool VKRMemoryManager::CreateBufferFromHostMemory(void* hostPointer, VkDeviceSiz
|
||||||
importHostMem.sType = VK_STRUCTURE_TYPE_IMPORT_MEMORY_HOST_POINTER_INFO_EXT;
|
importHostMem.sType = VK_STRUCTURE_TYPE_IMPORT_MEMORY_HOST_POINTER_INFO_EXT;
|
||||||
importHostMem.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_HOST_ALLOCATION_BIT_EXT;
|
importHostMem.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_HOST_ALLOCATION_BIT_EXT;
|
||||||
importHostMem.pHostPointer = hostPointer;
|
importHostMem.pHostPointer = hostPointer;
|
||||||
// VK_EXTERNAL_MEMORY_HANDLE_TYPE_HOST_ALLOCATION_BIT_EXT or
|
// VK_EXTERNAL_MEMORY_HANDLE_TYPE_HOST_ALLOCATION_BIT_EXT or
|
||||||
// VK_EXTERNAL_MEMORY_HANDLE_TYPE_HOST_MAPPED_FOREIGN_MEMORY_BIT_EXT
|
// VK_EXTERNAL_MEMORY_HANDLE_TYPE_HOST_MAPPED_FOREIGN_MEMORY_BIT_EXT
|
||||||
// whats the difference ?
|
// whats the difference ?
|
||||||
|
|
||||||
allocInfo.pNext = &importHostMem;
|
allocInfo.pNext = &importHostMem;
|
||||||
|
|
||||||
if (!FindMemoryType2(memRequirements.memoryTypeBits, properties, allocInfo.memoryTypeIndex))
|
if (!FindMemoryType(memRequirements.memoryTypeBits, properties, allocInfo.memoryTypeIndex))
|
||||||
{
|
{
|
||||||
vkDestroyBuffer(m_vkr->GetLogicalDevice(), buffer, nullptr);
|
vkDestroyBuffer(m_vkr->GetLogicalDevice(), buffer, nullptr);
|
||||||
return false;
|
return false;
|
||||||
|
@ -469,11 +573,11 @@ VkImageMemAllocation* VKRMemoryManager::imageMemoryAllocate(VkImage image)
|
||||||
auto it = map_textureHeap.find(typeFilter);
|
auto it = map_textureHeap.find(typeFilter);
|
||||||
if (it == map_textureHeap.end())
|
if (it == map_textureHeap.end())
|
||||||
{
|
{
|
||||||
texHeap = new VkTextureChunkedHeap(this, typeFilter, m_vkr->GetLogicalDevice());
|
texHeap = new VkTextureChunkedHeap(this, typeFilter);
|
||||||
map_textureHeap.emplace(typeFilter, texHeap);
|
map_textureHeap.emplace(typeFilter, texHeap);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
texHeap = it->second;
|
texHeap = it->second.get();
|
||||||
|
|
||||||
// alloc mem from heap
|
// alloc mem from heap
|
||||||
uint32 allocationSize = (uint32)memRequirements.size;
|
uint32 allocationSize = (uint32)memRequirements.size;
|
||||||
|
|
|
@ -2,6 +2,36 @@
|
||||||
#include "Cafe/HW/Latte/Renderer/Renderer.h"
|
#include "Cafe/HW/Latte/Renderer/Renderer.h"
|
||||||
#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h"
|
#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h"
|
||||||
#include "util/ChunkedHeap/ChunkedHeap.h"
|
#include "util/ChunkedHeap/ChunkedHeap.h"
|
||||||
|
#include "util/helpers/MemoryPool.h"
|
||||||
|
|
||||||
|
enum class VKR_BUFFER_TYPE
|
||||||
|
{
|
||||||
|
STAGING, // staging upload buffer
|
||||||
|
INDEX, // buffer for index data
|
||||||
|
STRIDE, // buffer for stride-adjusted vertex data
|
||||||
|
};
|
||||||
|
|
||||||
|
class VKRBuffer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static VKRBuffer* Create(VKR_BUFFER_TYPE bufferType, size_t bufferSize, VkMemoryPropertyFlags properties);
|
||||||
|
~VKRBuffer();
|
||||||
|
|
||||||
|
VkBuffer GetVkBuffer() const { return m_buffer; }
|
||||||
|
VkDeviceMemory GetVkBufferMemory() const { return m_bufferMemory; }
|
||||||
|
|
||||||
|
uint8* GetPtr() const { return m_mappedMemory; }
|
||||||
|
|
||||||
|
bool RequiresFlush() const { return m_requiresFlush; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
VKRBuffer(VkBuffer buffer, VkDeviceMemory bufferMem) : m_buffer(buffer), m_bufferMemory(bufferMem) { };
|
||||||
|
|
||||||
|
VkBuffer m_buffer;
|
||||||
|
VkDeviceMemory m_bufferMemory;
|
||||||
|
uint8* m_mappedMemory;
|
||||||
|
bool m_requiresFlush{false};
|
||||||
|
};
|
||||||
|
|
||||||
struct VkImageMemAllocation
|
struct VkImageMemAllocation
|
||||||
{
|
{
|
||||||
|
@ -14,18 +44,17 @@ struct VkImageMemAllocation
|
||||||
uint32 getAllocationSize() { return allocationSize; }
|
uint32 getAllocationSize() { return allocationSize; }
|
||||||
};
|
};
|
||||||
|
|
||||||
class VkTextureChunkedHeap : private ChunkedHeap
|
class VkTextureChunkedHeap : private ChunkedHeap<>
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
VkTextureChunkedHeap(class VKRMemoryManager* memoryManager, uint32 typeFilter, VkDevice device) : m_vkrMemoryManager(memoryManager), m_typeFilter(typeFilter), m_device(device) { };
|
VkTextureChunkedHeap(class VKRMemoryManager* memoryManager, uint32 typeFilter) : m_vkrMemoryManager(memoryManager), m_typeFilter(typeFilter) { };
|
||||||
|
~VkTextureChunkedHeap();
|
||||||
|
|
||||||
struct ChunkInfo
|
struct ChunkInfo
|
||||||
{
|
{
|
||||||
VkDeviceMemory mem;
|
VkDeviceMemory mem;
|
||||||
};
|
};
|
||||||
|
|
||||||
uint32 allocateNewChunk(uint32 chunkIndex, uint32 minimumAllocationSize) override;
|
|
||||||
|
|
||||||
CHAddr allocMem(uint32 size, uint32 alignment)
|
CHAddr allocMem(uint32 size, uint32 alignment)
|
||||||
{
|
{
|
||||||
if (alignment < 4)
|
if (alignment < 4)
|
||||||
|
@ -43,11 +72,6 @@ public:
|
||||||
this->free(addr);
|
this->free(addr);
|
||||||
}
|
}
|
||||||
|
|
||||||
void setDevice(VkDevice dev)
|
|
||||||
{
|
|
||||||
m_device = dev;
|
|
||||||
}
|
|
||||||
|
|
||||||
VkDeviceMemory getChunkMem(uint32 index)
|
VkDeviceMemory getChunkMem(uint32 index)
|
||||||
{
|
{
|
||||||
if (index >= m_list_chunkInfo.size())
|
if (index >= m_list_chunkInfo.size())
|
||||||
|
@ -57,29 +81,75 @@ public:
|
||||||
|
|
||||||
void getStatistics(uint32& totalHeapSize, uint32& allocatedBytes) const
|
void getStatistics(uint32& totalHeapSize, uint32& allocatedBytes) const
|
||||||
{
|
{
|
||||||
totalHeapSize = numHeapBytes;
|
totalHeapSize = m_numHeapBytes;
|
||||||
allocatedBytes = numAllocatedBytes;
|
allocatedBytes = m_numAllocatedBytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
VkDevice m_device;
|
private:
|
||||||
|
uint32 allocateNewChunk(uint32 chunkIndex, uint32 minimumAllocationSize) override;
|
||||||
|
|
||||||
uint32 m_typeFilter{ 0xFFFFFFFF };
|
uint32 m_typeFilter{ 0xFFFFFFFF };
|
||||||
class VKRMemoryManager* m_vkrMemoryManager;
|
class VKRMemoryManager* m_vkrMemoryManager;
|
||||||
std::vector<ChunkInfo> m_list_chunkInfo;
|
std::vector<ChunkInfo> m_list_chunkInfo;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class VkBufferChunkedHeap : private ChunkedHeap<>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
VkBufferChunkedHeap(VKR_BUFFER_TYPE bufferType, size_t minimumBufferAllocationSize) : m_bufferType(bufferType), m_minimumBufferAllocationSize(minimumBufferAllocationSize) { };
|
||||||
|
~VkBufferChunkedHeap();
|
||||||
|
|
||||||
|
using ChunkedHeap::alloc;
|
||||||
|
using ChunkedHeap::free;
|
||||||
|
|
||||||
|
uint8* GetChunkPtr(uint32 index) const
|
||||||
|
{
|
||||||
|
if (index >= m_chunkBuffers.size())
|
||||||
|
return nullptr;
|
||||||
|
return m_chunkBuffers[index]->GetPtr();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GetChunkVkMemInfo(uint32 index, VkBuffer& buffer, VkDeviceMemory& mem)
|
||||||
|
{
|
||||||
|
if (index >= m_chunkBuffers.size())
|
||||||
|
{
|
||||||
|
buffer = VK_NULL_HANDLE;
|
||||||
|
mem = VK_NULL_HANDLE;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
buffer = m_chunkBuffers[index]->GetVkBuffer();
|
||||||
|
mem = m_chunkBuffers[index]->GetVkBufferMemory();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GetStats(uint32& numBuffers, size_t& totalBufferSize, size_t& freeBufferSize) const
|
||||||
|
{
|
||||||
|
numBuffers = m_chunkBuffers.size();
|
||||||
|
totalBufferSize = m_numHeapBytes;
|
||||||
|
freeBufferSize = m_numHeapBytes - m_numAllocatedBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RequiresFlush(uint32 index) const
|
||||||
|
{
|
||||||
|
if (index >= m_chunkBuffers.size())
|
||||||
|
return false;
|
||||||
|
return m_chunkBuffers[index]->RequiresFlush();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint32 allocateNewChunk(uint32 chunkIndex, uint32 minimumAllocationSize) override;
|
||||||
|
|
||||||
|
VKR_BUFFER_TYPE m_bufferType;
|
||||||
|
std::vector<VKRBuffer*> m_chunkBuffers;
|
||||||
|
size_t m_minimumBufferAllocationSize;
|
||||||
|
};
|
||||||
|
|
||||||
// a circular ring-buffer which tracks and releases memory per command-buffer
|
// a circular ring-buffer which tracks and releases memory per command-buffer
|
||||||
class VKRSynchronizedRingAllocator
|
class VKRSynchronizedRingAllocator
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
enum class BUFFER_TYPE
|
VKRSynchronizedRingAllocator(class VulkanRenderer* vkRenderer, class VKRMemoryManager* vkMemoryManager, VKR_BUFFER_TYPE bufferType, uint32 minimumBufferAllocSize) : m_vkr(vkRenderer), m_vkrMemMgr(vkMemoryManager), m_bufferType(bufferType), m_minimumBufferAllocSize(minimumBufferAllocSize) {};
|
||||||
{
|
|
||||||
STAGING, // staging upload buffer
|
|
||||||
INDEX, // buffer for index data
|
|
||||||
STRIDE, // buffer for stride-adjusted vertex data
|
|
||||||
};
|
|
||||||
|
|
||||||
VKRSynchronizedRingAllocator(class VulkanRenderer* vkRenderer, class VKRMemoryManager* vkMemoryManager, BUFFER_TYPE bufferType, uint32 minimumBufferAllocSize) : m_vkr(vkRenderer), m_vkrMemMgr(vkMemoryManager), m_bufferType(bufferType), m_minimumBufferAllocSize(minimumBufferAllocSize) {};
|
|
||||||
VKRSynchronizedRingAllocator(const VKRSynchronizedRingAllocator&) = delete; // disallow copy
|
VKRSynchronizedRingAllocator(const VKRSynchronizedRingAllocator&) = delete; // disallow copy
|
||||||
|
~VKRSynchronizedRingAllocator();
|
||||||
|
|
||||||
struct BufferSyncPoint_t
|
struct BufferSyncPoint_t
|
||||||
{
|
{
|
||||||
|
@ -126,13 +196,53 @@ private:
|
||||||
|
|
||||||
const class VulkanRenderer* m_vkr;
|
const class VulkanRenderer* m_vkr;
|
||||||
const class VKRMemoryManager* m_vkrMemMgr;
|
const class VKRMemoryManager* m_vkrMemMgr;
|
||||||
const BUFFER_TYPE m_bufferType;
|
const VKR_BUFFER_TYPE m_bufferType;
|
||||||
const uint32 m_minimumBufferAllocSize;
|
const uint32 m_minimumBufferAllocSize;
|
||||||
|
|
||||||
std::vector<AllocatorBuffer_t> m_buffers;
|
std::vector<AllocatorBuffer_t> m_buffers;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// heap style allocator with released memory being freed after the current command buffer finishes
|
||||||
|
class VKRSynchronizedHeapAllocator
|
||||||
|
{
|
||||||
|
struct TrackedAllocation
|
||||||
|
{
|
||||||
|
TrackedAllocation(CHAddr allocation) : allocation(allocation) {};
|
||||||
|
CHAddr allocation;
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
VKRSynchronizedHeapAllocator(class VKRMemoryManager* vkMemoryManager, VKR_BUFFER_TYPE bufferType, size_t minimumBufferAllocSize);
|
||||||
|
VKRSynchronizedHeapAllocator(const VKRSynchronizedHeapAllocator&) = delete; // disallow copy
|
||||||
|
|
||||||
|
struct AllocatorReservation
|
||||||
|
{
|
||||||
|
VkBuffer vkBuffer;
|
||||||
|
VkDeviceMemory vkMem;
|
||||||
|
uint8* memPtr;
|
||||||
|
uint32 bufferOffset;
|
||||||
|
uint32 size;
|
||||||
|
uint32 bufferIndex;
|
||||||
|
};
|
||||||
|
|
||||||
|
AllocatorReservation* AllocateBufferMemory(uint32 size, uint32 alignment);
|
||||||
|
void FreeReservation(AllocatorReservation* uploadReservation);
|
||||||
|
void FlushReservation(AllocatorReservation* uploadReservation);
|
||||||
|
|
||||||
|
void CleanupBuffer(uint64 latestFinishedCommandBufferId);
|
||||||
|
|
||||||
|
void GetStats(uint32& numBuffers, size_t& totalBufferSize, size_t& freeBufferSize) const;
|
||||||
|
private:
|
||||||
|
const class VKRMemoryManager* m_vkrMemMgr;
|
||||||
|
VkBufferChunkedHeap m_chunkedHeap;
|
||||||
|
// allocations
|
||||||
|
std::vector<TrackedAllocation> m_activeAllocations;
|
||||||
|
MemoryPool<AllocatorReservation> m_poolAllocatorReservation{32};
|
||||||
|
// release queue
|
||||||
|
std::unordered_map<uint64, std::vector<CHAddr>> m_releaseQueue;
|
||||||
|
};
|
||||||
|
|
||||||
void LatteIndices_invalidateAll();
|
void LatteIndices_invalidateAll();
|
||||||
|
|
||||||
class VKRMemoryManager
|
class VKRMemoryManager
|
||||||
|
@ -140,15 +250,15 @@ class VKRMemoryManager
|
||||||
friend class VKRSynchronizedRingAllocator;
|
friend class VKRSynchronizedRingAllocator;
|
||||||
public:
|
public:
|
||||||
VKRMemoryManager(class VulkanRenderer* renderer) :
|
VKRMemoryManager(class VulkanRenderer* renderer) :
|
||||||
m_stagingBuffer(renderer, this, VKRSynchronizedRingAllocator::BUFFER_TYPE::STAGING, 32u * 1024 * 1024),
|
m_stagingBuffer(renderer, this, VKR_BUFFER_TYPE::STAGING, 32u * 1024 * 1024),
|
||||||
m_indexBuffer(renderer, this, VKRSynchronizedRingAllocator::BUFFER_TYPE::INDEX, 4u * 1024 * 1024),
|
m_indexBuffer(this, VKR_BUFFER_TYPE::INDEX, 4u * 1024 * 1024),
|
||||||
m_vertexStrideMetalBuffer(renderer, this, VKRSynchronizedRingAllocator::BUFFER_TYPE::STRIDE, 4u * 1024 * 1024)
|
m_vertexStrideMetalBuffer(renderer, this, VKR_BUFFER_TYPE::STRIDE, 4u * 1024 * 1024)
|
||||||
{
|
{
|
||||||
m_vkr = renderer;
|
m_vkr = renderer;
|
||||||
}
|
}
|
||||||
|
|
||||||
// texture memory management
|
// texture memory management
|
||||||
std::unordered_map<uint32, VkTextureChunkedHeap*> map_textureHeap; // one heap per memory type
|
std::unordered_map<uint32, std::unique_ptr<VkTextureChunkedHeap>> map_textureHeap; // one heap per memory type
|
||||||
std::vector<uint8> m_textureUploadBuffer;
|
std::vector<uint8> m_textureUploadBuffer;
|
||||||
|
|
||||||
// texture upload buffer
|
// texture upload buffer
|
||||||
|
@ -167,7 +277,7 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
VKRSynchronizedRingAllocator& getStagingAllocator() { return m_stagingBuffer; }; // allocator for texture/attribute/uniform uploads
|
VKRSynchronizedRingAllocator& getStagingAllocator() { return m_stagingBuffer; }; // allocator for texture/attribute/uniform uploads
|
||||||
VKRSynchronizedRingAllocator& getIndexAllocator() { return m_indexBuffer; }; // allocator for index data
|
VKRSynchronizedHeapAllocator& GetIndexAllocator() { return m_indexBuffer; }; // allocator for index data
|
||||||
VKRSynchronizedRingAllocator& getMetalStrideWorkaroundAllocator() { return m_vertexStrideMetalBuffer; }; // allocator for stride-adjusted vertex data
|
VKRSynchronizedRingAllocator& getMetalStrideWorkaroundAllocator() { return m_vertexStrideMetalBuffer; }; // allocator for stride-adjusted vertex data
|
||||||
|
|
||||||
void cleanupBuffers(uint64 latestFinishedCommandBufferId)
|
void cleanupBuffers(uint64 latestFinishedCommandBufferId)
|
||||||
|
@ -178,9 +288,7 @@ public:
|
||||||
m_vertexStrideMetalBuffer.CleanupBuffer(latestFinishedCommandBufferId);
|
m_vertexStrideMetalBuffer.CleanupBuffer(latestFinishedCommandBufferId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// memory helpers
|
bool FindMemoryType(uint32 typeFilter, VkMemoryPropertyFlags properties, uint32& memoryIndex) const; // searches for exact properties. Can gracefully fail without throwing exception (returns false)
|
||||||
uint32_t FindMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) const;
|
|
||||||
bool FindMemoryType2(uint32 typeFilter, VkMemoryPropertyFlags properties, uint32& memoryIndex) const; // searches for exact properties. Can gracefully fail without throwing exception (returns false)
|
|
||||||
std::vector<uint32> FindMemoryTypes(uint32_t typeFilter, VkMemoryPropertyFlags properties) const;
|
std::vector<uint32> FindMemoryTypes(uint32_t typeFilter, VkMemoryPropertyFlags properties) const;
|
||||||
|
|
||||||
// image memory allocation
|
// image memory allocation
|
||||||
|
@ -190,8 +298,7 @@ public:
|
||||||
// buffer management
|
// buffer management
|
||||||
size_t GetTotalMemoryForBufferType(VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, size_t minimumBufferSize = 16 * 1024 * 1024);
|
size_t GetTotalMemoryForBufferType(VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, size_t minimumBufferSize = 16 * 1024 * 1024);
|
||||||
|
|
||||||
void CreateBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory) const;
|
bool CreateBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory) const; // same as CreateBuffer but doesn't throw exception on failure
|
||||||
bool CreateBuffer2(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory) const; // same as CreateBuffer but doesn't throw exception on failure
|
|
||||||
bool CreateBufferFromHostMemory(void* hostPointer, VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory) const;
|
bool CreateBufferFromHostMemory(void* hostPointer, VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory) const;
|
||||||
|
|
||||||
void DeleteBuffer(VkBuffer& buffer, VkDeviceMemory& deviceMem) const;
|
void DeleteBuffer(VkBuffer& buffer, VkDeviceMemory& deviceMem) const;
|
||||||
|
@ -202,6 +309,6 @@ public:
|
||||||
private:
|
private:
|
||||||
class VulkanRenderer* m_vkr;
|
class VulkanRenderer* m_vkr;
|
||||||
VKRSynchronizedRingAllocator m_stagingBuffer;
|
VKRSynchronizedRingAllocator m_stagingBuffer;
|
||||||
VKRSynchronizedRingAllocator m_indexBuffer;
|
VKRSynchronizedHeapAllocator m_indexBuffer;
|
||||||
VKRSynchronizedRingAllocator m_vertexStrideMetalBuffer;
|
VKRSynchronizedRingAllocator m_vertexStrideMetalBuffer;
|
||||||
};
|
};
|
||||||
|
|
|
@ -165,6 +165,7 @@ VKFUNC_DEVICE(vkCmdDraw);
|
||||||
VKFUNC_DEVICE(vkCmdCopyBufferToImage);
|
VKFUNC_DEVICE(vkCmdCopyBufferToImage);
|
||||||
VKFUNC_DEVICE(vkCmdCopyImageToBuffer);
|
VKFUNC_DEVICE(vkCmdCopyImageToBuffer);
|
||||||
VKFUNC_DEVICE(vkCmdClearColorImage);
|
VKFUNC_DEVICE(vkCmdClearColorImage);
|
||||||
|
VKFUNC_DEVICE(vkCmdClearAttachments);
|
||||||
VKFUNC_DEVICE(vkCmdBindIndexBuffer);
|
VKFUNC_DEVICE(vkCmdBindIndexBuffer);
|
||||||
VKFUNC_DEVICE(vkCmdBindVertexBuffers);
|
VKFUNC_DEVICE(vkCmdBindVertexBuffers);
|
||||||
VKFUNC_DEVICE(vkCmdDrawIndexed);
|
VKFUNC_DEVICE(vkCmdDrawIndexed);
|
||||||
|
@ -198,6 +199,7 @@ VKFUNC_DEVICE(vkCmdEndTransformFeedbackEXT);
|
||||||
|
|
||||||
// query
|
// query
|
||||||
VKFUNC_DEVICE(vkCreateQueryPool);
|
VKFUNC_DEVICE(vkCreateQueryPool);
|
||||||
|
VKFUNC_DEVICE(vkDestroyQueryPool);
|
||||||
VKFUNC_DEVICE(vkCmdResetQueryPool);
|
VKFUNC_DEVICE(vkCmdResetQueryPool);
|
||||||
VKFUNC_DEVICE(vkCmdBeginQuery);
|
VKFUNC_DEVICE(vkCmdBeginQuery);
|
||||||
VKFUNC_DEVICE(vkCmdEndQuery);
|
VKFUNC_DEVICE(vkCmdEndQuery);
|
||||||
|
@ -236,6 +238,7 @@ VKFUNC_DEVICE(vkAllocateDescriptorSets);
|
||||||
VKFUNC_DEVICE(vkFreeDescriptorSets);
|
VKFUNC_DEVICE(vkFreeDescriptorSets);
|
||||||
VKFUNC_DEVICE(vkUpdateDescriptorSets);
|
VKFUNC_DEVICE(vkUpdateDescriptorSets);
|
||||||
VKFUNC_DEVICE(vkCreateDescriptorPool);
|
VKFUNC_DEVICE(vkCreateDescriptorPool);
|
||||||
|
VKFUNC_DEVICE(vkDestroyDescriptorPool);
|
||||||
VKFUNC_DEVICE(vkDestroyDescriptorSetLayout);
|
VKFUNC_DEVICE(vkDestroyDescriptorSetLayout);
|
||||||
|
|
||||||
#undef VKFUNC_INIT
|
#undef VKFUNC_INIT
|
||||||
|
|
|
@ -439,7 +439,7 @@ VulkanRenderer::VulkanRenderer()
|
||||||
GetDeviceFeatures();
|
GetDeviceFeatures();
|
||||||
|
|
||||||
// init memory manager
|
// init memory manager
|
||||||
memoryManager = new VKRMemoryManager(this);
|
memoryManager.reset(new VKRMemoryManager(this));
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -577,15 +577,15 @@ VulkanRenderer::VulkanRenderer()
|
||||||
void* bufferPtr;
|
void* bufferPtr;
|
||||||
// init ringbuffer for uniform vars
|
// init ringbuffer for uniform vars
|
||||||
m_uniformVarBufferMemoryIsCoherent = false;
|
m_uniformVarBufferMemoryIsCoherent = false;
|
||||||
if (memoryManager->CreateBuffer2(UNIFORMVAR_RINGBUFFER_SIZE, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT, m_uniformVarBuffer, m_uniformVarBufferMemory))
|
if (memoryManager->CreateBuffer(UNIFORMVAR_RINGBUFFER_SIZE, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT, m_uniformVarBuffer, m_uniformVarBufferMemory))
|
||||||
m_uniformVarBufferMemoryIsCoherent = true;
|
m_uniformVarBufferMemoryIsCoherent = true;
|
||||||
else if (memoryManager->CreateBuffer2(UNIFORMVAR_RINGBUFFER_SIZE, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT | VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, m_uniformVarBuffer, m_uniformVarBufferMemory))
|
else if (memoryManager->CreateBuffer(UNIFORMVAR_RINGBUFFER_SIZE, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT | VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, m_uniformVarBuffer, m_uniformVarBufferMemory))
|
||||||
m_uniformVarBufferMemoryIsCoherent = true; // unified memory
|
m_uniformVarBufferMemoryIsCoherent = true; // unified memory
|
||||||
else if (memoryManager->CreateBuffer2(UNIFORMVAR_RINGBUFFER_SIZE, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, m_uniformVarBuffer, m_uniformVarBufferMemory))
|
else if (memoryManager->CreateBuffer(UNIFORMVAR_RINGBUFFER_SIZE, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, m_uniformVarBuffer, m_uniformVarBufferMemory))
|
||||||
m_uniformVarBufferMemoryIsCoherent = true;
|
m_uniformVarBufferMemoryIsCoherent = true;
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
memoryManager->CreateBuffer2(UNIFORMVAR_RINGBUFFER_SIZE, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, m_uniformVarBuffer, m_uniformVarBufferMemory);
|
memoryManager->CreateBuffer(UNIFORMVAR_RINGBUFFER_SIZE, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, m_uniformVarBuffer, m_uniformVarBufferMemory);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!m_uniformVarBufferMemoryIsCoherent)
|
if (!m_uniformVarBufferMemoryIsCoherent)
|
||||||
|
@ -628,6 +628,31 @@ VulkanRenderer::~VulkanRenderer()
|
||||||
m_pipeline_cache_semaphore.notify();
|
m_pipeline_cache_semaphore.notify();
|
||||||
m_pipeline_cache_save_thread.join();
|
m_pipeline_cache_save_thread.join();
|
||||||
|
|
||||||
|
vkDestroyPipelineCache(m_logicalDevice, m_pipeline_cache, nullptr);
|
||||||
|
|
||||||
|
if(!m_backbufferBlitDescriptorSetCache.empty())
|
||||||
|
{
|
||||||
|
std::vector<VkDescriptorSet> freeVector;
|
||||||
|
freeVector.reserve(m_backbufferBlitDescriptorSetCache.size());
|
||||||
|
std::transform(m_backbufferBlitDescriptorSetCache.begin(), m_backbufferBlitDescriptorSetCache.end(), std::back_inserter(freeVector), [](auto& i) {
|
||||||
|
return i.second;
|
||||||
|
});
|
||||||
|
vkFreeDescriptorSets(m_logicalDevice, m_descriptorPool, freeVector.size(), freeVector.data());
|
||||||
|
}
|
||||||
|
|
||||||
|
vkDestroyDescriptorPool(m_logicalDevice, m_descriptorPool, nullptr);
|
||||||
|
|
||||||
|
for(auto& i : m_backbufferBlitPipelineCache)
|
||||||
|
{
|
||||||
|
vkDestroyPipeline(m_logicalDevice, i.second, nullptr);
|
||||||
|
}
|
||||||
|
m_backbufferBlitPipelineCache = {};
|
||||||
|
|
||||||
|
if(m_occlusionQueries.queryPool != VK_NULL_HANDLE)
|
||||||
|
vkDestroyQueryPool(m_logicalDevice, m_occlusionQueries.queryPool, nullptr);
|
||||||
|
|
||||||
|
vkDestroyDescriptorSetLayout(m_logicalDevice, m_swapchainDescriptorSetLayout, nullptr);
|
||||||
|
|
||||||
// shut down imgui
|
// shut down imgui
|
||||||
ImGui_ImplVulkan_Shutdown();
|
ImGui_ImplVulkan_Shutdown();
|
||||||
|
|
||||||
|
@ -640,10 +665,6 @@ VulkanRenderer::~VulkanRenderer()
|
||||||
memoryManager->DeleteBuffer(m_xfbRingBuffer, m_xfbRingBufferMemory);
|
memoryManager->DeleteBuffer(m_xfbRingBuffer, m_xfbRingBufferMemory);
|
||||||
memoryManager->DeleteBuffer(m_occlusionQueries.bufferQueryResults, m_occlusionQueries.memoryQueryResults);
|
memoryManager->DeleteBuffer(m_occlusionQueries.bufferQueryResults, m_occlusionQueries.memoryQueryResults);
|
||||||
memoryManager->DeleteBuffer(m_bufferCache, m_bufferCacheMemory);
|
memoryManager->DeleteBuffer(m_bufferCache, m_bufferCacheMemory);
|
||||||
// texture memory
|
|
||||||
// todo
|
|
||||||
// upload buffers
|
|
||||||
// todo
|
|
||||||
|
|
||||||
m_padSwapchainInfo = nullptr;
|
m_padSwapchainInfo = nullptr;
|
||||||
m_mainSwapchainInfo = nullptr;
|
m_mainSwapchainInfo = nullptr;
|
||||||
|
@ -666,12 +687,20 @@ VulkanRenderer::~VulkanRenderer()
|
||||||
it = VK_NULL_HANDLE;
|
it = VK_NULL_HANDLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for(auto& sem : m_commandBufferSemaphores)
|
||||||
|
{
|
||||||
|
vkDestroySemaphore(m_logicalDevice, sem, nullptr);
|
||||||
|
sem = VK_NULL_HANDLE;
|
||||||
|
}
|
||||||
|
|
||||||
if (m_pipelineLayout != VK_NULL_HANDLE)
|
if (m_pipelineLayout != VK_NULL_HANDLE)
|
||||||
vkDestroyPipelineLayout(m_logicalDevice, m_pipelineLayout, nullptr);
|
vkDestroyPipelineLayout(m_logicalDevice, m_pipelineLayout, nullptr);
|
||||||
|
|
||||||
if (m_commandPool != VK_NULL_HANDLE)
|
if (m_commandPool != VK_NULL_HANDLE)
|
||||||
vkDestroyCommandPool(m_logicalDevice, m_commandPool, nullptr);
|
vkDestroyCommandPool(m_logicalDevice, m_commandPool, nullptr);
|
||||||
|
|
||||||
|
VKRObjectSampler::DestroyCache();
|
||||||
|
|
||||||
// destroy debug callback
|
// destroy debug callback
|
||||||
if (m_debugCallback)
|
if (m_debugCallback)
|
||||||
{
|
{
|
||||||
|
@ -679,6 +708,12 @@ VulkanRenderer::~VulkanRenderer()
|
||||||
vkDestroyDebugUtilsMessengerEXT(m_instance, m_debugCallback, nullptr);
|
vkDestroyDebugUtilsMessengerEXT(m_instance, m_debugCallback, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
while(!m_destructionQueue.empty())
|
||||||
|
ProcessDestructionQueue();
|
||||||
|
|
||||||
|
// destroy memory manager
|
||||||
|
memoryManager.reset();
|
||||||
|
|
||||||
// destroy instance, devices
|
// destroy instance, devices
|
||||||
if (m_instance != VK_NULL_HANDLE)
|
if (m_instance != VK_NULL_HANDLE)
|
||||||
{
|
{
|
||||||
|
@ -690,9 +725,6 @@ VulkanRenderer::~VulkanRenderer()
|
||||||
vkDestroyInstance(m_instance, nullptr);
|
vkDestroyInstance(m_instance, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
// destroy memory manager
|
|
||||||
delete memoryManager;
|
|
||||||
|
|
||||||
// crashes?
|
// crashes?
|
||||||
//glslang::FinalizeProcess();
|
//glslang::FinalizeProcess();
|
||||||
}
|
}
|
||||||
|
@ -823,7 +855,14 @@ void VulkanRenderer::HandleScreenshotRequest(LatteTextureView* texView, bool pad
|
||||||
VkMemoryAllocateInfo allocInfo{};
|
VkMemoryAllocateInfo allocInfo{};
|
||||||
allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
|
allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
|
||||||
allocInfo.allocationSize = memRequirements.size;
|
allocInfo.allocationSize = memRequirements.size;
|
||||||
allocInfo.memoryTypeIndex = memoryManager->FindMemoryType(memRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
|
uint32 memIndex;
|
||||||
|
bool foundMemory = memoryManager->FindMemoryType(memRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, memIndex);
|
||||||
|
if(!foundMemory)
|
||||||
|
{
|
||||||
|
cemuLog_log(LogType::Force, "Screenshot request failed due to incompatible vulkan memory types.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
allocInfo.memoryTypeIndex = memIndex;
|
||||||
|
|
||||||
if (vkAllocateMemory(m_logicalDevice, &allocInfo, nullptr, &imageMemory) != VK_SUCCESS)
|
if (vkAllocateMemory(m_logicalDevice, &allocInfo, nullptr, &imageMemory) != VK_SUCCESS)
|
||||||
{
|
{
|
||||||
|
@ -1606,6 +1645,7 @@ void VulkanRenderer::Initialize()
|
||||||
|
|
||||||
void VulkanRenderer::Shutdown()
|
void VulkanRenderer::Shutdown()
|
||||||
{
|
{
|
||||||
|
DeleteFontTextures();
|
||||||
Renderer::Shutdown();
|
Renderer::Shutdown();
|
||||||
SubmitCommandBuffer();
|
SubmitCommandBuffer();
|
||||||
WaitDeviceIdle();
|
WaitDeviceIdle();
|
||||||
|
@ -1806,7 +1846,6 @@ void VulkanRenderer::ImguiEnd()
|
||||||
vkCmdEndRenderPass(m_state.currentCommandBuffer);
|
vkCmdEndRenderPass(m_state.currentCommandBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<LatteTextureVk*> g_imgui_textures; // TODO manage better
|
|
||||||
ImTextureID VulkanRenderer::GenerateTexture(const std::vector<uint8>& data, const Vector2i& size)
|
ImTextureID VulkanRenderer::GenerateTexture(const std::vector<uint8>& data, const Vector2i& size)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
@ -1836,6 +1875,7 @@ void VulkanRenderer::DeleteTexture(ImTextureID id)
|
||||||
|
|
||||||
void VulkanRenderer::DeleteFontTextures()
|
void VulkanRenderer::DeleteFontTextures()
|
||||||
{
|
{
|
||||||
|
WaitDeviceIdle();
|
||||||
ImGui_ImplVulkan_DestroyFontsTexture();
|
ImGui_ImplVulkan_DestroyFontsTexture();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1874,7 +1914,7 @@ void VulkanRenderer::InitFirstCommandBuffer()
|
||||||
vkResetFences(m_logicalDevice, 1, &m_cmd_buffer_fences[m_commandBufferIndex]);
|
vkResetFences(m_logicalDevice, 1, &m_cmd_buffer_fences[m_commandBufferIndex]);
|
||||||
VkCommandBufferBeginInfo beginInfo{};
|
VkCommandBufferBeginInfo beginInfo{};
|
||||||
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
|
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
|
||||||
beginInfo.flags = VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT;
|
beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
|
||||||
vkBeginCommandBuffer(m_state.currentCommandBuffer, &beginInfo);
|
vkBeginCommandBuffer(m_state.currentCommandBuffer, &beginInfo);
|
||||||
|
|
||||||
vkCmdSetViewport(m_state.currentCommandBuffer, 0, 1, &m_state.currentViewport);
|
vkCmdSetViewport(m_state.currentCommandBuffer, 0, 1, &m_state.currentViewport);
|
||||||
|
@ -1892,6 +1932,7 @@ void VulkanRenderer::ProcessFinishedCommandBuffers()
|
||||||
if (fenceStatus == VK_SUCCESS)
|
if (fenceStatus == VK_SUCCESS)
|
||||||
{
|
{
|
||||||
ProcessDestructionQueue();
|
ProcessDestructionQueue();
|
||||||
|
m_uniformVarBufferReadIndex = m_cmdBufferUniformRingbufIndices[m_commandBufferSyncIndex];
|
||||||
m_commandBufferSyncIndex = (m_commandBufferSyncIndex + 1) % m_commandBuffers.size();
|
m_commandBufferSyncIndex = (m_commandBufferSyncIndex + 1) % m_commandBuffers.size();
|
||||||
memoryManager->cleanupBuffers(m_countCommandBufferFinished);
|
memoryManager->cleanupBuffers(m_countCommandBufferFinished);
|
||||||
m_countCommandBufferFinished++;
|
m_countCommandBufferFinished++;
|
||||||
|
@ -1985,6 +2026,7 @@ void VulkanRenderer::SubmitCommandBuffer(VkSemaphore signalSemaphore, VkSemaphor
|
||||||
cemuLog_logDebug(LogType::Force, "Vulkan: Waiting for available command buffer...");
|
cemuLog_logDebug(LogType::Force, "Vulkan: Waiting for available command buffer...");
|
||||||
WaitForNextFinishedCommandBuffer();
|
WaitForNextFinishedCommandBuffer();
|
||||||
}
|
}
|
||||||
|
m_cmdBufferUniformRingbufIndices[nextCmdBufferIndex] = m_cmdBufferUniformRingbufIndices[m_commandBufferIndex];
|
||||||
m_commandBufferIndex = nextCmdBufferIndex;
|
m_commandBufferIndex = nextCmdBufferIndex;
|
||||||
|
|
||||||
|
|
||||||
|
@ -1994,7 +2036,7 @@ void VulkanRenderer::SubmitCommandBuffer(VkSemaphore signalSemaphore, VkSemaphor
|
||||||
|
|
||||||
VkCommandBufferBeginInfo beginInfo{};
|
VkCommandBufferBeginInfo beginInfo{};
|
||||||
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
|
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
|
||||||
beginInfo.flags = VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT;
|
beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
|
||||||
vkBeginCommandBuffer(m_state.currentCommandBuffer, &beginInfo);
|
vkBeginCommandBuffer(m_state.currentCommandBuffer, &beginInfo);
|
||||||
|
|
||||||
// make sure some states are set for this command buffer
|
// make sure some states are set for this command buffer
|
||||||
|
@ -2515,9 +2557,8 @@ VkPipeline VulkanRenderer::backbufferBlit_createGraphicsPipeline(VkDescriptorSet
|
||||||
hash += (uint64)(chainInfo.m_usesSRGB);
|
hash += (uint64)(chainInfo.m_usesSRGB);
|
||||||
hash += ((uint64)padView) << 1;
|
hash += ((uint64)padView) << 1;
|
||||||
|
|
||||||
static std::unordered_map<uint64, VkPipeline> s_pipeline_cache;
|
const auto it = m_backbufferBlitPipelineCache.find(hash);
|
||||||
const auto it = s_pipeline_cache.find(hash);
|
if (it != m_backbufferBlitPipelineCache.cend())
|
||||||
if (it != s_pipeline_cache.cend())
|
|
||||||
return it->second;
|
return it->second;
|
||||||
|
|
||||||
std::vector<VkPipelineShaderStageCreateInfo> shaderStages;
|
std::vector<VkPipelineShaderStageCreateInfo> shaderStages;
|
||||||
|
@ -2579,10 +2620,18 @@ VkPipeline VulkanRenderer::backbufferBlit_createGraphicsPipeline(VkDescriptorSet
|
||||||
colorBlending.blendConstants[2] = 0.0f;
|
colorBlending.blendConstants[2] = 0.0f;
|
||||||
colorBlending.blendConstants[3] = 0.0f;
|
colorBlending.blendConstants[3] = 0.0f;
|
||||||
|
|
||||||
|
VkPushConstantRange pushConstantRange{
|
||||||
|
.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT,
|
||||||
|
.offset = 0,
|
||||||
|
.size = 3 * sizeof(float) * 2 // 3 vec2's
|
||||||
|
};
|
||||||
|
|
||||||
VkPipelineLayoutCreateInfo pipelineLayoutInfo{};
|
VkPipelineLayoutCreateInfo pipelineLayoutInfo{};
|
||||||
pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
|
pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
|
||||||
pipelineLayoutInfo.setLayoutCount = 1;
|
pipelineLayoutInfo.setLayoutCount = 1;
|
||||||
pipelineLayoutInfo.pSetLayouts = &descriptorLayout;
|
pipelineLayoutInfo.pSetLayouts = &descriptorLayout;
|
||||||
|
pipelineLayoutInfo.pushConstantRangeCount = 1;
|
||||||
|
pipelineLayoutInfo.pPushConstantRanges = &pushConstantRange;
|
||||||
|
|
||||||
VkResult result = vkCreatePipelineLayout(m_logicalDevice, &pipelineLayoutInfo, nullptr, &m_pipelineLayout);
|
VkResult result = vkCreatePipelineLayout(m_logicalDevice, &pipelineLayoutInfo, nullptr, &m_pipelineLayout);
|
||||||
if (result != VK_SUCCESS)
|
if (result != VK_SUCCESS)
|
||||||
|
@ -2613,7 +2662,7 @@ VkPipeline VulkanRenderer::backbufferBlit_createGraphicsPipeline(VkDescriptorSet
|
||||||
throw std::runtime_error(fmt::format("Failed to create graphics pipeline: {}", result));
|
throw std::runtime_error(fmt::format("Failed to create graphics pipeline: {}", result));
|
||||||
}
|
}
|
||||||
|
|
||||||
s_pipeline_cache[hash] = pipeline;
|
m_backbufferBlitPipelineCache[hash] = pipeline;
|
||||||
m_pipeline_cache_semaphore.notify();
|
m_pipeline_cache_semaphore.notify();
|
||||||
|
|
||||||
return pipeline;
|
return pipeline;
|
||||||
|
@ -2910,9 +2959,6 @@ void VulkanRenderer::DrawBackbufferQuad(LatteTextureView* texView, RendererOutpu
|
||||||
LatteTextureViewVk* texViewVk = (LatteTextureViewVk*)texView;
|
LatteTextureViewVk* texViewVk = (LatteTextureViewVk*)texView;
|
||||||
draw_endRenderPass();
|
draw_endRenderPass();
|
||||||
|
|
||||||
if (clearBackground)
|
|
||||||
ClearColorbuffer(padView);
|
|
||||||
|
|
||||||
// barrier for input texture
|
// barrier for input texture
|
||||||
VkMemoryBarrier memoryBarrier{};
|
VkMemoryBarrier memoryBarrier{};
|
||||||
memoryBarrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER;
|
memoryBarrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER;
|
||||||
|
@ -2949,11 +2995,40 @@ void VulkanRenderer::DrawBackbufferQuad(LatteTextureView* texView, RendererOutpu
|
||||||
|
|
||||||
vkCmdBeginRenderPass(m_state.currentCommandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);
|
vkCmdBeginRenderPass(m_state.currentCommandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);
|
||||||
|
|
||||||
|
if (clearBackground)
|
||||||
|
{
|
||||||
|
VkClearAttachment clearAttachment{};
|
||||||
|
clearAttachment.clearValue = {0,0,0,0};
|
||||||
|
clearAttachment.colorAttachment = 0;
|
||||||
|
clearAttachment.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||||
|
VkClearRect clearExtent = {{{0,0},chainInfo.m_actualExtent}, 0, 1};
|
||||||
|
vkCmdClearAttachments(m_state.currentCommandBuffer, 1, &clearAttachment, 1, &clearExtent);
|
||||||
|
}
|
||||||
|
|
||||||
vkCmdBindPipeline(m_state.currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
|
vkCmdBindPipeline(m_state.currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
|
||||||
m_state.currentPipeline = pipeline;
|
m_state.currentPipeline = pipeline;
|
||||||
|
|
||||||
vkCmdBindDescriptorSets(m_state.currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipelineLayout, 0, 1, &descriptSet, 0, nullptr);
|
vkCmdBindDescriptorSets(m_state.currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipelineLayout, 0, 1, &descriptSet, 0, nullptr);
|
||||||
|
|
||||||
|
// update push constants
|
||||||
|
Vector2f pushData[3];
|
||||||
|
|
||||||
|
// textureSrcResolution
|
||||||
|
sint32 effectiveWidth, effectiveHeight;
|
||||||
|
texView->baseTexture->GetEffectiveSize(effectiveWidth, effectiveHeight, 0);
|
||||||
|
pushData[0] = {(float)effectiveWidth, (float)effectiveHeight};
|
||||||
|
|
||||||
|
// nativeResolution
|
||||||
|
pushData[1] = {
|
||||||
|
(float)texViewVk->baseTexture->width,
|
||||||
|
(float)texViewVk->baseTexture->height,
|
||||||
|
};
|
||||||
|
|
||||||
|
// outputResolution
|
||||||
|
pushData[2] = {(float)imageWidth,(float)imageHeight};
|
||||||
|
|
||||||
|
vkCmdPushConstants(m_state.currentCommandBuffer, m_pipelineLayout, VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(float) * 2 * 3, &pushData);
|
||||||
|
|
||||||
vkCmdDraw(m_state.currentCommandBuffer, 6, 1, 0, 0);
|
vkCmdDraw(m_state.currentCommandBuffer, 6, 1, 0, 0);
|
||||||
|
|
||||||
vkCmdEndRenderPass(m_state.currentCommandBuffer);
|
vkCmdEndRenderPass(m_state.currentCommandBuffer);
|
||||||
|
@ -2994,9 +3069,8 @@ VkDescriptorSet VulkanRenderer::backbufferBlit_createDescriptorSet(VkDescriptorS
|
||||||
hash += (uint64)texViewVk->GetViewRGBA();
|
hash += (uint64)texViewVk->GetViewRGBA();
|
||||||
hash += (uint64)texViewVk->GetDefaultTextureSampler(useLinearTexFilter);
|
hash += (uint64)texViewVk->GetDefaultTextureSampler(useLinearTexFilter);
|
||||||
|
|
||||||
static std::unordered_map<uint64, VkDescriptorSet> s_set_cache;
|
const auto it = m_backbufferBlitDescriptorSetCache.find(hash);
|
||||||
const auto it = s_set_cache.find(hash);
|
if (it != m_backbufferBlitDescriptorSetCache.cend())
|
||||||
if (it != s_set_cache.cend())
|
|
||||||
return it->second;
|
return it->second;
|
||||||
|
|
||||||
VkDescriptorSetAllocateInfo allocInfo = {};
|
VkDescriptorSetAllocateInfo allocInfo = {};
|
||||||
|
@ -3027,7 +3101,7 @@ VkDescriptorSet VulkanRenderer::backbufferBlit_createDescriptorSet(VkDescriptorS
|
||||||
vkUpdateDescriptorSets(m_logicalDevice, 1, &descriptorWrites, 0, nullptr);
|
vkUpdateDescriptorSets(m_logicalDevice, 1, &descriptorWrites, 0, nullptr);
|
||||||
performanceMonitor.vk.numDescriptorSamplerTextures.increment();
|
performanceMonitor.vk.numDescriptorSamplerTextures.increment();
|
||||||
|
|
||||||
s_set_cache[hash] = result;
|
m_backbufferBlitDescriptorSetCache[hash] = result;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3160,7 +3234,8 @@ VkDescriptorSetInfo::~VkDescriptorSetInfo()
|
||||||
performanceMonitor.vk.numDescriptorDynUniformBuffers.decrement(statsNumDynUniformBuffers);
|
performanceMonitor.vk.numDescriptorDynUniformBuffers.decrement(statsNumDynUniformBuffers);
|
||||||
performanceMonitor.vk.numDescriptorStorageBuffers.decrement(statsNumStorageBuffers);
|
performanceMonitor.vk.numDescriptorStorageBuffers.decrement(statsNumStorageBuffers);
|
||||||
|
|
||||||
VulkanRenderer::GetInstance()->ReleaseDestructibleObject(m_vkObjDescriptorSet);
|
auto renderer = VulkanRenderer::GetInstance();
|
||||||
|
renderer->ReleaseDestructibleObject(m_vkObjDescriptorSet);
|
||||||
m_vkObjDescriptorSet = nullptr;
|
m_vkObjDescriptorSet = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3562,13 +3637,13 @@ void VulkanRenderer::buffer_bindUniformBuffer(LatteConst::ShaderType shaderType,
|
||||||
switch (shaderType)
|
switch (shaderType)
|
||||||
{
|
{
|
||||||
case LatteConst::ShaderType::Vertex:
|
case LatteConst::ShaderType::Vertex:
|
||||||
dynamicOffsetInfo.shaderUB[VulkanRendererConst::SHADER_STAGE_INDEX_VERTEX].unformBufferOffset[bufferIndex] = offset;
|
dynamicOffsetInfo.shaderUB[VulkanRendererConst::SHADER_STAGE_INDEX_VERTEX].uniformBufferOffset[bufferIndex] = offset;
|
||||||
break;
|
break;
|
||||||
case LatteConst::ShaderType::Geometry:
|
case LatteConst::ShaderType::Geometry:
|
||||||
dynamicOffsetInfo.shaderUB[VulkanRendererConst::SHADER_STAGE_INDEX_GEOMETRY].unformBufferOffset[bufferIndex] = offset;
|
dynamicOffsetInfo.shaderUB[VulkanRendererConst::SHADER_STAGE_INDEX_GEOMETRY].uniformBufferOffset[bufferIndex] = offset;
|
||||||
break;
|
break;
|
||||||
case LatteConst::ShaderType::Pixel:
|
case LatteConst::ShaderType::Pixel:
|
||||||
dynamicOffsetInfo.shaderUB[VulkanRendererConst::SHADER_STAGE_INDEX_FRAGMENT].unformBufferOffset[bufferIndex] = offset;
|
dynamicOffsetInfo.shaderUB[VulkanRendererConst::SHADER_STAGE_INDEX_FRAGMENT].uniformBufferOffset[bufferIndex] = offset;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
cemu_assert_debug(false);
|
cemu_assert_debug(false);
|
||||||
|
@ -3670,7 +3745,7 @@ void VulkanRenderer::bufferCache_copyStreamoutToMainBuffer(uint32 srcOffset, uin
|
||||||
|
|
||||||
void VulkanRenderer::AppendOverlayDebugInfo()
|
void VulkanRenderer::AppendOverlayDebugInfo()
|
||||||
{
|
{
|
||||||
ImGui::Text("--- Vulkan info ---");
|
ImGui::Text("--- Vulkan debug info ---");
|
||||||
ImGui::Text("GfxPipelines %u", performanceMonitor.vk.numGraphicPipelines.get());
|
ImGui::Text("GfxPipelines %u", performanceMonitor.vk.numGraphicPipelines.get());
|
||||||
ImGui::Text("DescriptorSets %u", performanceMonitor.vk.numDescriptorSets.get());
|
ImGui::Text("DescriptorSets %u", performanceMonitor.vk.numDescriptorSets.get());
|
||||||
ImGui::Text("DS ImgSamplers %u", performanceMonitor.vk.numDescriptorSamplerTextures.get());
|
ImGui::Text("DS ImgSamplers %u", performanceMonitor.vk.numDescriptorSamplerTextures.get());
|
||||||
|
@ -3678,6 +3753,7 @@ void VulkanRenderer::AppendOverlayDebugInfo()
|
||||||
ImGui::Text("DS StorageBuf %u", performanceMonitor.vk.numDescriptorStorageBuffers.get());
|
ImGui::Text("DS StorageBuf %u", performanceMonitor.vk.numDescriptorStorageBuffers.get());
|
||||||
ImGui::Text("Images %u", performanceMonitor.vk.numImages.get());
|
ImGui::Text("Images %u", performanceMonitor.vk.numImages.get());
|
||||||
ImGui::Text("ImageView %u", performanceMonitor.vk.numImageViews.get());
|
ImGui::Text("ImageView %u", performanceMonitor.vk.numImageViews.get());
|
||||||
|
ImGui::Text("ImageSampler %u", performanceMonitor.vk.numSamplers.get());
|
||||||
ImGui::Text("RenderPass %u", performanceMonitor.vk.numRenderPass.get());
|
ImGui::Text("RenderPass %u", performanceMonitor.vk.numRenderPass.get());
|
||||||
ImGui::Text("Framebuffer %u", performanceMonitor.vk.numFramebuffer.get());
|
ImGui::Text("Framebuffer %u", performanceMonitor.vk.numFramebuffer.get());
|
||||||
m_spinlockDestructionQueue.lock();
|
m_spinlockDestructionQueue.lock();
|
||||||
|
@ -3687,7 +3763,7 @@ void VulkanRenderer::AppendOverlayDebugInfo()
|
||||||
|
|
||||||
ImGui::Text("BeginRP/f %u", performanceMonitor.vk.numBeginRenderpassPerFrame.get());
|
ImGui::Text("BeginRP/f %u", performanceMonitor.vk.numBeginRenderpassPerFrame.get());
|
||||||
ImGui::Text("Barriers/f %u", performanceMonitor.vk.numDrawBarriersPerFrame.get());
|
ImGui::Text("Barriers/f %u", performanceMonitor.vk.numDrawBarriersPerFrame.get());
|
||||||
ImGui::Text("--- Cache info ---");
|
ImGui::Text("--- Cache debug info ---");
|
||||||
|
|
||||||
uint32 bufferCacheHeapSize = 0;
|
uint32 bufferCacheHeapSize = 0;
|
||||||
uint32 bufferCacheAllocationSize = 0;
|
uint32 bufferCacheAllocationSize = 0;
|
||||||
|
@ -3707,7 +3783,7 @@ void VulkanRenderer::AppendOverlayDebugInfo()
|
||||||
ImGui::SameLine(60.0f);
|
ImGui::SameLine(60.0f);
|
||||||
ImGui::Text("%06uKB / %06uKB Buffers: %u", ((uint32)(totalSize - freeSize) + 1023) / 1024, ((uint32)totalSize + 1023) / 1024, (uint32)numBuffers);
|
ImGui::Text("%06uKB / %06uKB Buffers: %u", ((uint32)(totalSize - freeSize) + 1023) / 1024, ((uint32)totalSize + 1023) / 1024, (uint32)numBuffers);
|
||||||
|
|
||||||
memoryManager->getIndexAllocator().GetStats(numBuffers, totalSize, freeSize);
|
memoryManager->GetIndexAllocator().GetStats(numBuffers, totalSize, freeSize);
|
||||||
ImGui::Text("Index");
|
ImGui::Text("Index");
|
||||||
ImGui::SameLine(60.0f);
|
ImGui::SameLine(60.0f);
|
||||||
ImGui::Text("%06uKB / %06uKB Buffers: %u", ((uint32)(totalSize - freeSize) + 1023) / 1024, ((uint32)totalSize + 1023) / 1024, (uint32)numBuffers);
|
ImGui::Text("%06uKB / %06uKB Buffers: %u", ((uint32)(totalSize - freeSize) + 1023) / 1024, ((uint32)totalSize + 1023) / 1024, (uint32)numBuffers);
|
||||||
|
@ -3723,7 +3799,7 @@ void VKRDestructibleObject::flagForCurrentCommandBuffer()
|
||||||
|
|
||||||
bool VKRDestructibleObject::canDestroy()
|
bool VKRDestructibleObject::canDestroy()
|
||||||
{
|
{
|
||||||
if (refCount > 0)
|
if (m_refCount > 0)
|
||||||
return false;
|
return false;
|
||||||
return VulkanRenderer::GetInstance()->HasCommandBufferFinished(m_lastCmdBufferId);
|
return VulkanRenderer::GetInstance()->HasCommandBufferFinished(m_lastCmdBufferId);
|
||||||
}
|
}
|
||||||
|
@ -3764,6 +3840,111 @@ VKRObjectTextureView::~VKRObjectTextureView()
|
||||||
performanceMonitor.vk.numImageViews.decrement();
|
performanceMonitor.vk.numImageViews.decrement();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static uint64 CalcHashSamplerCreateInfo(const VkSamplerCreateInfo& info)
|
||||||
|
{
|
||||||
|
uint64 h = 0xcbf29ce484222325ULL;
|
||||||
|
auto fnvHashCombine = [](uint64_t &h, auto val) {
|
||||||
|
using T = decltype(val);
|
||||||
|
static_assert(sizeof(T) <= 8);
|
||||||
|
uint64_t val64 = 0;
|
||||||
|
std::memcpy(&val64, &val, sizeof(val));
|
||||||
|
h ^= val64;
|
||||||
|
h *= 0x100000001b3ULL;
|
||||||
|
};
|
||||||
|
cemu_assert_debug(info.sType == VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO);
|
||||||
|
fnvHashCombine(h, info.flags);
|
||||||
|
fnvHashCombine(h, info.magFilter);
|
||||||
|
fnvHashCombine(h, info.minFilter);
|
||||||
|
fnvHashCombine(h, info.mipmapMode);
|
||||||
|
fnvHashCombine(h, info.addressModeU);
|
||||||
|
fnvHashCombine(h, info.addressModeV);
|
||||||
|
fnvHashCombine(h, info.addressModeW);
|
||||||
|
fnvHashCombine(h, info.mipLodBias);
|
||||||
|
fnvHashCombine(h, info.anisotropyEnable);
|
||||||
|
if(info.anisotropyEnable == VK_TRUE)
|
||||||
|
fnvHashCombine(h, info.maxAnisotropy);
|
||||||
|
fnvHashCombine(h, info.compareEnable);
|
||||||
|
if(info.compareEnable == VK_TRUE)
|
||||||
|
fnvHashCombine(h, info.compareOp);
|
||||||
|
fnvHashCombine(h, info.minLod);
|
||||||
|
fnvHashCombine(h, info.maxLod);
|
||||||
|
fnvHashCombine(h, info.borderColor);
|
||||||
|
fnvHashCombine(h, info.unnormalizedCoordinates);
|
||||||
|
// handle custom border color
|
||||||
|
VkBaseOutStructure* ext = (VkBaseOutStructure*)info.pNext;
|
||||||
|
while(ext)
|
||||||
|
{
|
||||||
|
if(ext->sType == VK_STRUCTURE_TYPE_SAMPLER_CUSTOM_BORDER_COLOR_CREATE_INFO_EXT)
|
||||||
|
{
|
||||||
|
auto* extInfo = (VkSamplerCustomBorderColorCreateInfoEXT*)ext;
|
||||||
|
fnvHashCombine(h, extInfo->customBorderColor.uint32[0]);
|
||||||
|
fnvHashCombine(h, extInfo->customBorderColor.uint32[1]);
|
||||||
|
fnvHashCombine(h, extInfo->customBorderColor.uint32[2]);
|
||||||
|
fnvHashCombine(h, extInfo->customBorderColor.uint32[3]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cemu_assert_unimplemented();
|
||||||
|
}
|
||||||
|
ext = ext->pNext;
|
||||||
|
}
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unordered_map<uint64, VKRObjectSampler*> VKRObjectSampler::s_samplerCache;
|
||||||
|
|
||||||
|
VKRObjectSampler::VKRObjectSampler(VkSamplerCreateInfo* samplerInfo)
|
||||||
|
{
|
||||||
|
auto* vulkanRenderer = VulkanRenderer::GetInstance();
|
||||||
|
if (vkCreateSampler(vulkanRenderer->GetLogicalDevice(), samplerInfo, nullptr, &m_sampler) != VK_SUCCESS)
|
||||||
|
vulkanRenderer->UnrecoverableError("Failed to create texture sampler");
|
||||||
|
performanceMonitor.vk.numSamplers.increment();
|
||||||
|
m_hash = CalcHashSamplerCreateInfo(*samplerInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
VKRObjectSampler::~VKRObjectSampler()
|
||||||
|
{
|
||||||
|
vkDestroySampler(VulkanRenderer::GetInstance()->GetLogicalDevice(), m_sampler, nullptr);
|
||||||
|
performanceMonitor.vk.numSamplers.decrement();
|
||||||
|
// remove from cache
|
||||||
|
auto it = s_samplerCache.find(m_hash);
|
||||||
|
if(it != s_samplerCache.end())
|
||||||
|
s_samplerCache.erase(it);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VKRObjectSampler::RefCountReachedZero()
|
||||||
|
{
|
||||||
|
VulkanRenderer::GetInstance()->ReleaseDestructibleObject(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
VKRObjectSampler* VKRObjectSampler::GetOrCreateSampler(VkSamplerCreateInfo* samplerInfo)
|
||||||
|
{
|
||||||
|
auto* vulkanRenderer = VulkanRenderer::GetInstance();
|
||||||
|
uint64 hash = CalcHashSamplerCreateInfo(*samplerInfo);
|
||||||
|
auto it = s_samplerCache.find(hash);
|
||||||
|
if (it != s_samplerCache.end())
|
||||||
|
{
|
||||||
|
auto* sampler = it->second;
|
||||||
|
return sampler;
|
||||||
|
}
|
||||||
|
auto* sampler = new VKRObjectSampler(samplerInfo);
|
||||||
|
s_samplerCache[hash] = sampler;
|
||||||
|
return sampler;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VKRObjectSampler::DestroyCache()
|
||||||
|
{
|
||||||
|
// assuming all other objects which depend on vkSampler are destroyed, this cache should also have been emptied already
|
||||||
|
// but just to be sure lets still clear the cache
|
||||||
|
cemu_assert_debug(s_samplerCache.empty());
|
||||||
|
for(auto& sampler : s_samplerCache)
|
||||||
|
{
|
||||||
|
cemu_assert_debug(sampler.second->m_refCount == 0);
|
||||||
|
delete sampler.second;
|
||||||
|
}
|
||||||
|
s_samplerCache.clear();
|
||||||
|
}
|
||||||
|
|
||||||
VKRObjectRenderPass::VKRObjectRenderPass(AttachmentInfo_t& attachmentInfo, sint32 colorAttachmentCount)
|
VKRObjectRenderPass::VKRObjectRenderPass(AttachmentInfo_t& attachmentInfo, sint32 colorAttachmentCount)
|
||||||
{
|
{
|
||||||
// generate helper hash for pipeline state
|
// generate helper hash for pipeline state
|
||||||
|
|
|
@ -137,8 +137,8 @@ class VulkanRenderer : public Renderer
|
||||||
public:
|
public:
|
||||||
|
|
||||||
// memory management
|
// memory management
|
||||||
VKRMemoryManager* memoryManager{};
|
std::unique_ptr<VKRMemoryManager> memoryManager;
|
||||||
VKRMemoryManager* GetMemoryManager() const { return memoryManager; };
|
VKRMemoryManager* GetMemoryManager() const { return memoryManager.get(); };
|
||||||
|
|
||||||
VkSupportedFormatInfo_t m_supportedFormatInfo;
|
VkSupportedFormatInfo_t m_supportedFormatInfo;
|
||||||
|
|
||||||
|
@ -328,8 +328,9 @@ public:
|
||||||
|
|
||||||
RendererShader* shader_create(RendererShader::ShaderType type, uint64 baseHash, uint64 auxHash, const std::string& source, bool isGameShader, bool isGfxPackShader) override;
|
RendererShader* shader_create(RendererShader::ShaderType type, uint64 baseHash, uint64 auxHash, const std::string& source, bool isGameShader, bool isGfxPackShader) override;
|
||||||
|
|
||||||
void* indexData_reserveIndexMemory(uint32 size, uint32& offset, uint32& bufferIndex) override;
|
IndexAllocation indexData_reserveIndexMemory(uint32 size) override;
|
||||||
void indexData_uploadIndexMemory(uint32 offset, uint32 size) override;
|
void indexData_releaseIndexMemory(IndexAllocation& allocation) override;
|
||||||
|
void indexData_uploadIndexMemory(IndexAllocation& allocation) override;
|
||||||
|
|
||||||
// externally callable
|
// externally callable
|
||||||
void GetTextureFormatInfoVK(Latte::E_GX2SURFFMT format, bool isDepth, Latte::E_DIM dim, sint32 width, sint32 height, FormatInfoVK* formatInfoOut);
|
void GetTextureFormatInfoVK(Latte::E_GX2SURFFMT format, bool isDepth, Latte::E_DIM dim, sint32 width, sint32 height, FormatInfoVK* formatInfoOut);
|
||||||
|
@ -582,6 +583,8 @@ private:
|
||||||
std::shared_mutex m_pipeline_cache_save_mutex;
|
std::shared_mutex m_pipeline_cache_save_mutex;
|
||||||
std::thread m_pipeline_cache_save_thread;
|
std::thread m_pipeline_cache_save_thread;
|
||||||
VkPipelineCache m_pipeline_cache{ nullptr };
|
VkPipelineCache m_pipeline_cache{ nullptr };
|
||||||
|
std::unordered_map<uint64, VkPipeline> m_backbufferBlitPipelineCache;
|
||||||
|
std::unordered_map<uint64, VkDescriptorSet> m_backbufferBlitDescriptorSetCache;
|
||||||
VkPipelineLayout m_pipelineLayout{nullptr};
|
VkPipelineLayout m_pipelineLayout{nullptr};
|
||||||
VkCommandPool m_commandPool{ nullptr };
|
VkCommandPool m_commandPool{ nullptr };
|
||||||
|
|
||||||
|
@ -591,6 +594,7 @@ private:
|
||||||
bool m_uniformVarBufferMemoryIsCoherent{false};
|
bool m_uniformVarBufferMemoryIsCoherent{false};
|
||||||
uint8* m_uniformVarBufferPtr = nullptr;
|
uint8* m_uniformVarBufferPtr = nullptr;
|
||||||
uint32 m_uniformVarBufferWriteIndex = 0;
|
uint32 m_uniformVarBufferWriteIndex = 0;
|
||||||
|
uint32 m_uniformVarBufferReadIndex = 0;
|
||||||
|
|
||||||
// transform feedback ringbuffer
|
// transform feedback ringbuffer
|
||||||
VkBuffer m_xfbRingBuffer = VK_NULL_HANDLE;
|
VkBuffer m_xfbRingBuffer = VK_NULL_HANDLE;
|
||||||
|
@ -637,6 +641,7 @@ private:
|
||||||
size_t m_commandBufferIndex = 0; // current buffer being filled
|
size_t m_commandBufferIndex = 0; // current buffer being filled
|
||||||
size_t m_commandBufferSyncIndex = 0; // latest buffer that finished execution (updated on submit)
|
size_t m_commandBufferSyncIndex = 0; // latest buffer that finished execution (updated on submit)
|
||||||
size_t m_commandBufferIDOfPrevFrame = 0;
|
size_t m_commandBufferIDOfPrevFrame = 0;
|
||||||
|
std::array<size_t, kCommandBufferPoolSize> m_cmdBufferUniformRingbufIndices {}; // index in the uniform ringbuffer
|
||||||
std::array<VkFence, kCommandBufferPoolSize> m_cmd_buffer_fences;
|
std::array<VkFence, kCommandBufferPoolSize> m_cmd_buffer_fences;
|
||||||
std::array<VkCommandBuffer, kCommandBufferPoolSize> m_commandBuffers;
|
std::array<VkCommandBuffer, kCommandBufferPoolSize> m_commandBuffers;
|
||||||
std::array<VkSemaphore, kCommandBufferPoolSize> m_commandBufferSemaphores;
|
std::array<VkSemaphore, kCommandBufferPoolSize> m_commandBufferSemaphores;
|
||||||
|
@ -659,7 +664,7 @@ private:
|
||||||
uint32 uniformVarBufferOffset[VulkanRendererConst::SHADER_STAGE_INDEX_COUNT];
|
uint32 uniformVarBufferOffset[VulkanRendererConst::SHADER_STAGE_INDEX_COUNT];
|
||||||
struct
|
struct
|
||||||
{
|
{
|
||||||
uint32 unformBufferOffset[LATTE_NUM_MAX_UNIFORM_BUFFERS];
|
uint32 uniformBufferOffset[LATTE_NUM_MAX_UNIFORM_BUFFERS];
|
||||||
}shaderUB[VulkanRendererConst::SHADER_STAGE_INDEX_COUNT];
|
}shaderUB[VulkanRendererConst::SHADER_STAGE_INDEX_COUNT];
|
||||||
}dynamicOffsetInfo{};
|
}dynamicOffsetInfo{};
|
||||||
|
|
||||||
|
@ -857,7 +862,7 @@ private:
|
||||||
memBarrier.pNext = nullptr;
|
memBarrier.pNext = nullptr;
|
||||||
|
|
||||||
VkPipelineStageFlags srcStages = VK_PIPELINE_STAGE_TRANSFER_BIT;
|
VkPipelineStageFlags srcStages = VK_PIPELINE_STAGE_TRANSFER_BIT;
|
||||||
VkPipelineStageFlags dstStages = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
|
VkPipelineStageFlags dstStages = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT;
|
||||||
|
|
||||||
memBarrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT;
|
memBarrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||||
memBarrier.dstAccessMask = 0;
|
memBarrier.dstAccessMask = 0;
|
||||||
|
|
|
@ -357,42 +357,40 @@ PipelineInfo* VulkanRenderer::draw_getOrCreateGraphicsPipeline(uint32 indexCount
|
||||||
return draw_createGraphicsPipeline(indexCount);
|
return draw_createGraphicsPipeline(indexCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
void* VulkanRenderer::indexData_reserveIndexMemory(uint32 size, uint32& offset, uint32& bufferIndex)
|
Renderer::IndexAllocation VulkanRenderer::indexData_reserveIndexMemory(uint32 size)
|
||||||
{
|
{
|
||||||
auto& indexAllocator = this->memoryManager->getIndexAllocator();
|
VKRSynchronizedHeapAllocator::AllocatorReservation* resv = memoryManager->GetIndexAllocator().AllocateBufferMemory(size, 32);
|
||||||
auto resv = indexAllocator.AllocateBufferMemory(size, 32);
|
return { resv->memPtr, resv };
|
||||||
offset = resv.bufferOffset;
|
|
||||||
bufferIndex = resv.bufferIndex;
|
|
||||||
return resv.memPtr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanRenderer::indexData_uploadIndexMemory(uint32 offset, uint32 size)
|
void VulkanRenderer::indexData_releaseIndexMemory(IndexAllocation& allocation)
|
||||||
{
|
{
|
||||||
// does nothing since the index buffer memory is coherent
|
memoryManager->GetIndexAllocator().FreeReservation((VKRSynchronizedHeapAllocator::AllocatorReservation*)allocation.rendererInternal);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanRenderer::indexData_uploadIndexMemory(IndexAllocation& allocation)
|
||||||
|
{
|
||||||
|
memoryManager->GetIndexAllocator().FlushReservation((VKRSynchronizedHeapAllocator::AllocatorReservation*)allocation.rendererInternal);
|
||||||
}
|
}
|
||||||
|
|
||||||
float s_vkUniformData[512 * 4];
|
float s_vkUniformData[512 * 4];
|
||||||
|
|
||||||
void VulkanRenderer::uniformData_updateUniformVars(uint32 shaderStageIndex, LatteDecompilerShader* shader)
|
void VulkanRenderer::uniformData_updateUniformVars(uint32 shaderStageIndex, LatteDecompilerShader* shader)
|
||||||
{
|
{
|
||||||
auto GET_UNIFORM_DATA_PTR = [&](size_t index) { return s_vkUniformData + (index / 4); };
|
auto GET_UNIFORM_DATA_PTR = [](size_t index) { return s_vkUniformData + (index / 4); };
|
||||||
|
|
||||||
sint32 shaderAluConst;
|
sint32 shaderAluConst;
|
||||||
sint32 shaderUniformRegisterOffset;
|
|
||||||
|
|
||||||
switch (shader->shaderType)
|
switch (shader->shaderType)
|
||||||
{
|
{
|
||||||
case LatteConst::ShaderType::Vertex:
|
case LatteConst::ShaderType::Vertex:
|
||||||
shaderAluConst = 0x400;
|
shaderAluConst = 0x400;
|
||||||
shaderUniformRegisterOffset = mmSQ_VTX_UNIFORM_BLOCK_START;
|
|
||||||
break;
|
break;
|
||||||
case LatteConst::ShaderType::Pixel:
|
case LatteConst::ShaderType::Pixel:
|
||||||
shaderAluConst = 0;
|
shaderAluConst = 0;
|
||||||
shaderUniformRegisterOffset = mmSQ_PS_UNIFORM_BLOCK_START;
|
|
||||||
break;
|
break;
|
||||||
case LatteConst::ShaderType::Geometry:
|
case LatteConst::ShaderType::Geometry:
|
||||||
shaderAluConst = 0; // geometry shader has no ALU const
|
shaderAluConst = 0; // geometry shader has no ALU const
|
||||||
shaderUniformRegisterOffset = mmSQ_GS_UNIFORM_BLOCK_START;
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
UNREACHABLE;
|
UNREACHABLE;
|
||||||
|
@ -445,7 +443,7 @@ void VulkanRenderer::uniformData_updateUniformVars(uint32 shaderStageIndex, Latt
|
||||||
}
|
}
|
||||||
if (shader->uniform.loc_verticesPerInstance >= 0)
|
if (shader->uniform.loc_verticesPerInstance >= 0)
|
||||||
{
|
{
|
||||||
*(int*)(s_vkUniformData + ((size_t)shader->uniform.loc_verticesPerInstance / 4)) = m_streamoutState.verticesPerInstance;
|
*(int*)GET_UNIFORM_DATA_PTR(shader->uniform.loc_verticesPerInstance) = m_streamoutState.verticesPerInstance;
|
||||||
for (sint32 b = 0; b < LATTE_NUM_STREAMOUT_BUFFER; b++)
|
for (sint32 b = 0; b < LATTE_NUM_STREAMOUT_BUFFER; b++)
|
||||||
{
|
{
|
||||||
if (shader->uniform.loc_streamoutBufferBase[b] >= 0)
|
if (shader->uniform.loc_streamoutBufferBase[b] >= 0)
|
||||||
|
@ -455,26 +453,63 @@ void VulkanRenderer::uniformData_updateUniformVars(uint32 shaderStageIndex, Latt
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// upload
|
// upload
|
||||||
if ((m_uniformVarBufferWriteIndex + shader->uniform.uniformRangeSize + 1024) > UNIFORMVAR_RINGBUFFER_SIZE)
|
const uint32 bufferAlignmentM1 = std::max(m_featureControl.limits.minUniformBufferOffsetAlignment, m_featureControl.limits.nonCoherentAtomSize) - 1;
|
||||||
|
const uint32 uniformSize = (shader->uniform.uniformRangeSize + bufferAlignmentM1) & ~bufferAlignmentM1;
|
||||||
|
|
||||||
|
auto waitWhileCondition = [&](std::function<bool()> condition) {
|
||||||
|
while (condition())
|
||||||
|
{
|
||||||
|
if (m_commandBufferSyncIndex == m_commandBufferIndex)
|
||||||
|
{
|
||||||
|
if (m_cmdBufferUniformRingbufIndices[m_commandBufferIndex] != m_uniformVarBufferReadIndex)
|
||||||
|
{
|
||||||
|
draw_endRenderPass();
|
||||||
|
SubmitCommandBuffer();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// submitting work would not change readIndex, so there's no way for conditions based on it to change
|
||||||
|
cemuLog_log(LogType::Force, "draw call overflowed and corrupted uniform ringbuffer. expect visual corruption");
|
||||||
|
cemu_assert_suspicious();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
WaitForNextFinishedCommandBuffer();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// wrap around if it doesnt fit consecutively
|
||||||
|
if (m_uniformVarBufferWriteIndex + uniformSize > UNIFORMVAR_RINGBUFFER_SIZE)
|
||||||
{
|
{
|
||||||
|
waitWhileCondition([&]() {
|
||||||
|
return m_uniformVarBufferReadIndex > m_uniformVarBufferWriteIndex || m_uniformVarBufferReadIndex == 0;
|
||||||
|
});
|
||||||
m_uniformVarBufferWriteIndex = 0;
|
m_uniformVarBufferWriteIndex = 0;
|
||||||
}
|
}
|
||||||
uint32 bufferAlignmentM1 = std::max(m_featureControl.limits.minUniformBufferOffsetAlignment, m_featureControl.limits.nonCoherentAtomSize) - 1;
|
|
||||||
|
auto ringBufRemaining = [&]() {
|
||||||
|
ssize_t ringBufferUsedBytes = (ssize_t)m_uniformVarBufferWriteIndex - m_uniformVarBufferReadIndex;
|
||||||
|
if (ringBufferUsedBytes < 0)
|
||||||
|
ringBufferUsedBytes += UNIFORMVAR_RINGBUFFER_SIZE;
|
||||||
|
return UNIFORMVAR_RINGBUFFER_SIZE - 1 - ringBufferUsedBytes;
|
||||||
|
};
|
||||||
|
waitWhileCondition([&]() {
|
||||||
|
return ringBufRemaining() < uniformSize;
|
||||||
|
});
|
||||||
|
|
||||||
const uint32 uniformOffset = m_uniformVarBufferWriteIndex;
|
const uint32 uniformOffset = m_uniformVarBufferWriteIndex;
|
||||||
memcpy(m_uniformVarBufferPtr + uniformOffset, s_vkUniformData, shader->uniform.uniformRangeSize);
|
memcpy(m_uniformVarBufferPtr + uniformOffset, s_vkUniformData, shader->uniform.uniformRangeSize);
|
||||||
m_uniformVarBufferWriteIndex += shader->uniform.uniformRangeSize;
|
m_uniformVarBufferWriteIndex += uniformSize;
|
||||||
m_uniformVarBufferWriteIndex = (m_uniformVarBufferWriteIndex + bufferAlignmentM1) & ~bufferAlignmentM1;
|
|
||||||
// update dynamic offset
|
// update dynamic offset
|
||||||
dynamicOffsetInfo.uniformVarBufferOffset[shaderStageIndex] = uniformOffset;
|
dynamicOffsetInfo.uniformVarBufferOffset[shaderStageIndex] = uniformOffset;
|
||||||
// flush if not coherent
|
// flush if not coherent
|
||||||
if (!m_uniformVarBufferMemoryIsCoherent)
|
if (!m_uniformVarBufferMemoryIsCoherent)
|
||||||
{
|
{
|
||||||
uint32 nonCoherentAtomSizeM1 = m_featureControl.limits.nonCoherentAtomSize - 1;
|
|
||||||
VkMappedMemoryRange flushedRange{};
|
VkMappedMemoryRange flushedRange{};
|
||||||
flushedRange.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE;
|
flushedRange.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE;
|
||||||
flushedRange.memory = m_uniformVarBufferMemory;
|
flushedRange.memory = m_uniformVarBufferMemory;
|
||||||
flushedRange.offset = uniformOffset;
|
flushedRange.offset = uniformOffset;
|
||||||
flushedRange.size = (shader->uniform.uniformRangeSize + nonCoherentAtomSizeM1) & ~nonCoherentAtomSizeM1;
|
flushedRange.size = uniformSize;
|
||||||
vkFlushMappedMemoryRanges(m_logicalDevice, 1, &flushedRange);
|
vkFlushMappedMemoryRanges(m_logicalDevice, 1, &flushedRange);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -494,7 +529,7 @@ void VulkanRenderer::draw_prepareDynamicOffsetsForDescriptorSet(uint32 shaderSta
|
||||||
{
|
{
|
||||||
for (auto& itr : pipeline_info->dynamicOffsetInfo.list_uniformBuffers[shaderStageIndex])
|
for (auto& itr : pipeline_info->dynamicOffsetInfo.list_uniformBuffers[shaderStageIndex])
|
||||||
{
|
{
|
||||||
dynamicOffsets[numDynOffsets] = dynamicOffsetInfo.shaderUB[shaderStageIndex].unformBufferOffset[itr];
|
dynamicOffsets[numDynOffsets] = dynamicOffsetInfo.shaderUB[shaderStageIndex].uniformBufferOffset[itr];
|
||||||
numDynOffsets++;
|
numDynOffsets++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -694,7 +729,6 @@ VkDescriptorSetInfo* VulkanRenderer::draw_getOrCreateDescriptorSet(PipelineInfo*
|
||||||
|
|
||||||
VkSamplerCustomBorderColorCreateInfoEXT samplerCustomBorderColor{};
|
VkSamplerCustomBorderColorCreateInfoEXT samplerCustomBorderColor{};
|
||||||
|
|
||||||
VkSampler sampler;
|
|
||||||
VkSamplerCreateInfo samplerInfo{};
|
VkSamplerCreateInfo samplerInfo{};
|
||||||
samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
|
samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
|
||||||
|
|
||||||
|
@ -867,9 +901,9 @@ VkDescriptorSetInfo* VulkanRenderer::draw_getOrCreateDescriptorSet(PipelineInfo*
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (vkCreateSampler(m_logicalDevice, &samplerInfo, nullptr, &sampler) != VK_SUCCESS)
|
VKRObjectSampler* samplerObj = VKRObjectSampler::GetOrCreateSampler(&samplerInfo);
|
||||||
UnrecoverableError("Failed to create texture sampler");
|
vkObjDS->addRef(samplerObj);
|
||||||
info.sampler = sampler;
|
info.sampler = samplerObj->GetSampler();
|
||||||
textureArray.emplace_back(info);
|
textureArray.emplace_back(info);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1130,28 +1164,17 @@ void VulkanRenderer::draw_prepareDescriptorSets(PipelineInfo* pipeline_info, VkD
|
||||||
const auto geometryShader = LatteSHRC_GetActiveGeometryShader();
|
const auto geometryShader = LatteSHRC_GetActiveGeometryShader();
|
||||||
const auto pixelShader = LatteSHRC_GetActivePixelShader();
|
const auto pixelShader = LatteSHRC_GetActivePixelShader();
|
||||||
|
|
||||||
|
auto prepareShaderDescriptors = [this, &pipeline_info](LatteDecompilerShader* shader) -> VkDescriptorSetInfo* {
|
||||||
if (vertexShader)
|
if (!shader)
|
||||||
{
|
return nullptr;
|
||||||
auto descriptorSetInfo = draw_getOrCreateDescriptorSet(pipeline_info, vertexShader);
|
auto descriptorSetInfo = draw_getOrCreateDescriptorSet(pipeline_info, shader);
|
||||||
descriptorSetInfo->m_vkObjDescriptorSet->flagForCurrentCommandBuffer();
|
descriptorSetInfo->m_vkObjDescriptorSet->flagForCurrentCommandBuffer();
|
||||||
vertexDS = descriptorSetInfo;
|
return descriptorSetInfo;
|
||||||
}
|
};
|
||||||
|
|
||||||
if (pixelShader)
|
vertexDS = prepareShaderDescriptors(vertexShader);
|
||||||
{
|
pixelDS = prepareShaderDescriptors(pixelShader);
|
||||||
auto descriptorSetInfo = draw_getOrCreateDescriptorSet(pipeline_info, pixelShader);
|
geometryDS = prepareShaderDescriptors(geometryShader);
|
||||||
descriptorSetInfo->m_vkObjDescriptorSet->flagForCurrentCommandBuffer();
|
|
||||||
pixelDS = descriptorSetInfo;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if (geometryShader)
|
|
||||||
{
|
|
||||||
auto descriptorSetInfo = draw_getOrCreateDescriptorSet(pipeline_info, geometryShader);
|
|
||||||
descriptorSetInfo->m_vkObjDescriptorSet->flagForCurrentCommandBuffer();
|
|
||||||
geometryDS = descriptorSetInfo;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanRenderer::draw_updateVkBlendConstants()
|
void VulkanRenderer::draw_updateVkBlendConstants()
|
||||||
|
@ -1357,6 +1380,24 @@ void VulkanRenderer::draw_execute(uint32 baseVertex, uint32 baseInstance, uint32
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// prepare streamout
|
||||||
|
m_streamoutState.verticesPerInstance = count;
|
||||||
|
LatteStreamout_PrepareDrawcall(count, instanceCount);
|
||||||
|
|
||||||
|
// update uniform vars
|
||||||
|
LatteDecompilerShader* vertexShader = LatteSHRC_GetActiveVertexShader();
|
||||||
|
LatteDecompilerShader* pixelShader = LatteSHRC_GetActivePixelShader();
|
||||||
|
LatteDecompilerShader* geometryShader = LatteSHRC_GetActiveGeometryShader();
|
||||||
|
|
||||||
|
if (vertexShader)
|
||||||
|
uniformData_updateUniformVars(VulkanRendererConst::SHADER_STAGE_INDEX_VERTEX, vertexShader);
|
||||||
|
if (pixelShader)
|
||||||
|
uniformData_updateUniformVars(VulkanRendererConst::SHADER_STAGE_INDEX_FRAGMENT, pixelShader);
|
||||||
|
if (geometryShader)
|
||||||
|
uniformData_updateUniformVars(VulkanRendererConst::SHADER_STAGE_INDEX_GEOMETRY, geometryShader);
|
||||||
|
// store where the read pointer should go after command buffer execution
|
||||||
|
m_cmdBufferUniformRingbufIndices[m_commandBufferIndex] = m_uniformVarBufferWriteIndex;
|
||||||
|
|
||||||
// process index data
|
// process index data
|
||||||
const LattePrimitiveMode primitiveMode = static_cast<LattePrimitiveMode>(LatteGPUState.contextRegister[mmVGT_PRIMITIVE_TYPE]);
|
const LattePrimitiveMode primitiveMode = static_cast<LattePrimitiveMode>(LatteGPUState.contextRegister[mmVGT_PRIMITIVE_TYPE]);
|
||||||
|
|
||||||
|
@ -1364,14 +1405,15 @@ void VulkanRenderer::draw_execute(uint32 baseVertex, uint32 baseInstance, uint32
|
||||||
uint32 hostIndexCount;
|
uint32 hostIndexCount;
|
||||||
uint32 indexMin = 0;
|
uint32 indexMin = 0;
|
||||||
uint32 indexMax = 0;
|
uint32 indexMax = 0;
|
||||||
uint32 indexBufferOffset = 0;
|
Renderer::IndexAllocation indexAllocation;
|
||||||
uint32 indexBufferIndex = 0;
|
LatteIndices_decode(memory_getPointerFromVirtualOffset(indexDataMPTR), indexType, count, primitiveMode, indexMin, indexMax, hostIndexType, hostIndexCount, indexAllocation);
|
||||||
LatteIndices_decode(memory_getPointerFromVirtualOffset(indexDataMPTR), indexType, count, primitiveMode, indexMin, indexMax, hostIndexType, hostIndexCount, indexBufferOffset, indexBufferIndex);
|
VKRSynchronizedHeapAllocator::AllocatorReservation* indexReservation = (VKRSynchronizedHeapAllocator::AllocatorReservation*)indexAllocation.rendererInternal;
|
||||||
|
|
||||||
// update index binding
|
// update index binding
|
||||||
bool isPrevIndexData = false;
|
bool isPrevIndexData = false;
|
||||||
if (hostIndexType != INDEX_TYPE::NONE)
|
if (hostIndexType != INDEX_TYPE::NONE)
|
||||||
{
|
{
|
||||||
|
uint32 indexBufferIndex = indexReservation->bufferIndex;
|
||||||
|
uint32 indexBufferOffset = indexReservation->bufferOffset;
|
||||||
if (m_state.activeIndexBufferOffset != indexBufferOffset || m_state.activeIndexBufferIndex != indexBufferIndex || m_state.activeIndexType != hostIndexType)
|
if (m_state.activeIndexBufferOffset != indexBufferOffset || m_state.activeIndexBufferIndex != indexBufferIndex || m_state.activeIndexType != hostIndexType)
|
||||||
{
|
{
|
||||||
m_state.activeIndexType = hostIndexType;
|
m_state.activeIndexType = hostIndexType;
|
||||||
|
@ -1384,7 +1426,7 @@ void VulkanRenderer::draw_execute(uint32 baseVertex, uint32 baseInstance, uint32
|
||||||
vkType = VK_INDEX_TYPE_UINT32;
|
vkType = VK_INDEX_TYPE_UINT32;
|
||||||
else
|
else
|
||||||
cemu_assert(false);
|
cemu_assert(false);
|
||||||
vkCmdBindIndexBuffer(m_state.currentCommandBuffer, memoryManager->getIndexAllocator().GetBufferByIndex(indexBufferIndex), indexBufferOffset, vkType);
|
vkCmdBindIndexBuffer(m_state.currentCommandBuffer, indexReservation->vkBuffer, indexBufferOffset, vkType);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
isPrevIndexData = true;
|
isPrevIndexData = true;
|
||||||
|
@ -1410,22 +1452,6 @@ void VulkanRenderer::draw_execute(uint32 baseVertex, uint32 baseInstance, uint32
|
||||||
LatteBufferCache_Sync(indexMin + baseVertex, indexMax + baseVertex, baseInstance, instanceCount);
|
LatteBufferCache_Sync(indexMin + baseVertex, indexMax + baseVertex, baseInstance, instanceCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
// prepare streamout
|
|
||||||
m_streamoutState.verticesPerInstance = count;
|
|
||||||
LatteStreamout_PrepareDrawcall(count, instanceCount);
|
|
||||||
|
|
||||||
// update uniform vars
|
|
||||||
LatteDecompilerShader* vertexShader = LatteSHRC_GetActiveVertexShader();
|
|
||||||
LatteDecompilerShader* pixelShader = LatteSHRC_GetActivePixelShader();
|
|
||||||
LatteDecompilerShader* geometryShader = LatteSHRC_GetActiveGeometryShader();
|
|
||||||
|
|
||||||
if (vertexShader)
|
|
||||||
uniformData_updateUniformVars(VulkanRendererConst::SHADER_STAGE_INDEX_VERTEX, vertexShader);
|
|
||||||
if (pixelShader)
|
|
||||||
uniformData_updateUniformVars(VulkanRendererConst::SHADER_STAGE_INDEX_FRAGMENT, pixelShader);
|
|
||||||
if (geometryShader)
|
|
||||||
uniformData_updateUniformVars(VulkanRendererConst::SHADER_STAGE_INDEX_GEOMETRY, geometryShader);
|
|
||||||
|
|
||||||
PipelineInfo* pipeline_info;
|
PipelineInfo* pipeline_info;
|
||||||
|
|
||||||
if (!isFirst)
|
if (!isFirst)
|
||||||
|
@ -1613,13 +1639,13 @@ void VulkanRenderer::draw_updateUniformBuffersDirectAccess(LatteDecompilerShader
|
||||||
switch (shaderType)
|
switch (shaderType)
|
||||||
{
|
{
|
||||||
case LatteConst::ShaderType::Vertex:
|
case LatteConst::ShaderType::Vertex:
|
||||||
dynamicOffsetInfo.shaderUB[VulkanRendererConst::SHADER_STAGE_INDEX_VERTEX].unformBufferOffset[bufferIndex] = physicalAddr - m_importedMemBaseAddress;
|
dynamicOffsetInfo.shaderUB[VulkanRendererConst::SHADER_STAGE_INDEX_VERTEX].uniformBufferOffset[bufferIndex] = physicalAddr - m_importedMemBaseAddress;
|
||||||
break;
|
break;
|
||||||
case LatteConst::ShaderType::Geometry:
|
case LatteConst::ShaderType::Geometry:
|
||||||
dynamicOffsetInfo.shaderUB[VulkanRendererConst::SHADER_STAGE_INDEX_GEOMETRY].unformBufferOffset[bufferIndex] = physicalAddr - m_importedMemBaseAddress;
|
dynamicOffsetInfo.shaderUB[VulkanRendererConst::SHADER_STAGE_INDEX_GEOMETRY].uniformBufferOffset[bufferIndex] = physicalAddr - m_importedMemBaseAddress;
|
||||||
break;
|
break;
|
||||||
case LatteConst::ShaderType::Pixel:
|
case LatteConst::ShaderType::Pixel:
|
||||||
dynamicOffsetInfo.shaderUB[VulkanRendererConst::SHADER_STAGE_INDEX_FRAGMENT].unformBufferOffset[bufferIndex] = physicalAddr - m_importedMemBaseAddress;
|
dynamicOffsetInfo.shaderUB[VulkanRendererConst::SHADER_STAGE_INDEX_FRAGMENT].uniformBufferOffset[bufferIndex] = physicalAddr - m_importedMemBaseAddress;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
UNREACHABLE;
|
UNREACHABLE;
|
||||||
|
|
|
@ -76,6 +76,30 @@ struct CopySurfacePipelineInfo
|
||||||
CopySurfacePipelineInfo() = default;
|
CopySurfacePipelineInfo() = default;
|
||||||
CopySurfacePipelineInfo(VkDevice device) : m_device(device) {}
|
CopySurfacePipelineInfo(VkDevice device) : m_device(device) {}
|
||||||
CopySurfacePipelineInfo(const CopySurfacePipelineInfo& info) = delete;
|
CopySurfacePipelineInfo(const CopySurfacePipelineInfo& info) = delete;
|
||||||
|
~CopySurfacePipelineInfo()
|
||||||
|
{
|
||||||
|
auto renderer = VulkanRenderer::GetInstance();
|
||||||
|
renderer->ReleaseDestructibleObject(vkObjRenderPass);
|
||||||
|
renderer->ReleaseDestructibleObject(vkObjPipeline);
|
||||||
|
|
||||||
|
for(auto& i : map_framebuffers)
|
||||||
|
{
|
||||||
|
for(auto& fb : i.second.m_array)
|
||||||
|
{
|
||||||
|
renderer->ReleaseDestructibleObject(fb->vkObjFramebuffer);
|
||||||
|
renderer->ReleaseDestructibleObject(fb->vkObjImageView);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for(auto& i : map_descriptors)
|
||||||
|
{
|
||||||
|
for(auto& descriptor : i.second.m_array)
|
||||||
|
{
|
||||||
|
renderer->ReleaseDestructibleObject(descriptor->vkObjImageView);
|
||||||
|
renderer->ReleaseDestructibleObject(descriptor->vkObjDescriptorSet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
VkDevice m_device = nullptr;
|
VkDevice m_device = nullptr;
|
||||||
|
|
||||||
|
@ -842,5 +866,9 @@ void VulkanRenderer::surfaceCopy_notifyTextureRelease(LatteTextureVk* hostTextur
|
||||||
|
|
||||||
void VulkanRenderer::surfaceCopy_cleanup()
|
void VulkanRenderer::surfaceCopy_cleanup()
|
||||||
{
|
{
|
||||||
// todo - release m_copySurfacePipelineCache etc
|
for(auto& i : m_copySurfacePipelineCache)
|
||||||
|
{
|
||||||
|
delete i.second;
|
||||||
|
}
|
||||||
|
m_copySurfacePipelineCache = {};
|
||||||
}
|
}
|
||||||
|
|
|
@ -469,7 +469,7 @@ namespace iosu
|
||||||
entry->ukn0C = 0;
|
entry->ukn0C = 0;
|
||||||
entry->sizeA = _swapEndianU64(0); // ukn
|
entry->sizeA = _swapEndianU64(0); // ukn
|
||||||
entry->sizeB = _swapEndianU64(dirSize);
|
entry->sizeB = _swapEndianU64(dirSize);
|
||||||
entry->time = _swapEndianU64((coreinit::coreinit_getOSTime() / ESPRESSO_TIMER_CLOCK));
|
entry->time = _swapEndianU64((coreinit::OSGetTime() / ESPRESSO_TIMER_CLOCK));
|
||||||
sprintf(entry->path, "%susr/save/%08x/%08x/meta/", devicePath, (uint32)(titleId >> 32), (uint32)(titleId & 0xFFFFFFFF));
|
sprintf(entry->path, "%susr/save/%08x/%08x/meta/", devicePath, (uint32)(titleId >> 32), (uint32)(titleId & 0xFFFFFFFF));
|
||||||
count++;
|
count++;
|
||||||
}
|
}
|
||||||
|
@ -504,7 +504,7 @@ namespace iosu
|
||||||
entry->ukn0C = 0;
|
entry->ukn0C = 0;
|
||||||
entry->sizeA = _swapEndianU64(0);
|
entry->sizeA = _swapEndianU64(0);
|
||||||
entry->sizeB = _swapEndianU64(0);
|
entry->sizeB = _swapEndianU64(0);
|
||||||
entry->time = _swapEndianU64((coreinit::coreinit_getOSTime() / ESPRESSO_TIMER_CLOCK));
|
entry->time = _swapEndianU64((coreinit::OSGetTime() / ESPRESSO_TIMER_CLOCK));
|
||||||
sprintf(entry->path, "%susr/save/%08x/%08x/meta/", devicePath, (uint32)(titleId >> 32), (uint32)(titleId & 0xFFFFFFFF));
|
sprintf(entry->path, "%susr/save/%08x/%08x/meta/", devicePath, (uint32)(titleId >> 32), (uint32)(titleId & 0xFFFFFFFF));
|
||||||
count++;
|
count++;
|
||||||
}
|
}
|
||||||
|
@ -584,7 +584,7 @@ namespace iosu
|
||||||
|
|
||||||
uint64 _ACPGetTimestamp()
|
uint64 _ACPGetTimestamp()
|
||||||
{
|
{
|
||||||
return coreinit::coreinit_getOSTime() / ESPRESSO_TIMER_CLOCK;
|
return coreinit::OSGetTime() / ESPRESSO_TIMER_CLOCK;
|
||||||
}
|
}
|
||||||
|
|
||||||
nnResult ACPUpdateSaveTimeStamp(uint32 persistentId, uint64 titleId, ACPDeviceType deviceType)
|
nnResult ACPUpdateSaveTimeStamp(uint32 persistentId, uint64 titleId, ACPDeviceType deviceType)
|
||||||
|
|
|
@ -116,7 +116,7 @@ typedef struct
|
||||||
/* +0x34 */ uint32be ukn34;
|
/* +0x34 */ uint32be ukn34;
|
||||||
/* +0x38 */ uint32be ukn38;
|
/* +0x38 */ uint32be ukn38;
|
||||||
/* +0x3C */ uint32be ukn3C;
|
/* +0x3C */ uint32be ukn3C;
|
||||||
/* +0x40 */ uint32be toolkitVersion;
|
/* +0x40 */ uint32be minimumToolkitVersion;
|
||||||
/* +0x44 */ uint32be ukn44;
|
/* +0x44 */ uint32be ukn44;
|
||||||
/* +0x48 */ uint32be ukn48;
|
/* +0x48 */ uint32be ukn48;
|
||||||
/* +0x4C */ uint32be ukn4C;
|
/* +0x4C */ uint32be ukn4C;
|
||||||
|
|
|
@ -179,7 +179,7 @@ namespace camera
|
||||||
*error = CAMStatus::DeviceInUse;
|
*error = CAMStatus::DeviceInUse;
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!initInfo || !initInfo->workMemoryData ||
|
if (!initInfo || !initInfo->workMemoryData ||
|
||||||
!match_any_of(initInfo->forceDisplay, CAMForceDisplay::None, CAMForceDisplay::DRC) ||
|
!match_any_of(initInfo->forceDisplay, CAMForceDisplay::None, CAMForceDisplay::DRC) ||
|
||||||
!match_any_of(initInfo->fps, CAMFps::_15, CAMFps::_30) ||
|
!match_any_of(initInfo->fps, CAMFps::_15, CAMFps::_30) ||
|
||||||
|
|
|
@ -166,7 +166,7 @@ namespace coreinit
|
||||||
void alarm_update()
|
void alarm_update()
|
||||||
{
|
{
|
||||||
cemu_assert_debug(!__OSHasSchedulerLock());
|
cemu_assert_debug(!__OSHasSchedulerLock());
|
||||||
uint64 currentTick = coreinit::coreinit_getOSTime();
|
uint64 currentTick = coreinit::OSGetTime();
|
||||||
if (!OSHostAlarm::quickCheckForAlarm(currentTick))
|
if (!OSHostAlarm::quickCheckForAlarm(currentTick))
|
||||||
return;
|
return;
|
||||||
__OSLockScheduler();
|
__OSLockScheduler();
|
||||||
|
@ -233,7 +233,7 @@ namespace coreinit
|
||||||
if (period == 0)
|
if (period == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
uint64 currentTime = coreinit_getOSTime();
|
uint64 currentTime = OSGetTime();
|
||||||
|
|
||||||
uint64 ticksSinceStart = currentTime - startTime;
|
uint64 ticksSinceStart = currentTime - startTime;
|
||||||
uint64 numPeriods = ticksSinceStart / period;
|
uint64 numPeriods = ticksSinceStart / period;
|
||||||
|
@ -267,7 +267,7 @@ namespace coreinit
|
||||||
void OSSetAlarm(OSAlarm_t* alarm, uint64 delayInTicks, MPTR handlerFunc)
|
void OSSetAlarm(OSAlarm_t* alarm, uint64 delayInTicks, MPTR handlerFunc)
|
||||||
{
|
{
|
||||||
__OSLockScheduler();
|
__OSLockScheduler();
|
||||||
__OSInitiateAlarm(alarm, coreinit_getOSTime() + delayInTicks, 0, handlerFunc, false);
|
__OSInitiateAlarm(alarm, OSGetTime() + delayInTicks, 0, handlerFunc, false);
|
||||||
__OSUnlockScheduler();
|
__OSUnlockScheduler();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -310,7 +310,7 @@ namespace coreinit
|
||||||
while( true )
|
while( true )
|
||||||
{
|
{
|
||||||
OSWaitEvent(g_alarmEvent.GetPtr());
|
OSWaitEvent(g_alarmEvent.GetPtr());
|
||||||
uint64 currentTick = coreinit_getOSTime();
|
uint64 currentTick = OSGetTime();
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
// get alarm to fire
|
// get alarm to fire
|
||||||
|
|
|
@ -156,12 +156,22 @@ namespace coreinit
|
||||||
return ¤tThread->crt.eh_mem_manage;
|
return ¤tThread->crt.eh_mem_manage;
|
||||||
}
|
}
|
||||||
|
|
||||||
void* __gh_errno_ptr()
|
sint32be* __gh_errno_ptr()
|
||||||
{
|
{
|
||||||
OSThread_t* currentThread = coreinit::OSGetCurrentThread();
|
OSThread_t* currentThread = coreinit::OSGetCurrentThread();
|
||||||
return ¤tThread->context.ghs_errno;
|
return ¤tThread->context.ghs_errno;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void __gh_set_errno(sint32 errNo)
|
||||||
|
{
|
||||||
|
*__gh_errno_ptr() = errNo;
|
||||||
|
}
|
||||||
|
|
||||||
|
sint32 __gh_get_errno()
|
||||||
|
{
|
||||||
|
return *__gh_errno_ptr();
|
||||||
|
}
|
||||||
|
|
||||||
void* __get_eh_store_globals()
|
void* __get_eh_store_globals()
|
||||||
{
|
{
|
||||||
OSThread_t* currentThread = coreinit::OSGetCurrentThread();
|
OSThread_t* currentThread = coreinit::OSGetCurrentThread();
|
||||||
|
@ -272,6 +282,8 @@ namespace coreinit
|
||||||
cafeExportRegister("coreinit", __get_eh_globals, LogType::Placeholder);
|
cafeExportRegister("coreinit", __get_eh_globals, LogType::Placeholder);
|
||||||
cafeExportRegister("coreinit", __get_eh_mem_manage, LogType::Placeholder);
|
cafeExportRegister("coreinit", __get_eh_mem_manage, LogType::Placeholder);
|
||||||
cafeExportRegister("coreinit", __gh_errno_ptr, LogType::Placeholder);
|
cafeExportRegister("coreinit", __gh_errno_ptr, LogType::Placeholder);
|
||||||
|
cafeExportRegister("coreinit", __gh_set_errno, LogType::Placeholder);
|
||||||
|
cafeExportRegister("coreinit", __gh_get_errno, LogType::Placeholder);
|
||||||
cafeExportRegister("coreinit", __get_eh_store_globals, LogType::Placeholder);
|
cafeExportRegister("coreinit", __get_eh_store_globals, LogType::Placeholder);
|
||||||
cafeExportRegister("coreinit", __get_eh_store_globals_tdeh, LogType::Placeholder);
|
cafeExportRegister("coreinit", __get_eh_store_globals_tdeh, LogType::Placeholder);
|
||||||
|
|
||||||
|
|
|
@ -4,5 +4,9 @@ namespace coreinit
|
||||||
{
|
{
|
||||||
void PrepareGHSRuntime();
|
void PrepareGHSRuntime();
|
||||||
|
|
||||||
|
sint32be* __gh_errno_ptr();
|
||||||
|
void __gh_set_errno(sint32 errNo);
|
||||||
|
sint32 __gh_get_errno();
|
||||||
|
|
||||||
void InitializeGHS();
|
void InitializeGHS();
|
||||||
};
|
};
|
|
@ -86,11 +86,11 @@ namespace coreinit
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// loop until lock acquired or timeout occurred
|
// loop until lock acquired or timeout occurred
|
||||||
uint64 timeoutValue = coreinit_getTimerTick() + coreinit::EspressoTime::ConvertNsToTimerTicks(timeout);
|
uint64 timeoutValue = OSGetSystemTime() + coreinit::EspressoTime::ConvertNsToTimerTicks(timeout);
|
||||||
while (!spinlock->ownerThread.atomic_compare_exchange(nullptr, currentThread))
|
while (!spinlock->ownerThread.atomic_compare_exchange(nullptr, currentThread))
|
||||||
{
|
{
|
||||||
OSYieldThread();
|
OSYieldThread();
|
||||||
if (coreinit_getTimerTick() >= timeoutValue)
|
if (OSGetSystemTime() >= timeoutValue)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -182,11 +182,11 @@ namespace coreinit
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// loop until lock acquired or timeout occurred
|
// loop until lock acquired or timeout occurred
|
||||||
uint64 timeoutValue = coreinit_getTimerTick() + coreinit::EspressoTime::ConvertNsToTimerTicks(timeout);
|
uint64 timeoutValue = OSGetSystemTime() + coreinit::EspressoTime::ConvertNsToTimerTicks(timeout);
|
||||||
while (!spinlock->ownerThread.atomic_compare_exchange(nullptr, currentThread))
|
while (!spinlock->ownerThread.atomic_compare_exchange(nullptr, currentThread))
|
||||||
{
|
{
|
||||||
OSYieldThread();
|
OSYieldThread();
|
||||||
if (coreinit_getTimerTick() >= timeoutValue)
|
if (OSGetSystemTime() >= timeoutValue)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,8 +73,6 @@ namespace coreinit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64 coreinit_getOSTime();
|
|
||||||
|
|
||||||
bool OSWaitEventWithTimeout(OSEvent* event, uint64 timeout)
|
bool OSWaitEventWithTimeout(OSEvent* event, uint64 timeout)
|
||||||
{
|
{
|
||||||
__OSLockScheduler();
|
__OSLockScheduler();
|
||||||
|
@ -95,14 +93,14 @@ namespace coreinit
|
||||||
|
|
||||||
// workaround for a bad implementation in some Unity games (like Qube Directors Cut, see FEventWiiU::Wait)
|
// workaround for a bad implementation in some Unity games (like Qube Directors Cut, see FEventWiiU::Wait)
|
||||||
// where the the return value of OSWaitEventWithTimeout is ignored and instead the game measures the elapsed time to determine if a timeout occurred
|
// where the the return value of OSWaitEventWithTimeout is ignored and instead the game measures the elapsed time to determine if a timeout occurred
|
||||||
timeout = timeout * 98ULL / 100ULL; // 98% (we want the function to return slightly before the actual timeout)
|
if (timeout < 0x00FFFFFFFFFFFFFFULL)
|
||||||
|
timeout = timeout * 98ULL / 100ULL; // 98% (we want the function to return slightly before the actual timeout)
|
||||||
|
|
||||||
WaitEventWithTimeoutData data;
|
WaitEventWithTimeoutData data;
|
||||||
data.thread = OSGetCurrentThread();
|
data.thread = OSGetCurrentThread();
|
||||||
data.threadQueue = &event->threadQueue;
|
data.threadQueue = &event->threadQueue;
|
||||||
data.hasTimeout = false;
|
data.hasTimeout = false;
|
||||||
|
auto hostAlarm = coreinit::OSHostAlarmCreate(OSGetTime() + coreinit::EspressoTime::ConvertNsToTimerTicks(timeout), 0, _OSWaitEventWithTimeoutHandler, &data);
|
||||||
auto hostAlarm = coreinit::OSHostAlarmCreate(coreinit::coreinit_getOSTime() + coreinit::EspressoTime::ConvertNsToTimerTicks(timeout), 0, _OSWaitEventWithTimeoutHandler, &data);
|
|
||||||
event->threadQueue.queueAndWait(OSGetCurrentThread());
|
event->threadQueue.queueAndWait(OSGetCurrentThread());
|
||||||
coreinit::OSHostAlarmDestroy(hostAlarm);
|
coreinit::OSHostAlarmDestroy(hostAlarm);
|
||||||
if (data.hasTimeout)
|
if (data.hasTimeout)
|
||||||
|
|
|
@ -655,7 +655,7 @@ namespace coreinit
|
||||||
StackAllocator<OSThreadQueue> _threadQueue;
|
StackAllocator<OSThreadQueue> _threadQueue;
|
||||||
OSInitThreadQueue(_threadQueue.GetPointer());
|
OSInitThreadQueue(_threadQueue.GetPointer());
|
||||||
__OSLockScheduler();
|
__OSLockScheduler();
|
||||||
OSHostAlarm* hostAlarm = OSHostAlarmCreate(coreinit_getOSTime() + ticks, 0, _OSSleepTicks_alarmHandler, _threadQueue.GetPointer());
|
OSHostAlarm* hostAlarm = OSHostAlarmCreate(OSGetTime() + ticks, 0, _OSSleepTicks_alarmHandler, _threadQueue.GetPointer());
|
||||||
_threadQueue.GetPointer()->queueAndWait(OSGetCurrentThread());
|
_threadQueue.GetPointer()->queueAndWait(OSGetCurrentThread());
|
||||||
OSHostAlarmDestroy(hostAlarm);
|
OSHostAlarmDestroy(hostAlarm);
|
||||||
__OSUnlockScheduler();
|
__OSUnlockScheduler();
|
||||||
|
@ -1114,13 +1114,13 @@ namespace coreinit
|
||||||
thread->requestFlags = (OSThread_t::REQUEST_FLAG_BIT)(thread->requestFlags & OSThread_t::REQUEST_FLAG_CANCEL); // remove all flags except cancel flag
|
thread->requestFlags = (OSThread_t::REQUEST_FLAG_BIT)(thread->requestFlags & OSThread_t::REQUEST_FLAG_CANCEL); // remove all flags except cancel flag
|
||||||
|
|
||||||
// update total cycles
|
// update total cycles
|
||||||
uint64 remainingCycles = std::min((uint64)hCPU->remainingCycles, (uint64)thread->quantumTicks);
|
sint64 executedCycles = (sint64)thread->quantumTicks - (sint64)hCPU->remainingCycles;
|
||||||
uint64 executedCycles = thread->quantumTicks - remainingCycles;
|
executedCycles = std::max<sint64>(executedCycles, 0);
|
||||||
if (executedCycles < hCPU->skippedCycles)
|
if (executedCycles < (sint64)hCPU->skippedCycles)
|
||||||
executedCycles = 0;
|
executedCycles = 0;
|
||||||
else
|
else
|
||||||
executedCycles -= hCPU->skippedCycles;
|
executedCycles -= hCPU->skippedCycles;
|
||||||
thread->totalCycles += executedCycles;
|
thread->totalCycles += (uint64)executedCycles;
|
||||||
// store context and set current thread to null
|
// store context and set current thread to null
|
||||||
__OSThreadStoreContext(hCPU, thread);
|
__OSThreadStoreContext(hCPU, thread);
|
||||||
OSSetCurrentThread(OSGetCoreId(), nullptr);
|
OSSetCurrentThread(OSGetCoreId(), nullptr);
|
||||||
|
|
|
@ -38,7 +38,7 @@ struct OSContext_t
|
||||||
/* +0x1E0 */ uint64be fp_ps1[32];
|
/* +0x1E0 */ uint64be fp_ps1[32];
|
||||||
/* +0x2E0 */ uint64be coretime[3];
|
/* +0x2E0 */ uint64be coretime[3];
|
||||||
/* +0x2F8 */ uint64be starttime;
|
/* +0x2F8 */ uint64be starttime;
|
||||||
/* +0x300 */ uint32be ghs_errno; // returned by __gh_errno_ptr() (used by socketlasterr)
|
/* +0x300 */ sint32be ghs_errno; // returned by __gh_errno_ptr() (used by socketlasterr)
|
||||||
/* +0x304 */ uint32be affinity;
|
/* +0x304 */ uint32be affinity;
|
||||||
/* +0x308 */ uint32be upmc1;
|
/* +0x308 */ uint32be upmc1;
|
||||||
/* +0x30C */ uint32be upmc2;
|
/* +0x30C */ uint32be upmc2;
|
||||||
|
|
|
@ -3,38 +3,32 @@
|
||||||
|
|
||||||
namespace coreinit
|
namespace coreinit
|
||||||
{
|
{
|
||||||
|
uint64 coreinit_GetMFTB()
|
||||||
uint64 coreinit_getTimerTick()
|
|
||||||
{
|
{
|
||||||
// bus clock is 1/5th of core clock
|
// bus clock is 1/5th of core clock
|
||||||
// timer clock is 1/4th of bus clock
|
// timer clock is 1/4th of bus clock
|
||||||
return PPCInterpreter_getMainCoreCycleCounter() / 20ULL;
|
return PPCInterpreter_getMainCoreCycleCounter() / 20ULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64 coreinit_getOSTime()
|
uint64 OSGetSystemTime()
|
||||||
{
|
{
|
||||||
return coreinit_getTimerTick() + ppcCyclesSince2000TimerClock;
|
return coreinit_GetMFTB();
|
||||||
}
|
|
||||||
|
|
||||||
void export_OSGetTick(PPCInterpreter_t* hCPU)
|
|
||||||
{
|
|
||||||
uint64 osTime = coreinit_getOSTime();
|
|
||||||
osLib_returnFromFunction(hCPU, (uint32)osTime);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64 OSGetTime()
|
uint64 OSGetTime()
|
||||||
{
|
{
|
||||||
return coreinit_getOSTime();
|
return OSGetSystemTime() + ppcCyclesSince2000TimerClock;
|
||||||
}
|
}
|
||||||
|
|
||||||
void export_OSGetSystemTime(PPCInterpreter_t* hCPU)
|
uint32 OSGetSystemTick()
|
||||||
{
|
{
|
||||||
osLib_returnFromFunction64(hCPU, coreinit_getTimerTick());
|
return static_cast<uint32>(coreinit_GetMFTB());
|
||||||
}
|
}
|
||||||
|
|
||||||
void export_OSGetSystemTick(PPCInterpreter_t* hCPU)
|
uint32 OSGetTick()
|
||||||
{
|
{
|
||||||
osLib_returnFromFunction(hCPU, (uint32)coreinit_getTimerTick());
|
uint64 osTime = OSGetTime();
|
||||||
|
return static_cast<uint32>(osTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32 getLeapDaysUntilYear(uint32 year)
|
uint32 getLeapDaysUntilYear(uint32 year)
|
||||||
|
@ -360,14 +354,13 @@ namespace coreinit
|
||||||
void InitializeTimeAndCalendar()
|
void InitializeTimeAndCalendar()
|
||||||
{
|
{
|
||||||
cafeExportRegister("coreinit", OSGetTime, LogType::Placeholder);
|
cafeExportRegister("coreinit", OSGetTime, LogType::Placeholder);
|
||||||
osLib_addFunction("coreinit", "OSGetSystemTime", export_OSGetSystemTime);
|
cafeExportRegister("coreinit", OSGetSystemTime, LogType::Placeholder);
|
||||||
osLib_addFunction("coreinit", "OSGetTick", export_OSGetTick);
|
cafeExportRegister("coreinit", OSGetTick, LogType::Placeholder);
|
||||||
osLib_addFunction("coreinit", "OSGetSystemTick", export_OSGetSystemTick);
|
cafeExportRegister("coreinit", OSGetSystemTick, LogType::Placeholder);
|
||||||
|
|
||||||
cafeExportRegister("coreinit", OSTicksToCalendarTime, LogType::Placeholder);
|
cafeExportRegister("coreinit", OSTicksToCalendarTime, LogType::Placeholder);
|
||||||
cafeExportRegister("coreinit", OSCalendarTimeToTicks, LogType::Placeholder);
|
cafeExportRegister("coreinit", OSCalendarTimeToTicks, LogType::Placeholder);
|
||||||
|
|
||||||
|
|
||||||
//timeTest();
|
//timeTest();
|
||||||
}
|
}
|
||||||
};
|
};
|
|
@ -40,20 +40,21 @@ namespace coreinit
|
||||||
|
|
||||||
inline TimerTicks ConvertNsToTimerTicks(uint64 ns)
|
inline TimerTicks ConvertNsToTimerTicks(uint64 ns)
|
||||||
{
|
{
|
||||||
return ((GetTimerClock() / 31250LL) * ((ns)) / 32000LL);
|
return static_cast<TimerTicks>((static_cast<uint64>(GetTimerClock()) / 31250ULL) * (ns) / 32000ULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline TimerTicks ConvertMsToTimerTicks(uint64 ms)
|
||||||
|
{
|
||||||
|
return static_cast<TimerTicks>(ms * static_cast<uint64>(GetTimerClock()) / 1000ULL);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
void OSTicksToCalendarTime(uint64 ticks, OSCalendarTime_t* calenderStruct);
|
void OSTicksToCalendarTime(uint64 ticks, OSCalendarTime_t* calenderStruct);
|
||||||
|
|
||||||
|
uint64 OSGetSystemTime();
|
||||||
uint64 OSGetTime();
|
uint64 OSGetTime();
|
||||||
|
uint32 OSGetSystemTick();
|
||||||
uint64 coreinit_getOSTime();
|
uint32 OSGetTick();
|
||||||
uint64 coreinit_getTimerTick();
|
|
||||||
|
|
||||||
static uint64 OSGetSystemTime()
|
|
||||||
{
|
|
||||||
return coreinit_getTimerTick();
|
|
||||||
}
|
|
||||||
|
|
||||||
void InitializeTimeAndCalendar();
|
void InitializeTimeAndCalendar();
|
||||||
};
|
};
|
||||||
|
|
|
@ -11,7 +11,7 @@ uint64 dmaeRetiredTimestamp = 0;
|
||||||
|
|
||||||
uint64 dmae_getTimestamp()
|
uint64 dmae_getTimestamp()
|
||||||
{
|
{
|
||||||
return coreinit::coreinit_getTimerTick();
|
return coreinit::OSGetSystemTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
void dmae_setRetiredTimestamp(uint64 timestamp)
|
void dmae_setRetiredTimestamp(uint64 timestamp)
|
||||||
|
|
|
@ -9,32 +9,45 @@
|
||||||
#include <wx/msgdlg.h>
|
#include <wx/msgdlg.h>
|
||||||
|
|
||||||
#include "Cafe/OS/libs/coreinit/coreinit_FS.h"
|
#include "Cafe/OS/libs/coreinit/coreinit_FS.h"
|
||||||
|
#include "Cafe/OS/libs/coreinit/coreinit_Time.h"
|
||||||
#include "Cafe/OS/libs/vpad/vpad.h"
|
#include "Cafe/OS/libs/vpad/vpad.h"
|
||||||
|
|
||||||
namespace nn
|
namespace nn
|
||||||
{
|
{
|
||||||
namespace erreula
|
namespace erreula
|
||||||
{
|
{
|
||||||
#define RESULTTYPE_NONE 0
|
|
||||||
#define RESULTTYPE_FINISH 1
|
|
||||||
#define RESULTTYPE_NEXT 2
|
|
||||||
#define RESULTTYPE_JUMP 3
|
|
||||||
#define RESULTTYPE_PASSWORD 4
|
|
||||||
|
|
||||||
#define ERRORTYPE_CODE 0
|
enum class ErrorDialogType : uint32
|
||||||
#define ERRORTYPE_TEXT 1
|
|
||||||
#define ERRORTYPE_TEXT_ONE_BUTTON 2
|
|
||||||
#define ERRORTYPE_TEXT_TWO_BUTTON 3
|
|
||||||
|
|
||||||
#define ERREULA_STATE_HIDDEN 0
|
|
||||||
#define ERREULA_STATE_APPEARING 1
|
|
||||||
#define ERREULA_STATE_VISIBLE 2
|
|
||||||
#define ERREULA_STATE_DISAPPEARING 3
|
|
||||||
|
|
||||||
struct AppearArg_t
|
|
||||||
{
|
{
|
||||||
AppearArg_t() = default;
|
Code = 0,
|
||||||
AppearArg_t(const AppearArg_t& o)
|
Text = 1,
|
||||||
|
TextOneButton = 2,
|
||||||
|
TextTwoButton = 3
|
||||||
|
};
|
||||||
|
|
||||||
|
static const sint32 FADE_TIME = 80;
|
||||||
|
|
||||||
|
enum class ErrEulaState : uint32
|
||||||
|
{
|
||||||
|
Hidden = 0,
|
||||||
|
Appearing = 1,
|
||||||
|
Visible = 2,
|
||||||
|
Disappearing = 3
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class ResultType : uint32
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
Finish = 1,
|
||||||
|
Next = 2,
|
||||||
|
Jump = 3,
|
||||||
|
Password = 4
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AppearError
|
||||||
|
{
|
||||||
|
AppearError() = default;
|
||||||
|
AppearError(const AppearError& o)
|
||||||
{
|
{
|
||||||
errorType = o.errorType;
|
errorType = o.errorType;
|
||||||
screenType = o.screenType;
|
screenType = o.screenType;
|
||||||
|
@ -49,7 +62,7 @@ namespace erreula
|
||||||
drawCursor = o.drawCursor;
|
drawCursor = o.drawCursor;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32be errorType;
|
betype<ErrorDialogType> errorType;
|
||||||
uint32be screenType;
|
uint32be screenType;
|
||||||
uint32be controllerType;
|
uint32be controllerType;
|
||||||
uint32be holdType;
|
uint32be holdType;
|
||||||
|
@ -63,7 +76,9 @@ namespace erreula
|
||||||
bool drawCursor{};
|
bool drawCursor{};
|
||||||
};
|
};
|
||||||
|
|
||||||
static_assert(sizeof(AppearArg_t) == 0x2C); // maybe larger
|
using AppearArg = AppearError;
|
||||||
|
|
||||||
|
static_assert(sizeof(AppearError) == 0x2C); // maybe larger
|
||||||
|
|
||||||
struct HomeNixSignArg_t
|
struct HomeNixSignArg_t
|
||||||
{
|
{
|
||||||
|
@ -80,6 +95,132 @@ namespace erreula
|
||||||
|
|
||||||
static_assert(sizeof(ControllerInfo_t) == 0x14); // maybe larger
|
static_assert(sizeof(ControllerInfo_t) == 0x14); // maybe larger
|
||||||
|
|
||||||
|
class ErrEulaInstance
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
enum class BUTTON_SELECTION : uint32
|
||||||
|
{
|
||||||
|
NONE = 0xFFFFFFFF,
|
||||||
|
LEFT = 0,
|
||||||
|
RIGHT = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
void Init()
|
||||||
|
{
|
||||||
|
m_buttonSelection = BUTTON_SELECTION::NONE;
|
||||||
|
m_resultCode = -1;
|
||||||
|
m_resultCodeForLeftButton = 0;
|
||||||
|
m_resultCodeForRightButton = 0;
|
||||||
|
SetState(ErrEulaState::Hidden);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DoAppearError(AppearArg* arg)
|
||||||
|
{
|
||||||
|
m_buttonSelection = BUTTON_SELECTION::NONE;
|
||||||
|
m_resultCode = -1;
|
||||||
|
m_resultCodeForLeftButton = -1;
|
||||||
|
m_resultCodeForRightButton = -1;
|
||||||
|
// for standard dialog its 0 and 1?
|
||||||
|
m_resultCodeForLeftButton = 0;
|
||||||
|
m_resultCodeForRightButton = 1;
|
||||||
|
SetState(ErrEulaState::Appearing);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DoDisappearError()
|
||||||
|
{
|
||||||
|
if(m_state != ErrEulaState::Visible)
|
||||||
|
return;
|
||||||
|
SetState(ErrEulaState::Disappearing);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DoCalc()
|
||||||
|
{
|
||||||
|
// appearing and disappearing state will automatically advance after some time
|
||||||
|
if (m_state == ErrEulaState::Appearing || m_state == ErrEulaState::Disappearing)
|
||||||
|
{
|
||||||
|
uint32 elapsedTick = coreinit::OSGetTime() - m_lastStateChange;
|
||||||
|
if (elapsedTick > coreinit::EspressoTime::ConvertMsToTimerTicks(FADE_TIME))
|
||||||
|
{
|
||||||
|
SetState(m_state == ErrEulaState::Appearing ? ErrEulaState::Visible : ErrEulaState::Hidden);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsDecideSelectButtonError() const
|
||||||
|
{
|
||||||
|
return m_buttonSelection != BUTTON_SELECTION::NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsDecideSelectLeftButtonError() const
|
||||||
|
{
|
||||||
|
return m_buttonSelection != BUTTON_SELECTION::LEFT;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsDecideSelectRightButtonError() const
|
||||||
|
{
|
||||||
|
return m_buttonSelection != BUTTON_SELECTION::RIGHT;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetButtonSelection(BUTTON_SELECTION selection)
|
||||||
|
{
|
||||||
|
cemu_assert_debug(m_buttonSelection == BUTTON_SELECTION::NONE);
|
||||||
|
m_buttonSelection = selection;
|
||||||
|
cemu_assert_debug(selection == BUTTON_SELECTION::LEFT || selection == BUTTON_SELECTION::RIGHT);
|
||||||
|
m_resultCode = selection == BUTTON_SELECTION::LEFT ? m_resultCodeForLeftButton : m_resultCodeForRightButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
ErrEulaState GetState() const
|
||||||
|
{
|
||||||
|
return m_state;
|
||||||
|
}
|
||||||
|
|
||||||
|
sint32 GetResultCode() const
|
||||||
|
{
|
||||||
|
return m_resultCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultType GetResultType() const
|
||||||
|
{
|
||||||
|
if(m_resultCode == -1)
|
||||||
|
return ResultType::None;
|
||||||
|
if(m_resultCode < 10)
|
||||||
|
return ResultType::Finish;
|
||||||
|
if(m_resultCode >= 9999)
|
||||||
|
return ResultType::Next;
|
||||||
|
if(m_resultCode == 40)
|
||||||
|
return ResultType::Password;
|
||||||
|
return ResultType::Jump;
|
||||||
|
}
|
||||||
|
|
||||||
|
float GetFadeTransparency() const
|
||||||
|
{
|
||||||
|
if(m_state == ErrEulaState::Appearing || m_state == ErrEulaState::Disappearing)
|
||||||
|
{
|
||||||
|
uint32 elapsedTick = coreinit::OSGetTime() - m_lastStateChange;
|
||||||
|
if(m_state == ErrEulaState::Appearing)
|
||||||
|
return std::min<float>(1.0f, (float)elapsedTick / (float)coreinit::EspressoTime::ConvertMsToTimerTicks(FADE_TIME));
|
||||||
|
else
|
||||||
|
return std::max<float>(0.0f, 1.0f - (float)elapsedTick / (float)coreinit::EspressoTime::ConvertMsToTimerTicks(FADE_TIME));
|
||||||
|
}
|
||||||
|
return 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void SetState(ErrEulaState state)
|
||||||
|
{
|
||||||
|
m_state = state;
|
||||||
|
m_lastStateChange = coreinit::OSGetTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
ErrEulaState m_state;
|
||||||
|
uint32 m_lastStateChange;
|
||||||
|
|
||||||
|
/* +0x30 */ betype<sint32> m_resultCode;
|
||||||
|
/* +0x239C */ betype<BUTTON_SELECTION> m_buttonSelection;
|
||||||
|
/* +0x23A0 */ betype<sint32> m_resultCodeForLeftButton;
|
||||||
|
/* +0x23A4 */ betype<sint32> m_resultCodeForRightButton;
|
||||||
|
};
|
||||||
|
|
||||||
struct ErrEula_t
|
struct ErrEula_t
|
||||||
{
|
{
|
||||||
SysAllocator<coreinit::OSMutex> mutex;
|
SysAllocator<coreinit::OSMutex> mutex;
|
||||||
|
@ -87,17 +228,11 @@ namespace erreula
|
||||||
uint32 langType;
|
uint32 langType;
|
||||||
MEMPTR<coreinit::FSClient_t> fsClient;
|
MEMPTR<coreinit::FSClient_t> fsClient;
|
||||||
|
|
||||||
AppearArg_t currentDialog;
|
std::unique_ptr<ErrEulaInstance> errEulaInstance;
|
||||||
uint32 state;
|
|
||||||
bool buttonPressed;
|
|
||||||
bool rightButtonPressed;
|
|
||||||
|
|
||||||
|
AppearError currentDialog;
|
||||||
bool homeNixSignVisible;
|
bool homeNixSignVisible;
|
||||||
|
|
||||||
std::chrono::steady_clock::time_point stateTimer{};
|
|
||||||
} g_errEula = {};
|
} g_errEula = {};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
std::wstring GetText(uint16be* text)
|
std::wstring GetText(uint16be* text)
|
||||||
{
|
{
|
||||||
|
@ -113,22 +248,61 @@ namespace erreula
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void export_ErrEulaCreate(PPCInterpreter_t* hCPU)
|
void ErrEulaCreate(void* workmem, uint32 regionType, uint32 langType, coreinit::FSClient_t* fsClient)
|
||||||
{
|
{
|
||||||
ppcDefineParamMEMPTR(thisptr, uint8, 0);
|
|
||||||
ppcDefineParamU32(regionType, 1);
|
|
||||||
ppcDefineParamU32(langType, 2);
|
|
||||||
ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 3);
|
|
||||||
|
|
||||||
coreinit::OSLockMutex(&g_errEula.mutex);
|
coreinit::OSLockMutex(&g_errEula.mutex);
|
||||||
|
|
||||||
g_errEula.regionType = regionType;
|
g_errEula.regionType = regionType;
|
||||||
g_errEula.langType = langType;
|
g_errEula.langType = langType;
|
||||||
g_errEula.fsClient = fsClient;
|
g_errEula.fsClient = fsClient;
|
||||||
|
cemu_assert_debug(!g_errEula.errEulaInstance);
|
||||||
|
g_errEula.errEulaInstance = std::make_unique<ErrEulaInstance>();
|
||||||
|
g_errEula.errEulaInstance->Init();
|
||||||
|
|
||||||
coreinit::OSUnlockMutex(&g_errEula.mutex);
|
coreinit::OSUnlockMutex(&g_errEula.mutex);
|
||||||
|
}
|
||||||
|
|
||||||
osLib_returnFromFunction(hCPU, 0);
|
void ErrEulaDestroy()
|
||||||
|
{
|
||||||
|
g_errEula.errEulaInstance.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if any dialog button was selected
|
||||||
|
bool IsDecideSelectButtonError()
|
||||||
|
{
|
||||||
|
if(!g_errEula.errEulaInstance)
|
||||||
|
return false;
|
||||||
|
return g_errEula.errEulaInstance->IsDecideSelectButtonError();
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if left dialog button was selected
|
||||||
|
bool IsDecideSelectLeftButtonError()
|
||||||
|
{
|
||||||
|
if(!g_errEula.errEulaInstance)
|
||||||
|
return false;
|
||||||
|
return g_errEula.errEulaInstance->IsDecideSelectLeftButtonError();
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if right dialog button was selected
|
||||||
|
bool IsDecideSelectRightButtonError()
|
||||||
|
{
|
||||||
|
if(!g_errEula.errEulaInstance)
|
||||||
|
return false;
|
||||||
|
return g_errEula.errEulaInstance->IsDecideSelectRightButtonError();
|
||||||
|
}
|
||||||
|
|
||||||
|
sint32 GetResultCode()
|
||||||
|
{
|
||||||
|
if(!g_errEula.errEulaInstance)
|
||||||
|
return -1;
|
||||||
|
return g_errEula.errEulaInstance->GetResultCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultType GetResultType()
|
||||||
|
{
|
||||||
|
if(!g_errEula.errEulaInstance)
|
||||||
|
return ResultType::None;
|
||||||
|
return g_errEula.errEulaInstance->GetResultType();
|
||||||
}
|
}
|
||||||
|
|
||||||
void export_AppearHomeNixSign(PPCInterpreter_t* hCPU)
|
void export_AppearHomeNixSign(PPCInterpreter_t* hCPU)
|
||||||
|
@ -137,28 +311,24 @@ namespace erreula
|
||||||
osLib_returnFromFunction(hCPU, 0);
|
osLib_returnFromFunction(hCPU, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void export_AppearError(PPCInterpreter_t* hCPU)
|
void ErrEulaAppearError(AppearArg* arg)
|
||||||
{
|
{
|
||||||
ppcDefineParamMEMPTR(arg, AppearArg_t, 0);
|
g_errEula.currentDialog = *arg;
|
||||||
|
if(g_errEula.errEulaInstance)
|
||||||
g_errEula.currentDialog = *arg.GetPtr();
|
g_errEula.errEulaInstance->DoAppearError(arg);
|
||||||
g_errEula.state = ERREULA_STATE_APPEARING;
|
|
||||||
g_errEula.buttonPressed = false;
|
|
||||||
g_errEula.rightButtonPressed = false;
|
|
||||||
|
|
||||||
g_errEula.stateTimer = tick_cached();
|
|
||||||
|
|
||||||
osLib_returnFromFunction(hCPU, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void export_GetStateErrorViewer(PPCInterpreter_t* hCPU)
|
void ErrEulaDisappearError()
|
||||||
{
|
{
|
||||||
osLib_returnFromFunction(hCPU, g_errEula.state);
|
if(g_errEula.errEulaInstance)
|
||||||
|
g_errEula.errEulaInstance->DoDisappearError();
|
||||||
}
|
}
|
||||||
void export_DisappearError(PPCInterpreter_t* hCPU)
|
|
||||||
|
ErrEulaState ErrEulaGetStateErrorViewer()
|
||||||
{
|
{
|
||||||
g_errEula.state = ERREULA_STATE_HIDDEN;
|
if(!g_errEula.errEulaInstance)
|
||||||
osLib_returnFromFunction(hCPU, 0);
|
return ErrEulaState::Hidden;
|
||||||
|
return g_errEula.errEulaInstance->GetState();
|
||||||
}
|
}
|
||||||
|
|
||||||
void export_ChangeLang(PPCInterpreter_t* hCPU)
|
void export_ChangeLang(PPCInterpreter_t* hCPU)
|
||||||
|
@ -168,27 +338,6 @@ namespace erreula
|
||||||
osLib_returnFromFunction(hCPU, 0);
|
osLib_returnFromFunction(hCPU, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void export_IsDecideSelectButtonError(PPCInterpreter_t* hCPU)
|
|
||||||
{
|
|
||||||
if (g_errEula.buttonPressed)
|
|
||||||
cemuLog_logDebug(LogType::Force, "IsDecideSelectButtonError: TRUE");
|
|
||||||
osLib_returnFromFunction(hCPU, g_errEula.buttonPressed);
|
|
||||||
}
|
|
||||||
|
|
||||||
void export_IsDecideSelectLeftButtonError(PPCInterpreter_t* hCPU)
|
|
||||||
{
|
|
||||||
if (g_errEula.buttonPressed)
|
|
||||||
cemuLog_logDebug(LogType::Force, "IsDecideSelectLeftButtonError: TRUE");
|
|
||||||
osLib_returnFromFunction(hCPU, g_errEula.buttonPressed);
|
|
||||||
}
|
|
||||||
|
|
||||||
void export_IsDecideSelectRightButtonError(PPCInterpreter_t* hCPU)
|
|
||||||
{
|
|
||||||
if (g_errEula.rightButtonPressed)
|
|
||||||
cemuLog_logDebug(LogType::Force, "IsDecideSelectRightButtonError: TRUE");
|
|
||||||
osLib_returnFromFunction(hCPU, g_errEula.rightButtonPressed);
|
|
||||||
}
|
|
||||||
|
|
||||||
void export_IsAppearHomeNixSign(PPCInterpreter_t* hCPU)
|
void export_IsAppearHomeNixSign(PPCInterpreter_t* hCPU)
|
||||||
{
|
{
|
||||||
osLib_returnFromFunction(hCPU, g_errEula.homeNixSignVisible);
|
osLib_returnFromFunction(hCPU, g_errEula.homeNixSignVisible);
|
||||||
|
@ -200,61 +349,19 @@ namespace erreula
|
||||||
osLib_returnFromFunction(hCPU, 0);
|
osLib_returnFromFunction(hCPU, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void export_GetResultType(PPCInterpreter_t* hCPU)
|
void ErrEulaCalc(ControllerInfo_t* controllerInfo)
|
||||||
{
|
{
|
||||||
uint32 result = RESULTTYPE_NONE;
|
if(g_errEula.errEulaInstance)
|
||||||
if (g_errEula.buttonPressed || g_errEula.rightButtonPressed)
|
g_errEula.errEulaInstance->DoCalc();
|
||||||
{
|
|
||||||
cemuLog_logDebug(LogType::Force, "GetResultType: FINISH");
|
|
||||||
result = RESULTTYPE_FINISH;
|
|
||||||
}
|
|
||||||
|
|
||||||
osLib_returnFromFunction(hCPU, result);
|
|
||||||
}
|
|
||||||
|
|
||||||
void export_Calc(PPCInterpreter_t* hCPU)
|
|
||||||
{
|
|
||||||
ppcDefineParamMEMPTR(controllerInfo, ControllerInfo_t, 0);
|
|
||||||
// TODO: check controller buttons bla to accept dialog?
|
|
||||||
osLib_returnFromFunction(hCPU, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void render(bool mainWindow)
|
void render(bool mainWindow)
|
||||||
{
|
{
|
||||||
if(g_errEula.state == ERREULA_STATE_HIDDEN)
|
if(!g_errEula.errEulaInstance)
|
||||||
return;
|
return;
|
||||||
|
if(g_errEula.errEulaInstance->GetState() != ErrEulaState::Visible && g_errEula.errEulaInstance->GetState() != ErrEulaState::Appearing && g_errEula.errEulaInstance->GetState() != ErrEulaState::Disappearing)
|
||||||
if(g_errEula.state == ERREULA_STATE_APPEARING)
|
|
||||||
{
|
|
||||||
if(std::chrono::duration_cast<std::chrono::milliseconds>(tick_cached() - g_errEula.stateTimer).count() <= 1000)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
g_errEula.state = ERREULA_STATE_VISIBLE;
|
|
||||||
g_errEula.stateTimer = tick_cached();
|
|
||||||
}
|
|
||||||
/*else if(g_errEula.state == STATE_VISIBLE)
|
|
||||||
{
|
|
||||||
if (std::chrono::duration_cast<std::chrono::milliseconds>(tick_cached() - g_errEula.stateTimer).count() >= 1000)
|
|
||||||
{
|
|
||||||
g_errEula.state = STATE_DISAPPEARING;
|
|
||||||
g_errEula.stateTimer = tick_cached();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
else if(g_errEula.state == ERREULA_STATE_DISAPPEARING)
|
|
||||||
{
|
|
||||||
if (std::chrono::duration_cast<std::chrono::milliseconds>(tick_cached() - g_errEula.stateTimer).count() >= 2000)
|
|
||||||
{
|
|
||||||
g_errEula.state = ERREULA_STATE_HIDDEN;
|
|
||||||
g_errEula.stateTimer = tick_cached();
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
const AppearError& appearArg = g_errEula.currentDialog;
|
||||||
|
|
||||||
const AppearArg_t& appearArg = g_errEula.currentDialog;
|
|
||||||
std::string text;
|
std::string text;
|
||||||
const uint32 errorCode = (uint32)appearArg.errorCode;
|
const uint32 errorCode = (uint32)appearArg.errorCode;
|
||||||
if (errorCode != 0)
|
if (errorCode != 0)
|
||||||
|
@ -276,17 +383,28 @@ namespace erreula
|
||||||
ImGui::SetNextWindowPos(position, ImGuiCond_Always, pivot);
|
ImGui::SetNextWindowPos(position, ImGuiCond_Always, pivot);
|
||||||
ImGui::SetNextWindowBgAlpha(0.9f);
|
ImGui::SetNextWindowBgAlpha(0.9f);
|
||||||
ImGui::PushFont(font);
|
ImGui::PushFont(font);
|
||||||
|
|
||||||
std::string title;
|
std::string title;
|
||||||
if (appearArg.title)
|
if (appearArg.title)
|
||||||
title = boost::nowide::narrow(GetText(appearArg.title.GetPtr()));
|
title = boost::nowide::narrow(GetText(appearArg.title.GetPtr()));
|
||||||
if(title.empty()) // ImGui doesn't allow empty titles, so set one if appearArg.title is not set or empty
|
if (title.empty()) // ImGui doesn't allow empty titles, so set one if appearArg.title is not set or empty
|
||||||
title = "ErrEula";
|
title = "ErrEula";
|
||||||
|
|
||||||
|
float fadeTransparency = 1.0f;
|
||||||
|
if (g_errEula.errEulaInstance->GetState() == ErrEulaState::Appearing || g_errEula.errEulaInstance->GetState() == ErrEulaState::Disappearing)
|
||||||
|
{
|
||||||
|
fadeTransparency = g_errEula.errEulaInstance->GetFadeTransparency();
|
||||||
|
}
|
||||||
|
|
||||||
|
float originalAlpha = ImGui::GetStyle().Alpha;
|
||||||
|
ImGui::GetStyle().Alpha = fadeTransparency;
|
||||||
|
ImGui::SetNextWindowBgAlpha(0.9f * fadeTransparency);
|
||||||
if (ImGui::Begin(title.c_str(), nullptr, kPopupFlags))
|
if (ImGui::Begin(title.c_str(), nullptr, kPopupFlags))
|
||||||
{
|
{
|
||||||
const float startx = ImGui::GetWindowSize().x / 2.0f;
|
const float startx = ImGui::GetWindowSize().x / 2.0f;
|
||||||
|
bool hasLeftButtonPressed = false, hasRightButtonPressed = false;
|
||||||
|
|
||||||
switch ((uint32)appearArg.errorType)
|
switch (appearArg.errorType)
|
||||||
{
|
{
|
||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
|
@ -294,11 +412,10 @@ namespace erreula
|
||||||
ImGui::TextUnformatted(text.c_str(), text.c_str() + text.size());
|
ImGui::TextUnformatted(text.c_str(), text.c_str() + text.size());
|
||||||
ImGui::Spacing();
|
ImGui::Spacing();
|
||||||
ImGui::SetCursorPosX(startx - 50);
|
ImGui::SetCursorPosX(startx - 50);
|
||||||
g_errEula.buttonPressed |= ImGui::Button("OK", {100, 0});
|
hasLeftButtonPressed = ImGui::Button("OK", {100, 0});
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ERRORTYPE_TEXT:
|
case ErrorDialogType::Text:
|
||||||
{
|
{
|
||||||
std::string txtTmp = "Unknown Error";
|
std::string txtTmp = "Unknown Error";
|
||||||
if (appearArg.text)
|
if (appearArg.text)
|
||||||
|
@ -309,10 +426,10 @@ namespace erreula
|
||||||
ImGui::Spacing();
|
ImGui::Spacing();
|
||||||
|
|
||||||
ImGui::SetCursorPosX(startx - 50);
|
ImGui::SetCursorPosX(startx - 50);
|
||||||
g_errEula.buttonPressed |= ImGui::Button("OK", { 100, 0 });
|
hasLeftButtonPressed = ImGui::Button("OK", { 100, 0 });
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ERRORTYPE_TEXT_ONE_BUTTON:
|
case ErrorDialogType::TextOneButton:
|
||||||
{
|
{
|
||||||
std::string txtTmp = "Unknown Error";
|
std::string txtTmp = "Unknown Error";
|
||||||
if (appearArg.text)
|
if (appearArg.text)
|
||||||
|
@ -328,10 +445,10 @@ namespace erreula
|
||||||
|
|
||||||
float width = std::max(100.0f, ImGui::CalcTextSize(button1.c_str()).x + 10.0f);
|
float width = std::max(100.0f, ImGui::CalcTextSize(button1.c_str()).x + 10.0f);
|
||||||
ImGui::SetCursorPosX(startx - (width / 2.0f));
|
ImGui::SetCursorPosX(startx - (width / 2.0f));
|
||||||
g_errEula.buttonPressed |= ImGui::Button(button1.c_str(), { width, 0 });
|
hasLeftButtonPressed = ImGui::Button(button1.c_str(), { width, 0 });
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ERRORTYPE_TEXT_TWO_BUTTON:
|
case ErrorDialogType::TextTwoButton:
|
||||||
{
|
{
|
||||||
std::string txtTmp = "Unknown Error";
|
std::string txtTmp = "Unknown Error";
|
||||||
if (appearArg.text)
|
if (appearArg.text)
|
||||||
|
@ -352,42 +469,52 @@ namespace erreula
|
||||||
float width2 = std::max(100.0f, ImGui::CalcTextSize(button2.c_str()).x + 10.0f);
|
float width2 = std::max(100.0f, ImGui::CalcTextSize(button2.c_str()).x + 10.0f);
|
||||||
ImGui::SetCursorPosX(startx - (width1 / 2.0f) - (width2 / 2.0f) - 10);
|
ImGui::SetCursorPosX(startx - (width1 / 2.0f) - (width2 / 2.0f) - 10);
|
||||||
|
|
||||||
g_errEula.buttonPressed |= ImGui::Button(button1.c_str(), { width1, 0 });
|
hasLeftButtonPressed = ImGui::Button(button1.c_str(), { width1, 0 });
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
|
|
||||||
g_errEula.rightButtonPressed |= ImGui::Button(button2.c_str(), { width2, 0 });
|
hasRightButtonPressed = ImGui::Button(button2.c_str(), { width2, 0 });
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!g_errEula.errEulaInstance->IsDecideSelectButtonError())
|
||||||
|
{
|
||||||
|
if (hasLeftButtonPressed)
|
||||||
|
g_errEula.errEulaInstance->SetButtonSelection(ErrEulaInstance::BUTTON_SELECTION::LEFT);
|
||||||
|
if (hasRightButtonPressed)
|
||||||
|
g_errEula.errEulaInstance->SetButtonSelection(ErrEulaInstance::BUTTON_SELECTION::RIGHT);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ImGui::End();
|
ImGui::End();
|
||||||
ImGui::PopFont();
|
ImGui::PopFont();
|
||||||
|
ImGui::GetStyle().Alpha = originalAlpha;
|
||||||
if(g_errEula.buttonPressed || g_errEula.rightButtonPressed)
|
|
||||||
{
|
|
||||||
g_errEula.state = ERREULA_STATE_DISAPPEARING;
|
|
||||||
g_errEula.stateTimer = tick_cached();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void load()
|
void load()
|
||||||
{
|
{
|
||||||
|
g_errEula.errEulaInstance.reset();
|
||||||
|
|
||||||
OSInitMutexEx(&g_errEula.mutex, nullptr);
|
OSInitMutexEx(&g_errEula.mutex, nullptr);
|
||||||
|
|
||||||
//osLib_addFunction("erreula", "ErrEulaCreate__3RplFPUcQ3_2nn7erreula10", export_ErrEulaCreate); // copy ctor?
|
cafeExportRegisterFunc(ErrEulaCreate, "erreula", "ErrEulaCreate__3RplFPUcQ3_2nn7erreula10RegionTypeQ3_2nn7erreula8LangTypeP8FSClient", LogType::Placeholder);
|
||||||
osLib_addFunction("erreula", "ErrEulaCreate__3RplFPUcQ3_2nn7erreula10RegionTypeQ3_2nn7erreula8LangTypeP8FSClient", export_ErrEulaCreate);
|
cafeExportRegisterFunc(ErrEulaDestroy, "erreula", "ErrEulaDestroy__3RplFv", LogType::Placeholder);
|
||||||
|
|
||||||
|
cafeExportRegisterFunc(IsDecideSelectButtonError, "erreula", "ErrEulaIsDecideSelectButtonError__3RplFv", LogType::Placeholder);
|
||||||
|
cafeExportRegisterFunc(IsDecideSelectLeftButtonError, "erreula", "ErrEulaIsDecideSelectLeftButtonError__3RplFv", LogType::Placeholder);
|
||||||
|
cafeExportRegisterFunc(IsDecideSelectRightButtonError, "erreula", "ErrEulaIsDecideSelectRightButtonError__3RplFv", LogType::Placeholder);
|
||||||
|
|
||||||
|
cafeExportRegisterFunc(GetResultCode, "erreula", "ErrEulaGetResultCode__3RplFv", LogType::Placeholder);
|
||||||
|
cafeExportRegisterFunc(GetResultType, "erreula", "ErrEulaGetResultType__3RplFv", LogType::Placeholder);
|
||||||
|
|
||||||
|
cafeExportRegisterFunc(ErrEulaAppearError, "erreula", "ErrEulaAppearError__3RplFRCQ3_2nn7erreula9AppearArg", LogType::Placeholder);
|
||||||
|
cafeExportRegisterFunc(ErrEulaDisappearError, "erreula", "ErrEulaDisappearError__3RplFv", LogType::Placeholder);
|
||||||
|
cafeExportRegisterFunc(ErrEulaGetStateErrorViewer, "erreula", "ErrEulaGetStateErrorViewer__3RplFv", LogType::Placeholder);
|
||||||
|
|
||||||
|
cafeExportRegisterFunc(ErrEulaCalc, "erreula", "ErrEulaCalc__3RplFRCQ3_2nn7erreula14ControllerInfo", LogType::Placeholder);
|
||||||
|
|
||||||
osLib_addFunction("erreula", "ErrEulaAppearHomeNixSign__3RplFRCQ3_2nn7erreula14HomeNixSignArg", export_AppearHomeNixSign);
|
osLib_addFunction("erreula", "ErrEulaAppearHomeNixSign__3RplFRCQ3_2nn7erreula14HomeNixSignArg", export_AppearHomeNixSign);
|
||||||
osLib_addFunction("erreula", "ErrEulaAppearError__3RplFRCQ3_2nn7erreula9AppearArg", export_AppearError);
|
|
||||||
osLib_addFunction("erreula", "ErrEulaGetStateErrorViewer__3RplFv", export_GetStateErrorViewer);
|
|
||||||
osLib_addFunction("erreula", "ErrEulaChangeLang__3RplFQ3_2nn7erreula8LangType", export_ChangeLang);
|
osLib_addFunction("erreula", "ErrEulaChangeLang__3RplFQ3_2nn7erreula8LangType", export_ChangeLang);
|
||||||
osLib_addFunction("erreula", "ErrEulaIsDecideSelectButtonError__3RplFv", export_IsDecideSelectButtonError);
|
|
||||||
osLib_addFunction("erreula", "ErrEulaCalc__3RplFRCQ3_2nn7erreula14ControllerInfo", export_Calc);
|
|
||||||
osLib_addFunction("erreula", "ErrEulaIsDecideSelectLeftButtonError__3RplFv", export_IsDecideSelectLeftButtonError);
|
|
||||||
osLib_addFunction("erreula", "ErrEulaIsDecideSelectRightButtonError__3RplFv", export_IsDecideSelectRightButtonError);
|
|
||||||
osLib_addFunction("erreula", "ErrEulaIsAppearHomeNixSign__3RplFv", export_IsAppearHomeNixSign);
|
osLib_addFunction("erreula", "ErrEulaIsAppearHomeNixSign__3RplFv", export_IsAppearHomeNixSign);
|
||||||
osLib_addFunction("erreula", "ErrEulaDisappearHomeNixSign__3RplFv", export_DisappearHomeNixSign);
|
osLib_addFunction("erreula", "ErrEulaDisappearHomeNixSign__3RplFv", export_DisappearHomeNixSign);
|
||||||
osLib_addFunction("erreula", "ErrEulaGetResultType__3RplFv", export_GetResultType);
|
|
||||||
osLib_addFunction("erreula", "ErrEulaDisappearError__3RplFv", export_DisappearError);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -322,7 +322,7 @@ uint64 _prevReturnedGPUTime = 0;
|
||||||
|
|
||||||
uint64 Latte_GetTime()
|
uint64 Latte_GetTime()
|
||||||
{
|
{
|
||||||
uint64 gpuTime = coreinit::coreinit_getTimerTick();
|
uint64 gpuTime = coreinit::OSGetSystemTime();
|
||||||
gpuTime *= 20000ULL;
|
gpuTime *= 20000ULL;
|
||||||
if (gpuTime <= _prevReturnedGPUTime)
|
if (gpuTime <= _prevReturnedGPUTime)
|
||||||
gpuTime = _prevReturnedGPUTime + 1; // avoid ever returning identical timestamps
|
gpuTime = _prevReturnedGPUTime + 1; // avoid ever returning identical timestamps
|
||||||
|
|
|
@ -54,7 +54,7 @@ void gx2Export_GX2GetGPUTimeout(PPCInterpreter_t* hCPU)
|
||||||
void gx2Export_GX2SampleTopGPUCycle(PPCInterpreter_t* hCPU)
|
void gx2Export_GX2SampleTopGPUCycle(PPCInterpreter_t* hCPU)
|
||||||
{
|
{
|
||||||
cemuLog_log(LogType::GX2, "GX2SampleTopGPUCycle(0x{:08x})", hCPU->gpr[3]);
|
cemuLog_log(LogType::GX2, "GX2SampleTopGPUCycle(0x{:08x})", hCPU->gpr[3]);
|
||||||
memory_writeU64(hCPU->gpr[3], coreinit::coreinit_getTimerTick());
|
memory_writeU64(hCPU->gpr[3], coreinit::OSGetSystemTime());
|
||||||
osLib_returnFromFunction(hCPU, 0);
|
osLib_returnFromFunction(hCPU, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -87,6 +87,11 @@ namespace GX2
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GX2RSetBufferName(GX2RBuffer* buffer, const char* name)
|
||||||
|
{
|
||||||
|
// no-op in production builds
|
||||||
|
}
|
||||||
|
|
||||||
void* GX2RLockBufferEx(GX2RBuffer* buffer, uint32 resFlags)
|
void* GX2RLockBufferEx(GX2RBuffer* buffer, uint32 resFlags)
|
||||||
{
|
{
|
||||||
return buffer->GetPtr();
|
return buffer->GetPtr();
|
||||||
|
@ -226,6 +231,7 @@ namespace GX2
|
||||||
cafeExportRegister("gx2", GX2RCreateBufferUserMemory, LogType::GX2);
|
cafeExportRegister("gx2", GX2RCreateBufferUserMemory, LogType::GX2);
|
||||||
cafeExportRegister("gx2", GX2RDestroyBufferEx, LogType::GX2);
|
cafeExportRegister("gx2", GX2RDestroyBufferEx, LogType::GX2);
|
||||||
cafeExportRegister("gx2", GX2RBufferExists, LogType::GX2);
|
cafeExportRegister("gx2", GX2RBufferExists, LogType::GX2);
|
||||||
|
cafeExportRegister("gx2", GX2RSetBufferName, LogType::GX2);
|
||||||
cafeExportRegister("gx2", GX2RLockBufferEx, LogType::GX2);
|
cafeExportRegister("gx2", GX2RLockBufferEx, LogType::GX2);
|
||||||
cafeExportRegister("gx2", GX2RUnlockBufferEx, LogType::GX2);
|
cafeExportRegister("gx2", GX2RUnlockBufferEx, LogType::GX2);
|
||||||
cafeExportRegister("gx2", GX2RInvalidateBuffer, LogType::GX2);
|
cafeExportRegister("gx2", GX2RInvalidateBuffer, LogType::GX2);
|
||||||
|
|
|
@ -421,7 +421,7 @@ namespace GX2
|
||||||
{
|
{
|
||||||
if(aluRegisterOffset&0x8000)
|
if(aluRegisterOffset&0x8000)
|
||||||
{
|
{
|
||||||
cemuLog_logDebug(LogType::Force, "_GX2SubmitUniformReg(): Unhandled loop const special case or invalid offset");
|
cemuLog_logDebugOnce(LogType::Force, "_GX2SubmitUniformReg(): Unhandled loop const special case or invalid offset");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if((aluRegisterOffset+sizeInU32s) > 0x400)
|
if((aluRegisterOffset+sizeInU32s) > 0x400)
|
||||||
|
|
|
@ -315,7 +315,7 @@ namespace acp
|
||||||
ppcDefineParamU32BEPtr(timestamp64, 0);
|
ppcDefineParamU32BEPtr(timestamp64, 0);
|
||||||
ppcDefineParamU32BEPtr(ukn, 1); // probably timezone or offset? Could also be a bool for success/failed
|
ppcDefineParamU32BEPtr(ukn, 1); // probably timezone or offset? Could also be a bool for success/failed
|
||||||
|
|
||||||
uint64 t = coreinit::coreinit_getOSTime() + (uint64)((sint64)(ppcCyclesSince2000_UTC - ppcCyclesSince2000) / 20LL);
|
uint64 t = coreinit::OSGetTime() + (uint64)((sint64)(ppcCyclesSince2000_UTC - ppcCyclesSince2000) / 20LL);
|
||||||
|
|
||||||
timestamp64[0] = (uint32)(t >> 32);
|
timestamp64[0] = (uint32)(t >> 32);
|
||||||
timestamp64[1] = (uint32)(t & 0xFFFFFFFF);
|
timestamp64[1] = (uint32)(t & 0xFFFFFFFF);
|
||||||
|
|
|
@ -334,45 +334,63 @@ void nnNfpExport_MountRom(PPCInterpreter_t* hCPU)
|
||||||
osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_NFP, 0));
|
osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_NFP, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef struct
|
namespace nn::nfp
|
||||||
{
|
{
|
||||||
/* +0x00 */ uint8 characterId[3];
|
struct RomInfo
|
||||||
/* +0x03 */ uint8 amiiboSeries;
|
|
||||||
/* +0x04 */ uint16be number;
|
|
||||||
/* +0x06 */ uint8 nfpType;
|
|
||||||
/* +0x07 */ uint8 unused[0x2F];
|
|
||||||
}nfpRomInfo_t;
|
|
||||||
|
|
||||||
static_assert(offsetof(nfpRomInfo_t, amiiboSeries) == 0x3, "nfpRomInfo.seriesId has invalid offset");
|
|
||||||
static_assert(offsetof(nfpRomInfo_t, number) == 0x4, "nfpRomInfo.number has invalid offset");
|
|
||||||
static_assert(offsetof(nfpRomInfo_t, nfpType) == 0x6, "nfpRomInfo.nfpType has invalid offset");
|
|
||||||
static_assert(sizeof(nfpRomInfo_t) == 0x36, "nfpRomInfo_t has invalid size");
|
|
||||||
|
|
||||||
void nnNfpExport_GetNfpRomInfo(PPCInterpreter_t* hCPU)
|
|
||||||
{
|
|
||||||
cemuLog_log(LogType::NN_NFP, "GetNfpRomInfo(0x{:08x})", hCPU->gpr[3]);
|
|
||||||
ppcDefineParamStructPtr(romInfo, nfpRomInfo_t, 0);
|
|
||||||
|
|
||||||
nnNfpLock();
|
|
||||||
if (nfp_data.hasActiveAmiibo == false)
|
|
||||||
{
|
{
|
||||||
nnNfpUnlock();
|
/* +0x00 */ uint8 characterId[3];
|
||||||
osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_NFP, 0)); // todo: Return correct error code
|
/* +0x03 */ uint8 amiiboSeries;
|
||||||
return;
|
/* +0x04 */ uint16be number;
|
||||||
|
/* +0x06 */ uint8 nfpType;
|
||||||
|
/* +0x07 */ uint8 unused[0x2F];
|
||||||
|
};
|
||||||
|
|
||||||
|
static_assert(offsetof(RomInfo, amiiboSeries) == 0x3);
|
||||||
|
static_assert(offsetof(RomInfo, number) == 0x4);
|
||||||
|
static_assert(offsetof(RomInfo, nfpType) == 0x6);
|
||||||
|
static_assert(sizeof(RomInfo) == 0x36);
|
||||||
|
|
||||||
|
using ReadOnlyInfo = RomInfo; // same layout
|
||||||
|
|
||||||
|
void GetRomInfo(RomInfo* romInfo)
|
||||||
|
{
|
||||||
|
cemu_assert_debug(nfp_data.hasActiveAmiibo);
|
||||||
|
memset(romInfo, 0x00, sizeof(RomInfo));
|
||||||
|
romInfo->characterId[0] = nfp_data.amiiboNFCData.amiiboIdentificationBlock.gameAndCharacterId[0];
|
||||||
|
romInfo->characterId[1] = nfp_data.amiiboNFCData.amiiboIdentificationBlock.gameAndCharacterId[1];
|
||||||
|
romInfo->characterId[2] = nfp_data.amiiboNFCData.amiiboIdentificationBlock.characterVariation; // guessed
|
||||||
|
romInfo->amiiboSeries = nfp_data.amiiboNFCData.amiiboIdentificationBlock.amiiboSeries; // guessed
|
||||||
|
romInfo->number = *(uint16be*)nfp_data.amiiboNFCData.amiiboIdentificationBlock.amiiboModelNumber; // guessed
|
||||||
|
romInfo->nfpType = nfp_data.amiiboNFCData.amiiboIdentificationBlock.amiiboFigureType; // guessed
|
||||||
|
memset(romInfo->unused, 0x00, sizeof(romInfo->unused));
|
||||||
}
|
}
|
||||||
memset(romInfo, 0x00, sizeof(nfpRomInfo_t));
|
|
||||||
|
|
||||||
romInfo->characterId[0] = nfp_data.amiiboNFCData.amiiboIdentificationBlock.gameAndCharacterId[0];
|
nnResult GetNfpRomInfo(RomInfo* romInfo)
|
||||||
romInfo->characterId[1] = nfp_data.amiiboNFCData.amiiboIdentificationBlock.gameAndCharacterId[1];
|
{
|
||||||
romInfo->characterId[2] = nfp_data.amiiboNFCData.amiiboIdentificationBlock.characterVariation; // guessed
|
nnNfpLock();
|
||||||
|
if (nfp_data.hasActiveAmiibo == false)
|
||||||
|
{
|
||||||
|
nnNfpUnlock();
|
||||||
|
return BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_NFP, 0); // todo: Return correct error code
|
||||||
|
}
|
||||||
|
GetRomInfo(romInfo);
|
||||||
|
nnNfpUnlock();
|
||||||
|
return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_NFP, 0);
|
||||||
|
}
|
||||||
|
|
||||||
romInfo->amiiboSeries = nfp_data.amiiboNFCData.amiiboIdentificationBlock.amiiboSeries; // guessed
|
nnResult GetNfpReadOnlyInfo(ReadOnlyInfo* readOnlyInfo)
|
||||||
romInfo->number = *(uint16be*)nfp_data.amiiboNFCData.amiiboIdentificationBlock.amiiboModelNumber; // guessed
|
{
|
||||||
romInfo->nfpType = nfp_data.amiiboNFCData.amiiboIdentificationBlock.amiiboFigureType; // guessed
|
nnNfpLock();
|
||||||
|
if (nfp_data.hasActiveAmiibo == false)
|
||||||
nnNfpUnlock();
|
{
|
||||||
osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_NFP, 0));
|
nnNfpUnlock();
|
||||||
}
|
return BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_NFP, 0); // todo: Return correct error code
|
||||||
|
}
|
||||||
|
GetRomInfo(readOnlyInfo);
|
||||||
|
nnNfpUnlock();
|
||||||
|
return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_NFP, 0);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
typedef struct
|
typedef struct
|
||||||
{
|
{
|
||||||
|
@ -880,13 +898,13 @@ void nnNfp_update()
|
||||||
if (amiiboElapsedTouchTime >= 1500)
|
if (amiiboElapsedTouchTime >= 1500)
|
||||||
{
|
{
|
||||||
nnNfp_unloadAmiibo();
|
nnNfp_unloadAmiibo();
|
||||||
|
if (nfp_data.deactivateEvent)
|
||||||
|
{
|
||||||
|
coreinit::OSEvent* osEvent = (coreinit::OSEvent*)memory_getPointerFromVirtualOffset(nfp_data.deactivateEvent);
|
||||||
|
coreinit::OSSignalEvent(osEvent);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
nnNfpUnlock();
|
nnNfpUnlock();
|
||||||
if (nfp_data.deactivateEvent)
|
|
||||||
{
|
|
||||||
coreinit::OSEvent* osEvent = (coreinit::OSEvent*)memory_getPointerFromVirtualOffset(nfp_data.deactivateEvent);
|
|
||||||
coreinit::OSSignalEvent(osEvent);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void nnNfpExport_GetNfpState(PPCInterpreter_t* hCPU)
|
void nnNfpExport_GetNfpState(PPCInterpreter_t* hCPU)
|
||||||
|
@ -1001,8 +1019,6 @@ namespace nn::nfp
|
||||||
osLib_addFunction("nn_nfp", "Mount__Q2_2nn3nfpFv", nnNfpExport_Mount);
|
osLib_addFunction("nn_nfp", "Mount__Q2_2nn3nfpFv", nnNfpExport_Mount);
|
||||||
osLib_addFunction("nn_nfp", "MountRom__Q2_2nn3nfpFv", nnNfpExport_MountRom);
|
osLib_addFunction("nn_nfp", "MountRom__Q2_2nn3nfpFv", nnNfpExport_MountRom);
|
||||||
osLib_addFunction("nn_nfp", "Unmount__Q2_2nn3nfpFv", nnNfpExport_Unmount);
|
osLib_addFunction("nn_nfp", "Unmount__Q2_2nn3nfpFv", nnNfpExport_Unmount);
|
||||||
|
|
||||||
osLib_addFunction("nn_nfp", "GetNfpRomInfo__Q2_2nn3nfpFPQ3_2nn3nfp7RomInfo", nnNfpExport_GetNfpRomInfo);
|
|
||||||
osLib_addFunction("nn_nfp", "GetNfpCommonInfo__Q2_2nn3nfpFPQ3_2nn3nfp10CommonInfo", nnNfpExport_GetNfpCommonInfo);
|
osLib_addFunction("nn_nfp", "GetNfpCommonInfo__Q2_2nn3nfpFPQ3_2nn3nfp10CommonInfo", nnNfpExport_GetNfpCommonInfo);
|
||||||
osLib_addFunction("nn_nfp", "GetNfpRegisterInfo__Q2_2nn3nfpFPQ3_2nn3nfp12RegisterInfo", nnNfpExport_GetNfpRegisterInfo);
|
osLib_addFunction("nn_nfp", "GetNfpRegisterInfo__Q2_2nn3nfpFPQ3_2nn3nfp12RegisterInfo", nnNfpExport_GetNfpRegisterInfo);
|
||||||
|
|
||||||
|
@ -1028,7 +1044,9 @@ namespace nn::nfp
|
||||||
{
|
{
|
||||||
nnNfp_load(); // legacy interface, update these to use cafeExportRegister / cafeExportRegisterFunc
|
nnNfp_load(); // legacy interface, update these to use cafeExportRegister / cafeExportRegisterFunc
|
||||||
|
|
||||||
cafeExportRegisterFunc(nn::nfp::GetErrorCode, "nn_nfp", "GetErrorCode__Q2_2nn3nfpFRCQ2_2nn6Result", LogType::Placeholder);
|
cafeExportRegisterFunc(nn::nfp::GetErrorCode, "nn_nfp", "GetErrorCode__Q2_2nn3nfpFRCQ2_2nn6Result", LogType::NN_NFP);
|
||||||
|
cafeExportRegisterFunc(nn::nfp::GetNfpRomInfo, "nn_nfp", "GetNfpRomInfo__Q2_2nn3nfpFPQ3_2nn3nfp7RomInfo", LogType::NN_NFP);
|
||||||
|
cafeExportRegisterFunc(nn::nfp::GetNfpReadOnlyInfo, "nn_nfp", "GetNfpReadOnlyInfo__Q2_2nn3nfpFPQ3_2nn3nfp12ReadOnlyInfo", LogType::NN_NFP);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,24 +1,12 @@
|
||||||
#include "nsyshid.h"
|
#include "nsyshid.h"
|
||||||
#include "Backend.h"
|
#include "Backend.h"
|
||||||
#include "BackendEmulated.h"
|
#include "BackendEmulated.h"
|
||||||
|
|
||||||
#if NSYSHID_ENABLE_BACKEND_LIBUSB
|
|
||||||
|
|
||||||
#include "BackendLibusb.h"
|
#include "BackendLibusb.h"
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if NSYSHID_ENABLE_BACKEND_WINDOWS_HID
|
|
||||||
|
|
||||||
#include "BackendWindowsHID.h"
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace nsyshid::backend
|
namespace nsyshid::backend
|
||||||
{
|
{
|
||||||
void AttachDefaultBackends()
|
void AttachDefaultBackends()
|
||||||
{
|
{
|
||||||
#if NSYSHID_ENABLE_BACKEND_LIBUSB
|
|
||||||
// add libusb backend
|
// add libusb backend
|
||||||
{
|
{
|
||||||
auto backendLibusb = std::make_shared<backend::libusb::BackendLibusb>();
|
auto backendLibusb = std::make_shared<backend::libusb::BackendLibusb>();
|
||||||
|
@ -27,17 +15,6 @@ namespace nsyshid::backend
|
||||||
AttachBackend(backendLibusb);
|
AttachBackend(backendLibusb);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif // NSYSHID_ENABLE_BACKEND_LIBUSB
|
|
||||||
#if NSYSHID_ENABLE_BACKEND_WINDOWS_HID
|
|
||||||
// add windows hid backend
|
|
||||||
{
|
|
||||||
auto backendWindowsHID = std::make_shared<backend::windows::BackendWindowsHID>();
|
|
||||||
if (backendWindowsHID->IsInitialisedOk())
|
|
||||||
{
|
|
||||||
AttachBackend(backendWindowsHID);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif // NSYSHID_ENABLE_BACKEND_WINDOWS_HID
|
|
||||||
// add emulated backend
|
// add emulated backend
|
||||||
{
|
{
|
||||||
auto backendEmulated = std::make_shared<backend::emulated::BackendEmulated>();
|
auto backendEmulated = std::make_shared<backend::emulated::BackendEmulated>();
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
#ifndef CEMU_NSYSHID_BACKEND_H
|
#pragma once
|
||||||
#define CEMU_NSYSHID_BACKEND_H
|
|
||||||
|
|
||||||
#include <list>
|
#include <list>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
@ -26,9 +25,9 @@ namespace nsyshid
|
||||||
struct TransferCommand
|
struct TransferCommand
|
||||||
{
|
{
|
||||||
uint8* data;
|
uint8* data;
|
||||||
sint32 length;
|
uint32 length;
|
||||||
|
|
||||||
TransferCommand(uint8* data, sint32 length)
|
TransferCommand(uint8* data, uint32 length)
|
||||||
: data(data), length(length)
|
: data(data), length(length)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -39,7 +38,7 @@ namespace nsyshid
|
||||||
{
|
{
|
||||||
sint32 bytesRead;
|
sint32 bytesRead;
|
||||||
|
|
||||||
ReadMessage(uint8* data, sint32 length, sint32 bytesRead)
|
ReadMessage(uint8* data, uint32 length, sint32 bytesRead)
|
||||||
: bytesRead(bytesRead), TransferCommand(data, length)
|
: bytesRead(bytesRead), TransferCommand(data, length)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -50,7 +49,7 @@ namespace nsyshid
|
||||||
{
|
{
|
||||||
sint32 bytesWritten;
|
sint32 bytesWritten;
|
||||||
|
|
||||||
WriteMessage(uint8* data, sint32 length, sint32 bytesWritten)
|
WriteMessage(uint8* data, uint32 length, sint32 bytesWritten)
|
||||||
: bytesWritten(bytesWritten), TransferCommand(data, length)
|
: bytesWritten(bytesWritten), TransferCommand(data, length)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -59,14 +58,11 @@ namespace nsyshid
|
||||||
|
|
||||||
struct ReportMessage final : TransferCommand
|
struct ReportMessage final : TransferCommand
|
||||||
{
|
{
|
||||||
uint8* reportData;
|
uint8 reportType;
|
||||||
sint32 length;
|
uint8 reportId;
|
||||||
uint8* originalData;
|
|
||||||
sint32 originalLength;
|
|
||||||
|
|
||||||
ReportMessage(uint8* reportData, sint32 length, uint8* originalData, sint32 originalLength)
|
ReportMessage(uint8 reportType, uint8 reportId, uint8* data, uint32 length)
|
||||||
: reportData(reportData), length(length), originalData(originalData),
|
: reportType(reportType), reportId(reportId), TransferCommand(data, length)
|
||||||
originalLength(originalLength), TransferCommand(reportData, length)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
using TransferCommand::TransferCommand;
|
using TransferCommand::TransferCommand;
|
||||||
|
@ -77,7 +73,8 @@ namespace nsyshid
|
||||||
static_assert(offsetof(HID_t, ifIndex) == 0xC, "");
|
static_assert(offsetof(HID_t, ifIndex) == 0xC, "");
|
||||||
static_assert(offsetof(HID_t, protocol) == 0xE, "");
|
static_assert(offsetof(HID_t, protocol) == 0xE, "");
|
||||||
|
|
||||||
class Device {
|
class Device
|
||||||
|
{
|
||||||
public:
|
public:
|
||||||
Device() = delete;
|
Device() = delete;
|
||||||
|
|
||||||
|
@ -131,16 +128,21 @@ namespace nsyshid
|
||||||
|
|
||||||
virtual bool GetDescriptor(uint8 descType,
|
virtual bool GetDescriptor(uint8 descType,
|
||||||
uint8 descIndex,
|
uint8 descIndex,
|
||||||
uint8 lang,
|
uint16 lang,
|
||||||
uint8* output,
|
uint8* output,
|
||||||
uint32 outputMaxLength) = 0;
|
uint32 outputMaxLength) = 0;
|
||||||
|
|
||||||
|
virtual bool SetIdle(uint8 ifIndex,
|
||||||
|
uint8 reportId,
|
||||||
|
uint8 duration) = 0;
|
||||||
|
|
||||||
virtual bool SetProtocol(uint8 ifIndex, uint8 protocol) = 0;
|
virtual bool SetProtocol(uint8 ifIndex, uint8 protocol) = 0;
|
||||||
|
|
||||||
virtual bool SetReport(ReportMessage* message) = 0;
|
virtual bool SetReport(ReportMessage* message) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Backend {
|
class Backend
|
||||||
|
{
|
||||||
public:
|
public:
|
||||||
Backend();
|
Backend();
|
||||||
|
|
||||||
|
@ -188,5 +190,3 @@ namespace nsyshid
|
||||||
void AttachDefaultBackends();
|
void AttachDefaultBackends();
|
||||||
}
|
}
|
||||||
} // namespace nsyshid
|
} // namespace nsyshid
|
||||||
|
|
||||||
#endif // CEMU_NSYSHID_BACKEND_H
|
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
#include "BackendEmulated.h"
|
#include "BackendEmulated.h"
|
||||||
|
|
||||||
|
#include "Dimensions.h"
|
||||||
#include "Infinity.h"
|
#include "Infinity.h"
|
||||||
#include "Skylander.h"
|
#include "Skylander.h"
|
||||||
#include "config/CemuConfig.h"
|
#include "config/CemuConfig.h"
|
||||||
|
@ -33,5 +35,12 @@ namespace nsyshid::backend::emulated
|
||||||
auto device = std::make_shared<InfinityBaseDevice>();
|
auto device = std::make_shared<InfinityBaseDevice>();
|
||||||
AttachDevice(device);
|
AttachDevice(device);
|
||||||
}
|
}
|
||||||
|
if (GetConfig().emulated_usb_devices.emulate_dimensions_toypad && !FindDeviceById(0x0E6F, 0x0241))
|
||||||
|
{
|
||||||
|
cemuLog_logDebug(LogType::Force, "Attaching Emulated Toypad");
|
||||||
|
// Add Dimensions Toypad
|
||||||
|
auto device = std::make_shared<DimensionsToypadDevice>();
|
||||||
|
AttachDevice(device);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} // namespace nsyshid::backend::emulated
|
} // namespace nsyshid::backend::emulated
|
|
@ -1,7 +1,5 @@
|
||||||
#include "BackendLibusb.h"
|
#include "BackendLibusb.h"
|
||||||
|
|
||||||
#if NSYSHID_ENABLE_BACKEND_LIBUSB
|
|
||||||
|
|
||||||
namespace nsyshid::backend::libusb
|
namespace nsyshid::backend::libusb
|
||||||
{
|
{
|
||||||
BackendLibusb::BackendLibusb()
|
BackendLibusb::BackendLibusb()
|
||||||
|
@ -15,8 +13,8 @@ namespace nsyshid::backend::libusb
|
||||||
if (m_initReturnCode < 0)
|
if (m_initReturnCode < 0)
|
||||||
{
|
{
|
||||||
m_ctx = nullptr;
|
m_ctx = nullptr;
|
||||||
cemuLog_logDebug(LogType::Force, "nsyshid::BackendLibusb: failed to initialize libusb with return code %i",
|
cemuLog_logDebug(LogType::Force, "nsyshid::BackendLibusb: failed to initialize libusb, return code: {}",
|
||||||
m_initReturnCode);
|
m_initReturnCode);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,8 +33,8 @@ namespace nsyshid::backend::libusb
|
||||||
if (ret != LIBUSB_SUCCESS)
|
if (ret != LIBUSB_SUCCESS)
|
||||||
{
|
{
|
||||||
cemuLog_logDebug(LogType::Force,
|
cemuLog_logDebug(LogType::Force,
|
||||||
"nsyshid::BackendLibusb: failed to register hotplug callback with return code %i",
|
"nsyshid::BackendLibusb: failed to register hotplug callback with return code {}",
|
||||||
ret);
|
ret);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -53,8 +51,8 @@ namespace nsyshid::backend::libusb
|
||||||
if (ret != 0)
|
if (ret != 0)
|
||||||
{
|
{
|
||||||
cemuLog_logDebug(LogType::Force,
|
cemuLog_logDebug(LogType::Force,
|
||||||
"nsyshid::BackendLibusb: hotplug thread: error handling events: {}",
|
"nsyshid::BackendLibusb: hotplug thread: error handling events: {}",
|
||||||
ret);
|
ret);
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
|
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -139,8 +137,8 @@ namespace nsyshid::backend::libusb
|
||||||
case LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED:
|
case LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED:
|
||||||
{
|
{
|
||||||
cemuLog_logDebug(LogType::Force, "nsyshid::BackendLibusb::OnHotplug(): device arrived: {:04x}:{:04x}",
|
cemuLog_logDebug(LogType::Force, "nsyshid::BackendLibusb::OnHotplug(): device arrived: {:04x}:{:04x}",
|
||||||
desc.idVendor,
|
desc.idVendor,
|
||||||
desc.idProduct);
|
desc.idProduct);
|
||||||
auto device = CheckAndCreateDevice(dev);
|
auto device = CheckAndCreateDevice(dev);
|
||||||
if (device != nullptr)
|
if (device != nullptr)
|
||||||
{
|
{
|
||||||
|
@ -167,8 +165,8 @@ namespace nsyshid::backend::libusb
|
||||||
case LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT:
|
case LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT:
|
||||||
{
|
{
|
||||||
cemuLog_logDebug(LogType::Force, "nsyshid::BackendLibusb::OnHotplug(): device left: {:04x}:{:04x}",
|
cemuLog_logDebug(LogType::Force, "nsyshid::BackendLibusb::OnHotplug(): device left: {:04x}:{:04x}",
|
||||||
desc.idVendor,
|
desc.idVendor,
|
||||||
desc.idProduct);
|
desc.idProduct);
|
||||||
auto device = FindLibusbDevice(dev);
|
auto device = FindLibusbDevice(dev);
|
||||||
if (device != nullptr)
|
if (device != nullptr)
|
||||||
{
|
{
|
||||||
|
@ -204,7 +202,7 @@ namespace nsyshid::backend::libusb
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
{
|
{
|
||||||
cemuLog_logDebug(LogType::Force,
|
cemuLog_logDebug(LogType::Force,
|
||||||
"nsyshid::BackendLibusb::FindLibusbDevice(): failed to get device descriptor");
|
"nsyshid::BackendLibusb::FindLibusbDevice(): failed to get device descriptor");
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
uint8 busNumber = libusb_get_bus_number(dev);
|
uint8 busNumber = libusb_get_bus_number(dev);
|
||||||
|
@ -269,12 +267,12 @@ namespace nsyshid::backend::libusb
|
||||||
if (desc.idVendor == 0x0e6f && desc.idProduct == 0x0241)
|
if (desc.idVendor == 0x0e6f && desc.idProduct == 0x0241)
|
||||||
{
|
{
|
||||||
cemuLog_logDebug(LogType::Force,
|
cemuLog_logDebug(LogType::Force,
|
||||||
"nsyshid::BackendLibusb::CheckAndCreateDevice(): lego dimensions portal detected");
|
"nsyshid::BackendLibusb::CheckAndCreateDevice(): lego dimensions portal detected");
|
||||||
}
|
}
|
||||||
auto device = std::make_shared<DeviceLibusb>(m_ctx,
|
auto device = std::make_shared<DeviceLibusb>(m_ctx,
|
||||||
desc.idVendor,
|
desc.idVendor,
|
||||||
desc.idProduct,
|
desc.idProduct,
|
||||||
1,
|
0,
|
||||||
2,
|
2,
|
||||||
0,
|
0,
|
||||||
libusb_get_bus_number(dev),
|
libusb_get_bus_number(dev),
|
||||||
|
@ -415,7 +413,7 @@ namespace nsyshid::backend::libusb
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
{
|
{
|
||||||
cemuLog_log(LogType::Force,
|
cemuLog_log(LogType::Force,
|
||||||
"nsyshid::DeviceLibusb::open(): failed to get device descriptor; return code: %i",
|
"nsyshid::DeviceLibusb::open(): failed to get device descriptor, return code: {}",
|
||||||
ret);
|
ret);
|
||||||
libusb_free_device_list(devices, 1);
|
libusb_free_device_list(devices, 1);
|
||||||
return false;
|
return false;
|
||||||
|
@ -439,19 +437,20 @@ namespace nsyshid::backend::libusb
|
||||||
{
|
{
|
||||||
this->m_libusbHandle = nullptr;
|
this->m_libusbHandle = nullptr;
|
||||||
cemuLog_log(LogType::Force,
|
cemuLog_log(LogType::Force,
|
||||||
"nsyshid::DeviceLibusb::open(): failed to open device; return code: %i",
|
"nsyshid::DeviceLibusb::open(): failed to open device: {}",
|
||||||
ret);
|
libusb_strerror(ret));
|
||||||
libusb_free_device_list(devices, 1);
|
libusb_free_device_list(devices, 1);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
this->m_handleInUseCounter = 0;
|
this->m_handleInUseCounter = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int ret = ClaimAllInterfaces(0);
|
||||||
|
|
||||||
|
if (ret != 0)
|
||||||
{
|
{
|
||||||
int ret = ClaimAllInterfaces(0);
|
cemuLog_log(LogType::Force, "nsyshid::DeviceLibusb::open(): cannot claim interface for config 0");
|
||||||
if (ret != 0)
|
return false;
|
||||||
{
|
|
||||||
cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::open(): cannot claim interface");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -475,7 +474,7 @@ namespace nsyshid::backend::libusb
|
||||||
{
|
{
|
||||||
m_handleInUseCounterDecremented.wait(lock);
|
m_handleInUseCounterDecremented.wait(lock);
|
||||||
}
|
}
|
||||||
libusb_release_interface(handle, 0);
|
ReleaseAllInterfacesForCurrentConfig();
|
||||||
libusb_close(handle);
|
libusb_close(handle);
|
||||||
m_handleInUseCounter = -1;
|
m_handleInUseCounter = -1;
|
||||||
m_handleInUseCounterDecremented.notify_all();
|
m_handleInUseCounterDecremented.notify_all();
|
||||||
|
@ -493,21 +492,26 @@ namespace nsyshid::backend::libusb
|
||||||
if (!handleLock->IsValid())
|
if (!handleLock->IsValid())
|
||||||
{
|
{
|
||||||
cemuLog_logDebug(LogType::Force,
|
cemuLog_logDebug(LogType::Force,
|
||||||
"nsyshid::DeviceLibusb::read(): cannot read from a non-opened device\n");
|
"nsyshid::DeviceLibusb::read(): cannot read from a non-opened device\n");
|
||||||
return ReadResult::Error;
|
return ReadResult::Error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < m_config_descriptors.size(); i++)
|
||||||
|
{
|
||||||
|
ClaimAllInterfaces(i);
|
||||||
|
}
|
||||||
|
|
||||||
const unsigned int timeout = 50;
|
const unsigned int timeout = 50;
|
||||||
int actualLength = 0;
|
int actualLength = 0;
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
ret = libusb_bulk_transfer(handleLock->GetHandle(),
|
ret = libusb_interrupt_transfer(handleLock->GetHandle(),
|
||||||
this->m_libusbEndpointIn,
|
this->m_libusbEndpointIn,
|
||||||
message->data,
|
message->data,
|
||||||
message->length,
|
message->length,
|
||||||
&actualLength,
|
&actualLength,
|
||||||
timeout);
|
timeout);
|
||||||
}
|
}
|
||||||
while (ret == LIBUSB_ERROR_TIMEOUT && actualLength == 0 && IsOpened());
|
while (ret == LIBUSB_ERROR_TIMEOUT && actualLength == 0 && IsOpened());
|
||||||
|
|
||||||
|
@ -521,8 +525,8 @@ namespace nsyshid::backend::libusb
|
||||||
return ReadResult::Success;
|
return ReadResult::Success;
|
||||||
}
|
}
|
||||||
cemuLog_logDebug(LogType::Force,
|
cemuLog_logDebug(LogType::Force,
|
||||||
"nsyshid::DeviceLibusb::read(): failed with error code: {}",
|
"nsyshid::DeviceLibusb::read(): failed at endpoint 0x{:02x} with error message: {}", this->m_libusbEndpointIn,
|
||||||
ret);
|
libusb_error_name(ret));
|
||||||
return ReadResult::Error;
|
return ReadResult::Error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -532,18 +536,23 @@ namespace nsyshid::backend::libusb
|
||||||
if (!handleLock->IsValid())
|
if (!handleLock->IsValid())
|
||||||
{
|
{
|
||||||
cemuLog_logDebug(LogType::Force,
|
cemuLog_logDebug(LogType::Force,
|
||||||
"nsyshid::DeviceLibusb::write(): cannot write to a non-opened device\n");
|
"nsyshid::DeviceLibusb::write(): cannot write to a non-opened device\n");
|
||||||
return WriteResult::Error;
|
return WriteResult::Error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < m_config_descriptors.size(); i++)
|
||||||
|
{
|
||||||
|
ClaimAllInterfaces(i);
|
||||||
|
}
|
||||||
|
|
||||||
message->bytesWritten = 0;
|
message->bytesWritten = 0;
|
||||||
int actualLength = 0;
|
int actualLength = 0;
|
||||||
int ret = libusb_bulk_transfer(handleLock->GetHandle(),
|
int ret = libusb_interrupt_transfer(handleLock->GetHandle(),
|
||||||
this->m_libusbEndpointOut,
|
this->m_libusbEndpointOut,
|
||||||
message->data,
|
message->data,
|
||||||
message->length,
|
message->length,
|
||||||
&actualLength,
|
&actualLength,
|
||||||
0);
|
0);
|
||||||
|
|
||||||
if (ret == 0)
|
if (ret == 0)
|
||||||
{
|
{
|
||||||
|
@ -556,14 +565,14 @@ namespace nsyshid::backend::libusb
|
||||||
return WriteResult::Success;
|
return WriteResult::Success;
|
||||||
}
|
}
|
||||||
cemuLog_logDebug(LogType::Force,
|
cemuLog_logDebug(LogType::Force,
|
||||||
"nsyshid::DeviceLibusb::write(): failed with error code: {}",
|
"nsyshid::DeviceLibusb::write(): failed with error code: {}",
|
||||||
ret);
|
ret);
|
||||||
return WriteResult::Error;
|
return WriteResult::Error;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DeviceLibusb::GetDescriptor(uint8 descType,
|
bool DeviceLibusb::GetDescriptor(uint8 descType,
|
||||||
uint8 descIndex,
|
uint8 descIndex,
|
||||||
uint8 lang,
|
uint16 lang,
|
||||||
uint8* output,
|
uint8* output,
|
||||||
uint32 outputMaxLength)
|
uint32 outputMaxLength)
|
||||||
{
|
{
|
||||||
|
@ -579,7 +588,6 @@ namespace nsyshid::backend::libusb
|
||||||
struct libusb_config_descriptor* conf = nullptr;
|
struct libusb_config_descriptor* conf = nullptr;
|
||||||
libusb_device* dev = libusb_get_device(handleLock->GetHandle());
|
libusb_device* dev = libusb_get_device(handleLock->GetHandle());
|
||||||
int ret = libusb_get_active_config_descriptor(dev, &conf);
|
int ret = libusb_get_active_config_descriptor(dev, &conf);
|
||||||
|
|
||||||
if (ret == 0)
|
if (ret == 0)
|
||||||
{
|
{
|
||||||
std::vector<uint8> configurationDescriptor(conf->wTotalLength);
|
std::vector<uint8> configurationDescriptor(conf->wTotalLength);
|
||||||
|
@ -656,7 +664,6 @@ namespace nsyshid::backend::libusb
|
||||||
extraReadPointer += bLength;
|
extraReadPointer += bLength;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int endpointIndex = 0; endpointIndex < altsetting.bNumEndpoints; endpointIndex++)
|
for (int endpointIndex = 0; endpointIndex < altsetting.bNumEndpoints; endpointIndex++)
|
||||||
{
|
{
|
||||||
// endpoint descriptor
|
// endpoint descriptor
|
||||||
|
@ -681,24 +688,61 @@ namespace nsyshid::backend::libusb
|
||||||
uint32 bytesWritten = currentWritePtr - &configurationDescriptor[0];
|
uint32 bytesWritten = currentWritePtr - &configurationDescriptor[0];
|
||||||
libusb_free_config_descriptor(conf);
|
libusb_free_config_descriptor(conf);
|
||||||
cemu_assert_debug(bytesWritten <= conf->wTotalLength);
|
cemu_assert_debug(bytesWritten <= conf->wTotalLength);
|
||||||
|
|
||||||
memcpy(output, &configurationDescriptor[0],
|
memcpy(output, &configurationDescriptor[0],
|
||||||
std::min<uint32>(outputMaxLength, bytesWritten));
|
std::min<uint32>(outputMaxLength, bytesWritten));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
cemuLog_logDebug(LogType::Force,
|
|
||||||
"nsyshid::DeviceLibusb::getDescriptor(): failed to get config descriptor with error code: {}",
|
|
||||||
ret);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
cemu_assert_unimplemented();
|
uint16 wValue = uint16(descType) << 8 | uint16(descIndex);
|
||||||
|
// HID Get_Descriptor requests are handled via libusb_control_transfer
|
||||||
|
int ret = libusb_control_transfer(handleLock->GetHandle(),
|
||||||
|
LIBUSB_REQUEST_TYPE_STANDARD | LIBUSB_ENDPOINT_IN,
|
||||||
|
LIBUSB_REQUEST_GET_DESCRIPTOR,
|
||||||
|
wValue,
|
||||||
|
lang,
|
||||||
|
output,
|
||||||
|
outputMaxLength,
|
||||||
|
0);
|
||||||
|
if (ret != outputMaxLength)
|
||||||
|
{
|
||||||
|
cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::GetDescriptor(): Control Transfer Failed: {}", libusb_error_name(ret));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DeviceLibusb::SetIdle(uint8 ifIndex,
|
||||||
|
uint8 reportId,
|
||||||
|
uint8 duration)
|
||||||
|
{
|
||||||
|
auto handleLock = AquireHandleLock();
|
||||||
|
if (!handleLock->IsValid())
|
||||||
|
{
|
||||||
|
cemuLog_log(LogType::Force, "nsyshid::DeviceLibusb::SetIdle(): device is not opened");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16 wValue = uint16(duration) << 8 | uint16(reportId);
|
||||||
|
|
||||||
|
// HID Set_Idle requests are handled via libusb_control_transfer
|
||||||
|
int ret = libusb_control_transfer(handleLock->GetHandle(),
|
||||||
|
LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE | LIBUSB_ENDPOINT_OUT,
|
||||||
|
HID_CLASS_SET_IDLE, // Defined in HID Class Specific Requests (7.2)
|
||||||
|
wValue,
|
||||||
|
ifIndex,
|
||||||
|
nullptr,
|
||||||
|
0,
|
||||||
|
0);
|
||||||
|
|
||||||
|
if (ret != 0)
|
||||||
|
{
|
||||||
|
cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::SetIdle(): Control Transfer Failed: {}", libusb_error_name(ret));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename Configs, typename Function>
|
template<typename Configs, typename Function>
|
||||||
|
@ -767,18 +811,22 @@ namespace nsyshid::backend::libusb
|
||||||
cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::SetProtocol(): device is not opened");
|
cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::SetProtocol(): device is not opened");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (m_interfaceIndex != ifIndex)
|
|
||||||
m_interfaceIndex = ifIndex;
|
|
||||||
|
|
||||||
ReleaseAllInterfacesForCurrentConfig();
|
int ret = libusb_control_transfer(handleLock->GetHandle(),
|
||||||
int ret = libusb_set_configuration(AquireHandleLock()->GetHandle(), protocol);
|
LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE | LIBUSB_ENDPOINT_OUT,
|
||||||
if (ret == LIBUSB_SUCCESS)
|
HID_CLASS_SET_PROTOCOL, // Defined in HID Class Specific Requests (7.2)
|
||||||
ret = ClaimAllInterfaces(protocol);
|
protocol,
|
||||||
|
ifIndex,
|
||||||
|
nullptr,
|
||||||
|
0,
|
||||||
|
0);
|
||||||
|
|
||||||
if (ret == LIBUSB_SUCCESS)
|
if (ret != 0)
|
||||||
return true;
|
{
|
||||||
|
cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::SetProtocol(): Control Transfer Failed: {}", libusb_error_name(ret));
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DeviceLibusb::SetReport(ReportMessage* message)
|
bool DeviceLibusb::SetReport(ReportMessage* message)
|
||||||
|
@ -790,18 +838,20 @@ namespace nsyshid::backend::libusb
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint16 wValue = uint16(message->reportType) << 8 | uint16(message->reportId);
|
||||||
|
|
||||||
int ret = libusb_control_transfer(handleLock->GetHandle(),
|
int ret = libusb_control_transfer(handleLock->GetHandle(),
|
||||||
LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE | LIBUSB_ENDPOINT_OUT,
|
LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE | LIBUSB_ENDPOINT_OUT,
|
||||||
LIBUSB_REQUEST_SET_CONFIGURATION,
|
HID_CLASS_SET_REPORT, // Defined in HID Class Specific Requests (7.2)
|
||||||
512,
|
wValue,
|
||||||
0,
|
m_interfaceIndex,
|
||||||
message->originalData,
|
message->data,
|
||||||
message->originalLength,
|
uint16(message->length & 0xFFFF),
|
||||||
0);
|
0);
|
||||||
|
|
||||||
if (ret != message->originalLength)
|
if (ret != message->length)
|
||||||
{
|
{
|
||||||
cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::SetReport(): Control Transfer Failed: {}", libusb_error_name(ret));
|
cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::SetReport(): Control Transfer Failed at interface {} : {}", m_interfaceIndex, libusb_error_name(ret));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
@ -854,5 +904,3 @@ namespace nsyshid::backend::libusb
|
||||||
return m_handle;
|
return m_handle;
|
||||||
}
|
}
|
||||||
} // namespace nsyshid::backend::libusb
|
} // namespace nsyshid::backend::libusb
|
||||||
|
|
||||||
#endif // NSYSHID_ENABLE_BACKEND_LIBUSB
|
|
||||||
|
|
|
@ -1,15 +1,20 @@
|
||||||
#ifndef CEMU_NSYSHID_BACKEND_LIBUSB_H
|
|
||||||
#define CEMU_NSYSHID_BACKEND_LIBUSB_H
|
|
||||||
|
|
||||||
#include "nsyshid.h"
|
#include "nsyshid.h"
|
||||||
|
|
||||||
#if NSYSHID_ENABLE_BACKEND_LIBUSB
|
|
||||||
|
|
||||||
#include <libusb-1.0/libusb.h>
|
#include <libusb-1.0/libusb.h>
|
||||||
#include "Backend.h"
|
#include "Backend.h"
|
||||||
|
|
||||||
namespace nsyshid::backend::libusb
|
namespace nsyshid::backend::libusb
|
||||||
{
|
{
|
||||||
|
enum : uint8
|
||||||
|
{
|
||||||
|
HID_CLASS_GET_REPORT = 0x01,
|
||||||
|
HID_CLASS_GET_IDLE = 0x02,
|
||||||
|
HID_CLASS_GET_PROTOCOL = 0x03,
|
||||||
|
HID_CLASS_SET_REPORT = 0x09,
|
||||||
|
HID_CLASS_SET_IDLE = 0x0A,
|
||||||
|
HID_CLASS_SET_PROTOCOL = 0x0B
|
||||||
|
};
|
||||||
|
|
||||||
class BackendLibusb : public nsyshid::Backend {
|
class BackendLibusb : public nsyshid::Backend {
|
||||||
public:
|
public:
|
||||||
BackendLibusb();
|
BackendLibusb();
|
||||||
|
@ -75,10 +80,14 @@ namespace nsyshid::backend::libusb
|
||||||
|
|
||||||
bool GetDescriptor(uint8 descType,
|
bool GetDescriptor(uint8 descType,
|
||||||
uint8 descIndex,
|
uint8 descIndex,
|
||||||
uint8 lang,
|
uint16 lang,
|
||||||
uint8* output,
|
uint8* output,
|
||||||
uint32 outputMaxLength) override;
|
uint32 outputMaxLength) override;
|
||||||
|
|
||||||
|
bool SetIdle(uint8 ifIndex,
|
||||||
|
uint8 reportId,
|
||||||
|
uint8 duration) override;
|
||||||
|
|
||||||
bool SetProtocol(uint8 ifIndex, uint8 protocol) override;
|
bool SetProtocol(uint8 ifIndex, uint8 protocol) override;
|
||||||
|
|
||||||
int ClaimAllInterfaces(uint8 config_num);
|
int ClaimAllInterfaces(uint8 config_num);
|
||||||
|
@ -134,7 +143,3 @@ namespace nsyshid::backend::libusb
|
||||||
std::unique_ptr<HandleLock> AquireHandleLock();
|
std::unique_ptr<HandleLock> AquireHandleLock();
|
||||||
};
|
};
|
||||||
} // namespace nsyshid::backend::libusb
|
} // namespace nsyshid::backend::libusb
|
||||||
|
|
||||||
#endif // NSYSHID_ENABLE_BACKEND_LIBUSB
|
|
||||||
|
|
||||||
#endif // CEMU_NSYSHID_BACKEND_LIBUSB_H
|
|
||||||
|
|
|
@ -1,444 +0,0 @@
|
||||||
#include "BackendWindowsHID.h"
|
|
||||||
|
|
||||||
#if NSYSHID_ENABLE_BACKEND_WINDOWS_HID
|
|
||||||
|
|
||||||
#include <setupapi.h>
|
|
||||||
#include <initguid.h>
|
|
||||||
#include <hidsdi.h>
|
|
||||||
|
|
||||||
#pragma comment(lib, "Setupapi.lib")
|
|
||||||
#pragma comment(lib, "hid.lib")
|
|
||||||
|
|
||||||
DEFINE_GUID(GUID_DEVINTERFACE_HID,
|
|
||||||
0x4D1E55B2L, 0xF16F, 0x11CF, 0x88, 0xCB, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30);
|
|
||||||
|
|
||||||
namespace nsyshid::backend::windows
|
|
||||||
{
|
|
||||||
BackendWindowsHID::BackendWindowsHID()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void BackendWindowsHID::AttachVisibleDevices()
|
|
||||||
{
|
|
||||||
// add all currently connected devices
|
|
||||||
HDEVINFO hDevInfo;
|
|
||||||
SP_DEVICE_INTERFACE_DATA DevIntfData;
|
|
||||||
PSP_DEVICE_INTERFACE_DETAIL_DATA DevIntfDetailData;
|
|
||||||
SP_DEVINFO_DATA DevData;
|
|
||||||
|
|
||||||
DWORD dwSize, dwMemberIdx;
|
|
||||||
|
|
||||||
hDevInfo = SetupDiGetClassDevs(&GUID_DEVINTERFACE_HID, NULL, 0, DIGCF_DEVICEINTERFACE | DIGCF_PRESENT);
|
|
||||||
|
|
||||||
if (hDevInfo != INVALID_HANDLE_VALUE)
|
|
||||||
{
|
|
||||||
DevIntfData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
|
|
||||||
dwMemberIdx = 0;
|
|
||||||
|
|
||||||
SetupDiEnumDeviceInterfaces(hDevInfo, NULL, &GUID_DEVINTERFACE_HID,
|
|
||||||
dwMemberIdx, &DevIntfData);
|
|
||||||
|
|
||||||
while (GetLastError() != ERROR_NO_MORE_ITEMS)
|
|
||||||
{
|
|
||||||
DevData.cbSize = sizeof(DevData);
|
|
||||||
SetupDiGetDeviceInterfaceDetail(
|
|
||||||
hDevInfo, &DevIntfData, NULL, 0, &dwSize, NULL);
|
|
||||||
|
|
||||||
DevIntfDetailData = (PSP_DEVICE_INTERFACE_DETAIL_DATA)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY,
|
|
||||||
dwSize);
|
|
||||||
DevIntfDetailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
|
|
||||||
|
|
||||||
if (SetupDiGetDeviceInterfaceDetail(hDevInfo, &DevIntfData,
|
|
||||||
DevIntfDetailData, dwSize, &dwSize, &DevData))
|
|
||||||
{
|
|
||||||
HANDLE hHIDDevice = OpenDevice(DevIntfDetailData->DevicePath);
|
|
||||||
if (hHIDDevice != INVALID_HANDLE_VALUE)
|
|
||||||
{
|
|
||||||
auto device = CheckAndCreateDevice(DevIntfDetailData->DevicePath, hHIDDevice);
|
|
||||||
if (device != nullptr)
|
|
||||||
{
|
|
||||||
if (IsDeviceWhitelisted(device->m_vendorId, device->m_productId))
|
|
||||||
{
|
|
||||||
if (!AttachDevice(device))
|
|
||||||
{
|
|
||||||
cemuLog_log(LogType::Force,
|
|
||||||
"nsyshid::BackendWindowsHID: failed to attach device: {:04x}:{:04x}",
|
|
||||||
device->m_vendorId,
|
|
||||||
device->m_productId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
CloseHandle(hHIDDevice);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
HeapFree(GetProcessHeap(), 0, DevIntfDetailData);
|
|
||||||
// next
|
|
||||||
SetupDiEnumDeviceInterfaces(hDevInfo, NULL, &GUID_DEVINTERFACE_HID, ++dwMemberIdx, &DevIntfData);
|
|
||||||
}
|
|
||||||
SetupDiDestroyDeviceInfoList(hDevInfo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
BackendWindowsHID::~BackendWindowsHID()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
bool BackendWindowsHID::IsInitialisedOk()
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::shared_ptr<Device> BackendWindowsHID::CheckAndCreateDevice(wchar_t* devicePath, HANDLE hDevice)
|
|
||||||
{
|
|
||||||
HIDD_ATTRIBUTES hidAttr;
|
|
||||||
hidAttr.Size = sizeof(HIDD_ATTRIBUTES);
|
|
||||||
if (HidD_GetAttributes(hDevice, &hidAttr) == FALSE)
|
|
||||||
return nullptr;
|
|
||||||
|
|
||||||
auto device = std::make_shared<DeviceWindowsHID>(hidAttr.VendorID,
|
|
||||||
hidAttr.ProductID,
|
|
||||||
1,
|
|
||||||
2,
|
|
||||||
0,
|
|
||||||
_wcsdup(devicePath));
|
|
||||||
// get additional device info
|
|
||||||
sint32 maxPacketInputLength = -1;
|
|
||||||
sint32 maxPacketOutputLength = -1;
|
|
||||||
PHIDP_PREPARSED_DATA ppData = nullptr;
|
|
||||||
if (HidD_GetPreparsedData(hDevice, &ppData))
|
|
||||||
{
|
|
||||||
HIDP_CAPS caps;
|
|
||||||
if (HidP_GetCaps(ppData, &caps) == HIDP_STATUS_SUCCESS)
|
|
||||||
{
|
|
||||||
// length includes the report id byte
|
|
||||||
maxPacketInputLength = caps.InputReportByteLength - 1;
|
|
||||||
maxPacketOutputLength = caps.OutputReportByteLength - 1;
|
|
||||||
}
|
|
||||||
HidD_FreePreparsedData(ppData);
|
|
||||||
}
|
|
||||||
if (maxPacketInputLength <= 0 || maxPacketInputLength >= 0xF000)
|
|
||||||
{
|
|
||||||
cemuLog_logDebug(LogType::Force, "HID: Input packet length not available or out of range (length = {})", maxPacketInputLength);
|
|
||||||
maxPacketInputLength = 0x20;
|
|
||||||
}
|
|
||||||
if (maxPacketOutputLength <= 0 || maxPacketOutputLength >= 0xF000)
|
|
||||||
{
|
|
||||||
cemuLog_logDebug(LogType::Force, "HID: Output packet length not available or out of range (length = {})", maxPacketOutputLength);
|
|
||||||
maxPacketOutputLength = 0x20;
|
|
||||||
}
|
|
||||||
|
|
||||||
device->m_maxPacketSizeRX = maxPacketInputLength;
|
|
||||||
device->m_maxPacketSizeTX = maxPacketOutputLength;
|
|
||||||
|
|
||||||
return device;
|
|
||||||
}
|
|
||||||
|
|
||||||
DeviceWindowsHID::DeviceWindowsHID(uint16 vendorId,
|
|
||||||
uint16 productId,
|
|
||||||
uint8 interfaceIndex,
|
|
||||||
uint8 interfaceSubClass,
|
|
||||||
uint8 protocol,
|
|
||||||
wchar_t* devicePath)
|
|
||||||
: Device(vendorId,
|
|
||||||
productId,
|
|
||||||
interfaceIndex,
|
|
||||||
interfaceSubClass,
|
|
||||||
protocol),
|
|
||||||
m_devicePath(devicePath),
|
|
||||||
m_hFile(INVALID_HANDLE_VALUE)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
DeviceWindowsHID::~DeviceWindowsHID()
|
|
||||||
{
|
|
||||||
if (m_hFile != INVALID_HANDLE_VALUE)
|
|
||||||
{
|
|
||||||
CloseHandle(m_hFile);
|
|
||||||
m_hFile = INVALID_HANDLE_VALUE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DeviceWindowsHID::Open()
|
|
||||||
{
|
|
||||||
if (IsOpened())
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
m_hFile = OpenDevice(m_devicePath);
|
|
||||||
if (m_hFile == INVALID_HANDLE_VALUE)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
HidD_SetNumInputBuffers(m_hFile, 2); // don't cache too many reports
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DeviceWindowsHID::Close()
|
|
||||||
{
|
|
||||||
if (m_hFile != INVALID_HANDLE_VALUE)
|
|
||||||
{
|
|
||||||
CloseHandle(m_hFile);
|
|
||||||
m_hFile = INVALID_HANDLE_VALUE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DeviceWindowsHID::IsOpened()
|
|
||||||
{
|
|
||||||
return m_hFile != INVALID_HANDLE_VALUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
Device::ReadResult DeviceWindowsHID::Read(ReadMessage* message)
|
|
||||||
{
|
|
||||||
message->bytesRead = 0;
|
|
||||||
DWORD bt;
|
|
||||||
OVERLAPPED ovlp = {0};
|
|
||||||
ovlp.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
|
|
||||||
|
|
||||||
uint8* tempBuffer = (uint8*)malloc(message->length + 1);
|
|
||||||
sint32 transferLength = 0; // minus report byte
|
|
||||||
|
|
||||||
_debugPrintHex("HID_READ_BEFORE", message->data, message->length);
|
|
||||||
|
|
||||||
cemuLog_logDebug(LogType::Force, "HidRead Begin (Length 0x{:08x})", message->length);
|
|
||||||
BOOL readResult = ReadFile(this->m_hFile, tempBuffer, message->length + 1, &bt, &ovlp);
|
|
||||||
if (readResult != FALSE)
|
|
||||||
{
|
|
||||||
// sometimes we get the result immediately
|
|
||||||
if (bt == 0)
|
|
||||||
transferLength = 0;
|
|
||||||
else
|
|
||||||
transferLength = bt - 1;
|
|
||||||
cemuLog_logDebug(LogType::Force, "HidRead Result received immediately (error 0x{:08x}) Length 0x{:08x}",
|
|
||||||
GetLastError(), transferLength);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// wait for result
|
|
||||||
cemuLog_logDebug(LogType::Force, "HidRead WaitForResult (error 0x{:08x})", GetLastError());
|
|
||||||
// async hid read is never supposed to return unless there is a response? Lego Dimensions stops HIDRead calls as soon as one of them fails with a non-zero error (which includes time out)
|
|
||||||
DWORD r = WaitForSingleObject(ovlp.hEvent, 2000 * 100);
|
|
||||||
if (r == WAIT_TIMEOUT)
|
|
||||||
{
|
|
||||||
cemuLog_logDebug(LogType::Force, "HidRead internal timeout (error 0x{:08x})", GetLastError());
|
|
||||||
// return -108 in case of timeout
|
|
||||||
free(tempBuffer);
|
|
||||||
CloseHandle(ovlp.hEvent);
|
|
||||||
return ReadResult::ErrorTimeout;
|
|
||||||
}
|
|
||||||
|
|
||||||
cemuLog_logDebug(LogType::Force, "HidRead WaitHalfComplete");
|
|
||||||
GetOverlappedResult(this->m_hFile, &ovlp, &bt, false);
|
|
||||||
if (bt == 0)
|
|
||||||
transferLength = 0;
|
|
||||||
else
|
|
||||||
transferLength = bt - 1;
|
|
||||||
cemuLog_logDebug(LogType::Force, "HidRead WaitComplete Length: 0x{:08x}", transferLength);
|
|
||||||
}
|
|
||||||
sint32 returnCode = 0;
|
|
||||||
ReadResult result = ReadResult::Success;
|
|
||||||
if (bt != 0)
|
|
||||||
{
|
|
||||||
memcpy(message->data, tempBuffer + 1, transferLength);
|
|
||||||
sint32 hidReadLength = transferLength;
|
|
||||||
|
|
||||||
char debugOutput[1024] = {0};
|
|
||||||
for (sint32 i = 0; i < transferLength; i++)
|
|
||||||
{
|
|
||||||
sprintf(debugOutput + i * 3, "%02x ", tempBuffer[1 + i]);
|
|
||||||
}
|
|
||||||
cemuLog_logDebug(LogType::Force, "HIDRead data: {}", debugOutput);
|
|
||||||
|
|
||||||
message->bytesRead = transferLength;
|
|
||||||
result = ReadResult::Success;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
cemuLog_log(LogType::Force, "Failed HID read");
|
|
||||||
result = ReadResult::Error;
|
|
||||||
}
|
|
||||||
free(tempBuffer);
|
|
||||||
CloseHandle(ovlp.hEvent);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
Device::WriteResult DeviceWindowsHID::Write(WriteMessage* message)
|
|
||||||
{
|
|
||||||
message->bytesWritten = 0;
|
|
||||||
DWORD bt;
|
|
||||||
OVERLAPPED ovlp = {0};
|
|
||||||
ovlp.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
|
|
||||||
|
|
||||||
uint8* tempBuffer = (uint8*)malloc(message->length + 1);
|
|
||||||
memcpy(tempBuffer + 1, message->data, message->length);
|
|
||||||
tempBuffer[0] = 0; // report byte?
|
|
||||||
|
|
||||||
cemuLog_logDebug(LogType::Force, "HidWrite Begin (Length 0x{:08x})", message->length);
|
|
||||||
BOOL writeResult = WriteFile(this->m_hFile, tempBuffer, message->length + 1, &bt, &ovlp);
|
|
||||||
if (writeResult != FALSE)
|
|
||||||
{
|
|
||||||
// sometimes we get the result immediately
|
|
||||||
cemuLog_logDebug(LogType::Force, "HidWrite Result received immediately (error 0x{:08x}) Length 0x{:08x}",
|
|
||||||
GetLastError());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// wait for result
|
|
||||||
cemuLog_logDebug(LogType::Force, "HidWrite WaitForResult (error 0x{:08x})", GetLastError());
|
|
||||||
// todo - check for error type
|
|
||||||
DWORD r = WaitForSingleObject(ovlp.hEvent, 2000);
|
|
||||||
if (r == WAIT_TIMEOUT)
|
|
||||||
{
|
|
||||||
cemuLog_logDebug(LogType::Force, "HidWrite internal timeout");
|
|
||||||
// return -108 in case of timeout
|
|
||||||
free(tempBuffer);
|
|
||||||
CloseHandle(ovlp.hEvent);
|
|
||||||
return WriteResult::ErrorTimeout;
|
|
||||||
}
|
|
||||||
|
|
||||||
cemuLog_logDebug(LogType::Force, "HidWrite WaitHalfComplete");
|
|
||||||
GetOverlappedResult(this->m_hFile, &ovlp, &bt, false);
|
|
||||||
cemuLog_logDebug(LogType::Force, "HidWrite WaitComplete");
|
|
||||||
}
|
|
||||||
|
|
||||||
free(tempBuffer);
|
|
||||||
CloseHandle(ovlp.hEvent);
|
|
||||||
|
|
||||||
if (bt != 0)
|
|
||||||
{
|
|
||||||
message->bytesWritten = message->length;
|
|
||||||
return WriteResult::Success;
|
|
||||||
}
|
|
||||||
return WriteResult::Error;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DeviceWindowsHID::GetDescriptor(uint8 descType,
|
|
||||||
uint8 descIndex,
|
|
||||||
uint8 lang,
|
|
||||||
uint8* output,
|
|
||||||
uint32 outputMaxLength)
|
|
||||||
{
|
|
||||||
if (!IsOpened())
|
|
||||||
{
|
|
||||||
cemuLog_logDebug(LogType::Force, "nsyshid::DeviceWindowsHID::getDescriptor(): device is not opened");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (descType == 0x02)
|
|
||||||
{
|
|
||||||
uint8 configurationDescriptor[0x29];
|
|
||||||
|
|
||||||
uint8* currentWritePtr;
|
|
||||||
|
|
||||||
// configuration descriptor
|
|
||||||
currentWritePtr = configurationDescriptor + 0;
|
|
||||||
*(uint8*)(currentWritePtr + 0) = 9; // bLength
|
|
||||||
*(uint8*)(currentWritePtr + 1) = 2; // bDescriptorType
|
|
||||||
*(uint16be*)(currentWritePtr + 2) = 0x0029; // wTotalLength
|
|
||||||
*(uint8*)(currentWritePtr + 4) = 1; // bNumInterfaces
|
|
||||||
*(uint8*)(currentWritePtr + 5) = 1; // bConfigurationValue
|
|
||||||
*(uint8*)(currentWritePtr + 6) = 0; // iConfiguration
|
|
||||||
*(uint8*)(currentWritePtr + 7) = 0x80; // bmAttributes
|
|
||||||
*(uint8*)(currentWritePtr + 8) = 0xFA; // MaxPower
|
|
||||||
currentWritePtr = currentWritePtr + 9;
|
|
||||||
// configuration descriptor
|
|
||||||
*(uint8*)(currentWritePtr + 0) = 9; // bLength
|
|
||||||
*(uint8*)(currentWritePtr + 1) = 0x04; // bDescriptorType
|
|
||||||
*(uint8*)(currentWritePtr + 2) = 0; // bInterfaceNumber
|
|
||||||
*(uint8*)(currentWritePtr + 3) = 0; // bAlternateSetting
|
|
||||||
*(uint8*)(currentWritePtr + 4) = 2; // bNumEndpoints
|
|
||||||
*(uint8*)(currentWritePtr + 5) = 3; // bInterfaceClass
|
|
||||||
*(uint8*)(currentWritePtr + 6) = 0; // bInterfaceSubClass
|
|
||||||
*(uint8*)(currentWritePtr + 7) = 0; // bInterfaceProtocol
|
|
||||||
*(uint8*)(currentWritePtr + 8) = 0; // iInterface
|
|
||||||
currentWritePtr = currentWritePtr + 9;
|
|
||||||
// configuration descriptor
|
|
||||||
*(uint8*)(currentWritePtr + 0) = 9; // bLength
|
|
||||||
*(uint8*)(currentWritePtr + 1) = 0x21; // bDescriptorType
|
|
||||||
*(uint16be*)(currentWritePtr + 2) = 0x0111; // bcdHID
|
|
||||||
*(uint8*)(currentWritePtr + 4) = 0x00; // bCountryCode
|
|
||||||
*(uint8*)(currentWritePtr + 5) = 0x01; // bNumDescriptors
|
|
||||||
*(uint8*)(currentWritePtr + 6) = 0x22; // bDescriptorType
|
|
||||||
*(uint16be*)(currentWritePtr + 7) = 0x001D; // wDescriptorLength
|
|
||||||
currentWritePtr = currentWritePtr + 9;
|
|
||||||
// endpoint descriptor 1
|
|
||||||
*(uint8*)(currentWritePtr + 0) = 7; // bLength
|
|
||||||
*(uint8*)(currentWritePtr + 1) = 0x05; // bDescriptorType
|
|
||||||
*(uint8*)(currentWritePtr + 2) = 0x81; // bEndpointAddress
|
|
||||||
*(uint8*)(currentWritePtr + 3) = 0x03; // bmAttributes
|
|
||||||
*(uint16be*)(currentWritePtr + 4) =
|
|
||||||
this->m_maxPacketSizeRX; // wMaxPacketSize
|
|
||||||
*(uint8*)(currentWritePtr + 6) = 0x01; // bInterval
|
|
||||||
currentWritePtr = currentWritePtr + 7;
|
|
||||||
// endpoint descriptor 2
|
|
||||||
*(uint8*)(currentWritePtr + 0) = 7; // bLength
|
|
||||||
*(uint8*)(currentWritePtr + 1) = 0x05; // bDescriptorType
|
|
||||||
*(uint8*)(currentWritePtr + 2) = 0x02; // bEndpointAddress
|
|
||||||
*(uint8*)(currentWritePtr + 3) = 0x03; // bmAttributes
|
|
||||||
*(uint16be*)(currentWritePtr + 4) =
|
|
||||||
this->m_maxPacketSizeTX; // wMaxPacketSize
|
|
||||||
*(uint8*)(currentWritePtr + 6) = 0x01; // bInterval
|
|
||||||
currentWritePtr = currentWritePtr + 7;
|
|
||||||
|
|
||||||
cemu_assert_debug((currentWritePtr - configurationDescriptor) == 0x29);
|
|
||||||
|
|
||||||
memcpy(output, configurationDescriptor,
|
|
||||||
std::min<uint32>(outputMaxLength, sizeof(configurationDescriptor)));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
cemu_assert_unimplemented();
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DeviceWindowsHID::SetProtocol(uint8 ifIndex, uint8 protocol)
|
|
||||||
{
|
|
||||||
// ToDo: implement this
|
|
||||||
// pretend that everything is fine
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DeviceWindowsHID::SetReport(ReportMessage* message)
|
|
||||||
{
|
|
||||||
sint32 retryCount = 0;
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
BOOL r = HidD_SetOutputReport(this->m_hFile, message->reportData, message->length);
|
|
||||||
if (r != FALSE)
|
|
||||||
break;
|
|
||||||
Sleep(20); // retry
|
|
||||||
retryCount++;
|
|
||||||
if (retryCount >= 50)
|
|
||||||
{
|
|
||||||
cemuLog_log(LogType::Force, "nsyshid::DeviceWindowsHID::SetReport(): HID SetReport failed");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
HANDLE OpenDevice(wchar_t* devicePath)
|
|
||||||
{
|
|
||||||
return CreateFile(devicePath,
|
|
||||||
GENERIC_READ | GENERIC_WRITE,
|
|
||||||
FILE_SHARE_READ |
|
|
||||||
FILE_SHARE_WRITE,
|
|
||||||
NULL,
|
|
||||||
OPEN_EXISTING,
|
|
||||||
FILE_FLAG_OVERLAPPED,
|
|
||||||
NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _debugPrintHex(std::string prefix, uint8* data, size_t len)
|
|
||||||
{
|
|
||||||
char debugOutput[1024] = {0};
|
|
||||||
len = std::min(len, (size_t)100);
|
|
||||||
for (sint32 i = 0; i < len; i++)
|
|
||||||
{
|
|
||||||
sprintf(debugOutput + i * 3, "%02x ", data[i]);
|
|
||||||
}
|
|
||||||
cemuLog_logDebug(LogType::Force, "[{}] Data: {}", prefix, debugOutput);
|
|
||||||
}
|
|
||||||
} // namespace nsyshid::backend::windows
|
|
||||||
|
|
||||||
#endif // NSYSHID_ENABLE_BACKEND_WINDOWS_HID
|
|
|
@ -1,66 +0,0 @@
|
||||||
#ifndef CEMU_NSYSHID_BACKEND_WINDOWS_HID_H
|
|
||||||
#define CEMU_NSYSHID_BACKEND_WINDOWS_HID_H
|
|
||||||
|
|
||||||
#include "nsyshid.h"
|
|
||||||
|
|
||||||
#if NSYSHID_ENABLE_BACKEND_WINDOWS_HID
|
|
||||||
|
|
||||||
#include "Backend.h"
|
|
||||||
|
|
||||||
namespace nsyshid::backend::windows
|
|
||||||
{
|
|
||||||
class BackendWindowsHID : public nsyshid::Backend {
|
|
||||||
public:
|
|
||||||
BackendWindowsHID();
|
|
||||||
|
|
||||||
~BackendWindowsHID();
|
|
||||||
|
|
||||||
bool IsInitialisedOk() override;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void AttachVisibleDevices() override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::shared_ptr<Device> CheckAndCreateDevice(wchar_t* devicePath, HANDLE hDevice);
|
|
||||||
};
|
|
||||||
|
|
||||||
class DeviceWindowsHID : public nsyshid::Device {
|
|
||||||
public:
|
|
||||||
DeviceWindowsHID(uint16 vendorId,
|
|
||||||
uint16 productId,
|
|
||||||
uint8 interfaceIndex,
|
|
||||||
uint8 interfaceSubClass,
|
|
||||||
uint8 protocol,
|
|
||||||
wchar_t* devicePath);
|
|
||||||
|
|
||||||
~DeviceWindowsHID();
|
|
||||||
|
|
||||||
bool Open() override;
|
|
||||||
|
|
||||||
void Close() override;
|
|
||||||
|
|
||||||
bool IsOpened() override;
|
|
||||||
|
|
||||||
ReadResult Read(ReadMessage* message) override;
|
|
||||||
|
|
||||||
WriteResult Write(WriteMessage* message) override;
|
|
||||||
|
|
||||||
bool GetDescriptor(uint8 descType, uint8 descIndex, uint8 lang, uint8* output, uint32 outputMaxLength) override;
|
|
||||||
|
|
||||||
bool SetProtocol(uint8 ifIndex, uint8 protocol) override;
|
|
||||||
|
|
||||||
bool SetReport(ReportMessage* message) override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
wchar_t* m_devicePath;
|
|
||||||
HANDLE m_hFile;
|
|
||||||
};
|
|
||||||
|
|
||||||
HANDLE OpenDevice(wchar_t* devicePath);
|
|
||||||
|
|
||||||
void _debugPrintHex(std::string prefix, uint8* data, size_t len);
|
|
||||||
} // namespace nsyshid::backend::windows
|
|
||||||
|
|
||||||
#endif // NSYSHID_ENABLE_BACKEND_WINDOWS_HID
|
|
||||||
|
|
||||||
#endif // CEMU_NSYSHID_BACKEND_WINDOWS_HID_H
|
|
1170
src/Cafe/OS/libs/nsyshid/Dimensions.cpp
Normal file
1170
src/Cafe/OS/libs/nsyshid/Dimensions.cpp
Normal file
File diff suppressed because it is too large
Load diff
112
src/Cafe/OS/libs/nsyshid/Dimensions.h
Normal file
112
src/Cafe/OS/libs/nsyshid/Dimensions.h
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
|
#include "nsyshid.h"
|
||||||
|
#include "Backend.h"
|
||||||
|
|
||||||
|
#include "Common/FileStream.h"
|
||||||
|
|
||||||
|
namespace nsyshid
|
||||||
|
{
|
||||||
|
class DimensionsToypadDevice final : public Device
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
DimensionsToypadDevice();
|
||||||
|
~DimensionsToypadDevice() = default;
|
||||||
|
|
||||||
|
bool Open() override;
|
||||||
|
|
||||||
|
void Close() override;
|
||||||
|
|
||||||
|
bool IsOpened() override;
|
||||||
|
|
||||||
|
ReadResult Read(ReadMessage* message) override;
|
||||||
|
|
||||||
|
WriteResult Write(WriteMessage* message) override;
|
||||||
|
|
||||||
|
bool GetDescriptor(uint8 descType,
|
||||||
|
uint8 descIndex,
|
||||||
|
uint16 lang,
|
||||||
|
uint8* output,
|
||||||
|
uint32 outputMaxLength) override;
|
||||||
|
|
||||||
|
bool SetIdle(uint8 ifIndex,
|
||||||
|
uint8 reportId,
|
||||||
|
uint8 duration) override;
|
||||||
|
|
||||||
|
bool SetProtocol(uint8 ifIndex, uint8 protocol) override;
|
||||||
|
|
||||||
|
bool SetReport(ReportMessage* message) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool m_IsOpened;
|
||||||
|
};
|
||||||
|
|
||||||
|
class DimensionsUSB
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
struct DimensionsMini final
|
||||||
|
{
|
||||||
|
std::unique_ptr<FileStream> dimFile;
|
||||||
|
std::array<uint8, 0x2D * 0x04> data{};
|
||||||
|
uint8 index = 255;
|
||||||
|
uint8 pad = 255;
|
||||||
|
uint32 id = 0;
|
||||||
|
void Save();
|
||||||
|
};
|
||||||
|
|
||||||
|
void SendCommand(std::span<const uint8, 32> buf);
|
||||||
|
std::array<uint8, 32> GetStatus();
|
||||||
|
|
||||||
|
void GenerateRandomNumber(std::span<const uint8, 8> buf, uint8 sequence,
|
||||||
|
std::array<uint8, 32>& replyBuf);
|
||||||
|
void InitializeRNG(uint32 seed);
|
||||||
|
void GetChallengeResponse(std::span<const uint8, 8> buf, uint8 sequence,
|
||||||
|
std::array<uint8, 32>& replyBuf);
|
||||||
|
void QueryBlock(uint8 index, uint8 page, std::array<uint8, 32>& replyBuf,
|
||||||
|
uint8 sequence);
|
||||||
|
void WriteBlock(uint8 index, uint8 page, std::span<const uint8, 4> toWriteBuf, std::array<uint8, 32>& replyBuf,
|
||||||
|
uint8 sequence);
|
||||||
|
void GetModel(std::span<const uint8, 8> buf, uint8 sequence,
|
||||||
|
std::array<uint8, 32>& replyBuf);
|
||||||
|
|
||||||
|
bool RemoveFigure(uint8 pad, uint8 index, bool fullRemove);
|
||||||
|
bool TempRemove(uint8 index);
|
||||||
|
bool CancelRemove(uint8 index);
|
||||||
|
uint32 LoadFigure(const std::array<uint8, 0x2D * 0x04>& buf, std::unique_ptr<FileStream> file, uint8 pad, uint8 index);
|
||||||
|
bool CreateFigure(fs::path pathName, uint32 id);
|
||||||
|
bool MoveFigure(uint8 pad, uint8 index, uint8 oldPad, uint8 oldIndex);
|
||||||
|
static std::map<const uint32, const char*> GetListMinifigs();
|
||||||
|
static std::map<const uint32, const char*> GetListTokens();
|
||||||
|
std::string FindFigure(uint32 figNum);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::mutex m_dimensionsMutex;
|
||||||
|
std::array<DimensionsMini, 7> m_figures{};
|
||||||
|
|
||||||
|
private:
|
||||||
|
void RandomUID(std::array<uint8, 0x2D * 0x04>& uidBuffer);
|
||||||
|
uint8 GenerateChecksum(const std::array<uint8, 32>& data,
|
||||||
|
int numOfBytes) const;
|
||||||
|
std::array<uint8, 8> Decrypt(std::span<const uint8, 8> buf, std::optional<std::array<uint8, 16>> key);
|
||||||
|
std::array<uint8, 8> Encrypt(std::span<const uint8, 8> buf, std::optional<std::array<uint8, 16>> key);
|
||||||
|
std::array<uint8, 16> GenerateFigureKey(const std::array<uint8, 0x2D * 0x04>& uid);
|
||||||
|
std::array<uint8, 4> PWDGenerate(const std::array<uint8, 0x2D * 0x04>& uid);
|
||||||
|
std::array<uint8, 4> DimensionsRandomize(const std::vector<uint8> key, uint8 count);
|
||||||
|
uint32 GetFigureId(const std::array<uint8, 0x2D * 0x04>& buf);
|
||||||
|
uint32 Scramble(const std::array<uint8, 7>& uid, uint8 count);
|
||||||
|
uint32 GetNext();
|
||||||
|
DimensionsMini& GetFigureByIndex(uint8 index);
|
||||||
|
|
||||||
|
uint32 m_randomA;
|
||||||
|
uint32 m_randomB;
|
||||||
|
uint32 m_randomC;
|
||||||
|
uint32 m_randomD;
|
||||||
|
|
||||||
|
bool m_isAwake = false;
|
||||||
|
|
||||||
|
std::queue<std::array<uint8, 32>> m_figureAddedRemovedResponses;
|
||||||
|
std::queue<std::array<uint8, 32>> m_queries;
|
||||||
|
};
|
||||||
|
extern DimensionsUSB g_dimensionstoypad;
|
||||||
|
|
||||||
|
} // namespace nsyshid
|
|
@ -387,7 +387,7 @@ namespace nsyshid
|
||||||
|
|
||||||
bool InfinityBaseDevice::GetDescriptor(uint8 descType,
|
bool InfinityBaseDevice::GetDescriptor(uint8 descType,
|
||||||
uint8 descIndex,
|
uint8 descIndex,
|
||||||
uint8 lang,
|
uint16 lang,
|
||||||
uint8* output,
|
uint8* output,
|
||||||
uint32 outputMaxLength)
|
uint32 outputMaxLength)
|
||||||
{
|
{
|
||||||
|
@ -450,6 +450,13 @@ namespace nsyshid
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool InfinityBaseDevice::SetIdle(uint8 ifIndex,
|
||||||
|
uint8 reportId,
|
||||||
|
uint8 duration)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool InfinityBaseDevice::SetProtocol(uint8 ifIndex, uint8 protocol)
|
bool InfinityBaseDevice::SetProtocol(uint8 ifIndex, uint8 protocol)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
|
@ -492,7 +499,7 @@ namespace nsyshid
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
void InfinityUSB::SendCommand(uint8* buf, sint32 originalLength)
|
void InfinityUSB::SendCommand(uint8* buf, uint32 length)
|
||||||
{
|
{
|
||||||
const uint8 command = buf[2];
|
const uint8 command = buf[2];
|
||||||
const uint8 sequence = buf[3];
|
const uint8 sequence = buf[3];
|
||||||
|
|
|
@ -26,10 +26,14 @@ namespace nsyshid
|
||||||
|
|
||||||
bool GetDescriptor(uint8 descType,
|
bool GetDescriptor(uint8 descType,
|
||||||
uint8 descIndex,
|
uint8 descIndex,
|
||||||
uint8 lang,
|
uint16 lang,
|
||||||
uint8* output,
|
uint8* output,
|
||||||
uint32 outputMaxLength) override;
|
uint32 outputMaxLength) override;
|
||||||
|
|
||||||
|
bool SetIdle(uint8 ifIndex,
|
||||||
|
uint8 reportId,
|
||||||
|
uint8 duration) override;
|
||||||
|
|
||||||
bool SetProtocol(uint8 ifIndex, uint8 protocol) override;
|
bool SetProtocol(uint8 ifIndex, uint8 protocol) override;
|
||||||
|
|
||||||
bool SetReport(ReportMessage* message) override;
|
bool SetReport(ReportMessage* message) override;
|
||||||
|
@ -53,7 +57,7 @@ namespace nsyshid
|
||||||
void Save();
|
void Save();
|
||||||
};
|
};
|
||||||
|
|
||||||
void SendCommand(uint8* buf, sint32 originalLength);
|
void SendCommand(uint8* buf, uint32 length);
|
||||||
std::array<uint8, 32> GetStatus();
|
std::array<uint8, 32> GetStatus();
|
||||||
|
|
||||||
void GetBlankResponse(uint8 sequence, std::array<uint8, 32>& replyBuf);
|
void GetBlankResponse(uint8 sequence, std::array<uint8, 32>& replyBuf);
|
||||||
|
|
|
@ -564,7 +564,7 @@ namespace nsyshid
|
||||||
|
|
||||||
bool SkylanderPortalDevice::GetDescriptor(uint8 descType,
|
bool SkylanderPortalDevice::GetDescriptor(uint8 descType,
|
||||||
uint8 descIndex,
|
uint8 descIndex,
|
||||||
uint8 lang,
|
uint16 lang,
|
||||||
uint8* output,
|
uint8* output,
|
||||||
uint32 outputMaxLength)
|
uint32 outputMaxLength)
|
||||||
{
|
{
|
||||||
|
@ -583,7 +583,7 @@ namespace nsyshid
|
||||||
*(uint8*)(currentWritePtr + 7) = 0x80; // bmAttributes
|
*(uint8*)(currentWritePtr + 7) = 0x80; // bmAttributes
|
||||||
*(uint8*)(currentWritePtr + 8) = 0xFA; // MaxPower
|
*(uint8*)(currentWritePtr + 8) = 0xFA; // MaxPower
|
||||||
currentWritePtr = currentWritePtr + 9;
|
currentWritePtr = currentWritePtr + 9;
|
||||||
// configuration descriptor
|
// interface descriptor
|
||||||
*(uint8*)(currentWritePtr + 0) = 9; // bLength
|
*(uint8*)(currentWritePtr + 0) = 9; // bLength
|
||||||
*(uint8*)(currentWritePtr + 1) = 0x04; // bDescriptorType
|
*(uint8*)(currentWritePtr + 1) = 0x04; // bDescriptorType
|
||||||
*(uint8*)(currentWritePtr + 2) = 0; // bInterfaceNumber
|
*(uint8*)(currentWritePtr + 2) = 0; // bInterfaceNumber
|
||||||
|
@ -594,7 +594,7 @@ namespace nsyshid
|
||||||
*(uint8*)(currentWritePtr + 7) = 0; // bInterfaceProtocol
|
*(uint8*)(currentWritePtr + 7) = 0; // bInterfaceProtocol
|
||||||
*(uint8*)(currentWritePtr + 8) = 0; // iInterface
|
*(uint8*)(currentWritePtr + 8) = 0; // iInterface
|
||||||
currentWritePtr = currentWritePtr + 9;
|
currentWritePtr = currentWritePtr + 9;
|
||||||
// configuration descriptor
|
// HID descriptor
|
||||||
*(uint8*)(currentWritePtr + 0) = 9; // bLength
|
*(uint8*)(currentWritePtr + 0) = 9; // bLength
|
||||||
*(uint8*)(currentWritePtr + 1) = 0x21; // bDescriptorType
|
*(uint8*)(currentWritePtr + 1) = 0x21; // bDescriptorType
|
||||||
*(uint16be*)(currentWritePtr + 2) = 0x0111; // bcdHID
|
*(uint16be*)(currentWritePtr + 2) = 0x0111; // bcdHID
|
||||||
|
@ -608,7 +608,7 @@ namespace nsyshid
|
||||||
*(uint8*)(currentWritePtr + 1) = 0x05; // bDescriptorType
|
*(uint8*)(currentWritePtr + 1) = 0x05; // bDescriptorType
|
||||||
*(uint8*)(currentWritePtr + 2) = 0x81; // bEndpointAddress
|
*(uint8*)(currentWritePtr + 2) = 0x81; // bEndpointAddress
|
||||||
*(uint8*)(currentWritePtr + 3) = 0x03; // bmAttributes
|
*(uint8*)(currentWritePtr + 3) = 0x03; // bmAttributes
|
||||||
*(uint16be*)(currentWritePtr + 4) = 0x40; // wMaxPacketSize
|
*(uint16be*)(currentWritePtr + 4) = 0x0040; // wMaxPacketSize
|
||||||
*(uint8*)(currentWritePtr + 6) = 0x01; // bInterval
|
*(uint8*)(currentWritePtr + 6) = 0x01; // bInterval
|
||||||
currentWritePtr = currentWritePtr + 7;
|
currentWritePtr = currentWritePtr + 7;
|
||||||
// endpoint descriptor 2
|
// endpoint descriptor 2
|
||||||
|
@ -616,7 +616,7 @@ namespace nsyshid
|
||||||
*(uint8*)(currentWritePtr + 1) = 0x05; // bDescriptorType
|
*(uint8*)(currentWritePtr + 1) = 0x05; // bDescriptorType
|
||||||
*(uint8*)(currentWritePtr + 2) = 0x02; // bEndpointAddress
|
*(uint8*)(currentWritePtr + 2) = 0x02; // bEndpointAddress
|
||||||
*(uint8*)(currentWritePtr + 3) = 0x03; // bmAttributes
|
*(uint8*)(currentWritePtr + 3) = 0x03; // bmAttributes
|
||||||
*(uint16be*)(currentWritePtr + 4) = 0x40; // wMaxPacketSize
|
*(uint16be*)(currentWritePtr + 4) = 0x0040; // wMaxPacketSize
|
||||||
*(uint8*)(currentWritePtr + 6) = 0x01; // bInterval
|
*(uint8*)(currentWritePtr + 6) = 0x01; // bInterval
|
||||||
currentWritePtr = currentWritePtr + 7;
|
currentWritePtr = currentWritePtr + 7;
|
||||||
|
|
||||||
|
@ -627,6 +627,13 @@ namespace nsyshid
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool SkylanderPortalDevice::SetIdle(uint8 ifIndex,
|
||||||
|
uint8 reportId,
|
||||||
|
uint8 duration)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool SkylanderPortalDevice::SetProtocol(uint8 ifIndex, uint8 protocol)
|
bool SkylanderPortalDevice::SetProtocol(uint8 ifIndex, uint8 protocol)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
|
@ -634,12 +641,12 @@ namespace nsyshid
|
||||||
|
|
||||||
bool SkylanderPortalDevice::SetReport(ReportMessage* message)
|
bool SkylanderPortalDevice::SetReport(ReportMessage* message)
|
||||||
{
|
{
|
||||||
g_skyportal.ControlTransfer(message->originalData, message->originalLength);
|
g_skyportal.ControlTransfer(message->data, message->length);
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SkylanderUSB::ControlTransfer(uint8* buf, sint32 originalLength)
|
void SkylanderUSB::ControlTransfer(uint8* buf, uint32 length)
|
||||||
{
|
{
|
||||||
std::array<uint8, 64> interruptResponse = {};
|
std::array<uint8, 64> interruptResponse = {};
|
||||||
switch (buf[0])
|
switch (buf[0])
|
||||||
|
|
|
@ -26,10 +26,14 @@ namespace nsyshid
|
||||||
|
|
||||||
bool GetDescriptor(uint8 descType,
|
bool GetDescriptor(uint8 descType,
|
||||||
uint8 descIndex,
|
uint8 descIndex,
|
||||||
uint8 lang,
|
uint16 lang,
|
||||||
uint8* output,
|
uint8* output,
|
||||||
uint32 outputMaxLength) override;
|
uint32 outputMaxLength) override;
|
||||||
|
|
||||||
|
bool SetIdle(uint8 ifIndex,
|
||||||
|
uint8 reportId,
|
||||||
|
uint8 duration) override;
|
||||||
|
|
||||||
bool SetProtocol(uint8 ifIndex, uint8 protocol) override;
|
bool SetProtocol(uint8 ifIndex, uint8 protocol) override;
|
||||||
|
|
||||||
bool SetReport(ReportMessage* message) override;
|
bool SetReport(ReportMessage* message) override;
|
||||||
|
@ -70,7 +74,7 @@ namespace nsyshid
|
||||||
uint8 blue = 0;
|
uint8 blue = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
void ControlTransfer(uint8* buf, sint32 originalLength);
|
void ControlTransfer(uint8* buf, uint32 length);
|
||||||
|
|
||||||
void Activate();
|
void Activate();
|
||||||
void Deactivate();
|
void Deactivate();
|
||||||
|
|
|
@ -305,47 +305,37 @@ namespace nsyshid
|
||||||
osLib_returnFromFunction(hCPU, 0);
|
osLib_returnFromFunction(hCPU, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void export_HIDGetDescriptor(PPCInterpreter_t* hCPU)
|
void _debugPrintHex(const std::string prefix, const uint8* data, size_t size)
|
||||||
{
|
{
|
||||||
ppcDefineParamU32(hidHandle, 0); // r3
|
constexpr size_t BYTES_PER_LINE = 16;
|
||||||
ppcDefineParamU8(descType, 1); // r4
|
|
||||||
ppcDefineParamU8(descIndex, 2); // r5
|
|
||||||
ppcDefineParamU8(lang, 3); // r6
|
|
||||||
ppcDefineParamUStr(output, 4); // r7
|
|
||||||
ppcDefineParamU32(outputMaxLength, 5); // r8
|
|
||||||
ppcDefineParamMPTR(cbFuncMPTR, 6); // r9
|
|
||||||
ppcDefineParamMPTR(cbParamMPTR, 7); // r10
|
|
||||||
|
|
||||||
int returnValue = -1;
|
std::string out;
|
||||||
std::shared_ptr<Device> device = GetDeviceByHandle(hidHandle, true);
|
for (size_t row_start = 0; row_start < size; row_start += BYTES_PER_LINE)
|
||||||
if (device)
|
|
||||||
{
|
{
|
||||||
memset(output, 0, outputMaxLength);
|
out += fmt::format("{:06x}: ", row_start);
|
||||||
if (device->GetDescriptor(descType, descIndex, lang, output, outputMaxLength))
|
for (size_t i = 0; i < BYTES_PER_LINE; ++i)
|
||||||
{
|
{
|
||||||
returnValue = 0;
|
if (row_start + i < size)
|
||||||
|
{
|
||||||
|
out += fmt::format("{:02x} ", data[row_start + i]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
out += " ";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
out += " ";
|
||||||
|
for (size_t i = 0; i < BYTES_PER_LINE; ++i)
|
||||||
{
|
{
|
||||||
returnValue = -1;
|
if (row_start + i < size)
|
||||||
|
{
|
||||||
|
char c = static_cast<char>(data[row_start + i]);
|
||||||
|
out += std::isprint(c, std::locale::classic()) ? c : '.';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
out += "\n";
|
||||||
}
|
}
|
||||||
else
|
cemuLog_logDebug(LogType::Force, "[{}] Data: \n{}", prefix, out);
|
||||||
{
|
|
||||||
cemu_assert_suspicious();
|
|
||||||
}
|
|
||||||
osLib_returnFromFunction(hCPU, returnValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _debugPrintHex(std::string prefix, uint8* data, size_t len)
|
|
||||||
{
|
|
||||||
char debugOutput[1024] = {0};
|
|
||||||
len = std::min(len, (size_t)100);
|
|
||||||
for (sint32 i = 0; i < len; i++)
|
|
||||||
{
|
|
||||||
sprintf(debugOutput + i * 3, "%02x ", data[i]);
|
|
||||||
}
|
|
||||||
cemuLog_logDebug(LogType::Force, "[{}] Data: {}", prefix, debugOutput);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DoHIDTransferCallback(MPTR callbackFuncMPTR, MPTR callbackParamMPTR, uint32 hidHandle, uint32 errorCode,
|
void DoHIDTransferCallback(MPTR callbackFuncMPTR, MPTR callbackParamMPTR, uint32 hidHandle, uint32 errorCode,
|
||||||
|
@ -354,26 +344,152 @@ namespace nsyshid
|
||||||
coreinitAsyncCallback_add(callbackFuncMPTR, 5, hidHandle, errorCode, buffer, length, callbackParamMPTR);
|
coreinitAsyncCallback_add(callbackFuncMPTR, 5, hidHandle, errorCode, buffer, length, callbackParamMPTR);
|
||||||
}
|
}
|
||||||
|
|
||||||
void export_HIDSetIdle(PPCInterpreter_t* hCPU)
|
void _hidGetDescriptorAsync(std::shared_ptr<Device> device, uint8 descType, uint8 descIndex, uint16 lang, uint8* output, uint32 outputMaxLength, MPTR callbackFuncMPTR, MPTR callbackParamMPTR)
|
||||||
{
|
{
|
||||||
ppcDefineParamU32(hidHandle, 0); // r3
|
if (device->GetDescriptor(descType, descIndex, lang, output, outputMaxLength))
|
||||||
ppcDefineParamU32(ifIndex, 1); // r4
|
|
||||||
ppcDefineParamU32(ukn, 2); // r5
|
|
||||||
ppcDefineParamU32(duration, 3); // r6
|
|
||||||
ppcDefineParamMPTR(callbackFuncMPTR, 4); // r7
|
|
||||||
ppcDefineParamMPTR(callbackParamMPTR, 5); // r8
|
|
||||||
cemuLog_logDebug(LogType::Force, "nsyshid.HIDSetIdle(...)");
|
|
||||||
|
|
||||||
// todo
|
|
||||||
if (callbackFuncMPTR)
|
|
||||||
{
|
{
|
||||||
DoHIDTransferCallback(callbackFuncMPTR, callbackParamMPTR, hidHandle, 0, MPTR_NULL, 0);
|
DoHIDTransferCallback(callbackFuncMPTR,
|
||||||
|
callbackParamMPTR,
|
||||||
|
device->m_hid->handle,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
cemu_assert_unimplemented();
|
DoHIDTransferCallback(callbackFuncMPTR,
|
||||||
|
callbackParamMPTR,
|
||||||
|
device->m_hid->handle,
|
||||||
|
-1,
|
||||||
|
0,
|
||||||
|
0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void export_HIDGetDescriptor(PPCInterpreter_t* hCPU)
|
||||||
|
{
|
||||||
|
ppcDefineParamU32(hidHandle, 0); // r3
|
||||||
|
ppcDefineParamU8(descType, 1); // r4
|
||||||
|
ppcDefineParamU8(descIndex, 2); // r5
|
||||||
|
ppcDefineParamU16(lang, 3); // r6
|
||||||
|
ppcDefineParamUStr(output, 4); // r7
|
||||||
|
ppcDefineParamU32(outputMaxLength, 5); // r8
|
||||||
|
ppcDefineParamMPTR(cbFuncMPTR, 6); // r9
|
||||||
|
ppcDefineParamMPTR(cbParamMPTR, 7); // r10
|
||||||
|
cemuLog_logDebug(LogType::Force, "nsyshid.HIDGetDescriptor(0x{:08x}, 0x{:02x}, 0x{:02x}, 0x{:04x}, 0x{:x}, 0x{:08x}, 0x{:08x}, 0x{:08x})",
|
||||||
|
hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6], hCPU->gpr[7], hCPU->gpr[8], hCPU->gpr[9], hCPU->gpr[10]);
|
||||||
|
|
||||||
|
std::shared_ptr<Device> device = GetDeviceByHandle(hidHandle, true);
|
||||||
|
if (device == nullptr)
|
||||||
|
{
|
||||||
|
cemuLog_log(LogType::Force, "nsyshid.HIDGetDescriptor(): Unable to find device with hid handle {}", hidHandle);
|
||||||
|
osLib_returnFromFunction(hCPU, -1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// issue request (synchronous or asynchronous)
|
||||||
|
sint32 returnCode = 0;
|
||||||
|
if (cbFuncMPTR == MPTR_NULL)
|
||||||
|
{
|
||||||
|
// synchronous
|
||||||
|
returnCode = -1;
|
||||||
|
if (device->GetDescriptor(descType, descIndex, lang, output, outputMaxLength))
|
||||||
|
{
|
||||||
|
returnCode = outputMaxLength;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// asynchronous
|
||||||
|
std::thread(&_hidGetDescriptorAsync, device, descType, descIndex, lang, output, outputMaxLength, cbFuncMPTR, cbParamMPTR)
|
||||||
|
.detach();
|
||||||
|
returnCode = 0;
|
||||||
|
}
|
||||||
|
osLib_returnFromFunction(hCPU, returnCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _hidSetIdleAsync(std::shared_ptr<Device> device, uint8 ifIndex, uint8 reportId, uint8 duration, MPTR callbackFuncMPTR, MPTR callbackParamMPTR)
|
||||||
|
{
|
||||||
|
if (device->SetIdle(ifIndex, reportId, duration))
|
||||||
|
{
|
||||||
|
DoHIDTransferCallback(callbackFuncMPTR,
|
||||||
|
callbackParamMPTR,
|
||||||
|
device->m_hid->handle,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
DoHIDTransferCallback(callbackFuncMPTR,
|
||||||
|
callbackParamMPTR,
|
||||||
|
device->m_hid->handle,
|
||||||
|
-1,
|
||||||
|
0,
|
||||||
|
0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void export_HIDSetIdle(PPCInterpreter_t* hCPU)
|
||||||
|
{
|
||||||
|
ppcDefineParamU32(hidHandle, 0); // r3
|
||||||
|
ppcDefineParamU8(ifIndex, 1); // r4
|
||||||
|
ppcDefineParamU8(reportId, 2); // r5
|
||||||
|
ppcDefineParamU8(duration, 3); // r6
|
||||||
|
ppcDefineParamMPTR(callbackFuncMPTR, 4); // r7
|
||||||
|
ppcDefineParamMPTR(callbackParamMPTR, 5); // r8
|
||||||
|
cemuLog_logDebug(LogType::Force, "nsyshid.HIDSetIdle(0x{:08x}, 0x{:02x}, 0x{:02x}, 0x{:02x}, 0x{:08x}, 0x{:08x})", hCPU->gpr[3],
|
||||||
|
hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6], hCPU->gpr[7], hCPU->gpr[8]);
|
||||||
|
|
||||||
|
std::shared_ptr<Device> device = GetDeviceByHandle(hidHandle, true);
|
||||||
|
if (device == nullptr)
|
||||||
|
{
|
||||||
|
cemuLog_log(LogType::Force, "nsyshid.HIDSetIdle(): Unable to find device with hid handle {}", hidHandle);
|
||||||
|
osLib_returnFromFunction(hCPU, -1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// issue request (synchronous or asynchronous)
|
||||||
|
sint32 returnCode = 0;
|
||||||
|
if (callbackFuncMPTR == MPTR_NULL)
|
||||||
|
{
|
||||||
|
// synchronous
|
||||||
|
returnCode = -1;
|
||||||
|
if (device->SetIdle(ifIndex, reportId, duration))
|
||||||
|
{
|
||||||
|
returnCode = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// asynchronous
|
||||||
|
std::thread(&_hidSetIdleAsync, device, ifIndex, reportId, duration, callbackFuncMPTR, callbackParamMPTR)
|
||||||
|
.detach();
|
||||||
|
returnCode = 0;
|
||||||
|
}
|
||||||
|
osLib_returnFromFunction(hCPU, returnCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _hidSetProtocolAsync(std::shared_ptr<Device> device, uint8 ifIndex, uint8 protocol, MPTR callbackFuncMPTR, MPTR callbackParamMPTR)
|
||||||
|
{
|
||||||
|
if (device->SetProtocol(ifIndex, protocol))
|
||||||
|
{
|
||||||
|
DoHIDTransferCallback(callbackFuncMPTR,
|
||||||
|
callbackParamMPTR,
|
||||||
|
device->m_hid->handle,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
DoHIDTransferCallback(callbackFuncMPTR,
|
||||||
|
callbackParamMPTR,
|
||||||
|
device->m_hid->handle,
|
||||||
|
-1,
|
||||||
|
0,
|
||||||
|
0);
|
||||||
}
|
}
|
||||||
osLib_returnFromFunction(hCPU, 0); // for non-async version, return number of bytes transferred
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void export_HIDSetProtocol(PPCInterpreter_t* hCPU)
|
void export_HIDSetProtocol(PPCInterpreter_t* hCPU)
|
||||||
|
@ -383,51 +499,51 @@ namespace nsyshid
|
||||||
ppcDefineParamU8(protocol, 2); // r5
|
ppcDefineParamU8(protocol, 2); // r5
|
||||||
ppcDefineParamMPTR(callbackFuncMPTR, 3); // r6
|
ppcDefineParamMPTR(callbackFuncMPTR, 3); // r6
|
||||||
ppcDefineParamMPTR(callbackParamMPTR, 4); // r7
|
ppcDefineParamMPTR(callbackParamMPTR, 4); // r7
|
||||||
cemuLog_logDebug(LogType::Force, "nsyshid.HIDSetProtocol(...)");
|
cemuLog_logDebug(LogType::Force, "nsyshid.HIDSetProtocol(0x{:08x}, 0x{:02x}, 0x{:02x}, 0x{:08x}, 0x{:08x})", hCPU->gpr[3],
|
||||||
|
hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6], hCPU->gpr[7]);
|
||||||
|
|
||||||
std::shared_ptr<Device> device = GetDeviceByHandle(hidHandle, true);
|
std::shared_ptr<Device> device = GetDeviceByHandle(hidHandle, true);
|
||||||
sint32 returnCode = -1;
|
if (device == nullptr)
|
||||||
if (device)
|
|
||||||
{
|
{
|
||||||
if (!device->IsOpened())
|
cemuLog_log(LogType::Force, "nsyshid.HIDSetProtocol(): Unable to find device with hid handle {}", hidHandle);
|
||||||
|
osLib_returnFromFunction(hCPU, -1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// issue request (synchronous or asynchronous)
|
||||||
|
sint32 returnCode = 0;
|
||||||
|
if (callbackFuncMPTR == MPTR_NULL)
|
||||||
|
{
|
||||||
|
// synchronous
|
||||||
|
returnCode = -1;
|
||||||
|
if (device->SetProtocol(ifIndex, protocol))
|
||||||
{
|
{
|
||||||
cemuLog_logDebug(LogType::Force, "nsyshid.HIDSetProtocol(): error: device is not opened");
|
returnCode = 0;
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (device->SetProtocol(ifIndex, protocol))
|
|
||||||
{
|
|
||||||
returnCode = 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
cemu_assert_suspicious();
|
// asynchronous
|
||||||
}
|
std::thread(&_hidSetProtocolAsync, device, ifIndex, protocol, callbackFuncMPTR, callbackParamMPTR)
|
||||||
|
.detach();
|
||||||
if (callbackFuncMPTR)
|
returnCode = 0;
|
||||||
{
|
|
||||||
DoHIDTransferCallback(callbackFuncMPTR, callbackParamMPTR, hidHandle, 0, MPTR_NULL, 0);
|
|
||||||
}
|
}
|
||||||
osLib_returnFromFunction(hCPU, returnCode);
|
osLib_returnFromFunction(hCPU, returnCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
// handler for async HIDSetReport transfers
|
// handler for async HIDSetReport transfers
|
||||||
void _hidSetReportAsync(std::shared_ptr<Device> device, uint8* reportData, sint32 length,
|
void _hidSetReportAsync(std::shared_ptr<Device> device, uint8 reportType, uint8 reportId, uint8* data, uint32 length,
|
||||||
uint8* originalData,
|
MPTR callbackFuncMPTR, MPTR callbackParamMPTR)
|
||||||
sint32 originalLength, MPTR callbackFuncMPTR, MPTR callbackParamMPTR)
|
|
||||||
{
|
{
|
||||||
cemuLog_logDebug(LogType::Force, "_hidSetReportAsync begin");
|
cemuLog_logDebug(LogType::Force, "_hidSetReportAsync begin");
|
||||||
ReportMessage message(reportData, length, originalData, originalLength);
|
ReportMessage message(reportType, reportId, data, length);
|
||||||
if (device->SetReport(&message))
|
if (device->SetReport(&message))
|
||||||
{
|
{
|
||||||
DoHIDTransferCallback(callbackFuncMPTR,
|
DoHIDTransferCallback(callbackFuncMPTR,
|
||||||
callbackParamMPTR,
|
callbackParamMPTR,
|
||||||
device->m_hid->handle,
|
device->m_hid->handle,
|
||||||
0,
|
0,
|
||||||
memory_getVirtualOffsetFromPointer(originalData),
|
memory_getVirtualOffsetFromPointer(data),
|
||||||
originalLength);
|
length);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -435,24 +551,22 @@ namespace nsyshid
|
||||||
callbackParamMPTR,
|
callbackParamMPTR,
|
||||||
device->m_hid->handle,
|
device->m_hid->handle,
|
||||||
-1,
|
-1,
|
||||||
memory_getVirtualOffsetFromPointer(originalData),
|
memory_getVirtualOffsetFromPointer(data),
|
||||||
0);
|
length);
|
||||||
}
|
}
|
||||||
free(reportData);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// handler for synchronous HIDSetReport transfers
|
// handler for synchronous HIDSetReport transfers
|
||||||
sint32 _hidSetReportSync(std::shared_ptr<Device> device, uint8* reportData, sint32 length,
|
sint32 _hidSetReportSync(std::shared_ptr<Device> device, uint8 reportType, uint8 reportId,
|
||||||
uint8* originalData, sint32 originalLength, coreinit::OSEvent* event)
|
uint8* data, uint32 length, coreinit::OSEvent* event)
|
||||||
{
|
{
|
||||||
_debugPrintHex("_hidSetReportSync Begin", reportData, length);
|
_debugPrintHex("_hidSetReportSync Begin", data, length);
|
||||||
sint32 returnCode = 0;
|
sint32 returnCode = 0;
|
||||||
ReportMessage message(reportData, length, originalData, originalLength);
|
ReportMessage message(reportType, reportId, data, length);
|
||||||
if (device->SetReport(&message))
|
if (device->SetReport(&message))
|
||||||
{
|
{
|
||||||
returnCode = originalLength;
|
returnCode = length;
|
||||||
}
|
}
|
||||||
free(reportData);
|
|
||||||
cemuLog_logDebug(LogType::Force, "_hidSetReportSync end. returnCode: {}", returnCode);
|
cemuLog_logDebug(LogType::Force, "_hidSetReportSync end. returnCode: {}", returnCode);
|
||||||
coreinit::OSSignalEvent(event);
|
coreinit::OSSignalEvent(event);
|
||||||
return returnCode;
|
return returnCode;
|
||||||
|
@ -461,19 +575,19 @@ namespace nsyshid
|
||||||
void export_HIDSetReport(PPCInterpreter_t* hCPU)
|
void export_HIDSetReport(PPCInterpreter_t* hCPU)
|
||||||
{
|
{
|
||||||
ppcDefineParamU32(hidHandle, 0); // r3
|
ppcDefineParamU32(hidHandle, 0); // r3
|
||||||
ppcDefineParamU32(reportRelatedUkn, 1); // r4
|
ppcDefineParamU8(reportType, 1); // r4
|
||||||
ppcDefineParamU32(reportId, 2); // r5
|
ppcDefineParamU8(reportId, 2); // r5
|
||||||
ppcDefineParamUStr(data, 3); // r6
|
ppcDefineParamUStr(data, 3); // r6
|
||||||
ppcDefineParamU32(dataLength, 4); // r7
|
ppcDefineParamU32(dataLength, 4); // r7
|
||||||
ppcDefineParamMPTR(callbackFuncMPTR, 5); // r8
|
ppcDefineParamMPTR(callbackFuncMPTR, 5); // r8
|
||||||
ppcDefineParamMPTR(callbackParamMPTR, 6); // r9
|
ppcDefineParamMPTR(callbackParamMPTR, 6); // r9
|
||||||
cemuLog_logDebug(LogType::Force, "nsyshid.HIDSetReport({},0x{:02x},0x{:02x},...)", hidHandle, reportRelatedUkn,
|
cemuLog_logDebug(LogType::Force, "nsyshid.HIDSetReport(0x{:08x}, 0x{:02x}, 0x{:02x}, 0x{:08x}, 0x{:08x}, 0x{:08x}, 0x{:08x})", hCPU->gpr[3],
|
||||||
reportId);
|
hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6], hCPU->gpr[7], hCPU->gpr[8], hCPU->gpr[9]);
|
||||||
|
|
||||||
_debugPrintHex("HIDSetReport", data, dataLength);
|
_debugPrintHex("HIDSetReport", data, dataLength);
|
||||||
|
|
||||||
#ifdef CEMU_DEBUG_ASSERT
|
#ifdef CEMU_DEBUG_ASSERT
|
||||||
if (reportRelatedUkn != 2 || reportId != 0)
|
if (reportType != 2 || reportId != 0)
|
||||||
assert_dbg();
|
assert_dbg();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -485,15 +599,6 @@ namespace nsyshid
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// prepare report data
|
|
||||||
// note: Currently we need to pad the data to 0x20 bytes for it to work (plus one extra byte for HidD_SetOutputReport)
|
|
||||||
// Does IOSU pad data to 0x20 byte? Also check if this is specific to Skylanders portal
|
|
||||||
sint32 paddedLength = (dataLength + 0x1F) & ~0x1F;
|
|
||||||
uint8* reportData = (uint8*)malloc(paddedLength + 1);
|
|
||||||
memset(reportData, 0, paddedLength + 1);
|
|
||||||
reportData[0] = 0;
|
|
||||||
memcpy(reportData + 1, data, dataLength);
|
|
||||||
|
|
||||||
// issue request (synchronous or asynchronous)
|
// issue request (synchronous or asynchronous)
|
||||||
sint32 returnCode = 0;
|
sint32 returnCode = 0;
|
||||||
if (callbackFuncMPTR == MPTR_NULL)
|
if (callbackFuncMPTR == MPTR_NULL)
|
||||||
|
@ -501,15 +606,14 @@ namespace nsyshid
|
||||||
// synchronous
|
// synchronous
|
||||||
StackAllocator<coreinit::OSEvent> event;
|
StackAllocator<coreinit::OSEvent> event;
|
||||||
coreinit::OSInitEvent(&event, coreinit::OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, coreinit::OSEvent::EVENT_MODE::MODE_AUTO);
|
coreinit::OSInitEvent(&event, coreinit::OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, coreinit::OSEvent::EVENT_MODE::MODE_AUTO);
|
||||||
std::future<sint32> res = std::async(std::launch::async, &_hidSetReportSync, device, reportData,
|
std::future<sint32> res = std::async(std::launch::async, &_hidSetReportSync, device, reportType, reportId, data, dataLength, &event);
|
||||||
paddedLength + 1, data, dataLength, &event);
|
|
||||||
coreinit::OSWaitEvent(&event);
|
coreinit::OSWaitEvent(&event);
|
||||||
returnCode = res.get();
|
returnCode = res.get();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// asynchronous
|
// asynchronous
|
||||||
std::thread(&_hidSetReportAsync, device, reportData, paddedLength + 1, data, dataLength,
|
std::thread(&_hidSetReportAsync, device, reportType, reportId, data, dataLength,
|
||||||
callbackFuncMPTR, callbackParamMPTR)
|
callbackFuncMPTR, callbackParamMPTR)
|
||||||
.detach();
|
.detach();
|
||||||
returnCode = 0;
|
returnCode = 0;
|
||||||
|
@ -586,7 +690,7 @@ namespace nsyshid
|
||||||
ppcDefineParamMPTR(callbackFuncMPTR, 3); // r6
|
ppcDefineParamMPTR(callbackFuncMPTR, 3); // r6
|
||||||
ppcDefineParamMPTR(callbackParamMPTR, 4); // r7
|
ppcDefineParamMPTR(callbackParamMPTR, 4); // r7
|
||||||
cemuLog_logDebug(LogType::Force, "nsyshid.HIDRead(0x{:x},0x{:08x},0x{:08x},0x{:08x},0x{:08x})", hCPU->gpr[3],
|
cemuLog_logDebug(LogType::Force, "nsyshid.HIDRead(0x{:x},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[4], hCPU->gpr[5], hCPU->gpr[6], hCPU->gpr[7]);
|
||||||
|
|
||||||
std::shared_ptr<Device> device = GetDeviceByHandle(hidHandle, true);
|
std::shared_ptr<Device> device = GetDeviceByHandle(hidHandle, true);
|
||||||
if (device == nullptr)
|
if (device == nullptr)
|
||||||
|
@ -683,7 +787,7 @@ namespace nsyshid
|
||||||
ppcDefineParamMPTR(callbackFuncMPTR, 3); // r6
|
ppcDefineParamMPTR(callbackFuncMPTR, 3); // r6
|
||||||
ppcDefineParamMPTR(callbackParamMPTR, 4); // r7
|
ppcDefineParamMPTR(callbackParamMPTR, 4); // r7
|
||||||
cemuLog_logDebug(LogType::Force, "nsyshid.HIDWrite(0x{:x},0x{:08x},0x{:08x},0x{:08x},0x{:08x})", hCPU->gpr[3],
|
cemuLog_logDebug(LogType::Force, "nsyshid.HIDWrite(0x{:x},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[4], hCPU->gpr[5], hCPU->gpr[6], hCPU->gpr[7]);
|
||||||
|
|
||||||
std::shared_ptr<Device> device = GetDeviceByHandle(hidHandle, true);
|
std::shared_ptr<Device> device = GetDeviceByHandle(hidHandle, true);
|
||||||
if (device == nullptr)
|
if (device == nullptr)
|
||||||
|
@ -718,7 +822,7 @@ namespace nsyshid
|
||||||
ppcDefineParamTypePtr(ukn0, uint32be, 1);
|
ppcDefineParamTypePtr(ukn0, uint32be, 1);
|
||||||
ppcDefineParamTypePtr(ukn1, uint32be, 2);
|
ppcDefineParamTypePtr(ukn1, uint32be, 2);
|
||||||
cemuLog_logDebug(LogType::Force, "nsyshid.HIDDecodeError(0x{:08x},0x{:08x},0x{:08x})", hCPU->gpr[3],
|
cemuLog_logDebug(LogType::Force, "nsyshid.HIDDecodeError(0x{:08x},0x{:08x},0x{:08x})", hCPU->gpr[3],
|
||||||
hCPU->gpr[4], hCPU->gpr[5]);
|
hCPU->gpr[4], hCPU->gpr[5]);
|
||||||
|
|
||||||
// todo
|
// todo
|
||||||
*ukn0 = 0x3FF;
|
*ukn0 = 0x3FF;
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
#include "Cafe/OS/libs/coreinit/coreinit_Thread.h"
|
#include "Cafe/OS/libs/coreinit/coreinit_Thread.h"
|
||||||
#include "Cafe/IOSU/legacy/iosu_crypto.h"
|
#include "Cafe/IOSU/legacy/iosu_crypto.h"
|
||||||
#include "Cafe/OS/libs/coreinit/coreinit_Time.h"
|
#include "Cafe/OS/libs/coreinit/coreinit_Time.h"
|
||||||
|
#include "Cafe/OS/libs/coreinit/coreinit_GHS.h"
|
||||||
|
|
||||||
#include "Common/socket.h"
|
#include "Common/socket.h"
|
||||||
|
|
||||||
|
@ -117,20 +118,14 @@ void nsysnetExport_socket_lib_finish(PPCInterpreter_t* hCPU)
|
||||||
osLib_returnFromFunction(hCPU, 0); // 0 -> Success
|
osLib_returnFromFunction(hCPU, 0); // 0 -> Success
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint32be* __gh_errno_ptr()
|
|
||||||
{
|
|
||||||
OSThread_t* osThread = coreinit::OSGetCurrentThread();
|
|
||||||
return &osThread->context.ghs_errno;
|
|
||||||
}
|
|
||||||
|
|
||||||
void _setSockError(sint32 errCode)
|
void _setSockError(sint32 errCode)
|
||||||
{
|
{
|
||||||
*(uint32be*)__gh_errno_ptr() = (uint32)errCode;
|
coreinit::__gh_set_errno(errCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
sint32 _getSockError()
|
sint32 _getSockError()
|
||||||
{
|
{
|
||||||
return (sint32)*(uint32be*)__gh_errno_ptr();
|
return coreinit::__gh_get_errno();
|
||||||
}
|
}
|
||||||
|
|
||||||
// error translation modes for _translateError
|
// error translation modes for _translateError
|
||||||
|
|
|
@ -760,7 +760,7 @@ namespace padscore
|
||||||
void start()
|
void start()
|
||||||
{
|
{
|
||||||
OSCreateAlarm(&g_padscore.alarm);
|
OSCreateAlarm(&g_padscore.alarm);
|
||||||
const uint64 start_tick = coreinit::coreinit_getOSTime();
|
const uint64 start_tick = coreinit::OSGetTime();
|
||||||
const uint64 period_tick = coreinit::EspressoTime::GetTimerClock() / 200; // every 5ms
|
const uint64 period_tick = coreinit::EspressoTime::GetTimerClock() / 200; // every 5ms
|
||||||
MPTR handler = PPCInterpreter_makeCallableExportDepr(TickFunction);
|
MPTR handler = PPCInterpreter_makeCallableExportDepr(TickFunction);
|
||||||
OSSetPeriodicAlarm(&g_padscore.alarm, start_tick, period_tick, handler);
|
OSSetPeriodicAlarm(&g_padscore.alarm, start_tick, period_tick, handler);
|
||||||
|
|
|
@ -427,7 +427,7 @@ namespace proc_ui
|
||||||
}
|
}
|
||||||
if(callbackType != ProcUICallbackId::AcquireForeground)
|
if(callbackType != ProcUICallbackId::AcquireForeground)
|
||||||
priority = -priority;
|
priority = -priority;
|
||||||
AddCallbackInternal(funcPtr, userParam, priority, 0, s_CallbackTables[stdx::to_underlying(callbackType)][coreIndex]);
|
AddCallbackInternal(funcPtr, userParam, 0, priority, s_CallbackTables[stdx::to_underlying(callbackType)][coreIndex]);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProcUIRegisterCallback(ProcUICallbackId callbackType, void* funcPtr, void* userParam, sint32 priority)
|
void ProcUIRegisterCallback(ProcUICallbackId callbackType, void* funcPtr, void* userParam, sint32 priority)
|
||||||
|
@ -437,7 +437,7 @@ namespace proc_ui
|
||||||
|
|
||||||
void ProcUIRegisterBackgroundCallback(void* funcPtr, void* userParam, uint64 tickDelay)
|
void ProcUIRegisterBackgroundCallback(void* funcPtr, void* userParam, uint64 tickDelay)
|
||||||
{
|
{
|
||||||
AddCallbackInternal(funcPtr, userParam, 0, tickDelay, s_backgroundCallbackList);
|
AddCallbackInternal(funcPtr, userParam, tickDelay, 0, s_backgroundCallbackList);
|
||||||
}
|
}
|
||||||
|
|
||||||
void FreeCallbackChain(ProcUICallbackList& callbackList)
|
void FreeCallbackChain(ProcUICallbackList& callbackList)
|
||||||
|
|
|
@ -396,90 +396,35 @@ namespace snd_core
|
||||||
|
|
||||||
void AXOut_init()
|
void AXOut_init()
|
||||||
{
|
{
|
||||||
auto& config = GetConfig();
|
|
||||||
const auto audio_api = (IAudioAPI::AudioAPI)config.audio_api;
|
|
||||||
|
|
||||||
numQueuedFramesSndGeneric = 0;
|
numQueuedFramesSndGeneric = 0;
|
||||||
|
|
||||||
std::unique_lock lock(g_audioMutex);
|
std::unique_lock lock(g_audioMutex);
|
||||||
if (!g_tvAudio)
|
if (!g_tvAudio)
|
||||||
{
|
{
|
||||||
sint32 channels;
|
try
|
||||||
switch (config.tv_channels)
|
|
||||||
{
|
{
|
||||||
case 0:
|
g_tvAudio = IAudioAPI::CreateDeviceFromConfig(true, 48000, snd_core::AX_SAMPLES_PER_3MS_48KHZ * AX_FRAMES_PER_GROUP, 16);
|
||||||
channels = 1; // will mix mono sound on both output channels
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
channels = 6;
|
|
||||||
break;
|
|
||||||
default: // stereo
|
|
||||||
channels = 2;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
catch (std::runtime_error& ex)
|
||||||
IAudioAPI::DeviceDescriptionPtr device_description;
|
|
||||||
if (IAudioAPI::IsAudioAPIAvailable(audio_api))
|
|
||||||
{
|
{
|
||||||
auto devices = IAudioAPI::GetDevices(audio_api);
|
cemuLog_log(LogType::Force, "can't initialize tv audio: {}", ex.what());
|
||||||
const auto it = std::find_if(devices.begin(), devices.end(), [&config](const auto& d) {return d->GetIdentifier() == config.tv_device; });
|
exit(0);
|
||||||
if (it != devices.end())
|
|
||||||
device_description = *it;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (device_description)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
g_tvAudio = IAudioAPI::CreateDevice((IAudioAPI::AudioAPI)config.audio_api, device_description, 48000, channels, snd_core::AX_SAMPLES_PER_3MS_48KHZ * AX_FRAMES_PER_GROUP, 16);
|
|
||||||
g_tvAudio->SetVolume(config.tv_volume);
|
|
||||||
}
|
|
||||||
catch (std::runtime_error& ex)
|
|
||||||
{
|
|
||||||
cemuLog_log(LogType::Force, "can't initialize tv audio: {}", ex.what());
|
|
||||||
exit(0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!g_padAudio)
|
if (!g_padAudio)
|
||||||
{
|
{
|
||||||
sint32 channels;
|
try
|
||||||
switch (config.pad_channels)
|
|
||||||
{
|
{
|
||||||
case 0:
|
g_padAudio = IAudioAPI::CreateDeviceFromConfig(false, 48000, snd_core::AX_SAMPLES_PER_3MS_48KHZ * AX_FRAMES_PER_GROUP, 16);
|
||||||
channels = 1; // will mix mono sound on both output channels
|
if(g_padAudio)
|
||||||
break;
|
g_padVolume = g_padAudio->GetVolume();
|
||||||
case 2:
|
|
||||||
channels = 6;
|
|
||||||
break;
|
|
||||||
default: // stereo
|
|
||||||
channels = 2;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
catch (std::runtime_error& ex)
|
||||||
IAudioAPI::DeviceDescriptionPtr device_description;
|
|
||||||
if (IAudioAPI::IsAudioAPIAvailable(audio_api))
|
|
||||||
{
|
{
|
||||||
auto devices = IAudioAPI::GetDevices(audio_api);
|
cemuLog_log(LogType::Force, "can't initialize pad audio: {}", ex.what());
|
||||||
const auto it = std::find_if(devices.begin(), devices.end(), [&config](const auto& d) {return d->GetIdentifier() == config.pad_device; });
|
exit(0);
|
||||||
if (it != devices.end())
|
|
||||||
device_description = *it;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (device_description)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
g_padAudio = IAudioAPI::CreateDevice((IAudioAPI::AudioAPI)config.audio_api, device_description, 48000, channels, snd_core::AX_SAMPLES_PER_3MS_48KHZ * AX_FRAMES_PER_GROUP, 16);
|
|
||||||
g_padAudio->SetVolume(config.pad_volume);
|
|
||||||
g_padVolume = config.pad_volume;
|
|
||||||
}
|
|
||||||
catch (std::runtime_error& ex)
|
|
||||||
{
|
|
||||||
cemuLog_log(LogType::Force, "can't initialize pad audio: {}", ex.what());
|
|
||||||
exit(0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -267,7 +267,7 @@ namespace vpad
|
||||||
{
|
{
|
||||||
if (channel <= 1 && vpadDelayEnabled)
|
if (channel <= 1 && vpadDelayEnabled)
|
||||||
{
|
{
|
||||||
uint64 currentTime = coreinit::coreinit_getOSTime();
|
uint64 currentTime = coreinit::OSGetTime();
|
||||||
const auto dif = currentTime - vpad::g_vpad.controller_data[channel].drcLastCallTime;
|
const auto dif = currentTime - vpad::g_vpad.controller_data[channel].drcLastCallTime;
|
||||||
if (dif <= (ESPRESSO_TIMER_CLOCK / 60ull))
|
if (dif <= (ESPRESSO_TIMER_CLOCK / 60ull))
|
||||||
{
|
{
|
||||||
|
@ -1149,7 +1149,7 @@ namespace vpad
|
||||||
void start()
|
void start()
|
||||||
{
|
{
|
||||||
coreinit::OSCreateAlarm(&g_vpad.alarm);
|
coreinit::OSCreateAlarm(&g_vpad.alarm);
|
||||||
const uint64 start_tick = coreinit::coreinit_getOSTime();
|
const uint64 start_tick = coreinit::OSGetTime();
|
||||||
const uint64 period_tick = coreinit::EspressoTime::GetTimerClock() * 5 / 1000;
|
const uint64 period_tick = coreinit::EspressoTime::GetTimerClock() * 5 / 1000;
|
||||||
const MPTR handler = PPCInterpreter_makeCallableExportDepr(TickFunction);
|
const MPTR handler = PPCInterpreter_makeCallableExportDepr(TickFunction);
|
||||||
coreinit::OSSetPeriodicAlarm(&g_vpad.alarm, start_tick, period_tick, handler);
|
coreinit::OSSetPeriodicAlarm(&g_vpad.alarm, start_tick, period_tick, handler);
|
||||||
|
|
|
@ -91,7 +91,11 @@ bool cemuLog_log(LogType type, std::basic_string<T> formatStr, TArgs&&... args)
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
const auto format_view = fmt::basic_string_view<T>(formatStr);
|
const auto format_view = fmt::basic_string_view<T>(formatStr);
|
||||||
|
#if FMT_VERSION >= 110000
|
||||||
|
const auto text = fmt::vformat(format_view, fmt::make_format_args<fmt::buffered_context<T>>(args...));
|
||||||
|
#else
|
||||||
const auto text = fmt::vformat(format_view, fmt::make_format_args<fmt::buffer_context<T>>(args...));
|
const auto text = fmt::vformat(format_view, fmt::make_format_args<fmt::buffer_context<T>>(args...));
|
||||||
|
#endif
|
||||||
cemuLog_log(type, std::basic_string_view(text.data(), text.size()));
|
cemuLog_log(type, std::basic_string_view(text.data(), text.size()));
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -98,35 +98,36 @@ class MEMPTR : MEMPTRBase
|
||||||
return MEMPTR<X>(this->m_value);
|
return MEMPTR<X>(this->m_value);
|
||||||
}
|
}
|
||||||
|
|
||||||
MEMPTR operator+(const MEMPTR& ptr) noexcept
|
sint32 operator-(const MEMPTR& ptr) noexcept
|
||||||
|
requires(!std::is_void_v<T>)
|
||||||
{
|
{
|
||||||
return MEMPTR(this->GetMPTR() + ptr.GetMPTR());
|
return static_cast<sint32>(this->GetMPTR() - ptr.GetMPTR());
|
||||||
}
|
|
||||||
MEMPTR operator-(const MEMPTR& ptr) noexcept
|
|
||||||
{
|
|
||||||
return MEMPTR(this->GetMPTR() - ptr.GetMPTR());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MEMPTR operator+(sint32 v) noexcept
|
MEMPTR operator+(sint32 v) noexcept
|
||||||
|
requires(!std::is_void_v<T>)
|
||||||
{
|
{
|
||||||
// pointer arithmetic
|
// pointer arithmetic
|
||||||
return MEMPTR(this->GetMPTR() + v * 4);
|
return MEMPTR(this->GetMPTR() + v * sizeof(T));
|
||||||
}
|
}
|
||||||
|
|
||||||
MEMPTR operator-(sint32 v) noexcept
|
MEMPTR operator-(sint32 v) noexcept
|
||||||
|
requires(!std::is_void_v<T>)
|
||||||
{
|
{
|
||||||
// pointer arithmetic
|
// pointer arithmetic
|
||||||
return MEMPTR(this->GetMPTR() - v * 4);
|
return MEMPTR(this->GetMPTR() - v * sizeof(T));
|
||||||
}
|
}
|
||||||
|
|
||||||
MEMPTR& operator+=(sint32 v) noexcept
|
MEMPTR& operator+=(sint32 v) noexcept
|
||||||
|
requires(!std::is_void_v<T>)
|
||||||
{
|
{
|
||||||
m_value += v * sizeof(T);
|
m_value += v * sizeof(T);
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename Q = T>
|
template<typename Q = T>
|
||||||
std::enable_if_t<!std::is_same_v<Q, void>, Q>& operator*() const noexcept
|
requires(!std::is_void_v<Q>)
|
||||||
|
Q& operator*() const noexcept
|
||||||
{
|
{
|
||||||
return *GetPtr();
|
return *GetPtr();
|
||||||
}
|
}
|
||||||
|
@ -137,7 +138,8 @@ class MEMPTR : MEMPTRBase
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename Q = T>
|
template<typename Q = T>
|
||||||
std::enable_if_t<!std::is_same_v<Q, void>, Q>& operator[](int index) noexcept
|
requires(!std::is_void_v<Q>)
|
||||||
|
Q& operator[](int index) noexcept
|
||||||
{
|
{
|
||||||
return GetPtr()[index];
|
return GetPtr()[index];
|
||||||
}
|
}
|
||||||
|
|
|
@ -274,6 +274,25 @@ inline uint64 _udiv128(uint64 highDividend, uint64 lowDividend, uint64 divisor,
|
||||||
#define NOEXPORT __attribute__ ((visibility ("hidden")))
|
#define NOEXPORT __attribute__ ((visibility ("hidden")))
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if defined(_MSC_VER)
|
||||||
|
#define FORCE_INLINE __forceinline
|
||||||
|
#elif defined(__GNUC__) || defined(__clang__)
|
||||||
|
#define FORCE_INLINE inline __attribute__((always_inline))
|
||||||
|
#else
|
||||||
|
#define FORCE_INLINE inline
|
||||||
|
#endif
|
||||||
|
|
||||||
|
FORCE_INLINE int BSF(uint32 v) // returns index of first bit set, counting from LSB. If v is 0 then result is undefined
|
||||||
|
{
|
||||||
|
#if defined(_MSC_VER)
|
||||||
|
return _tzcnt_u32(v); // TZCNT requires BMI1. But if not supported it will execute as BSF
|
||||||
|
#elif defined(__GNUC__) || defined(__clang__)
|
||||||
|
return __builtin_ctz(v);
|
||||||
|
#else
|
||||||
|
return std::countr_zero(v);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
// On aarch64 we handle some of the x86 intrinsics by implementing them as wrappers
|
// On aarch64 we handle some of the x86 intrinsics by implementing them as wrappers
|
||||||
#if defined(__aarch64__)
|
#if defined(__aarch64__)
|
||||||
|
|
||||||
|
@ -597,4 +616,4 @@ namespace stdx
|
||||||
scope_exit& operator=(scope_exit) = delete;
|
scope_exit& operator=(scope_exit) = delete;
|
||||||
void release() { m_released = true;}
|
void release() { m_released = true;}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -114,7 +114,7 @@ CubebAPI::~CubebAPI()
|
||||||
bool CubebAPI::NeedAdditionalBlocks() const
|
bool CubebAPI::NeedAdditionalBlocks() const
|
||||||
{
|
{
|
||||||
std::shared_lock lock(m_mutex);
|
std::shared_lock lock(m_mutex);
|
||||||
return m_buffer.size() < s_audioDelay * m_bytesPerBlock;
|
return m_buffer.size() < GetAudioDelay() * m_bytesPerBlock;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CubebAPI::FeedBlock(sint16* data)
|
bool CubebAPI::FeedBlock(sint16* data)
|
||||||
|
@ -183,17 +183,17 @@ void CubebAPI::Destroy()
|
||||||
|
|
||||||
std::vector<IAudioAPI::DeviceDescriptionPtr> CubebAPI::GetDevices()
|
std::vector<IAudioAPI::DeviceDescriptionPtr> CubebAPI::GetDevices()
|
||||||
{
|
{
|
||||||
cubeb_device_collection devices;
|
|
||||||
if (cubeb_enumerate_devices(s_context, CUBEB_DEVICE_TYPE_OUTPUT, &devices) != CUBEB_OK)
|
|
||||||
return {};
|
|
||||||
|
|
||||||
std::vector<DeviceDescriptionPtr> result;
|
std::vector<DeviceDescriptionPtr> result;
|
||||||
result.reserve(devices.count + 1); // Reserve space for the default device
|
|
||||||
|
|
||||||
// Add the default device to the list
|
// Add the default device to the list
|
||||||
auto defaultDevice = std::make_shared<CubebDeviceDescription>(nullptr, "default", L"Default Device");
|
auto defaultDevice = std::make_shared<CubebDeviceDescription>(nullptr, "default", L"Default Device");
|
||||||
result.emplace_back(defaultDevice);
|
result.emplace_back(defaultDevice);
|
||||||
|
|
||||||
|
cubeb_device_collection devices;
|
||||||
|
if (cubeb_enumerate_devices(s_context, CUBEB_DEVICE_TYPE_OUTPUT, &devices) != CUBEB_OK)
|
||||||
|
return result;
|
||||||
|
|
||||||
|
result.reserve(devices.count + 1); // The default device already occupies one element
|
||||||
|
|
||||||
for (size_t i = 0; i < devices.count; ++i)
|
for (size_t i = 0; i < devices.count; ++i)
|
||||||
{
|
{
|
||||||
// const auto& device = devices.device[i];
|
// const auto& device = devices.device[i];
|
||||||
|
|
|
@ -175,17 +175,17 @@ void CubebInputAPI::Destroy()
|
||||||
|
|
||||||
std::vector<IAudioInputAPI::DeviceDescriptionPtr> CubebInputAPI::GetDevices()
|
std::vector<IAudioInputAPI::DeviceDescriptionPtr> CubebInputAPI::GetDevices()
|
||||||
{
|
{
|
||||||
cubeb_device_collection devices;
|
|
||||||
if (cubeb_enumerate_devices(s_context, CUBEB_DEVICE_TYPE_INPUT, &devices) != CUBEB_OK)
|
|
||||||
return {};
|
|
||||||
|
|
||||||
std::vector<DeviceDescriptionPtr> result;
|
std::vector<DeviceDescriptionPtr> result;
|
||||||
result.reserve(devices.count + 1); // Reserve space for the default device
|
|
||||||
|
|
||||||
// Add the default device to the list
|
// Add the default device to the list
|
||||||
auto defaultDevice = std::make_shared<CubebDeviceDescription>(nullptr, "default", L"Default Device");
|
auto defaultDevice = std::make_shared<CubebDeviceDescription>(nullptr, "default", L"Default Device");
|
||||||
result.emplace_back(defaultDevice);
|
result.emplace_back(defaultDevice);
|
||||||
|
|
||||||
|
cubeb_device_collection devices;
|
||||||
|
if (cubeb_enumerate_devices(s_context, CUBEB_DEVICE_TYPE_INPUT, &devices) != CUBEB_OK)
|
||||||
|
return result;
|
||||||
|
|
||||||
|
result.reserve(devices.count + 1); // The default device already occupies one element
|
||||||
|
|
||||||
for (size_t i = 0; i < devices.count; ++i)
|
for (size_t i = 0; i < devices.count; ++i)
|
||||||
{
|
{
|
||||||
// const auto& device = devices.device[i];
|
// const auto& device = devices.device[i];
|
||||||
|
|
|
@ -210,7 +210,7 @@ void DirectSoundAPI::SetVolume(sint32 volume)
|
||||||
bool DirectSoundAPI::NeedAdditionalBlocks() const
|
bool DirectSoundAPI::NeedAdditionalBlocks() const
|
||||||
{
|
{
|
||||||
std::shared_lock lock(m_mutex);
|
std::shared_lock lock(m_mutex);
|
||||||
return m_buffer.size() < s_audioDelay;
|
return m_buffer.size() < GetAudioDelay();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<DirectSoundAPI::DeviceDescriptionPtr> DirectSoundAPI::GetDevices()
|
std::vector<DirectSoundAPI::DeviceDescriptionPtr> DirectSoundAPI::GetDevices()
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue