diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000000..8c5bc479011 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,438 @@ +# SPDX-FileCopyrightText: 2024 shadPS4 Emulator Project +# SPDX-License-Identifier: GPL-2.0-or-later + +name: Build and Release + +on: + push: + branches: [ "*" ] + pull_request: + branches: [ "*" ] + +env: + BUILD_TYPE: Release + +jobs: + reuse: + runs-on: ubuntu-latest + continue-on-error: true + steps: + - uses: actions/checkout@v4 + - uses: fsfe/reuse-action@v4 + + clang-format: + runs-on: ubuntu-latest + continue-on-error: true + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Install + run: | + wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add - + sudo add-apt-repository 'deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-17 main' + sudo apt update + sudo apt install clang-format-17 + - name: Build + env: + COMMIT_RANGE: ${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }} + run: ./.ci/clang-format.sh + + get-info: + runs-on: ubuntu-latest + outputs: + date: ${{ steps.vars.outputs.date }} + shorthash: ${{ steps.vars.outputs.shorthash }} + steps: + - uses: actions/checkout@v4 + - name: Get date and git hash + id: vars + run: | + echo "date=$(date +'%Y-%m-%d')" >> $GITHUB_ENV + echo "shorthash=$(git rev-parse --short HEAD)" >> $GITHUB_ENV + echo "date=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT + echo "shorthash=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT + + windows-sdl: + runs-on: windows-latest + needs: get-info + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Cache CMake Configuration + uses: actions/cache@v4 + env: + cache-name: ${{ runner.os }}-sdl-ninja-cache-cmake-configuration + with: + path: | + ${{github.workspace}}/build + key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }} + restore-keys: | + ${{ env.cache-name }}- + + - name: Cache CMake Build + uses: hendrikmuhs/ccache-action@v1.2.14 + env: + cache-name: ${{ runner.os }}-sdl-cache-cmake-build + with: + append-timestamp: false + key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }} + + - name: Setup VS Environment + uses: ilammy/msvc-dev-cmd@v1.13.0 + with: + arch: amd64 + + - name: Configure CMake + run: cmake --fresh -G Ninja -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_C_COMPILER=clang-cl -DCMAKE_CXX_COMPILER=clang-cl -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache + + - name: Build + run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel + + - name: Upload Windows SDL artifact + uses: actions/upload-artifact@v4 + with: + name: shadps4-win64-sdl-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.shorthash }} + path: ${{github.workspace}}/build/shadPS4.exe + + windows-qt: + runs-on: windows-latest + needs: get-info + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Setup Qt + uses: jurplel/install-qt-action@v4 + with: + version: 6.7.2 + host: windows + target: desktop + arch: win64_msvc2019_64 + archives: qtbase qttools + + - name: Cache CMake Configuration + uses: actions/cache@v4 + env: + cache-name: ${{ runner.os }}-qt-ninja-cache-cmake-configuration + with: + path: | + ${{github.workspace}}/build + key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }} + restore-keys: | + ${{ env.cache-name }}- + + - name: Cache CMake Build + uses: hendrikmuhs/ccache-action@v1.2.14 + env: + cache-name: ${{ runner.os }}-qt-cache-cmake-build + with: + append-timestamp: false + key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }} + + - name: Setup VS Environment + uses: ilammy/msvc-dev-cmd@v1.13.0 + with: + arch: amd64 + + - name: Configure CMake + run: cmake --fresh -G Ninja -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DENABLE_QT_GUI=ON -DCMAKE_C_COMPILER=clang-cl -DCMAKE_CXX_COMPILER=clang-cl -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache + + - name: Build + run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel + + - name: Deploy and Package + run: | + mkdir upload + move build/shadPS4.exe upload + windeployqt --no-compiler-runtime --no-system-d3d-compiler --no-system-dxc-compiler --dir upload upload/shadPS4.exe + Compress-Archive -Path upload/* -DestinationPath shadps4-win64-qt-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.shorthash }}.zip + + - name: Upload Windows Qt artifact + uses: actions/upload-artifact@v4 + with: + name: shadps4-win64-qt-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.shorthash }} + path: upload/ + + macos-sdl: + runs-on: macos-latest + needs: get-info + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Setup latest Xcode + uses: maxim-lobanov/setup-xcode@v1 + with: + xcode-version: latest + + - name: Install MoltenVK + run: | + arch -x86_64 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" + arch -x86_64 /usr/local/bin/brew install molten-vk + + - name: Cache CMake Configuration + uses: actions/cache@v4 + env: + cache-name: ${{ runner.os }}-sdl-cache-cmake-configuration + with: + path: | + ${{github.workspace}}/build + key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }} + restore-keys: | + ${{ env.cache-name }}- + + - name: Cache CMake Build + uses: hendrikmuhs/ccache-action@v1.2.14 + env: + cache-name: ${{runner.os}}-sdl-cache-cmake-build + with: + append-timestamp: false + create-symlink: true + key: ${{env.cache-name}}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }} + variant: sccache + + - name: Configure CMake + run: cmake --fresh -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_OSX_ARCHITECTURES=x86_64 -DCMAKE_C_COMPILER_LAUNCHER=sccache -DCMAKE_CXX_COMPILER_LAUNCHER=sccache + + - name: Build + run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel $(sysctl -n hw.ncpu) + + - name: Package and Upload macOS SDL artifact + run: | + mkdir upload + mv ${{github.workspace}}/build/shadps4 upload + cp $(arch -x86_64 /usr/local/bin/brew --prefix)/opt/molten-vk/lib/libMoltenVK.dylib upload + tar cf shadps4-macos-sdl.tar.gz -C upload . + - uses: actions/upload-artifact@v4 + with: + name: shadps4-macos-sdl-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.shorthash }} + path: shadps4-macos-sdl.tar.gz + + macos-qt: + runs-on: macos-latest + needs: get-info + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Setup latest Xcode + uses: maxim-lobanov/setup-xcode@v1 + with: + xcode-version: latest + + - name: Install MoltenVK and Setup Qt + run: | + arch -x86_64 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" + arch -x86_64 /usr/local/bin/brew install molten-vk + - uses: jurplel/install-qt-action@v4 + with: + version: 6.7.2 + host: mac + target: desktop + arch: clang_64 + archives: qtbase qttools + + - name: Cache CMake Configuration + uses: actions/cache@v4 + env: + cache-name: ${{ runner.os }}-qt-cache-cmake-configuration + with: + path: | + ${{github.workspace}}/build + key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }} + restore-keys: | + ${{ env.cache-name }}- + + - name: Cache CMake Build + uses: hendrikmuhs/ccache-action@v1.2.14 + env: + cache-name: ${{runner.os}}-qt-cache-cmake-build + with: + append-timestamp: false + create-symlink: true + key: ${{env.cache-name}}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }} + variant: sccache + + - name: Configure CMake + run: cmake --fresh -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_OSX_ARCHITECTURES=x86_64 -DENABLE_QT_GUI=ON -DCMAKE_C_COMPILER_LAUNCHER=sccache -DCMAKE_CXX_COMPILER_LAUNCHER=sccache + + - name: Build + run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel $(sysctl -n hw.ncpu) + + - name: Package and Upload macOS Qt artifact + run: | + mkdir upload + mv ${{github.workspace}}/build/shadps4.app upload + macdeployqt upload/shadps4.app + tar cf shadps4-macos-qt.tar.gz -C upload . + - uses: actions/upload-artifact@v4 + with: + name: shadps4-macos-qt-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.shorthash }} + path: shadps4-macos-qt.tar.gz + + linux-sdl: + runs-on: ubuntu-24.04 + needs: get-info + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Install dependencies + run: sudo apt-get update && sudo apt install -y libx11-dev libxext-dev libwayland-dev libfuse2 clang build-essential + + - name: Cache CMake Configuration + uses: actions/cache@v4 + env: + cache-name: ${{ runner.os }}-sdl-cache-cmake-configuration + with: + path: | + ${{github.workspace}}/build + key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }} + restore-keys: | + ${{ env.cache-name }}- + + - name: Cache CMake Build + uses: hendrikmuhs/ccache-action@v1.2.14 + env: + cache-name: ${{ runner.os }}-sdl-cache-cmake-build + with: + append-timestamp: false + key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }} + + - name: Configure CMake + run: cmake --fresh -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache + + - name: Build + run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel + + - name: Package and Upload Linux(ubuntu64) SDL artifact + run: | + ls -la ${{ github.workspace }}/build/shadps4 + + - uses: actions/upload-artifact@v4 + with: + name: shadps4-ubuntu64-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.shorthash }} + path: ${{ github.workspace }}/build/shadps4 + + - name: Run AppImage packaging script + run: ./.github/linux-appimage-sdl.sh + + - name: Package and Upload Linux SDL artifact + run: | + tar cf shadps4-linux-sdl.tar.gz -C ${{github.workspace}}/build shadps4 + - uses: actions/upload-artifact@v4 + with: + name: shadps4-linux-sdl-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.shorthash }} + path: Shadps4-sdl.AppImage + + linux-qt: + runs-on: ubuntu-24.04 + needs: get-info + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Install dependencies + run: sudo apt-get update && sudo apt install -y libx11-dev libxext-dev libwayland-dev libfuse2 clang build-essential qt6-base-dev qt6-tools-dev + + - name: Cache CMake Configuration + uses: actions/cache@v4 + env: + cache-name: ${{ runner.os }}-qt-cache-cmake-configuration + with: + path: | + ${{github.workspace}}/build + key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }} + restore-keys: | + ${{ env.cache-name }}- + + - name: Cache CMake Build + uses: hendrikmuhs/ccache-action@v1.2.14 + env: + cache-name: ${{ runner.os }}-qt-cache-cmake-build + with: + append-timestamp: false + key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }} + + - name: Configure CMake + run: cmake --fresh -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DENABLE_QT_GUI=ON -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache + + - name: Build + run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel + + - name: Run AppImage packaging script + run: ./.github/linux-appimage-qt.sh + + - name: Package and Upload Linux Qt artifact + run: | + tar cf shadps4-linux-qt.tar.gz -C ${{github.workspace}}/build shadps4 + - uses: actions/upload-artifact@v4 + with: + name: shadps4-linux-qt-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.shorthash }} + path: Shadps4-qt.AppImage + + pre-release: + if: github.ref == 'refs/heads/main' && github.event_name == 'push' + needs: [get-info, windows-sdl, windows-qt, macos-sdl, macos-qt, linux-sdl, linux-qt] + runs-on: ubuntu-latest + steps: + - name: Download all artifacts + uses: actions/download-artifact@v4 + with: + path: ./artifacts + + - name: Compress individual directories (without parent directory) + run: | + cd ./artifacts + for dir in */; do + if [ -d "$dir" ]; then + dir_name=${dir%/} + echo "Creating zip for $dir_name" + (cd "$dir_name" && zip -r "../${dir_name}.zip" .) + fi + done + + - name: Create Pre-Release on GitHub + uses: ncipollo/release-action@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + name: "Pre-release-shadPS4-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.shorthash }}" + tag: "Pre-release-shadPS4-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.shorthash }}" + draft: false + prerelease: true + artifacts: | + ./artifacts/*.zip + + - name: Delete old pre-releases and tags + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + current_date="${{ needs.get-info.outputs.date }}" + api_url="https://api.github.com/repos/${{ github.repository }}/releases" + + # Get current pre-releases + releases=$(curl -H "Authorization: token $GITHUB_TOKEN" "$api_url?per_page=100") + + # Parse and delete old pre-releases + echo "$releases" | jq -c '.[] | select(.prerelease == true)' | while read -r release; do + release_date=$(echo "$release" | jq -r '.created_at' | cut -d'T' -f1) + release_id=$(echo "$release" | jq -r '.id') + release_tag=$(echo "$release" | jq -r '.tag_name') + + # Compare dates + if [[ "$release_date" < "$current_date" ]]; then + echo "Deleting old pre-release: $release_id from $release_date with tag: $release_tag" + # Delete the pre-release + curl -X DELETE -H "Authorization: token $GITHUB_TOKEN" "$api_url/$release_id" + # Delete the tag + curl -X DELETE -H "Authorization: token $GITHUB_TOKEN" "https://api.github.com/repos/${{ github.repository }}/git/refs/tags/$release_tag" + fi + done diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index 123c7f3f801..00000000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,17 +0,0 @@ -# SPDX-FileCopyrightText: 2021 yuzu Emulator Project -# SPDX-License-Identifier: GPL-2.0-or-later - -name: Reuse - -on: - push: - branches: [ main ] - tags: [ "*" ] - pull_request: - branches: [ main ] -jobs: - reuse: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: fsfe/reuse-action@v4 diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml deleted file mode 100644 index b9887ccac1b..00000000000 --- a/.github/workflows/format.yml +++ /dev/null @@ -1,28 +0,0 @@ -# SPDX-FileCopyrightText: 2024 shadPS4 Emulator Project -# SPDX-License-Identifier: GPL-2.0-or-later - -name: Clang Format - -on: - push: - branches: [ "*" ] - pull_request: - branches: [ main ] - -jobs: - clang-format: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - name: Install - run: | - wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add - - sudo add-apt-repository 'deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-17 main' - sudo apt update - sudo apt install clang-format-17 - - name: Build - env: - COMMIT_RANGE: ${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }} - run: ./.ci/clang-format.sh \ No newline at end of file diff --git a/.github/workflows/linux-qt.yml b/.github/workflows/linux-qt.yml deleted file mode 100644 index 6388536bb29..00000000000 --- a/.github/workflows/linux-qt.yml +++ /dev/null @@ -1,37 +0,0 @@ -# SPDX-FileCopyrightText: 2024 shadPS4 Emulator Project -# SPDX-License-Identifier: GPL-2.0-or-later - -name: Linux-Qt - -on: [push, pull_request] - -env: - BUILD_TYPE: Release - -jobs: - build: - runs-on: ubuntu-24.04 - - steps: - - uses: actions/checkout@v4 - with: - submodules: recursive - - - name: Install misc packages - run: > - sudo apt-get update && sudo apt install libx11-dev libxext-dev libwayland-dev libfuse2 clang build-essential qt6-base-dev qt6-tools-dev - - - name: Configure CMake - run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DENABLE_QT_GUI=ON - - - name: Build - run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel - - - name: Run AppImage packaging script - run: ./.github/linux-appimage-qt.sh - - - name: Upload executable - uses: actions/upload-artifact@v4 - with: - name: shadps4-linux-qt - path: Shadps4-qt.AppImage diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml deleted file mode 100644 index ef77a16c8ba..00000000000 --- a/.github/workflows/linux.yml +++ /dev/null @@ -1,48 +0,0 @@ -# SPDX-FileCopyrightText: 2024 shadPS4 Emulator Project -# SPDX-License-Identifier: GPL-2.0-or-later - -name: Linux - -on: - push: - branches: [ "main" ] - pull_request: - branches: [ "main" ] - -env: - BUILD_TYPE: Release - -jobs: - build: - runs-on: ubuntu-24.04 - - steps: - - uses: actions/checkout@v4 - with: - submodules: recursive - - - name: Install misc packages - run: > - sudo apt-get update && sudo apt install libx11-dev libxext-dev libwayland-dev libfuse2 clang build-essential - - - name: Configure CMake - run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ - - - name: Build - run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel - - - name: Upload executable - uses: actions/upload-artifact@v4 - with: - name: shadps4-ubuntu64 - path: | - ${{github.workspace}}/build/shadps4 - - - name: Run AppImage packaging script - run: ./.github/linux-appimage-sdl.sh - - - name: Upload executable - uses: actions/upload-artifact@v4 - with: - name: shadps4-sdl-appimage - path: Shadps4-sdl.AppImage diff --git a/.github/workflows/macos-qt.yml b/.github/workflows/macos-qt.yml deleted file mode 100644 index c507fa7ce06..00000000000 --- a/.github/workflows/macos-qt.yml +++ /dev/null @@ -1,57 +0,0 @@ -# SPDX-FileCopyrightText: 2024 shadPS4 Emulator Project -# SPDX-License-Identifier: GPL-2.0-or-later - -name: macOS-Qt - -on: [push, pull_request] - -env: - BUILD_TYPE: Release - -jobs: - build: - runs-on: macos-latest - - steps: - - uses: actions/checkout@v4 - with: - submodules: recursive - - - name: Setup latest Xcode - uses: maxim-lobanov/setup-xcode@v1 - with: - xcode-version: latest - - - name: Install MoltenVK - run: | - arch -x86_64 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" - arch -x86_64 /usr/local/bin/brew install molten-vk - - - name: Setup Qt - uses: jurplel/install-qt-action@v4 - with: - version: 6.7.2 - host: mac - target: desktop - arch: clang_64 - archives: qtbase qttools - - - name: Configure CMake - run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_OSX_ARCHITECTURES=x86_64 -DENABLE_QT_GUI=ON - - - name: Build - run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel $(sysctl -n hw.ncpu) - - - name: Package - run: | - mkdir upload - mv ${{github.workspace}}/build/shadps4.app upload - mv ${{github.workspace}}/build/translations upload - macdeployqt upload/shadps4.app - tar cf shadps4-macos-qt.tar.gz -C upload . - - - name: Upload executable - uses: actions/upload-artifact@v4 - with: - name: shadps4-macos-qt - path: shadps4-macos-qt.tar.gz diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml deleted file mode 100644 index 1a2a6eff61b..00000000000 --- a/.github/workflows/macos.yml +++ /dev/null @@ -1,52 +0,0 @@ -# SPDX-FileCopyrightText: 2024 shadPS4 Emulator Project -# SPDX-License-Identifier: GPL-2.0-or-later - -name: macOS - -on: - push: - branches: [ "main" ] - pull_request: - branches: [ "main" ] - -env: - BUILD_TYPE: Release - -jobs: - build: - runs-on: macos-latest - - steps: - - uses: actions/checkout@v4 - with: - submodules: recursive - - - name: Setup latest Xcode - uses: maxim-lobanov/setup-xcode@v1 - with: - xcode-version: latest - - - name: Install MoltenVK - run: | - arch -x86_64 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" - arch -x86_64 /usr/local/bin/brew install molten-vk - - - name: Configure CMake - run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_OSX_ARCHITECTURES=x86_64 - - - name: Build - run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel $(sysctl -n hw.ncpu) - - - name: Package - run: | - mkdir upload - mv ${{github.workspace}}/build/shadps4 upload - cp $(arch -x86_64 /usr/local/bin/brew --prefix)/opt/molten-vk/lib/libMoltenVK.dylib upload - install_name_tool -add_rpath "@loader_path" upload/shadps4 - tar cf shadps4-macos-sdl.tar.gz -C upload . - - - name: Upload executable - uses: actions/upload-artifact@v4 - with: - name: shadps4-macos-sdl - path: shadps4-macos-sdl.tar.gz diff --git a/.github/workflows/windows-qt.yml b/.github/workflows/windows-qt.yml deleted file mode 100644 index a792e9d5f29..00000000000 --- a/.github/workflows/windows-qt.yml +++ /dev/null @@ -1,46 +0,0 @@ -# SPDX-FileCopyrightText: 2024 shadPS4 Emulator Project -# SPDX-License-Identifier: GPL-2.0-or-later - -name: Windows-Qt - -on: [push, pull_request] - -env: - BUILD_TYPE: Release - -jobs: - build: - runs-on: windows-latest - - steps: - - uses: actions/checkout@v4 - with: - submodules: recursive - - - name: Setup Qt - uses: jurplel/install-qt-action@v4 - with: - version: 6.7.2 - host: windows - target: desktop - arch: win64_msvc2019_64 - archives: qtbase qttools - - - name: Configure CMake - run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -T ClangCL -DENABLE_QT_GUI=ON - - - name: Build - run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel - - - name: Deploy - run: | - mkdir upload - move build/Release/shadPS4.exe upload - move build/translations upload - windeployqt --dir upload upload/shadPS4.exe - - - name: Upload executable - uses: actions/upload-artifact@v4 - with: - name: shadps4-win64-qt - path: upload diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml deleted file mode 100644 index 499124863a1..00000000000 --- a/.github/workflows/windows.yml +++ /dev/null @@ -1,34 +0,0 @@ -# SPDX-FileCopyrightText: 2024 shadPS4 Emulator Project -# SPDX-License-Identifier: GPL-2.0-or-later - -name: Windows - -on: - push: - branches: [ "main" ] - pull_request: - branches: [ "main" ] - -env: - BUILD_TYPE: Release - -jobs: - build: - runs-on: windows-latest - steps: - - uses: actions/checkout@v4 - with: - submodules: recursive - - - name: Configure CMake - run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -T ClangCL - - - name: Build - run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel - - - name: Upload executable - uses: actions/upload-artifact@v4 - with: - name: shadps4-win64-sdl - path: | - ${{github.workspace}}/build/Release/shadPS4.exe diff --git a/.gitignore b/.gitignore index 2a3145085a6..087f29683df 100644 --- a/.gitignore +++ b/.gitignore @@ -409,3 +409,6 @@ FodyWeavers.xsd /out/* /third-party/out/* /src/common/scm_rev.cpp + +# for macOS +**/.DS_Store \ No newline at end of file diff --git a/.gitmodules b/.gitmodules index 95b0fc0bb4d..b2543534f30 100644 --- a/.gitmodules +++ b/.gitmodules @@ -85,3 +85,11 @@ [submodule "externals/half"] path = externals/half url = https://github.com/ROCm/half.git +[submodule "externals/dear_imgui"] + path = externals/dear_imgui + url = https://github.com/shadps4-emu/ext-imgui.git + shallow = true + branch = docking +[submodule "externals/pugixml"] + path = externals/pugixml + url = https://github.com/zeux/pugixml.git diff --git a/.reuse/dep5 b/.reuse/dep5 index 0140c0c02fb..30a9065b772 100644 --- a/.reuse/dep5 +++ b/.reuse/dep5 @@ -58,3 +58,7 @@ License: MIT Files: externals/tracy/* Copyright: 2017-2024 Bartosz Taudul License: BSD-3-Clause + +Files: src/imgui/renderer/fonts/NotoSansJP-Regular.ttf +Copyright: 2012 Google Inc. All Rights Reserved. +License: OFL-1.1 diff --git a/CMakeLists.txt b/CMakeLists.txt index 8c7e03e3cec..e0408ae19b1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,8 +17,41 @@ endif() project(shadPS4) +# Forcing PIE makes sure that the base address is high enough so that it doesn't clash with the PS4 memory. +if(UNIX AND NOT APPLE) + set(CMAKE_POSITION_INDEPENDENT_CODE TRUE) + + # check PIE support at link time + include(CheckPIESupported) + check_pie_supported(OUTPUT_VARIABLE pie_check LANGUAGES C CXX) + if(NOT CMAKE_C_LINK_PIE_SUPPORTED OR NOT CMAKE_CXX_LINK_PIE_SUPPORTED) + message(WARNING "PIE is not supported at link time: ${pie_check}") + endif() +endif() + option(ENABLE_QT_GUI "Enable the Qt GUI. If not selected then the emulator uses a minimal SDL-based UI instead" OFF) +# First, determine whether to use CMAKE_OSX_ARCHITECTURES or CMAKE_SYSTEM_PROCESSOR. +if (APPLE AND CMAKE_OSX_ARCHITECTURES) + set(BASE_ARCHITECTURE "${CMAKE_OSX_ARCHITECTURES}") +else() + set(BASE_ARCHITECTURE "${CMAKE_SYSTEM_PROCESSOR}") +endif() + +# Next, match common architecture strings down to a known common value. +if (BASE_ARCHITECTURE MATCHES "(x86)|(X86)|(amd64)|(AMD64)") + set(ARCHITECTURE "x86_64") +elseif (BASE_ARCHITECTURE MATCHES "(aarch64)|(AARCH64)|(arm64)|(ARM64)") + set(ARCHITECTURE "arm64") +else() + message(FATAL_ERROR "Unsupported CPU architecture: ${BASE_ARCHITECTURE}") +endif() + +if (APPLE AND ARCHITECTURE STREQUAL "x86_64") + # Exclude ARM homebrew path to avoid conflicts when cross compiling. + list(APPEND CMAKE_IGNORE_PREFIX_PATH "/opt/homebrew") +endif() + # This function should be passed a list of all files in a target. It will automatically generate file groups # following the directory hierarchy, so that the layout of the files in IDEs matches the one in the filesystem. function(create_target_directory_groups target_name) @@ -80,6 +113,7 @@ find_package(xbyak 7.07 CONFIG) find_package(xxHash 0.8.2 MODULE) find_package(zlib-ng 2.1.7 MODULE) find_package(Zydis 5.0.0 CONFIG) +find_package(pugixml 1.14 CONFIG) if (NOT CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR NOT MSVC) find_package(cryptopp 8.9.0 MODULE) @@ -151,7 +185,7 @@ set(GNM_LIB src/core/libraries/gnmdriver/gnmdriver.cpp src/core/libraries/gnmdriver/gnm_error.h ) -set(KERNEL_LIB +set(KERNEL_LIB src/core/libraries/kernel/event_flag/event_flag.cpp src/core/libraries/kernel/event_flag/event_flag.h src/core/libraries/kernel/event_flag/event_flag_obj.cpp @@ -183,6 +217,9 @@ set(NETWORK_LIBS src/core/libraries/network/http.cpp src/core/libraries/network/net.cpp src/core/libraries/network/netctl.cpp src/core/libraries/network/netctl.h + src/core/libraries/network/net_ctl_obj.cpp + src/core/libraries/network/net_ctl_obj.h + src/core/libraries/network/net_ctl_codes.h src/core/libraries/network/net.h src/core/libraries/network/ssl.cpp src/core/libraries/network/ssl.h @@ -192,13 +229,21 @@ set(SYSTEM_LIBS src/core/libraries/system/commondialog.cpp src/core/libraries/system/commondialog.h src/core/libraries/system/msgdialog.cpp src/core/libraries/system/msgdialog.h + src/core/libraries/system/msgdialog_ui.cpp src/core/libraries/system/posix.cpp src/core/libraries/system/posix.h - src/core/libraries/save_data/error_codes.h + src/core/libraries/save_data/save_backup.cpp + src/core/libraries/save_data/save_backup.h + src/core/libraries/save_data/save_instance.cpp + src/core/libraries/save_data/save_instance.h + src/core/libraries/save_data/save_memory.cpp + src/core/libraries/save_data/save_memory.h src/core/libraries/save_data/savedata.cpp src/core/libraries/save_data/savedata.h - src/core/libraries/system/savedatadialog.cpp - src/core/libraries/system/savedatadialog.h + src/core/libraries/save_data/dialog/savedatadialog.cpp + src/core/libraries/save_data/dialog/savedatadialog.h + src/core/libraries/save_data/dialog/savedatadialog_ui.cpp + src/core/libraries/save_data/dialog/savedatadialog_ui.h src/core/libraries/system/sysmodule.cpp src/core/libraries/system/sysmodule.h src/core/libraries/system/systemservice.cpp @@ -231,6 +276,11 @@ set(SYSTEM_LIBS src/core/libraries/system/commondialog.cpp src/core/libraries/ngs2/ngs2_impl.cpp src/core/libraries/ngs2/ngs2_impl.h src/core/libraries/ajm/ajm_error.h + src/core/libraries/audio3d/audio3d.cpp + src/core/libraries/audio3d/audio3d.h + src/core/libraries/audio3d/audio3d_error.h + src/core/libraries/audio3d/audio3d_impl.cpp + src/core/libraries/audio3d/audio3d_impl.h ) set(VIDEOOUT_LIB src/core/libraries/videoout/buffer.h @@ -279,6 +329,8 @@ set(NP_LIBS src/core/libraries/np_manager/np_manager.cpp src/core/libraries/np_score/np_score.h src/core/libraries/np_trophy/np_trophy.cpp src/core/libraries/np_trophy/np_trophy.h + src/core/libraries/np_trophy/trophy_ui.cpp + src/core/libraries/np_trophy/trophy_ui.h ) set(MISC_LIBS src/core/libraries/screenshot/screenshot.cpp @@ -296,6 +348,7 @@ set(COMMON src/common/logging/backend.cpp src/common/logging/text_formatter.h src/common/logging/types.h src/common/alignment.h + src/common/arch.h src/common/assert.cpp src/common/assert.h src/common/bit_field.h @@ -303,9 +356,11 @@ set(COMMON src/common/logging/backend.cpp src/common/concepts.h src/common/config.cpp src/common/config.h + src/common/cstring.h src/common/debug.h - src/common/disassembler.cpp - src/common/disassembler.h + src/common/decoder.cpp + src/common/decoder.h + src/common/elf_info.h src/common/endian.h src/common/enum.h src/common/io_file.cpp @@ -313,6 +368,7 @@ set(COMMON src/common/logging/backend.cpp src/common/error.cpp src/common/error.h src/common/scope_exit.h + src/common/fixed_value.h src/common/func_traits.h src/common/native_clock.cpp src/common/native_clock.h @@ -322,6 +378,8 @@ set(COMMON src/common/logging/backend.cpp src/common/polyfill_thread.h src/common/rdtsc.cpp src/common/rdtsc.h + src/common/signal_context.h + src/common/signal_context.cpp src/common/singleton.h src/common/slot_vector.h src/common/string_util.cpp @@ -334,6 +392,8 @@ set(COMMON src/common/logging/backend.cpp src/common/version.h src/common/ntapi.h src/common/ntapi.cpp + src/common/memory_patcher.h + src/common/memory_patcher.cpp src/common/scm_rev.cpp src/common/scm_rev.h ) @@ -344,10 +404,8 @@ set(CORE src/core/aerolib/stubs.cpp src/core/aerolib/aerolib.h src/core/address_space.cpp src/core/address_space.h - src/core/cpu_patches.cpp - src/core/cpu_patches.h src/core/crypto/crypto.cpp - src/core/crypto/crypto.h + src/core/crypto/crypto.h src/core/crypto/keys.h src/core/file_format/pfs.h src/core/file_format/pkg.cpp @@ -372,7 +430,7 @@ set(CORE src/core/aerolib/stubs.cpp src/core/loader/elf.h src/core/loader/symbols_resolver.h src/core/loader/symbols_resolver.cpp - src/core/libraries/error_codes.h + src/core/libraries/error_codes.h src/core/libraries/libs.h src/core/libraries/libs.cpp ${AUDIO_LIB} @@ -397,17 +455,29 @@ set(CORE src/core/aerolib/stubs.cpp src/core/module.cpp src/core/module.h src/core/platform.h + src/core/signals.cpp + src/core/signals.h src/core/tls.cpp src/core/tls.h src/core/virtual_memory.cpp src/core/virtual_memory.h ) +if (ARCHITECTURE STREQUAL "x86_64") + set(CORE ${CORE} + src/core/cpu_patches.cpp + src/core/cpu_patches.h) +endif() + set(SHADER_RECOMPILER src/shader_recompiler/exception.h src/shader_recompiler/profile.h src/shader_recompiler/recompiler.cpp src/shader_recompiler/recompiler.h + src/shader_recompiler/info.h + src/shader_recompiler/params.h src/shader_recompiler/runtime_info.h + src/shader_recompiler/specialization.h + src/shader_recompiler/backend/bindings.h src/shader_recompiler/backend/spirv/emit_spirv.cpp src/shader_recompiler/backend/spirv/emit_spirv.h src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp @@ -513,6 +583,8 @@ set(VIDEO_CORE src/video_core/amdgpu/liverpool.cpp src/video_core/renderer_vulkan/vk_master_semaphore.h src/video_core/renderer_vulkan/vk_pipeline_cache.cpp src/video_core/renderer_vulkan/vk_pipeline_cache.h + src/video_core/renderer_vulkan/vk_pipeline_common.cpp + src/video_core/renderer_vulkan/vk_pipeline_common.h src/video_core/renderer_vulkan/vk_platform.cpp src/video_core/renderer_vulkan/vk_platform.h src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -521,8 +593,6 @@ set(VIDEO_CORE src/video_core/amdgpu/liverpool.cpp src/video_core/renderer_vulkan/vk_resource_pool.h src/video_core/renderer_vulkan/vk_scheduler.cpp src/video_core/renderer_vulkan/vk_scheduler.h - src/video_core/renderer_vulkan/vk_shader_cache.cpp - src/video_core/renderer_vulkan/vk_shader_cache.h src/video_core/renderer_vulkan/vk_shader_util.cpp src/video_core/renderer_vulkan/vk_shader_util.h src/video_core/renderer_vulkan/vk_swapchain.cpp @@ -540,6 +610,7 @@ set(VIDEO_CORE src/video_core/amdgpu/liverpool.cpp src/video_core/texture_cache/tile_manager.cpp src/video_core/texture_cache/tile_manager.h src/video_core/texture_cache/types.h + src/video_core/texture_cache/host_compatibility.h src/video_core/page_manager.cpp src/video_core/page_manager.h src/video_core/multi_level_page_table.h @@ -547,6 +618,22 @@ set(VIDEO_CORE src/video_core/amdgpu/liverpool.cpp src/video_core/renderdoc.h ) +set(IMGUI src/imgui/imgui_config.h + src/imgui/imgui_layer.h + src/imgui/imgui_std.h + src/imgui/imgui_texture.h + src/imgui/layer/video_info.cpp + src/imgui/layer/video_info.h + src/imgui/renderer/imgui_core.cpp + src/imgui/renderer/imgui_core.h + src/imgui/renderer/imgui_impl_sdl3.cpp + src/imgui/renderer/imgui_impl_sdl3.h + src/imgui/renderer/imgui_impl_vulkan.cpp + src/imgui/renderer/imgui_impl_vulkan.h + src/imgui/renderer/texture_manager.cpp + src/imgui/renderer/texture_manager.h +) + set(INPUT src/input/controller.cpp src/input/controller.h ) @@ -567,8 +654,6 @@ set(QT_GUI src/qt_gui/about_dialog.cpp src/qt_gui/about_dialog.ui src/qt_gui/cheats_patches.cpp src/qt_gui/cheats_patches.h - src/qt_gui/memory_patcher.cpp - src/qt_gui/memory_patcher.h src/qt_gui/main_window_ui.h src/qt_gui/main_window.cpp src/qt_gui/main_window.h @@ -603,6 +688,7 @@ endif() if (ENABLE_QT_GUI) qt_add_executable(shadps4 ${AUDIO_CORE} + ${IMGUI} ${INPUT} ${QT_GUI} ${COMMON} @@ -615,6 +701,7 @@ if (ENABLE_QT_GUI) else() add_executable(shadps4 ${AUDIO_CORE} + ${IMGUI} ${INPUT} ${COMMON} ${CORE} @@ -631,16 +718,26 @@ endif() create_target_directory_groups(shadps4) -target_link_libraries(shadps4 PRIVATE magic_enum::magic_enum fmt::fmt toml11::toml11 tsl::robin_map xbyak::xbyak Tracy::TracyClient RenderDoc::API FFmpeg::ffmpeg) -target_link_libraries(shadps4 PRIVATE Boost::headers GPUOpen::VulkanMemoryAllocator sirit Vulkan::Headers xxHash::xxhash Zydis::Zydis glslang::SPIRV glslang::glslang SDL3::SDL3) +target_link_libraries(shadps4 PRIVATE magic_enum::magic_enum fmt::fmt toml11::toml11 tsl::robin_map xbyak::xbyak Tracy::TracyClient RenderDoc::API FFmpeg::ffmpeg Dear_ImGui) +target_link_libraries(shadps4 PRIVATE Boost::headers GPUOpen::VulkanMemoryAllocator sirit Vulkan::Headers xxHash::xxhash Zydis::Zydis glslang::SPIRV glslang::glslang SDL3::SDL3 pugixml::pugixml) -if (APPLE) - # Reserve system-managed memory space. - target_link_options(shadps4 PRIVATE -Wl,-no_pie,-no_fixup_chains,-no_huge,-pagezero_size,0x400000,-segaddr,GUEST_SYSTEM,0x400000,-image_base,0x10000000000) +target_compile_definitions(shadps4 PRIVATE IMGUI_USER_CONFIG="imgui/imgui_config.h") +target_compile_definitions(Dear_ImGui PRIVATE IMGUI_USER_CONFIG="${PROJECT_SOURCE_DIR}/src/imgui/imgui_config.h") - # Link MoltenVK for Vulkan support - find_library(MOLTENVK MoltenVK REQUIRED) - target_link_libraries(shadps4 PRIVATE ${MOLTENVK}) +if (APPLE) + option(USE_SYSTEM_VULKAN_LOADER "Enables using the system Vulkan loader instead of directly linking with MoltenVK. Useful for loading validation layers." OFF) + if (USE_SYSTEM_VULKAN_LOADER) + target_compile_definitions(shadps4 PRIVATE USE_SYSTEM_VULKAN_LOADER=1) + else() + # Link MoltenVK for Vulkan support + find_library(MOLTENVK MoltenVK REQUIRED) + target_link_libraries(shadps4 PRIVATE ${MOLTENVK}) + endif() + + if (ARCHITECTURE STREQUAL "x86_64") + # Reserve system-managed memory space. + target_link_options(shadps4 PRIVATE -Wl,-no_pie,-no_fixup_chains,-no_huge,-pagezero_size,0x4000,-segaddr,TCB_SPACE,0x4000,-segaddr,GUEST_SYSTEM,0x400000,-image_base,0x20000000000) + endif() # Replacement for std::chrono::time_zone target_link_libraries(shadps4 PRIVATE date::date-tz) @@ -716,6 +813,11 @@ add_subdirectory(${HOST_SHADERS_INCLUDE}) add_dependencies(shadps4 host_shaders) target_include_directories(shadps4 PRIVATE ${HOST_SHADERS_INCLUDE}) +# ImGui resources +add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/src/imgui/renderer) +add_dependencies(shadps4 ImGui_Resources) +target_include_directories(shadps4 PRIVATE ${IMGUI_RESOURCES_INCLUDE}) + if (ENABLE_QT_GUI) set_target_properties(shadps4 PROPERTIES # WIN32_EXECUTABLE ON @@ -725,3 +827,10 @@ if (ENABLE_QT_GUI) set_source_files_properties(src/images/shadPS4.icns PROPERTIES MACOSX_PACKAGE_LOCATION Resources) endif() + +if (UNIX AND NOT APPLE) + if (ENABLE_QT_GUI) + find_package(OpenSSL REQUIRED) + target_link_libraries(shadps4 PRIVATE ${OPENSSL_LIBRARIES}) + endif() +endif() diff --git a/LICENSES/OFL-1.1.txt b/LICENSES/OFL-1.1.txt new file mode 100644 index 00000000000..6fe84ee21eb --- /dev/null +++ b/LICENSES/OFL-1.1.txt @@ -0,0 +1,43 @@ +SIL OPEN FONT LICENSE + +Version 1.1 - 26 February 2007 + +PREAMBLE + +The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others. + +The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives. + +DEFINITIONS + +"Font Software" refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the copyright statement(s). + +"Original Version" refers to the collection of Font Software components as distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, or substituting — in part or in whole — any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment. + +"Author" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS + +Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission. + +5) The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software. + +TERMINATION + +This license becomes null and void if any of the above conditions are not met. + +DISCLAIMER + +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/README.md b/README.md index c99142c78f2..1be14c4fa28 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ SPDX-License-Identifier: GPL-2.0-or-later

- + @@ -40,14 +40,14 @@ If you encounter problems or have doubts, do not hesitate to look at the [**Quic To verify that a game works, you can look at [**shadPS4 Game Compatibility**](https://github.com/shadps4-emu/shadps4-game-compatibility). -To discuss shadPS4 development, suggest ideas or to ask for help, join our [**Discord server**](https://discord.gg/MyZRaBngxA). +To discuss shadPS4 development, suggest ideas or to ask for help, join our [**Discord server**](https://discord.gg/bFJxfftGW6). To get the latest news, go to our [**X (Twitter)**](https://x.com/shadps4) or our [**website**](https://shadps4.net/). # Status > [!IMPORTANT] -> shadPS4 is early in developement, don't expect a flawless experience. +> shadPS4 is early in development, don't expect a flawless experience. Currently, the emulator successfully runs small games like [**Sonic Mania**](https://www.youtube.com/watch?v=AAHoNzhHyCU), [**Undertale**](https://youtu.be/5zIvdy65Ro4) and it can even *somewhat* run [**Bloodborne**](https://www.youtube.com/watch?v=wC6s0avpQRE). @@ -65,6 +65,12 @@ Check the build instructions for [**Windows**](https://github.com/shadps4-emu/sh Check the build instructions for [**Linux**](https://github.com/shadps4-emu/shadPS4/blob/main/documents/building-linux.md). +## macOS + +Check the build instructions for [**macOS**](https://github.com/shadps4-emu/shadPS4/blob/main/documents/building-macos.md). + +Note that macOS users need at least macOS 15 on an Apple Silicon Mac, or at least macOS 11 on an Intel Mac. + ## Building status
@@ -153,6 +159,20 @@ Open a PR and we'll check it :) + +# Special Thanks + +A few noteworthy teams/projects who've helped us along the way are: + +- [**Panda3DS**](https://github.com/wheremyfoodat/Panda3DS): A multiplatform 3DS emulator from our co-author wheremyfoodat. They have been incredibly helpful in understanding and solving problems that came up from natively executing the x64 code of PS4 binaries + +- [**fpPS4**](https://github.com/red-prig/fpPS4): The fpPS4 team has assisted massively with understanding some of the more complex parts of the PS4 operating system and libraries, by helping with reverse engineering work and research. + +- **yuzu**: Our shader compiler has been designed with yuzu's Hades compiler as a blueprint. This allowed us to focus on the challenges of emulating a modern AMD GPU while having a high-quality optimizing shader compiler implementation as a base. + +- [**hydra**](https://github.com/hydra-emu/hydra): A multisystem, multiplatform emulator (chip-8, GB, NES, N64) from Paris. + + # Sister Projects - [**Panda3DS**](https://github.com/wheremyfoodat/Panda3DS): A multiplatform 3DS emulator from our co-author wheremyfoodat. diff --git a/documents/building-linux.md b/documents/building-linux.md index 9645d8b4fcb..622de543b75 100644 --- a/documents/building-linux.md +++ b/documents/building-linux.md @@ -9,7 +9,7 @@ SPDX-License-Identifier: GPL-2.0-or-later #### Debian & Ubuntu ``` -sudo apt-get install build-essential libasound2-dev libpulse-dev libopenal-dev zlib1g-dev libedit-dev libvulkan-dev libudev-dev git libevdev-dev libsdl2-2.0 libsdl2-dev libjack-dev libsndio-dev +sudo apt-get install build-essential libasound2-dev libpulse-dev libopenal-dev zlib1g-dev libedit-dev libvulkan-dev libudev-dev git libevdev-dev libsdl2-2.0 libsdl2-dev libjack-dev libsndio-dev qt6-base-dev qt6-tools-dev ``` #### Fedora @@ -34,9 +34,9 @@ git clone --recursive https://github.com/shadps4-emu/shadPS4.git cd shadPS4 ``` -Generate the build directory in the shadPS4 directory: +Generate the build directory in the shadPS4 directory. To enable the QT GUI, pass the ```-DENABLE_QT_GUI=ON``` flag: ``` -cmake -S . -B build/ +cmake -S . -B build/ -DENABLE_QT_GUI=ON ``` Enter the directory: @@ -49,8 +49,11 @@ Use make to build the project: cmake --build . --parallel$(nproc) ``` -Now run the emulator: - +Now run the emulator. If QT is enabled: +``` +./shadps4 +``` +Otherwise, specify the path to your PKG's boot file: ``` ./shadps4 /"PATH"/"TO"/"GAME"/"FOLDER"/eboot.bin ``` diff --git a/documents/building-macos.md b/documents/building-macos.md new file mode 100644 index 00000000000..d8cc414e2de --- /dev/null +++ b/documents/building-macos.md @@ -0,0 +1,73 @@ + + +## Build shadPS4 for macOS + +### Install the necessary tools to build shadPS4: + +First, make sure you have **Xcode 16.0 or newer** installed. + +For installing other tools and library dependencies we will be using [Homebrew](https://brew.sh/). + +On an ARM system, we will need the native ARM Homebrew to install tools and x86_64 Homebrew to install libraries. + +First, install native Homebrew and tools: +``` +# Installs native Homebrew to /opt/homebrew +/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" +# Adds Homebrew to your path +echo 'eval $(/opt/homebrew/bin/brew shellenv)' >> ~/.zprofile +eval $(/opt/homebrew/bin/brew shellenv) +# Installs tools. +brew install clang-format cmake +``` + +Next, install x86_64 Homebrew and libraries. + +**If you are on an ARM Mac:** +``` +# Installs x86_64 Homebrew to /usr/local +arch -x86_64 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" +# Installs libraries. +arch -x86_64 /usr/local/bin/brew install molten-vk qt@6 +``` + +**If you are on an x86_64 Mac:** +``` +brew install molten-vk qt@6 +``` + +If you don't need the Qt GUI you can remove `qt@6` from the last command. + +### Cloning and compiling: + +Clone the repository recursively: +``` +git clone --recursive https://github.com/shadps4-emu/shadPS4.git +cd shadPS4 +``` + +Generate the build directory in the shadPS4 directory: +``` +cmake -S . -B build/ -DCMAKE_OSX_ARCHITECTURES=x86_64 +``` + +If you want to build the Qt GUI, add `-DENABLE_QT_GUI=ON` to the end of this command as well. + +Enter the directory: +``` +cd build/ +``` + +Use make to build the project: +``` +cmake --build . --parallel$(sysctl -n hw.ncpu) +``` + +Now run the emulator: + +``` +./shadps4 /"PATH"/"TO"/"GAME"/"FOLDER"/eboot.bin +``` diff --git a/documents/building-windows.md b/documents/building-windows.md index 21fd8715470..fb1bb93caa3 100644 --- a/documents/building-windows.md +++ b/documents/building-windows.md @@ -6,7 +6,7 @@ SPDX-License-Identifier: GPL-2.0-or-later # Build shadPS4 for Windows This tutorial reads as if you have none of the prerequisites already installed. If you do, just ignore the steps regarding installation. -If you are building to contribute to the project, please omit `--depth 1` from the git invokations. +If you are building to contribute to the project, please omit `--depth 1` from the git invocations. Note: **ARM64 is not supported!** As of writing, it will not build nor run. The instructions with respect to ARM64 are for developers only. @@ -15,6 +15,7 @@ Note: **ARM64 is not supported!** As of writing, it will not build nor run. The ### (Prerequisite) Download the Community edition from [**Visual Studio 2022**](https://visualstudio.microsoft.com/vs/) Once you are within the installer: + 1. Select `Desktop development with C++` 2. Go to "Individual Components" tab 3. Search and select `C++ Clang Compiler for Windows` and `MSBuild support for LLVM` @@ -30,11 +31,12 @@ Beware, this requires you to create a Qt account. If you do not want to do this, Go through the installation normally. If you know what you are doing, you may unselect individual components that eat up too much disk space. 2. Download and install [Qt Visual Studio Tools](https://marketplace.visualstudio.com/items?itemName=TheQtCompany.QtVisualStudioTools2022) - + Once you are finished, you will have to configure Qt within Visual Studio: + 1. Tools -> Options -> Qt -> Versions -2. Add a new Qt version and navigate it to the correct folder. Should look like so: `C:\Qt\6.7.2\msvc2019_64` -3. Enable the default checkmark on the new version you just created. +2. Add a new Qt version and navigate it to the correct folder. Should look like so: `C:\Qt\6.7.2\msvc2019_64` +3. Enable the default checkmark on the new version you just created. ### (Prerequisite) Download [**Git for Windows**](https://git-scm.com/download/win) @@ -62,7 +64,7 @@ Go through the Git for Windows installation as normal Your shadps4.exe will be in `c:\path\to\source\Build\x64-Clang-Release\` To automatically populate the necessary files to run shadPS4.exe, run in a command prompt or terminal: -`C:\Qt\6.7.2\msvc2019_64\bin\windeployqt.exe c:\path\to\shadps4.exe` +`C:\Qt\6.7.2\msvc2019_64\bin\windeployqt.exe "c:\path\to\shadps4.exe"` (Change Qt path if you've installed it to non-default path) ## Option 2: MSYS2/MinGW @@ -74,27 +76,35 @@ Go through the MSYS2 installation as normal If you are building to distribute, please omit `-DCMAKE_CXX_FLAGS="-O2 -march=native"` within the build configuration step. Normal x86-based computers, follow: + 1. Open "MSYS2 MINGW64" from your new applications 2. Run `pacman -Syu`, let it complete; -3. Run `pacman -S --needed git mingw-w64-x86_64-binutils mingw-w64-x86_64-clang mingw-w64-x86_64-cmake mingw-w64-x86_64-ninja mingw-w64-x86_64-qt6-base` +3. Run `pacman -S --needed git mingw-w64-x86_64-binutils mingw-w64-x86_64-clang mingw-w64-x86_64-cmake mingw-w64-x86_64-ninja mingw-w64-x86_64-ffmpeg` + 1. Optional (Qt only): run `pacman -S --needed mingw-w64-x86_64-qt6-base mingw-w64-x86_64-qt6-tools` 4. Run `git clone --depth 1 --recursive https://github.com/shadps4-emu/shadPS4` 5. Run `cd shadPS4` -6. Run `cmake -S . -B build -DCMAKE_CXX_COMPILER="clang++.exe" -DCMAKE_C_COMPILER="clang.exe" -DCMAKE_CXX_FLAGS="-O2 -march=native"` +6. Run `cmake -S . -B build -DCMAKE_C_COMPILER="clang.exe" -DCMAKE_CXX_COMPILER="clang++.exe" -DCMAKE_CXX_FLAGS="-O2 -march=native"` + 1. Optional (Qt only): add `-DENABLE_QT_GUI=ON` 7. Run `cmake --build build` + 1. Optional (Qt only): run `windeployqt6 build/shadps4.exe` 8. To run the finished product, run `./build/shadPS4.exe` ARM64-based computers, follow: + 1. Open "MSYS2 CLANGARM64" from your new applications 2. Run `pacman -Syu`, let it complete; -3. Run `pacman -S --needed git mingw-w64-clang-aarch64-binutils mingw-w64-clang-aarch64-clang mingw-w64-clang-aarch64-cmake mingw-w64-clang-aarch64-ninja mingw-w64-clang-aarch64-qt6-base` +3. Run `pacman -S --needed git mingw-w64-clang-aarch64-binutils mingw-w64-clang-aarch64-clang mingw-w64-clang-aarch64-cmake mingw-w64-clang-aarch64-ninja mingw-w64-clang-aarch64-ffmpeg` + 1. Optional (Qt only): run `pacman -S --needed mingw-w64-clang-aarch64-qt6-base mingw-w64-clang-aarch64-qt6-tools` 4. Run `git clone --depth 1 --recursive https://github.com/shadps4-emu/shadPS4` 5. Run `cd shadPS4` -6. Run `cmake -S . -B build -DCMAKE_CXX_COMPILER="clang++.exe" -DCMAKE_C_COMPILER="clang.exe" -DCMAKE_CXX_FLAGS="-O2 -march=native"` +6. Run `cmake -S . -B build -DCMAKE_C_COMPILER="clang.exe" -DCMAKE_CXX_COMPILER="clang++.exe" -DCMAKE_CXX_FLAGS="-O2 -march=native"` + 1. Optional (Qt only): add `-DENABLE_QT_GUI=ON` 7. Run `cmake --build build` + 1. Optional (Qt only): run `windeployqt6 build/shadps4.exe` 8. To run the finished product, run `./build/shadPS4.exe` ## Note on MSYS2 builds These builds may not be easily copyable to people who do not also have a MSYS2 installation. If you want to distribute these builds, you need to copy over the correct DLLs into a distribution folder. -In order to run them, you must be within the MSYS2 shell environment. \ No newline at end of file +In order to run them, you must be within the MSYS2 shell environment. diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index de0317ff900..5410f37ebd3 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -43,7 +43,6 @@ else() endif() if (NOT TARGET FFmpeg::ffmpeg) - set(ARCHITECTURE "x86_64") add_subdirectory(ffmpeg-core) add_library(FFmpeg::ffmpeg ALIAS ffmpeg) endif() @@ -155,6 +154,17 @@ if (APPLE) endif() endif() +# Dear ImGui +add_library(Dear_ImGui + dear_imgui/imgui.cpp + dear_imgui/imgui_demo.cpp + dear_imgui/imgui_draw.cpp + dear_imgui/imgui_internal.h + dear_imgui/imgui_tables.cpp + dear_imgui/imgui_widgets.cpp +) +target_include_directories(Dear_ImGui INTERFACE dear_imgui/) + # Tracy option(TRACY_ENABLE "" ON) option(TRACY_NO_CRASH_HANDLER "" ON) # Otherwise texture cache exceptions will be treaten as a crash @@ -168,3 +178,8 @@ option(TRACY_NO_SAMPLING "" ON) option(TRACY_ONLY_LOCALHOST "" ON) option(TRACY_NO_CONTEXT_SWITCH "" ON) add_subdirectory(tracy) + +# pugixml +if (NOT TARGET pugixml::pugixml) + add_subdirectory(pugixml) +endif() \ No newline at end of file diff --git a/externals/dear_imgui b/externals/dear_imgui new file mode 160000 index 00000000000..636cd4a7d62 --- /dev/null +++ b/externals/dear_imgui @@ -0,0 +1 @@ +Subproject commit 636cd4a7d623a2bc9bf59bb3acbb4ca075befba3 diff --git a/externals/pugixml b/externals/pugixml new file mode 160000 index 00000000000..30cc354fe37 --- /dev/null +++ b/externals/pugixml @@ -0,0 +1 @@ +Subproject commit 30cc354fe37114ec7a0a4ed2192951690357c2ed diff --git a/externals/sirit b/externals/sirit index 8db09231c44..37090c74cc6 160000 --- a/externals/sirit +++ b/externals/sirit @@ -1 +1 @@ -Subproject commit 8db09231c448b913ae905d5237ce2eca46e3fe87 +Subproject commit 37090c74cc6e680f2bc334cac8fd182f7634a1f6 diff --git a/scripts/file_formats/sfo.hexpat b/scripts/file_formats/sfo.hexpat new file mode 100644 index 00000000000..cfc1f87896e --- /dev/null +++ b/scripts/file_formats/sfo.hexpat @@ -0,0 +1,52 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +import std.io; +import std.sys; + +struct Header { + u32 magic; + u32 version; + u32 key_table_offset; + u32 data_table_offset; + u32 index_table_entries; +}; + +struct KeyEntry { + char name[]; +} [[inline]]; + +struct DataEntry { + if (fmt == 0x0404) { + u32 int_value; + } else if(fmt == 0x0004) { + char bin_value[size]; + } else if(fmt == 0x0204) { + char str_value[size]; + } else { + std::warning("unknown fmt type"); + } +} [[inline]]; + +struct IndexEntry { + u16 key_offset; + u16 param_fmt; + u32 param_len; + u32 param_max_len; + u32 data_offset; +}; + +struct Entry { + u64 begin = $; + IndexEntry index; + KeyEntry key @ KeyTableOffset + index.key_offset; + DataEntry data @ DataTableOffset + index.data_offset; + u8 data_empty[index.param_max_len - index.param_len] @ DataTableOffset + index.data_offset + index.param_len; + $ = begin + sizeof(IndexEntry); +}; + +Header header @ 0; +std::assert(header.magic == 0x46535000, "Miss match magic"); +std::assert(header.version == 0x00000101, "Miss match version"); + +Entry list[header.index_table_entries] @ 0x14; \ No newline at end of file diff --git a/src/common/arch.h b/src/common/arch.h new file mode 100644 index 00000000000..b22366cb7c9 --- /dev/null +++ b/src/common/arch.h @@ -0,0 +1,10 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#if defined(__x86_64__) || defined(_M_X64) +#define ARCH_X86_64 1 +#elif defined(__aarch64__) || defined(_M_ARM64) +#define ARCH_ARM64 1 +#endif diff --git a/src/common/assert.cpp b/src/common/assert.cpp index 3a49c9396b9..be0feb71dac 100644 --- a/src/common/assert.cpp +++ b/src/common/assert.cpp @@ -1,10 +1,17 @@ // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include "common/arch.h" #include "common/assert.h" #include "common/logging/backend.h" +#if defined(ARCH_X86_64) #define Crash() __asm__ __volatile__("int $3") +#elif defined(ARCH_ARM64) +#define Crash() __asm__ __volatile__("brk 0") +#else +#error "Missing Crash() implementation for target CPU architecture." +#endif void assert_fail_impl() { Common::Log::Stop(); @@ -18,3 +25,8 @@ void assert_fail_impl() { Crash(); throw std::runtime_error("Unreachable code"); } + +void assert_fail_debug_msg(const char* msg) { + LOG_CRITICAL(Debug, "Assertion Failed!\n{}", msg); + assert_fail_impl(); +} diff --git a/src/common/config.cpp b/src/common/config.cpp index 8d87ed3c3a3..fb6ee120a11 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -30,7 +30,9 @@ static bool vkValidation = false; static bool vkValidationSync = false; static bool vkValidationGpu = false; static bool rdocEnable = false; -static bool rdocMarkersEnable = false; +static bool vkMarkers = false; +static bool vkCrashDiagnostic = false; + // Gui std::string settings_install_dir = ""; u32 main_window_geometry_x = 400; @@ -121,7 +123,7 @@ bool isRdocEnabled() { } bool isMarkersEnabled() { - return rdocMarkersEnable; + return vkMarkers; } u32 vblankDiv() { @@ -140,6 +142,14 @@ bool vkValidationGpuEnabled() { return vkValidationGpu; } +bool vkMarkersEnabled() { + return vkMarkers || vkCrashDiagnostic; // Crash diagnostic forces markers on +} + +bool vkCrashDiagnosticEnabled() { + return vkCrashDiagnostic; +} + void setGpuId(s32 selectedGpuId) { gpuId = selectedGpuId; } @@ -384,7 +394,8 @@ void load(const std::filesystem::path& path) { vkValidationSync = toml::find_or(vk, "validation_sync", false); vkValidationGpu = toml::find_or(vk, "validation_gpu", true); rdocEnable = toml::find_or(vk, "rdocEnable", false); - rdocMarkersEnable = toml::find_or(vk, "rdocMarkersEnable", false); + vkMarkers = toml::find_or(vk, "rdocMarkersEnable", false); + vkCrashDiagnostic = toml::find_or(vk, "crashDiagnostic", false); } if (data.contains("Debug")) { @@ -460,7 +471,8 @@ void save(const std::filesystem::path& path) { data["Vulkan"]["validation_sync"] = vkValidationSync; data["Vulkan"]["validation_gpu"] = vkValidationGpu; data["Vulkan"]["rdocEnable"] = rdocEnable; - data["Vulkan"]["rdocMarkersEnable"] = rdocMarkersEnable; + data["Vulkan"]["rdocMarkersEnable"] = vkMarkers; + data["Vulkan"]["crashDiagnostic"] = vkCrashDiagnostic; data["Debug"]["DebugDump"] = isDebugDump; data["GUI"]["theme"] = mw_themes; data["GUI"]["iconSize"] = m_icon_size; @@ -504,7 +516,11 @@ void setDefaultValues() { shouldDumpPM4 = false; vblankDivider = 1; vkValidation = false; + vkValidationSync = false; + vkValidationGpu = false; rdocEnable = false; + vkMarkers = false; + vkCrashDiagnostic = false; emulator_language = "en"; m_language = 1; gpuId = -1; diff --git a/src/common/config.h b/src/common/config.h index 11e7d882789..7e717fe71bf 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -31,7 +31,6 @@ bool copyGPUCmdBuffers(); bool dumpShaders(); bool dumpPM4(); bool isRdocEnabled(); -bool isMarkersEnabled(); u32 vblankDiv(); void setDebugDump(bool enable); @@ -62,6 +61,8 @@ void setRdocEnabled(bool enable); bool vkValidationEnabled(); bool vkValidationSyncEnabled(); bool vkValidationGpuEnabled(); +bool vkMarkersEnabled(); +bool vkCrashDiagnosticEnabled(); // Gui void setMainWindowGeometry(u32 x, u32 y, u32 w, u32 h); diff --git a/src/common/cstring.h b/src/common/cstring.h new file mode 100644 index 00000000000..fb29443ee30 --- /dev/null +++ b/src/common/cstring.h @@ -0,0 +1,157 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "assert.h" + +namespace Common { + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wtautological-undefined-compare" + +/** + * @brief A null-terminated string with a fixed maximum length + * This class is not meant to be used as a general-purpose string class + * It is meant to be used as `char[N]` where memory layout is fixed + * @tparam N Maximum length of the string + * @tparam T Type of character + */ +template +class CString { + T data[N]{}; + +public: + class Iterator; + + CString() = default; + + template + explicit CString(const CString& other) + requires(M <= N) + { + if (this == nullptr) { + return; + } + std::ranges::copy(other.begin(), other.end(), data); + } + + void FromString(const std::basic_string_view& str) { + if (this == nullptr) { + return; + } + size_t p = str.copy(data, N - 1); + data[p] = '\0'; + } + + void Zero() { + if (this == nullptr) { + return; + } + std::ranges::fill(data, 0); + } + + explicit(false) operator std::basic_string_view() const { + if (this == nullptr) { + return {}; + } + return std::basic_string_view{data}; + } + + explicit operator std::basic_string() const { + if (this == nullptr) { + return {}; + } + return std::basic_string{data}; + } + + std::basic_string to_string() const { + if (this == nullptr) { + return {}; + } + return std::basic_string{data}; + } + + std::basic_string_view to_view() const { + if (this == nullptr) { + return {}; + } + return std::basic_string_view{data}; + } + + char* begin() { + if (this == nullptr) { + return nullptr; + } + return data; + } + + const char* begin() const { + if (this == nullptr) { + return nullptr; + } + return data; + } + + char* end() { + if (this == nullptr) { + return nullptr; + } + return data + N; + } + + const char* end() const { + if (this == nullptr) { + return nullptr; + } + return data + N; + } + + T& operator[](size_t idx) { + return data[idx]; + } + + const T& operator[](size_t idx) const { + return data[idx]; + } + + class Iterator { + T* ptr; + T* end; + + public: + using difference_type = std::ptrdiff_t; + using value_type = T; + using pointer = T*; + using reference = T&; + using iterator_category = std::random_access_iterator_tag; + + Iterator() = default; + explicit Iterator(T* ptr) : ptr(ptr), end(ptr + N) {} + + Iterator& operator++() { + ++ptr; + return *this; + } + + Iterator operator++(int) { + Iterator tmp = *this; + ++ptr; + return tmp; + } + + operator T*() { + ASSERT_MSG(ptr >= end, "CString iterator out of bounds"); + return ptr; + } + }; +}; + +static_assert(sizeof(CString<13>) == sizeof(char[13])); // Ensure size still matches a simple array +static_assert(std::weakly_incrementable::Iterator>); + +#pragma clang diagnostic pop + +} // namespace Common \ No newline at end of file diff --git a/src/common/disassembler.cpp b/src/common/decoder.cpp similarity index 65% rename from src/common/disassembler.cpp rename to src/common/decoder.cpp index 2d1264a4eb8..24990741979 100644 --- a/src/common/disassembler.cpp +++ b/src/common/decoder.cpp @@ -2,18 +2,18 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include -#include "common/disassembler.h" +#include "common/decoder.h" namespace Common { -Disassembler::Disassembler() { +DecoderImpl::DecoderImpl() { ZydisDecoderInit(&m_decoder, ZYDIS_MACHINE_MODE_LONG_64, ZYDIS_STACK_WIDTH_64); ZydisFormatterInit(&m_formatter, ZYDIS_FORMATTER_STYLE_INTEL); } -Disassembler::~Disassembler() = default; +DecoderImpl::~DecoderImpl() = default; -void Disassembler::printInstruction(void* code, u64 address) { +void DecoderImpl::printInstruction(void* code, u64 address) { ZydisDecodedInstruction instruction; ZydisDecodedOperand operands[ZYDIS_MAX_OPERAND_COUNT_VISIBLE]; ZyanStatus status = @@ -25,8 +25,8 @@ void Disassembler::printInstruction(void* code, u64 address) { } } -void Disassembler::printInst(ZydisDecodedInstruction& inst, ZydisDecodedOperand* operands, - u64 address) { +void DecoderImpl::printInst(ZydisDecodedInstruction& inst, ZydisDecodedOperand* operands, + u64 address) { const int bufLen = 256; char szBuffer[bufLen]; ZydisFormatterFormatInstruction(&m_formatter, &inst, operands, inst.operand_count_visible, @@ -34,4 +34,9 @@ void Disassembler::printInst(ZydisDecodedInstruction& inst, ZydisDecodedOperand* fmt::print("instruction: {}\n", szBuffer); } +ZyanStatus DecoderImpl::decodeInstruction(ZydisDecodedInstruction& inst, + ZydisDecodedOperand* operands, void* data, u64 size) { + return ZydisDecoderDecodeFull(&m_decoder, data, size, &inst, operands); +} + } // namespace Common diff --git a/src/common/disassembler.h b/src/common/decoder.h similarity index 60% rename from src/common/disassembler.h rename to src/common/decoder.h index b81f9e31bc3..1f221959688 100644 --- a/src/common/disassembler.h +++ b/src/common/decoder.h @@ -4,21 +4,26 @@ #pragma once #include +#include "common/singleton.h" #include "common/types.h" namespace Common { -class Disassembler { +class DecoderImpl { public: - Disassembler(); - ~Disassembler(); + DecoderImpl(); + ~DecoderImpl(); void printInst(ZydisDecodedInstruction& inst, ZydisDecodedOperand* operands, u64 address); void printInstruction(void* code, u64 address); + ZyanStatus decodeInstruction(ZydisDecodedInstruction& inst, ZydisDecodedOperand* operands, + void* data, u64 size = 15); private: ZydisDecoder m_decoder; ZydisFormatter m_formatter; }; +using Decoder = Common::Singleton; + } // namespace Common diff --git a/src/common/elf_info.h b/src/common/elf_info.h new file mode 100644 index 00000000000..5a2c914e04c --- /dev/null +++ b/src/common/elf_info.h @@ -0,0 +1,72 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +#include "assert.h" +#include "singleton.h" +#include "types.h" + +namespace Core { +class Emulator; +} + +namespace Common { + +class ElfInfo { + friend class Core::Emulator; + + bool initialized = false; + + std::string game_serial{}; + std::string title{}; + std::string app_ver{}; + u32 firmware_ver = 0; + u32 raw_firmware_ver = 0; + +public: + static constexpr u32 FW_15 = 0x1500000; + static constexpr u32 FW_16 = 0x1600000; + static constexpr u32 FW_17 = 0x1700000; + static constexpr u32 FW_20 = 0x2000000; + static constexpr u32 FW_25 = 0x2500000; + static constexpr u32 FW_30 = 0x3000000; + static constexpr u32 FW_40 = 0x4000000; + static constexpr u32 FW_45 = 0x4500000; + static constexpr u32 FW_50 = 0x5000000; + static constexpr u32 FW_80 = 0x8000000; + + static ElfInfo& Instance() { + return *Singleton::Instance(); + } + + [[nodiscard]] std::string_view GameSerial() const { + ASSERT(initialized); + return Instance().game_serial; + } + + [[nodiscard]] std::string_view Title() const { + ASSERT(initialized); + return title; + } + + [[nodiscard]] std::string_view AppVer() const { + ASSERT(initialized); + return app_ver; + } + + [[nodiscard]] u32 FirmwareVer() const { + ASSERT(initialized); + return firmware_ver; + } + + [[nodiscard]] u32 RawFirmwareVer() const { + ASSERT(initialized); + return raw_firmware_ver; + } +}; + +} // namespace Common diff --git a/src/common/fixed_value.h b/src/common/fixed_value.h new file mode 100644 index 00000000000..e32a795f223 --- /dev/null +++ b/src/common/fixed_value.h @@ -0,0 +1,35 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +/** + * @brief A template class that encapsulates a fixed, compile-time constant value. + * + * @tparam T The type of the value. + * @tparam Value The fixed value of type T. + * + * This class provides a way to encapsulate a value that is constant and known at compile-time. + * The value is stored as a private member and cannot be changed. Any attempt to assign a new + * value to an object of this class will reset it to the fixed value. + */ +template +class FixedValue { + T m_value{Value}; + +public: + constexpr FixedValue() = default; + + constexpr explicit(false) operator T() const { + return m_value; + } + + FixedValue& operator=(const T&) { + m_value = Value; + return *this; + } + FixedValue& operator=(T&&) noexcept { + m_value = {Value}; + return *this; + } +}; diff --git a/src/common/io_file.cpp b/src/common/io_file.cpp index fbc37a10c98..c1d9cc59207 100644 --- a/src/common/io_file.cpp +++ b/src/common/io_file.cpp @@ -396,4 +396,18 @@ s64 IOFile::Tell() const { return ftello(file); } +u64 GetDirectorySize(const std::filesystem::path& path) { + if (!fs::exists(path)) { + return 0; + } + + u64 total = 0; + for (const auto& entry : fs::recursive_directory_iterator(path)) { + if (fs::is_regular_file(entry.path())) { + total += fs::file_size(entry.path()); + } + } + return total; +} + } // namespace Common::FS diff --git a/src/common/io_file.h b/src/common/io_file.h index 2c3df3f69c2..177bddbad63 100644 --- a/src/common/io_file.h +++ b/src/common/io_file.h @@ -219,4 +219,6 @@ class IOFile final { uintptr_t file_mapping = 0; }; +u64 GetDirectorySize(const std::filesystem::path& path); + } // namespace Common::FS diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp index a21af8bbaab..7802977f5f7 100644 --- a/src/common/logging/backend.cpp +++ b/src/common/logging/backend.cpp @@ -144,6 +144,10 @@ class Impl { initialization_in_progress_suppress_logging = false; } + static bool IsActive() { + return instance != nullptr; + } + static void Start() { instance->StartBackendThread(); } @@ -275,6 +279,10 @@ void Initialize(std::string_view log_file) { Impl::Initialize(log_file.empty() ? LOG_FILE : log_file); } +bool IsActive() { + return Impl::IsActive(); +} + void Start() { Impl::Start(); } diff --git a/src/common/logging/backend.h b/src/common/logging/backend.h index 91c9da8327a..a1ad663691e 100644 --- a/src/common/logging/backend.h +++ b/src/common/logging/backend.h @@ -13,6 +13,8 @@ class Filter; /// Initializes the logging system. This should be the first thing called in main. void Initialize(std::string_view log_file = ""); +bool IsActive(); + /// Starts the logging threads. void Start(); diff --git a/src/common/logging/filter.cpp b/src/common/logging/filter.cpp index ab3468ca077..c3088f926ce 100644 --- a/src/common/logging/filter.cpp +++ b/src/common/logging/filter.cpp @@ -113,10 +113,12 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) { SUB(Lib, ImeDialog) \ SUB(Lib, AvPlayer) \ SUB(Lib, Ngs2) \ + SUB(Lib, Audio3d) \ CLS(Frontend) \ CLS(Render) \ SUB(Render, Vulkan) \ SUB(Render, Recompiler) \ + CLS(ImGui) \ CLS(Input) \ CLS(Tty) \ CLS(Loader) diff --git a/src/common/logging/types.h b/src/common/logging/types.h index dd2376ea67e..749568da169 100644 --- a/src/common/logging/types.h +++ b/src/common/logging/types.h @@ -80,10 +80,12 @@ enum class Class : u8 { Lib_ImeDialog, ///< The LibSceImeDialog implementation. Lib_AvPlayer, ///< The LibSceAvPlayer implementation. Lib_Ngs2, ///< The LibSceNgs2 implementation. + Lib_Audio3d, ///< The LibSceAudio3d implementation. Frontend, ///< Emulator UI Render, ///< Video Core Render_Vulkan, ///< Vulkan backend Render_Recompiler, ///< Shader recompiler + ImGui, ///< ImGui Loader, ///< ROM loader Input, ///< Input emulation Tty, ///< Debug output from emu diff --git a/src/qt_gui/memory_patcher.cpp b/src/common/memory_patcher.cpp similarity index 66% rename from src/qt_gui/memory_patcher.cpp rename to src/common/memory_patcher.cpp index d5ffa1c99bf..85a25167a30 100644 --- a/src/qt_gui/memory_patcher.cpp +++ b/src/common/memory_patcher.cpp @@ -2,14 +2,20 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include +#include +#include #include +#include +#ifdef ENABLE_QT_GUI #include #include #include #include #include #include +#include #include +#endif #include "common/logging/log.h" #include "common/path_util.h" #include "memory_patcher.h" @@ -17,78 +23,169 @@ namespace MemoryPatcher { uintptr_t g_eboot_address; -u64 g_eboot_image_size; +uint64_t g_eboot_image_size; std::string g_game_serial; +std::string patchFile; std::vector pending_patches; -QString toHex(unsigned long long value, size_t byteSize) { +std::string toHex(unsigned long long value, size_t byteSize) { std::stringstream ss; ss << std::hex << std::setfill('0') << std::setw(byteSize * 2) << value; - return QString::fromStdString(ss.str()); + return ss.str(); } -QString convertValueToHex(const QString& type, const QString& valueStr) { - QString result; - std::string typeStr = type.toStdString(); - std::string valueStrStd = valueStr.toStdString(); +std::string convertValueToHex(const std::string type, const std::string valueStr) { + std::string result; - if (typeStr == "byte") { - unsigned int value = std::stoul(valueStrStd, nullptr, 16); + if (type == "byte") { + unsigned int value = std::stoul(valueStr, nullptr, 16); result = toHex(value, 1); - } else if (typeStr == "bytes16") { - unsigned int value = std::stoul(valueStrStd, nullptr, 16); + } else if (type == "bytes16") { + unsigned int value = std::stoul(valueStr, nullptr, 16); result = toHex(value, 2); - } else if (typeStr == "bytes32") { - unsigned long value = std::stoul(valueStrStd, nullptr, 16); + } else if (type == "bytes32") { + unsigned long value = std::stoul(valueStr, nullptr, 16); result = toHex(value, 4); - } else if (typeStr == "bytes64") { - unsigned long long value = std::stoull(valueStrStd, nullptr, 16); + } else if (type == "bytes64") { + unsigned long long value = std::stoull(valueStr, nullptr, 16); result = toHex(value, 8); - } else if (typeStr == "float32") { + } else if (type == "float32") { union { float f; uint32_t i; } floatUnion; - floatUnion.f = std::stof(valueStrStd); + floatUnion.f = std::stof(valueStr); result = toHex(floatUnion.i, sizeof(floatUnion.i)); - } else if (typeStr == "float64") { + } else if (type == "float64") { union { double d; uint64_t i; } doubleUnion; - doubleUnion.d = std::stod(valueStrStd); + doubleUnion.d = std::stod(valueStr); result = toHex(doubleUnion.i, sizeof(doubleUnion.i)); - } else if (typeStr == "utf8") { - QByteArray byteArray = QString::fromStdString(valueStrStd).toUtf8(); - byteArray.append('\0'); + } else if (type == "utf8") { + std::vector byteArray = + std::vector(valueStr.begin(), valueStr.end()); + byteArray.push_back('\0'); std::stringstream ss; for (unsigned char c : byteArray) { ss << std::hex << std::setfill('0') << std::setw(2) << static_cast(c); } - result = QString::fromStdString(ss.str()); - } else if (typeStr == "utf16") { - QByteArray byteArray( - reinterpret_cast(QString::fromStdString(valueStrStd).utf16()), - QString::fromStdString(valueStrStd).size() * 2); - byteArray.append('\0'); - byteArray.append('\0'); + result = ss.str(); + } else if (type == "utf16") { + std::wstring wide_str(valueStr.size(), L'\0'); + std::mbstowcs(&wide_str[0], valueStr.c_str(), valueStr.size()); + wide_str.resize(std::wcslen(wide_str.c_str())); + + std::u16string valueStringU16; + + for (wchar_t wc : wide_str) { + if (wc <= 0xFFFF) { + valueStringU16.push_back(static_cast(wc)); + } else { + wc -= 0x10000; + valueStringU16.push_back(static_cast(0xD800 | (wc >> 10))); + valueStringU16.push_back(static_cast(0xDC00 | (wc & 0x3FF))); + } + } + + std::vector byteArray; + // convert to little endian + for (char16_t ch : valueStringU16) { + unsigned char low_byte = static_cast(ch & 0x00FF); + unsigned char high_byte = static_cast((ch >> 8) & 0x00FF); + + byteArray.push_back(low_byte); + byteArray.push_back(high_byte); + } + byteArray.push_back('\0'); + byteArray.push_back('\0'); std::stringstream ss; - for (unsigned char c : byteArray) { - ss << std::hex << std::setfill('0') << std::setw(2) << static_cast(c); + + for (unsigned char ch : byteArray) { + ss << std::hex << std::setw(2) << std::setfill('0') << static_cast(ch); } - result = QString::fromStdString(ss.str()); - } else if (typeStr == "bytes") { + result = ss.str(); + } else if (type == "bytes") { result = valueStr; - } else if (typeStr == "mask" || typeStr == "mask_jump32") { + } else if (type == "mask" || type == "mask_jump32") { result = valueStr; } else { - LOG_INFO(Loader, "Error applying Patch, unknown type: {}", typeStr); + LOG_INFO(Loader, "Error applying Patch, unknown type: {}", type); } return result; } void OnGameLoaded() { + if (!patchFile.empty()) { + std::string patchDir = Common::FS::GetUserPath(Common::FS::PathType::PatchesDir).string(); + + std::string filePath = patchDir + "/" + patchFile; + + pugi::xml_document doc; + pugi::xml_parse_result result = doc.load_file(filePath.c_str()); + + if (result) { + auto patchXML = doc.child("Patch"); + for (pugi::xml_node_iterator it = patchXML.children().begin(); + it != patchXML.children().end(); ++it) { + + if (std::string(it->name()) == "Metadata") { + if (std::string(it->attribute("isEnabled").value()) == "true") { + auto patchList = it->first_child(); + + std::string currentPatchName = it->attribute("Name").value(); + + for (pugi::xml_node_iterator patchLineIt = patchList.children().begin(); + patchLineIt != patchList.children().end(); ++patchLineIt) { + + std::string type = patchLineIt->attribute("Type").value(); + std::string address = patchLineIt->attribute("Address").value(); + std::string patchValue = patchLineIt->attribute("Value").value(); + std::string maskOffsetStr = patchLineIt->attribute("type").value(); + + patchValue = convertValueToHex(type, patchValue); + + bool littleEndian = false; + + if (type == "bytes16") { + littleEndian = true; + } else if (type == "bytes32") { + littleEndian = true; + } else if (type == "bytes64") { + littleEndian = true; + } + + MemoryPatcher::PatchMask patchMask = MemoryPatcher::PatchMask::None; + int maskOffsetValue = 0; + + if (type == "mask") { + patchMask = MemoryPatcher::PatchMask::Mask; + + // im not sure if this works, there is no games to test the mask + // offset on yet + if (!maskOffsetStr.empty()) + maskOffsetValue = std::stoi(maskOffsetStr, 0, 10); + } + + if (type == "mask_jump32") + patchMask = MemoryPatcher::PatchMask::Mask_Jump32; + + MemoryPatcher::PatchMemory(currentPatchName, address, patchValue, false, + littleEndian, patchMask); + } + } + } + } + } else + LOG_ERROR(Loader, "couldnt patch parse xml : {}", result.description()); + + ApplyPendingPatches(); + return; + } + +#ifdef ENABLE_QT_GUI // We use the QT headers for the xml and json parsing, this define is only true on QT builds QString patchDir = QString::fromStdString(Common::FS::GetUserPath(Common::FS::PathType::PatchesDir).string()); @@ -190,7 +287,8 @@ void OnGameLoaded() { QString patchValue = lineObject["Value"].toString(); QString maskOffsetStr = lineObject["Offset"].toString(); - patchValue = convertValueToHex(type, patchValue); + patchValue = QString::fromStdString( + convertValueToHex(type.toStdString(), patchValue.toStdString())); bool littleEndian = false; @@ -233,6 +331,7 @@ void OnGameLoaded() { } ApplyPendingPatches(); } +#endif } void AddPatchToQueue(patchInfo patchToAdd) { diff --git a/src/qt_gui/memory_patcher.h b/src/common/memory_patcher.h similarity index 87% rename from src/qt_gui/memory_patcher.h rename to src/common/memory_patcher.h index 821184adec4..899ffccb109 100644 --- a/src/qt_gui/memory_patcher.h +++ b/src/common/memory_patcher.h @@ -5,13 +5,13 @@ #include #include #include -#include namespace MemoryPatcher { extern uintptr_t g_eboot_address; -extern u64 g_eboot_image_size; +extern uint64_t g_eboot_image_size; extern std::string g_game_serial; +extern std::string patchFile; enum PatchMask : uint8_t { None, @@ -32,7 +32,7 @@ struct patchInfo { extern std::vector pending_patches; -QString convertValueToHex(const QString& type, const QString& valueStr); +std::string convertValueToHex(const std::string type, const std::string valueStr); void OnGameLoaded(); void AddPatchToQueue(patchInfo patchToAdd); diff --git a/src/common/path_util.cpp b/src/common/path_util.cpp index 8d369fc7bfc..cce12ebcf42 100644 --- a/src/common/path_util.cpp +++ b/src/common/path_util.cpp @@ -82,11 +82,20 @@ static std::filesystem::path GetBundleParentDirectory() { static auto UserPaths = [] { #ifdef __APPLE__ - std::filesystem::current_path(GetBundleParentDirectory()); + // Start by assuming the base directory is the bundle's parent directory. + std::filesystem::path base_dir = GetBundleParentDirectory(); + std::filesystem::path user_dir = base_dir / PORTABLE_DIR; + // Check if the "user" directory exists in the current path: + if (!std::filesystem::exists(user_dir)) { + // If it doesn't exist, use the new hardcoded path: + user_dir = + std::filesystem::path(getenv("HOME")) / "Library" / "Application Support" / "shadPS4"; + } +#else + const auto user_dir = std::filesystem::current_path() / PORTABLE_DIR; #endif std::unordered_map paths; - const auto user_dir = std::filesystem::current_path() / PORTABLE_DIR; const auto create_path = [&](PathType shad_path, const fs::path& new_path) { std::filesystem::create_directory(new_path); @@ -107,6 +116,7 @@ static auto UserPaths = [] { create_path(PathType::CheatsDir, user_dir / CHEATS_DIR); create_path(PathType::PatchesDir, user_dir / PATCHES_DIR); create_path(PathType::AddonsDir, user_dir / ADDONS_DIR); + create_path(PathType::MetaDataDir, user_dir / METADATA_DIR); return paths; }(); @@ -155,4 +165,4 @@ void SetUserPath(PathType shad_path, const fs::path& new_path) { UserPaths.insert_or_assign(shad_path, new_path); } -} // namespace Common::FS +} // namespace Common::FS \ No newline at end of file diff --git a/src/common/path_util.h b/src/common/path_util.h index bee93c1b9c5..623b285ed2a 100644 --- a/src/common/path_util.h +++ b/src/common/path_util.h @@ -23,6 +23,7 @@ enum class PathType { CheatsDir, // Where cheats are stored. PatchesDir, // Where patches are stored. AddonsDir, // Where additional content is stored. + MetaDataDir, // Where game metadata (e.g. trophies and menu backgrounds) is stored. }; constexpr auto PORTABLE_DIR = "user"; @@ -41,6 +42,7 @@ constexpr auto CAPTURES_DIR = "captures"; constexpr auto CHEATS_DIR = "cheats"; constexpr auto PATCHES_DIR = "patches"; constexpr auto ADDONS_DIR = "addcont"; +constexpr auto METADATA_DIR = "game_data"; // Filenames constexpr auto LOG_FILE = "shad_log.txt"; diff --git a/src/common/rdtsc.h b/src/common/rdtsc.h index 3180273e50b..4e4d5843696 100644 --- a/src/common/rdtsc.h +++ b/src/common/rdtsc.h @@ -3,6 +3,8 @@ #pragma once +#include "common/arch.h" + #ifdef _MSC_VER #include #endif @@ -13,15 +15,20 @@ namespace Common { #ifdef _MSC_VER __forceinline static u64 FencedRDTSC() { +#ifdef ARCH_X86_64 _mm_lfence(); _ReadWriteBarrier(); const u64 result = __rdtsc(); _mm_lfence(); _ReadWriteBarrier(); return result; +#else +#error "Missing FencedRDTSC() implementation for target CPU architecture." +#endif } #else static inline u64 FencedRDTSC() { +#ifdef ARCH_X86_64 u64 eax; u64 edx; asm volatile("lfence\n\t" @@ -29,6 +36,16 @@ static inline u64 FencedRDTSC() { "lfence\n\t" : "=a"(eax), "=d"(edx)); return (edx << 32) | eax; +#elif defined(ARCH_ARM64) + u64 ret; + asm volatile("isb\n\t" + "mrs %0, cntvct_el0\n\t" + "isb\n\t" + : "=r"(ret)::"memory"); + return ret; +#else +#error "Missing FencedRDTSC() implementation for target CPU architecture." +#endif } #endif diff --git a/src/common/signal_context.cpp b/src/common/signal_context.cpp new file mode 100644 index 00000000000..112160bc803 --- /dev/null +++ b/src/common/signal_context.cpp @@ -0,0 +1,92 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/arch.h" +#include "common/assert.h" +#include "common/signal_context.h" + +#ifdef _WIN32 +#include +#else +#include +#endif + +namespace Common { + +void* GetXmmPointer(void* ctx, u8 index) { +#if defined(_WIN32) +#define CASE(index) \ + case index: \ + return (void*)(&((EXCEPTION_POINTERS*)ctx)->ContextRecord->Xmm##index.Low) +#elif defined(__APPLE__) +#define CASE(index) \ + case index: \ + return (void*)(&((ucontext_t*)ctx)->uc_mcontext->__fs.__fpu_xmm##index); +#else +#define CASE(index) \ + case index: \ + return (void*)(&((ucontext_t*)ctx)->uc_mcontext.fpregs->_xmm[index].element[0]) +#endif + switch (index) { + CASE(0); + CASE(1); + CASE(2); + CASE(3); + CASE(4); + CASE(5); + CASE(6); + CASE(7); + CASE(8); + CASE(9); + CASE(10); + CASE(11); + CASE(12); + CASE(13); + CASE(14); + CASE(15); + default: { + UNREACHABLE_MSG("Invalid XMM register index: {}", index); + return nullptr; + } + } +#undef CASE +} + +void* GetRip(void* ctx) { +#if defined(_WIN32) + return (void*)((EXCEPTION_POINTERS*)ctx)->ContextRecord->Rip; +#elif defined(__APPLE__) + return (void*)((ucontext_t*)ctx)->uc_mcontext->__ss.__rip; +#else + return (void*)((ucontext_t*)ctx)->uc_mcontext.gregs[REG_RIP]; +#endif +} + +void IncrementRip(void* ctx, u64 length) { +#if defined(_WIN32) + ((EXCEPTION_POINTERS*)ctx)->ContextRecord->Rip += length; +#elif defined(__APPLE__) + ((ucontext_t*)ctx)->uc_mcontext->__ss.__rip += length; +#else + ((ucontext_t*)ctx)->uc_mcontext.gregs[REG_RIP] += length; +#endif +} + +bool IsWriteError(void* ctx) { +#if defined(_WIN32) + return ((EXCEPTION_POINTERS*)ctx)->ExceptionRecord->ExceptionInformation[0] == 1; +#elif defined(__APPLE__) +#if defined(ARCH_X86_64) + return ((ucontext_t*)ctx)->uc_mcontext->__es.__err & 0x2; +#elif defined(ARCH_ARM64) + return ((ucontext_t*)ctx)->uc_mcontext->__es.__esr & 0x40; +#endif +#else +#if defined(ARCH_X86_64) + return ((ucontext_t*)ctx)->uc_mcontext.gregs[REG_ERR] & 0x2; +#else +#error "Unsupported architecture" +#endif +#endif +} +} // namespace Common \ No newline at end of file diff --git a/src/common/signal_context.h b/src/common/signal_context.h new file mode 100644 index 00000000000..b09da64f250 --- /dev/null +++ b/src/common/signal_context.h @@ -0,0 +1,18 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/types.h" + +namespace Common { + +void* GetXmmPointer(void* ctx, u8 index); + +void* GetRip(void* ctx); + +void IncrementRip(void* ctx, u64 length); + +bool IsWriteError(void* ctx); + +} // namespace Common \ No newline at end of file diff --git a/src/common/string_util.cpp b/src/common/string_util.cpp index 29e6aeb4f4f..6d5a254cd28 100644 --- a/src/common/string_util.cpp +++ b/src/common/string_util.cpp @@ -14,12 +14,17 @@ namespace Common { -std::string ToLower(std::string str) { - std::transform(str.begin(), str.end(), str.begin(), - [](unsigned char c) { return static_cast(std::tolower(c)); }); +std::string ToLower(std::string_view input) { + std::string str; + str.resize(input.size()); + std::ranges::transform(input, str.begin(), tolower); return str; } +void ToLowerInPlace(std::string& str) { + std::ranges::transform(str, str.begin(), tolower); +} + std::vector SplitString(const std::string& str, char delimiter) { std::istringstream iss(str); std::vector output(1); diff --git a/src/common/string_util.h b/src/common/string_util.h index 8dae6c75b06..23e82b93c83 100644 --- a/src/common/string_util.h +++ b/src/common/string_util.h @@ -10,7 +10,9 @@ namespace Common { /// Make a string lowercase -[[nodiscard]] std::string ToLower(std::string str); +[[nodiscard]] std::string ToLower(std::string_view str); + +void ToLowerInPlace(std::string& str); std::vector SplitString(const std::string& str, char delimiter); diff --git a/src/common/thread.cpp b/src/common/thread.cpp index f08b36faae9..46df68c3883 100644 --- a/src/common/thread.cpp +++ b/src/common/thread.cpp @@ -3,12 +3,15 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include +#include #include "common/error.h" #include "common/logging/log.h" #include "common/thread.h" +#include "ntapi.h" #ifdef __APPLE__ #include +#include #include #elif defined(_WIN32) #include @@ -31,6 +34,48 @@ namespace Common { +#ifdef __APPLE__ + +void SetCurrentThreadRealtime(const std::chrono::nanoseconds period_ns) { + // CPU time to grant. + const std::chrono::nanoseconds computation_ns = period_ns / 2; + + // Determine the timebase for converting time to ticks. + struct mach_timebase_info timebase {}; + mach_timebase_info(&timebase); + const auto ticks_per_ns = + static_cast(timebase.denom) / static_cast(timebase.numer); + + const auto period_ticks = + static_cast(static_cast(period_ns.count()) * ticks_per_ns); + const auto computation_ticks = + static_cast(static_cast(computation_ns.count()) * ticks_per_ns); + + thread_time_constraint_policy policy = { + .period = period_ticks, + .computation = computation_ticks, + // Should not matter since preemptible is false, but needs to be >= computation regardless. + .constraint = computation_ticks, + .preemptible = false, + }; + + int ret = thread_policy_set( + pthread_mach_thread_np(pthread_self()), THREAD_TIME_CONSTRAINT_POLICY, + reinterpret_cast(&policy), THREAD_TIME_CONSTRAINT_POLICY_COUNT); + if (ret != KERN_SUCCESS) { + LOG_ERROR(Common, "Could not set thread to real-time with period {} ns: {}", + period_ns.count(), ret); + } +} + +#else + +void SetCurrentThreadRealtime(const std::chrono::nanoseconds period_ns) { + // Not implemented +} + +#endif + #ifdef _WIN32 void SetCurrentThreadPriority(ThreadPriority new_priority) { @@ -59,6 +104,16 @@ void SetCurrentThreadPriority(ThreadPriority new_priority) { SetThreadPriority(handle, windows_priority); } +static void AccurateSleep(std::chrono::nanoseconds duration) { + LARGE_INTEGER interval{ + .QuadPart = -1 * (duration.count() / 100u), + }; + HANDLE timer = ::CreateWaitableTimer(NULL, TRUE, NULL); + SetWaitableTimer(timer, &interval, 0, NULL, NULL, 0); + WaitForSingleObject(timer, INFINITE); + ::CloseHandle(timer); +} + #else void SetCurrentThreadPriority(ThreadPriority new_priority) { @@ -79,6 +134,10 @@ void SetCurrentThreadPriority(ThreadPriority new_priority) { pthread_setschedparam(this_thread, scheduling_type, ¶ms); } +static void AccurateSleep(std::chrono::nanoseconds duration) { + std::this_thread::sleep_for(duration); +} + #endif #ifdef _MSC_VER @@ -121,4 +180,22 @@ void SetCurrentThreadName(const char*) { #endif +AccurateTimer::AccurateTimer(std::chrono::nanoseconds target_interval) + : target_interval(target_interval) {} + +void AccurateTimer::Start() { + auto begin_sleep = std::chrono::high_resolution_clock::now(); + if (total_wait.count() > 0) { + AccurateSleep(total_wait); + } + start_time = std::chrono::high_resolution_clock::now(); + total_wait -= std::chrono::duration_cast(start_time - begin_sleep); +} + +void AccurateTimer::End() { + auto now = std::chrono::high_resolution_clock::now(); + total_wait += + target_interval - std::chrono::duration_cast(now - start_time); +} + } // namespace Common diff --git a/src/common/thread.h b/src/common/thread.h index 39acc1db551..fd962f8e5af 100644 --- a/src/common/thread.h +++ b/src/common/thread.h @@ -4,6 +4,7 @@ #pragma once +#include #include "common/types.h" namespace Common { @@ -16,8 +17,24 @@ enum class ThreadPriority : u32 { Critical = 4, }; +void SetCurrentThreadRealtime(std::chrono::nanoseconds period_ns); + void SetCurrentThreadPriority(ThreadPriority new_priority); void SetCurrentThreadName(const char* name); +class AccurateTimer { + std::chrono::nanoseconds target_interval{}; + std::chrono::nanoseconds total_wait{}; + + std::chrono::high_resolution_clock::time_point start_time; + +public: + explicit AccurateTimer(std::chrono::nanoseconds target_interval); + + void Start(); + + void End(); +}; + } // namespace Common diff --git a/src/common/version.h b/src/common/version.h index 80de187b027..12fd1704191 100644 --- a/src/common/version.h +++ b/src/common/version.h @@ -8,7 +8,7 @@ namespace Common { -constexpr char VERSION[] = "0.2.1 WIP"; +constexpr char VERSION[] = "0.3.1 WIP"; constexpr bool isRelease = false; } // namespace Common diff --git a/src/core/address_space.cpp b/src/core/address_space.cpp index 235113700ca..3950bd5fe57 100644 --- a/src/core/address_space.cpp +++ b/src/core/address_space.cpp @@ -3,11 +3,13 @@ #include #include "common/alignment.h" +#include "common/arch.h" #include "common/assert.h" #include "common/error.h" #include "core/address_space.h" #include "core/libraries/kernel/memory_management.h" #include "core/memory.h" +#include "libraries/error_codes.h" #ifdef _WIN32 #include @@ -16,7 +18,7 @@ #include #endif -#ifdef __APPLE__ +#if defined(__APPLE__) && defined(ARCH_X86_64) // Reserve space for the system address space using a zerofill section. asm(".zerofill GUEST_SYSTEM,GUEST_SYSTEM,__guest_system,0xFBFC00000"); #endif @@ -231,27 +233,36 @@ struct AddressSpace::Impl { void Protect(VAddr virtual_addr, size_t size, bool read, bool write, bool execute) { DWORD new_flags{}; - if (read && write) { + + if (read && write && execute) { + new_flags = PAGE_EXECUTE_READWRITE; + } else if (read && write) { new_flags = PAGE_READWRITE; } else if (read && !write) { new_flags = PAGE_READONLY; - } else if (!read && !write) { + } else if (execute && !read && not write) { + new_flags = PAGE_EXECUTE; + } else if (!read && !write && !execute) { new_flags = PAGE_NOACCESS; } else { - UNIMPLEMENTED_MSG("Protection flag combination read={} write={}", read, write); + LOG_CRITICAL(Common_Memory, + "Unsupported protection flag combination for address {:#x}, size {}", + virtual_addr, size); + return; } - const VAddr virtual_end = virtual_addr + size; - auto [it, end] = placeholders.equal_range({virtual_addr, virtual_end}); - while (it != end) { - const size_t offset = std::max(it->lower(), virtual_addr); - const size_t protect_length = std::min(it->upper(), virtual_end) - offset; - DWORD old_flags{}; - if (!VirtualProtect(virtual_base + offset, protect_length, new_flags, &old_flags)) { - LOG_CRITICAL(Common_Memory, "Failed to change virtual memory protect rules"); - } - ++it; + DWORD old_flags{}; + bool success = + VirtualProtect(reinterpret_cast(virtual_addr), size, new_flags, &old_flags); + + if (!success) { + LOG_ERROR(Common_Memory, + "Failed to change virtual memory protection for address {:#x}, size {}", + virtual_addr, size); } + + // Use assert to ensure success in debug builds + DEBUG_ASSERT(success && "Failed to change virtual memory protection"); } HANDLE process{}; @@ -298,12 +309,12 @@ struct AddressSpace::Impl { constexpr int protection_flags = PROT_READ | PROT_WRITE; constexpr int base_map_flags = MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE; -#ifdef __APPLE__ - // On ARM64 Macs, we run into limitations due to the commpage from 0xFC0000000 - 0xFFFFFFFFF - // and the GPU carveout region from 0x1000000000 - 0x6FFFFFFFFF. We can allocate the system - // managed region, as well as system reserved if reduced in size slightly, but we cannot map - // the user region where we want, so we must let the OS put it wherever possible and hope - // the game won't rely on its location. +#if defined(__APPLE__) && defined(ARCH_X86_64) + // On ARM64 Macs under Rosetta 2, we run into limitations due to the commpage from + // 0xFC0000000 - 0xFFFFFFFFF and the GPU carveout region from 0x1000000000 - 0x6FFFFFFFFF. + // We can allocate the system managed region, as well as system reserved if reduced in size + // slightly, but we cannot map the user region where we want, so we must let the OS put it + // wherever possible and hope the game won't rely on its location. system_managed_base = reinterpret_cast( mmap(reinterpret_cast(SYSTEM_MANAGED_MIN), system_managed_size, protection_flags, base_map_flags | MAP_FIXED, -1, 0)); @@ -315,12 +326,22 @@ struct AddressSpace::Impl { protection_flags, base_map_flags, -1, 0)); #else const auto virtual_size = system_managed_size + system_reserved_size + user_size; +#if defined(ARCH_X86_64) const auto virtual_base = reinterpret_cast(mmap(reinterpret_cast(SYSTEM_MANAGED_MIN), virtual_size, protection_flags, base_map_flags | MAP_FIXED, -1, 0)); system_managed_base = virtual_base; system_reserved_base = reinterpret_cast(SYSTEM_RESERVED_MIN); user_base = reinterpret_cast(USER_MIN); +#else + // Map memory wherever possible and instruction translation can handle offsetting to the + // base. + const auto virtual_base = reinterpret_cast( + mmap(nullptr, virtual_size, protection_flags, base_map_flags, -1, 0)); + system_managed_base = virtual_base; + system_reserved_base = virtual_base + SYSTEM_RESERVED_MIN - SYSTEM_MANAGED_MIN; + user_base = virtual_base + USER_MIN - SYSTEM_MANAGED_MIN; +#endif #endif if (system_managed_base == MAP_FAILED || system_reserved_base == MAP_FAILED || user_base == MAP_FAILED) { @@ -420,9 +441,11 @@ struct AddressSpace::Impl { if (write) { flags |= PROT_WRITE; } +#ifdef ARCH_X86_64 if (execute) { flags |= PROT_EXEC; } +#endif int ret = mprotect(reinterpret_cast(virtual_addr), size, flags); ASSERT_MSG(ret == 0, "mprotect failed: {}", strerror(errno)); } @@ -453,8 +476,14 @@ AddressSpace::~AddressSpace() = default; void* AddressSpace::Map(VAddr virtual_addr, size_t size, u64 alignment, PAddr phys_addr, bool is_exec) { - return impl->Map(virtual_addr, phys_addr, size, - is_exec ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE); +#if ARCH_X86_64 + const auto prot = is_exec ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE; +#else + // On non-native architectures, we can simplify things by ignoring the execute flag for the + // canonical copy of the memory and rely on the JIT to map translated code as executable. + constexpr auto prot = PAGE_READWRITE; +#endif + return impl->Map(virtual_addr, phys_addr, size, prot); } void* AddressSpace::MapFile(VAddr virtual_addr, size_t size, size_t offset, u32 prot, @@ -493,7 +522,10 @@ void AddressSpace::Unmap(VAddr virtual_addr, size_t size, VAddr start_in_vma, VA } void AddressSpace::Protect(VAddr virtual_addr, size_t size, MemoryPermission perms) { - return impl->Protect(virtual_addr, size, true, true, true); + const bool read = True(perms & MemoryPermission::Read); + const bool write = True(perms & MemoryPermission::Write); + const bool execute = True(perms & MemoryPermission::Execute); + return impl->Protect(virtual_addr, size, read, write, execute); } } // namespace Core diff --git a/src/core/address_space.h b/src/core/address_space.h index 2a3488d572d..3233c758812 100644 --- a/src/core/address_space.h +++ b/src/core/address_space.h @@ -4,6 +4,7 @@ #pragma once #include +#include "common/arch.h" #include "common/enum.h" #include "common/types.h" @@ -23,7 +24,7 @@ constexpr VAddr CODE_BASE_OFFSET = 0x100000000ULL; constexpr VAddr SYSTEM_MANAGED_MIN = 0x00000400000ULL; constexpr VAddr SYSTEM_MANAGED_MAX = 0x07FFFFBFFFULL; constexpr VAddr SYSTEM_RESERVED_MIN = 0x07FFFFC000ULL; -#ifdef __APPLE__ +#if defined(__APPLE__) && defined(ARCH_X86_64) // Can only comfortably reserve the first 0x7C0000000 of system reserved space. constexpr VAddr SYSTEM_RESERVED_MAX = 0xFBFFFFFFFULL; #else diff --git a/src/core/cpu_patches.cpp b/src/core/cpu_patches.cpp index 55bbf23b111..24438b6b5e8 100644 --- a/src/core/cpu_patches.cpp +++ b/src/core/cpu_patches.cpp @@ -1,12 +1,20 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include #include #include +#include #include #include +#include +#include "common/alignment.h" +#include "common/arch.h" #include "common/assert.h" +#include "common/decoder.h" +#include "common/signal_context.h" #include "common/types.h" +#include "core/signals.h" #include "core/tls.h" #include "cpu_patches.h" @@ -22,6 +30,16 @@ using namespace Xbyak::util; +#define MAYBE_AVX(OPCODE, ...) \ + [&] { \ + Cpu cpu; \ + if (cpu.has(Cpu::tAVX)) { \ + c.v##OPCODE(__VA_ARGS__); \ + } else { \ + c.OPCODE(__VA_ARGS__); \ + } \ + }() + namespace Core { static Xbyak::Reg ZydisToXbyakRegister(const ZydisRegister reg) { @@ -126,39 +144,35 @@ static Xbyak::Reg AllocateScratchRegister( static pthread_key_t stack_pointer_slot; static pthread_key_t patch_stack_slot; static std::once_flag patch_context_slots_init_flag; +static constexpr u32 patch_stack_size = 0x1000; static_assert(sizeof(void*) == sizeof(u64), "Cannot fit a register inside a thread local storage slot."); +static void FreePatchStack(void* patch_stack) { + // Subtract back to the bottom of the stack for free. + std::free(static_cast(patch_stack) - patch_stack_size); +} + static void InitializePatchContextSlots() { ASSERT_MSG(pthread_key_create(&stack_pointer_slot, nullptr) == 0, "Unable to allocate thread-local register for stack pointer."); - ASSERT_MSG(pthread_key_create(&patch_stack_slot, nullptr) == 0, + ASSERT_MSG(pthread_key_create(&patch_stack_slot, FreePatchStack) == 0, "Unable to allocate thread-local register for patch stack."); } void InitializeThreadPatchStack() { std::call_once(patch_context_slots_init_flag, InitializePatchContextSlots); - const auto* patch_stack = std::malloc(0x1000); - pthread_setspecific(patch_stack_slot, patch_stack); -} - -void CleanupThreadPatchStack() { - std::call_once(patch_context_slots_init_flag, InitializePatchContextSlots); - - auto* patch_stack = pthread_getspecific(patch_stack_slot); - if (patch_stack != nullptr) { - std::free(patch_stack); - pthread_setspecific(patch_stack_slot, nullptr); - } + pthread_setspecific(patch_stack_slot, + static_cast(std::malloc(patch_stack_size)) + patch_stack_size); } /// Saves the stack pointer to thread local storage and loads the patch stack. static void SaveStack(Xbyak::CodeGenerator& c) { std::call_once(patch_context_slots_init_flag, InitializePatchContextSlots); - // Save stack pointer and load patch stack. + // Save original stack pointer and load patch stack. c.putSeg(gs); c.mov(qword[reinterpret_cast(stack_pointer_slot * sizeof(void*))], rsp); c.putSeg(gs); @@ -184,10 +198,6 @@ void InitializeThreadPatchStack() { // No-op } -void CleanupThreadPatchStack() { - // No-op -} - /// Saves the stack pointer to thread local storage and loads the patch stack. static void SaveStack(Xbyak::CodeGenerator& c) { UNIMPLEMENTED(); @@ -220,31 +230,38 @@ static void RestoreRegisters(Xbyak::CodeGenerator& c, } /// Switches to the patch stack and stores all registers. -static void SaveContext(Xbyak::CodeGenerator& c) { +static void SaveContext(Xbyak::CodeGenerator& c, bool save_flags = false) { SaveStack(c); for (int reg = Xbyak::Operand::RAX; reg <= Xbyak::Operand::R15; reg++) { c.push(Xbyak::Reg64(reg)); } for (int reg = 0; reg <= 7; reg++) { - c.sub(rsp, 32); + c.lea(rsp, ptr[rsp - 32]); c.vmovdqu(ptr[rsp], Xbyak::Ymm(reg)); } + if (save_flags) { + c.pushfq(); + } } /// Restores all registers and restores the original stack. /// If the destination is a register, it is not restored to preserve the output. -static void RestoreContext(Xbyak::CodeGenerator& c, const Xbyak::Operand& dst) { +static void RestoreContext(Xbyak::CodeGenerator& c, const Xbyak::Operand& dst, + bool restore_flags = false) { + if (restore_flags) { + c.popfq(); + } for (int reg = 7; reg >= 0; reg--) { if ((!dst.isXMM() && !dst.isYMM()) || dst.getIdx() != reg) { c.vmovdqu(Xbyak::Ymm(reg), ptr[rsp]); } - c.add(rsp, 32); + c.lea(rsp, ptr[rsp + 32]); } for (int reg = Xbyak::Operand::R15; reg >= Xbyak::Operand::RAX; reg--) { if (!dst.isREG() || dst.getIdx() != reg) { c.pop(Xbyak::Reg64(reg)); } else { - c.add(rsp, 4); + c.lea(rsp, ptr[rsp + 8]); } } RestoreStack(c); @@ -315,9 +332,22 @@ static void GenerateBLSI(const ZydisDecodedOperand* operands, Xbyak::CodeGenerat SaveRegisters(c, {scratch}); + // BLSI sets CF to zero if source is zero, otherwise it sets CF to one. + Xbyak::Label clear_carry, end; + c.mov(scratch, *src); - c.neg(scratch); + c.neg(scratch); // NEG, like BLSI, clears CF if the source is zero and sets it otherwise + c.jnc(clear_carry); + + c.and_(scratch, *src); + c.stc(); // setting/clearing carry needs to happen after the AND because that clears CF + c.jmp(end); + + c.L(clear_carry); c.and_(scratch, *src); + // We don't need to clear carry here since AND does that for us + + c.L(end); c.mov(dst, scratch); RestoreRegisters(c, {scratch}); @@ -331,9 +361,24 @@ static void GenerateBLSMSK(const ZydisDecodedOperand* operands, Xbyak::CodeGener SaveRegisters(c, {scratch}); + Xbyak::Label clear_carry, end; + + // BLSMSK sets CF to zero if source is NOT zero, otherwise it sets CF to one. c.mov(scratch, *src); + c.test(scratch, scratch); + c.jnz(clear_carry); + + c.dec(scratch); + c.xor_(scratch, *src); + c.stc(); + c.jmp(end); + + c.L(clear_carry); c.dec(scratch); c.xor_(scratch, *src); + // We don't need to clear carry here since XOR does that for us + + c.L(end); c.mov(dst, scratch); RestoreRegisters(c, {scratch}); @@ -347,9 +392,24 @@ static void GenerateBLSR(const ZydisDecodedOperand* operands, Xbyak::CodeGenerat SaveRegisters(c, {scratch}); + Xbyak::Label clear_carry, end; + + // BLSR sets CF to zero if source is NOT zero, otherwise it sets CF to one. c.mov(scratch, *src); + c.test(scratch, scratch); + c.jnz(clear_carry); + + c.dec(scratch); + c.and_(scratch, *src); + c.stc(); + c.jmp(end); + + c.L(clear_carry); c.dec(scratch); c.and_(scratch, *src); + // We don't need to clear carry here since AND does that for us + + c.L(end); c.mov(dst, scratch); RestoreRegisters(c, {scratch}); @@ -369,7 +429,7 @@ static void GenerateVCVTPH2PS(const ZydisDecodedOperand* operands, Xbyak::CodeGe const auto float_count = dst.getBit() / 32; const auto byte_count = float_count * 4; - SaveContext(c); + SaveContext(c, true); // Allocate stack space for outputs and load into first parameter. c.sub(rsp, byte_count); @@ -405,7 +465,7 @@ static void GenerateVCVTPH2PS(const ZydisDecodedOperand* operands, Xbyak::CodeGe } c.add(rsp, byte_count); - RestoreContext(c, dst); + RestoreContext(c, dst, true); } using SingleToHalfFloatConverter = half_float::half (*)(float); @@ -433,7 +493,7 @@ static void GenerateVCVTPS2PH(const ZydisDecodedOperand* operands, Xbyak::CodeGe const auto float_count = src.getBit() / 32; const auto byte_count = float_count * 4; - SaveContext(c); + SaveContext(c, true); if (dst->isXMM()) { // Allocate stack space for outputs and load into first parameter. @@ -480,7 +540,7 @@ static void GenerateVCVTPS2PH(const ZydisDecodedOperand* operands, Xbyak::CodeGe c.add(rsp, byte_count); } - RestoreContext(c, *dst); + RestoreContext(c, *dst, true); } static bool FilterRosetta2Only(const ZydisDecodedOperand*) { @@ -492,7 +552,7 @@ static bool FilterRosetta2Only(const ZydisDecodedOperand*) { return ret; } -#endif // __APPLE__ +#else // __APPLE__ static bool FilterTcbAccess(const ZydisDecodedOperand* operands) { const auto& dst_op = operands[0]; @@ -507,7 +567,6 @@ static bool FilterTcbAccess(const ZydisDecodedOperand* operands) { static void GenerateTcbAccess(const ZydisDecodedOperand* operands, Xbyak::CodeGenerator& c) { const auto dst = ZydisToXbyakRegisterOperand(operands[0]); - const auto slot = GetTcbKey(); #if defined(_WIN32) // The following logic is based on the Kernel32.dll asm of TlsGetValue @@ -515,6 +574,8 @@ static void GenerateTcbAccess(const ZydisDecodedOperand* operands, Xbyak::CodeGe static constexpr u32 TlsExpansionSlotsOffset = 0x1780; static constexpr u32 TlsMinimumAvailable = 64; + const auto slot = GetTcbKey(); + // Load the pointer to the table of TLS slots. c.putSeg(gs); if (slot < TlsMinimumAvailable) { @@ -528,11 +589,6 @@ static void GenerateTcbAccess(const ZydisDecodedOperand* operands, Xbyak::CodeGe // Load the pointer to our buffer. c.mov(dst, qword[dst + tls_index * sizeof(LPVOID)]); } -#elif defined(__APPLE__) - // The following logic is based on the Darwin implementation of _os_tsd_get_direct, used by - // pthread_getspecific https://github.com/apple/darwin-xnu/blob/main/libsyscall/os/tsd.h#L89-L96 - c.putSeg(gs); - c.mov(dst, qword[reinterpret_cast(slot * sizeof(void*))]); #else const auto src = ZydisToXbyakMemoryOperand(operands[1]); @@ -542,6 +598,116 @@ static void GenerateTcbAccess(const ZydisDecodedOperand* operands, Xbyak::CodeGe #endif } +#endif // __APPLE__ + +static bool FilterNoSSE4a(const ZydisDecodedOperand*) { + Cpu cpu; + return !cpu.has(Cpu::tSSE4a); +} + +static void GenerateEXTRQ(const ZydisDecodedOperand* operands, Xbyak::CodeGenerator& c) { + bool immediateForm = operands[1].type == ZYDIS_OPERAND_TYPE_IMMEDIATE && + operands[2].type == ZYDIS_OPERAND_TYPE_IMMEDIATE; + + ASSERT_MSG(operands[0].type == ZYDIS_OPERAND_TYPE_REGISTER, "operand 0 must be a register"); + + const auto dst = ZydisToXbyakRegisterOperand(operands[0]); + + ASSERT_MSG(dst.isXMM(), "operand 0 must be an XMM register"); + + Xbyak::Xmm xmm_dst = *reinterpret_cast(&dst); + + if (immediateForm) { + u8 length = operands[1].imm.value.u & 0x3F; + u8 index = operands[2].imm.value.u & 0x3F; + if (length == 0) { + length = 64; + } + + LOG_DEBUG(Core, "Patching immediate form EXTRQ, length: {}, index: {}", length, index); + + const Xbyak::Reg64 scratch1 = rax; + const Xbyak::Reg64 scratch2 = rcx; + + // Set rsp to before red zone and save scratch registers + c.lea(rsp, ptr[rsp - 128]); + c.pushfq(); + c.push(scratch1); + c.push(scratch2); + + u64 mask = (1ULL << length) - 1; + + // Get lower qword from xmm register + MAYBE_AVX(movq, scratch1, xmm_dst); + + if (index != 0) { + c.shr(scratch1, index); + } + + // We need to move mask to a register because we can't use all the possible + // immediate values with `and reg, imm32` + c.mov(scratch2, mask); + c.and_(scratch1, scratch2); + + // Writeback to xmm register, extrq instruction says top 64-bits are undefined so we don't + // care to preserve them + MAYBE_AVX(movq, xmm_dst, scratch1); + + c.pop(scratch2); + c.pop(scratch1); + c.popfq(); + c.lea(rsp, ptr[rsp + 128]); + } else { + ASSERT_MSG(operands[0].type == ZYDIS_OPERAND_TYPE_REGISTER && + operands[1].type == ZYDIS_OPERAND_TYPE_REGISTER && + operands[0].reg.value >= ZYDIS_REGISTER_XMM0 && + operands[0].reg.value <= ZYDIS_REGISTER_XMM15 && + operands[1].reg.value >= ZYDIS_REGISTER_XMM0 && + operands[1].reg.value <= ZYDIS_REGISTER_XMM15, + "Unexpected operand types for EXTRQ instruction"); + + const auto src = ZydisToXbyakRegisterOperand(operands[1]); + + ASSERT_MSG(src.isXMM(), "operand 1 must be an XMM register"); + + Xbyak::Xmm xmm_src = *reinterpret_cast(&src); + + const Xbyak::Reg64 scratch1 = rax; + const Xbyak::Reg64 scratch2 = rcx; + const Xbyak::Reg64 mask = rdx; + + c.lea(rsp, ptr[rsp - 128]); + c.pushfq(); + c.push(scratch1); + c.push(scratch2); + c.push(mask); + + // Construct the mask out of the length that resides in bottom 6 bits of source xmm + MAYBE_AVX(movq, scratch1, xmm_src); + c.mov(scratch2, scratch1); + c.and_(scratch2, 0x3F); + c.mov(mask, 1); + c.shl(mask, cl); + c.dec(mask); + + // Get the shift amount and store it in scratch2 + c.shr(scratch1, 8); + c.and_(scratch1, 0x3F); + c.mov(scratch2, scratch1); // cl now contains the shift amount + + MAYBE_AVX(movq, scratch1, xmm_dst); + c.shr(scratch1, cl); + c.and_(scratch1, mask); + MAYBE_AVX(movq, xmm_dst, scratch1); + + c.pop(mask); + c.pop(scratch2); + c.pop(scratch1); + c.popfq(); + c.lea(rsp, ptr[rsp + 128]); + } +} + using PatchFilter = bool (*)(const ZydisDecodedOperand*); using InstructionGenerator = void (*)(const ZydisDecodedOperand*, Xbyak::CodeGenerator&); struct PatchInfo { @@ -556,13 +722,15 @@ struct PatchInfo { }; static const std::unordered_map Patches = { -#if defined(_WIN32) || defined(__APPLE__) - // Windows and Apple need a trampoline. +#if defined(_WIN32) + // Windows needs a trampoline. {ZYDIS_MNEMONIC_MOV, {FilterTcbAccess, GenerateTcbAccess, true}}, -#else +#elif !defined(__APPLE__) {ZYDIS_MNEMONIC_MOV, {FilterTcbAccess, GenerateTcbAccess, false}}, #endif + {ZYDIS_MNEMONIC_EXTRQ, {FilterNoSSE4a, GenerateEXTRQ, true}}, + #ifdef __APPLE__ // Patches for instruction sets not supported by Rosetta 2. // BMI1 @@ -577,64 +745,263 @@ static const std::unordered_map Patches = { #endif }; -void PatchInstructions(u64 segment_addr, u64 segment_size, Xbyak::CodeGenerator& c) { - if (Patches.empty()) { - // Nothing to patch on this platform. - return; +static std::once_flag init_flag; + +struct PatchModule { + /// Mutex controlling access to module code regions. + std::mutex mutex{}; + + /// Start of the module. + u8* start; + + /// End of the module. + u8* end; + + /// Tracker for patched code locations. + std::set patched; + + /// Code generator for patching the module. + Xbyak::CodeGenerator patch_gen; + + /// Code generator for writing trampoline patches. + Xbyak::CodeGenerator trampoline_gen; + + PatchModule(u8* module_ptr, const u64 module_size, u8* trampoline_ptr, + const u64 trampoline_size) + : start(module_ptr), end(module_ptr + module_size), patch_gen(module_size, module_ptr), + trampoline_gen(trampoline_size, trampoline_ptr) {} +}; +static std::map modules; + +static PatchModule* GetModule(const void* ptr) { + auto upper_bound = modules.upper_bound(reinterpret_cast(ptr)); + if (upper_bound == modules.begin()) { + return nullptr; } + return &(std::prev(upper_bound)->second); +} - ZydisDecoder instr_decoder; +/// Returns a boolean indicating whether the instruction was patched, and the offset to advance past +/// whatever is at the current code pointer. +static std::pair TryPatch(u8* code, PatchModule* module) { ZydisDecodedInstruction instruction; ZydisDecodedOperand operands[ZYDIS_MAX_OPERAND_COUNT]; - ZydisDecoderInit(&instr_decoder, ZYDIS_MACHINE_MODE_LONG_64, ZYDIS_STACK_WIDTH_64); + const auto status = Common::Decoder::Instance()->decodeInstruction(instruction, operands, code, + module->end - code); + if (!ZYAN_SUCCESS(status)) { + return std::make_pair(false, 1); + } + + if (Patches.contains(instruction.mnemonic)) { + const auto& patch_info = Patches.at(instruction.mnemonic); + bool needs_trampoline = patch_info.trampoline; + if (patch_info.filter(operands)) { + auto& patch_gen = module->patch_gen; + + if (needs_trampoline && instruction.length < 5) { + // Trampoline is needed but instruction is too short to patch. + // Return false and length to fall back to the illegal instruction handler, + // or to signal to AOT compilation that this instruction should be skipped and + // handled at runtime. + return std::make_pair(false, instruction.length); + } - u8* code = reinterpret_cast(segment_addr); - u8* end = code + segment_size; - while (code < end) { - ZyanStatus status = - ZydisDecoderDecodeFull(&instr_decoder, code, end - code, &instruction, operands); - if (!ZYAN_SUCCESS(status)) { - code++; - continue; + // Reset state and move to current code position. + patch_gen.reset(); + patch_gen.setSize(code - patch_gen.getCode()); + + if (needs_trampoline) { + auto& trampoline_gen = module->trampoline_gen; + const auto trampoline_ptr = trampoline_gen.getCurr(); + + patch_info.generator(operands, trampoline_gen); + + // Return to the following instruction at the end of the trampoline. + trampoline_gen.jmp(code + instruction.length); + + // Replace instruction with near jump to the trampoline. + patch_gen.jmp(trampoline_ptr, Xbyak::CodeGenerator::LabelType::T_NEAR); + } else { + patch_info.generator(operands, patch_gen); + } + + const auto patch_size = patch_gen.getCurr() - code; + if (patch_size > 0) { + ASSERT_MSG(instruction.length >= patch_size, + "Instruction {} with length {} is too short to replace at: {}", + ZydisMnemonicGetString(instruction.mnemonic), instruction.length, + fmt::ptr(code)); + + // Fill remaining space with nops. + patch_gen.nop(instruction.length - patch_size); + + module->patched.insert(code); + LOG_DEBUG(Core, "Patched instruction '{}' at: {}", + ZydisMnemonicGetString(instruction.mnemonic), fmt::ptr(code)); + return std::make_pair(true, instruction.length); + } } + } + + return std::make_pair(false, instruction.length); +} + +#if defined(ARCH_X86_64) + +static bool TryExecuteIllegalInstruction(void* ctx, void* code_address) { + ZydisDecodedInstruction instruction; + ZydisDecodedOperand operands[ZYDIS_MAX_OPERAND_COUNT]; + const auto status = + Common::Decoder::Instance()->decodeInstruction(instruction, operands, code_address); + + switch (instruction.mnemonic) { + case ZYDIS_MNEMONIC_EXTRQ: { + bool immediateForm = operands[1].type == ZYDIS_OPERAND_TYPE_IMMEDIATE && + operands[2].type == ZYDIS_OPERAND_TYPE_IMMEDIATE; + if (immediateForm) { + LOG_ERROR(Core, "EXTRQ immediate form should have been patched at code address: {}", + fmt::ptr(code_address)); + return false; + } else { + ASSERT_MSG(operands[0].type == ZYDIS_OPERAND_TYPE_REGISTER && + operands[1].type == ZYDIS_OPERAND_TYPE_REGISTER && + operands[0].reg.value >= ZYDIS_REGISTER_XMM0 && + operands[0].reg.value <= ZYDIS_REGISTER_XMM15 && + operands[1].reg.value >= ZYDIS_REGISTER_XMM0 && + operands[1].reg.value <= ZYDIS_REGISTER_XMM15, + "Unexpected operand types for EXTRQ instruction"); - if (Patches.contains(instruction.mnemonic)) { - auto patch_info = Patches.at(instruction.mnemonic); - if (patch_info.filter(operands)) { - auto patch_gen = Xbyak::CodeGenerator(instruction.length, code); + const auto dstIndex = operands[0].reg.value - ZYDIS_REGISTER_XMM0; + const auto srcIndex = operands[1].reg.value - ZYDIS_REGISTER_XMM0; - if (patch_info.trampoline) { - const auto trampoline_ptr = c.getCurr(); + const auto dst = Common::GetXmmPointer(ctx, dstIndex); + const auto src = Common::GetXmmPointer(ctx, srcIndex); - patch_info.generator(operands, c); + u64 lowQWordSrc; + memcpy(&lowQWordSrc, src, sizeof(lowQWordSrc)); - // Return to the following instruction at the end of the trampoline. - c.jmp(code + instruction.length); + u64 lowQWordDst; + memcpy(&lowQWordDst, dst, sizeof(lowQWordDst)); - // Replace instruction with near jump to the trampoline. - patch_gen.jmp(trampoline_ptr, Xbyak::CodeGenerator::LabelType::T_NEAR); - } else { - patch_info.generator(operands, patch_gen); - } + u64 mask = lowQWordSrc & 0x3F; + mask = (1ULL << mask) - 1; - const auto patch_size = patch_gen.getCurr() - code; - if (patch_size > 0) { - ASSERT_MSG(instruction.length >= patch_size, - "Instruction {} with length {} is too short to replace at: {}", - ZydisMnemonicGetString(instruction.mnemonic), instruction.length, - fmt::ptr(code)); + u64 shift = (lowQWordSrc >> 8) & 0x3F; - // Fill remaining space with nops. - patch_gen.nop(instruction.length - patch_size); + lowQWordDst >>= shift; + lowQWordDst &= mask; - LOG_DEBUG(Core, "Patched instruction '{}' at: {}", - ZydisMnemonicGetString(instruction.mnemonic), fmt::ptr(code)); - } - } + memcpy(dst, &lowQWordDst, sizeof(lowQWordDst)); + + Common::IncrementRip(ctx, instruction.length); + + return true; } + break; + } + default: { + LOG_ERROR(Core, "Unhandled illegal instruction at code address {}: {}", + fmt::ptr(code_address), ZydisMnemonicGetString(instruction.mnemonic)); + return false; + } + } + + UNREACHABLE(); +} +#elif defined(ARCH_ARM64) +// These functions shouldn't be needed for ARM as it will use a JIT so there's no need to patch +// instructions. +static bool TryExecuteIllegalInstruction(void*, void*) { + return false; +} +#else +#error "Unsupported architecture" +#endif + +static bool TryPatchJit(void* code_address) { + auto* code = static_cast(code_address); + auto* module = GetModule(code); + if (module == nullptr) { + return false; + } + + std::unique_lock lock{module->mutex}; + + // Return early if already patched, in case multiple threads signaled at the same time. + if (std::ranges::find(module->patched, code) != module->patched.end()) { + return true; + } + + return TryPatch(code, module).first; +} + +static void TryPatchAot(void* code_address, u64 code_size) { + auto* code = static_cast(code_address); + auto* module = GetModule(code); + if (module == nullptr) { + return; + } + + std::unique_lock lock{module->mutex}; - code += instruction.length; + const auto* end = code + code_size; + while (code < end) { + code += TryPatch(code, module).second; + } +} + +static bool PatchesAccessViolationHandler(void* context, void* /* fault_address */) { + return TryPatchJit(Common::GetRip(context)); +} + +static bool PatchesIllegalInstructionHandler(void* context) { + void* code_address = Common::GetRip(context); + if (!TryPatchJit(code_address)) { + return TryExecuteIllegalInstruction(context, code_address); + } + return true; +} + +static void PatchesInit() { + if (!Patches.empty()) { + auto* signals = Signals::Instance(); + // Should be called last. + constexpr auto priority = std::numeric_limits::max(); + signals->RegisterAccessViolationHandler(PatchesAccessViolationHandler, priority); + signals->RegisterIllegalInstructionHandler(PatchesIllegalInstructionHandler, priority); } } +void RegisterPatchModule(void* module_ptr, u64 module_size, void* trampoline_area_ptr, + u64 trampoline_area_size) { + std::call_once(init_flag, PatchesInit); + + const auto module_addr = reinterpret_cast(module_ptr); + modules.emplace(std::piecewise_construct, std::forward_as_tuple(module_addr), + std::forward_as_tuple(static_cast(module_ptr), module_size, + static_cast(trampoline_area_ptr), + trampoline_area_size)); +} + +void PrePatchInstructions(u64 segment_addr, u64 segment_size) { +#if defined(__APPLE__) + // HACK: For some reason patching in the signal handler at the start of a page does not work + // under Rosetta 2. Patch any instructions at the start of a page ahead of time. + if (!Patches.empty()) { + auto* code_page = reinterpret_cast(Common::AlignUp(segment_addr, 0x1000)); + const auto* end_page = code_page + Common::AlignUp(segment_size, 0x1000); + while (code_page < end_page) { + TryPatchJit(code_page); + code_page += 0x1000; + } + } +#elif !defined(_WIN32) + // Linux and others have an FS segment pointing to valid memory, so continue to do full + // ahead-of-time patching for now until a better solution is worked out. + if (!Patches.empty()) { + TryPatchAot(reinterpret_cast(segment_addr), segment_size); + } +#endif +} + } // namespace Core diff --git a/src/core/cpu_patches.h b/src/core/cpu_patches.h index 9126074ed11..f9f7fe6460f 100644 --- a/src/core/cpu_patches.h +++ b/src/core/cpu_patches.h @@ -3,10 +3,6 @@ #pragma once -namespace Xbyak { -class CodeGenerator; -} - namespace Core { /// Initializes a stack for the current thread for use by patch implementations. @@ -15,7 +11,11 @@ void InitializeThreadPatchStack(); /// Cleans up the patch stack for the current thread. void CleanupThreadPatchStack(); -/// Patches CPU instructions that cannot run as-is on the host. -void PatchInstructions(u64 segment_addr, u64 segment_size, Xbyak::CodeGenerator& c); +/// Registers a module for patching, providing an area to generate trampoline code. +void RegisterPatchModule(void* module_ptr, u64 module_size, void* trampoline_area_ptr, + u64 trampoline_area_size); + +/// Applies CPU patches that need to be done before beginning executions. +void PrePatchInstructions(u64 segment_addr, u64 segment_size); } // namespace Core diff --git a/src/core/file_format/psf.cpp b/src/core/file_format/psf.cpp index 3d076acdceb..1df5d430ee6 100644 --- a/src/core/file_format/psf.cpp +++ b/src/core/file_format/psf.cpp @@ -2,61 +2,275 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include + +#include "common/assert.h" #include "common/io_file.h" +#include "common/logging/log.h" #include "core/file_format/psf.h" -PSF::PSF() = default; - -PSF::~PSF() = default; +static const std::unordered_map psf_known_max_sizes = { + {"ACCOUNT_ID", 8}, {"CATEGORY", 4}, {"DETAIL", 1024}, {"FORMAT", 4}, + {"MAINTITLE", 128}, {"PARAMS", 1024}, {"SAVEDATA_BLOCKS", 8}, {"SAVEDATA_DIRECTORY", 32}, + {"SUBTITLE", 128}, {"TITLE_ID", 12}, +}; +static inline u32 get_max_size(std::string_view key, u32 default_value) { + if (const auto& v = psf_known_max_sizes.find(key); v != psf_known_max_sizes.end()) { + return v->second; + } + return default_value; +} -bool PSF::open(const std::string& filepath, const std::vector& psfBuffer) { - if (!psfBuffer.empty()) { - psf.resize(psfBuffer.size()); - psf = psfBuffer; - } else { - Common::FS::IOFile file(filepath, Common::FS::FileAccessMode::Read); - if (!file.IsOpen()) { - return false; - } +bool PSF::Open(const std::filesystem::path& filepath) { + if (std::filesystem::exists(filepath)) { + last_write = std::filesystem::last_write_time(filepath); + } - const u64 psfSize = file.GetSize(); - psf.resize(psfSize); - file.Seek(0); - file.Read(psf); - file.Close(); + Common::FS::IOFile file(filepath, Common::FS::FileAccessMode::Read); + if (!file.IsOpen()) { + return false; } + const u64 psfSize = file.GetSize(); + std::vector psf(psfSize); + file.Seek(0); + file.Read(psf); + file.Close(); + return Open(psf); +} + +bool PSF::Open(const std::vector& psf_buffer) { + const u8* psf_data = psf_buffer.data(); + + entry_list.clear(); + map_binaries.clear(); + map_strings.clear(); + map_integers.clear(); + // Parse file contents - PSFHeader header; - std::memcpy(&header, psf.data(), sizeof(header)); + PSFHeader header{}; + std::memcpy(&header, psf_data, sizeof(header)); + + if (header.magic != PSF_MAGIC) { + LOG_ERROR(Core, "Invalid PSF magic number"); + return false; + } + if (header.version != PSF_VERSION_1_1 && header.version != PSF_VERSION_1_0) { + LOG_ERROR(Core, "Unsupported PSF version: 0x{:08x}", header.version); + return false; + } + for (u32 i = 0; i < header.index_table_entries; i++) { - PSFEntry entry; - std::memcpy(&entry, &psf[sizeof(PSFHeader) + i * sizeof(PSFEntry)], sizeof(entry)); + PSFRawEntry raw_entry{}; + std::memcpy(&raw_entry, psf_data + sizeof(PSFHeader) + i * sizeof(PSFRawEntry), + sizeof(raw_entry)); - const std::string key = (char*)&psf[header.key_table_offset + entry.key_offset]; - if (entry.param_fmt == PSFEntry::Fmt::TextRaw || - entry.param_fmt == PSFEntry::Fmt::TextNormal) { - map_strings[key] = (char*)&psf[header.data_table_offset + entry.data_offset]; - } - if (entry.param_fmt == PSFEntry::Fmt::Integer) { - u32 value; - std::memcpy(&value, &psf[header.data_table_offset + entry.data_offset], sizeof(value)); - map_integers[key] = value; + Entry& entry = entry_list.emplace_back(); + entry.key = std::string{(char*)(psf_data + header.key_table_offset + raw_entry.key_offset)}; + entry.param_fmt = static_cast(raw_entry.param_fmt.Raw()); + entry.max_len = raw_entry.param_max_len; + + const u8* data = psf_data + header.data_table_offset + raw_entry.data_offset; + + switch (entry.param_fmt) { + case PSFEntryFmt::Binary: { + std::vector value(raw_entry.param_len); + std::memcpy(value.data(), data, raw_entry.param_len); + map_binaries.emplace(i, std::move(value)); + } break; + case PSFEntryFmt::Text: { + std::string c_str{reinterpret_cast(data)}; + map_strings.emplace(i, std::move(c_str)); + } break; + case PSFEntryFmt::Integer: { + ASSERT_MSG(raw_entry.param_len == sizeof(s32), "PSF integer entry size mismatch"); + s32 integer = *(s32*)data; + map_integers.emplace(i, integer); + } break; + default: + UNREACHABLE_MSG("Unknown PSF entry format"); } } return true; } -std::string PSF::GetString(const std::string& key) { - if (map_strings.find(key) != map_strings.end()) { - return map_strings.at(key); +bool PSF::Encode(const std::filesystem::path& filepath) const { + Common::FS::IOFile file(filepath, Common::FS::FileAccessMode::Write); + if (!file.IsOpen()) { + return false; } - return ""; + + last_write = std::filesystem::file_time_type::clock::now(); + + const auto psf_buffer = Encode(); + return file.Write(psf_buffer) == psf_buffer.size(); +} + +std::vector PSF::Encode() const { + std::vector psf_buffer; + Encode(psf_buffer); + return psf_buffer; } -u32 PSF::GetInteger(const std::string& key) { - if (map_integers.find(key) != map_integers.end()) { - return map_integers.at(key); +void PSF::Encode(std::vector& psf_buffer) const { + psf_buffer.resize(sizeof(PSFHeader) + sizeof(PSFRawEntry) * entry_list.size()); + + { + auto& header = *(PSFHeader*)psf_buffer.data(); + header.magic = PSF_MAGIC; + header.version = PSF_VERSION_1_1; + header.index_table_entries = entry_list.size(); + } + + const size_t key_table_offset = psf_buffer.size(); + ((PSFHeader*)psf_buffer.data())->key_table_offset = key_table_offset; + for (size_t i = 0; i < entry_list.size(); i++) { + auto& raw_entry = ((PSFRawEntry*)(psf_buffer.data() + sizeof(PSFHeader)))[i]; + const Entry& entry = entry_list[i]; + raw_entry.key_offset = psf_buffer.size() - key_table_offset; + raw_entry.param_fmt.FromRaw(static_cast(entry.param_fmt)); + raw_entry.param_max_len = entry.max_len; + std::ranges::copy(entry.key, std::back_inserter(psf_buffer)); + psf_buffer.push_back(0); // NULL terminator } - return -1; + + const size_t data_table_offset = psf_buffer.size(); + ((PSFHeader*)psf_buffer.data())->data_table_offset = data_table_offset; + for (size_t i = 0; i < entry_list.size(); i++) { + if (psf_buffer.size() % 4 != 0) { + std::ranges::fill_n(std::back_inserter(psf_buffer), 4 - psf_buffer.size() % 4, 0); + } + auto& raw_entry = ((PSFRawEntry*)(psf_buffer.data() + sizeof(PSFHeader)))[i]; + const Entry& entry = entry_list[i]; + raw_entry.data_offset = psf_buffer.size() - data_table_offset; + + s32 additional_padding = s32(raw_entry.param_max_len); + + switch (entry.param_fmt) { + case PSFEntryFmt::Binary: { + const auto& value = map_binaries.at(i); + raw_entry.param_len = value.size(); + additional_padding -= s32(raw_entry.param_len); + std::ranges::copy(value, std::back_inserter(psf_buffer)); + } break; + case PSFEntryFmt::Text: { + const auto& value = map_strings.at(i); + raw_entry.param_len = value.size() + 1; + additional_padding -= s32(raw_entry.param_len); + std::ranges::copy(value, std::back_inserter(psf_buffer)); + psf_buffer.push_back(0); // NULL terminator + } break; + case PSFEntryFmt::Integer: { + const auto& value = map_integers.at(i); + raw_entry.param_len = sizeof(s32); + additional_padding -= s32(raw_entry.param_len); + const auto value_bytes = reinterpret_cast(&value); + std::ranges::copy(value_bytes, value_bytes + sizeof(s32), + std::back_inserter(psf_buffer)); + } break; + default: + UNREACHABLE_MSG("Unknown PSF entry format"); + } + ASSERT_MSG(additional_padding >= 0, "PSF entry max size mismatch"); + std::ranges::fill_n(std::back_inserter(psf_buffer), additional_padding, 0); + } +} + +std::optional> PSF::GetBinary(std::string_view key) const { + const auto& [it, index] = FindEntry(key); + if (it == entry_list.end()) { + return {}; + } + ASSERT(it->param_fmt == PSFEntryFmt::Binary); + return std::span{map_binaries.at(index)}; +} + +std::optional PSF::GetString(std::string_view key) const { + const auto& [it, index] = FindEntry(key); + if (it == entry_list.end()) { + return {}; + } + ASSERT(it->param_fmt == PSFEntryFmt::Text); + return std::string_view{map_strings.at(index)}; +} + +std::optional PSF::GetInteger(std::string_view key) const { + const auto& [it, index] = FindEntry(key); + if (it == entry_list.end()) { + return {}; + } + ASSERT(it->param_fmt == PSFEntryFmt::Integer); + return map_integers.at(index); +} + +void PSF::AddBinary(std::string key, std::vector value, bool update) { + auto [it, index] = FindEntry(key); + bool exist = it != entry_list.end(); + if (exist && !update) { + LOG_ERROR(Core, "PSF: Tried to add binary key that already exists: {}", key); + return; + } + if (exist) { + ASSERT_MSG(it->param_fmt == PSFEntryFmt::Binary, "PSF: Change format is not supported"); + it->max_len = get_max_size(key, value.size()); + map_binaries.at(index) = std::move(value); + return; + } + Entry& entry = entry_list.emplace_back(); + entry.max_len = get_max_size(key, value.size()); + entry.key = std::move(key); + entry.param_fmt = PSFEntryFmt::Binary; + map_binaries.emplace(entry_list.size() - 1, std::move(value)); +} + +void PSF::AddString(std::string key, std::string value, bool update) { + auto [it, index] = FindEntry(key); + bool exist = it != entry_list.end(); + if (exist && !update) { + LOG_ERROR(Core, "PSF: Tried to add string key that already exists: {}", key); + return; + } + if (exist) { + ASSERT_MSG(it->param_fmt == PSFEntryFmt::Text, "PSF: Change format is not supported"); + it->max_len = get_max_size(key, value.size() + 1); + map_strings.at(index) = std::move(value); + return; + } + Entry& entry = entry_list.emplace_back(); + entry.max_len = get_max_size(key, value.size() + 1); + entry.key = std::move(key); + entry.param_fmt = PSFEntryFmt::Text; + map_strings.emplace(entry_list.size() - 1, std::move(value)); +} + +void PSF::AddInteger(std::string key, s32 value, bool update) { + auto [it, index] = FindEntry(key); + bool exist = it != entry_list.end(); + if (exist && !update) { + LOG_ERROR(Core, "PSF: Tried to add integer key that already exists: {}", key); + return; + } + if (exist) { + ASSERT_MSG(it->param_fmt == PSFEntryFmt::Integer, "PSF: Change format is not supported"); + it->max_len = sizeof(s32); + map_integers.at(index) = value; + return; + } + Entry& entry = entry_list.emplace_back(); + entry.key = std::move(key); + entry.param_fmt = PSFEntryFmt::Integer; + entry.max_len = sizeof(s32); + map_integers.emplace(entry_list.size() - 1, value); +} + +std::pair::iterator, size_t> PSF::FindEntry(std::string_view key) { + auto entry = + std::ranges::find_if(entry_list, [&](const auto& entry) { return entry.key == key; }); + return {entry, std::distance(entry_list.begin(), entry)}; +} + +std::pair::const_iterator, size_t> PSF::FindEntry( + std::string_view key) const { + auto entry = + std::ranges::find_if(entry_list, [&](const auto& entry) { return entry.key == key; }); + return {entry, std::distance(entry_list.begin(), entry)}; } diff --git a/src/core/file_format/psf.h b/src/core/file_format/psf.h index 9a162b1d526..d25b79eece7 100644 --- a/src/core/file_format/psf.h +++ b/src/core/file_format/psf.h @@ -3,11 +3,18 @@ #pragma once +#include +#include #include +#include #include #include #include "common/endian.h" +constexpr u32 PSF_MAGIC = 0x00505346; +constexpr u32 PSF_VERSION_1_1 = 0x00000101; +constexpr u32 PSF_VERSION_1_0 = 0x00000100; + struct PSFHeader { u32_be magic; u32_le version; @@ -15,34 +22,72 @@ struct PSFHeader { u32_le data_table_offset; u32_le index_table_entries; }; +static_assert(sizeof(PSFHeader) == 0x14); -struct PSFEntry { - enum Fmt : u16 { - TextRaw = 0x0400, // String in UTF-8 format and not NULL terminated - TextNormal = 0x0402, // String in UTF-8 format and NULL terminated - Integer = 0x0404, // Unsigned 32-bit integer - }; - +struct PSFRawEntry { u16_le key_offset; u16_be param_fmt; u32_le param_len; u32_le param_max_len; u32_le data_offset; }; +static_assert(sizeof(PSFRawEntry) == 0x10); + +enum class PSFEntryFmt : u16 { + Binary = 0x0004, // Binary data + Text = 0x0204, // String in UTF-8 format and NULL terminated + Integer = 0x0404, // Signed 32-bit integer +}; class PSF { + struct Entry { + std::string key; + PSFEntryFmt param_fmt; + u32 max_len; + }; + public: - PSF(); - ~PSF(); + PSF() = default; + ~PSF() = default; - bool open(const std::string& filepath, const std::vector& psfBuffer); + PSF(const PSF& other) = default; + PSF(PSF&& other) noexcept = default; + PSF& operator=(const PSF& other) = default; + PSF& operator=(PSF&& other) noexcept = default; - std::string GetString(const std::string& key); - u32 GetInteger(const std::string& key); + bool Open(const std::filesystem::path& filepath); + bool Open(const std::vector& psf_buffer); - std::unordered_map map_strings; - std::unordered_map map_integers; + [[nodiscard]] std::vector Encode() const; + void Encode(std::vector& buf) const; + bool Encode(const std::filesystem::path& filepath) const; + + std::optional> GetBinary(std::string_view key) const; + std::optional GetString(std::string_view key) const; + std::optional GetInteger(std::string_view key) const; + + void AddBinary(std::string key, std::vector value, bool update = false); + void AddString(std::string key, std::string value, bool update = false); + void AddInteger(std::string key, s32 value, bool update = false); + + [[nodiscard]] std::filesystem::file_time_type GetLastWrite() const { + return last_write; + } + + [[nodiscard]] const std::vector& GetEntries() const { + return entry_list; + } private: - std::vector psf; + mutable std::filesystem::file_time_type last_write; + + std::vector entry_list; + + std::unordered_map> map_binaries; + std::unordered_map map_strings; + std::unordered_map map_integers; + + [[nodiscard]] std::pair::iterator, size_t> FindEntry(std::string_view key); + [[nodiscard]] std::pair::const_iterator, size_t> FindEntry( + std::string_view key) const; }; diff --git a/src/core/file_format/trp.cpp b/src/core/file_format/trp.cpp index f122709e4b7..86865fe63b2 100644 --- a/src/core/file_format/trp.cpp +++ b/src/core/file_format/trp.cpp @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include "common/path_util.h" #include "trp.h" TRP::TRP() = default; @@ -48,8 +49,9 @@ bool TRP::Extract(const std::filesystem::path& trophyPath) { return false; s64 seekPos = sizeof(TrpHeader); - std::filesystem::path trpFilesPath(std::filesystem::current_path() / "user/game_data" / - title / "TrophyFiles" / it.path().stem()); + std::filesystem::path trpFilesPath( + Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / title / "TrophyFiles" / + it.path().stem()); std::filesystem::create_directories(trpFilesPath / "Icons"); std::filesystem::create_directory(trpFilesPath / "Xml"); diff --git a/src/core/file_sys/fs.cpp b/src/core/file_sys/fs.cpp index 199e42a046e..3b060dd8327 100644 --- a/src/core/file_sys/fs.cpp +++ b/src/core/file_sys/fs.cpp @@ -9,12 +9,14 @@ namespace Core::FileSys { constexpr int RESERVED_HANDLES = 3; // First 3 handles are stdin,stdout,stderr -void MntPoints::Mount(const std::filesystem::path& host_folder, const std::string& guest_folder) { +void MntPoints::Mount(const std::filesystem::path& host_folder, const std::string& guest_folder, + bool read_only) { std::scoped_lock lock{m_mutex}; - m_mnt_pairs.emplace_back(host_folder, guest_folder); + m_mnt_pairs.emplace_back(host_folder, guest_folder, read_only); } void MntPoints::Unmount(const std::filesystem::path& host_folder, const std::string& guest_folder) { + std::scoped_lock lock{m_mutex}; auto it = std::remove_if(m_mnt_pairs.begin(), m_mnt_pairs.end(), [&](const MntPair& pair) { return pair.mount == guest_folder; }); m_mnt_pairs.erase(it, m_mnt_pairs.end()); @@ -25,7 +27,7 @@ void MntPoints::UnmountAll() { m_mnt_pairs.clear(); } -std::filesystem::path MntPoints::GetHostPath(std::string_view guest_directory) { +std::filesystem::path MntPoints::GetHostPath(std::string_view guest_directory, bool* is_read_only) { // Evil games like Turok2 pass double slashes e.g /app0//game.kpf std::string corrected_path(guest_directory); size_t pos = corrected_path.find("//"); @@ -39,6 +41,10 @@ std::filesystem::path MntPoints::GetHostPath(std::string_view guest_directory) { return ""; } + if (is_read_only) { + *is_read_only = mount->read_only; + } + // Nothing to do if getting the mount itself. if (corrected_path == mount->mount) { return mount->host_path; diff --git a/src/core/file_sys/fs.h b/src/core/file_sys/fs.h index 2c55b0513cf..eeaeaf7810b 100644 --- a/src/core/file_sys/fs.h +++ b/src/core/file_sys/fs.h @@ -22,18 +22,22 @@ class MntPoints { struct MntPair { std::filesystem::path host_path; std::string mount; // e.g /app0/ + bool read_only; }; explicit MntPoints() = default; ~MntPoints() = default; - void Mount(const std::filesystem::path& host_folder, const std::string& guest_folder); + void Mount(const std::filesystem::path& host_folder, const std::string& guest_folder, + bool read_only = false); void Unmount(const std::filesystem::path& host_folder, const std::string& guest_folder); void UnmountAll(); - std::filesystem::path GetHostPath(std::string_view guest_directory); + std::filesystem::path GetHostPath(std::string_view guest_directory, + bool* is_read_only = nullptr); const MntPair* GetMount(const std::string& guest_path) { + std::scoped_lock lock{m_mutex}; const auto it = std::ranges::find_if( m_mnt_pairs, [&](const auto& mount) { return guest_path.starts_with(mount.mount); }); return it == m_mnt_pairs.end() ? nullptr : &*it; diff --git a/src/core/libraries/app_content/app_content.cpp b/src/core/libraries/app_content/app_content.cpp index 125d19684a6..754343eef92 100644 --- a/src/core/libraries/app_content/app_content.cpp +++ b/src/core/libraries/app_content/app_content.cpp @@ -4,6 +4,7 @@ #include #include "app_content.h" +#include "common/assert.h" #include "common/io_file.h" #include "common/logging/log.h" #include "common/path_util.h" @@ -90,37 +91,32 @@ int PS4_SYSV_ABI sceAppContentAddcontUnmount() { return ORBIS_OK; } -int PS4_SYSV_ABI sceAppContentAppParamGetInt(OrbisAppContentAppParamId paramId, s32* value) { - if (value == nullptr) +int PS4_SYSV_ABI sceAppContentAppParamGetInt(OrbisAppContentAppParamId paramId, s32* out_value) { + if (out_value == nullptr) return ORBIS_APP_CONTENT_ERROR_PARAMETER; auto* param_sfo = Common::Singleton::Instance(); + std::optional value; switch (paramId) { case ORBIS_APP_CONTENT_APPPARAM_ID_SKU_FLAG: - *value = ORBIS_APP_CONTENT_APPPARAM_SKU_FLAG_FULL; + value = ORBIS_APP_CONTENT_APPPARAM_SKU_FLAG_FULL; break; case ORBIS_APP_CONTENT_APPPARAM_ID_USER_DEFINED_PARAM_1: - *value = param_sfo->GetInteger("USER_DEFINED_PARAM_1"); + value = param_sfo->GetInteger("USER_DEFINED_PARAM_1"); break; case ORBIS_APP_CONTENT_APPPARAM_ID_USER_DEFINED_PARAM_2: - *value = param_sfo->GetInteger("USER_DEFINED_PARAM_2"); + value = param_sfo->GetInteger("USER_DEFINED_PARAM_2"); break; case ORBIS_APP_CONTENT_APPPARAM_ID_USER_DEFINED_PARAM_3: - *value = param_sfo->GetInteger("USER_DEFINED_PARAM_3"); + value = param_sfo->GetInteger("USER_DEFINED_PARAM_3"); break; case ORBIS_APP_CONTENT_APPPARAM_ID_USER_DEFINED_PARAM_4: - *value = param_sfo->GetInteger("USER_DEFINED_PARAM_4"); + value = param_sfo->GetInteger("USER_DEFINED_PARAM_4"); break; default: - LOG_ERROR(Lib_AppContent, " paramId = {}, value = {} paramId is not valid", paramId, - *value); - return ORBIS_APP_CONTENT_ERROR_PARAMETER; - } - if (*value == -1) { - LOG_ERROR(Lib_AppContent, - " paramId = {}, value = {} value is not valid can't read param.sfo?", paramId, - *value); + LOG_ERROR(Lib_AppContent, " paramId = {} paramId is not valid", paramId); return ORBIS_APP_CONTENT_ERROR_PARAMETER; } + *out_value = value.value_or(0); return ORBIS_OK; } @@ -251,7 +247,11 @@ int PS4_SYSV_ABI sceAppContentInitialize(const OrbisAppContentInitParam* initPar auto* param_sfo = Common::Singleton::Instance(); const auto addons_dir = Common::FS::GetUserPath(Common::FS::PathType::AddonsDir); - title_id = param_sfo->GetString("TITLE_ID"); + if (const auto value = param_sfo->GetString("TITLE_ID"); value.has_value()) { + title_id = *value; + } else { + UNREACHABLE_MSG("Failed to get TITLE_ID"); + } auto addon_path = addons_dir / title_id; if (std::filesystem::exists(addon_path)) { for (const auto& entry : std::filesystem::directory_iterator(addon_path)) { diff --git a/src/core/libraries/audio3d/audio3d.cpp b/src/core/libraries/audio3d/audio3d.cpp new file mode 100644 index 00000000000..63815a06820 --- /dev/null +++ b/src/core/libraries/audio3d/audio3d.cpp @@ -0,0 +1,344 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio3d.h" +#include "audio3d_error.h" +#include "audio3d_impl.h" + +#include "common/logging/log.h" +#include "core/libraries/audio/audioout.h" +#include "core/libraries/error_codes.h" +#include "core/libraries/libs.h" + +namespace Libraries::Audio3d { + +// Audio3d + +int PS4_SYSV_ABI sceAudio3dInitialize(s64 iReserved) { + LOG_INFO(Lib_Audio3d, "iReserved = {}", iReserved); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceAudio3dTerminate() { + // TODO: When not initialized or some ports still open, return ORBIS_AUDIO3D_ERROR_NOT_READY + LOG_INFO(Lib_Audio3d, "called"); + return ORBIS_OK; +} + +void PS4_SYSV_ABI sceAudio3dGetDefaultOpenParameters(OrbisAudio3dOpenParameters* sParameters) { + if (sParameters != NULL) { + sParameters->szSizeThis = sizeof(OrbisAudio3dOpenParameters); + sParameters->uiGranularity = 256; + sParameters->eRate = ORBIS_AUDIO3D_RATE_48000; + sParameters->uiMaxObjects = 512; + sParameters->uiQueueDepth = 2; + sParameters->eBufferMode = ORBIS_AUDIO3D_BUFFER_ADVANCE_AND_PUSH; + sParameters->uiNumBeds = 2; + } else { + LOG_ERROR(Lib_Audio3d, "Invalid OpenParameters ptr"); + } +} + +int PS4_SYSV_ABI sceAudio3dPortOpen(OrbisUserServiceUserId iUserId, + const OrbisAudio3dOpenParameters* pParameters, + OrbisAudio3dPortId* pId) { + LOG_INFO(Lib_Audio3d, "iUserId = {}", iUserId); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceAudio3dPortClose(OrbisAudio3dPortId uiPortId) { + LOG_INFO(Lib_Audio3d, "uiPortId = {}", uiPortId); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceAudio3dPortSetAttribute(OrbisAudio3dPortId uiPortId, + OrbisAudio3dAttributeId uiAttributeId, + const void* pAttribute, size_t szAttribute) { + LOG_INFO(Lib_Audio3d, "uiPortId = {}, uiAttributeId = {}, szAttribute = {}", uiPortId, + uiAttributeId, szAttribute); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceAudio3dPortFlush(OrbisAudio3dPortId uiPortId) { + LOG_INFO(Lib_Audio3d, "uiPortId = {}", uiPortId); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceAudio3dPortAdvance(OrbisAudio3dPortId uiPortId) { + LOG_INFO(Lib_Audio3d, "uiPortId = {}", uiPortId); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceAudio3dPortPush(OrbisAudio3dPortId uiPortId, OrbisAudio3dBlocking eBlocking) { + LOG_INFO(Lib_Audio3d, "uiPortId = {}", uiPortId); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceAudio3dPortGetAttributesSupported(OrbisAudio3dPortId uiPortId, + OrbisAudio3dAttributeId* pCapabilities, + unsigned int* pNumCapabilities) { + LOG_INFO(Lib_Audio3d, "uiPortId = {}", uiPortId); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceAudio3dPortGetQueueLevel(OrbisAudio3dPortId uiPortId, unsigned int* pQueueLevel, + unsigned int* pQueueAvailable) { + LOG_INFO(Lib_Audio3d, "uiPortId = {}", uiPortId); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceAudio3dObjectReserve(OrbisAudio3dPortId uiPortId, OrbisAudio3dObjectId* pId) { + LOG_INFO(Lib_Audio3d, "uiPortId = {}", uiPortId); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceAudio3dObjectUnreserve(OrbisAudio3dPortId uiPortId, + OrbisAudio3dObjectId uiObjectId) { + LOG_INFO(Lib_Audio3d, "uiPortId = {}, uiObjectId = {}", uiPortId, uiObjectId); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceAudio3dObjectSetAttributes(OrbisAudio3dPortId uiPortId, + OrbisAudio3dObjectId uiObjectId, + size_t szNumAttributes, + const OrbisAudio3dAttribute* pAttributeArray) { + LOG_INFO(Lib_Audio3d, "uiPortId = {}, uiObjectId = {}, szNumAttributes = {}", uiPortId, + uiObjectId, szNumAttributes); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceAudio3dBedWrite(OrbisAudio3dPortId uiPortId, unsigned int uiNumChannels, + OrbisAudio3dFormat eFormat, const void* pBuffer, + unsigned int uiNumSamples) { + LOG_INFO(Lib_Audio3d, "uiPortId = {}, uiNumChannels = {}, uiNumSamples = {}", uiPortId, + uiNumChannels, uiNumSamples); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceAudio3dBedWrite2(OrbisAudio3dPortId uiPortId, unsigned int uiNumChannels, + OrbisAudio3dFormat eFormat, const void* pBuffer, + unsigned int uiNumSamples, + OrbisAudio3dOutputRoute eOutputRoute, bool bRestricted) { + LOG_INFO(Lib_Audio3d, "uiPortId = {}, uiNumChannels = {}, uiNumSamples = {}, bRestricted = {}", + uiPortId, uiNumChannels, uiNumSamples, bRestricted); + return ORBIS_OK; +} + +size_t PS4_SYSV_ABI sceAudio3dGetSpeakerArrayMemorySize(unsigned int uiNumSpeakers, bool bIs3d) { + LOG_INFO(Lib_Audio3d, "uiNumSpeakers = {}, bIs3d = {}", uiNumSpeakers, bIs3d); + return ORBIS_OK; +} + +int PS4_SYSV_ABI +sceAudio3dCreateSpeakerArray(OrbisAudio3dSpeakerArrayHandle* pHandle, + const OrbisAudio3dSpeakerArrayParameters* pParameters) { + if (pHandle == nullptr || pParameters == nullptr) { + LOG_ERROR(Lib_Audio3d, "invalid SpeakerArray parameters"); + return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER; + } + LOG_INFO(Lib_Audio3d, "called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceAudio3dDeleteSpeakerArray(OrbisAudio3dSpeakerArrayHandle handle) { + if (handle == nullptr) { + LOG_ERROR(Lib_Audio3d, "invalid SpeakerArrayHandle"); + return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER; + } + LOG_INFO(Lib_Audio3d, "called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceAudio3dGetSpeakerArrayMixCoefficients(OrbisAudio3dSpeakerArrayHandle handle, + OrbisAudio3dPosition pos, float fSpread, + float* pCoefficients, + unsigned int uiNumCoefficients) { + LOG_INFO(Lib_Audio3d, "fSpread = {}, uiNumCoefficients = {}", fSpread, uiNumCoefficients); + if (handle == nullptr) { + LOG_ERROR(Lib_Audio3d, "invalid SpeakerArrayHandle"); + return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER; + } + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceAudio3dGetSpeakerArrayMixCoefficients2(OrbisAudio3dSpeakerArrayHandle handle, + OrbisAudio3dPosition pos, float fSpread, + float* pCoefficients, + unsigned int uiNumCoefficients, + bool bHeightAware, + float fDownmixSpreadRadius) { + LOG_INFO(Lib_Audio3d, + "fSpread = {}, uiNumCoefficients = {}, bHeightAware = {}, fDownmixSpreadRadius = {}", + fSpread, uiNumCoefficients, bHeightAware, fDownmixSpreadRadius); + if (handle == nullptr) { + LOG_ERROR(Lib_Audio3d, "invalid SpeakerArrayHandle"); + return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER; + } + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAudio3dAudioOutOpen(OrbisAudio3dPortId uiPortId, OrbisUserServiceUserId userId, + s32 type, s32 index, u32 len, u32 freq, u32 param) { + LOG_INFO(Lib_Audio3d, + "uiPortId = {}, userId = {}, type = {}, index = {}, len = {}, freq = {}, param = {}", + uiPortId, userId, type, index, len, freq, param); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAudio3dAudioOutClose(s32 handle) { + LOG_INFO(Lib_Audio3d, "handle = {}", handle); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAudio3dAudioOutOutput(s32 handle, const void* ptr) { + LOG_INFO(Lib_Audio3d, "handle = {}", handle); + if (ptr == nullptr) { + LOG_ERROR(Lib_Audio3d, "invalid Output ptr"); + return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER; + } + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAudio3dAudioOutOutputs(::Libraries::AudioOut::OrbisAudioOutOutputParam* param, + s32 num) { + LOG_INFO(Lib_Audio3d, "num = {}", num); + if (param == nullptr) { + LOG_ERROR(Lib_Audio3d, "invalid OutputParam ptr"); + return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER; + } + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceAudio3dPortCreate(unsigned int uiGranularity, OrbisAudio3dRate eRate, + s64 iReserved, OrbisAudio3dPortId* pId) { + LOG_INFO(Lib_Audio3d, "uiGranularity = {}, iReserved = {}", uiGranularity, iReserved); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceAudio3dPortDestroy(OrbisAudio3dPortId uiPortId) { + LOG_INFO(Lib_Audio3d, "uiPortId = {}", uiPortId); + return ORBIS_OK; +} + +// Audio3dPrivate + +const char* PS4_SYSV_ABI sceAudio3dStrError(int iErrorCode) { + LOG_ERROR(Lib_Audio3d, "(PRIVATE) called, iErrorCode = {}", iErrorCode); + return "NOT_IMPLEMENTED"; +} + +// Audio3dDebug + +int PS4_SYSV_ABI sceAudio3dPortQueryDebug() { + LOG_WARNING(Lib_Audio3d, "(DEBUG) sceAudio3dPortQueryDebug called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceAudio3dPortGetList() { + LOG_WARNING(Lib_Audio3d, "(DEBUG) sceAudio3dPortGetList called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceAudio3dPortGetParameters() { + LOG_WARNING(Lib_Audio3d, "(DEBUG) sceAudio3dPortGetParameters called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceAudio3dPortGetState() { + LOG_WARNING(Lib_Audio3d, "(DEBUG) sceAudio3dPortGetState called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceAudio3dPortFreeState() { + LOG_WARNING(Lib_Audio3d, "(DEBUG) sceAudio3dPortFreeState called"); + return ORBIS_OK; +} + +// Unknown + +int PS4_SYSV_ABI sceAudio3dPortGetBufferLevel() { + LOG_ERROR(Lib_Audio3d, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceAudio3dPortGetStatus() { + LOG_ERROR(Lib_Audio3d, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceAudio3dReportRegisterHandler() { + LOG_ERROR(Lib_Audio3d, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceAudio3dReportUnregisterHandler() { + LOG_ERROR(Lib_Audio3d, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceAudio3dSetGpuRenderer() { + LOG_ERROR(Lib_Audio3d, "(STUBBED) called"); + return ORBIS_OK; +} + +void RegisterlibSceAudio3d(Core::Loader::SymbolsResolver* sym) { + LIB_FUNCTION("-R1DukFq7Dk", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, + sceAudio3dGetSpeakerArrayMixCoefficients); + LIB_FUNCTION("-Re+pCWvwjQ", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, + sceAudio3dGetSpeakerArrayMixCoefficients2); + LIB_FUNCTION("-pzYDZozm+M", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, + sceAudio3dPortQueryDebug); + LIB_FUNCTION("1HXxo-+1qCw", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, + sceAudio3dObjectUnreserve); + LIB_FUNCTION("4uyHN9q4ZeU", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, + sceAudio3dObjectSetAttributes); + LIB_FUNCTION("7NYEzJ9SJbM", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, + sceAudio3dAudioOutOutput); + LIB_FUNCTION("8hm6YdoQgwg", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, + sceAudio3dDeleteSpeakerArray); + LIB_FUNCTION("9ZA23Ia46Po", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, + sceAudio3dPortGetAttributesSupported); + LIB_FUNCTION("9tEwE0GV0qo", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dBedWrite); + LIB_FUNCTION("Aacl5qkRU6U", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dStrError); + LIB_FUNCTION("CKHlRW2E9dA", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dPortGetState); + LIB_FUNCTION("HbxYY27lK6E", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, + sceAudio3dAudioOutOutputs); + LIB_FUNCTION("Im+jOoa5WAI", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, + sceAudio3dGetDefaultOpenParameters); + LIB_FUNCTION("Mw9mRQtWepY", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dPortDestroy); + LIB_FUNCTION("OyVqOeVNtSk", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dPortClose); + LIB_FUNCTION("QfNXBrKZeI0", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, + sceAudio3dReportRegisterHandler); + LIB_FUNCTION("QqgTQQdzEMY", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, + sceAudio3dPortGetBufferLevel); + LIB_FUNCTION("SEggctIeTcI", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dPortGetList); + LIB_FUNCTION("UHFOgVNz0kk", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dPortCreate); + LIB_FUNCTION("UmCvjSmuZIw", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dInitialize); + LIB_FUNCTION("VEVhZ9qd4ZY", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dPortPush); + LIB_FUNCTION("WW1TS2iz5yc", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dTerminate); + LIB_FUNCTION("XeDDK0xJWQA", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dPortOpen); + LIB_FUNCTION("YaaDbDwKpFM", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, + sceAudio3dPortGetQueueLevel); + LIB_FUNCTION("Yq9bfUQ0uJg", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, + sceAudio3dPortSetAttribute); + LIB_FUNCTION("ZOGrxWLgQzE", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dPortFlush); + LIB_FUNCTION("flPcUaXVXcw", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, + sceAudio3dPortGetParameters); + LIB_FUNCTION("iRX6GJs9tvE", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dPortGetStatus); + LIB_FUNCTION("jO2tec4dJ2M", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dObjectReserve); + LIB_FUNCTION("kEqqyDkmgdI", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, + sceAudio3dGetSpeakerArrayMemorySize); + LIB_FUNCTION("lvWMW6vEqFU", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, + sceAudio3dCreateSpeakerArray); + LIB_FUNCTION("lw0qrdSjZt8", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dPortAdvance); + LIB_FUNCTION("pZlOm1aF3aA", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dAudioOutClose); + LIB_FUNCTION("psv2gbihC1A", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, + sceAudio3dReportUnregisterHandler); + LIB_FUNCTION("uJ0VhGcxCTQ", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dPortFreeState); + LIB_FUNCTION("ucEsi62soTo", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dAudioOutOpen); + LIB_FUNCTION("xH4Q9UILL3o", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, sceAudio3dBedWrite2); + LIB_FUNCTION("yEYXcbAGK14", "libSceAudio3d", 1, "libSceAudio3d", 1, 1, + sceAudio3dSetGpuRenderer); +}; + +} // namespace Libraries::Audio3d \ No newline at end of file diff --git a/src/core/libraries/audio3d/audio3d.h b/src/core/libraries/audio3d/audio3d.h new file mode 100644 index 00000000000..6cbe2d02f68 --- /dev/null +++ b/src/core/libraries/audio3d/audio3d.h @@ -0,0 +1,134 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/types.h" + +#include + +namespace Core::Loader { +class SymbolsResolver; +} + +namespace Libraries::Audio3d { + +class Audio3d; + +typedef int OrbisUserServiceUserId; +typedef unsigned int OrbisAudio3dPortId; +typedef unsigned int OrbisAudio3dObjectId; +typedef unsigned int OrbisAudio3dAttributeId; + +enum OrbisAudio3dFormat { + ORBIS_AUDIO3D_FORMAT_S16 = 0, // s16 + ORBIS_AUDIO3D_FORMAT_FLOAT = 1 // f32 +}; + +enum OrbisAudio3dRate { ORBIS_AUDIO3D_RATE_48000 = 0 }; + +enum OrbisAudio3dBufferMode { + ORBIS_AUDIO3D_BUFFER_NO_ADVANCE = 0, + ORBIS_AUDIO3D_BUFFER_ADVANCE_NO_PUSH = 1, + ORBIS_AUDIO3D_BUFFER_ADVANCE_AND_PUSH = 2 +}; + +enum OrbisAudio3dBlocking { ORBIS_AUDIO3D_BLOCKING_ASYNC = 0, ORBIS_AUDIO3D_BLOCKING_SYNC = 1 }; + +enum OrbisAudio3dPassthrough { + ORBIS_AUDIO3D_PASSTHROUGH_NONE = 0, + ORBIS_AUDIO3D_PASSTHROUGH_LEFT = 1, + ORBIS_AUDIO3D_PASSTHROUGH_RIGHT = 2 +}; + +enum OrbisAudio3dOutputRoute { + ORBIS_AUDIO3D_OUTPUT_BOTH = 0, + ORBIS_AUDIO3D_OUTPUT_HMU_ONLY = 1, + ORBIS_AUDIO3D_OUTPUT_TV_ONLY = 2 +}; + +enum OrbisAudio3dAmbisonics { + ORBIS_AUDIO3D_AMBISONICS_NONE = ~0, + ORBIS_AUDIO3D_AMBISONICS_W = 0, + ORBIS_AUDIO3D_AMBISONICS_X = 1, + ORBIS_AUDIO3D_AMBISONICS_Y = 2, + ORBIS_AUDIO3D_AMBISONICS_Z = 3, + ORBIS_AUDIO3D_AMBISONICS_R = 4, + ORBIS_AUDIO3D_AMBISONICS_S = 5, + ORBIS_AUDIO3D_AMBISONICS_T = 6, + ORBIS_AUDIO3D_AMBISONICS_U = 7, + ORBIS_AUDIO3D_AMBISONICS_V = 8, + ORBIS_AUDIO3D_AMBISONICS_K = 9, + ORBIS_AUDIO3D_AMBISONICS_L = 10, + ORBIS_AUDIO3D_AMBISONICS_M = 11, + ORBIS_AUDIO3D_AMBISONICS_N = 12, + ORBIS_AUDIO3D_AMBISONICS_O = 13, + ORBIS_AUDIO3D_AMBISONICS_P = 14, + ORBIS_AUDIO3D_AMBISONICS_Q = 15 +}; + +static const OrbisAudio3dAttributeId s_sceAudio3dAttributePcm = 0x00000001; +static const OrbisAudio3dAttributeId s_sceAudio3dAttributePriority = 0x00000002; +static const OrbisAudio3dAttributeId s_sceAudio3dAttributePosition = 0x00000003; +static const OrbisAudio3dAttributeId s_sceAudio3dAttributeSpread = 0x00000004; +static const OrbisAudio3dAttributeId s_sceAudio3dAttributeGain = 0x00000005; +static const OrbisAudio3dAttributeId s_sceAudio3dAttributePassthrough = 0x00000006; +static const OrbisAudio3dAttributeId s_sceAudio3dAttributeResetState = 0x00000007; +static const OrbisAudio3dAttributeId s_sceAudio3dAttributeApplicationSpecific = 0x00000008; +static const OrbisAudio3dAttributeId s_sceAudio3dAttributeAmbisonics = 0x00000009; +static const OrbisAudio3dAttributeId s_sceAudio3dAttributeRestricted = 0x0000000A; +static const OrbisAudio3dAttributeId s_sceAudio3dAttributeOutputRoute = 0x0000000B; +static const OrbisAudio3dAttributeId s_sceAudio3dAttributeLateReverbLevel = 0x00010001; +static const OrbisAudio3dAttributeId s_sceAudio3dAttributeDownmixSpreadRadius = 0x00010002; +static const OrbisAudio3dAttributeId s_sceAudio3dAttributeDownmixSpreadHeightAware = 0x00010003; + +struct OrbisAudio3dSpeakerArray; +using OrbisAudio3dSpeakerArrayHandle = OrbisAudio3dSpeakerArray*; // head + +struct OrbisAudio3dOpenParameters { + size_t szSizeThis; + unsigned int uiGranularity; + OrbisAudio3dRate eRate; + unsigned int uiMaxObjects; + unsigned int uiQueueDepth; + OrbisAudio3dBufferMode eBufferMode; + char padding[32]; + unsigned int uiNumBeds; +}; + +struct OrbisAudio3dAttribute { + OrbisAudio3dAttributeId uiAttributeId; + char padding[32]; + const void* pValue; + size_t szValue; +}; + +struct OrbisAudio3dPosition { + float fX; + float fY; + float fZ; +}; + +struct OrbisAudio3dPcm { + OrbisAudio3dFormat eFormat; + const void* pSampleBuffer; + unsigned int uiNumSamples; +}; + +struct OrbisAudio3dSpeakerArrayParameters { + OrbisAudio3dPosition* pSpeakerPosition; + unsigned int uiNumSpeakers; + bool bIs3d; + void* pBuffer; + size_t szSize; +}; + +struct OrbisAudio3dApplicationSpecific { + size_t szSizeThis; + u8 cApplicationSpecific[32]; +}; + +void PS4_SYSV_ABI sceAudio3dGetDefaultOpenParameters(OrbisAudio3dOpenParameters* sParameters); + +void RegisterlibSceAudio3d(Core::Loader::SymbolsResolver* sym); +} // namespace Libraries::Audio3d \ No newline at end of file diff --git a/src/core/libraries/audio3d/audio3d_error.h b/src/core/libraries/audio3d/audio3d_error.h new file mode 100644 index 00000000000..ff9d9749cc2 --- /dev/null +++ b/src/core/libraries/audio3d/audio3d_error.h @@ -0,0 +1,13 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +constexpr int ORBIS_AUDIO3D_ERROR_UNKNOWN = 0x80EA0001; +constexpr int ORBIS_AUDIO3D_ERROR_INVALID_PORT = 0x80EA0002; +constexpr int ORBIS_AUDIO3D_ERROR_INVALID_OBJECT = 0x80EA0003; +constexpr int ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER = 0x80EA0004; +constexpr int ORBIS_AUDIO3D_ERROR_OUT_OF_MEMORY = 0x80EA0005; +constexpr int ORBIS_AUDIO3D_ERROR_OUT_OF_RESOURCES = 0x80EA0006; +constexpr int ORBIS_AUDIO3D_ERROR_NOT_READY = 0x80EA0007; +constexpr int ORBIS_AUDIO3D_ERROR_NOT_SUPPORTED = 0x80EA0008; diff --git a/src/core/libraries/audio3d/audio3d_impl.cpp b/src/core/libraries/audio3d/audio3d_impl.cpp new file mode 100644 index 00000000000..c267c096fce --- /dev/null +++ b/src/core/libraries/audio3d/audio3d_impl.cpp @@ -0,0 +1,13 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio3d_error.h" +#include "audio3d_impl.h" + +#include "common/logging/log.h" +#include "core/libraries/error_codes.h" +#include "core/libraries/kernel/libkernel.h" + +using namespace Libraries::Kernel; + +namespace Libraries::Audio3d {} // namespace Libraries::Audio3d diff --git a/src/core/libraries/audio3d/audio3d_impl.h b/src/core/libraries/audio3d/audio3d_impl.h new file mode 100644 index 00000000000..4e6342b1b23 --- /dev/null +++ b/src/core/libraries/audio3d/audio3d_impl.h @@ -0,0 +1,16 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "audio3d.h" + +namespace Libraries::Audio3d { + +class Audio3d { +public: +private: + typedef unsigned int OrbisAudio3dPluginId; +}; + +} // namespace Libraries::Audio3d diff --git a/src/core/libraries/avplayer/avplayer.cpp b/src/core/libraries/avplayer/avplayer.cpp index 23e1e987a26..60d68c4f70a 100644 --- a/src/core/libraries/avplayer/avplayer.cpp +++ b/src/core/libraries/avplayer/avplayer.cpp @@ -166,8 +166,8 @@ s32 PS4_SYSV_ABI sceAvPlayerInitEx(const SceAvPlayerInitDataEx* p_data, bool PS4_SYSV_ABI sceAvPlayerIsActive(SceAvPlayerHandle handle) { LOG_TRACE(Lib_AvPlayer, "called"); if (handle == nullptr) { - LOG_TRACE(Lib_AvPlayer, "returning ORBIS_AVPLAYER_ERROR_INVALID_PARAMS"); - return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS; + LOG_TRACE(Lib_AvPlayer, "returning false"); + return false; } const auto res = handle->IsActive(); LOG_TRACE(Lib_AvPlayer, "returning {}", res); diff --git a/src/core/libraries/avplayer/avplayer.h b/src/core/libraries/avplayer/avplayer.h index 360f06b6540..98e93207031 100644 --- a/src/core/libraries/avplayer/avplayer.h +++ b/src/core/libraries/avplayer/avplayer.h @@ -161,7 +161,20 @@ struct SceAvPlayerFileReplacement { SceAvPlayerSizeFile size; }; -typedef void PS4_SYSV_ABI (*SceAvPlayerEventCallback)(void* p, s32 event, s32 src_id, void* data); +enum SceAvPlayerEvents { + SCE_AVPLAYER_STATE_STOP = 0x01, + SCE_AVPLAYER_STATE_READY = 0x02, + SCE_AVPLAYER_STATE_PLAY = 0x03, + SCE_AVPLAYER_STATE_PAUSE = 0x04, + SCE_AVPLAYER_STATE_BUFFERING = 0x05, + SCE_AVPLAYER_TIMED_TEXT_DELIVERY = 0x10, + SCE_AVPLAYER_WARNING_ID = 0x20, + SCE_AVPLAYER_ENCRYPTION = 0x30, + SCE_AVPLAYER_DRM_ERROR = 0x40 +}; + +typedef void PS4_SYSV_ABI (*SceAvPlayerEventCallback)(void* p, SceAvPlayerEvents event, s32 src_id, + void* data); struct SceAvPlayerEventReplacement { void* object_ptr; @@ -275,18 +288,6 @@ enum SceAvPlayerAvSyncMode { typedef int PS4_SYSV_ABI (*SceAvPlayerLogCallback)(void* p, const char* format, va_list args); -enum SceAvPlayerEvents { - SCE_AVPLAYER_STATE_STOP = 0x01, - SCE_AVPLAYER_STATE_READY = 0x02, - SCE_AVPLAYER_STATE_PLAY = 0x03, - SCE_AVPLAYER_STATE_PAUSE = 0x04, - SCE_AVPLAYER_STATE_BUFFERING = 0x05, - SCE_AVPLAYER_TIMED_TEXT_DELIVERY = 0x10, - SCE_AVPLAYER_WARNING_ID = 0x20, - SCE_AVPLAYER_ENCRYPTION = 0x30, - SCE_AVPLAYER_DRM_ERROR = 0x40 -}; - void RegisterlibSceAvPlayer(Core::Loader::SymbolsResolver* sym); } // namespace Libraries::AvPlayer diff --git a/src/core/libraries/avplayer/avplayer_impl.h b/src/core/libraries/avplayer/avplayer_impl.h index 09989d39919..b6ad940c9fa 100644 --- a/src/core/libraries/avplayer/avplayer_impl.h +++ b/src/core/libraries/avplayer/avplayer_impl.h @@ -40,8 +40,6 @@ class AvPlayer { bool SetLooping(bool is_looping); private: - using ScePthreadMutex = Kernel::ScePthreadMutex; - // Memory Replacement static void* PS4_SYSV_ABI Allocate(void* handle, u32 alignment, u32 size); static void PS4_SYSV_ABI Deallocate(void* handle, void* memory); diff --git a/src/core/libraries/avplayer/avplayer_source.cpp b/src/core/libraries/avplayer/avplayer_source.cpp index 99ba2e8b65e..fcae180e752 100644 --- a/src/core/libraries/avplayer/avplayer_source.cpp +++ b/src/core/libraries/avplayer/avplayer_source.cpp @@ -7,6 +7,8 @@ #include "common/alignment.h" #include "common/singleton.h" +#include "common/thread.h" + #include "core/file_sys/fs.h" #include "core/libraries/kernel/time_management.h" @@ -20,6 +22,17 @@ extern "C" { #include } +// The av_err2str macro in libavutil/error.h does not play nice with C++ +#ifdef av_err2str +#undef av_err2str +#include +av_always_inline std::string av_err2string(int errnum) { + char errbuf[AV_ERROR_MAX_STRING_SIZE]; + return av_make_error_string(errbuf, AV_ERROR_MAX_STRING_SIZE, errnum); +} +#define av_err2str(err) av_err2string(err).c_str() +#endif // av_err2str + namespace Libraries::AvPlayer { using namespace Kernel; @@ -421,6 +434,8 @@ void AvPlayerSource::ReleaseAVFormatContext(AVFormatContext* context) { void AvPlayerSource::DemuxerThread(std::stop_token stop) { using namespace std::chrono; + Common::SetCurrentThreadName("shadPS4:AvDemuxer"); + if (!m_audio_stream_index.has_value() && !m_video_stream_index.has_value()) { LOG_WARNING(Lib_AvPlayer, "Could not start DEMUXER thread. No streams enabled."); return; @@ -428,7 +443,8 @@ void AvPlayerSource::DemuxerThread(std::stop_token stop) { LOG_INFO(Lib_AvPlayer, "Demuxer Thread started"); while (!stop.stop_requested()) { - if (m_video_packets.Size() > 30 && m_audio_packets.Size() > 8) { + if (m_video_packets.Size() > 30 && + (!m_audio_stream_index.has_value() || m_audio_packets.Size() > 8)) { std::this_thread::sleep_for(milliseconds(5)); continue; } @@ -487,7 +503,7 @@ void AvPlayerSource::DemuxerThread(std::stop_token stop) { } m_state.OnEOF(); - LOG_INFO(Lib_AvPlayer, "Demuxer Thread exited normaly"); + LOG_INFO(Lib_AvPlayer, "Demuxer Thread exited normally"); } AvPlayerSource::AVFramePtr AvPlayerSource::ConvertVideoFrame(const AVFrame& frame) { @@ -586,6 +602,8 @@ Frame AvPlayerSource::PrepareVideoFrame(FrameBuffer buffer, const AVFrame& frame void AvPlayerSource::VideoDecoderThread(std::stop_token stop) { using namespace std::chrono; + Common::SetCurrentThreadName("shadPS4:AvVideoDecoder"); + LOG_INFO(Lib_AvPlayer, "Video Decoder Thread started"); while ((!m_is_eof || m_video_packets.Size() != 0) && !stop.stop_requested()) { if (!m_video_packets_cv.Wait(stop, @@ -641,7 +659,7 @@ void AvPlayerSource::VideoDecoderThread(std::stop_token stop) { } } - LOG_INFO(Lib_AvPlayer, "Video Decoder Thread exited normaly"); + LOG_INFO(Lib_AvPlayer, "Video Decoder Thread exited normally"); } AvPlayerSource::AVFramePtr AvPlayerSource::ConvertAudioFrame(const AVFrame& frame) { @@ -706,6 +724,8 @@ Frame AvPlayerSource::PrepareAudioFrame(FrameBuffer buffer, const AVFrame& frame void AvPlayerSource::AudioDecoderThread(std::stop_token stop) { using namespace std::chrono; + Common::SetCurrentThreadName("shadPS4:AvAudioDecoder"); + LOG_INFO(Lib_AvPlayer, "Audio Decoder Thread started"); while ((!m_is_eof || m_audio_packets.Size() != 0) && !stop.stop_requested()) { if (!m_audio_packets_cv.Wait(stop, @@ -761,7 +781,7 @@ void AvPlayerSource::AudioDecoderThread(std::stop_token stop) { } } - LOG_INFO(Lib_AvPlayer, "Audio Decoder Thread exited normaly"); + LOG_INFO(Lib_AvPlayer, "Audio Decoder Thread exited normally"); } bool AvPlayerSource::HasRunningThreads() const { diff --git a/src/core/libraries/avplayer/avplayer_state.cpp b/src/core/libraries/avplayer/avplayer_state.cpp index a512063f2b8..c4d666fce9e 100644 --- a/src/core/libraries/avplayer/avplayer_state.cpp +++ b/src/core/libraries/avplayer/avplayer_state.cpp @@ -5,8 +5,11 @@ #include "avplayer_source.h" #include "avplayer_state.h" +#include "common/singleton.h" +#include "common/thread.h" #include "core/libraries/error_codes.h" #include "core/libraries/kernel/time_management.h" +#include "core/linker.h" #include @@ -14,8 +17,8 @@ namespace Libraries::AvPlayer { using namespace Kernel; -void PS4_SYSV_ABI AvPlayerState::AutoPlayEventCallback(void* opaque, s32 event_id, s32 source_id, - void* event_data) { +void PS4_SYSV_ABI AvPlayerState::AutoPlayEventCallback(void* opaque, SceAvPlayerEvents event_id, + s32 source_id, void* event_data) { auto const self = reinterpret_cast(opaque); if (event_id == SCE_AVPLAYER_STATE_READY) { @@ -88,7 +91,8 @@ void PS4_SYSV_ABI AvPlayerState::AutoPlayEventCallback(void* opaque, s32 event_i const auto callback = self->m_event_replacement.event_callback; const auto ptr = self->m_event_replacement.object_ptr; if (callback != nullptr) { - callback(ptr, event_id, 0, event_data); + auto* linker = Common::Singleton::Instance(); + linker->ExecuteGuest(callback, ptr, event_id, 0, event_data); } } @@ -178,6 +182,7 @@ bool AvPlayerState::Start() { void AvPlayerState::AvControllerThread(std::stop_token stop) { using std::chrono::milliseconds; + Common::SetCurrentThreadName("shadPS4:AvController"); while (!stop.stop_requested()) { if (m_event_queue.Size() != 0) { @@ -362,7 +367,8 @@ void AvPlayerState::EmitEvent(SceAvPlayerEvents event_id, void* event_data) { const auto callback = m_init_data.event_replacement.event_callback; if (callback) { const auto ptr = m_init_data.event_replacement.object_ptr; - callback(ptr, event_id, 0, event_data); + auto* linker = Common::Singleton::Instance(); + linker->ExecuteGuest(callback, ptr, event_id, 0, event_data); } } diff --git a/src/core/libraries/avplayer/avplayer_state.h b/src/core/libraries/avplayer/avplayer_state.h index a5a3bd689c6..f50d1bc1fba 100644 --- a/src/core/libraries/avplayer/avplayer_state.h +++ b/src/core/libraries/avplayer/avplayer_state.h @@ -38,12 +38,9 @@ class AvPlayerState : public AvPlayerStateCallback { bool SetLooping(bool is_looping); private: - using ScePthreadMutex = Kernel::ScePthreadMutex; - using ScePthread = Kernel::ScePthread; - // Event Replacement - static void PS4_SYSV_ABI AutoPlayEventCallback(void* handle, s32 event_id, s32 source_id, - void* event_data); + static void PS4_SYSV_ABI AutoPlayEventCallback(void* handle, SceAvPlayerEvents event_id, + s32 source_id, void* event_data); void OnWarning(u32 id) override; void OnError() override; diff --git a/src/core/libraries/error_codes.h b/src/core/libraries/error_codes.h index 094ea66031f..b9896b6c311 100644 --- a/src/core/libraries/error_codes.h +++ b/src/core/libraries/error_codes.h @@ -440,11 +440,47 @@ constexpr int ORBIS_USER_SERVICE_ERROR_BUFFER_TOO_SHORT = 0x8096000A; constexpr int ORBIS_SYSTEM_SERVICE_ERROR_PARAMETER = 0x80A10003; // NpTrophy library +constexpr int ORBIS_NP_TROPHY_ERROR_UNKNOWN = 0x80551600; +constexpr int ORBIS_NP_TROPHY_ERROR_NOT_INITIALIZED = 0x80551601; +constexpr int ORBIS_NP_TROPHY_ERROR_ALREADY_INITIALIZED = 0x80551602; +constexpr int ORBIS_NP_TROPHY_ERROR_OUT_OF_MEMORY = 0x80551603; constexpr int ORBIS_NP_TROPHY_ERROR_INVALID_ARGUMENT = 0x80551604; +constexpr int ORBIS_NP_TROPHY_ERROR_INSUFFICIENT_BUFFER = 0x80551605; +constexpr int ORBIS_NP_TROPHY_ERROR_EXCEEDS_MAX = 0x80551606; +constexpr int ORBIS_NP_TROPHY_ERROR_ABORT = 0x80551607; constexpr int ORBIS_NP_TROPHY_ERROR_INVALID_HANDLE = 0x80551608; -constexpr int ORBIS_NP_TROPHY_ERROR_HANDLE_EXCEEDS_MAX = 0x80551624; +constexpr int ORBIS_NP_TROPHY_ERROR_INVALID_CONTEXT = 0x80551609; +constexpr int ORBIS_NP_TROPHY_ERROR_INVALID_TROPHY_ID = 0x8055160A; +constexpr int ORBIS_NP_TROPHY_ERROR_INVALID_GROUP_ID = 0x8055160B; +constexpr int ORBIS_NP_TROPHY_ERROR_TROPHY_ALREADY_UNLOCKED = 0x8055160C; +constexpr int ORBIS_NP_TROPHY_ERROR_PLATINUM_CANNOT_UNLOCK = 0x8055160D; +constexpr int ORBIS_NP_TROPHY_ERROR_ACCOUNTID_NOT_MATCH = 0x8055160E; +constexpr int ORBIS_NP_TROPHY_ERROR_NOT_REGISTERED = 0x8055160F; +constexpr int ORBIS_NP_TROPHY_ERROR_ALREADY_REGISTERED = 0x80551610; +constexpr int ORBIS_NP_TROPHY_ERROR_BROKEN_DATA = 0x80551611; +constexpr int ORBIS_NP_TROPHY_ERROR_INSUFFICIENT_SPACE = 0x80551612; constexpr int ORBIS_NP_TROPHY_ERROR_CONTEXT_ALREADY_EXISTS = 0x80551613; -constexpr int ORBIS_NP_TROPHY_ERROR_CONTEXT_EXCEEDS_MAX = 0x80551622; +constexpr int ORBIS_NP_TROPHY_ERROR_ICON_FILE_NOT_FOUND = 0x80551614; +constexpr int ORBIS_NP_TROPHY_ERROR_INVALID_TRP_FILE_FORMAT = 0x80551616; +constexpr int ORBIS_NP_TROPHY_ERROR_UNSUPPORTED_TRP_FILE = 0x80551617; +constexpr int ORBIS_NP_TROPHY_ERROR_INVALID_TROPHY_CONF_FORMAT = 0x80551618; +constexpr int ORBIS_NP_TROPHY_ERROR_UNSUPPORTED_TROPHY_CONF = 0x80551619; +constexpr int ORBIS_NP_TROPHY_ERROR_TROPHY_NOT_UNLOCKED = 0x8055161A; +constexpr int ORBIS_NP_TROPHY_ERROR_USER_NOT_FOUND = 0x8055161C; +constexpr int ORBIS_NP_TROPHY_ERROR_USER_NOT_LOGGED_IN = 0x8055161D; +constexpr int ORBIS_NP_TROPHY_ERROR_CONTEXT_USER_LOGOUT = 0x8055161E; +constexpr int ORBIS_NP_TROPHY_ERROR_USE_TRP_FOR_DEVELOPMENT = 0x8055161F; +constexpr int ORBIS_NP_TROPHY_ERROR_INVALID_NP_SERVICE_LABEL = 0x80551621; +constexpr int ORBIS_NP_TROPHY_ERROR_NOT_SUPPORTED = 0x80551622; +constexpr int ORBIS_NP_TROPHY_ERROR_CONTEXT_EXCEEDS_MAX = 0x80551623; +constexpr int ORBIS_NP_TROPHY_ERROR_HANDLE_EXCEEDS_MAX = 0x80551624; +constexpr int ORBIS_NP_TROPHY_ERROR_INVALID_USER_ID = 0x80551625; +constexpr int ORBIS_NP_TROPHY_ERROR_TITLE_CONF_NOT_INSTALLED = 0x80551626; +constexpr int ORBIS_NP_TROPHY_ERROR_BROKEN_TITLE_CONF = 0x80551627; +constexpr int ORBIS_NP_TROPHY_ERROR_INCONSISTENT_TITLE_CONF = 0x80551628; +constexpr int ORBIS_NP_TROPHY_ERROR_TITLE_BACKGROUND = 0x80551629; +constexpr int ORBIS_NP_TROPHY_ERROR_SCREENSHOT_DISABLED = 0x8055162B; +constexpr int ORBIS_NP_TROPHY_ERROR_SCREENSHOT_DISPLAY_BUFFER_NOT_IN_USE = 0x8055162D; // AvPlayer library constexpr int ORBIS_AVPLAYER_ERROR_INVALID_PARAMS = 0x806A0001; diff --git a/src/core/libraries/gnmdriver/gnmdriver.cpp b/src/core/libraries/gnmdriver/gnmdriver.cpp index 6b8b070b845..fd4c261e96a 100644 --- a/src/core/libraries/gnmdriver/gnmdriver.cpp +++ b/src/core/libraries/gnmdriver/gnmdriver.cpp @@ -499,7 +499,7 @@ int PS4_SYSV_ABI sceGnmDestroyWorkloadStream() { } void PS4_SYSV_ABI sceGnmDingDong(u32 gnm_vqid, u32 next_offs_dw) { - LOG_INFO(Lib_GnmDriver, "vqid {}, offset_dw {}", gnm_vqid, next_offs_dw); + LOG_DEBUG(Lib_GnmDriver, "vqid {}, offset_dw {}", gnm_vqid, next_offs_dw); if (gnm_vqid == 0) { return; @@ -650,12 +650,12 @@ s32 PS4_SYSV_ABI sceGnmDrawIndexAuto(u32* cmdbuf, u32 size, u32 index_count, u32 } s32 PS4_SYSV_ABI sceGnmDrawIndexIndirect(u32* cmdbuf, u32 size, u32 data_offset, u32 shader_stage, - u32 vertex_sgpr_offset, u32 instance_vgpr_offset, + u32 vertex_sgpr_offset, u32 instance_sgpr_offset, u32 flags) { LOG_TRACE(Lib_GnmDriver, "called"); if (cmdbuf && (size == 9) && (shader_stage < ShaderStages::Max) && - (vertex_sgpr_offset < 0x10u) && (instance_vgpr_offset < 0x10u)) { + (vertex_sgpr_offset < 0x10u) && (instance_sgpr_offset < 0x10u)) { const auto predicate = flags & 1 ? PM4Predicate::PredEnable : PM4Predicate::PredDisable; cmdbuf = WriteHeader( @@ -665,7 +665,7 @@ s32 PS4_SYSV_ABI sceGnmDrawIndexIndirect(u32* cmdbuf, u32 size, u32 data_offset, cmdbuf[0] = data_offset; cmdbuf[1] = vertex_sgpr_offset == 0 ? 0 : (vertex_sgpr_offset & 0xffffu) + sgpr_offset; - cmdbuf[2] = instance_vgpr_offset == 0 ? 0 : (instance_vgpr_offset & 0xffffu) + sgpr_offset; + cmdbuf[2] = instance_sgpr_offset == 0 ? 0 : (instance_sgpr_offset & 0xffffu) + sgpr_offset; cmdbuf[3] = 0; cmdbuf += 4; @@ -707,11 +707,11 @@ s32 PS4_SYSV_ABI sceGnmDrawIndexOffset(u32* cmdbuf, u32 size, u32 index_offset, } s32 PS4_SYSV_ABI sceGnmDrawIndirect(u32* cmdbuf, u32 size, u32 data_offset, u32 shader_stage, - u32 vertex_sgpr_offset, u32 instance_vgpr_offset, u32 flags) { + u32 vertex_sgpr_offset, u32 instance_sgpr_offset, u32 flags) { LOG_TRACE(Lib_GnmDriver, "called"); if (cmdbuf && (size == 9) && (shader_stage < ShaderStages::Max) && - (vertex_sgpr_offset < 0x10u) && (instance_vgpr_offset < 0x10u)) { + (vertex_sgpr_offset < 0x10u) && (instance_sgpr_offset < 0x10u)) { const auto predicate = flags & 1 ? PM4Predicate::PredEnable : PM4Predicate::PredDisable; cmdbuf = WriteHeader(cmdbuf, 4, PM4ShaderType::ShaderGraphics, @@ -721,7 +721,7 @@ s32 PS4_SYSV_ABI sceGnmDrawIndirect(u32* cmdbuf, u32 size, u32 data_offset, u32 cmdbuf[0] = data_offset; cmdbuf[1] = vertex_sgpr_offset == 0 ? 0 : (vertex_sgpr_offset & 0xffffu) + sgpr_offset; - cmdbuf[2] = instance_vgpr_offset == 0 ? 0 : (instance_vgpr_offset & 0xffffu) + sgpr_offset; + cmdbuf[2] = instance_sgpr_offset == 0 ? 0 : (instance_sgpr_offset & 0xffffu) + sgpr_offset; cmdbuf[3] = 2; // auto index cmdbuf += 4; @@ -1272,8 +1272,12 @@ int PS4_SYSV_ABI sceGnmRequestMipStatsReportAndReset() { return ORBIS_OK; } -int PS4_SYSV_ABI sceGnmResetVgtControl() { - LOG_ERROR(Lib_GnmDriver, "(STUBBED) called"); +s32 PS4_SYSV_ABI sceGnmResetVgtControl(u32* cmdbuf, u32 size) { + LOG_TRACE(Lib_GnmDriver, "called"); + if (cmdbuf == nullptr || size != 3) { + return -1; + } + PM4CmdSetData::SetContextReg(cmdbuf, 0x2aau, 0xffu); // IA_MULTI_VGT_PARAM return ORBIS_OK; } @@ -2054,7 +2058,7 @@ s32 PS4_SYSV_ABI sceGnmSubmitAndFlipCommandBuffers(u32 count, u32* dcb_gpu_addrs u32* dcb_sizes_in_bytes, u32* ccb_gpu_addrs[], u32* ccb_sizes_in_bytes, u32 vo_handle, u32 buf_idx, u32 flip_mode, u32 flip_arg) { - LOG_INFO(Lib_GnmDriver, "called [buf = {}]", buf_idx); + LOG_DEBUG(Lib_GnmDriver, "called [buf = {}]", buf_idx); auto* cmdbuf = dcb_gpu_addrs[count - 1]; const auto size_dw = dcb_sizes_in_bytes[count - 1] / 4; @@ -2078,7 +2082,7 @@ int PS4_SYSV_ABI sceGnmSubmitAndFlipCommandBuffersForWorkload() { s32 PS4_SYSV_ABI sceGnmSubmitCommandBuffers(u32 count, const u32* dcb_gpu_addrs[], u32* dcb_sizes_in_bytes, const u32* ccb_gpu_addrs[], u32* ccb_sizes_in_bytes) { - LOG_INFO(Lib_GnmDriver, "called"); + LOG_DEBUG(Lib_GnmDriver, "called"); if (!dcb_gpu_addrs || !dcb_sizes_in_bytes) { LOG_ERROR(Lib_GnmDriver, "dcbGpuAddrs and dcbSizesInBytes must not be NULL"); @@ -2154,7 +2158,8 @@ int PS4_SYSV_ABI sceGnmSubmitCommandBuffersForWorkload() { } int PS4_SYSV_ABI sceGnmSubmitDone() { - LOG_INFO(Lib_GnmDriver, "called"); + LOG_DEBUG(Lib_GnmDriver, "called"); + WaitGpuIdle(); if (!liverpool->IsGpuIdle()) { submission_lock = true; } @@ -2346,9 +2351,9 @@ s32 PS4_SYSV_ABI sceGnmUpdateVsShader(u32* cmdbuf, u32 size, const u32* vs_regs, return ORBIS_OK; } -int PS4_SYSV_ABI sceGnmValidateCommandBuffers() { - LOG_ERROR(Lib_GnmDriver, "(STUBBED) called"); - return ORBIS_OK; +s32 PS4_SYSV_ABI sceGnmValidateCommandBuffers() { + LOG_TRACE(Lib_GnmDriver, "called"); + return ORBIS_GNM_ERROR_VALIDATION_NOT_ENABLED; // not available in retail FW; } int PS4_SYSV_ABI sceGnmValidateDisableDiagnostics() { @@ -2666,6 +2671,10 @@ void RegisterlibSceGnmDriver(Core::Loader::SymbolsResolver* sym) { sdk_version = 0; } + if (Config::copyGPUCmdBuffers()) { + liverpool->reserveCopyBufferSpace(); + } + Platform::IrqC::Instance()->Register(Platform::InterruptId::GpuIdle, ResetSubmissionLock, nullptr); diff --git a/src/core/libraries/gnmdriver/gnmdriver.h b/src/core/libraries/gnmdriver/gnmdriver.h index 84872297e9d..55a70cbf30a 100644 --- a/src/core/libraries/gnmdriver/gnmdriver.h +++ b/src/core/libraries/gnmdriver/gnmdriver.h @@ -45,7 +45,7 @@ s32 PS4_SYSV_ABI sceGnmDrawIndex(u32* cmdbuf, u32 size, u32 index_count, uintptr u32 flags, u32 type); s32 PS4_SYSV_ABI sceGnmDrawIndexAuto(u32* cmdbuf, u32 size, u32 index_count, u32 flags); s32 PS4_SYSV_ABI sceGnmDrawIndexIndirect(u32* cmdbuf, u32 size, u32 data_offset, u32 shader_stage, - u32 vertex_sgpr_offset, u32 instance_vgpr_offset, + u32 vertex_sgpr_offset, u32 instance_sgpr_offset, u32 flags); int PS4_SYSV_ABI sceGnmDrawIndexIndirectCountMulti(); int PS4_SYSV_ABI sceGnmDrawIndexIndirectMulti(); @@ -53,7 +53,7 @@ int PS4_SYSV_ABI sceGnmDrawIndexMultiInstanced(); s32 PS4_SYSV_ABI sceGnmDrawIndexOffset(u32* cmdbuf, u32 size, u32 index_offset, u32 index_count, u32 flags); s32 PS4_SYSV_ABI sceGnmDrawIndirect(u32* cmdbuf, u32 size, u32 data_offset, u32 shader_stage, - u32 vertex_sgpr_offset, u32 instance_vgpr_offset, u32 flags); + u32 vertex_sgpr_offset, u32 instance_sgpr_offset, u32 flags); int PS4_SYSV_ABI sceGnmDrawIndirectCountMulti(); int PS4_SYSV_ABI sceGnmDrawIndirectMulti(); u32 PS4_SYSV_ABI sceGnmDrawInitDefaultHardwareState(u32* cmdbuf, u32 size); @@ -134,7 +134,7 @@ s32 PS4_SYSV_ABI sceGnmRegisterResource(void* res_handle, void* owner_handle, co int PS4_SYSV_ABI sceGnmRequestFlipAndSubmitDone(); int PS4_SYSV_ABI sceGnmRequestFlipAndSubmitDoneForWorkload(); int PS4_SYSV_ABI sceGnmRequestMipStatsReportAndReset(); -int PS4_SYSV_ABI sceGnmResetVgtControl(); +s32 PS4_SYSV_ABI sceGnmResetVgtControl(u32* cmdbuf, u32 size); int PS4_SYSV_ABI sceGnmSdmaClose(); int PS4_SYSV_ABI sceGnmSdmaConstFill(); int PS4_SYSV_ABI sceGnmSdmaCopyLinear(); @@ -223,7 +223,7 @@ s32 PS4_SYSV_ABI sceGnmUpdatePsShader(u32* cmdbuf, u32 size, const u32* ps_regs) s32 PS4_SYSV_ABI sceGnmUpdatePsShader350(u32* cmdbuf, u32 size, const u32* ps_regs); s32 PS4_SYSV_ABI sceGnmUpdateVsShader(u32* cmdbuf, u32 size, const u32* vs_regs, u32 shader_modifier); -int PS4_SYSV_ABI sceGnmValidateCommandBuffers(); +s32 PS4_SYSV_ABI sceGnmValidateCommandBuffers(); int PS4_SYSV_ABI sceGnmValidateDisableDiagnostics(); int PS4_SYSV_ABI sceGnmValidateDisableDiagnostics2(); int PS4_SYSV_ABI sceGnmValidateDispatchCommandBuffers(); diff --git a/src/core/libraries/kernel/cpu_management.cpp b/src/core/libraries/kernel/cpu_management.cpp index 93dc60bd02f..3bf609dfe6e 100644 --- a/src/core/libraries/kernel/cpu_management.cpp +++ b/src/core/libraries/kernel/cpu_management.cpp @@ -8,7 +8,7 @@ namespace Libraries::Kernel { int PS4_SYSV_ABI sceKernelIsNeoMode() { - LOG_INFO(Kernel_Sce, "called"); + LOG_DEBUG(Kernel_Sce, "called"); return Config::isNeoMode(); } diff --git a/src/core/libraries/kernel/event_flag/event_flag.cpp b/src/core/libraries/kernel/event_flag/event_flag.cpp index 0fd0c3bb75d..c85aa0d9069 100644 --- a/src/core/libraries/kernel/event_flag/event_flag.cpp +++ b/src/core/libraries/kernel/event_flag/event_flag.cpp @@ -78,7 +78,7 @@ int PS4_SYSV_ABI sceKernelCloseEventFlag() { return ORBIS_OK; } int PS4_SYSV_ABI sceKernelClearEventFlag(OrbisKernelEventFlag ef, u64 bitPattern) { - LOG_INFO(Kernel_Event, "called"); + LOG_DEBUG(Kernel_Event, "called"); ef->Clear(bitPattern); return ORBIS_OK; } @@ -97,7 +97,7 @@ int PS4_SYSV_ABI sceKernelSetEventFlag(OrbisKernelEventFlag ef, u64 bitPattern) } int PS4_SYSV_ABI sceKernelPollEventFlag(OrbisKernelEventFlag ef, u64 bitPattern, u32 waitMode, u64* pResultPat) { - LOG_INFO(Kernel_Event, "called bitPattern = {:#x} waitMode = {:#x}", bitPattern, waitMode); + LOG_DEBUG(Kernel_Event, "called bitPattern = {:#x} waitMode = {:#x}", bitPattern, waitMode); if (ef == nullptr) { return ORBIS_KERNEL_ERROR_ESRCH; @@ -137,7 +137,7 @@ int PS4_SYSV_ABI sceKernelPollEventFlag(OrbisKernelEventFlag ef, u64 bitPattern, auto result = ef->Poll(bitPattern, wait, clear, pResultPat); - if (result != ORBIS_OK) { + if (result != ORBIS_OK && result != ORBIS_KERNEL_ERROR_EBUSY) { LOG_ERROR(Kernel_Event, "returned {}", result); } @@ -145,7 +145,7 @@ int PS4_SYSV_ABI sceKernelPollEventFlag(OrbisKernelEventFlag ef, u64 bitPattern, } int PS4_SYSV_ABI sceKernelWaitEventFlag(OrbisKernelEventFlag ef, u64 bitPattern, u32 waitMode, u64* pResultPat, OrbisKernelUseconds* pTimeout) { - LOG_INFO(Kernel_Event, "called bitPattern = {:#x} waitMode = {:#x}", bitPattern, waitMode); + LOG_DEBUG(Kernel_Event, "called bitPattern = {:#x} waitMode = {:#x}", bitPattern, waitMode); if (ef == nullptr) { return ORBIS_KERNEL_ERROR_ESRCH; } diff --git a/src/core/libraries/kernel/file_system.cpp b/src/core/libraries/kernel/file_system.cpp index f7f58df5925..45ebb4be895 100644 --- a/src/core/libraries/kernel/file_system.cpp +++ b/src/core/libraries/kernel/file_system.cpp @@ -89,6 +89,8 @@ int PS4_SYSV_ABI sceKernelOpen(const char* path, int flags, u16 mode) { } // RW, then scekernelWrite is called and savedata is written just fine now. e = file->f.Open(file->m_host_name, Common::FS::FileAccessMode::ReadWrite); + } else if (write) { + e = file->f.Open(file->m_host_name, Common::FS::FileAccessMode::Write); } else { UNREACHABLE(); } @@ -150,7 +152,6 @@ int PS4_SYSV_ABI posix_close(int d) { return -1; } return result; - return ORBIS_OK; } size_t PS4_SYSV_ABI sceKernelWrite(int d, const void* buf, size_t nbytes) { @@ -180,11 +181,16 @@ int PS4_SYSV_ABI sceKernelUnlink(const char* path) { auto* h = Common::Singleton::Instance(); auto* mnt = Common::Singleton::Instance(); - const auto host_path = mnt->GetHostPath(path); + bool ro = false; + const auto host_path = mnt->GetHostPath(path, &ro); if (host_path.empty()) { return SCE_KERNEL_ERROR_EACCES; } + if (ro) { + return SCE_KERNEL_ERROR_EROFS; + } + if (std::filesystem::is_directory(host_path)) { return SCE_KERNEL_ERROR_EPERM; } @@ -271,11 +277,18 @@ int PS4_SYSV_ABI sceKernelMkdir(const char* path, u16 mode) { return SCE_KERNEL_ERROR_EINVAL; } auto* mnt = Common::Singleton::Instance(); - const auto dir_name = mnt->GetHostPath(path); + + bool ro = false; + const auto dir_name = mnt->GetHostPath(path, &ro); + if (std::filesystem::exists(dir_name)) { return SCE_KERNEL_ERROR_EEXIST; } + if (ro) { + return SCE_KERNEL_ERROR_EROFS; + } + // CUSA02456: path = /aotl after sceSaveDataMount(mode = 1) if (dir_name.empty() || !std::filesystem::create_directory(dir_name)) { return SCE_KERNEL_ERROR_EIO; @@ -300,7 +313,8 @@ int PS4_SYSV_ABI posix_mkdir(const char* path, u16 mode) { int PS4_SYSV_ABI sceKernelStat(const char* path, OrbisKernelStat* sb) { LOG_INFO(Kernel_Fs, "(PARTIAL) path = {}", path); auto* mnt = Common::Singleton::Instance(); - const auto path_name = mnt->GetHostPath(path); + bool ro = false; + const auto path_name = mnt->GetHostPath(path, &ro); std::memset(sb, 0, sizeof(OrbisKernelStat)); const bool is_dir = std::filesystem::is_directory(path_name); const bool is_file = std::filesystem::is_regular_file(path_name); @@ -320,6 +334,10 @@ int PS4_SYSV_ABI sceKernelStat(const char* path, OrbisKernelStat* sb) { sb->st_blocks = (sb->st_size + 511) / 512; // TODO incomplete } + if (ro) { + sb->st_mode &= ~0000555u; + } + return ORBIS_OK; } @@ -367,8 +385,17 @@ s64 PS4_SYSV_ABI sceKernelPread(int d, void* buf, size_t nbytes, s64 offset) { int PS4_SYSV_ABI sceKernelFStat(int fd, OrbisKernelStat* sb) { LOG_INFO(Kernel_Fs, "(PARTIAL) fd = {}", fd); + if (fd < 3) { + return ORBIS_KERNEL_ERROR_EPERM; + } + if (sb == nullptr) { + return ORBIS_KERNEL_ERROR_EFAULT; + } auto* h = Common::Singleton::Instance(); auto* file = h->GetFile(fd); + if (file == nullptr) { + return ORBIS_KERNEL_ERROR_EBADF; + } std::memset(sb, 0, sizeof(OrbisKernelStat)); if (file->is_directory) { @@ -421,15 +448,24 @@ int PS4_SYSV_ABI sceKernelFtruncate(int fd, s64 length) { } static int GetDents(int fd, char* buf, int nbytes, s64* basep) { - // TODO error codes - ASSERT(buf != nullptr); + if (fd < 3) { + return ORBIS_KERNEL_ERROR_EBADF; + } + + if (buf == nullptr) { + return ORBIS_KERNEL_ERROR_EFAULT; + } auto* h = Common::Singleton::Instance(); auto* file = h->GetFile(fd); - + if (file == nullptr) { + return ORBIS_KERNEL_ERROR_EBADF; + } if (file->dirents_index == file->dirents.size()) { return ORBIS_OK; } - + if (!file->is_directory || nbytes < 512 || file->dirents_index > file->dirents.size()) { + return ORBIS_KERNEL_ERROR_EINVAL; + } const auto& entry = file->dirents.at(file->dirents_index++); auto str = entry.name; auto str_size = str.size() - 1; @@ -483,11 +519,18 @@ s64 PS4_SYSV_ABI sceKernelPwrite(int d, void* buf, size_t nbytes, s64 offset) { s32 PS4_SYSV_ABI sceKernelRename(const char* from, const char* to) { auto* mnt = Common::Singleton::Instance(); - const auto src_path = mnt->GetHostPath(from); + bool ro = false; + const auto src_path = mnt->GetHostPath(from, &ro); if (!std::filesystem::exists(src_path)) { return ORBIS_KERNEL_ERROR_ENOENT; } - const auto dst_path = mnt->GetHostPath(to); + if (ro) { + return SCE_KERNEL_ERROR_EROFS; + } + const auto dst_path = mnt->GetHostPath(to, &ro); + if (ro) { + return SCE_KERNEL_ERROR_EROFS; + } const bool src_is_dir = std::filesystem::is_directory(src_path); const bool dst_is_dir = std::filesystem::is_directory(dst_path); if (src_is_dir && !dst_is_dir) { diff --git a/src/core/libraries/kernel/libkernel.cpp b/src/core/libraries/kernel/libkernel.cpp index 2634e25c863..65d3dde14e9 100644 --- a/src/core/libraries/kernel/libkernel.cpp +++ b/src/core/libraries/kernel/libkernel.cpp @@ -8,6 +8,7 @@ #include "common/assert.h" #include "common/debug.h" +#include "common/elf_info.h" #include "common/logging/log.h" #include "common/polyfill_thread.h" #include "common/singleton.h" @@ -243,8 +244,7 @@ int PS4_SYSV_ABI sceKernelConvertUtcToLocaltime(time_t time, time_t* local_time, } int PS4_SYSV_ABI sceKernelGetCompiledSdkVersion(int* ver) { - auto* param_sfo = Common::Singleton::Instance(); - int version = param_sfo->GetInteger("SYSTEM_VER"); + int version = Common::ElfInfo::Instance().RawFirmwareVer(); LOG_INFO(Kernel, "returned system version = {:#x}", version); *ver = version; return (version > 0) ? ORBIS_OK : ORBIS_KERNEL_ERROR_EINVAL; @@ -425,6 +425,7 @@ void LibKernel_Register(Core::Loader::SymbolsResolver* sym) { LIB_FUNCTION("WFcfL2lzido", "libkernel", 1, "libkernel", 1, 1, sceKernelQueryMemoryProtection); LIB_FUNCTION("BHouLQzh0X0", "libkernel", 1, "libkernel", 1, 1, sceKernelDirectMemoryQuery); LIB_FUNCTION("MBuItvba6z8", "libkernel", 1, "libkernel", 1, 1, sceKernelReleaseDirectMemory); + LIB_FUNCTION("PGhQHd-dzv8", "libkernel", 1, "libkernel", 1, 1, sceKernelMmap); LIB_FUNCTION("cQke9UuBQOk", "libkernel", 1, "libkernel", 1, 1, sceKernelMunmap); LIB_FUNCTION("mL8NDH86iQI", "libkernel", 1, "libkernel", 1, 1, sceKernelMapNamedFlexibleMemory); LIB_FUNCTION("aNz11fnnzi4", "libkernel", 1, "libkernel", 1, 1, @@ -454,6 +455,8 @@ void LibKernel_Register(Core::Loader::SymbolsResolver* sym) { LIB_FUNCTION("F6e0kwo4cnk", "libkernel", 1, "libkernel", 1, 1, sceKernelTriggerUserEvent); LIB_FUNCTION("LJDwdSNTnDg", "libkernel", 1, "libkernel", 1, 1, sceKernelDeleteUserEvent); LIB_FUNCTION("mJ7aghmgvfc", "libkernel", 1, "libkernel", 1, 1, sceKernelGetEventId); + LIB_FUNCTION("9bfdLIyuwCY", "libkernel", 1, "libkernel", 1, 1, sceKernelMTypeProtect); + LIB_FUNCTION("vSMAm3cxYTY", "libkernel", 1, "libkernel", 1, 1, sceKernelMProtect); LIB_FUNCTION("23CPPI1tyBY", "libkernel", 1, "libkernel", 1, 1, sceKernelGetEventFilter); // misc diff --git a/src/core/libraries/kernel/libkernel.h b/src/core/libraries/kernel/libkernel.h index c28a548ff87..73705cdc2b0 100644 --- a/src/core/libraries/kernel/libkernel.h +++ b/src/core/libraries/kernel/libkernel.h @@ -33,6 +33,8 @@ typedef struct { } OrbisKernelUuid; int* PS4_SYSV_ABI __Error(); +int PS4_SYSV_ABI sceKernelConvertUtcToLocaltime(time_t time, time_t* local_time, + struct OrbisTimesec* st, unsigned long* dst_sec); int PS4_SYSV_ABI sceKernelGetCompiledSdkVersion(int* ver); void LibKernel_Register(Core::Loader::SymbolsResolver* sym); diff --git a/src/core/libraries/kernel/memory_management.cpp b/src/core/libraries/kernel/memory_management.cpp index a5288a65632..7853a77a41d 100644 --- a/src/core/libraries/kernel/memory_management.cpp +++ b/src/core/libraries/kernel/memory_management.cpp @@ -7,6 +7,7 @@ #include "common/assert.h" #include "common/logging/log.h" #include "common/singleton.h" +#include "core/address_space.h" #include "core/libraries/error_codes.h" #include "core/libraries/kernel/memory_management.h" #include "core/linker.h" @@ -218,6 +219,18 @@ int PS4_SYSV_ABI sceKernelQueryMemoryProtection(void* addr, void** start, void** return memory->QueryProtection(std::bit_cast(addr), start, end, prot); } +int PS4_SYSV_ABI sceKernelMProtect(const void* addr, size_t size, int prot) { + Core::MemoryManager* memory_manager = Core::Memory::Instance(); + Core::MemoryProt protection_flags = static_cast(prot); + return memory_manager->Protect(std::bit_cast(addr), size, protection_flags); +} + +int PS4_SYSV_ABI sceKernelMTypeProtect(const void* addr, size_t size, int mtype, int prot) { + Core::MemoryManager* memory_manager = Core::Memory::Instance(); + Core::MemoryProt protection_flags = static_cast(prot); + return memory_manager->Protect(std::bit_cast(addr), size, protection_flags); +} + int PS4_SYSV_ABI sceKernelDirectMemoryQuery(u64 offset, int flags, OrbisQueryInfo* query_info, size_t infoSize) { LOG_WARNING(Kernel_Vmm, "called offset = {:#x}, flags = {:#x}", offset, flags); @@ -282,6 +295,12 @@ s32 PS4_SYSV_ABI sceKernelBatchMap2(OrbisKernelBatchMapEntry* entries, int numEn entries[i].operation, entries[i].length, result); break; } + case MemoryOpTypes::ORBIS_KERNEL_MAP_OP_PROTECT: { + result = sceKernelMProtect(entries[i].start, entries[i].length, entries[i].protection); + LOG_INFO(Kernel_Vmm, "entry = {}, operation = {}, len = {:#x}, result = {}", i, + entries[i].operation, entries[i].length, result); + break; + } case MemoryOpTypes::ORBIS_KERNEL_MAP_OP_MAP_FLEXIBLE: { result = sceKernelMapNamedFlexibleMemory(&entries[i].start, entries[i].length, entries[i].protection, flags, ""); @@ -292,11 +311,10 @@ s32 PS4_SYSV_ABI sceKernelBatchMap2(OrbisKernelBatchMapEntry* entries, int numEn break; } case MemoryOpTypes::ORBIS_KERNEL_MAP_OP_TYPE_PROTECT: { - // By now, ignore protection and log it instead - LOG_WARNING(Kernel_Vmm, - "entry = {}, operation = {}, len = {:#x}, type = {} " - "is UNSUPPORTED and skipped", - i, entries[i].operation, entries[i].length, (u8)entries[i].type); + result = sceKernelMTypeProtect(entries[i].start, entries[i].length, entries[i].type, + entries[i].protection); + LOG_INFO(Kernel_Vmm, "entry = {}, operation = {}, len = {:#x}, result = {}", i, + entries[i].operation, entries[i].length, result); break; } default: { diff --git a/src/core/libraries/kernel/memory_management.h b/src/core/libraries/kernel/memory_management.h index 9a447fe8bb6..205b2274fe7 100644 --- a/src/core/libraries/kernel/memory_management.h +++ b/src/core/libraries/kernel/memory_management.h @@ -56,7 +56,7 @@ struct OrbisVirtualQueryInfo { BitField<1, 1, u32> is_direct; BitField<2, 1, u32> is_stack; BitField<3, 1, u32> is_pooled; - BitField<4, 1, u32> is_commited; + BitField<4, 1, u32> is_committed; }; std::array name; }; @@ -95,6 +95,10 @@ s32 PS4_SYSV_ABI sceKernelMapFlexibleMemory(void** addr_in_out, std::size_t len, int flags); int PS4_SYSV_ABI sceKernelQueryMemoryProtection(void* addr, void** start, void** end, u32* prot); +int PS4_SYSV_ABI sceKernelMProtect(const void* addr, size_t size, int prot); + +int PS4_SYSV_ABI sceKernelMTypeProtect(const void* addr, size_t size, int mtype, int prot); + int PS4_SYSV_ABI sceKernelDirectMemoryQuery(u64 offset, int flags, OrbisQueryInfo* query_info, size_t infoSize); s32 PS4_SYSV_ABI sceKernelAvailableFlexibleMemorySize(size_t* sizeOut); diff --git a/src/core/libraries/kernel/thread_management.cpp b/src/core/libraries/kernel/thread_management.cpp index 74ef392fa27..b7a8f1533f3 100644 --- a/src/core/libraries/kernel/thread_management.cpp +++ b/src/core/libraries/kernel/thread_management.cpp @@ -11,13 +11,13 @@ #include "common/logging/log.h" #include "common/singleton.h" #include "common/thread.h" -#include "core/cpu_patches.h" #include "core/libraries/error_codes.h" #include "core/libraries/kernel/libkernel.h" #include "core/libraries/kernel/thread_management.h" #include "core/libraries/kernel/threads/threads.h" #include "core/libraries/libs.h" #include "core/linker.h" +#include "core/tls.h" #ifdef _WIN64 #include #else @@ -52,11 +52,12 @@ void init_pthreads() { } void pthreadInitSelfMainThread() { + const char* name = "Main_Thread"; auto* pthread_pool = g_pthread_cxt->GetPthreadPool(); - g_pthread_self = pthread_pool->Create(); + g_pthread_self = pthread_pool->Create(name); scePthreadAttrInit(&g_pthread_self->attr); g_pthread_self->pth = pthread_self(); - g_pthread_self->name = "Main_Thread"; + g_pthread_self->name = name; } int PS4_SYSV_ABI scePthreadAttrInit(ScePthreadAttr* attr) { @@ -294,7 +295,7 @@ ScePthread PS4_SYSV_ABI scePthreadSelf() { int PS4_SYSV_ABI scePthreadAttrSetaffinity(ScePthreadAttr* pattr, const /*SceKernelCpumask*/ u64 mask) { - LOG_INFO(Kernel_Pthread, "called"); + LOG_DEBUG(Kernel_Pthread, "called"); if (pattr == nullptr || *pattr == nullptr) { return SCE_KERNEL_ERROR_EINVAL; @@ -386,7 +387,7 @@ int PS4_SYSV_ABI posix_pthread_attr_setstacksize(ScePthreadAttr* attr, size_t st } int PS4_SYSV_ABI scePthreadSetaffinity(ScePthread thread, const /*SceKernelCpumask*/ u64 mask) { - LOG_INFO(Kernel_Pthread, "called"); + LOG_DEBUG(Kernel_Pthread, "called"); if (thread == nullptr) { return SCE_KERNEL_ERROR_ESRCH; @@ -413,11 +414,6 @@ ScePthreadMutex* createMutex(ScePthreadMutex* addr) { if (addr == nullptr || *addr != nullptr) { return addr; } - static std::mutex mutex; - std::scoped_lock lk{mutex}; - if (*addr != nullptr) { - return addr; - } const VAddr vaddr = reinterpret_cast(addr); std::string name = fmt::format("mutex{:#x}", vaddr); scePthreadMutexInit(addr, nullptr, name.c_str()); @@ -583,8 +579,7 @@ int PS4_SYSV_ABI scePthreadMutexLock(ScePthreadMutex* mutex) { } int PS4_SYSV_ABI scePthreadMutexUnlock(ScePthreadMutex* mutex) { - mutex = createMutex(mutex); - if (mutex == nullptr) { + if (mutex == nullptr || *mutex == nullptr) { return SCE_KERNEL_ERROR_EINVAL; } @@ -657,7 +652,7 @@ int PS4_SYSV_ABI scePthreadCondInit(ScePthreadCond* cond, const ScePthreadCondat int result = pthread_cond_init(&(*cond)->cond, &(*attr)->cond_attr); if (name != nullptr) { - LOG_INFO(Kernel_Pthread, "name={}, result={}", (*cond)->name, result); + LOG_TRACE(Kernel_Pthread, "name={}, result={}", (*cond)->name, result); } switch (result) { @@ -987,7 +982,7 @@ static void cleanup_thread(void* arg) { destructor(value); } } - Core::CleanupThreadPatchStack(); + Core::SetTcbBase(nullptr); thread->is_almost_done = true; } @@ -995,13 +990,11 @@ static void* run_thread(void* arg) { auto* thread = static_cast(arg); Common::SetCurrentThreadName(thread->name.c_str()); auto* linker = Common::Singleton::Instance(); - Core::InitializeThreadPatchStack(); - linker->InitTlsForThread(false); void* ret = nullptr; g_pthread_self = thread; pthread_cleanup_push(cleanup_thread, thread); thread->is_started = true; - ret = thread->entry(thread->arg); + ret = linker->ExecuteGuest(thread->entry, thread->arg); pthread_cleanup_pop(1); return ret; } @@ -1018,7 +1011,7 @@ int PS4_SYSV_ABI scePthreadCreate(ScePthread* thread, const ScePthreadAttr* attr attr = g_pthread_cxt->GetDefaultAttr(); } - *thread = pthread_pool->Create(); + *thread = pthread_pool->Create(name); if ((*thread)->attr != nullptr) { scePthreadAttrDestroy(&(*thread)->attr); @@ -1060,11 +1053,11 @@ int PS4_SYSV_ABI scePthreadCreate(ScePthread* thread, const ScePthreadAttr* attr } } -ScePthread PThreadPool::Create() { +ScePthread PThreadPool::Create(const char* name) { std::scoped_lock lock{m_mutex}; for (auto* p : m_threads) { - if (p->is_free) { + if (p->is_free && name != nullptr && p->name == name) { p->is_free = false; return p; } @@ -1187,6 +1180,7 @@ int PS4_SYSV_ABI scePthreadCondattrDestroy(ScePthreadCondattr* attr) { int result = pthread_condattr_destroy(&(*attr)->cond_attr); LOG_DEBUG(Kernel_Pthread, "scePthreadCondattrDestroy: result = {} ", result); + delete *attr; switch (result) { case 0: @@ -1492,6 +1486,8 @@ int PS4_SYSV_ABI scePthreadOnce(int* once_control, void (*init_routine)(void)) { } [[noreturn]] void PS4_SYSV_ABI scePthreadExit(void* value_ptr) { + g_pthread_self->is_free = true; + pthread_exit(value_ptr); UNREACHABLE(); } diff --git a/src/core/libraries/kernel/thread_management.h b/src/core/libraries/kernel/thread_management.h index a2b2f6feaf0..7385b55ce0e 100644 --- a/src/core/libraries/kernel/thread_management.h +++ b/src/core/libraries/kernel/thread_management.h @@ -119,7 +119,7 @@ struct PthreadSemInternal { class PThreadPool { public: - ScePthread Create(); + ScePthread Create(const char* name); private: std::vector m_threads; diff --git a/src/core/libraries/kernel/threads/semaphore.cpp b/src/core/libraries/kernel/threads/semaphore.cpp index 63ca25338d6..ff536802366 100644 --- a/src/core/libraries/kernel/threads/semaphore.cpp +++ b/src/core/libraries/kernel/threads/semaphore.cpp @@ -32,12 +32,20 @@ class Semaphore { return ORBIS_KERNEL_ERROR_EBUSY; } + if (timeout && *timeout == 0) { + return SCE_KERNEL_ERROR_ETIMEDOUT; + } + // Create waiting thread object and add it into the list of waiters. WaitingThread waiter{need_count, is_fifo}; - AddWaiter(&waiter); + const auto it = AddWaiter(&waiter); // Perform the wait. - return waiter.Wait(lk, timeout); + const s32 result = waiter.Wait(lk, timeout); + if (result == SCE_KERNEL_ERROR_ETIMEDOUT) { + wait_list.erase(it); + } + return result; } bool Signal(s32 signal_count) { @@ -129,11 +137,13 @@ class Semaphore { } }; - void AddWaiter(WaitingThread* waiter) { + using WaitList = std::list; + + WaitList::iterator AddWaiter(WaitingThread* waiter) { // Insert at the end of the list for FIFO order. if (is_fifo) { wait_list.push_back(waiter); - return; + return --wait_list.end(); } // Find the first with priority less then us and insert right before it. auto it = wait_list.begin(); @@ -141,9 +151,10 @@ class Semaphore { it++; } wait_list.insert(it, waiter); + return it; } - std::list wait_list; + WaitList wait_list; std::string name; std::atomic token_count; std::mutex mutex; diff --git a/src/core/libraries/kernel/time_management.cpp b/src/core/libraries/kernel/time_management.cpp index 7a6ba4f6203..5e5e0ef27d3 100644 --- a/src/core/libraries/kernel/time_management.cpp +++ b/src/core/libraries/kernel/time_management.cpp @@ -147,13 +147,20 @@ int PS4_SYSV_ABI sceKernelGettimeofday(OrbisKernelTimeval* tp) { } #ifdef _WIN64 - auto now = std::chrono::system_clock::now(); - auto duration = now.time_since_epoch(); - auto seconds = std::chrono::duration_cast(duration); - auto microsecs = std::chrono::duration_cast(duration - seconds); + FILETIME filetime; + GetSystemTimeAsFileTime(&filetime); - tp->tv_sec = seconds.count(); - tp->tv_usec = microsecs.count(); + constexpr u64 UNIX_TIME_START = 0x295E9648864000; + constexpr u64 TICKS_PER_SECOND = 1000000; + + u64 ticks = filetime.dwHighDateTime; + ticks <<= 32; + ticks |= filetime.dwLowDateTime; + ticks /= 10; + ticks -= UNIX_TIME_START; + + tp->tv_sec = ticks / TICKS_PER_SECOND; + tp->tv_usec = ticks % TICKS_PER_SECOND; #else timeval tv; gettimeofday(&tv, nullptr); diff --git a/src/core/libraries/kernel/time_management.h b/src/core/libraries/kernel/time_management.h index a28f8c1333a..a28e6e55808 100644 --- a/src/core/libraries/kernel/time_management.h +++ b/src/core/libraries/kernel/time_management.h @@ -3,6 +3,8 @@ #pragma once +#include + #include "common/types.h" namespace Core::Loader { @@ -50,7 +52,10 @@ u64 PS4_SYSV_ABI sceKernelGetProcessTime(); u64 PS4_SYSV_ABI sceKernelGetProcessTimeCounter(); u64 PS4_SYSV_ABI sceKernelGetProcessTimeCounterFrequency(); u64 PS4_SYSV_ABI sceKernelReadTsc(); - +int PS4_SYSV_ABI sceKernelClockGettime(s32 clock_id, OrbisKernelTimespec* tp); +s32 PS4_SYSV_ABI sceKernelGettimezone(OrbisKernelTimezone* tz); +int PS4_SYSV_ABI sceKernelConvertLocaltimeToUtc(time_t param_1, int64_t param_2, time_t* seconds, + OrbisKernelTimezone* timezone, int* dst_seconds); void timeSymbolsRegister(Core::Loader::SymbolsResolver* sym); } // namespace Libraries::Kernel diff --git a/src/core/libraries/libs.cpp b/src/core/libraries/libs.cpp index e91a51e684a..5b6c17b10e5 100644 --- a/src/core/libraries/libs.cpp +++ b/src/core/libraries/libs.cpp @@ -6,6 +6,7 @@ #include "core/libraries/app_content/app_content.h" #include "core/libraries/audio/audioin.h" #include "core/libraries/audio/audioout.h" +#include "core/libraries/audio3d/audio3d.h" #include "core/libraries/avplayer/avplayer.h" #include "core/libraries/dialogs/error_dialog.h" #include "core/libraries/dialogs/ime_dialog.h" @@ -26,12 +27,12 @@ #include "core/libraries/playgo/playgo.h" #include "core/libraries/random/random.h" #include "core/libraries/rtc/rtc.h" +#include "core/libraries/save_data/dialog/savedatadialog.h" #include "core/libraries/save_data/savedata.h" #include "core/libraries/screenshot/screenshot.h" #include "core/libraries/system/commondialog.h" #include "core/libraries/system/msgdialog.h" #include "core/libraries/system/posix.h" -#include "core/libraries/system/savedatadialog.h" #include "core/libraries/system/sysmodule.h" #include "core/libraries/system/systemservice.h" #include "core/libraries/system/userservice.h" @@ -56,11 +57,11 @@ void InitHLELibs(Core::Loader::SymbolsResolver* sym) { Libraries::Net::RegisterlibSceNet(sym); Libraries::NetCtl::RegisterlibSceNetCtl(sym); Libraries::SaveData::RegisterlibSceSaveData(sym); + Libraries::SaveData::Dialog::RegisterlibSceSaveDataDialog(sym); Libraries::Ssl::RegisterlibSceSsl(sym); Libraries::SysModule::RegisterlibSceSysmodule(sym); Libraries::Posix::Registerlibsceposix(sym); Libraries::AudioIn::RegisterlibSceAudioIn(sym); - Libraries::SaveDataDialog::RegisterlibSceSaveDataDialog(sym); Libraries::NpManager::RegisterlibSceNpManager(sym); Libraries::NpScore::RegisterlibSceNpScore(sym); Libraries::NpTrophy::RegisterlibSceNpTrophy(sym); @@ -75,6 +76,7 @@ void InitHLELibs(Core::Loader::SymbolsResolver* sym) { Libraries::ErrorDialog::RegisterlibSceErrorDialog(sym); Libraries::ImeDialog::RegisterlibSceImeDialog(sym); Libraries::AvPlayer::RegisterlibSceAvPlayer(sym); + Libraries::Audio3d::RegisterlibSceAudio3d(sym); } } // namespace Libraries diff --git a/src/core/libraries/network/net.h b/src/core/libraries/network/net.h index 965b76809af..eababdb67ae 100644 --- a/src/core/libraries/network/net.h +++ b/src/core/libraries/network/net.h @@ -10,8 +10,12 @@ class SymbolsResolver; } // Define our own htonll and ntohll because its not available in some systems/platforms +#ifndef HTONLL #define HTONLL(x) (((uint64_t)htonl((x) & 0xFFFFFFFFUL)) << 32) | htonl((uint32_t)((x) >> 32)) +#endif +#ifndef NTOHLL #define NTOHLL(x) (((uint64_t)ntohl((x) & 0xFFFFFFFFUL)) << 32) | ntohl((uint32_t)((x) >> 32)) +#endif namespace Libraries::Net { diff --git a/src/core/libraries/network/net_ctl_codes.h b/src/core/libraries/network/net_ctl_codes.h new file mode 100644 index 00000000000..a38565c1ec7 --- /dev/null +++ b/src/core/libraries/network/net_ctl_codes.h @@ -0,0 +1,28 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +// error codes +constexpr int ORBIS_NET_CTL_ERROR_CALLBACK_MAX = 0x80412103; +constexpr int ORBIS_NET_CTL_ERROR_ID_NOT_FOUND = 0x80412104; +constexpr int ORBIS_NET_CTL_ERROR_INVALID_ID = 0x80412105; +constexpr int ORBIS_NET_CTL_ERROR_INVALID_ADDR = 0x80412107; +constexpr int ORBIS_NET_CTL_ERROR_NOT_CONNECTED = 0x80412108; +constexpr int ORBIS_NET_CTL_ERROR_NOT_AVAIL = 0x80412109; +constexpr int ORBIS_NET_CTL_ERROR_NETWORK_DISABLED = 0x8041210D; +constexpr int ORBIS_NET_CTL_ERROR_DISCONNECT_REQ = 0x8041210E; +constexpr int ORBIS_NET_CTL_ERROR_ETHERNET_PLUGOUT = 0x80412115; +constexpr int ORBIS_NET_CTL_ERROR_WIFI_DEAUTHED = 0x80412116; +constexpr int ORBIS_NET_CTL_ERROR_WIFI_BEACON_LOST = 0x80412117; + +// state codes +constexpr int ORBIS_NET_CTL_STATE_DISCONNECTED = 0; +constexpr int ORBIS_NET_CTL_STATE_CONNECTING = 1; +constexpr int ORBIS_NET_CTL_STATE_IPOBTAINING = 2; +constexpr int ORBIS_NET_CTL_STATE_IPOBTAINED = 3; + +// event type +constexpr int ORBIS_NET_CTL_EVENT_TYPE_DISCONNECTED = 1; +constexpr int ORBIS_SCE_NET_CTL_EVENT_TYPE_DISCONNECT_REQ_FINISHED = 2; +constexpr int ORBIS_NET_CTL_EVENT_TYPE_IPOBTAINED = 3; diff --git a/src/core/libraries/network/net_ctl_obj.cpp b/src/core/libraries/network/net_ctl_obj.cpp new file mode 100644 index 00000000000..8193c684e87 --- /dev/null +++ b/src/core/libraries/network/net_ctl_obj.cpp @@ -0,0 +1,80 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/singleton.h" +#include "core/linker.h" +#include "net_ctl_codes.h" +#include "net_ctl_obj.h" + +Libraries::NetCtl::NetCtlInternal::NetCtlInternal() { + callbacks.fill({nullptr, nullptr}); + nptoolCallbacks.fill({nullptr, nullptr}); +} + +Libraries::NetCtl::NetCtlInternal::~NetCtlInternal() {} + +s32 Libraries::NetCtl::NetCtlInternal::registerCallback(OrbisNetCtlCallback func, void* arg) { + std::unique_lock lock{m_mutex}; + + // Find the next available slot + int next_id = 0; + for (const auto& callback : callbacks) { + if (callback.func == nullptr) { + break; + } + next_id++; + } + + if (next_id == 8) { + return ORBIS_NET_CTL_ERROR_CALLBACK_MAX; + } + + callbacks[next_id].func = func; + callbacks[next_id].arg = arg; + return next_id; +} + +s32 Libraries::NetCtl::NetCtlInternal::registerNpToolkitCallback( + OrbisNetCtlCallbackForNpToolkit func, void* arg) { + + std::unique_lock lock{m_mutex}; + + // Find the next available slot + int next_id = 0; + for (const auto& callback : nptoolCallbacks) { + if (callback.func == nullptr) { + break; + } + next_id++; + } + + if (next_id == 8) { + return ORBIS_NET_CTL_ERROR_CALLBACK_MAX; + } + + nptoolCallbacks[next_id].func = func; + nptoolCallbacks[next_id].arg = arg; + return next_id; +} + +void Libraries::NetCtl::NetCtlInternal::checkCallback() { + std::unique_lock lock{m_mutex}; + auto* linker = Common::Singleton::Instance(); + for (auto& callback : callbacks) { + if (callback.func != nullptr) { + linker->ExecuteGuest(callback.func, ORBIS_NET_CTL_EVENT_TYPE_DISCONNECTED, + callback.arg); + } + } +} + +void Libraries::NetCtl::NetCtlInternal::checkNpToolkitCallback() { + std::unique_lock lock{m_mutex}; + auto* linker = Common::Singleton::Instance(); + for (auto& callback : nptoolCallbacks) { + if (callback.func != nullptr) { + linker->ExecuteGuest(callback.func, ORBIS_NET_CTL_EVENT_TYPE_DISCONNECTED, + callback.arg); + } + } +} diff --git a/src/core/libraries/network/net_ctl_obj.h b/src/core/libraries/network/net_ctl_obj.h new file mode 100644 index 00000000000..3178677f428 --- /dev/null +++ b/src/core/libraries/network/net_ctl_obj.h @@ -0,0 +1,40 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +#include "common/types.h" + +namespace Libraries::NetCtl { + +using OrbisNetCtlCallback = PS4_SYSV_ABI void (*)(int eventType, void* arg); +using OrbisNetCtlCallbackForNpToolkit = PS4_SYSV_ABI void (*)(int eventType, void* arg); + +struct NetCtlCallback { + OrbisNetCtlCallback func; + void* arg; +}; + +struct NetCtlCallbackForNpToolkit { + OrbisNetCtlCallbackForNpToolkit func; + void* arg; +}; + +class NetCtlInternal { +public: + NetCtlInternal(); + ~NetCtlInternal(); + s32 registerCallback(OrbisNetCtlCallback func, void* arg); + s32 registerNpToolkitCallback(OrbisNetCtlCallbackForNpToolkit func, void* arg); + void checkCallback(); + void checkNpToolkitCallback(); + +public: + std::array nptoolCallbacks; + std::array callbacks; + std::mutex m_mutex; +}; +} // namespace Libraries::NetCtl diff --git a/src/core/libraries/network/netctl.cpp b/src/core/libraries/network/netctl.cpp index a1c8e81c016..3ecdde7737c 100644 --- a/src/core/libraries/network/netctl.cpp +++ b/src/core/libraries/network/netctl.cpp @@ -2,8 +2,10 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include "common/logging/log.h" +#include "common/singleton.h" #include "core/libraries/error_codes.h" #include "core/libraries/libs.h" +#include "core/libraries/network/net_ctl_codes.h" #include "core/libraries/network/netctl.h" namespace Libraries::NetCtl { @@ -79,7 +81,8 @@ int PS4_SYSV_ABI sceNetCtlUnregisterCallbackV6() { } int PS4_SYSV_ABI sceNetCtlCheckCallback() { - LOG_TRACE(Lib_NetCtl, "(STUBBED) called"); + auto* netctl = Common::Singleton::Instance(); + netctl->checkCallback(); return ORBIS_OK; } @@ -143,7 +146,17 @@ int PS4_SYSV_ABI sceNetCtlGetIfStat() { return ORBIS_OK; } -int PS4_SYSV_ABI sceNetCtlGetInfo() { +int PS4_SYSV_ABI sceNetCtlGetInfo(int code, OrbisNetCtlInfo* info) { + switch (code) { + case ORBIS_NET_CTL_INFO_DEVICE: + info->device = 0; + break; + case ORBIS_NET_CTL_INFO_LINK: + info->link = 0; // disconnected + break; + default: + LOG_ERROR(Lib_NetCtl, "{} unsupported code", code); + } LOG_ERROR(Lib_NetCtl, "(STUBBED) called"); return ORBIS_OK; } @@ -173,8 +186,9 @@ int PS4_SYSV_ABI sceNetCtlGetNetEvConfigInfoIpcInt() { return ORBIS_OK; } -int PS4_SYSV_ABI sceNetCtlGetResult() { - LOG_ERROR(Lib_NetCtl, "(STUBBED) called"); +int PS4_SYSV_ABI sceNetCtlGetResult(int eventType, int* errorCode) { + LOG_ERROR(Lib_NetCtl, "(STUBBED) called eventType = {} ", eventType); + *errorCode = 0; return ORBIS_OK; } @@ -213,8 +227,8 @@ int PS4_SYSV_ABI sceNetCtlGetScanInfoForSsidScanIpcInt() { return ORBIS_OK; } -int PS4_SYSV_ABI sceNetCtlGetState() { - LOG_ERROR(Lib_NetCtl, "(STUBBED) called"); +int PS4_SYSV_ABI sceNetCtlGetState(int* state) { + *state = ORBIS_NET_CTL_STATE_DISCONNECTED; return ORBIS_OK; } @@ -248,8 +262,17 @@ int PS4_SYSV_ABI sceNetCtlIsBandwidthManagementEnabledIpcInt() { return ORBIS_OK; } -int PS4_SYSV_ABI sceNetCtlRegisterCallback() { - LOG_ERROR(Lib_NetCtl, "(STUBBED) called"); +int PS4_SYSV_ABI sceNetCtlRegisterCallback(OrbisNetCtlCallback func, void* arg, int* cid) { + if (!func || !cid) { + return ORBIS_NET_CTL_ERROR_INVALID_ADDR; + } + auto* netctl = Common::Singleton::Instance(); + s32 result = netctl->registerCallback(func, arg); + if (result < 0) { + return result; + } else { + *cid = result; + } return ORBIS_OK; } @@ -319,7 +342,8 @@ int PS4_SYSV_ABI Func_D8DCB6973537A3DC() { } int PS4_SYSV_ABI sceNetCtlCheckCallbackForNpToolkit() { - LOG_ERROR(Lib_NetCtl, "(STUBBED) called"); + auto* netctl = Common::Singleton::Instance(); + netctl->checkNpToolkitCallback(); return ORBIS_OK; } @@ -328,8 +352,18 @@ int PS4_SYSV_ABI sceNetCtlClearEventForNpToolkit() { return ORBIS_OK; } -int PS4_SYSV_ABI sceNetCtlRegisterCallbackForNpToolkit() { - LOG_ERROR(Lib_NetCtl, "(STUBBED) called"); +int PS4_SYSV_ABI sceNetCtlRegisterCallbackForNpToolkit(OrbisNetCtlCallbackForNpToolkit func, + void* arg, int* cid) { + if (!func || !cid) { + return ORBIS_NET_CTL_ERROR_INVALID_ADDR; + } + auto* netctl = Common::Singleton::Instance(); + s32 result = netctl->registerNpToolkitCallback(func, arg); + if (result < 0) { + return result; + } else { + *cid = result; + } return ORBIS_OK; } diff --git a/src/core/libraries/network/netctl.h b/src/core/libraries/network/netctl.h index 0d6adf9c122..850650f9746 100644 --- a/src/core/libraries/network/netctl.h +++ b/src/core/libraries/network/netctl.h @@ -4,6 +4,7 @@ #pragma once #include "common/types.h" +#include "net_ctl_obj.h" namespace Core::Loader { class SymbolsResolver; @@ -11,6 +12,45 @@ class SymbolsResolver; namespace Libraries::NetCtl { +constexpr int ORBIS_NET_ETHER_ADDR_LEN = 6; + +struct OrbisNetEtherAddr { + u8 data[ORBIS_NET_ETHER_ADDR_LEN]; +}; + +constexpr int ORBIS_NET_CTL_SSID_LEN = 32 + 1; +constexpr int ORBIS_NET_CTL_HOSTNAME_LEN = 255 + 1; +constexpr int ORBIS_NET_CTL_AUTH_NAME_LEN = 127 + 1; +constexpr int ORBIS_NET_CTL_IPV4_ADDR_STR_LEN = 16; + +typedef union OrbisNetCtlInfo { + u32 device; + OrbisNetEtherAddr ether_addr; + u32 mtu; + u32 link; + OrbisNetEtherAddr bssid; + char ssid[ORBIS_NET_CTL_SSID_LEN]; + u32 wifi_security; + u8 rssi_dbm; + uint8_t rssi_percentage; + u8 channel; + u32 ip_config; + char dhcp_hostname[ORBIS_NET_CTL_HOSTNAME_LEN]; + char pppoe_auth_name[ORBIS_NET_CTL_AUTH_NAME_LEN]; + char ip_address[ORBIS_NET_CTL_IPV4_ADDR_STR_LEN]; + char netmask[ORBIS_NET_CTL_IPV4_ADDR_STR_LEN]; + char default_route[ORBIS_NET_CTL_IPV4_ADDR_STR_LEN]; + char primary_dns[ORBIS_NET_CTL_IPV4_ADDR_STR_LEN]; + char secondary_dns[ORBIS_NET_CTL_IPV4_ADDR_STR_LEN]; + u32 http_proxy_config; + char http_proxy_server[ORBIS_NET_CTL_HOSTNAME_LEN]; + u16 http_proxy_port; +} SceNetCtlInfo; + +// GetInfo codes +constexpr int ORBIS_NET_CTL_INFO_DEVICE = 1; +constexpr int ORBIS_NET_CTL_INFO_LINK = 4; + int PS4_SYSV_ABI sceNetBweCheckCallbackIpcInt(); int PS4_SYSV_ABI sceNetBweClearEventIpcInt(); int PS4_SYSV_ABI sceNetBweFinishInternetConnectionTestIpcInt(); @@ -38,13 +78,13 @@ int PS4_SYSV_ABI sceNetCtlEnableBandwidthManagementIpcInt(); int PS4_SYSV_ABI sceNetCtlGetBandwidthInfoIpcInt(); int PS4_SYSV_ABI sceNetCtlGetEtherLinkMode(); int PS4_SYSV_ABI sceNetCtlGetIfStat(); -int PS4_SYSV_ABI sceNetCtlGetInfo(); +int PS4_SYSV_ABI sceNetCtlGetInfo(int code, OrbisNetCtlInfo* info); int PS4_SYSV_ABI sceNetCtlGetInfoIpcInt(); int PS4_SYSV_ABI sceNetCtlGetInfoV6IpcInt(); int PS4_SYSV_ABI sceNetCtlGetNatInfo(); int PS4_SYSV_ABI sceNetCtlGetNatInfoIpcInt(); int PS4_SYSV_ABI sceNetCtlGetNetEvConfigInfoIpcInt(); -int PS4_SYSV_ABI sceNetCtlGetResult(); +int PS4_SYSV_ABI sceNetCtlGetResult(int eventType, int* errorCode); int PS4_SYSV_ABI sceNetCtlGetResultIpcInt(); int PS4_SYSV_ABI sceNetCtlGetResultV6IpcInt(); int PS4_SYSV_ABI sceNetCtlGetScanInfoBssidForSsidListScanIpcInt(); @@ -52,14 +92,14 @@ int PS4_SYSV_ABI sceNetCtlGetScanInfoBssidIpcInt(); int PS4_SYSV_ABI sceNetCtlGetScanInfoByBssidIpcInt(); int PS4_SYSV_ABI sceNetCtlGetScanInfoForSsidListScanIpcInt(); int PS4_SYSV_ABI sceNetCtlGetScanInfoForSsidScanIpcInt(); -int PS4_SYSV_ABI sceNetCtlGetState(); +int PS4_SYSV_ABI sceNetCtlGetState(int* state); int PS4_SYSV_ABI sceNetCtlGetState2IpcInt(); int PS4_SYSV_ABI sceNetCtlGetStateIpcInt(); int PS4_SYSV_ABI sceNetCtlGetStateV6IpcInt(); int PS4_SYSV_ABI sceNetCtlGetWifiType(); int PS4_SYSV_ABI sceNetCtlInit(); int PS4_SYSV_ABI sceNetCtlIsBandwidthManagementEnabledIpcInt(); -int PS4_SYSV_ABI sceNetCtlRegisterCallback(); +int PS4_SYSV_ABI sceNetCtlRegisterCallback(OrbisNetCtlCallback func, void* arg, int* cid); int PS4_SYSV_ABI sceNetCtlRegisterCallbackForLibIpcInt(); int PS4_SYSV_ABI sceNetCtlRegisterCallbackIpcInt(); int PS4_SYSV_ABI sceNetCtlRegisterCallbackV6IpcInt(); @@ -75,7 +115,8 @@ int PS4_SYSV_ABI sceNetCtlUnsetStunWithPaddingFlagIpcInt(); int PS4_SYSV_ABI Func_D8DCB6973537A3DC(); int PS4_SYSV_ABI sceNetCtlCheckCallbackForNpToolkit(); int PS4_SYSV_ABI sceNetCtlClearEventForNpToolkit(); -int PS4_SYSV_ABI sceNetCtlRegisterCallbackForNpToolkit(); +int PS4_SYSV_ABI sceNetCtlRegisterCallbackForNpToolkit(OrbisNetCtlCallbackForNpToolkit func, + void* arg, int* cid); int PS4_SYSV_ABI sceNetCtlUnregisterCallbackForNpToolkit(); int PS4_SYSV_ABI sceNetCtlApCheckCallback(); int PS4_SYSV_ABI sceNetCtlApClearEvent(); diff --git a/src/core/libraries/np_manager/np_manager.cpp b/src/core/libraries/np_manager/np_manager.cpp index a761caa7394..e4c24cad34b 100644 --- a/src/core/libraries/np_manager/np_manager.cpp +++ b/src/core/libraries/np_manager/np_manager.cpp @@ -874,8 +874,16 @@ int PS4_SYSV_ABI sceNpCheckCallback() { return ORBIS_OK; } +struct NpStateCallbackForNpToolkit { + OrbisNpStateCallbackForNpToolkit func; + void* userdata; +}; + +NpStateCallbackForNpToolkit NpStateCbForNp; + int PS4_SYSV_ABI sceNpCheckCallbackForLib() { - LOG_ERROR(Lib_NpManager, "(STUBBED) called"); + // LOG_ERROR(Lib_NpManager, "(STUBBED) called"); + NpStateCbForNp.func(0, ORBIS_NP_STATE_SIGNED_OUT, NpStateCbForNp.userdata); return ORBIS_OK; } @@ -2507,9 +2515,12 @@ int PS4_SYSV_ABI Func_FF966E4351E564D6() { return ORBIS_OK; } -int PS4_SYSV_ABI sceNpRegisterStateCallbackForToolkit() { +int PS4_SYSV_ABI sceNpRegisterStateCallbackForToolkit(OrbisNpStateCallbackForNpToolkit callback, + void* userdata) { LOG_ERROR(Lib_NpManager, "(STUBBED) called"); - return ORBIS_OK; + NpStateCbForNp.func = callback; + NpStateCbForNp.userdata = userdata; + return 1; } int PS4_SYSV_ABI sceNpUnregisterStateCallbackForToolkit() { diff --git a/src/core/libraries/np_manager/np_manager.h b/src/core/libraries/np_manager/np_manager.h index 5955a40b4be..fa0ac82f334 100644 --- a/src/core/libraries/np_manager/np_manager.h +++ b/src/core/libraries/np_manager/np_manager.h @@ -11,6 +11,15 @@ class SymbolsResolver; namespace Libraries::NpManager { +enum OrbisNpState { + ORBIS_NP_STATE_UNKNOWN = 0, + ORBIS_NP_STATE_SIGNED_OUT, + ORBIS_NP_STATE_SIGNED_IN +}; + +using OrbisNpStateCallbackForNpToolkit = PS4_SYSV_ABI void (*)(s32 userId, OrbisNpState state, + void* userdata); + constexpr int ORBIS_NP_ONLINEID_MAX_LENGTH = 16; typedef int OrbisUserServiceUserId; @@ -526,7 +535,8 @@ int PS4_SYSV_ABI Func_F91B5B25CC9B30D9(); int PS4_SYSV_ABI Func_FC335B7102A585B3(); int PS4_SYSV_ABI Func_FCEAC354CA8B206E(); int PS4_SYSV_ABI Func_FF966E4351E564D6(); -int PS4_SYSV_ABI sceNpRegisterStateCallbackForToolkit(); +int PS4_SYSV_ABI sceNpRegisterStateCallbackForToolkit(OrbisNpStateCallbackForNpToolkit callback, + void* userdata); int PS4_SYSV_ABI sceNpUnregisterStateCallbackForToolkit(); void RegisterlibSceNpManager(Core::Loader::SymbolsResolver* sym); diff --git a/src/core/libraries/np_trophy/np_trophy.cpp b/src/core/libraries/np_trophy/np_trophy.cpp index ed25322b48e..c28e49dacf9 100644 --- a/src/core/libraries/np_trophy/np_trophy.cpp +++ b/src/core/libraries/np_trophy/np_trophy.cpp @@ -2,15 +2,22 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include +#include #include "common/logging/log.h" +#include "common/path_util.h" #include "common/slot_vector.h" #include "core/libraries/error_codes.h" #include "core/libraries/libs.h" #include "np_trophy.h" +#include "trophy_ui.h" namespace Libraries::NpTrophy { +static TrophyUI g_trophy_ui; + +std::string game_serial; + static constexpr auto MaxTrophyHandles = 4u; static constexpr auto MaxTrophyContexts = 8u; @@ -24,11 +31,70 @@ struct ContextKeyHash { struct TrophyContext { u32 context_id; }; -static Common::SlotVector trophy_handles{}; +static Common::SlotVector trophy_handles{}; static Common::SlotVector trophy_contexts{}; static std::unordered_map contexts_internal{}; -int PS4_SYSV_ABI sceNpTrophyAbortHandle() { +void ORBIS_NP_TROPHY_FLAG_ZERO(OrbisNpTrophyFlagArray* p) { + for (int i = 0; i < ORBIS_NP_TROPHY_NUM_MAX; i++) { + uint32_t array_index = i / 32; + uint32_t bit_position = i % 32; + + p->flag_bits[array_index] &= ~(1U << bit_position); + } +} + +void ORBIS_NP_TROPHY_FLAG_SET(int32_t trophyId, OrbisNpTrophyFlagArray* p) { + uint32_t array_index = trophyId / 32; + uint32_t bit_position = trophyId % 32; + + p->flag_bits[array_index] |= (1U << bit_position); +} + +void ORBIS_NP_TROPHY_FLAG_SET_ALL(OrbisNpTrophyFlagArray* p) { + for (int i = 0; i < ORBIS_NP_TROPHY_NUM_MAX; i++) { + uint32_t array_index = i / 32; + uint32_t bit_position = i % 32; + + p->flag_bits[array_index] |= (1U << bit_position); + } +} + +void ORBIS_NP_TROPHY_FLAG_CLR(int32_t trophyId, OrbisNpTrophyFlagArray* p) { + uint32_t array_index = trophyId / 32; + uint32_t bit_position = trophyId % 32; + + p->flag_bits[array_index] &= ~(1U << bit_position); +} + +bool ORBIS_NP_TROPHY_FLAG_ISSET(int32_t trophyId, OrbisNpTrophyFlagArray* p) { + uint32_t array_index = trophyId / 32; + uint32_t bit_position = trophyId % 32; + + return (p->flag_bits[array_index] & (1U << bit_position)) ? 1 : 0; +} + +OrbisNpTrophyGrade GetTrophyGradeFromChar(char trophyType) { + switch (trophyType) { + default: + return ORBIS_NP_TROPHY_GRADE_UNKNOWN; + break; + case 'B': + return ORBIS_NP_TROPHY_GRADE_BRONZE; + break; + case 'S': + return ORBIS_NP_TROPHY_GRADE_SILVER; + break; + case 'G': + return ORBIS_NP_TROPHY_GRADE_GOLD; + break; + case 'P': + return ORBIS_NP_TROPHY_GRADE_PLATINUM; + break; + } +} + +int PS4_SYSV_ABI sceNpTrophyAbortHandle(OrbisNpTrophyHandle handle) { LOG_ERROR(Lib_NpTrophy, "(STUBBED) called"); return ORBIS_OK; } @@ -83,8 +149,8 @@ int PS4_SYSV_ABI sceNpTrophyConfigHasGroupFeature() { return ORBIS_OK; } -s32 PS4_SYSV_ABI sceNpTrophyCreateContext(u32* context, u32 user_id, u32 service_label, - u64 options) { +s32 PS4_SYSV_ABI sceNpTrophyCreateContext(OrbisNpTrophyContext* context, int32_t user_id, + uint32_t service_label, uint64_t options) { ASSERT(options == 0ull); if (!context) { return ORBIS_NP_TROPHY_ERROR_INVALID_ARGUMENT; @@ -107,7 +173,7 @@ s32 PS4_SYSV_ABI sceNpTrophyCreateContext(u32* context, u32 user_id, u32 service return ORBIS_OK; } -s32 PS4_SYSV_ABI sceNpTrophyCreateHandle(u32* handle) { +s32 PS4_SYSV_ABI sceNpTrophyCreateHandle(OrbisNpTrophyHandle* handle) { if (!handle) { return ORBIS_NP_TROPHY_ERROR_INVALID_ARGUMENT; } @@ -122,55 +188,402 @@ s32 PS4_SYSV_ABI sceNpTrophyCreateHandle(u32* handle) { return ORBIS_OK; } -int PS4_SYSV_ABI sceNpTrophyDestroyContext() { - LOG_ERROR(Lib_NpTrophy, "(STUBBED) called"); +int PS4_SYSV_ABI sceNpTrophyDestroyContext(OrbisNpTrophyContext context) { + LOG_INFO(Lib_NpTrophy, "Destroyed Context {}", context); + + if (context == ORBIS_NP_TROPHY_INVALID_CONTEXT) + return ORBIS_NP_TROPHY_ERROR_INVALID_CONTEXT; + + Common::SlotId contextId; + contextId.index = context; + + ContextKey contextkey = trophy_contexts[contextId]; + trophy_contexts.erase(contextId); + contexts_internal.erase(contextkey); + return ORBIS_OK; } -s32 PS4_SYSV_ABI sceNpTrophyDestroyHandle(u32 handle) { - if (!trophy_handles.is_allocated({handle})) { +s32 PS4_SYSV_ABI sceNpTrophyDestroyHandle(OrbisNpTrophyHandle handle) { + if (handle == ORBIS_NP_TROPHY_INVALID_HANDLE) + return ORBIS_NP_TROPHY_ERROR_INVALID_HANDLE; + + if (!trophy_handles.is_allocated({static_cast(handle)})) { return ORBIS_NP_TROPHY_ERROR_INVALID_HANDLE; } - trophy_handles.erase({handle}); + trophy_handles.erase({static_cast(handle)}); LOG_INFO(Lib_NpTrophy, "Handle {} destroyed", handle); return ORBIS_OK; } -int PS4_SYSV_ABI sceNpTrophyGetGameIcon() { +int PS4_SYSV_ABI sceNpTrophyGetGameIcon(OrbisNpTrophyContext context, OrbisNpTrophyHandle handle, + void* buffer, size_t* size) { LOG_ERROR(Lib_NpTrophy, "(STUBBED) called"); return ORBIS_OK; } -int PS4_SYSV_ABI sceNpTrophyGetGameInfo() { - LOG_ERROR(Lib_NpTrophy, "(STUBBED) called"); - return ORBIS_OK; -} +int PS4_SYSV_ABI sceNpTrophyGetGameInfo(OrbisNpTrophyContext context, OrbisNpTrophyHandle handle, + OrbisNpTrophyGameDetails* details, + OrbisNpTrophyGameData* data) { + LOG_INFO(Lib_NpTrophy, "Getting Game Trophy"); -int PS4_SYSV_ABI sceNpTrophyGetGroupIcon() { - LOG_ERROR(Lib_NpTrophy, "(STUBBED) called"); - return ORBIS_OK; -} + if (context == ORBIS_NP_TROPHY_INVALID_CONTEXT) + return ORBIS_NP_TROPHY_ERROR_INVALID_CONTEXT; -int PS4_SYSV_ABI sceNpTrophyGetGroupInfo() { - LOG_ERROR(Lib_NpTrophy, "(STUBBED) called"); - return ORBIS_OK; -} + if (handle == ORBIS_NP_TROPHY_INVALID_HANDLE) + return ORBIS_NP_TROPHY_ERROR_INVALID_HANDLE; + + if (details == nullptr || data == nullptr) + return ORBIS_NP_TROPHY_ERROR_INVALID_ARGUMENT; + + if (details->size != 0x4A0 || data->size != 0x20) + return ORBIS_NP_TROPHY_ERROR_INVALID_ARGUMENT; + + const auto trophyDir = + Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / game_serial / "TrophyFiles"; + + pugi::xml_document doc; + pugi::xml_parse_result result = + doc.load_file((trophyDir.string() + "/trophy00/Xml/TROP.XML").c_str()); + + if (result) { + + uint32_t numGroups = 0; + uint32_t numTrophies = 0; + uint32_t numTrophiesByRarity[5]; + numTrophiesByRarity[1] = 0; + numTrophiesByRarity[2] = 0; + numTrophiesByRarity[3] = 0; + numTrophiesByRarity[4] = 0; + uint32_t unlockedTrophies = 0; + uint32_t unlockedTrophiesByRarity[5]; + unlockedTrophiesByRarity[1] = 0; + unlockedTrophiesByRarity[2] = 0; + unlockedTrophiesByRarity[3] = 0; + unlockedTrophiesByRarity[4] = 0; + + auto trophyconf = doc.child("trophyconf"); + for (pugi::xml_node_iterator it = trophyconf.children().begin(); + it != trophyconf.children().end(); ++it) { + + if (std::string(it->name()) == "title-name") { + strncpy(details->title, it->text().as_string(), + ORBIS_NP_TROPHY_GAME_TITLE_MAX_SIZE); + } + + if (std::string(it->name()) == "title-detail") { + strncpy(details->description, it->text().as_string(), + ORBIS_NP_TROPHY_GAME_DESCR_MAX_SIZE); + } + + if (std::string(it->name()) == "group") + numGroups++; + + if (std::string(it->name()) == "trophy") { + std::string currentTrophyUnlockState = it->attribute("unlockstate").value(); + std::string currentTrophyGrade = it->attribute("ttype").value(); + + numTrophies++; + if (!currentTrophyGrade.empty()) { + int trophyGrade = GetTrophyGradeFromChar(currentTrophyGrade.at(0)); + numTrophiesByRarity[trophyGrade]++; + if (currentTrophyUnlockState == "unlocked") { + unlockedTrophies++; + unlockedTrophiesByRarity[trophyGrade]++; + } + } + } + } + + details->numGroups = numGroups; + details->numTrophies = numTrophies; + details->numPlatinum = numTrophiesByRarity[ORBIS_NP_TROPHY_GRADE_PLATINUM]; + details->numGold = numTrophiesByRarity[ORBIS_NP_TROPHY_GRADE_GOLD]; + details->numSilver = numTrophiesByRarity[ORBIS_NP_TROPHY_GRADE_SILVER]; + details->numBronze = numTrophiesByRarity[ORBIS_NP_TROPHY_GRADE_BRONZE]; + data->unlockedTrophies = unlockedTrophies; + data->unlockedPlatinum = unlockedTrophiesByRarity[ORBIS_NP_TROPHY_GRADE_PLATINUM]; + data->unlockedGold = unlockedTrophiesByRarity[ORBIS_NP_TROPHY_GRADE_GOLD]; + data->unlockedSilver = unlockedTrophiesByRarity[ORBIS_NP_TROPHY_GRADE_SILVER]; + data->unlockedBronze = unlockedTrophiesByRarity[ORBIS_NP_TROPHY_GRADE_BRONZE]; + + // maybe this should be 1 instead of 100? + data->progressPercentage = 100; + + } else + LOG_INFO(Lib_NpTrophy, "couldnt parse xml : {}", result.description()); -int PS4_SYSV_ABI sceNpTrophyGetTrophyIcon() { - LOG_ERROR(Lib_NpTrophy, "(STUBBED) called"); return ORBIS_OK; } -int PS4_SYSV_ABI sceNpTrophyGetTrophyInfo() { +int PS4_SYSV_ABI sceNpTrophyGetGroupIcon(OrbisNpTrophyContext context, OrbisNpTrophyHandle handle, + OrbisNpTrophyGroupId groupId, void* buffer, size_t* size) { LOG_ERROR(Lib_NpTrophy, "(STUBBED) called"); return ORBIS_OK; } -s32 PS4_SYSV_ABI sceNpTrophyGetTrophyUnlockState(u32 context, u32 handle, u32* flags, u32* count) { - LOG_ERROR(Lib_NpTrophy, "(STUBBED) called"); - *flags = 0u; - *count = 0; +int PS4_SYSV_ABI sceNpTrophyGetGroupInfo(OrbisNpTrophyContext context, OrbisNpTrophyHandle handle, + OrbisNpTrophyGroupId groupId, + OrbisNpTrophyGroupDetails* details, + OrbisNpTrophyGroupData* data) { + LOG_INFO(Lib_NpTrophy, "Getting Trophy Group Info for id {}", groupId); + + if (context == ORBIS_NP_TROPHY_INVALID_CONTEXT) + return ORBIS_NP_TROPHY_ERROR_INVALID_CONTEXT; + + if (handle == ORBIS_NP_TROPHY_INVALID_HANDLE) + return ORBIS_NP_TROPHY_ERROR_INVALID_HANDLE; + + if (details == nullptr || data == nullptr) + return ORBIS_NP_TROPHY_ERROR_INVALID_ARGUMENT; + + if (details->size != 0x4A0 || data->size != 0x28) + return ORBIS_NP_TROPHY_ERROR_INVALID_ARGUMENT; + + const auto trophyDir = + Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / game_serial / "TrophyFiles"; + + pugi::xml_document doc; + pugi::xml_parse_result result = + doc.load_file((trophyDir.string() + "/trophy00/Xml/TROP.XML").c_str()); + + if (result) { + + uint32_t numGroups = 0; + uint32_t numTrophies = 0; + uint32_t numTrophiesByRarity[5]; + numTrophiesByRarity[1] = 0; + numTrophiesByRarity[2] = 0; + numTrophiesByRarity[3] = 0; + numTrophiesByRarity[4] = 0; + uint32_t unlockedTrophies = 0; + uint32_t unlockedTrophiesByRarity[5]; + unlockedTrophiesByRarity[1] = 0; + unlockedTrophiesByRarity[2] = 0; + unlockedTrophiesByRarity[3] = 0; + unlockedTrophiesByRarity[4] = 0; + + auto trophyconf = doc.child("trophyconf"); + for (pugi::xml_node_iterator it = trophyconf.children().begin(); + it != trophyconf.children().end(); ++it) { + + if (std::string(it->name()) == "group") { + numGroups++; + std::string currentGroupId = it->attribute("id").value(); + if (!currentGroupId.empty()) { + if (std::stoi(currentGroupId) == groupId) { + std::string currentGroupName = it->child("name").text().as_string(); + std::string currentGroupDescription = + it->child("detail").text().as_string(); + + strncpy(details->title, currentGroupName.c_str(), + ORBIS_NP_TROPHY_GROUP_TITLE_MAX_SIZE); + strncpy(details->description, currentGroupDescription.c_str(), + ORBIS_NP_TROPHY_GAME_DESCR_MAX_SIZE); + } + } + } + + data->groupId = groupId; + + if (std::string(it->name()) == "trophy") { + std::string currentTrophyUnlockState = it->attribute("unlockstate").value(); + std::string currentTrophyGrade = it->attribute("ttype").value(); + std::string currentTrophyGroupID = it->attribute("gid").value(); + + if (!currentTrophyGroupID.empty()) { + if (std::stoi(currentTrophyGroupID) == groupId) { + numTrophies++; + if (!currentTrophyGrade.empty()) { + int trophyGrade = GetTrophyGradeFromChar(currentTrophyGrade.at(0)); + numTrophiesByRarity[trophyGrade]++; + if (currentTrophyUnlockState == "unlocked") { + unlockedTrophies++; + unlockedTrophiesByRarity[trophyGrade]++; + } + } + } + } + } + } + + details->numTrophies = numTrophies; + details->numPlatinum = numTrophiesByRarity[ORBIS_NP_TROPHY_GRADE_PLATINUM]; + details->numGold = numTrophiesByRarity[ORBIS_NP_TROPHY_GRADE_GOLD]; + details->numSilver = numTrophiesByRarity[ORBIS_NP_TROPHY_GRADE_SILVER]; + details->numBronze = numTrophiesByRarity[ORBIS_NP_TROPHY_GRADE_BRONZE]; + data->unlockedTrophies = unlockedTrophies; + data->unlockedPlatinum = unlockedTrophiesByRarity[ORBIS_NP_TROPHY_GRADE_PLATINUM]; + data->unlockedGold = unlockedTrophiesByRarity[ORBIS_NP_TROPHY_GRADE_GOLD]; + data->unlockedSilver = unlockedTrophiesByRarity[ORBIS_NP_TROPHY_GRADE_SILVER]; + data->unlockedBronze = unlockedTrophiesByRarity[ORBIS_NP_TROPHY_GRADE_BRONZE]; + + // maybe this should be 1 instead of 100? + data->progressPercentage = 100; + + } else + LOG_INFO(Lib_NpTrophy, "couldnt parse xml : {}", result.description()); + + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceNpTrophyGetTrophyIcon(OrbisNpTrophyContext context, OrbisNpTrophyHandle handle, + OrbisNpTrophyId trophyId, void* buffer, size_t* size) { + LOG_ERROR(Lib_NpTrophy, "(STUBBED) called"); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceNpTrophyGetTrophyInfo(OrbisNpTrophyContext context, OrbisNpTrophyHandle handle, + OrbisNpTrophyId trophyId, OrbisNpTrophyDetails* details, + OrbisNpTrophyData* data) { + LOG_INFO(Lib_NpTrophy, "Getting trophy info for id {}", trophyId); + + if (context == ORBIS_NP_TROPHY_INVALID_CONTEXT) + return ORBIS_NP_TROPHY_ERROR_INVALID_CONTEXT; + + if (handle == ORBIS_NP_TROPHY_INVALID_HANDLE) + return ORBIS_NP_TROPHY_ERROR_INVALID_HANDLE; + + if (trophyId >= 127) + return ORBIS_NP_TROPHY_ERROR_INVALID_TROPHY_ID; + + if (details == nullptr || data == nullptr) + return ORBIS_NP_TROPHY_ERROR_INVALID_ARGUMENT; + + if (details->size != 0x498 || data->size != 0x18) + return ORBIS_NP_TROPHY_ERROR_INVALID_ARGUMENT; + + const auto trophyDir = + Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / game_serial / "TrophyFiles"; + + pugi::xml_document doc; + pugi::xml_parse_result result = + doc.load_file((trophyDir.string() + "/trophy00/Xml/TROP.XML").c_str()); + + if (result) { + auto trophyconf = doc.child("trophyconf"); + for (pugi::xml_node_iterator it = trophyconf.children().begin(); + it != trophyconf.children().end(); ++it) { + + if (std::string(it->name()) == "trophy") { + std::string currentTrophyId = it->attribute("id").value(); + if (std::stoi(currentTrophyId) == trophyId) { + std::string currentTrophyUnlockState = it->attribute("unlockstate").value(); + std::string currentTrophyTimestamp = it->attribute("timestamp").value(); + std::string currentTrophyGrade = it->attribute("ttype").value(); + std::string currentTrophyGroupID = it->attribute("gid").value(); + std::string currentTrophyHidden = it->attribute("hidden").value(); + std::string currentTrophyName = it->child("name").text().as_string(); + std::string currentTrophyDescription = it->child("detail").text().as_string(); + + if (currentTrophyUnlockState == "unlocked") { + details->trophyId = trophyId; + if (currentTrophyGrade.empty()) { + details->trophyGrade = ORBIS_NP_TROPHY_GRADE_UNKNOWN; + } else { + details->trophyGrade = GetTrophyGradeFromChar(currentTrophyGrade.at(0)); + } + if (currentTrophyGroupID.empty()) { + details->groupId = ORBIS_NP_TROPHY_BASE_GAME_GROUP_ID; + } else { + details->groupId = std::stoi(currentTrophyGroupID); + } + if (currentTrophyHidden == "yes") { + details->hidden = true; + } else { + details->hidden = false; + } + + strncpy(details->name, currentTrophyName.c_str(), + ORBIS_NP_TROPHY_NAME_MAX_SIZE); + strncpy(details->description, currentTrophyDescription.c_str(), + ORBIS_NP_TROPHY_DESCR_MAX_SIZE); + + data->trophyId = trophyId; + data->unlocked = true; + data->timestamp.tick = std::stoull(currentTrophyTimestamp); + } else { + details->trophyId = trophyId; + if (currentTrophyGrade.empty()) { + details->trophyGrade = ORBIS_NP_TROPHY_GRADE_UNKNOWN; + } else { + details->trophyGrade = GetTrophyGradeFromChar(currentTrophyGrade.at(0)); + } + if (currentTrophyGroupID.empty()) { + details->groupId = ORBIS_NP_TROPHY_BASE_GAME_GROUP_ID; + } else { + details->groupId = std::stoi(currentTrophyGroupID); + } + if (currentTrophyHidden == "yes") { + details->hidden = true; + } else { + details->hidden = false; + } + + strncpy(details->name, currentTrophyName.c_str(), + ORBIS_NP_TROPHY_NAME_MAX_SIZE); + strncpy(details->description, currentTrophyDescription.c_str(), + ORBIS_NP_TROPHY_DESCR_MAX_SIZE); + + data->trophyId = trophyId; + data->unlocked = false; + data->timestamp.tick = 0; + } + } + } + } + } else + LOG_INFO(Lib_NpTrophy, "couldnt parse xml : {}", result.description()); + + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpTrophyGetTrophyUnlockState(OrbisNpTrophyContext context, + OrbisNpTrophyHandle handle, + OrbisNpTrophyFlagArray* flags, u32* count) { + LOG_INFO(Lib_NpTrophy, "GetTrophyUnlockState called"); + + if (context == ORBIS_NP_TROPHY_INVALID_CONTEXT) + return ORBIS_NP_TROPHY_ERROR_INVALID_CONTEXT; + + if (handle == ORBIS_NP_TROPHY_INVALID_HANDLE) + return ORBIS_NP_TROPHY_ERROR_INVALID_HANDLE; + + if (flags == nullptr || count == nullptr) + return ORBIS_NP_TROPHY_ERROR_INVALID_ARGUMENT; + + ORBIS_NP_TROPHY_FLAG_ZERO(flags); + + const auto trophyDir = + Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / game_serial / "TrophyFiles"; + + pugi::xml_document doc; + pugi::xml_parse_result result = + doc.load_file((trophyDir.string() + "/trophy00/Xml/TROP.XML").c_str()); + + int numTrophies = 0; + + if (result) { + auto trophyconf = doc.child("trophyconf"); + for (pugi::xml_node_iterator it = trophyconf.children().begin(); + it != trophyconf.children().end(); ++it) { + + std::string currentTrophyId = it->attribute("id").value(); + std::string currentTrophyUnlockState = it->attribute("unlockstate").value(); + + if (std::string(it->name()) == "trophy") { + numTrophies++; + } + + if (currentTrophyUnlockState == "unlocked") { + ORBIS_NP_TROPHY_FLAG_SET(std::stoi(currentTrophyId), flags); + } + } + } else + LOG_INFO(Lib_NpTrophy, "couldnt parse xml : {}", result.description()); + + *count = numTrophies; return ORBIS_OK; } @@ -239,8 +652,16 @@ int PS4_SYSV_ABI sceNpTrophyNumInfoGetTotal() { return ORBIS_OK; } -int PS4_SYSV_ABI sceNpTrophyRegisterContext() { +int PS4_SYSV_ABI sceNpTrophyRegisterContext(OrbisNpTrophyContext context, + OrbisNpTrophyHandle handle, uint64_t options) { LOG_ERROR(Lib_NpTrophy, "(STUBBED) called"); + + if (context == ORBIS_NP_TROPHY_INVALID_CONTEXT) + return ORBIS_NP_TROPHY_ERROR_INVALID_CONTEXT; + + if (handle == ORBIS_NP_TROPHY_INVALID_HANDLE) + return ORBIS_NP_TROPHY_ERROR_INVALID_HANDLE; + return ORBIS_OK; } @@ -254,7 +675,8 @@ int PS4_SYSV_ABI sceNpTrophySetInfoGetTrophyNum() { return ORBIS_OK; } -int PS4_SYSV_ABI sceNpTrophyShowTrophyList() { +int PS4_SYSV_ABI sceNpTrophyShowTrophyList(OrbisNpTrophyContext context, + OrbisNpTrophyHandle handle) { LOG_ERROR(Lib_NpTrophy, "(STUBBED) called"); return ORBIS_OK; } @@ -474,8 +896,165 @@ int PS4_SYSV_ABI sceNpTrophySystemSetDbgParamInt() { return ORBIS_OK; } -int PS4_SYSV_ABI sceNpTrophyUnlockTrophy() { - LOG_ERROR(Lib_NpTrophy, "(STUBBED) called"); +int PS4_SYSV_ABI sceNpTrophyUnlockTrophy(OrbisNpTrophyContext context, OrbisNpTrophyHandle handle, + OrbisNpTrophyId trophyId, OrbisNpTrophyId* platinumId) { + LOG_INFO(Lib_NpTrophy, "Unlocking trophy id {}", trophyId); + + if (context == ORBIS_NP_TROPHY_INVALID_CONTEXT) + return ORBIS_NP_TROPHY_ERROR_INVALID_CONTEXT; + + if (handle == ORBIS_NP_TROPHY_INVALID_HANDLE) + return ORBIS_NP_TROPHY_ERROR_INVALID_HANDLE; + + if (trophyId >= 127) + return ORBIS_NP_TROPHY_ERROR_INVALID_TROPHY_ID; + + if (platinumId == nullptr) + return ORBIS_NP_TROPHY_ERROR_INVALID_ARGUMENT; + + const auto trophyDir = + Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / game_serial / "TrophyFiles"; + + pugi::xml_document doc; + pugi::xml_parse_result result = + doc.load_file((trophyDir.string() + "/trophy00/Xml/TROP.XML").c_str()); + + *platinumId = ORBIS_NP_TROPHY_INVALID_TROPHY_ID; + + int numTrophies = 0; + int numTrophiesUnlocked = 0; + + pugi::xml_node_iterator platinumIt; + int platinumTrophyGroup = -1; + + if (result) { + auto trophyconf = doc.child("trophyconf"); + for (pugi::xml_node_iterator it = trophyconf.children().begin(); + it != trophyconf.children().end(); ++it) { + + std::string currentTrophyId = it->attribute("id").value(); + std::string currentTrophyName = it->child("name").text().as_string(); + std::string currentTrophyDescription = it->child("detail").text().as_string(); + std::string currentTrophyType = it->attribute("ttype").value(); + std::string currentTrophyUnlockState = it->attribute("unlockstate").value(); + + if (currentTrophyType == "P") { + platinumIt = it; + + if (std::string(platinumIt->attribute("gid").value()).empty()) { + platinumTrophyGroup = -1; + } else { + platinumTrophyGroup = + std::stoi(std::string(platinumIt->attribute("gid").value())); + } + + if (trophyId == std::stoi(currentTrophyId)) { + return ORBIS_NP_TROPHY_ERROR_PLATINUM_CANNOT_UNLOCK; + } + } + + if (std::string(it->name()) == "trophy") { + if (platinumTrophyGroup == -1) { + if (std::string(it->attribute("gid").value()).empty()) { + numTrophies++; + if (currentTrophyUnlockState == "unlocked") { + numTrophiesUnlocked++; + } + } + } else { + if (!std::string(it->attribute("gid").value()).empty()) { + if (std::stoi(std::string(it->attribute("gid").value())) == + platinumTrophyGroup) { + numTrophies++; + if (currentTrophyUnlockState == "unlocked") { + numTrophiesUnlocked++; + } + } + } + } + + if (std::stoi(currentTrophyId) == trophyId) { + LOG_INFO(Lib_NpTrophy, "Found trophy to unlock {} : {}", + it->child("name").text().as_string(), + it->child("detail").text().as_string()); + if (currentTrophyUnlockState == "unlocked") { + LOG_INFO(Lib_NpTrophy, "Trophy already unlocked"); + return ORBIS_NP_TROPHY_ERROR_TROPHY_ALREADY_UNLOCKED; + } else { + if (std::string(it->attribute("unlockstate").value()).empty()) { + it->append_attribute("unlockstate") = "unlocked"; + } else { + it->attribute("unlockstate").set_value("unlocked"); + } + + Rtc::OrbisRtcTick trophyTimestamp; + Rtc::sceRtcGetCurrentTick(&trophyTimestamp); + + if (std::string(it->attribute("timestamp").value()).empty()) { + it->append_attribute("timestamp") = + std::to_string(trophyTimestamp.tick).c_str(); + } else { + it->attribute("timestamp") + .set_value(std::to_string(trophyTimestamp.tick).c_str()); + } + + g_trophy_ui.AddTrophyToQueue(trophyId, currentTrophyName); + } + } + } + } + + if (std::string(platinumIt->attribute("unlockstate").value()).empty()) { + if ((numTrophies - 2) == numTrophiesUnlocked) { + + platinumIt->append_attribute("unlockstate") = "unlocked"; + + Rtc::OrbisRtcTick trophyTimestamp; + Rtc::sceRtcGetCurrentTick(&trophyTimestamp); + + if (std::string(platinumIt->attribute("timestamp").value()).empty()) { + platinumIt->append_attribute("timestamp") = + std::to_string(trophyTimestamp.tick).c_str(); + } else { + platinumIt->attribute("timestamp") + .set_value(std::to_string(trophyTimestamp.tick).c_str()); + } + + std::string platinumTrophyId = platinumIt->attribute("id").value(); + std::string platinumTrophyName = platinumIt->child("name").text().as_string(); + + *platinumId = std::stoi(platinumTrophyId); + g_trophy_ui.AddTrophyToQueue(*platinumId, platinumTrophyName); + } + } else if (std::string(platinumIt->attribute("unlockstate").value()) == "locked") { + if ((numTrophies - 2) == numTrophiesUnlocked) { + + platinumIt->attribute("unlockstate").set_value("unlocked"); + + Rtc::OrbisRtcTick trophyTimestamp; + Rtc::sceRtcGetCurrentTick(&trophyTimestamp); + + if (std::string(platinumIt->attribute("timestamp").value()).empty()) { + platinumIt->append_attribute("timestamp") = + std::to_string(trophyTimestamp.tick).c_str(); + } else { + platinumIt->attribute("timestamp") + .set_value(std::to_string(trophyTimestamp.tick).c_str()); + } + + std::string platinumTrophyId = platinumIt->attribute("id").value(); + std::string platinumTrophyName = platinumIt->child("name").text().as_string(); + + *platinumId = std::stoi(platinumTrophyId); + g_trophy_ui.AddTrophyToQueue(*platinumId, platinumTrophyName); + } + } + + doc.save_file((trophyDir.string() + "/trophy00/Xml/TROP.XML").c_str()); + + } else + LOG_INFO(Lib_NpTrophy, "couldnt parse xml : {}", result.description()); + return ORBIS_OK; } diff --git a/src/core/libraries/np_trophy/np_trophy.h b/src/core/libraries/np_trophy/np_trophy.h index d05d353f1e5..ae08b29698a 100644 --- a/src/core/libraries/np_trophy/np_trophy.h +++ b/src/core/libraries/np_trophy/np_trophy.h @@ -4,6 +4,7 @@ #pragma once #include "common/types.h" +#include "core/libraries/rtc/rtc.h" namespace Core::Loader { class SymbolsResolver; @@ -11,7 +12,116 @@ class SymbolsResolver; namespace Libraries::NpTrophy { -int PS4_SYSV_ABI sceNpTrophyAbortHandle(); +extern std::string game_serial; + +constexpr int ORBIS_NP_TROPHY_FLAG_SETSIZE = 128; +constexpr int ORBIS_NP_TROPHY_FLAG_BITS_SHIFT = 5; + +constexpr int ORBIS_NP_TROPHY_GAME_TITLE_MAX_SIZE = 128; +constexpr int ORBIS_NP_TROPHY_GAME_DESCR_MAX_SIZE = 1024; +constexpr int ORBIS_NP_TROPHY_GROUP_TITLE_MAX_SIZE = 128; +constexpr int ORBIS_NP_TROPHY_GROUP_DESCR_MAX_SIZE = 1024; +constexpr int ORBIS_NP_TROPHY_NAME_MAX_SIZE = 128; +constexpr int ORBIS_NP_TROPHY_DESCR_MAX_SIZE = 1024; +constexpr int ORBIS_NP_TROPHY_NUM_MAX = 128; + +constexpr int ORBIS_NP_TROPHY_INVALID_HANDLE = -1; +constexpr int ORBIS_NP_TROPHY_INVALID_CONTEXT = -1; +constexpr int ORBIS_NP_TROPHY_INVALID_TROPHY_ID = -1; + +typedef int32_t OrbisNpTrophyHandle; +typedef int32_t OrbisNpTrophyContext; +typedef int32_t OrbisNpTrophyId; +typedef uint32_t OrbisNpTrophyFlagMask; + +struct OrbisNpTrophyFlagArray { + OrbisNpTrophyFlagMask + flag_bits[ORBIS_NP_TROPHY_FLAG_SETSIZE >> ORBIS_NP_TROPHY_FLAG_BITS_SHIFT]; +}; + +void ORBIS_NP_TROPHY_FLAG_ZERO(OrbisNpTrophyFlagArray* p); +void ORBIS_NP_TROPHY_FLAG_SET(int32_t trophyId, OrbisNpTrophyFlagArray* p); +void ORBIS_NP_TROPHY_FLAG_SET_ALL(OrbisNpTrophyFlagArray* p); +void ORBIS_NP_TROPHY_FLAG_CLR(int32_t trophyId, OrbisNpTrophyFlagArray* p); +bool ORBIS_NP_TROPHY_FLAG_ISSET(int32_t trophyId, OrbisNpTrophyFlagArray* p); + +struct OrbisNpTrophyData { + size_t size; + OrbisNpTrophyId trophyId; + bool unlocked; + uint8_t reserved[3]; + Rtc::OrbisRtcTick timestamp; +}; + +typedef int32_t OrbisNpTrophyGrade; +constexpr int ORBIS_NP_TROPHY_GRADE_UNKNOWN = 0; +constexpr int ORBIS_NP_TROPHY_GRADE_PLATINUM = 1; +constexpr int ORBIS_NP_TROPHY_GRADE_GOLD = 2; +constexpr int ORBIS_NP_TROPHY_GRADE_SILVER = 3; +constexpr int ORBIS_NP_TROPHY_GRADE_BRONZE = 4; + +typedef int32_t OrbisNpTrophyGroupId; +constexpr int ORBIS_NP_TROPHY_BASE_GAME_GROUP_ID = -1; +constexpr int ORBIS_NP_TROPHY_INVALID_GROUP_ID = -2; + +struct OrbisNpTrophyDetails { + size_t size; + OrbisNpTrophyId trophyId; + OrbisNpTrophyGrade trophyGrade; + OrbisNpTrophyGroupId groupId; + bool hidden; + uint8_t reserved[3]; + char name[ORBIS_NP_TROPHY_NAME_MAX_SIZE]; + char description[ORBIS_NP_TROPHY_DESCR_MAX_SIZE]; +}; + +struct OrbisNpTrophyGameData { + size_t size; + uint32_t unlockedTrophies; + uint32_t unlockedPlatinum; + uint32_t unlockedGold; + uint32_t unlockedSilver; + uint32_t unlockedBronze; + uint32_t progressPercentage; +}; + +struct OrbisNpTrophyGameDetails { + size_t size; + uint32_t numGroups; + uint32_t numTrophies; + uint32_t numPlatinum; + uint32_t numGold; + uint32_t numSilver; + uint32_t numBronze; + char title[ORBIS_NP_TROPHY_GAME_TITLE_MAX_SIZE]; + char description[ORBIS_NP_TROPHY_GAME_DESCR_MAX_SIZE]; +}; + +struct OrbisNpTrophyGroupData { + size_t size; + OrbisNpTrophyGroupId groupId; + uint32_t unlockedTrophies; + uint32_t unlockedPlatinum; + uint32_t unlockedGold; + uint32_t unlockedSilver; + uint32_t unlockedBronze; + uint32_t progressPercentage; + uint8_t reserved[4]; +}; + +struct OrbisNpTrophyGroupDetails { + size_t size; + OrbisNpTrophyGroupId groupId; + uint32_t numTrophies; + uint32_t numPlatinum; + uint32_t numGold; + uint32_t numSilver; + uint32_t numBronze; + char title[ORBIS_NP_TROPHY_GROUP_TITLE_MAX_SIZE]; + char description[ORBIS_NP_TROPHY_GROUP_DESCR_MAX_SIZE]; +}; + +int PS4_SYSV_ABI sceNpTrophyAbortHandle(OrbisNpTrophyHandle handle); int PS4_SYSV_ABI sceNpTrophyCaptureScreenshot(); int PS4_SYSV_ABI sceNpTrophyConfigGetTrophyDetails(); int PS4_SYSV_ABI sceNpTrophyConfigGetTrophyFlagArray(); @@ -22,18 +132,30 @@ int PS4_SYSV_ABI sceNpTrophyConfigGetTrophySetInfoInGroup(); int PS4_SYSV_ABI sceNpTrophyConfigGetTrophySetVersion(); int PS4_SYSV_ABI sceNpTrophyConfigGetTrophyTitleDetails(); int PS4_SYSV_ABI sceNpTrophyConfigHasGroupFeature(); -s32 PS4_SYSV_ABI sceNpTrophyCreateContext(u32* context, u32 user_id, u32 service_label, - u64 options); -s32 PS4_SYSV_ABI sceNpTrophyCreateHandle(u32* handle); -int PS4_SYSV_ABI sceNpTrophyDestroyContext(); -s32 PS4_SYSV_ABI sceNpTrophyDestroyHandle(u32 handle); -int PS4_SYSV_ABI sceNpTrophyGetGameIcon(); -int PS4_SYSV_ABI sceNpTrophyGetGameInfo(); -int PS4_SYSV_ABI sceNpTrophyGetGroupIcon(); -int PS4_SYSV_ABI sceNpTrophyGetGroupInfo(); -int PS4_SYSV_ABI sceNpTrophyGetTrophyIcon(); -int PS4_SYSV_ABI sceNpTrophyGetTrophyInfo(); -s32 PS4_SYSV_ABI sceNpTrophyGetTrophyUnlockState(u32 context, u32 handle, u32* flags, u32* count); +s32 PS4_SYSV_ABI sceNpTrophyCreateContext(OrbisNpTrophyContext* context, int32_t user_id, + uint32_t service_label, uint64_t options); +s32 PS4_SYSV_ABI sceNpTrophyCreateHandle(OrbisNpTrophyHandle* handle); +int PS4_SYSV_ABI sceNpTrophyDestroyContext(OrbisNpTrophyContext context); +s32 PS4_SYSV_ABI sceNpTrophyDestroyHandle(OrbisNpTrophyHandle handle); +int PS4_SYSV_ABI sceNpTrophyGetGameIcon(OrbisNpTrophyContext context, OrbisNpTrophyHandle handle, + void* buffer, size_t* size); +int PS4_SYSV_ABI sceNpTrophyGetGameInfo(OrbisNpTrophyContext context, OrbisNpTrophyHandle handle, + OrbisNpTrophyGameDetails* details, + OrbisNpTrophyGameData* data); +int PS4_SYSV_ABI sceNpTrophyGetGroupIcon(OrbisNpTrophyContext context, OrbisNpTrophyHandle handle, + OrbisNpTrophyGroupId groupId, void* buffer, size_t* size); +int PS4_SYSV_ABI sceNpTrophyGetGroupInfo(OrbisNpTrophyContext context, OrbisNpTrophyHandle handle, + OrbisNpTrophyGroupId groupId, + OrbisNpTrophyGroupDetails* details, + OrbisNpTrophyGroupData* data); +int PS4_SYSV_ABI sceNpTrophyGetTrophyIcon(OrbisNpTrophyContext context, OrbisNpTrophyHandle handle, + OrbisNpTrophyId trophyId, void* buffer, size_t* size); +int PS4_SYSV_ABI sceNpTrophyGetTrophyInfo(OrbisNpTrophyContext context, OrbisNpTrophyHandle handle, + OrbisNpTrophyId trophyId, OrbisNpTrophyDetails* details, + OrbisNpTrophyData* data); +s32 PS4_SYSV_ABI sceNpTrophyGetTrophyUnlockState(OrbisNpTrophyContext context, + OrbisNpTrophyHandle handle, + OrbisNpTrophyFlagArray* flags, u32* count); int PS4_SYSV_ABI sceNpTrophyGroupArrayGetNum(); int PS4_SYSV_ABI sceNpTrophyIntAbortHandle(); int PS4_SYSV_ABI sceNpTrophyIntCheckNetSyncTitles(); @@ -47,10 +169,12 @@ int PS4_SYSV_ABI sceNpTrophyIntGetTrpIconByUri(); int PS4_SYSV_ABI sceNpTrophyIntNetSyncTitle(); int PS4_SYSV_ABI sceNpTrophyIntNetSyncTitles(); int PS4_SYSV_ABI sceNpTrophyNumInfoGetTotal(); -int PS4_SYSV_ABI sceNpTrophyRegisterContext(); +int PS4_SYSV_ABI sceNpTrophyRegisterContext(OrbisNpTrophyContext context, + OrbisNpTrophyHandle handle, uint64_t options); int PS4_SYSV_ABI sceNpTrophySetInfoGetTrophyFlagArray(); int PS4_SYSV_ABI sceNpTrophySetInfoGetTrophyNum(); -int PS4_SYSV_ABI sceNpTrophyShowTrophyList(); +int PS4_SYSV_ABI sceNpTrophyShowTrophyList(OrbisNpTrophyContext context, + OrbisNpTrophyHandle handle); int PS4_SYSV_ABI sceNpTrophySystemAbortHandle(); int PS4_SYSV_ABI sceNpTrophySystemBuildGroupIconUri(); int PS4_SYSV_ABI sceNpTrophySystemBuildNetTrophyIconUri(); @@ -94,7 +218,8 @@ int PS4_SYSV_ABI sceNpTrophySystemRemoveTitleData(); int PS4_SYSV_ABI sceNpTrophySystemRemoveUserData(); int PS4_SYSV_ABI sceNpTrophySystemSetDbgParam(); int PS4_SYSV_ABI sceNpTrophySystemSetDbgParamInt(); -int PS4_SYSV_ABI sceNpTrophyUnlockTrophy(); +int PS4_SYSV_ABI sceNpTrophyUnlockTrophy(OrbisNpTrophyContext context, OrbisNpTrophyHandle handle, + OrbisNpTrophyId trophyId, OrbisNpTrophyId* platinumId); int PS4_SYSV_ABI Func_149656DA81D41C59(); int PS4_SYSV_ABI Func_9F80071876FFA5F6(); int PS4_SYSV_ABI Func_F8EF6F5350A91990(); diff --git a/src/core/libraries/np_trophy/trophy_ui.cpp b/src/core/libraries/np_trophy/trophy_ui.cpp new file mode 100644 index 00000000000..6f60e8f9af5 --- /dev/null +++ b/src/core/libraries/np_trophy/trophy_ui.cpp @@ -0,0 +1,74 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include "common/assert.h" +#include "imgui/imgui_std.h" +#include "trophy_ui.h" + +using namespace ImGui; +using namespace Libraries::NpTrophy; + +TrophyUI::TrophyUI() { + AddLayer(this); +} + +TrophyUI::~TrophyUI() { + Finish(); +} + +void Libraries::NpTrophy::TrophyUI::AddTrophyToQueue(int trophyId, std::string trophyName) { + TrophyInfo newInfo; + newInfo.trophyId = trophyId; + newInfo.trophyName = trophyName; + trophyQueue.push_back(newInfo); +} + +void TrophyUI::Finish() { + RemoveLayer(this); +} + +bool displayingTrophy; +std::chrono::steady_clock::time_point trophyStartedTime; + +void TrophyUI::Draw() { + const auto& io = GetIO(); + + const ImVec2 window_size{ + std::min(io.DisplaySize.x, 200.f), + std::min(io.DisplaySize.y, 75.f), + }; + + if (trophyQueue.size() != 0) { + if (!displayingTrophy) { + displayingTrophy = true; + trophyStartedTime = std::chrono::steady_clock::now(); + } + + std::chrono::steady_clock::time_point timeNow = std::chrono::steady_clock::now(); + std::chrono::seconds duration = + std::chrono::duration_cast(timeNow - trophyStartedTime); + + if (duration.count() >= 5) { + trophyQueue.erase(trophyQueue.begin()); + displayingTrophy = false; + } + + if (trophyQueue.size() != 0) { + SetNextWindowSize(window_size); + SetNextWindowCollapsed(false); + SetNextWindowPos(ImVec2(io.DisplaySize.x - 200, 50)); + KeepNavHighlight(); + + TrophyInfo currentTrophyInfo = trophyQueue[0]; + if (Begin("Trophy Window", nullptr, + ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings | + ImGuiWindowFlags_NoInputs)) { + Text("Trophy earned!"); + TextWrapped("%s", currentTrophyInfo.trophyName.c_str()); + } + End(); + } + } +} \ No newline at end of file diff --git a/src/core/libraries/np_trophy/trophy_ui.h b/src/core/libraries/np_trophy/trophy_ui.h new file mode 100644 index 00000000000..060d80dec57 --- /dev/null +++ b/src/core/libraries/np_trophy/trophy_ui.h @@ -0,0 +1,36 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include + +#include "common/fixed_value.h" +#include "common/types.h" +#include "core/libraries/np_trophy/np_trophy.h" +#include "imgui/imgui_layer.h" + +namespace Libraries::NpTrophy { + +struct TrophyInfo { + int trophyId = -1; + std::string trophyName; +}; + +class TrophyUI final : public ImGui::Layer { + std::vector trophyQueue; + +public: + TrophyUI(); + ~TrophyUI() override; + + void AddTrophyToQueue(int trophyId, std::string trophyName); + + void Finish(); + + void Draw() override; +}; + +}; // namespace Libraries::NpTrophy \ No newline at end of file diff --git a/src/core/libraries/pad/pad.cpp b/src/core/libraries/pad/pad.cpp index cb0da552a27..6c3b1f56c62 100644 --- a/src/core/libraries/pad/pad.cpp +++ b/src/core/libraries/pad/pad.cpp @@ -250,7 +250,7 @@ int PS4_SYSV_ABI scePadOpen(s32 userId, s32 type, s32 index, const OrbisPadOpenP if (type != ORBIS_PAD_PORT_TYPE_SPECIAL) return ORBIS_PAD_ERROR_DEVICE_NOT_CONNECTED; } else { - if (type != ORBIS_PAD_PORT_TYPE_STANDARD) + if (type != ORBIS_PAD_PORT_TYPE_STANDARD && type != ORBIS_PAD_PORT_TYPE_REMOTE_CONTROL) return ORBIS_PAD_ERROR_DEVICE_NOT_CONNECTED; } return 1; // dummy @@ -263,7 +263,7 @@ int PS4_SYSV_ABI scePadOpenExt(s32 userId, s32 type, s32 index, if (type != ORBIS_PAD_PORT_TYPE_SPECIAL) return ORBIS_PAD_ERROR_DEVICE_NOT_CONNECTED; } else { - if (type != ORBIS_PAD_PORT_TYPE_STANDARD) + if (type != ORBIS_PAD_PORT_TYPE_STANDARD && type != ORBIS_PAD_PORT_TYPE_REMOTE_CONTROL) return ORBIS_PAD_ERROR_DEVICE_NOT_CONNECTED; } return 1; // dummy @@ -522,8 +522,8 @@ int PS4_SYSV_ABI scePadSetUserColor() { int PS4_SYSV_ABI scePadSetVibration(s32 handle, const OrbisPadVibrationParam* pParam) { if (pParam != nullptr) { - LOG_INFO(Lib_Pad, "scePadSetVibration called handle = {} data = {} , {}", handle, - pParam->smallMotor, pParam->largeMotor); + LOG_DEBUG(Lib_Pad, "scePadSetVibration called handle = {} data = {} , {}", handle, + pParam->smallMotor, pParam->largeMotor); auto* controller = Common::Singleton::Instance(); controller->SetVibration(pParam->smallMotor, pParam->largeMotor); return ORBIS_OK; diff --git a/src/core/libraries/pad/pad.h b/src/core/libraries/pad/pad.h index b18bbc3555a..f94a642cfb7 100644 --- a/src/core/libraries/pad/pad.h +++ b/src/core/libraries/pad/pad.h @@ -16,6 +16,7 @@ constexpr int ORBIS_PAD_MAX_DEVICE_UNIQUE_DATA_SIZE = 12; constexpr int ORBIS_PAD_PORT_TYPE_STANDARD = 0; constexpr int ORBIS_PAD_PORT_TYPE_SPECIAL = 2; +constexpr int ORBIS_PAD_PORT_TYPE_REMOTE_CONTROL = 16; enum OrbisPadDeviceClass { ORBIS_PAD_DEVICE_CLASS_INVALID = -1, diff --git a/src/core/libraries/rtc/rtc.cpp b/src/core/libraries/rtc/rtc.cpp index 387a8558b36..7a46a1e3156 100644 --- a/src/core/libraries/rtc/rtc.cpp +++ b/src/core/libraries/rtc/rtc.cpp @@ -5,156 +5,827 @@ #include "common/logging/log.h" #include "core/libraries/error_codes.h" +#include "core/libraries/kernel/libkernel.h" +#include "core/libraries/kernel/time_management.h" #include "core/libraries/libs.h" #include "rtc.h" #include "rtc_error.h" namespace Libraries::Rtc { -int PS4_SYSV_ABI sceRtcCheckValid() { - LOG_ERROR(Lib_Rtc, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceRtcCheckValid(OrbisRtcDateTime* pTime) { + LOG_TRACE(Lib_Rtc, "called"); + + if (pTime == nullptr) + return ORBIS_RTC_ERROR_INVALID_POINTER; + + if (pTime->year == 0 || pTime->year > 9999) + return ORBIS_RTC_ERROR_INVALID_YEAR; + + if (pTime->month == 0 || pTime->month > 12) + return ORBIS_RTC_ERROR_INVALID_MONTH; + + if (pTime->day == 0) + return ORBIS_RTC_ERROR_INVALID_DAY; + + using namespace std::chrono; + year chronoYear = year(pTime->year); + month chronoMonth = month(pTime->month); + int lastDay = + static_cast(unsigned(year_month_day_last{chronoYear / chronoMonth / last}.day())); + + if (pTime->day > lastDay) + return ORBIS_RTC_ERROR_INVALID_DAY; + + if (pTime->hour >= 24) + return ORBIS_RTC_ERROR_INVALID_HOUR; + + if (pTime->minute >= 60) + return ORBIS_RTC_ERROR_INVALID_MINUTE; + + if (pTime->second >= 60) + return ORBIS_RTC_ERROR_INVALID_SECOND; + + if (pTime->microsecond >= 1000000) + return ORBIS_RTC_ERROR_INVALID_MICROSECOND; + + return SCE_OK; } -int PS4_SYSV_ABI sceRtcCompareTick() { - LOG_ERROR(Lib_Rtc, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceRtcCompareTick(OrbisRtcTick* pTick1, OrbisRtcTick* pTick2) { + LOG_TRACE(Lib_Rtc, "called"); + + if (pTick1 == nullptr || pTick2 == nullptr) + return ORBIS_RTC_ERROR_INVALID_POINTER; + + if (pTick1->tick <= pTick2->tick) + return 1; + else + return 0; + + return ORBIS_FAIL; } -int PS4_SYSV_ABI sceRtcConvertLocalTimeToUtc() { - LOG_ERROR(Lib_Rtc, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceRtcConvertLocalTimeToUtc(OrbisRtcTick* pTickLocal, OrbisRtcTick* pTickUtc) { + LOG_TRACE(Lib_Rtc, "called"); + + if (pTickLocal == nullptr) + return ORBIS_RTC_ERROR_INVALID_POINTER; + + time_t seconds; + Kernel::OrbisKernelTimezone timezone; + + int convertValue = Kernel::sceKernelConvertLocaltimeToUtc( + (pTickLocal->tick - UNIX_EPOCH_TICKS) / 1000000, 0xffffffff, &seconds, &timezone, 0); + + if (convertValue >= 0) { + convertValue = sceRtcTickAddMinutes( + pTickUtc, pTickLocal, -(((timezone.tz_dsttime * 60) - timezone.tz_minuteswest))); + } + + return convertValue; } -int PS4_SYSV_ABI sceRtcConvertUtcToLocalTime() { - LOG_ERROR(Lib_Rtc, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceRtcConvertUtcToLocalTime(OrbisRtcTick* pTickUtc, OrbisRtcTick* pTickLocal) { + LOG_TRACE(Lib_Rtc, "called"); + + if (pTickUtc == nullptr) + return ORBIS_RTC_ERROR_INVALID_POINTER; + + Kernel::OrbisKernelTimezone timeZone; + int returnValue = Kernel::sceKernelGettimezone(&timeZone); + + sceRtcTickAddMinutes(pTickLocal, pTickUtc, + -(timeZone.tz_minuteswest - (timeZone.tz_dsttime * 60))); + + return 0; } int PS4_SYSV_ABI sceRtcEnd() { - LOG_ERROR(Lib_Rtc, "(STUBBED) called"); - return ORBIS_OK; + return SCE_OK; } -int PS4_SYSV_ABI sceRtcFormatRFC2822() { - LOG_ERROR(Lib_Rtc, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceRtcFormatRFC2822(char* pszDateTime, const OrbisRtcTick* pTickUtc, + int iTimeZoneMinutes) { + LOG_TRACE(Lib_Rtc, "called"); + + if (pszDateTime == nullptr) + return ORBIS_RTC_ERROR_INVALID_POINTER; + + OrbisRtcTick formatTick; + + if (pTickUtc == nullptr) { + sceRtcGetCurrentTick(&formatTick); + } else { + formatTick.tick = pTickUtc->tick; + } + + sceRtcTickAddMinutes(&formatTick, &formatTick, iTimeZoneMinutes); + + OrbisRtcDateTime formatTime; + sceRtcSetTick(&formatTime, &formatTick); + + int validTime = sceRtcCheckValid(&formatTime); + + std::string formattedString; + + if (validTime >= 0) { + int weekDay = sceRtcGetDayOfWeek(formatTime.year, formatTime.month, formatTime.day); + switch (weekDay) { + case 0: + formattedString = "Sun, "; + break; + case 1: + formattedString = "Mon, "; + break; + case 2: + formattedString = "Tue, "; + break; + case 3: + formattedString = "Wed, "; + break; + case 4: + formattedString = "Thu, "; + break; + case 5: + formattedString = "Fri, "; + break; + case 6: + formattedString = "Sat, "; + break; + } + + if (formatTime.day < 10) { + formattedString += "0" + std::to_string(formatTime.day) + " "; + } else { + formattedString += std::to_string(formatTime.day) + " "; + } + + switch (formatTime.month) { + case 1: + formattedString += "Jan "; + break; + case 2: + formattedString += "Feb "; + break; + case 3: + formattedString += "Mar "; + break; + case 4: + formattedString += "Apr "; + break; + case 5: + formattedString += "May "; + break; + case 6: + formattedString += "Jun "; + break; + case 7: + formattedString += "Jul "; + break; + case 8: + formattedString += "Aug "; + break; + case 9: + formattedString += "Sep "; + break; + case 10: + formattedString += "Oct "; + break; + case 11: + formattedString += "Nov "; + break; + case 12: + formattedString += "Dec "; + break; + } + + formattedString += std::to_string(formatTime.year) + " "; + + if (formatTime.hour < 10) { + formattedString += "0" + std::to_string(formatTime.hour) + ":"; + } else { + formattedString += std::to_string(formatTime.hour) + ":"; + } + + if (formatTime.minute < 10) { + formattedString += "0" + std::to_string(formatTime.minute) + ":"; + } else { + formattedString += std::to_string(formatTime.minute) + ":"; + } + + if (formatTime.second < 10) { + formattedString += "0" + std::to_string(formatTime.second) + " "; + } else { + formattedString += std::to_string(formatTime.second) + " "; + } + + if (iTimeZoneMinutes == 0) { + formattedString += "+0000"; + } else { + int timeZoneHours = iTimeZoneMinutes / 60; + int timeZoneRemainder = iTimeZoneMinutes % 60; + + if (timeZoneHours < 0) { + formattedString += "-"; + timeZoneHours *= -1; + } else { + formattedString += "+"; + } + + if (timeZoneHours < 10) { + formattedString += "0" + std::to_string(timeZoneHours); + } else { + formattedString += std::to_string(timeZoneHours); + } + + if (timeZoneRemainder == 0) { + formattedString += "00"; + } else { + if (timeZoneRemainder < 0) + timeZoneRemainder *= -1; + formattedString += std::to_string(timeZoneRemainder); + } + } + + for (int i = 0; i < formattedString.size() + 1; ++i) { + pszDateTime[i] = formattedString.c_str()[i]; + } + } + + return SCE_OK; } -int PS4_SYSV_ABI sceRtcFormatRFC2822LocalTime() { - LOG_ERROR(Lib_Rtc, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceRtcFormatRFC2822LocalTime(char* pszDateTime, const OrbisRtcTick* pTickUtc) { + LOG_TRACE(Lib_Rtc, "called"); + + Kernel::OrbisKernelTimezone timeZone; + Kernel::sceKernelGettimezone(&timeZone); + + return sceRtcFormatRFC2822(pszDateTime, pTickUtc, + -(timeZone.tz_minuteswest - (timeZone.tz_dsttime * 60))); } -int PS4_SYSV_ABI sceRtcFormatRFC3339() { - LOG_ERROR(Lib_Rtc, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceRtcFormatRFC3339(char* pszDateTime, const OrbisRtcTick* pTickUtc, + int iTimeZoneMinutes) { + LOG_TRACE(Lib_Rtc, "called"); + return sceRtcFormatRFC3339Precise(pszDateTime, pTickUtc, iTimeZoneMinutes); } -int PS4_SYSV_ABI sceRtcFormatRFC3339LocalTime() { - LOG_ERROR(Lib_Rtc, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceRtcFormatRFC3339LocalTime(char* pszDateTime, const OrbisRtcTick* pTickUtc) { + LOG_TRACE(Lib_Rtc, "called"); + + Kernel::OrbisKernelTimezone timeZone; + Kernel::sceKernelGettimezone(&timeZone); + + return sceRtcFormatRFC3339(pszDateTime, pTickUtc, + -(timeZone.tz_minuteswest - (timeZone.tz_dsttime * 60))); } -int PS4_SYSV_ABI sceRtcFormatRFC3339Precise() { - LOG_ERROR(Lib_Rtc, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceRtcFormatRFC3339Precise(char* pszDateTime, const OrbisRtcTick* pTickUtc, + int iTimeZoneMinutes) { + LOG_TRACE(Lib_Rtc, "called"); + + if (pszDateTime == nullptr) + return ORBIS_RTC_ERROR_INVALID_POINTER; + + OrbisRtcTick formatTick; + + if (pTickUtc == nullptr) { + sceRtcGetCurrentTick(&formatTick); + } else { + formatTick.tick = pTickUtc->tick; + } + + sceRtcTickAddMinutes(&formatTick, &formatTick, iTimeZoneMinutes); + + OrbisRtcDateTime formatTime; + + sceRtcSetTick(&formatTime, &formatTick); + + std::string formattedString; + formattedString = std::to_string(formatTime.year) + "-"; + + if (formatTime.month < 10) { + formattedString += "0" + std::to_string(formatTime.month) + "-"; + } else { + formattedString += std::to_string(formatTime.month) + "-"; + } + + if (formatTime.day < 10) { + formattedString += "0" + std::to_string(formatTime.day) + "T"; + } else { + formattedString += std::to_string(formatTime.day) + "T"; + } + + if (formatTime.hour < 10) { + formattedString += "0" + std::to_string(formatTime.hour) + ":"; + } else { + formattedString += std::to_string(formatTime.hour) + ":"; + } + + if (formatTime.minute < 10) { + formattedString += "0" + std::to_string(formatTime.minute) + ":"; + } else { + formattedString += std::to_string(formatTime.minute) + ":"; + } + + if (formatTime.second < 10) { + formattedString += "0" + std::to_string(formatTime.second); + } else { + formattedString += std::to_string(formatTime.second); + } + + if (formatTime.microsecond != 0) { + formattedString += "." + std::to_string(formatTime.microsecond / 1000).substr(0, 2); + } else { + formattedString += ".00"; + } + + if (iTimeZoneMinutes == 0) { + formattedString += "Z"; + } else { + int timeZoneHours = iTimeZoneMinutes / 60; + int timeZoneRemainder = iTimeZoneMinutes % 60; + + if (timeZoneHours < 0) { + formattedString += "-"; + timeZoneHours *= -1; + } else { + formattedString += "+"; + } + + if (timeZoneHours < 10) { + formattedString += "0" + std::to_string(timeZoneHours); + } else { + formattedString += std::to_string(timeZoneHours); + } + + if (timeZoneRemainder == 0) { + formattedString += ":00"; + } else { + if (timeZoneRemainder < 0) + timeZoneRemainder *= -1; + formattedString += ":" + std::to_string(timeZoneRemainder); + } + } + + for (int i = 0; i < formattedString.size() + 1; ++i) { + pszDateTime[i] = formattedString.c_str()[i]; + } + + return SCE_OK; } -int PS4_SYSV_ABI sceRtcFormatRFC3339PreciseLocalTime() { - LOG_ERROR(Lib_Rtc, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceRtcFormatRFC3339PreciseLocalTime(char* pszDateTime, + const OrbisRtcTick* pTickUtc) { + LOG_TRACE(Lib_Rtc, "called"); + + Kernel::OrbisKernelTimezone timeZone; + Kernel::sceKernelGettimezone(&timeZone); + + return sceRtcFormatRFC3339Precise(pszDateTime, pTickUtc, + -(timeZone.tz_minuteswest - (timeZone.tz_dsttime * 60))); } -int PS4_SYSV_ABI sceRtcGetCurrentAdNetworkTick() { - LOG_ERROR(Lib_Rtc, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceRtcGetCurrentAdNetworkTick(OrbisRtcTick* pTick) { + LOG_TRACE(Lib_Rtc, "called"); + + if (pTick == nullptr) + return ORBIS_RTC_ERROR_INVALID_POINTER; + + Kernel::OrbisKernelTimespec clocktime; + int returnValue = Kernel::sceKernelClockGettime(Kernel::ORBIS_CLOCK_REALTIME, &clocktime); + + if (returnValue == SCE_OK) { + pTick->tick = clocktime.tv_nsec / 1000 + clocktime.tv_sec * 1000000 + UNIX_EPOCH_TICKS; + } else { + return ORBIS_RTC_ERROR_NOT_INITIALIZED; + } + + return SCE_OK; } -int PS4_SYSV_ABI sceRtcGetCurrentClock() { - LOG_ERROR(Lib_Rtc, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceRtcGetCurrentClock(OrbisRtcDateTime* pTime, int timeZone) { + LOG_TRACE(Lib_Rtc, "called"); + + if (pTime == nullptr) + return ORBIS_RTC_ERROR_DATETIME_UNINITIALIZED; + + Kernel::OrbisKernelTimespec clocktime; + int returnValue = Kernel::sceKernelClockGettime(Kernel::ORBIS_CLOCK_REALTIME, &clocktime); + + if (returnValue == SCE_OK) { + OrbisRtcTick clockTick; + clockTick.tick = clocktime.tv_nsec / 1000 + clocktime.tv_sec * 1000000 + UNIX_EPOCH_TICKS; + + sceRtcTickAddMinutes(&clockTick, &clockTick, timeZone); + sceRtcSetTick(pTime, &clockTick); + } + + return returnValue; } -int PS4_SYSV_ABI sceRtcGetCurrentClockLocalTime() { - LOG_ERROR(Lib_Rtc, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceRtcGetCurrentClockLocalTime(OrbisRtcDateTime* pTime) { + LOG_TRACE(Lib_Rtc, "called"); + + if (pTime == nullptr) + return ORBIS_RTC_ERROR_DATETIME_UNINITIALIZED; + + Kernel::OrbisKernelTimezone timeZone; + int returnValue = Kernel::sceKernelGettimezone(&timeZone); + + if (returnValue >= 0) { + Kernel::OrbisKernelTimespec clocktime; + + // calculate total timezone offset for converting UTC to local time + uint64_t tzOffset = -(timeZone.tz_minuteswest - (timeZone.tz_dsttime * 60)); + + if (returnValue >= 0) { + OrbisRtcTick newTick; + sceRtcGetCurrentTick(&newTick); + sceRtcTickAddMinutes(&newTick, &newTick, tzOffset); + sceRtcSetTick(pTime, &newTick); + } + } + + return returnValue; } -int PS4_SYSV_ABI sceRtcGetCurrentDebugNetworkTick() { - LOG_ERROR(Lib_Rtc, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceRtcGetCurrentDebugNetworkTick(OrbisRtcTick* pTick) { + LOG_TRACE(Lib_Rtc, "called"); + + if (pTick == nullptr) + return ORBIS_RTC_ERROR_INVALID_POINTER; + + Kernel::OrbisKernelTimespec clocktime; + int returnValue = Kernel::sceKernelClockGettime(Kernel::ORBIS_CLOCK_REALTIME, &clocktime); + + if (returnValue == SCE_OK) { + pTick->tick = clocktime.tv_nsec / 1000 + clocktime.tv_sec * 1000000 + UNIX_EPOCH_TICKS; + } else { + return ORBIS_RTC_ERROR_NOT_INITIALIZED; + } + + return SCE_OK; } -int PS4_SYSV_ABI sceRtcGetCurrentNetworkTick() { - LOG_ERROR(Lib_Rtc, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceRtcGetCurrentNetworkTick(OrbisRtcTick* pTick) { + LOG_TRACE(Lib_Rtc, "called"); + + if (pTick == nullptr) + return ORBIS_RTC_ERROR_INVALID_POINTER; + + Kernel::OrbisKernelTimespec clocktime; + int returnValue = Kernel::sceKernelClockGettime(Kernel::ORBIS_CLOCK_REALTIME, &clocktime); + + if (returnValue == SCE_OK) { + pTick->tick = clocktime.tv_nsec / 1000 + clocktime.tv_sec * 1000000 + UNIX_EPOCH_TICKS; + } else { + return ORBIS_RTC_ERROR_NOT_INITIALIZED; + } + + return SCE_OK; } -int PS4_SYSV_ABI sceRtcGetCurrentRawNetworkTick() { - LOG_ERROR(Lib_Rtc, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceRtcGetCurrentRawNetworkTick(OrbisRtcTick* pTick) { + LOG_TRACE(Lib_Rtc, "called"); + + if (pTick == nullptr) + return ORBIS_RTC_ERROR_INVALID_POINTER; + + Kernel::OrbisKernelTimespec clocktime; + int returnValue = Kernel::sceKernelClockGettime(Kernel::ORBIS_CLOCK_REALTIME, &clocktime); + + if (returnValue == SCE_OK) { + pTick->tick = clocktime.tv_nsec / 1000 + clocktime.tv_sec * 1000000 + UNIX_EPOCH_TICKS; + } else { + return ORBIS_RTC_ERROR_NOT_INITIALIZED; + } + + return SCE_OK; } int PS4_SYSV_ABI sceRtcGetCurrentTick(OrbisRtcTick* pTick) { - pTick->tick = std::chrono::duration_cast( - std::chrono::high_resolution_clock::now().time_since_epoch()) - .count(); - return ORBIS_OK; + LOG_TRACE(Lib_Rtc, "called"); + + if (pTick == nullptr) + return ORBIS_RTC_ERROR_DATETIME_UNINITIALIZED; + + Kernel::OrbisKernelTimespec clocktime; + int returnValue = Kernel::sceKernelClockGettime(Kernel::ORBIS_CLOCK_REALTIME, &clocktime); + + if (returnValue >= 0) { + pTick->tick = clocktime.tv_nsec / 1000 + clocktime.tv_sec * 1000000 + UNIX_EPOCH_TICKS; + } + + return SCE_OK; } -int PS4_SYSV_ABI sceRtcGetDayOfWeek() { - LOG_ERROR(Lib_Rtc, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceRtcGetDayOfWeek(int year, int month, int day) { + LOG_TRACE(Lib_Rtc, "called"); + + int sdk_version = 0; + int sdkResult = Kernel::sceKernelGetCompiledSdkVersion(&sdk_version); + if (sdkResult != ORBIS_OK) { + sdk_version = 0; + } + + if (sdk_version < 0x3000000) { + if (year < 1) { + return ORBIS_RTC_ERROR_INVALID_YEAR; + } + if (month > 12 || month <= 0) { + return ORBIS_RTC_ERROR_INVALID_MONTH; + } + } else { + if (year > 9999 || year < 1) { + return ORBIS_RTC_ERROR_INVALID_YEAR; + } + if (month > 12 || month <= 0) { + return ORBIS_RTC_ERROR_INVALID_MONTH; + } + } + + int daysInMonth = sceRtcGetDaysInMonth(year, month); + + if (day <= 0 || day > daysInMonth) + return ORBIS_RTC_ERROR_INVALID_DAY; + + std::chrono::sys_days chrono_time{std::chrono::year(year) / std::chrono::month(month) / + std::chrono::day(day)}; + std::chrono::weekday chrono_weekday{chrono_time}; + + return chrono_weekday.c_encoding(); } -int PS4_SYSV_ABI sceRtcGetDaysInMonth() { - LOG_ERROR(Lib_Rtc, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceRtcGetDaysInMonth(int year, int month) { + LOG_TRACE(Lib_Rtc, "called"); + + if (year <= 0) + return ORBIS_RTC_ERROR_INVALID_YEAR; + + if (month <= 0 || month > 12) + return ORBIS_RTC_ERROR_INVALID_MONTH; + + std::chrono::year chronoYear = std::chrono::year(year); + std::chrono::month chronoMonth = std::chrono::month(month); + int lastDay = static_cast(unsigned( + std::chrono::year_month_day_last{chronoYear / chronoMonth / std::chrono::last}.day())); + + return lastDay; } -int PS4_SYSV_ABI sceRtcGetDosTime() { - LOG_ERROR(Lib_Rtc, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceRtcGetDosTime(OrbisRtcDateTime* pTime, unsigned int* dosTime) { + LOG_TRACE(Lib_Rtc, "called"); + + if (pTime == nullptr || dosTime == nullptr) + return ORBIS_RTC_ERROR_INVALID_POINTER; + + int isValid = sceRtcCheckValid(pTime); + if (isValid != SCE_OK) { + return isValid; + } + + *dosTime |= (pTime->second / 2) & 0x1F; + *dosTime |= (pTime->minute & 0x3F) << 5; + *dosTime |= (pTime->hour & 0x1F) << 11; + *dosTime |= (pTime->day & 0x1F) << 16; + *dosTime |= (pTime->month & 0x0F) << 21; + *dosTime |= ((pTime->year - 1980) & 0x7F) << 25; + + return SCE_OK; } -int PS4_SYSV_ABI sceRtcGetTick() { - LOG_ERROR(Lib_Rtc, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceRtcGetTick(OrbisRtcDateTime* pTime, OrbisRtcTick* pTick) { + LOG_TRACE(Lib_Rtc, "called"); + + if (pTime == nullptr || pTick == nullptr) + return ORBIS_RTC_ERROR_INVALID_POINTER; + + int isTimeValid = sceRtcCheckValid(pTime); + if (isTimeValid != 0) + return isTimeValid; + + if (pTime->month > 2) { + pTime->month -= 3; + } else { + pTime->month += 9; + pTime->year -= 1; + } + + int c = pTime->year / 100; + int ya = pTime->year - 100 * c; + + u64 days; + u64 msec; + + days = ((146097 * c) >> 2) + ((1461 * ya) >> 2) + (153 * pTime->month + 2) / 5 + pTime->day; + days -= 307; + days *= 86400000000; + + msec = pTime->hour * 3600000000 + pTime->minute * 60000000 + pTime->second * 1000000 + + pTime->microsecond; + + pTick->tick = days + msec; + + return SCE_OK; } -int PS4_SYSV_ABI sceRtcGetTickResolution() { +unsigned int PS4_SYSV_ABI sceRtcGetTickResolution() { + LOG_TRACE(Lib_Rtc, "called"); + return 1000000; } -int PS4_SYSV_ABI sceRtcGetTime_t() { - LOG_ERROR(Lib_Rtc, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceRtcGetTime_t(OrbisRtcDateTime* pTime, time_t* llTime) { + LOG_TRACE(Lib_Rtc, "called"); + + if (pTime == nullptr || llTime == nullptr) + return ORBIS_RTC_ERROR_INVALID_POINTER; + + int isValid = sceRtcCheckValid(pTime); + if (isValid != SCE_OK) { + return isValid; + } + + OrbisRtcTick timeTick; + sceRtcGetTick(pTime, &timeTick); + + if (timeTick.tick < UNIX_EPOCH_TICKS) { + *llTime = 0; + } else { + *llTime = (timeTick.tick - UNIX_EPOCH_TICKS) / 1000000; + } + + return SCE_OK; } -int PS4_SYSV_ABI sceRtcGetWin32FileTime() { - LOG_ERROR(Lib_Rtc, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceRtcGetWin32FileTime(OrbisRtcDateTime* pTime, uint64_t* ulWin32Time) { + LOG_TRACE(Lib_Rtc, "called"); + + if (pTime == nullptr || ulWin32Time == nullptr) + return ORBIS_RTC_ERROR_INVALID_POINTER; + + int isValid = sceRtcCheckValid(pTime); + if (isValid != SCE_OK) { + return isValid; + } + + OrbisRtcTick timeTick; + sceRtcGetTick(pTime, &timeTick); + + if (timeTick.tick < WIN32_FILETIME_EPOCH_TICKS) { + *ulWin32Time = 0; + } else { + *ulWin32Time = (timeTick.tick - WIN32_FILETIME_EPOCH_TICKS) * 10; + } + + return SCE_OK; } int PS4_SYSV_ABI sceRtcInit() { - LOG_ERROR(Lib_Rtc, "(STUBBED) called"); - return ORBIS_OK; + return SCE_OK; } -int PS4_SYSV_ABI sceRtcIsLeapYear() { - LOG_ERROR(Lib_Rtc, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceRtcIsLeapYear(int yearInt) { + LOG_TRACE(Lib_Rtc, "called"); + + if (yearInt < 1) + return ORBIS_RTC_ERROR_INVALID_YEAR; + + using namespace std::chrono; + + year_month_day_last ymdl{year(yearInt) / February / last}; + return (ymdl.day() == 29d); } -int PS4_SYSV_ABI sceRtcParseDateTime() { - LOG_ERROR(Lib_Rtc, "(STUBBED) called"); - return ORBIS_OK; +int GetMonthFromString(std::string monthStr) { + if (monthStr == "Jan") + return 1; + + if (monthStr == "Feb") + return 2; + + if (monthStr == "Mar") + return 3; + + if (monthStr == "Apr") + return 4; + + if (monthStr == "May") + return 5; + + if (monthStr == "Jun") + return 6; + + if (monthStr == "Jul") + return 7; + + if (monthStr == "Aug") + return 8; + + if (monthStr == "Sep") + return 9; + + if (monthStr == "Oct") + return 10; + + if (monthStr == "Nov") + return 11; + + if (monthStr == "Dec") + return 12; + + return 1; } -int PS4_SYSV_ABI sceRtcParseRFC3339() { - LOG_ERROR(Lib_Rtc, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceRtcParseDateTime(OrbisRtcTick* pTickUtc, const char* pszDateTime) { + LOG_TRACE(Lib_Rtc, "called"); + + if (pTickUtc == nullptr || pszDateTime == nullptr) + return ORBIS_RTC_ERROR_INVALID_POINTER; + + std::string dateTimeString = std::string(pszDateTime); + + char formatKey = dateTimeString[22]; + OrbisRtcDateTime dateTime; + + if (formatKey == 'Z' || formatKey == '-' || formatKey == '+') { + // RFC3339 + sceRtcParseRFC3339(pTickUtc, pszDateTime); + } else if (formatKey == ':') { + // RFC2822 + dateTime.day = std::stoi(dateTimeString.substr(5, 2)); + dateTime.month = GetMonthFromString(dateTimeString.substr(8, 3)); + dateTime.year = std::stoi(dateTimeString.substr(12, 4)); + dateTime.hour = std::stoi(dateTimeString.substr(17, 2)); + dateTime.minute = std::stoi(dateTimeString.substr(20, 2)); + dateTime.second = std::stoi(dateTimeString.substr(23, 2)); + dateTime.microsecond = 0; + + sceRtcGetTick(&dateTime, pTickUtc); + + if (dateTimeString[26] == '+') { + int timeZoneOffset = std::stoi(dateTimeString.substr(27, 2)) * 60; + timeZoneOffset += std::stoi(dateTimeString.substr(29, 2)); + sceRtcTickAddMinutes(pTickUtc, pTickUtc, timeZoneOffset); + } else if (dateTimeString[26] == '-') { + int timeZoneOffset = std::stoi(dateTimeString.substr(27, 2)) * 60; + timeZoneOffset += std::stoi(dateTimeString.substr(29, 2)); + timeZoneOffset *= -1; + sceRtcTickAddMinutes(pTickUtc, pTickUtc, timeZoneOffset); + } + + } else { + // asctime + dateTime.month = GetMonthFromString(dateTimeString.substr(4, 3)); + dateTime.day = std::stoi(dateTimeString.substr(8, 2)); + dateTime.hour = std::stoi(dateTimeString.substr(11, 2)); + dateTime.minute = std::stoi(dateTimeString.substr(14, 2)); + dateTime.second = std::stoi(dateTimeString.substr(17, 2)); + dateTime.year = std::stoi(dateTimeString.substr(20, 4)); + dateTime.microsecond = 0; + + sceRtcGetTick(&dateTime, pTickUtc); + } + + return SCE_OK; +} + +int PS4_SYSV_ABI sceRtcParseRFC3339(OrbisRtcTick* pTickUtc, const char* pszDateTime) { + LOG_TRACE(Lib_Rtc, "called"); + + if (pTickUtc == nullptr || pszDateTime == nullptr) + return ORBIS_RTC_ERROR_INVALID_POINTER; + + std::string dateTimeString = std::string(pszDateTime); + + OrbisRtcDateTime dateTime; + dateTime.year = std::stoi(dateTimeString.substr(0, 4)); + dateTime.month = std::stoi(dateTimeString.substr(5, 2)); + dateTime.day = std::stoi(dateTimeString.substr(8, 2)); + dateTime.hour = std::stoi(dateTimeString.substr(11, 2)); + dateTime.minute = std::stoi(dateTimeString.substr(14, 2)); + dateTime.second = std::stoi(dateTimeString.substr(17, 2)); + dateTime.microsecond = std::stoi(dateTimeString.substr(20, 2)); + + sceRtcGetTick(&dateTime, pTickUtc); + + if (dateTimeString[22] != 'Z') { + if (dateTimeString[22] == '-') { + int timeZoneOffset = std::stoi(dateTimeString.substr(23, 2)) * 60; + timeZoneOffset += std::stoi(dateTimeString.substr(26, 2)); + timeZoneOffset *= -1; + sceRtcTickAddMinutes(pTickUtc, pTickUtc, timeZoneOffset); + } else if (dateTimeString[22] == '+') { + int timeZoneOffset = std::stoi(dateTimeString.substr(23, 2)) * 60; + timeZoneOffset += std::stoi(dateTimeString.substr(26, 2)); + sceRtcTickAddMinutes(pTickUtc, pTickUtc, timeZoneOffset); + } + } + + return SCE_OK; } int PS4_SYSV_ABI sceRtcSetConf() { @@ -162,89 +833,294 @@ int PS4_SYSV_ABI sceRtcSetConf() { return ORBIS_OK; } -int PS4_SYSV_ABI sceRtcSetCurrentAdNetworkTick() { +int PS4_SYSV_ABI sceRtcSetCurrentAdNetworkTick(OrbisRtcTick* pTick) { LOG_ERROR(Lib_Rtc, "(STUBBED) called"); return ORBIS_OK; } -int PS4_SYSV_ABI sceRtcSetCurrentDebugNetworkTick() { +int PS4_SYSV_ABI sceRtcSetCurrentDebugNetworkTick(OrbisRtcTick* pTick) { LOG_ERROR(Lib_Rtc, "(STUBBED) called"); return ORBIS_OK; } -int PS4_SYSV_ABI sceRtcSetCurrentNetworkTick() { +int PS4_SYSV_ABI sceRtcSetCurrentNetworkTick(OrbisRtcTick* pTick) { LOG_ERROR(Lib_Rtc, "(STUBBED) called"); return ORBIS_OK; } -int PS4_SYSV_ABI sceRtcSetCurrentTick() { +int PS4_SYSV_ABI sceRtcSetCurrentTick(OrbisRtcTick* pTick) { LOG_ERROR(Lib_Rtc, "(STUBBED) called"); return ORBIS_OK; } -int PS4_SYSV_ABI sceRtcSetDosTime() { - LOG_ERROR(Lib_Rtc, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceRtcSetDosTime(OrbisRtcDateTime* pTime, u32 dosTime) { + LOG_TRACE(Lib_Rtc, "called"); + + if (pTime == nullptr) + return ORBIS_RTC_ERROR_INVALID_POINTER; + + pTime->microsecond = 0; + pTime->second = (dosTime << 1) & 0x3e; + pTime->minute = (dosTime >> 5) & 0x3f; + pTime->hour = (dosTime & 0xf800) >> 0xb; + + int16_t days = dosTime >> 0x10; + + pTime->day = days & 0x1f; + pTime->month = (days >> 5) & 0x0f; + pTime->year = (days >> 9) + 1980; + return SCE_OK; } -int PS4_SYSV_ABI sceRtcSetTick() { - LOG_ERROR(Lib_Rtc, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceRtcSetTick(OrbisRtcDateTime* pTime, OrbisRtcTick* pTick) { + LOG_TRACE(Lib_Rtc, "called"); + + if (pTime == nullptr || pTick == nullptr) + return ORBIS_RTC_ERROR_INVALID_POINTER; + + u32 ly, ld, lm, j; + u64 days, msec; + + days = pTick->tick / 86400000000; + msec = pTick->tick % 86400000000; + + days += 307; + + j = (days << 2) - 1; + ly = j / 146097; + + j -= (146097 * ly); + ld = j >> 2; + + j = ((ld << 2) + 3) / 1461; + ld = (((ld << 2) + 7) - 1461 * j) >> 2; + + lm = (5 * ld - 3) / 153; + ld = (5 * ld + 2 - 153 * lm) / 5; + ly = 100 * ly + j; + + if (lm < 10) { + lm += 3; + } else { + lm -= 9; + ly++; + } + + pTime->year = ly; + pTime->month = lm; + pTime->day = ld; + + pTime->hour = msec / 3600000000; + msec %= 3600000000; + pTime->minute = msec / 60000000; + msec %= 60000000; + pTime->second = msec / 1000000; + msec %= 1000000; + pTime->microsecond = msec; + + return SCE_OK; } -int PS4_SYSV_ABI sceRtcSetTime_t() { - LOG_ERROR(Lib_Rtc, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceRtcSetTime_t(OrbisRtcDateTime* pTime, time_t llTime) { + LOG_TRACE(Lib_Rtc, "called"); + + if (pTime == nullptr) + return ORBIS_RTC_ERROR_INVALID_POINTER; + + int sdk_version; + int sdkResult = Kernel::sceKernelGetCompiledSdkVersion(&sdk_version); + if (sdkResult != ORBIS_OK) { + sdk_version = 0; + } + + OrbisRtcTick newTick; + if (sdk_version < 0x3000000) { + newTick.tick = (llTime & 0xffffffff) * 1000000; + } else { + if (llTime < 0) { + return ORBIS_RTC_ERROR_INVALID_VALUE; + } + newTick.tick = llTime * 1000000; + } + + newTick.tick += UNIX_EPOCH_TICKS; + sceRtcSetTick(pTime, &newTick); + + return SCE_OK; } -int PS4_SYSV_ABI sceRtcSetWin32FileTime() { - LOG_ERROR(Lib_Rtc, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceRtcSetWin32FileTime(OrbisRtcDateTime* pTime, int64_t ulWin32Time) { + LOG_TRACE(Lib_Rtc, "called"); + + if (pTime == nullptr) + return ORBIS_RTC_ERROR_INVALID_POINTER; + + u64 convertedTime = (ulWin32Time / 10) + WIN32_FILETIME_EPOCH_TICKS; + + OrbisRtcTick convertedTick; + convertedTick.tick = convertedTime; + + sceRtcSetTick(pTime, &convertedTick); + + return SCE_OK; } -int PS4_SYSV_ABI sceRtcTickAddDays() { - LOG_ERROR(Lib_Rtc, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceRtcTickAddDays(OrbisRtcTick* pTick1, OrbisRtcTick* pTick2, int32_t lAdd) { + LOG_TRACE(Lib_Rtc, "called"); + + if (pTick1 == nullptr || pTick2 == nullptr) + return ORBIS_RTC_ERROR_INVALID_POINTER; + + pTick1->tick = (lAdd * 86400000000) + pTick2->tick; + + return SCE_OK; } -int PS4_SYSV_ABI sceRtcTickAddHours() { - LOG_ERROR(Lib_Rtc, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceRtcTickAddHours(OrbisRtcTick* pTick1, OrbisRtcTick* pTick2, int32_t lAdd) { + LOG_TRACE(Lib_Rtc, "called"); + + if (pTick1 == nullptr || pTick2 == nullptr) + return ORBIS_RTC_ERROR_INVALID_POINTER; + + pTick1->tick = (lAdd * 3600000000) + pTick2->tick; + + return SCE_OK; } -int PS4_SYSV_ABI sceRtcTickAddMicroseconds() { - LOG_ERROR(Lib_Rtc, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceRtcTickAddMicroseconds(OrbisRtcTick* pTick1, OrbisRtcTick* pTick2, + int64_t lAdd) { + LOG_TRACE(Lib_Rtc, "called"); + + if (pTick1 == nullptr || pTick2 == nullptr) + return ORBIS_RTC_ERROR_INVALID_POINTER; + + pTick1->tick = lAdd + pTick2->tick; + + return SCE_OK; } -int PS4_SYSV_ABI sceRtcTickAddMinutes() { - LOG_ERROR(Lib_Rtc, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceRtcTickAddMinutes(OrbisRtcTick* pTick1, OrbisRtcTick* pTick2, int64_t lAdd) { + LOG_TRACE(Lib_Rtc, "called"); + + if (pTick1 == nullptr || pTick2 == nullptr) + return ORBIS_RTC_ERROR_INVALID_POINTER; + + pTick1->tick = (lAdd * 60000000) + pTick2->tick; + + return SCE_OK; } -int PS4_SYSV_ABI sceRtcTickAddMonths() { - LOG_ERROR(Lib_Rtc, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceRtcTickAddMonths(OrbisRtcTick* pTick1, OrbisRtcTick* pTick2, int32_t lAdd) { + LOG_TRACE(Lib_Rtc, "called"); + + if (pTick1 == nullptr || pTick2 == nullptr) + return ORBIS_RTC_ERROR_INVALID_POINTER; + + if (lAdd == 0) { + pTick1->tick = pTick2->tick; + return SCE_OK; + } + + OrbisRtcDateTime time; + s64 s; + s64 tempMonth; + + sceRtcSetTick(&time, pTick1); + + if (lAdd >= 0) { + s = 1; + } else { + s = -1; + } + + time.year += (lAdd / 12); + tempMonth = time.month + (lAdd % 12) - 1; + + if (tempMonth > 11 || tempMonth < 0) { + tempMonth -= s * 12; + time.year += s; + } + + time.month = tempMonth + 1; + + using namespace std::chrono; + year chronoYear = year(time.year); + month chronoMonth = month(time.month); + int lastDay = + static_cast(unsigned(year_month_day_last{chronoYear / chronoMonth / last}.day())); + + if (time.day > lastDay) { + time.day = lastDay; + } + + int timeIsValid = sceRtcCheckValid(&time); + if (timeIsValid == SCE_OK) { + sceRtcGetTick(&time, pTick1); + } else { + return timeIsValid; + } + + return SCE_OK; } -int PS4_SYSV_ABI sceRtcTickAddSeconds() { - LOG_ERROR(Lib_Rtc, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceRtcTickAddSeconds(OrbisRtcTick* pTick1, OrbisRtcTick* pTick2, int64_t lAdd) { + LOG_TRACE(Lib_Rtc, "called"); + + if (pTick1 == nullptr || pTick2 == nullptr) + return ORBIS_RTC_ERROR_INVALID_POINTER; + + pTick1->tick = (lAdd * 1000000) + pTick2->tick; + + return SCE_OK; } -int PS4_SYSV_ABI sceRtcTickAddTicks() { - LOG_ERROR(Lib_Rtc, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceRtcTickAddTicks(OrbisRtcTick* pTick1, OrbisRtcTick* pTick2, int64_t lAdd) { + LOG_TRACE(Lib_Rtc, "called"); + + if (pTick1 == nullptr || pTick2 == nullptr) + return ORBIS_RTC_ERROR_INVALID_POINTER; + + pTick1->tick = lAdd + pTick2->tick; + + return SCE_OK; } -int PS4_SYSV_ABI sceRtcTickAddWeeks() { - LOG_ERROR(Lib_Rtc, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceRtcTickAddWeeks(OrbisRtcTick* pTick1, OrbisRtcTick* pTick2, int32_t lAdd) { + LOG_TRACE(Lib_Rtc, "called"); + + if (pTick1 == nullptr || pTick2 == nullptr) + return ORBIS_RTC_ERROR_INVALID_POINTER; + + pTick1->tick = (lAdd * 604800000000) + pTick2->tick; + + return SCE_OK; } -int PS4_SYSV_ABI sceRtcTickAddYears() { - LOG_ERROR(Lib_Rtc, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceRtcTickAddYears(OrbisRtcTick* pTick1, OrbisRtcTick* pTick2, int32_t lAdd) { + LOG_TRACE(Lib_Rtc, "called"); + + if (pTick1 == nullptr || pTick2 == nullptr) + return ORBIS_RTC_ERROR_INVALID_POINTER; + + OrbisRtcDateTime time; + + if (lAdd == 0) { + pTick1->tick = pTick2->tick; + return SCE_OK; + } + + sceRtcSetTick(&time, pTick1); + + time.year += lAdd; + + int timeIsValid = sceRtcCheckValid(&time); + if (timeIsValid == SCE_OK) { + sceRtcGetTick(&time, pTick1); + } else { + return timeIsValid; + } + + return SCE_OK; } void RegisterlibSceRtc(Core::Loader::SymbolsResolver* sym) { diff --git a/src/core/libraries/rtc/rtc.h b/src/core/libraries/rtc/rtc.h index ee6afa70e97..c41040863fa 100644 --- a/src/core/libraries/rtc/rtc.h +++ b/src/core/libraries/rtc/rtc.h @@ -11,57 +11,81 @@ class SymbolsResolver; namespace Libraries::Rtc { +constexpr int ORBIS_RTC_DAYOFWEEK_SUNDAY = 0; +constexpr int ORBIS_RTC_DAYOFWEEK_MONDAY = 1; +constexpr int ORBIS_RTC_DAYOFWEEK_TUESDAY = 2; +constexpr int ORBIS_RTC_DAYOFWEEK_WEDNESDAY = 3; +constexpr int ORBIS_RTC_DAYOFWEEK_THURSDAY = 4; +constexpr int ORBIS_RTC_DAYOFWEEK_FRIDAY = 5; +constexpr int ORBIS_RTC_DAYOFWEEK_SATURDAY = 6; + +constexpr int64_t UNIX_EPOCH_TICKS = 0xdcbffeff2bc000; +constexpr int64_t WIN32_FILETIME_EPOCH_TICKS = 0xb36168b6a58000; + struct OrbisRtcTick { - u64 tick; + uint64_t tick; +}; + +struct OrbisRtcDateTime { + uint16_t year; + uint16_t month; + uint16_t day; + uint16_t hour; + uint16_t minute; + uint16_t second; + uint32_t microsecond; }; -int PS4_SYSV_ABI sceRtcCheckValid(); -int PS4_SYSV_ABI sceRtcCompareTick(); -int PS4_SYSV_ABI sceRtcConvertLocalTimeToUtc(); -int PS4_SYSV_ABI sceRtcConvertUtcToLocalTime(); +int PS4_SYSV_ABI sceRtcCheckValid(OrbisRtcDateTime* pTime); +int PS4_SYSV_ABI sceRtcCompareTick(OrbisRtcTick* pTick1, OrbisRtcTick* pTick2); +int PS4_SYSV_ABI sceRtcConvertLocalTimeToUtc(OrbisRtcTick* pTickLocal, OrbisRtcTick* pTickUtc); +int PS4_SYSV_ABI sceRtcConvertUtcToLocalTime(OrbisRtcTick* pTickUtc, OrbisRtcTick* pTickLocal); int PS4_SYSV_ABI sceRtcEnd(); -int PS4_SYSV_ABI sceRtcFormatRFC2822(); -int PS4_SYSV_ABI sceRtcFormatRFC2822LocalTime(); -int PS4_SYSV_ABI sceRtcFormatRFC3339(); -int PS4_SYSV_ABI sceRtcFormatRFC3339LocalTime(); -int PS4_SYSV_ABI sceRtcFormatRFC3339Precise(); -int PS4_SYSV_ABI sceRtcFormatRFC3339PreciseLocalTime(); -int PS4_SYSV_ABI sceRtcGetCurrentAdNetworkTick(); -int PS4_SYSV_ABI sceRtcGetCurrentClock(); -int PS4_SYSV_ABI sceRtcGetCurrentClockLocalTime(); -int PS4_SYSV_ABI sceRtcGetCurrentDebugNetworkTick(); -int PS4_SYSV_ABI sceRtcGetCurrentNetworkTick(); -int PS4_SYSV_ABI sceRtcGetCurrentRawNetworkTick(); +int PS4_SYSV_ABI sceRtcFormatRFC2822(char* pszDateTime, const OrbisRtcTick* pTickUtc, int minutes); +int PS4_SYSV_ABI sceRtcFormatRFC2822LocalTime(char* pszDateTime, const OrbisRtcTick* pTickUtc); +int PS4_SYSV_ABI sceRtcFormatRFC3339(char* pszDateTime, const OrbisRtcTick* pTickUtc, int minutes); +int PS4_SYSV_ABI sceRtcFormatRFC3339LocalTime(char* pszDateTime, const OrbisRtcTick* pTickUtc); +int PS4_SYSV_ABI sceRtcFormatRFC3339Precise(char* pszDateTime, const OrbisRtcTick* pTickUtc, + int minutes); +int PS4_SYSV_ABI sceRtcFormatRFC3339PreciseLocalTime(char* pszDateTime, + const OrbisRtcTick* pTickUtc); +int PS4_SYSV_ABI sceRtcGetCurrentAdNetworkTick(OrbisRtcTick* pTick); +int PS4_SYSV_ABI sceRtcGetCurrentClock(OrbisRtcDateTime* pTime, int timeZone); +int PS4_SYSV_ABI sceRtcGetCurrentClockLocalTime(OrbisRtcDateTime* pTime); +int PS4_SYSV_ABI sceRtcGetCurrentDebugNetworkTick(OrbisRtcTick* pTick); +int PS4_SYSV_ABI sceRtcGetCurrentNetworkTick(OrbisRtcTick* pTick); +int PS4_SYSV_ABI sceRtcGetCurrentRawNetworkTick(OrbisRtcTick* pTick); int PS4_SYSV_ABI sceRtcGetCurrentTick(OrbisRtcTick* pTick); -int PS4_SYSV_ABI sceRtcGetDayOfWeek(); -int PS4_SYSV_ABI sceRtcGetDaysInMonth(); -int PS4_SYSV_ABI sceRtcGetDosTime(); -int PS4_SYSV_ABI sceRtcGetTick(); -int PS4_SYSV_ABI sceRtcGetTickResolution(); -int PS4_SYSV_ABI sceRtcGetTime_t(); -int PS4_SYSV_ABI sceRtcGetWin32FileTime(); +int PS4_SYSV_ABI sceRtcGetDayOfWeek(int year, int month, int day); +int PS4_SYSV_ABI sceRtcGetDaysInMonth(int year, int month); +int PS4_SYSV_ABI sceRtcGetDosTime(OrbisRtcDateTime* pTime, unsigned int* dosTime); +int PS4_SYSV_ABI sceRtcGetTick(OrbisRtcDateTime* pTime, OrbisRtcTick* pTick); +unsigned int PS4_SYSV_ABI sceRtcGetTickResolution(); +int PS4_SYSV_ABI sceRtcGetTime_t(OrbisRtcDateTime* pTime, time_t* llTime); +int PS4_SYSV_ABI sceRtcGetWin32FileTime(OrbisRtcDateTime* pTime, uint64_t* ulWin32Time); int PS4_SYSV_ABI sceRtcInit(); -int PS4_SYSV_ABI sceRtcIsLeapYear(); -int PS4_SYSV_ABI sceRtcParseDateTime(); -int PS4_SYSV_ABI sceRtcParseRFC3339(); +int PS4_SYSV_ABI sceRtcIsLeapYear(int yearInt); +int PS4_SYSV_ABI sceRtcParseDateTime(OrbisRtcTick* pTickUtc, const char* pszDateTime); +int PS4_SYSV_ABI sceRtcParseRFC3339(OrbisRtcTick* pTickUtc, const char* pszDateTime); int PS4_SYSV_ABI sceRtcSetConf(); -int PS4_SYSV_ABI sceRtcSetCurrentAdNetworkTick(); -int PS4_SYSV_ABI sceRtcSetCurrentDebugNetworkTick(); -int PS4_SYSV_ABI sceRtcSetCurrentNetworkTick(); -int PS4_SYSV_ABI sceRtcSetCurrentTick(); -int PS4_SYSV_ABI sceRtcSetDosTime(); -int PS4_SYSV_ABI sceRtcSetTick(); -int PS4_SYSV_ABI sceRtcSetTime_t(); -int PS4_SYSV_ABI sceRtcSetWin32FileTime(); -int PS4_SYSV_ABI sceRtcTickAddDays(); -int PS4_SYSV_ABI sceRtcTickAddHours(); -int PS4_SYSV_ABI sceRtcTickAddMicroseconds(); -int PS4_SYSV_ABI sceRtcTickAddMinutes(); -int PS4_SYSV_ABI sceRtcTickAddMonths(); -int PS4_SYSV_ABI sceRtcTickAddSeconds(); -int PS4_SYSV_ABI sceRtcTickAddTicks(); -int PS4_SYSV_ABI sceRtcTickAddWeeks(); -int PS4_SYSV_ABI sceRtcTickAddYears(); +int PS4_SYSV_ABI sceRtcSetCurrentAdNetworkTick(OrbisRtcTick* pTick); +int PS4_SYSV_ABI sceRtcSetCurrentDebugNetworkTick(OrbisRtcTick* pTick); +int PS4_SYSV_ABI sceRtcSetCurrentNetworkTick(OrbisRtcTick* pTick); +int PS4_SYSV_ABI sceRtcSetCurrentTick(OrbisRtcTick* pTick); +int PS4_SYSV_ABI sceRtcSetDosTime(OrbisRtcDateTime* pTime, u32 dosTime); +int PS4_SYSV_ABI sceRtcSetTick(OrbisRtcDateTime* pTime, OrbisRtcTick* pTick); +int PS4_SYSV_ABI sceRtcSetTime_t(OrbisRtcDateTime* pTime, time_t llTime); +int PS4_SYSV_ABI sceRtcSetWin32FileTime(OrbisRtcDateTime* pTime, int64_t ulWin32Time); +int PS4_SYSV_ABI sceRtcTickAddDays(OrbisRtcTick* pTick1, OrbisRtcTick* pTick2, int32_t lAdd); +int PS4_SYSV_ABI sceRtcTickAddHours(OrbisRtcTick* pTick1, OrbisRtcTick* pTick2, int32_t lAdd); +int PS4_SYSV_ABI sceRtcTickAddMicroseconds(OrbisRtcTick* pTick1, OrbisRtcTick* pTick2, + int64_t lAdd); +int PS4_SYSV_ABI sceRtcTickAddMinutes(OrbisRtcTick* pTick1, OrbisRtcTick* pTick2, int64_t lAdd); +int PS4_SYSV_ABI sceRtcTickAddMonths(OrbisRtcTick* pTick1, OrbisRtcTick* pTick2, int32_t lAdd); +int PS4_SYSV_ABI sceRtcTickAddSeconds(OrbisRtcTick* pTick1, OrbisRtcTick* pTick2, int64_t lAdd); +int PS4_SYSV_ABI sceRtcTickAddTicks(OrbisRtcTick* pTick1, OrbisRtcTick* pTick2, int64_t lAdd); +int PS4_SYSV_ABI sceRtcTickAddWeeks(OrbisRtcTick* pTick1, OrbisRtcTick* pTick2, int32_t lAdd); +int PS4_SYSV_ABI sceRtcTickAddYears(OrbisRtcTick* pTick1, OrbisRtcTick* pTick2, int32_t lAdd); void RegisterlibSceRtc(Core::Loader::SymbolsResolver* sym); } // namespace Libraries::Rtc \ No newline at end of file diff --git a/src/core/libraries/rtc/rtc_error.h b/src/core/libraries/rtc/rtc_error.h index 04eecbbdfdb..3af5a68fdd7 100644 --- a/src/core/libraries/rtc/rtc_error.h +++ b/src/core/libraries/rtc/rtc_error.h @@ -3,6 +3,7 @@ #pragma once +constexpr int ORBIS_RTC_ERROR_DATETIME_UNINITIALIZED = 0x7FFEF9FE; constexpr int ORBIS_RTC_ERROR_INVALID_PARAMETER = 0x80010602; constexpr int ORBIS_RTC_ERROR_INVALID_TICK_PARAMETER = 0x80010603; constexpr int ORBIS_RTC_ERROR_INVALID_DATE_PARAMETER = 0x80010604; @@ -14,4 +15,18 @@ constexpr int ORBIS_RTC_ERROR_INVALID_DAYS_PARAMETER = 0x80010623; constexpr int ORBIS_RTC_ERROR_INVALID_HOURS_PARAMETER = 0x80010624; constexpr int ORBIS_RTC_ERROR_INVALID_MINUTES_PARAMETER = 0x80010625; constexpr int ORBIS_RTC_ERROR_INVALID_SECONDS_PARAMETER = 0x80010626; -constexpr int ORBIS_RTC_ERROR_INVALID_MILLISECONDS_PARAMETER = 0x80010627; \ No newline at end of file +constexpr int ORBIS_RTC_ERROR_INVALID_MILLISECONDS_PARAMETER = 0x80010627; +constexpr int ORBIS_RTC_ERROR_NOT_INITIALIZED = 0x80B50001; +constexpr int ORBIS_RTC_ERROR_INVALID_POINTER = 0x80B50002; +constexpr int ORBIS_RTC_ERROR_INVALID_VALUE = 0x80B50003; +constexpr int ORBIS_RTC_ERROR_INVALID_ARG = 0x80B50004; +constexpr int ORBIS_RTC_ERROR_NOT_SUPPORTED = 0x80B50005; +constexpr int ORBIS_RTC_ERROR_NO_CLOCK = 0x80B50006; +constexpr int ORBIS_RTC_ERROR_BAD_PARSE = 0x80B50007; +constexpr int ORBIS_RTC_ERROR_INVALID_YEAR = 0x80B50008; +constexpr int ORBIS_RTC_ERROR_INVALID_MONTH = 0x80B50009; +constexpr int ORBIS_RTC_ERROR_INVALID_DAY = 0x80B5000A; +constexpr int ORBIS_RTC_ERROR_INVALID_HOUR = 0x80B5000B; +constexpr int ORBIS_RTC_ERROR_INVALID_MINUTE = 0x80B5000C; +constexpr int ORBIS_RTC_ERROR_INVALID_SECOND = 0x80B5000D; +constexpr int ORBIS_RTC_ERROR_INVALID_MICROSECOND = 0x80B5000E; \ No newline at end of file diff --git a/src/core/libraries/save_data/dialog/savedatadialog.cpp b/src/core/libraries/save_data/dialog/savedatadialog.cpp new file mode 100644 index 00000000000..0ad7d7dc08f --- /dev/null +++ b/src/core/libraries/save_data/dialog/savedatadialog.cpp @@ -0,0 +1,164 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/elf_info.h" +#include "common/logging/log.h" +#include "core/libraries/libs.h" +#include "core/libraries/system/commondialog.h" +#include "magic_enum.hpp" +#include "savedatadialog.h" +#include "savedatadialog_ui.h" + +namespace Libraries::SaveData::Dialog { + +using CommonDialog::Error; +using CommonDialog::Result; +using CommonDialog::Status; + +static auto g_status = Status::NONE; +static SaveDialogState g_state{}; +static SaveDialogResult g_result{}; +static SaveDialogUi g_save_dialog_ui; + +Error PS4_SYSV_ABI sceSaveDataDialogClose() { + LOG_DEBUG(Lib_SaveDataDialog, "called"); + if (g_status != Status::RUNNING) { + return Error::NOT_RUNNING; + } + g_save_dialog_ui.Finish(ButtonId::INVALID); + g_save_dialog_ui = SaveDialogUi{}; + return Error::OK; +} + +Error PS4_SYSV_ABI sceSaveDataDialogGetResult(OrbisSaveDataDialogResult* result) { + LOG_DEBUG(Lib_SaveDataDialog, "called"); + if (g_status != Status::FINISHED) { + return Error::NOT_FINISHED; + } + if (result == nullptr) { + return Error::ARG_NULL; + } + g_result.CopyTo(*result); + return Error::OK; +} + +Status PS4_SYSV_ABI sceSaveDataDialogGetStatus() { + LOG_TRACE(Lib_SaveDataDialog, "called status={}", magic_enum::enum_name(g_status)); + return g_status; +} + +Error PS4_SYSV_ABI sceSaveDataDialogInitialize() { + LOG_DEBUG(Lib_SaveDataDialog, "called"); + if (!CommonDialog::g_isInitialized) { + return Error::NOT_SYSTEM_INITIALIZED; + } + if (g_status != Status::NONE) { + return Error::ALREADY_INITIALIZED; + } + if (CommonDialog::g_isUsed) { + return Error::BUSY; + } + g_status = Status::INITIALIZED; + CommonDialog::g_isUsed = true; + + return Error::OK; +} + +s32 PS4_SYSV_ABI sceSaveDataDialogIsReadyToDisplay() { + return 1; +} + +Error PS4_SYSV_ABI sceSaveDataDialogOpen(const OrbisSaveDataDialogParam* param) { + if (g_status != Status::INITIALIZED && g_status != Status::FINISHED) { + LOG_INFO(Lib_SaveDataDialog, "called without initialize"); + return Error::INVALID_STATE; + } + if (param == nullptr) { + LOG_DEBUG(Lib_SaveDataDialog, "called param:(NULL)"); + return Error::ARG_NULL; + } + LOG_DEBUG(Lib_SaveDataDialog, "called param->mode: {}", magic_enum::enum_name(param->mode)); + ASSERT(param->size == sizeof(OrbisSaveDataDialogParam)); + ASSERT(param->baseParam.size == sizeof(CommonDialog::BaseParam)); + g_result = {}; + g_state = SaveDialogState{*param}; + g_status = Status::RUNNING; + g_save_dialog_ui = SaveDialogUi(&g_state, &g_status, &g_result); + return Error::OK; +} + +Error PS4_SYSV_ABI sceSaveDataDialogProgressBarInc(OrbisSaveDataDialogProgressBarTarget target, + u32 delta) { + LOG_DEBUG(Lib_SaveDataDialog, "called"); + if (g_status != Status::RUNNING) { + return Error::NOT_RUNNING; + } + if (g_state.GetMode() != SaveDataDialogMode::PROGRESS_BAR) { + return Error::NOT_SUPPORTED; + } + if (target != OrbisSaveDataDialogProgressBarTarget::DEFAULT) { + return Error::PARAM_INVALID; + } + g_state.GetState().progress += delta; + return Error::OK; +} + +Error PS4_SYSV_ABI sceSaveDataDialogProgressBarSetValue(OrbisSaveDataDialogProgressBarTarget target, + u32 rate) { + LOG_DEBUG(Lib_SaveDataDialog, "called"); + if (g_status != Status::RUNNING) { + return Error::NOT_RUNNING; + } + if (g_state.GetMode() != SaveDataDialogMode::PROGRESS_BAR) { + return Error::NOT_SUPPORTED; + } + if (target != OrbisSaveDataDialogProgressBarTarget::DEFAULT) { + return Error::PARAM_INVALID; + } + g_state.GetState().progress = rate; + return Error::OK; +} + +Error PS4_SYSV_ABI sceSaveDataDialogTerminate() { + LOG_DEBUG(Lib_SaveDataDialog, "called"); + if (g_status == Status::RUNNING) { + sceSaveDataDialogClose(); + } + if (g_status == Status::NONE) { + return Error::NOT_INITIALIZED; + } + g_save_dialog_ui = SaveDialogUi{}; + g_status = Status::NONE; + CommonDialog::g_isUsed = false; + return Error::OK; +} + +Status PS4_SYSV_ABI sceSaveDataDialogUpdateStatus() { + LOG_TRACE(Lib_SaveDataDialog, "called status={}", magic_enum::enum_name(g_status)); + return g_status; +} + +void RegisterlibSceSaveDataDialog(Core::Loader::SymbolsResolver* sym) { + LIB_FUNCTION("fH46Lag88XY", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1, + sceSaveDataDialogClose); + LIB_FUNCTION("yEiJ-qqr6Cg", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1, + sceSaveDataDialogGetResult); + LIB_FUNCTION("ERKzksauAJA", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1, + sceSaveDataDialogGetStatus); + LIB_FUNCTION("s9e3+YpRnzw", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1, + sceSaveDataDialogInitialize); + LIB_FUNCTION("en7gNVnh878", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1, + sceSaveDataDialogIsReadyToDisplay); + LIB_FUNCTION("4tPhsP6FpDI", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1, + sceSaveDataDialogOpen); + LIB_FUNCTION("V-uEeFKARJU", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1, + sceSaveDataDialogProgressBarInc); + LIB_FUNCTION("hay1CfTmLyA", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1, + sceSaveDataDialogProgressBarSetValue); + LIB_FUNCTION("YuH2FA7azqQ", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1, + sceSaveDataDialogTerminate); + LIB_FUNCTION("KK3Bdg1RWK0", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1, + sceSaveDataDialogUpdateStatus); +}; + +} // namespace Libraries::SaveData::Dialog diff --git a/src/core/libraries/save_data/dialog/savedatadialog.h b/src/core/libraries/save_data/dialog/savedatadialog.h new file mode 100644 index 00000000000..34afe98a746 --- /dev/null +++ b/src/core/libraries/save_data/dialog/savedatadialog.h @@ -0,0 +1,33 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/types.h" +#include "core/libraries/system/commondialog.h" + +namespace Core::Loader { +class SymbolsResolver; +} + +namespace Libraries::SaveData::Dialog { + +struct OrbisSaveDataDialogParam; +struct OrbisSaveDataDialogResult; +enum class OrbisSaveDataDialogProgressBarTarget : u32; + +CommonDialog::Error PS4_SYSV_ABI sceSaveDataDialogClose(); +CommonDialog::Error PS4_SYSV_ABI sceSaveDataDialogGetResult(OrbisSaveDataDialogResult* result); +CommonDialog::Status PS4_SYSV_ABI sceSaveDataDialogGetStatus(); +CommonDialog::Error PS4_SYSV_ABI sceSaveDataDialogInitialize(); +s32 PS4_SYSV_ABI sceSaveDataDialogIsReadyToDisplay(); +CommonDialog::Error PS4_SYSV_ABI sceSaveDataDialogOpen(const OrbisSaveDataDialogParam* param); +CommonDialog::Error PS4_SYSV_ABI +sceSaveDataDialogProgressBarInc(OrbisSaveDataDialogProgressBarTarget target, u32 delta); +CommonDialog::Error PS4_SYSV_ABI +sceSaveDataDialogProgressBarSetValue(OrbisSaveDataDialogProgressBarTarget target, u32 rate); +CommonDialog::Error PS4_SYSV_ABI sceSaveDataDialogTerminate(); +CommonDialog::Status PS4_SYSV_ABI sceSaveDataDialogUpdateStatus(); + +void RegisterlibSceSaveDataDialog(Core::Loader::SymbolsResolver* sym); +} // namespace Libraries::SaveData::Dialog diff --git a/src/core/libraries/save_data/dialog/savedatadialog_ui.cpp b/src/core/libraries/save_data/dialog/savedatadialog_ui.cpp new file mode 100644 index 00000000000..793b4dd38f1 --- /dev/null +++ b/src/core/libraries/save_data/dialog/savedatadialog_ui.cpp @@ -0,0 +1,849 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include + +#include "common/elf_info.h" +#include "common/singleton.h" +#include "common/string_util.h" +#include "core/file_sys/fs.h" +#include "core/libraries/save_data/save_instance.h" +#include "imgui/imgui_std.h" +#include "savedatadialog_ui.h" + +using namespace ImGui; +using namespace Libraries::CommonDialog; +using Common::ElfInfo; + +constexpr u32 OrbisSaveDataBlockSize = 32768; // 32 KiB + +constexpr auto SAVE_ICON_SIZE = ImVec2{152.0f, 85.0f}; +constexpr auto SAVE_ICON_PADDING = ImVec2{8.0f, 2.0f}; + +static constexpr ImVec2 BUTTON_SIZE{100.0f, 30.0f}; +constexpr auto FOOTER_HEIGHT = BUTTON_SIZE.y + 15.0f; +static constexpr float PROGRESS_BAR_WIDTH{0.8f}; + +static ::Core::FileSys::MntPoints* g_mnt = + Common::Singleton<::Core::FileSys::MntPoints>::Instance(); + +static std::string SpaceSizeToString(size_t size) { + std::string size_str; + if (size > 1024 * 1024 * 1024) { // > 1GB + size_str = fmt::format("{:.2f} GB", double(size / 1024 / 1024) / 1024.0f); + } else if (size > 1024 * 1024) { // > 1MB + size_str = fmt::format("{:.2f} MB", double(size / 1024) / 1024.0f); + } else if (size > 1024) { // > 1KB + size_str = fmt::format("{:.2f} KB", double(size) / 1024.0f); + } else { + size_str = fmt::format("{} B", size); + } + return size_str; +} + +namespace Libraries::SaveData::Dialog { + +void SaveDialogResult::CopyTo(OrbisSaveDataDialogResult& result) const { + result.mode = this->mode; + result.result = this->result; + result.buttonId = this->button_id; + if (mode == SaveDataDialogMode::LIST || ElfInfo::Instance().FirmwareVer() >= ElfInfo::FW_45) { + if (result.dirName != nullptr) { + result.dirName->data.FromString(this->dir_name); + } + if (result.param != nullptr && this->param.GetString(SaveParams::MAINTITLE).has_value()) { + result.param->FromSFO(this->param); + } + } + result.userData = this->user_data; +} + +SaveDialogState::SaveDialogState(const OrbisSaveDataDialogParam& param) { + this->mode = param.mode; + this->type = param.dispType; + this->user_data = param.userData; + if (param.optionParam != nullptr) { + this->enable_back = {param.optionParam->back == OptionBack::ENABLE}; + } + + const auto& game_serial = Common::ElfInfo::Instance().GameSerial(); + + const auto item = param.items; + this->user_id = item->userId; + + if (item->titleId == nullptr) { + this->title_id = game_serial; + } else { + this->title_id = item->titleId->data.to_string(); + } + + for (u32 i = 0; i < item->dirNameNum; i++) { + const auto dir_name = item->dirName[i].data.to_view(); + + if (dir_name.empty()) { + continue; + } + + auto dir_path = SaveInstance::MakeDirSavePath(user_id, title_id, dir_name); + + auto param_sfo_path = dir_path / "sce_sys" / "param.sfo"; + if (!std::filesystem::exists(param_sfo_path)) { + continue; + } + + PSF param_sfo; + param_sfo.Open(param_sfo_path); + + auto last_write = param_sfo.GetLastWrite(); +#ifdef _WIN32 + auto utc_time = std::chrono::file_clock::to_utc(last_write); +#else + auto utc_time = std::chrono::file_clock::to_sys(last_write); +#endif + std::string date_str = fmt::format("{:%d %b, %Y %R}", utc_time); + + size_t size = Common::FS::GetDirectorySize(dir_path); + std::string size_str = SpaceSizeToString(size); + + auto icon_path = dir_path / "sce_sys" / "icon0.png"; + RefCountedTexture icon; + if (std::filesystem::exists(icon_path)) { + icon = RefCountedTexture::DecodePngFile(icon_path); + } + + bool is_corrupted = std::filesystem::exists(dir_path / "sce_sys" / "corrupted"); + + this->save_list.emplace_back(Item{ + .dir_name = std::string{dir_name}, + .icon = icon, + + .title = std::string{param_sfo.GetString(SaveParams::MAINTITLE).value_or("Unknown")}, + .subtitle = std::string{param_sfo.GetString(SaveParams::SUBTITLE).value_or("")}, + .details = std::string{param_sfo.GetString(SaveParams::DETAIL).value_or("")}, + .date = date_str, + .size = size_str, + .last_write = param_sfo.GetLastWrite(), + .pfo = param_sfo, + .is_corrupted = is_corrupted, + }); + } + + if (type == DialogType::SAVE && item->newItem != nullptr) { + RefCountedTexture icon; + std::string title{"New Save"}; + + const auto new_item = item->newItem; + if (new_item->iconBuf && new_item->iconSize) { + auto buf = (u8*)new_item->iconBuf; + icon = RefCountedTexture::DecodePngTexture({buf, buf + new_item->iconSize}); + } else { + const auto& src_icon = g_mnt->GetHostPath("/app0/sce_sys/save_data.png"); + if (std::filesystem::exists(src_icon)) { + icon = RefCountedTexture::DecodePngFile(src_icon); + } + } + if (new_item->title != nullptr) { + title = std::string{new_item->title}; + } + + this->new_item = Item{ + .dir_name = "", + .icon = icon, + .title = title, + }; + } + + if (item->focusPos != FocusPos::DIRNAME) { + this->focus_pos = item->focusPos; + } else { + this->focus_pos = item->focusPosDirName->data.to_string(); + } + this->style = item->itemStyle; + + switch (mode) { + case SaveDataDialogMode::USER_MSG: { + this->state = UserState{param}; + } break; + case SaveDataDialogMode::SYSTEM_MSG: + this->state = SystemState{*this, param}; + break; + case SaveDataDialogMode::ERROR_CODE: { + this->state = ErrorCodeState{param}; + } break; + case SaveDataDialogMode::PROGRESS_BAR: { + this->state = ProgressBarState{*this, param}; + } break; + default: + break; + } +} + +SaveDialogState::UserState::UserState(const OrbisSaveDataDialogParam& param) { + auto& user = *param.userMsgParam; + this->type = user.buttonType; + this->msg_type = user.msgType; + this->msg = user.msg != nullptr ? std::string{user.msg} : std::string{}; +} + +SaveDialogState::SystemState::SystemState(const SaveDialogState& state, + const OrbisSaveDataDialogParam& param) { +#define M(save, load, del) \ + if (type == DialogType::SAVE) \ + this->msg = save; \ + else if (type == DialogType::LOAD) \ + this->msg = load; \ + else if (type == DialogType::DELETE) \ + this->msg = del; \ + else \ + UNREACHABLE() + + auto type = param.dispType; + auto& sys = *param.sysMsgParam; + switch (sys.msgType) { + case SystemMessageType::NODATA: { + return_cancel = true; + this->msg = "There is no saved data"; + } break; + case SystemMessageType::CONFIRM: + show_no = true; + M("Do you want to save?", "Do you want to load this saved data?", + "Do you want to delete this saved data?"); + break; + case SystemMessageType::OVERWRITE: + show_no = true; + M("Do you want to overwrite the existing saved data?", "##UNKNOWN##", "##UNKNOWN##"); + break; + case SystemMessageType::NOSPACE: + return_cancel = true; + M(fmt::format( + "There is not enough space to save the data. To continue {} free space is required.", + SpaceSizeToString(sys.value * OrbisSaveDataBlockSize)), + "##UNKNOWN##", "##UNKNOWN##"); + break; + case SystemMessageType::PROGRESS: + hide_ok = true; + show_cancel = state.enable_back.value_or(false); + M("Saving...", "Loading...", "Deleting..."); + break; + case SystemMessageType::FILE_CORRUPTED: + return_cancel = true; + this->msg = "The saved data is corrupted."; + break; + case SystemMessageType::FINISHED: + return_cancel = true; + M("Saved successfully.", "Loading complete.", "Deletion complete."); + break; + case SystemMessageType::NOSPACE_CONTINUABLE: + return_cancel = true; + M(fmt::format("There is not enough space to save the data. {} free space is required.", + SpaceSizeToString(sys.value * OrbisSaveDataBlockSize)), + "##UNKNOWN##", "##UNKNOWN##"); + break; + case SystemMessageType::CORRUPTED_AND_DELETED: { + show_cancel = state.enable_back.value_or(true); + const char* msg1 = "The saved data is corrupted and will be deleted."; + M(msg1, msg1, "##UNKNOWN##"); + } break; + case SystemMessageType::CORRUPTED_AND_CREATED: { + show_cancel = state.enable_back.value_or(true); + const char* msg2 = "The saved data is corrupted. This saved data will be deleted and a new " + "one will be created."; + M(msg2, msg2, "##UNKNOWN##"); + } break; + case SystemMessageType::CORRUPTED_AND_RESTORE: { + show_cancel = state.enable_back.value_or(true); + const char* msg3 = + "The saved data is corrupted. The data that was backed up by the system will be " + "restored."; + M(msg3, msg3, "##UNKNOWN##"); + } break; + case SystemMessageType::TOTAL_SIZE_EXCEEDED: + M("Cannot create more saved data", "##UNKNOWN##", "##UNKNOWN##"); + break; + default: + msg = fmt::format("Unknown message type: {}", magic_enum::enum_name(sys.msgType)); + break; + } + +#undef M +} + +SaveDialogState::ErrorCodeState::ErrorCodeState(const OrbisSaveDataDialogParam& param) { + auto& err = *param.errorCodeParam; + constexpr auto NOT_FOUND = 0x809F0008; + constexpr auto BROKEN = 0x809F000F; + switch (err.errorCode) { + case NOT_FOUND: + this->error_msg = "There is not saved data."; + break; + case BROKEN: + this->error_msg = "The data is corrupted."; + break; + default: + this->error_msg = fmt::format("An error has occurred. ({:X})", err.errorCode); + break; + } +} +SaveDialogState::ProgressBarState::ProgressBarState(const SaveDialogState& state, + const OrbisSaveDataDialogParam& param) { + static auto fw_ver = ElfInfo::Instance().FirmwareVer(); + + this->progress = 0; + + auto& bar = *param.progressBarParam; + + if (bar.msg != nullptr) { + this->msg = std::string{bar.msg}; + } else { + switch (bar.sysMsgType) { + case ProgressSystemMessageType::INVALID: + this->msg = "INVALID"; + break; + case ProgressSystemMessageType::PROGRESS: + switch (state.type) { + case DialogType::SAVE: + this->msg = "Saving..."; + break; + case DialogType::LOAD: + this->msg = "Loading..."; + break; + case DialogType::DELETE: + this->msg = "Deleting..."; + break; + } + break; + case ProgressSystemMessageType::RESTORE: + this->msg = "Restoring saved data..."; + break; + } + } +} + +SaveDialogUi::SaveDialogUi(SaveDialogState* state, Status* status, SaveDialogResult* result) + : state(state), status(status), result(result) { + if (status && *status == Status::RUNNING) { + first_render = true; + AddLayer(this); + } +} + +SaveDialogUi::~SaveDialogUi() { + Finish(ButtonId::INVALID); +} + +SaveDialogUi::SaveDialogUi(SaveDialogUi&& other) noexcept + : Layer(other), state(other.state), status(other.status), result(other.result) { + std::scoped_lock lock(draw_mutex, other.draw_mutex); + other.state = nullptr; + other.status = nullptr; + other.result = nullptr; + if (status && *status == Status::RUNNING) { + first_render = true; + AddLayer(this); + } +} + +SaveDialogUi& SaveDialogUi::operator=(SaveDialogUi other) { + std::scoped_lock lock(draw_mutex, other.draw_mutex); + using std::swap; + swap(state, other.state); + swap(status, other.status); + swap(result, other.result); + if (status && *status == Status::RUNNING) { + first_render = true; + AddLayer(this); + } + return *this; +} + +void SaveDialogUi::Finish(ButtonId buttonId, Result r) { + std::unique_lock lock(draw_mutex); + if (result) { + result->mode = this->state->mode; + result->result = r; + result->button_id = buttonId; + result->user_data = this->state->user_data; + if (state && state->mode != SaveDataDialogMode::LIST && !state->save_list.empty()) { + result->dir_name = state->save_list.front().dir_name; + } + } + if (status) { + *status = Status::FINISHED; + } + RemoveLayer(this); +} + +void SaveDialogUi::Draw() { + std::unique_lock lock{draw_mutex}; + + if (status == nullptr || *status != Status::RUNNING || state == nullptr) { + return; + } + + const auto& ctx = *GetCurrentContext(); + const auto& io = ctx.IO; + + ImVec2 window_size; + + if (state->GetMode() == SaveDataDialogMode::LIST) { + window_size = ImVec2{ + std::min(io.DisplaySize.x - 200.0f, 1100.0f), + std::min(io.DisplaySize.y - 100.0f, 700.0f), + }; + } else { + window_size = ImVec2{ + std::min(io.DisplaySize.x, 600.0f), + std::min(io.DisplaySize.y, 300.0f), + }; + } + + CentralizeWindow(); + SetNextWindowSize(window_size); + SetNextWindowCollapsed(false); + if (first_render || !io.NavActive) { + SetNextWindowFocus(); + } + if (Begin("Save Data Dialog##SaveDataDialog", nullptr, + ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings)) { + DrawPrettyBackground(); + + Separator(); + // Draw title bigger + SetWindowFontScale(1.7f); + switch (state->type) { + case DialogType::SAVE: + TextUnformatted("Save"); + break; + case DialogType::LOAD: + TextUnformatted("Load"); + break; + case DialogType::DELETE: + TextUnformatted("Delete"); + break; + } + SetWindowFontScale(1.0f); + Separator(); + + BeginGroup(); + switch (state->GetMode()) { + case SaveDataDialogMode::LIST: + DrawList(); + break; + case SaveDataDialogMode::USER_MSG: + DrawUser(); + break; + case SaveDataDialogMode::SYSTEM_MSG: + DrawSystemMessage(); + break; + case SaveDataDialogMode::ERROR_CODE: + DrawErrorCode(); + break; + case SaveDataDialogMode::PROGRESS_BAR: + DrawProgressBar(); + break; + default: + TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f), "!!!Unknown dialog mode!!!"); + } + EndGroup(); + } + End(); + + first_render = false; + if (*status == Status::FINISHED) { + if (state) { + *state = SaveDialogState{}; + } + state = nullptr; + status = nullptr; + result = nullptr; + } +} + +void SaveDialogUi::DrawItem(int _id, const SaveDialogState::Item& item, bool clickable) { + constexpr auto text_spacing = 0.95f; + + auto& ctx = *GetCurrentContext(); + auto& window = *ctx.CurrentWindow; + + auto content_region_avail = GetContentRegionAvail(); + const auto outer_pos = window.DC.CursorPos; + const auto pos = outer_pos + SAVE_ICON_PADDING; + + const ImVec2 size = {content_region_avail.x - SAVE_ICON_PADDING.x, + SAVE_ICON_SIZE.y + SAVE_ICON_PADDING.y}; + const ImRect bb{outer_pos, outer_pos + size + SAVE_ICON_PADDING}; + + const ImGuiID id = GetID(_id); + + ItemSize(size + ImVec2{0.0f, SAVE_ICON_PADDING.y * 2.0f}); + if (!ItemAdd(bb, id)) { + return; + } + + window.DrawList->AddRectFilled(bb.Min + SAVE_ICON_PADDING, bb.Max - SAVE_ICON_PADDING, + GetColorU32(ImVec4{0.3f})); + + bool hovered = false; + if (clickable) { + bool held; + bool pressed = ButtonBehavior(bb, id, &hovered, &held); + if (pressed) { + result->dir_name = item.dir_name; + result->param = item.pfo; + Finish(ButtonId::INVALID); + } + RenderNavHighlight(bb, id); + } + + if (item.icon) { + auto texture = item.icon.GetTexture(); + window.DrawList->AddImage(texture.im_id, pos, pos + SAVE_ICON_SIZE); + } else { + // placeholder + window.DrawList->AddRectFilled(pos, pos + SAVE_ICON_SIZE, GetColorU32(ImVec4{0.7f})); + } + + auto pos_x = SAVE_ICON_SIZE.x + 5.0f; + auto pos_y = 2.0f; + + if (!item.title.empty()) { + const char* begin = &item.title.front(); + const char* end = &item.title.back() + 1; + SetWindowFontScale(1.5f); + RenderText(pos + ImVec2{pos_x, pos_y}, begin, end, false); + pos_y += ctx.FontSize * text_spacing; + } + SetWindowFontScale(1.1f); + + if (item.is_corrupted) { + pos_y -= ctx.FontSize * text_spacing * 0.3f; + const auto bright = (int)std::abs(std::sin(ctx.Time) * 0.15f * 255.0f); + PushStyleColor(ImGuiCol_Text, IM_COL32(bright + 216, bright, bright, 0xFF)); + RenderText(pos + ImVec2{pos_x, pos_y}, "Corrupted", nullptr, false); + PopStyleColor(); + pos_y += ctx.FontSize * text_spacing * 0.8f; + } + + if (state->style == ItemStyle::TITLE_SUBTITLE_DATESIZE) { + if (!item.subtitle.empty()) { + const char* begin = &item.subtitle.front(); + const char* end = &item.subtitle.back() + 1; + RenderText(pos + ImVec2{pos_x, pos_y}, begin, end, false); + } + pos_y += ctx.FontSize * text_spacing; + } + + { + float width = 0.0f; + if (!item.date.empty()) { + const char* d_begin = &item.date.front(); + const char* d_end = &item.date.back() + 1; + width = CalcTextSize(d_begin, d_end).x + 15.0f; + RenderText(pos + ImVec2{pos_x, pos_y}, d_begin, d_end, false); + } + if (!item.size.empty()) { + const char* s_begin = &item.size.front(); + const char* s_end = &item.size.back() + 1; + RenderText(pos + ImVec2{pos_x + width, pos_y}, s_begin, s_end, false); + } + pos_y += ctx.FontSize * text_spacing; + } + + if (state->style == ItemStyle::TITLE_DATASIZE_SUBTITLE && !item.subtitle.empty()) { + const char* begin = &item.subtitle.front(); + const char* end = &item.subtitle.back() + 1; + RenderText(pos + ImVec2{pos_x, pos_y}, begin, end, false); + } + + SetWindowFontScale(1.0f); + + if (hovered) { + window.DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_Border), 0.0f, 0, 2.0f); + } +} + +void SaveDialogUi::DrawList() { + auto availableSize = GetContentRegionAvail(); + + constexpr auto footerHeight = 30.0f; + availableSize.y -= footerHeight + 1.0f; + + BeginChild("##ScrollingRegion", availableSize, ImGuiChildFlags_NavFlattened); + int i = 0; + if (state->new_item.has_value()) { + DrawItem(i++, state->new_item.value()); + } + for (const auto& item : state->save_list) { + DrawItem(i++, item); + } + if (first_render) { // Make the initial focus + if (std::holds_alternative(state->focus_pos)) { + auto pos = std::get(state->focus_pos); + if (pos == FocusPos::LISTHEAD || pos == FocusPos::DATAHEAD) { + SetItemCurrentNavFocus(GetID(0)); + } else if (pos == FocusPos::LISTTAIL || pos == FocusPos::DATATAIL) { + SetItemCurrentNavFocus(GetID(std::max(i - 1, 0))); + } else { // Date + int idx = 0; + int max_idx = 0; + bool is_min = pos == FocusPos::DATAOLDEST; + std::filesystem::file_time_type max_write{}; + if (state->new_item.has_value()) { + idx++; + } + for (const auto& item : state->save_list) { + if (item.last_write > max_write ^ is_min) { + max_write = item.last_write; + max_idx = idx; + } + idx++; + } + SetItemCurrentNavFocus(GetID(max_idx)); + } + } else if (std::holds_alternative(state->focus_pos)) { + auto dir_name = std::get(state->focus_pos); + if (dir_name.empty()) { + SetItemCurrentNavFocus(GetID(0)); + } else { + int idx = 0; + if (state->new_item.has_value()) { + if (dir_name == state->new_item->dir_name) { + SetItemCurrentNavFocus(GetID(idx)); + } + idx++; + } + for (const auto& item : state->save_list) { + if (item.dir_name == dir_name) { + SetItemCurrentNavFocus(GetID(idx)); + break; + } + idx++; + } + } + } + } + EndChild(); + + Separator(); + if (state->enable_back.value_or(true)) { + constexpr auto back = "Back"; + constexpr float pad = 7.0f; + const auto txt_size = CalcTextSize(back); + const auto button_size = ImVec2{ + std::max(txt_size.x, 100.0f) + pad * 2.0f, + footerHeight - pad, + }; + SetCursorPosX(GetContentRegionAvail().x - button_size.x); + if (Button(back, button_size)) { + result->dir_name.clear(); + Finish(ButtonId::INVALID, Result::USER_CANCELED); + } + if (IsKeyPressed(ImGuiKey_GamepadFaceRight)) { + SetItemCurrentNavFocus(); + } + } +} + +void SaveDialogUi::DrawUser() { + const auto& user_state = state->GetState(); + const auto btn_type = user_state.type; + + const auto ws = GetWindowSize(); + + if (!state->save_list.empty()) { + DrawItem(0, state->save_list.front(), false); + } else if (state->new_item) { + DrawItem(0, *state->new_item, false); + } + + auto has_btn = btn_type != ButtonType::NONE; + ImVec2 btn_space; + if (has_btn) { + btn_space = ImVec2{0.0f, FOOTER_HEIGHT}; + } + + const auto& msg = user_state.msg; + if (!msg.empty()) { + const char* begin = &msg.front(); + const char* end = &msg.back() + 1; + if (user_state.msg_type == UserMessageType::ERROR) { + PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.0f, 0.0f, 1.0f)); + // Maybe make the text bold? + } + DrawCenteredText(begin, end, GetContentRegionAvail() - btn_space); + if (user_state.msg_type == UserMessageType::ERROR) { + PopStyleColor(); + } + } + + if (has_btn) { + int count = 1; + if (btn_type == ButtonType::YESNO || btn_type == ButtonType::OKCANCEL) { + ++count; + } + + SetCursorPos({ + ws.x / 2.0f - BUTTON_SIZE.x / 2.0f * static_cast(count), + ws.y - FOOTER_HEIGHT + 5.0f, + }); + + BeginGroup(); + if (btn_type == ButtonType::YESNO) { + if (Button("Yes", BUTTON_SIZE)) { + Finish(ButtonId::YES); + } + SameLine(); + if (Button("No", BUTTON_SIZE)) { + if (ElfInfo::Instance().FirmwareVer() < ElfInfo::FW_45) { + Finish(ButtonId::INVALID, Result::USER_CANCELED); + } else { + Finish(ButtonId::NO); + } + } + if (first_render || IsKeyPressed(ImGuiKey_GamepadFaceRight)) { + SetItemCurrentNavFocus(); + } + } else { + if (Button("OK", BUTTON_SIZE)) { + if (btn_type == ButtonType::OK && + ElfInfo::Instance().FirmwareVer() < ElfInfo::FW_45) { + Finish(ButtonId::INVALID, Result::USER_CANCELED); + } else { + Finish(ButtonId::OK); + } + } + if (first_render) { + SetItemCurrentNavFocus(); + } + if (btn_type == ButtonType::OKCANCEL) { + SameLine(); + if (Button("Cancel", BUTTON_SIZE)) { + Finish(ButtonId::INVALID, Result::USER_CANCELED); + } + if (IsKeyPressed(ImGuiKey_GamepadFaceRight)) { + SetItemCurrentNavFocus(); + } + } + } + EndGroup(); + } +} + +void SaveDialogUi::DrawSystemMessage() { + const auto& sys_state = state->GetState(); + + if (!state->save_list.empty()) { + DrawItem(0, state->save_list.front(), false); + } else if (state->new_item) { + DrawItem(0, *state->new_item, false); + } + + const auto ws = GetWindowSize(); + const auto& msg = sys_state.msg; + if (!msg.empty()) { + const char* begin = &msg.front(); + const char* end = &msg.back() + 1; + DrawCenteredText(begin, end, GetContentRegionAvail() - ImVec2{0.0f, FOOTER_HEIGHT}); + } + int count = 1; + if (sys_state.hide_ok) { + --count; + } + if (sys_state.show_no || sys_state.show_cancel) { + ++count; + } + + SetCursorPos({ + ws.x / 2.0f - BUTTON_SIZE.x / 2.0f * static_cast(count), + ws.y - FOOTER_HEIGHT + 5.0f, + }); + BeginGroup(); + if (Button(sys_state.show_no ? "Yes" : "OK", BUTTON_SIZE)) { + if (sys_state.return_cancel && ElfInfo::Instance().FirmwareVer() < ElfInfo::FW_45) { + Finish(ButtonId::INVALID, Result::USER_CANCELED); + } else { + Finish(ButtonId::YES); + } + } + SameLine(); + if (sys_state.show_no) { + if (Button("No", BUTTON_SIZE)) { + if (ElfInfo::Instance().FirmwareVer() < ElfInfo::FW_45) { + Finish(ButtonId::INVALID, Result::USER_CANCELED); + } else { + Finish(ButtonId::NO); + } + } + } else if (sys_state.show_cancel) { + if (Button("Cancel", BUTTON_SIZE)) { + Finish(ButtonId::INVALID, Result::USER_CANCELED); + } + } + if (first_render || IsKeyPressed(ImGuiKey_GamepadFaceRight)) { + SetItemCurrentNavFocus(); + } + EndGroup(); +} + +void SaveDialogUi::DrawErrorCode() { + const auto& err_state = state->GetState(); + + if (!state->save_list.empty()) { + DrawItem(0, state->save_list.front(), false); + } else if (state->new_item) { + DrawItem(0, *state->new_item, false); + } + + const auto ws = GetWindowSize(); + const auto& msg = err_state.error_msg; + if (!msg.empty()) { + const char* begin = &msg.front(); + const char* end = &msg.back() + 1; + DrawCenteredText(begin, end, GetContentRegionAvail() - ImVec2{0.0f, FOOTER_HEIGHT}); + } + + SetCursorPos({ + ws.x / 2.0f - BUTTON_SIZE.x / 2.0f, + ws.y - FOOTER_HEIGHT + 5.0f, + }); + if (Button("OK", BUTTON_SIZE)) { + if (ElfInfo::Instance().FirmwareVer() < ElfInfo::FW_45) { + Finish(ButtonId::INVALID, Result::USER_CANCELED); + } else { + Finish(ButtonId::OK); + } + } + if (first_render) { + SetItemCurrentNavFocus(); + } +} + +void SaveDialogUi::DrawProgressBar() { + const auto& bar_state = state->GetState(); + + const auto ws = GetWindowSize(); + + if (!state->save_list.empty()) { + DrawItem(0, state->save_list.front(), false); + } else if (state->new_item) { + DrawItem(0, *state->new_item, false); + } + + const auto& msg = bar_state.msg; + if (!msg.empty()) { + const char* begin = &msg.front(); + const char* end = &msg.back() + 1; + DrawCenteredText(begin, end, GetContentRegionAvail() - ImVec2{0.0f, FOOTER_HEIGHT}); + } + + SetCursorPos({ + ws.x * ((1 - PROGRESS_BAR_WIDTH) / 2.0f), + ws.y - FOOTER_HEIGHT + 5.0f, + }); + + ProgressBar(static_cast(bar_state.progress) / 100.0f, + {PROGRESS_BAR_WIDTH * ws.x, BUTTON_SIZE.y}); +} +}; // namespace Libraries::SaveData::Dialog \ No newline at end of file diff --git a/src/core/libraries/save_data/dialog/savedatadialog_ui.h b/src/core/libraries/save_data/dialog/savedatadialog_ui.h new file mode 100644 index 00000000000..3f414470f73 --- /dev/null +++ b/src/core/libraries/save_data/dialog/savedatadialog_ui.h @@ -0,0 +1,319 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include + +#include "core/file_format/psf.h" +#include "core/libraries/save_data/savedata.h" +#include "core/libraries/system/commondialog.h" +#include "imgui/imgui_layer.h" +#include "imgui/imgui_texture.h" + +namespace Libraries::SaveData::Dialog { + +using OrbisUserServiceUserId = s32; + +enum class SaveDataDialogMode : u32 { + INVALID = 0, + LIST = 1, + USER_MSG = 2, + SYSTEM_MSG = 3, + ERROR_CODE = 4, + PROGRESS_BAR = 5, +}; + +enum class DialogType : u32 { + SAVE = 1, + LOAD = 2, + DELETE = 3, +}; + +enum class DialogAnimation : u32 { + ON = 0, + OFF = 1, +}; + +enum class ButtonId : u32 { + INVALID = 0, + OK = 1, + YES = 1, + NO = 2, +}; + +enum class ButtonType : u32 { + OK = 0, + YESNO = 1, + NONE = 2, + OKCANCEL = 3, +}; + +enum class UserMessageType : u32 { + NORMAL = 0, + ERROR = 1, +}; + +enum class FocusPos : u32 { + LISTHEAD = 0, + LISTTAIL = 1, + DATAHEAD = 2, + DATATAIL = 3, + DATALTATEST = 4, + DATAOLDEST = 5, + DIRNAME = 6, +}; + +enum class ItemStyle : u32 { + TITLE_DATASIZE_SUBTITLE = 0, + TITLE_SUBTITLE_DATESIZE = 1, + TITLE_DATESIZE = 2, +}; + +enum class SystemMessageType : u32 { + NODATA = 1, + CONFIRM = 2, + OVERWRITE = 3, + NOSPACE = 4, + PROGRESS = 5, + FILE_CORRUPTED = 6, + FINISHED = 7, + NOSPACE_CONTINUABLE = 8, + CORRUPTED_AND_DELETED = 10, + CORRUPTED_AND_CREATED = 11, + CORRUPTED_AND_RESTORE = 13, + TOTAL_SIZE_EXCEEDED = 14, +}; + +enum class ProgressBarType : u32 { + PERCENTAGE = 0, +}; + +enum class ProgressSystemMessageType : u32 { + INVALID = 0, + PROGRESS = 1, + RESTORE = 2, +}; + +enum class OptionBack : u32 { + ENABLE = 0, + DISABLE = 1, +}; + +enum class OrbisSaveDataDialogProgressBarTarget : u32 { + DEFAULT = 0, +}; + +struct AnimationParam { + DialogAnimation userOK; + DialogAnimation userCancel; + std::array _reserved; +}; + +struct SaveDialogNewItem { + const char* title; + void* iconBuf; + size_t iconSize; + std::array _reserved; +}; + +struct SaveDialogItems { + OrbisUserServiceUserId userId; + s32 : 32; + const OrbisSaveDataTitleId* titleId; + const OrbisSaveDataDirName* dirName; + u32 dirNameNum; + s32 : 32; + const SaveDialogNewItem* newItem; + FocusPos focusPos; + s32 : 32; + const OrbisSaveDataDirName* focusPosDirName; + ItemStyle itemStyle; + std::array _reserved; +}; + +struct UserMessageParam { + ButtonType buttonType; + UserMessageType msgType; + const char* msg; + std::array _reserved; +}; + +struct SystemMessageParam { + SystemMessageType msgType; + s32 : 32; + u64 value; + std::array _reserved; +}; + +struct ErrorCodeParam { + u32 errorCode; + std::array _reserved; +}; + +struct ProgressBarParam { + ProgressBarType barType; + s32 : 32; + const char* msg; + ProgressSystemMessageType sysMsgType; + std::array _reserved; +}; + +struct OptionParam { + OptionBack back; + std::array _reserved; +}; + +struct OrbisSaveDataDialogParam { + CommonDialog::BaseParam baseParam; + s32 size; + SaveDataDialogMode mode; + DialogType dispType; + s32 : 32; + AnimationParam* animParam; + SaveDialogItems* items; + UserMessageParam* userMsgParam; + SystemMessageParam* sysMsgParam; + ErrorCodeParam* errorCodeParam; + ProgressBarParam* progressBarParam; + void* userData; + OptionParam* optionParam; + std::array _reserved; +}; + +struct OrbisSaveDataDialogResult { + SaveDataDialogMode mode{}; + CommonDialog::Result result{}; + ButtonId buttonId{}; + s32 : 32; + OrbisSaveDataDirName* dirName; + OrbisSaveDataParam* param; + void* userData; + std::array _reserved; +}; + +struct SaveDialogResult { + SaveDataDialogMode mode{}; + CommonDialog::Result result{CommonDialog::Result::OK}; + ButtonId button_id{ButtonId::INVALID}; + std::string dir_name{}; + PSF param{}; + void* user_data{}; + + void CopyTo(OrbisSaveDataDialogResult& result) const; +}; + +class SaveDialogState { + friend class SaveDialogUi; + +public: + struct UserState { + ButtonType type{}; + UserMessageType msg_type{}; + std::string msg{}; + + UserState(const OrbisSaveDataDialogParam& param); + }; + struct SystemState { + std::string msg{}; + bool hide_ok{}; + bool show_no{}; // Yes instead of OK + bool show_cancel{}; + + bool return_cancel{}; + + SystemState(const SaveDialogState& state, const OrbisSaveDataDialogParam& param); + }; + struct ErrorCodeState { + std::string error_msg{}; + + ErrorCodeState(const OrbisSaveDataDialogParam& param); + }; + struct ProgressBarState { + std::string msg{}; + u32 progress{}; + + ProgressBarState(const SaveDialogState& state, const OrbisSaveDataDialogParam& param); + }; + + struct Item { + std::string dir_name{}; + ImGui::RefCountedTexture icon{}; + + std::string title{}; + std::string subtitle{}; + std::string details{}; + std::string date{}; + std::string size{}; + + std::filesystem::file_time_type last_write{}; + PSF pfo{}; + bool is_corrupted{}; + }; + +private: + SaveDataDialogMode mode{}; + DialogType type{}; + void* user_data{}; + std::optional enable_back{}; + + OrbisUserServiceUserId user_id{}; + std::string title_id{}; + std::vector save_list{}; + std::variant focus_pos{std::monostate{}}; + ItemStyle style{}; + + std::optional new_item{}; + + std::variant state{ + std::monostate{}}; + +public: + explicit SaveDialogState(const OrbisSaveDataDialogParam& param); + + SaveDialogState() = default; + + [[nodiscard]] SaveDataDialogMode GetMode() const { + return mode; + } + + template + [[nodiscard]] T& GetState() { + return std::get(state); + } +}; + +class SaveDialogUi final : public ImGui::Layer { + bool first_render{false}; + SaveDialogState* state{}; + CommonDialog::Status* status{}; + SaveDialogResult* result{}; + + std::recursive_mutex draw_mutex{}; + +public: + explicit SaveDialogUi(SaveDialogState* state = nullptr, CommonDialog::Status* status = nullptr, + SaveDialogResult* result = nullptr); + + ~SaveDialogUi() override; + SaveDialogUi(const SaveDialogUi& other) = delete; + SaveDialogUi(SaveDialogUi&& other) noexcept; + SaveDialogUi& operator=(SaveDialogUi other); + + void Finish(ButtonId buttonId, CommonDialog::Result r = CommonDialog::Result::OK); + + void Draw() override; + +private: + void DrawItem(int id, const SaveDialogState::Item& item, bool clickable = true); + + void DrawList(); + void DrawUser(); + void DrawSystemMessage(); + void DrawErrorCode(); + void DrawProgressBar(); +}; + +}; // namespace Libraries::SaveData::Dialog \ No newline at end of file diff --git a/src/core/libraries/save_data/error_codes.h b/src/core/libraries/save_data/error_codes.h deleted file mode 100644 index a4a1b7a5a98..00000000000 --- a/src/core/libraries/save_data/error_codes.h +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -constexpr int ORBIS_SAVE_DATA_ERROR_PARAMETER = 0x809F0000; -constexpr int ORBIS_SAVE_DATA_ERROR_NOT_INITIALIZED = 0x809F0001; -constexpr int ORBIS_SAVE_DATA_ERROR_OUT_OF_MEMORY = 0x809F0002; -constexpr int ORBIS_SAVE_DATA_ERROR_BUSY = 0x809F0003; -constexpr int ORBIS_SAVE_DATA_ERROR_NOT_MOUNTED = 0x809F0004; -constexpr int ORBIS_SAVE_DATA_ERROR_NO_PERMISSION = 0x809F0005; -constexpr int ORBIS_SAVE_DATA_ERROR_FINGERPRINT_MISMATCH = 0x809F0006; -constexpr int ORBIS_SAVE_DATA_ERROR_EXISTS = 0x809F0007; -constexpr int ORBIS_SAVE_DATA_ERROR_NOT_FOUND = 0x809F0008; -constexpr int ORBIS_SAVE_DATA_ERROR_NO_SPACE_FS = 0x809F000A; -constexpr int ORBIS_SAVE_DATA_ERROR_INTERNAL = 0x809F000B; -constexpr int ORBIS_SAVE_DATA_ERROR_MOUNT_FULL = 0x809F000C; -constexpr int ORBIS_SAVE_DATA_ERROR_BAD_MOUNTED = 0x809F000D; -constexpr int ORBIS_SAVE_DATA_ERROR_FILE_NOT_FOUND = 0x809F000E; -constexpr int ORBIS_SAVE_DATA_ERROR_BROKEN = 0x809F000F; -constexpr int ORBIS_SAVE_DATA_ERROR_INVALID_LOGIN_USER = 0x809F0011; -constexpr int ORBIS_SAVE_DATA_ERROR_MEMORY_NOT_READY = 0x809F0012; -constexpr int ORBIS_SAVE_DATA_ERROR_BACKUP_BUSY = 0x809F0013; -constexpr int ORBIS_SAVE_DATA_ERROR_NOT_REGIST_CALLBACK = 0x809F0015; -constexpr int ORBIS_SAVE_DATA_ERROR_BUSY_FOR_SAVING = 0x809F0016; -constexpr int ORBIS_SAVE_DATA_ERROR_LIMITATION_OVER = 0x809F0017; -constexpr int ORBIS_SAVE_DATA_ERROR_EVENT_BUSY = 0x809F0018; -constexpr int ORBIS_SAVE_DATA_ERROR_PARAMSFO_TRANSFER_TITLE_ID_NOT_FOUND = 0x809F0019; diff --git a/src/core/libraries/save_data/save_backup.cpp b/src/core/libraries/save_data/save_backup.cpp new file mode 100644 index 00000000000..8f7e0d69ab0 --- /dev/null +++ b/src/core/libraries/save_data/save_backup.cpp @@ -0,0 +1,248 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include + +#include + +#include "save_backup.h" +#include "save_instance.h" + +#include "common/logging/log.h" +#include "common/logging/log_entry.h" +#include "common/polyfill_thread.h" +#include "common/thread.h" + +constexpr std::string_view sce_sys = "sce_sys"; // system folder inside save +constexpr std::string_view backup_dir = "sce_backup"; // backup folder +constexpr std::string_view backup_dir_tmp = "sce_backup_tmp"; // in-progress backup folder +constexpr std::string_view backup_dir_old = "sce_backup_old"; // previous backup folder + +namespace fs = std::filesystem; + +namespace Libraries::SaveData::Backup { + +static std::jthread g_backup_thread; +static std::counting_semaphore g_backup_thread_semaphore{0}; + +static std::mutex g_backup_running_mutex; + +static std::mutex g_backup_queue_mutex; +static std::deque g_backup_queue; +static std::deque g_result_queue; + +static std::atomic_int g_backup_progress = 0; +static std::atomic g_backup_status = WorkerStatus::NotStarted; + +static void backup(const std::filesystem::path& dir_name) { + std::unique_lock lk{g_backup_running_mutex}; + if (!fs::exists(dir_name)) { + return; + } + + const auto backup_dir = dir_name / ::backup_dir; + const auto backup_dir_tmp = dir_name / ::backup_dir_tmp; + const auto backup_dir_old = dir_name / ::backup_dir_old; + + fs::remove_all(backup_dir_tmp); + fs::remove_all(backup_dir_old); + + std::vector backup_files; + for (const auto& entry : fs::directory_iterator(dir_name)) { + const auto filename = entry.path().filename(); + if (filename != ::backup_dir) { + backup_files.push_back(entry.path()); + } + } + + g_backup_progress = 0; + + int total_count = static_cast(backup_files.size()); + int current_count = 0; + + fs::create_directory(backup_dir_tmp); + for (const auto& file : backup_files) { + fs::copy(file, backup_dir_tmp / file.filename(), fs::copy_options::recursive); + current_count++; + g_backup_progress = current_count * 100 / total_count; + } + bool has_existing_backup = fs::exists(backup_dir); + if (has_existing_backup) { + fs::rename(backup_dir, backup_dir_old); + } + fs::rename(backup_dir_tmp, backup_dir); + if (has_existing_backup) { + fs::remove_all(backup_dir_old); + } +} + +static void BackupThreadBody() { + Common::SetCurrentThreadName("SaveData_BackupThread"); + while (g_backup_status != WorkerStatus::Stopping) { + g_backup_status = WorkerStatus::Waiting; + + bool wait; + BackupRequest req; + { + std::scoped_lock lk{g_backup_queue_mutex}; + wait = g_backup_queue.empty(); + if (!wait) { + req = g_backup_queue.front(); + } + } + if (wait) { + g_backup_thread_semaphore.acquire(); + { + std::scoped_lock lk{g_backup_queue_mutex}; + if (g_backup_queue.empty()) { + continue; + } + req = g_backup_queue.front(); + } + } + if (req.save_path.empty()) { + break; + } + g_backup_status = WorkerStatus::Running; + + LOG_INFO(Lib_SaveData, "Backing up the following directory: {}", req.save_path.string()); + try { + backup(req.save_path); + } catch (const std::filesystem::filesystem_error& err) { + LOG_ERROR(Lib_SaveData, "Failed to backup {}: {}", req.save_path.string(), err.what()); + } + LOG_DEBUG(Lib_SaveData, "Backing up the following directory: {} finished", + req.save_path.string()); + { + std::scoped_lock lk{g_backup_queue_mutex}; + g_backup_queue.front().done = true; + } + std::this_thread::sleep_for(std::chrono::seconds(10)); // Don't backup too often + { + std::scoped_lock lk{g_backup_queue_mutex}; + g_backup_queue.pop_front(); + g_result_queue.push_back(std::move(req)); + if (g_result_queue.size() > 20) { + g_result_queue.pop_front(); + } + } + } + g_backup_status = WorkerStatus::NotStarted; +} + +void StartThread() { + if (g_backup_status != WorkerStatus::NotStarted) { + return; + } + LOG_DEBUG(Lib_SaveData, "Starting backup thread"); + g_backup_status = WorkerStatus::Waiting; + g_backup_thread = std::jthread{BackupThreadBody}; +} + +void StopThread() { + if (g_backup_status == WorkerStatus::NotStarted || g_backup_status == WorkerStatus::Stopping) { + return; + } + LOG_DEBUG(Lib_SaveData, "Stopping backup thread"); + { + std::scoped_lock lk{g_backup_queue_mutex}; + g_backup_queue.emplace_back(BackupRequest{}); + } + g_backup_thread_semaphore.release(); + g_backup_status = WorkerStatus::Stopping; +} + +bool NewRequest(OrbisUserServiceUserId user_id, std::string_view title_id, + std::string_view dir_name, OrbisSaveDataEventType origin) { + auto save_path = SaveInstance::MakeDirSavePath(user_id, title_id, dir_name); + + if (g_backup_status != WorkerStatus::Waiting && g_backup_status != WorkerStatus::Running) { + LOG_ERROR(Lib_SaveData, "Called backup while status is {}. Backup request to {} ignored", + magic_enum::enum_name(g_backup_status.load()), save_path.string()); + return false; + } + { + std::scoped_lock lk{g_backup_queue_mutex}; + for (const auto& it : g_backup_queue) { + if (it.dir_name == dir_name) { + LOG_TRACE(Lib_SaveData, "Backup request to {} ignored. Already queued", dir_name); + return false; + } + } + g_backup_queue.push_back(BackupRequest{ + .user_id = user_id, + .title_id = std::string{title_id}, + .dir_name = std::string{dir_name}, + .origin = origin, + .save_path = std::move(save_path), + }); + } + g_backup_thread_semaphore.release(); + return true; +} + +bool Restore(const std::filesystem::path& save_path) { + LOG_INFO(Lib_SaveData, "Restoring backup for {}", save_path.string()); + std::unique_lock lk{g_backup_running_mutex}; + if (!fs::exists(save_path) || !fs::exists(save_path / backup_dir)) { + return false; + } + for (const auto& entry : fs::directory_iterator(save_path)) { + const auto filename = entry.path().filename(); + if (filename != backup_dir) { + fs::remove_all(entry.path()); + } + } + + for (const auto& entry : fs::directory_iterator(save_path / backup_dir)) { + const auto filename = entry.path().filename(); + fs::copy(entry.path(), save_path / filename, fs::copy_options::recursive); + } + + return true; +} + +WorkerStatus GetWorkerStatus() { + return g_backup_status; +} + +bool IsBackupExecutingFor(const std::filesystem::path& save_path) { + std::scoped_lock lk{g_backup_queue_mutex}; + const auto& it = + std::ranges::find(g_backup_queue, save_path, [](const auto& v) { return v.save_path; }); + return it != g_backup_queue.end() && !it->done; +} + +std::filesystem::path MakeBackupPath(const std::filesystem::path& save_path) { + return save_path / backup_dir; +} + +std::optional PopLastEvent() { + std::scoped_lock lk{g_backup_queue_mutex}; + if (g_result_queue.empty()) { + return std::nullopt; + } + auto req = std::move(g_result_queue.front()); + g_result_queue.pop_front(); + return req; +} + +void PushBackupEvent(BackupRequest&& req) { + std::scoped_lock lk{g_backup_queue_mutex}; + g_result_queue.push_back(std::move(req)); + if (g_result_queue.size() > 20) { + g_result_queue.pop_front(); + } +} + +float GetProgress() { + return static_cast(g_backup_progress) / 100.0f; +} + +void ClearProgress() { + g_backup_progress = 0; +} + +} // namespace Libraries::SaveData::Backup \ No newline at end of file diff --git a/src/core/libraries/save_data/save_backup.h b/src/core/libraries/save_data/save_backup.h new file mode 100644 index 00000000000..e49c69f6073 --- /dev/null +++ b/src/core/libraries/save_data/save_backup.h @@ -0,0 +1,67 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +#include "common/types.h" + +namespace Libraries::SaveData { + +using OrbisUserServiceUserId = s32; + +namespace Backup { + +enum class WorkerStatus { + NotStarted, + Waiting, + Running, + Stopping, +}; + +enum class OrbisSaveDataEventType : u32 { + UMOUNT_BACKUP = 1, + BACKUP = 2, + SAVE_DATA_MEMORY_SYNC = 3, +}; + +struct BackupRequest { + bool done{}; + + OrbisUserServiceUserId user_id{}; + std::string title_id{}; + std::string dir_name{}; + OrbisSaveDataEventType origin{}; + + std::filesystem::path save_path{}; +}; + +// No problem calling this function if the backup thread is already running +void StartThread(); + +void StopThread(); + +bool NewRequest(OrbisUserServiceUserId user_id, std::string_view title_id, + std::string_view dir_name, OrbisSaveDataEventType origin); + +bool Restore(const std::filesystem::path& save_path); + +WorkerStatus GetWorkerStatus(); + +bool IsBackupExecutingFor(const std::filesystem::path& save_path); + +std::filesystem::path MakeBackupPath(const std::filesystem::path& save_path); + +std::optional PopLastEvent(); + +void PushBackupEvent(BackupRequest&& req); + +float GetProgress(); + +void ClearProgress(); + +} // namespace Backup + +} // namespace Libraries::SaveData diff --git a/src/core/libraries/save_data/save_instance.cpp b/src/core/libraries/save_data/save_instance.cpp new file mode 100644 index 00000000000..2624ca363a2 --- /dev/null +++ b/src/core/libraries/save_data/save_instance.cpp @@ -0,0 +1,228 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include + +#include + +#include "common/assert.h" +#include "common/config.h" +#include "common/path_util.h" +#include "common/singleton.h" +#include "core/file_sys/fs.h" +#include "save_instance.h" + +constexpr u32 OrbisSaveDataBlocksMax = 32768; // 1 GiB +constexpr std::string_view sce_sys = "sce_sys"; // system folder inside save +constexpr std::string_view max_block_file_name = "max_block.txt"; + +static Core::FileSys::MntPoints* g_mnt = Common::Singleton::Instance(); + +namespace fs = std::filesystem; + +// clang-format off +static const std::unordered_map default_title = { + {"ja_JP", "セーブデータ"}, + {"en", "Saved Data"}, + {"fr", "Données sauvegardées"}, + {"es_ES", "Datos guardados"}, + {"de", "Gespeicherte Daten"}, + {"it", "Dati salvati"}, + {"nl", "Opgeslagen data"}, + {"pt_PT", "Dados guardados"}, + {"ru", "Сохраненные данные"}, + {"ko_KR", "저장 데이터"}, + {"zh_CN", "保存数据"}, + {"fi", "Tallennetut tiedot"}, + {"sv_SE", "Sparade data"}, + {"da_DK", "Gemte data"}, + {"no_NO", "Lagrede data"}, + {"pl_PL", "Zapisane dane"}, + {"pt_BR", "Dados salvos"}, + {"tr_TR", "Kayıtlı Veriler"}, +}; +// clang-format on + +namespace Libraries::SaveData { + +std::filesystem::path SaveInstance::MakeTitleSavePath(OrbisUserServiceUserId user_id, + std::string_view game_serial) { + return Common::FS::GetUserPath(Common::FS::PathType::SaveDataDir) / std::to_string(user_id) / + game_serial; +} + +std::filesystem::path SaveInstance::MakeDirSavePath(OrbisUserServiceUserId user_id, + std::string_view game_serial, + std::string_view dir_name) { + return Common::FS::GetUserPath(Common::FS::PathType::SaveDataDir) / std::to_string(user_id) / + game_serial / dir_name; +} + +int SaveInstance::GetMaxBlocks(const std::filesystem::path& save_path) { + Common::FS::IOFile max_blocks_file{save_path / sce_sys / max_block_file_name, + Common::FS::FileAccessMode::Read}; + int max_blocks = 0; + if (max_blocks_file.IsOpen()) { + max_blocks = std::atoi(max_blocks_file.ReadString(16).c_str()); + } + if (max_blocks <= 0) { + max_blocks = OrbisSaveDataBlocksMax; + } + + return max_blocks; +} + +std::filesystem::path SaveInstance::GetParamSFOPath(const std::filesystem::path& dir_path) { + return dir_path / sce_sys / "param.sfo"; +} + +void SaveInstance::SetupDefaultParamSFO(PSF& param_sfo, std::string dir_name, + std::string game_serial) { + std::string locale = Config::getEmulatorLanguage(); + if (!default_title.contains(locale)) { + locale = "en"; + } + +#define P(type, key, ...) param_sfo.Add##type(std::string{key}, __VA_ARGS__) + // TODO Link with user service + P(Binary, SaveParams::ACCOUNT_ID, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}); + P(String, SaveParams::MAINTITLE, default_title.at(locale)); + P(String, SaveParams::SUBTITLE, ""); + P(String, SaveParams::DETAIL, ""); + P(String, SaveParams::SAVEDATA_DIRECTORY, std::move(dir_name)); + P(Integer, SaveParams::SAVEDATA_LIST_PARAM, 0); + P(String, SaveParams::TITLE_ID, std::move(game_serial)); +#undef P +} + +SaveInstance::SaveInstance(int slot_num, OrbisUserServiceUserId user_id, std::string _game_serial, + std::string_view _dir_name, int max_blocks) + : slot_num(slot_num), user_id(user_id), game_serial(std::move(_game_serial)), + dir_name(_dir_name), max_blocks(max_blocks) { + ASSERT(slot_num >= 0 && slot_num < 16); + + save_path = MakeDirSavePath(user_id, game_serial, dir_name); + + const auto sce_sys_path = save_path / sce_sys; + param_sfo_path = sce_sys_path / "param.sfo"; + corrupt_file_path = sce_sys_path / "corrupted"; + + mount_point = "/savedata" + std::to_string(slot_num); + + this->exists = fs::exists(param_sfo_path); + this->mounted = g_mnt->GetMount(mount_point) != nullptr; +} + +SaveInstance::~SaveInstance() { + if (mounted) { + Umount(); + } +} +SaveInstance::SaveInstance(SaveInstance&& other) noexcept { + if (this == &other) + return; + *this = std::move(other); +} + +SaveInstance& SaveInstance::operator=(SaveInstance&& other) noexcept { + if (this == &other) + return *this; + slot_num = other.slot_num; + user_id = other.user_id; + game_serial = std::move(other.game_serial); + dir_name = std::move(other.dir_name); + save_path = std::move(other.save_path); + param_sfo_path = std::move(other.param_sfo_path); + corrupt_file_path = std::move(other.corrupt_file_path); + corrupt_file = std::move(other.corrupt_file); + param_sfo = std::move(other.param_sfo); + mount_point = std::move(other.mount_point); + max_blocks = other.max_blocks; + exists = other.exists; + mounted = other.mounted; + read_only = other.read_only; + + other.mounted = false; + + return *this; +} + +void SaveInstance::SetupAndMount(bool read_only, bool copy_icon, bool ignore_corrupt) { + if (mounted) { + UNREACHABLE_MSG("Save instance is already mounted"); + } + this->exists = fs::exists(param_sfo_path); // check again just in case + if (!exists) { + CreateFiles(); + if (copy_icon) { + const auto& src_icon = g_mnt->GetHostPath("/app0/sce_sys/save_data.png"); + if (fs::exists(src_icon)) { + fs::copy_file(src_icon, GetIconPath()); + } + } + exists = true; + } else { + if (!ignore_corrupt && fs::exists(corrupt_file_path)) { + throw std::filesystem::filesystem_error( + "Corrupted save data", corrupt_file_path, + std::make_error_code(std::errc::illegal_byte_sequence)); + } + if (!param_sfo.Open(param_sfo_path)) { + throw std::filesystem::filesystem_error( + "Failed to read param.sfo", param_sfo_path, + std::make_error_code(std::errc::illegal_byte_sequence)); + } + } + + if (!ignore_corrupt && !read_only) { + int err = corrupt_file.Open(corrupt_file_path, Common::FS::FileAccessMode::Write); + if (err != 0) { + throw std::filesystem::filesystem_error( + "Failed to open corrupted file", corrupt_file_path, + std::make_error_code(std::errc::illegal_byte_sequence)); + } + } + + max_blocks = GetMaxBlocks(save_path); + + g_mnt->Mount(save_path, mount_point, read_only); + mounted = true; + this->read_only = read_only; +} + +void SaveInstance::Umount() { + if (!mounted) { + UNREACHABLE_MSG("Save instance is not mounted"); + return; + } + mounted = false; + const bool ok = param_sfo.Encode(param_sfo_path); + if (!ok) { + throw std::filesystem::filesystem_error("Failed to write param.sfo", param_sfo_path, + std::make_error_code(std::errc::permission_denied)); + } + param_sfo = PSF(); + + corrupt_file.Close(); + fs::remove(corrupt_file_path); + g_mnt->Unmount(save_path, mount_point); +} + +void SaveInstance::CreateFiles() { + const auto sce_sys_dir = save_path / sce_sys; + fs::create_directories(sce_sys_dir); + + SetupDefaultParamSFO(param_sfo, dir_name, game_serial); + + const bool ok = param_sfo.Encode(param_sfo_path); + if (!ok) { + throw std::filesystem::filesystem_error("Failed to write param.sfo", param_sfo_path, + std::make_error_code(std::errc::permission_denied)); + } + + Common::FS::IOFile max_block{sce_sys_dir / max_block_file_name, + Common::FS::FileAccessMode::Write}; + max_block.WriteString(std::to_string(max_blocks == 0 ? OrbisSaveDataBlocksMax : max_blocks)); +} + +} // namespace Libraries::SaveData \ No newline at end of file diff --git a/src/core/libraries/save_data/save_instance.h b/src/core/libraries/save_data/save_instance.h new file mode 100644 index 00000000000..f07011047ce --- /dev/null +++ b/src/core/libraries/save_data/save_instance.h @@ -0,0 +1,138 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "common/io_file.h" +#include "core/file_format/psf.h" + +namespace Core::FileSys { +class MntPoints; +} + +namespace Libraries::SaveData { + +// Used constexpr to easily use as string +namespace SaveParams { +constexpr std::string_view ACCOUNT_ID = "ACCOUNT_ID"; +constexpr std::string_view ATTRIBUTE = "ATTRIBUTE"; +constexpr std::string_view CATEGORY = "CATEGORY"; +constexpr std::string_view DETAIL = "DETAIL"; +constexpr std::string_view FORMAT = "FORMAT"; +constexpr std::string_view MAINTITLE = "MAINTITLE"; +constexpr std::string_view PARAMS = "PARAMS"; +constexpr std::string_view SAVEDATA_BLOCKS = "SAVEDATA_BLOCKS"; +constexpr std::string_view SAVEDATA_DIRECTORY = "SAVEDATA_DIRECTORY"; +constexpr std::string_view SAVEDATA_LIST_PARAM = "SAVEDATA_LIST_PARAM"; +constexpr std::string_view SUBTITLE = "SUBTITLE"; +constexpr std::string_view TITLE_ID = "TITLE_ID"; +} // namespace SaveParams + +using OrbisUserServiceUserId = s32; + +class SaveInstance { + int slot_num{}; + int user_id{}; + std::string game_serial; + std::string dir_name; + + std::filesystem::path save_path; + std::filesystem::path param_sfo_path; + std::filesystem::path corrupt_file_path; + + Common::FS::IOFile corrupt_file; + + PSF param_sfo; + std::string mount_point; + + int max_blocks{}; + bool exists{}; + bool mounted{}; + bool read_only{}; + +public: + // Location of all save data for a title + static std::filesystem::path MakeTitleSavePath(OrbisUserServiceUserId user_id, + std::string_view game_serial); + + // Location of a specific save data directory + static std::filesystem::path MakeDirSavePath(OrbisUserServiceUserId user_id, + std::string_view game_serial, + std::string_view dir_name); + + static int GetMaxBlocks(const std::filesystem::path& save_path); + + // Get param.sfo path from a dir_path generated by MakeDirSavePath + static std::filesystem::path GetParamSFOPath(const std::filesystem::path& dir_path); + + static void SetupDefaultParamSFO(PSF& param_sfo, std::string dir_name, std::string game_serial); + + explicit SaveInstance(int slot_num, OrbisUserServiceUserId user_id, std::string game_serial, + std::string_view dir_name, int max_blocks = 0); + + ~SaveInstance(); + + SaveInstance(const SaveInstance& other) = delete; + SaveInstance(SaveInstance&& other) noexcept; + + SaveInstance& operator=(const SaveInstance& other) = delete; + SaveInstance& operator=(SaveInstance&& other) noexcept; + + void SetupAndMount(bool read_only = false, bool copy_icon = false, bool ignore_corrupt = false); + + void Umount(); + + [[nodiscard]] std::filesystem::path GetIconPath() const noexcept { + return save_path / "sce_sys" / "icon0.png"; + } + + [[nodiscard]] bool Exists() const noexcept { + return exists; + } + + [[nodiscard]] OrbisUserServiceUserId GetUserId() const noexcept { + return user_id; + } + + [[nodiscard]] std::string_view GetTitleId() const noexcept { + return game_serial; + } + + [[nodiscard]] const std::string& GetDirName() const noexcept { + return dir_name; + } + + [[nodiscard]] const std::filesystem::path& GetSavePath() const noexcept { + return save_path; + } + + [[nodiscard]] const PSF& GetParamSFO() const noexcept { + return param_sfo; + } + + [[nodiscard]] PSF& GetParamSFO() noexcept { + return param_sfo; + } + + [[nodiscard]] const std::string& GetMountPoint() const noexcept { + return mount_point; + } + + [[nodiscard]] int GetMaxBlocks() const noexcept { + return max_blocks; + } + + [[nodiscard]] bool Mounted() const noexcept { + return mounted; + } + + [[nodiscard]] bool IsReadOnly() const noexcept { + return read_only; + } + + void CreateFiles(); +}; + +} // namespace Libraries::SaveData diff --git a/src/core/libraries/save_data/save_memory.cpp b/src/core/libraries/save_data/save_memory.cpp new file mode 100644 index 00000000000..6a685ee3916 --- /dev/null +++ b/src/core/libraries/save_data/save_memory.cpp @@ -0,0 +1,287 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "save_memory.h" + +#include +#include +#include +#include +#include + +#include + +#include "common/assert.h" +#include "common/logging/log.h" +#include "common/polyfill_thread.h" +#include "common/singleton.h" +#include "common/thread.h" +#include "core/file_sys/fs.h" +#include "save_instance.h" + +using Common::FS::IOFile; +namespace fs = std::filesystem; + +constexpr std::string_view sce_sys = "sce_sys"; // system folder inside save +constexpr std::string_view DirnameSaveDataMemory = "sce_sdmemory"; +constexpr std::string_view FilenameSaveDataMemory = "memory.dat"; + +namespace Libraries::SaveData::SaveMemory { + +static Core::FileSys::MntPoints* g_mnt = Common::Singleton::Instance(); + +static OrbisUserServiceUserId g_user_id{}; +static std::string g_game_serial{}; +static std::filesystem::path g_save_path{}; +static std::filesystem::path g_param_sfo_path{}; +static PSF g_param_sfo; + +static bool g_save_memory_initialized = false; +static std::mutex g_saving_memory_mutex; +static std::vector g_save_memory; + +static std::filesystem::path g_icon_path; +static std::vector g_icon_memory; + +static std::condition_variable g_trigger_save_memory; +static std::atomic_bool g_saving_memory = false; +static std::atomic_bool g_save_event = false; +static std::jthread g_save_memory_thread; + +static std::atomic_bool g_memory_dirty = false; +static std::atomic_bool g_param_dirty = false; +static std::atomic_bool g_icon_dirty = false; + +static void SaveFileSafe(void* buf, size_t count, const std::filesystem::path& path) { + const auto& dir = path.parent_path(); + const auto& name = path.filename(); + const auto tmp_path = dir / (name.string() + ".tmp"); + + IOFile file(tmp_path, Common::FS::FileAccessMode::Write); + file.WriteRaw(buf, count); + file.Close(); + + fs::remove(path); + fs::rename(tmp_path, path); +} + +[[noreturn]] void SaveThreadLoop() { + Common::SetCurrentThreadName("SaveData_SaveDataMemoryThread"); + std::mutex mtx; + while (true) { + { + std::unique_lock lk{mtx}; + g_trigger_save_memory.wait(lk); + } + // Save the memory + g_saving_memory = true; + std::scoped_lock lk{g_saving_memory_mutex}; + try { + LOG_DEBUG(Lib_SaveData, "Saving save data memory {}", g_save_path.string()); + + if (g_memory_dirty) { + g_memory_dirty = false; + SaveFileSafe(g_save_memory.data(), g_save_memory.size(), + g_save_path / FilenameSaveDataMemory); + } + if (g_param_dirty) { + g_param_dirty = false; + static std::vector buf; + g_param_sfo.Encode(buf); + SaveFileSafe(buf.data(), buf.size(), g_param_sfo_path); + } + if (g_icon_dirty) { + g_icon_dirty = false; + SaveFileSafe(g_icon_memory.data(), g_icon_memory.size(), g_icon_path); + } + + if (g_save_event) { + Backup::PushBackupEvent(Backup::BackupRequest{ + .user_id = g_user_id, + .title_id = g_game_serial, + .dir_name = std::string{DirnameSaveDataMemory}, + .origin = Backup::OrbisSaveDataEventType::SAVE_DATA_MEMORY_SYNC, + .save_path = g_save_path, + }); + g_save_event = false; + } + } catch (const fs::filesystem_error& e) { + LOG_ERROR(Lib_SaveData, "Failed to save save data memory: {}", e.what()); + MsgDialog::ShowMsgDialog(MsgDialog::MsgDialogState{ + MsgDialog::MsgDialogState::UserState{ + .type = MsgDialog::ButtonType::OK, + .msg = fmt::format("Failed to save save data memory.\nCode: <{}>\n{}", + e.code().message(), e.what()), + }, + }); + } + g_saving_memory = false; + } +} + +void SetDirectories(OrbisUserServiceUserId user_id, std::string _game_serial) { + g_user_id = user_id; + g_game_serial = std::move(_game_serial); + g_save_path = SaveInstance::MakeDirSavePath(user_id, g_game_serial, DirnameSaveDataMemory); + g_param_sfo_path = SaveInstance::GetParamSFOPath(g_save_path); + g_param_sfo = PSF(); + g_icon_path = g_save_path / sce_sys / "icon0.png"; +} + +const std::filesystem::path& GetSavePath() { + return g_save_path; +} + +size_t CreateSaveMemory(size_t memory_size) { + size_t existed_size = 0; + + static std::once_flag init_save_thread_flag; + std::call_once(init_save_thread_flag, + [] { g_save_memory_thread = std::jthread{SaveThreadLoop}; }); + + g_save_memory.resize(memory_size); + SaveInstance::SetupDefaultParamSFO(g_param_sfo, std::string{DirnameSaveDataMemory}, + g_game_serial); + + g_save_memory_initialized = true; + + if (!fs::exists(g_param_sfo_path)) { + // Create save memory + fs::create_directories(g_save_path / sce_sys); + + IOFile memory_file{g_save_path / FilenameSaveDataMemory, Common::FS::FileAccessMode::Write}; + bool ok = memory_file.SetSize(memory_size); + if (!ok) { + LOG_ERROR(Lib_SaveData, "Failed to set memory size"); + throw std::filesystem::filesystem_error( + "Failed to set save memory size", g_save_path / FilenameSaveDataMemory, + std::make_error_code(std::errc::no_space_on_device)); + } + memory_file.Close(); + } else { + // Load save memory + + bool ok = g_param_sfo.Open(g_param_sfo_path); + if (!ok) { + LOG_ERROR(Lib_SaveData, "Failed to open SFO at {}", g_param_sfo_path.string()); + throw std::filesystem::filesystem_error( + "failed to open SFO", g_param_sfo_path, + std::make_error_code(std::errc::illegal_byte_sequence)); + } + + IOFile memory_file{g_save_path / FilenameSaveDataMemory, Common::FS::FileAccessMode::Read}; + if (!memory_file.IsOpen()) { + LOG_ERROR(Lib_SaveData, "Failed to open save memory"); + throw std::filesystem::filesystem_error( + "failed to open save memory", g_save_path / FilenameSaveDataMemory, + std::make_error_code(std::errc::permission_denied)); + } + size_t save_size = memory_file.GetSize(); + existed_size = save_size; + memory_file.Seek(0); + memory_file.ReadRaw(g_save_memory.data(), std::min(save_size, memory_size)); + memory_file.Close(); + } + + return existed_size; +} + +void SetIcon(void* buf, size_t buf_size) { + if (buf == nullptr) { + const auto& src_icon = g_mnt->GetHostPath("/app0/sce_sys/save_data.png"); + if (fs::exists(src_icon)) { + fs::copy_file(src_icon, g_icon_path); + } + IOFile file(g_icon_path, Common::FS::FileAccessMode::Read); + size_t size = file.GetSize(); + file.Seek(0); + g_icon_memory.resize(size); + file.ReadRaw(g_icon_memory.data(), size); + file.Close(); + } else { + g_icon_memory.resize(buf_size); + std::memcpy(g_icon_memory.data(), buf, buf_size); + IOFile file(g_icon_path, Common::FS::FileAccessMode::Append); + file.Seek(0); + file.WriteRaw(g_icon_memory.data(), buf_size); + file.Close(); + } +} + +void WriteIcon(void* buf, size_t buf_size) { + if (buf_size != g_icon_memory.size()) { + g_icon_memory.resize(buf_size); + } + std::memcpy(g_icon_memory.data(), buf, buf_size); + g_icon_dirty = true; +} + +bool IsSaveMemoryInitialized() { + return g_save_memory_initialized; +} + +PSF& GetParamSFO() { + return g_param_sfo; +} + +std::span GetIcon() { + return {g_icon_memory}; +} + +void SaveSFO(bool sync) { + if (!sync) { + g_param_dirty = true; + return; + } + const bool ok = g_param_sfo.Encode(g_param_sfo_path); + if (!ok) { + LOG_ERROR(Lib_SaveData, "Failed to encode param.sfo"); + throw std::filesystem::filesystem_error("Failed to write param.sfo", g_param_sfo_path, + std::make_error_code(std::errc::permission_denied)); + } +} +bool IsSaving() { + return g_saving_memory; +} + +bool TriggerSaveWithoutEvent() { + if (g_saving_memory) { + return false; + } + g_trigger_save_memory.notify_one(); + return true; +} + +bool TriggerSave() { + if (g_saving_memory) { + return false; + } + g_save_event = true; + g_trigger_save_memory.notify_one(); + return true; +} + +void ReadMemory(void* buf, size_t buf_size, int64_t offset) { + std::scoped_lock lk{g_saving_memory_mutex}; + if (offset > g_save_memory.size()) { + UNREACHABLE_MSG("ReadMemory out of bounds"); + } + if (offset + buf_size > g_save_memory.size()) { + UNREACHABLE_MSG("ReadMemory out of bounds"); + } + std::memcpy(buf, g_save_memory.data() + offset, buf_size); +} + +void WriteMemory(void* buf, size_t buf_size, int64_t offset) { + std::scoped_lock lk{g_saving_memory_mutex}; + if (offset > g_save_memory.size()) { + UNREACHABLE_MSG("WriteMemory out of bounds"); + } + if (offset + buf_size > g_save_memory.size()) { + UNREACHABLE_MSG("WriteMemory out of bounds"); + } + std::memcpy(g_save_memory.data() + offset, buf, buf_size); + g_memory_dirty = true; +} + +} // namespace Libraries::SaveData::SaveMemory \ No newline at end of file diff --git a/src/core/libraries/save_data/save_memory.h b/src/core/libraries/save_data/save_memory.h new file mode 100644 index 00000000000..04eeaa65260 --- /dev/null +++ b/src/core/libraries/save_data/save_memory.h @@ -0,0 +1,49 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include "save_backup.h" + +class PSF; + +namespace Libraries::SaveData { +using OrbisUserServiceUserId = s32; +} + +namespace Libraries::SaveData::SaveMemory { + +void SetDirectories(OrbisUserServiceUserId user_id, std::string game_serial); + +[[nodiscard]] const std::filesystem::path& GetSavePath(); + +// returns the size of the existed save memory +size_t CreateSaveMemory(size_t memory_size); + +// Initialize the icon. Set buf to null to read the standard icon. +void SetIcon(void* buf, size_t buf_size); + +// Update the icon +void WriteIcon(void* buf, size_t buf_size); + +[[nodiscard]] bool IsSaveMemoryInitialized(); + +[[nodiscard]] PSF& GetParamSFO(); + +[[nodiscard]] std::span GetIcon(); + +// Save now or wait for the background thread to save +void SaveSFO(bool sync = false); + +[[nodiscard]] bool IsSaving(); + +bool TriggerSaveWithoutEvent(); + +bool TriggerSave(); + +void ReadMemory(void* buf, size_t buf_size, int64_t offset); + +void WriteMemory(void* buf, size_t buf_size, int64_t offset); + +} // namespace Libraries::SaveData::SaveMemory \ No newline at end of file diff --git a/src/core/libraries/save_data/savedata.cpp b/src/core/libraries/save_data/savedata.cpp index d5ea76e08af..d62c1b9a1ba 100644 --- a/src/core/libraries/save_data/savedata.cpp +++ b/src/core/libraries/save_data/savedata.cpp @@ -4,30 +4,552 @@ #include #include +#include +#include + #include "common/assert.h" +#include "common/cstring.h" +#include "common/elf_info.h" +#include "common/enum.h" #include "common/logging/log.h" #include "common/path_util.h" -#include "common/singleton.h" +#include "common/string_util.h" #include "core/file_format/psf.h" #include "core/file_sys/fs.h" #include "core/libraries/error_codes.h" #include "core/libraries/libs.h" #include "core/libraries/save_data/savedata.h" -#include "error_codes.h" +#include "core/libraries/system/msgdialog.h" +#include "save_backup.h" +#include "save_instance.h" +#include "save_memory.h" + +namespace fs = std::filesystem; +namespace chrono = std::chrono; + +using Common::CString; +using Common::ElfInfo; namespace Libraries::SaveData { -bool is_rw_mode = false; -static constexpr std::string_view g_mount_point = "/savedata0"; // temp mount point (todo) -std::string game_serial; + +enum class Error : u32 { + OK = 0, + USER_SERVICE_NOT_INITIALIZED = 0x80960002, + PARAMETER = 0x809F0000, + NOT_INITIALIZED = 0x809F0001, + OUT_OF_MEMORY = 0x809F0002, + BUSY = 0x809F0003, + NOT_MOUNTED = 0x809F0004, + EXISTS = 0x809F0007, + NOT_FOUND = 0x809F0008, + NO_SPACE_FS = 0x809F000A, + INTERNAL = 0x809F000B, + MOUNT_FULL = 0x809F000C, + BAD_MOUNTED = 0x809F000D, + BROKEN = 0x809F000F, + INVALID_LOGIN_USER = 0x809F0011, + MEMORY_NOT_READY = 0x809F0012, + BACKUP_BUSY = 0x809F0013, + BUSY_FOR_SAVING = 0x809F0016, +}; + +enum class OrbisSaveDataSaveDataMemoryOption : u32 { + NONE = 0, + SET_PARAM = 1 << 0, + DOUBLE_BUFFER = 1 << 1, + UNLOCK_LIMITATIONS = 1 << 2, +}; + +using OrbisUserServiceUserId = s32; +using OrbisSaveDataBlocks = u64; + +constexpr u32 OrbisSaveDataBlockSize = 32768; // 32 KiB +constexpr u32 OrbisSaveDataBlocksMin2 = 96; // 3MiB +constexpr u32 OrbisSaveDataBlocksMax = 32768; // 1 GiB + +// Maximum size for a mount point "/savedataN", where N is a number +constexpr size_t OrbisSaveDataMountPointDataMaxsize = 16; + +constexpr size_t OrbisSaveDataFingerprintDataSize = 65; // Maximum fingerprint size + +enum class OrbisSaveDataMountMode : u32 { + RDONLY = 1 << 0, + RDWR = 1 << 1, + CREATE = 1 << 2, + DESTRUCT_OFF = 1 << 3, + COPY_ICON = 1 << 4, + CREATE2 = 1 << 5, +}; +DECLARE_ENUM_FLAG_OPERATORS(OrbisSaveDataMountMode); + +enum class OrbisSaveDataMountStatus : u32 { + NOTHING = 0, + CREATED = 1, +}; + +enum class OrbisSaveDataParamType : u32 { + ALL = 0, + TITLE = 1, + SUB_TITLE = 2, + DETAIL = 3, + USER_PARAM = 4, + MTIME = 5, +}; + +enum class OrbisSaveDataSortKey : u32 { + DIRNAME = 0, + USER_PARAM = 1, + BLOCKS = 2, + MTIME = 3, + FREE_BLOCKS = 5, +}; + +enum class OrbisSaveDataSortOrder : u32 { + ASCENT = 0, + DESCENT = 1, +}; + +struct OrbisSaveDataFingerprint { + CString data; + std::array _pad; +}; + +struct OrbisSaveDataBackup { + OrbisUserServiceUserId userId; + s32 : 32; + const OrbisSaveDataTitleId* titleId; + const OrbisSaveDataDirName* dirName; + const OrbisSaveDataFingerprint* param; + std::array _reserved; +}; + +struct OrbisSaveDataCheckBackupData { + OrbisUserServiceUserId userId; + s32 : 32; + const OrbisSaveDataTitleId* titleId; + const OrbisSaveDataDirName* dirName; + OrbisSaveDataParam* param; + OrbisSaveDataIcon* icon; + std::array _reserved; +}; + +struct OrbisSaveDataDelete { + OrbisUserServiceUserId userId; + s32 : 32; + const OrbisSaveDataTitleId* titleId; + const OrbisSaveDataDirName* dirName; + u32 _unused; + std::array _reserved; + s32 : 32; +}; + +struct OrbisSaveDataIcon { + void* buf; + size_t bufSize; + size_t dataSize; + std::array _reserved; + + Error LoadIcon(const std::filesystem::path& icon_path) { + try { + const Common::FS::IOFile file(icon_path, Common::FS::FileAccessMode::Read); + dataSize = file.GetSize(); + file.Seek(0); + file.ReadRaw(buf, std::min(bufSize, dataSize)); + } catch (const fs::filesystem_error& e) { + LOG_ERROR(Lib_SaveData, "Failed to load icon: {}", e.what()); + return Error::INTERNAL; + } + return Error::OK; + } +}; + +struct OrbisSaveDataMemoryData { + void* buf; + size_t bufSize; + s64 offset; + u8 _reserved[40]; +}; + +struct OrbisSaveDataMemoryGet2 { + OrbisUserServiceUserId userId; + std::array _pad; + OrbisSaveDataMemoryData* data; + OrbisSaveDataParam* param; + OrbisSaveDataIcon* icon; + std::array _reserved; +}; + +struct OrbisSaveDataMemorySet2 { + OrbisUserServiceUserId userId; + std::array _pad; + const OrbisSaveDataMemoryData* data; + const OrbisSaveDataParam* param; + const OrbisSaveDataIcon* icon; + std::array _reserved; +}; + +struct OrbisSaveDataMemorySetup2 { + OrbisSaveDataSaveDataMemoryOption option; + OrbisUserServiceUserId userId; + size_t memorySize; + size_t iconMemorySize; + // +4.5 + const OrbisSaveDataParam* initParam; + // +4.5 + const OrbisSaveDataIcon* initIcon; + std::array _reserved; +}; + +struct OrbisSaveDataMemorySetupResult { + size_t existedMemorySize; + std::array _reserved; +}; + +struct OrbisSaveDataMemorySync { + OrbisUserServiceUserId userId; + std::array _reserved; +}; + +struct OrbisSaveDataMount2 { + OrbisUserServiceUserId userId; + s32 : 32; + const OrbisSaveDataDirName* dirName; + OrbisSaveDataBlocks blocks; + OrbisSaveDataMountMode mountMode; + std::array _reserved; + s32 : 32; +}; + +struct OrbisSaveDataMount { + OrbisUserServiceUserId userId; + s32 : 32; + const OrbisSaveDataTitleId* titleId; + const OrbisSaveDataDirName* dirName; + const OrbisSaveDataFingerprint* fingerprint; + OrbisSaveDataBlocks blocks; + OrbisSaveDataMountMode mountMode; + std::array _reserved; +}; + +struct OrbisSaveDataMountInfo { + OrbisSaveDataBlocks blocks; + OrbisSaveDataBlocks freeBlocks; + std::array _reserved; +}; + +struct OrbisSaveDataMountPoint { + CString data; +}; + +struct OrbisSaveDataMountResult { + OrbisSaveDataMountPoint mount_point; + OrbisSaveDataBlocks required_blocks; + u32 _unused; + // +4.5 + OrbisSaveDataMountStatus mount_status; + std::array _reserved; + s32 : 32; +}; + +struct OrbisSaveDataRestoreBackupData { + OrbisUserServiceUserId userId; + s32 : 32; + const OrbisSaveDataTitleId* titleId; + const OrbisSaveDataDirName* dirName; + const OrbisSaveDataFingerprint* fingerprint; + u32 _unused; + std::array _reserved; + s32 : 32; +}; + +struct OrbisSaveDataDirNameSearchCond { + OrbisUserServiceUserId userId; + int : 32; + const OrbisSaveDataTitleId* titleId; + const OrbisSaveDataDirName* dirName; + OrbisSaveDataSortKey key; + OrbisSaveDataSortOrder order; + std::array _reserved; +}; + +struct OrbisSaveDataSearchInfo { + u64 blocks; + u64 freeBlocks; + std::array _reserved; +}; + +struct OrbisSaveDataDirNameSearchResult { + u32 hitNum; + int : 32; + OrbisSaveDataDirName* dirNames; + u32 dirNamesNum; + // +1.7 + u32 setNum; + // +1.7 + OrbisSaveDataParam* params; + // +2.5 + OrbisSaveDataSearchInfo* infos; + std::array _reserved; + int : 32; +}; + +struct OrbisSaveDataEventParam { // dummy structure + OrbisSaveDataEventParam() = delete; +}; + +using OrbisSaveDataEventType = Backup::OrbisSaveDataEventType; + +struct OrbisSaveDataEvent { + OrbisSaveDataEventType type; + s32 errorCode; + OrbisUserServiceUserId userId; + std::array _pad; + OrbisSaveDataTitleId titleId; + OrbisSaveDataDirName dirName; + std::array _reserved; +}; + +static bool g_initialized = false; +static std::string g_game_serial; +static u32 g_fw_ver; +static std::array, 16> g_mount_slots; + +static void initialize() { + g_initialized = true; + g_game_serial = ElfInfo::Instance().GameSerial(); + g_fw_ver = ElfInfo::Instance().FirmwareVer(); +} + +// game_00other | game*other + +static bool match(std::string_view str, std::string_view pattern) { + auto str_it = str.begin(); + auto pat_it = pattern.begin(); + while (str_it != str.end() && pat_it != pattern.end()) { + if (*pat_it == '%') { // 0 or more wildcard + for (auto str_wild_it = str_it; str_wild_it <= str.end(); ++str_wild_it) { + if (match({str_wild_it, str.end()}, {pat_it + 1, pattern.end()})) { + return true; + } + } + return false; + } + if (*pat_it == '_') { // 1 character wildcard + ++str_it; + ++pat_it; + } else if (*pat_it != *str_it) { + return false; + } + ++str_it; + ++pat_it; + } + return str_it == str.end() && pat_it == pattern.end(); +} + +static Error setNotInitializedError() { + if (g_fw_ver < ElfInfo::FW_20) { + return Error::INTERNAL; + } + if (g_fw_ver < ElfInfo::FW_25) { + return Error::USER_SERVICE_NOT_INITIALIZED; + } + return Error::NOT_INITIALIZED; +} + +static Error saveDataMount(const OrbisSaveDataMount2* mount_info, + OrbisSaveDataMountResult* mount_result) { + + if (mount_info->userId < 0) { + return Error::INVALID_LOGIN_USER; + } + if (mount_info->dirName == nullptr) { + LOG_INFO(Lib_SaveData, "called without dirName"); + return Error::PARAMETER; + } + + // check backup status + { + const auto save_path = SaveInstance::MakeDirSavePath(mount_info->userId, g_game_serial, + mount_info->dirName->data); + if (Backup::IsBackupExecutingFor(save_path) && g_fw_ver) { + return Error::BACKUP_BUSY; + } + } + + auto mount_mode = mount_info->mountMode; + const bool is_ro = True(mount_mode & OrbisSaveDataMountMode::RDONLY); + + const bool create = True(mount_mode & OrbisSaveDataMountMode::CREATE); + const bool create_if_not_exist = + True(mount_mode & OrbisSaveDataMountMode::CREATE2) && g_fw_ver >= ElfInfo::FW_45; + ASSERT(!create || !create_if_not_exist); // Can't have both + + const bool copy_icon = True(mount_mode & OrbisSaveDataMountMode::COPY_ICON); + + const bool ignore_corrupt = + True(mount_mode & OrbisSaveDataMountMode::DESTRUCT_OFF) || g_fw_ver < ElfInfo::FW_16; + + const std::string_view dir_name{mount_info->dirName->data}; + + // find available mount point + int slot_num = -1; + for (size_t i = 0; i < g_mount_slots.size(); i++) { + const auto& instance = g_mount_slots[i]; + if (instance.has_value()) { + const auto& slot_name = instance->GetDirName(); + if (slot_name == dir_name) { + return Error::BUSY; + } + } else { + slot_num = static_cast(i); + break; + } + } + if (slot_num == -1) { + return Error::MOUNT_FULL; + } + + SaveInstance save_instance{slot_num, mount_info->userId, g_game_serial, dir_name, + (int)mount_info->blocks}; + + if (save_instance.Mounted()) { + UNREACHABLE_MSG("Save instance should not be mounted"); + } + + if (!create && !create_if_not_exist && !save_instance.Exists()) { + return Error::NOT_FOUND; + } + if (create && save_instance.Exists()) { + return Error::EXISTS; + } + + bool to_be_created = !save_instance.Exists(); + + if (to_be_created) { // Check size + + if (mount_info->blocks < OrbisSaveDataBlocksMin2 || + mount_info->blocks > OrbisSaveDataBlocksMax) { + LOG_INFO(Lib_SaveData, "called with invalid block size"); + } + + const auto root_save = Common::FS::GetUserPath(Common::FS::PathType::SaveDataDir); + fs::create_directories(root_save); + const auto available = fs::space(root_save).available; + + auto requested_size = mount_info->blocks * OrbisSaveDataBlockSize; + if (requested_size > available) { + mount_result->required_blocks = (requested_size - available) / OrbisSaveDataBlockSize; + return Error::NO_SPACE_FS; + } + } + + try { + save_instance.SetupAndMount(is_ro, copy_icon, ignore_corrupt); + } catch (const fs::filesystem_error& e) { + if (e.code() == std::errc::illegal_byte_sequence) { + LOG_ERROR(Lib_SaveData, "Corrupted save data"); + return Error::BROKEN; + } + if (e.code() == std::errc::no_space_on_device) { + return Error::NO_SPACE_FS; + } + LOG_ERROR(Lib_SaveData, "Failed to mount save data: {}", e.what()); + return Error::INTERNAL; + } + + mount_result->mount_point.data.FromString(save_instance.GetMountPoint()); + + if (g_fw_ver >= ElfInfo::FW_45) { + mount_result->mount_status = create_if_not_exist && to_be_created + ? OrbisSaveDataMountStatus::CREATED + : OrbisSaveDataMountStatus::NOTHING; + } + + g_mount_slots[slot_num].emplace(std::move(save_instance)); + + return Error::OK; +} + +static Error Umount(const OrbisSaveDataMountPoint* mountPoint, bool call_backup = false) { + if (!g_initialized) { + LOG_INFO(Lib_SaveData, "called without initialize"); + return setNotInitializedError(); + } + if (mountPoint == nullptr) { + LOG_INFO(Lib_SaveData, "called with invalid parameter"); + return Error::PARAMETER; + } + LOG_DEBUG(Lib_SaveData, "Umount mountPoint:{}", mountPoint->data.to_view()); + const std::string_view mount_point_str{mountPoint->data}; + for (auto& instance : g_mount_slots) { + if (instance.has_value()) { + const auto& slot_name = instance->GetMountPoint(); + if (slot_name == mount_point_str) { + if (call_backup) { + Backup::StartThread(); + Backup::NewRequest(instance->GetUserId(), instance->GetTitleId(), + instance->GetDirName(), + OrbisSaveDataEventType::UMOUNT_BACKUP); + } + // TODO: check if is busy + instance->Umount(); + instance.reset(); + return Error::OK; + } + } + } + return Error::NOT_FOUND; +} + +void OrbisSaveDataParam::FromSFO(const PSF& sfo) { + memset(this, 0, sizeof(OrbisSaveDataParam)); + title.FromString(sfo.GetString(SaveParams::MAINTITLE).value_or("Unknown")); + subTitle.FromString(sfo.GetString(SaveParams::SUBTITLE).value_or("")); + detail.FromString(sfo.GetString(SaveParams::DETAIL).value_or("")); + userParam = sfo.GetInteger(SaveParams::SAVEDATA_LIST_PARAM).value_or(0); + const auto time_since_epoch = sfo.GetLastWrite().time_since_epoch(); + mtime = chrono::duration_cast(time_since_epoch).count(); +} + +void OrbisSaveDataParam::ToSFO(PSF& sfo) const { + sfo.AddString(std::string{SaveParams::MAINTITLE}, std::string{title}, true); + sfo.AddString(std::string{SaveParams::SUBTITLE}, std::string{subTitle}, true); + sfo.AddString(std::string{SaveParams::DETAIL}, std::string{detail}, true); + sfo.AddInteger(std::string{SaveParams::SAVEDATA_LIST_PARAM}, static_cast(userParam), true); +} int PS4_SYSV_ABI sceSaveDataAbort() { LOG_ERROR(Lib_SaveData, "(STUBBED) called"); return ORBIS_OK; } -int PS4_SYSV_ABI sceSaveDataBackup() { - LOG_ERROR(Lib_SaveData, "(STUBBED) called"); - return ORBIS_OK; +Error PS4_SYSV_ABI sceSaveDataBackup(const OrbisSaveDataBackup* backup) { + if (!g_initialized) { + LOG_INFO(Lib_SaveData, "called without initialize"); + return setNotInitializedError(); + } + if (backup == nullptr || backup->dirName == nullptr) { + LOG_INFO(Lib_SaveData, "called with invalid parameter"); + return Error::PARAMETER; + } + + const std::string_view dir_name{backup->dirName->data}; + LOG_DEBUG(Lib_SaveData, "called dirName: {}", dir_name); + + std::string_view title{backup->titleId != nullptr ? std::string_view{backup->titleId->data} + : std::string_view{g_game_serial}}; + + for (const auto& instance : g_mount_slots) { + if (instance.has_value() && instance->GetUserId() == backup->userId && + instance->GetTitleId() == title && instance->GetDirName() == dir_name) { + return Error::BUSY; + } + } + + Backup::StartThread(); + Backup::NewRequest(backup->userId, title, dir_name, OrbisSaveDataEventType::BACKUP); + + return Error::OK; } int PS4_SYSV_ABI sceSaveDataBindPsnAccount() { @@ -50,15 +572,55 @@ int PS4_SYSV_ABI sceSaveDataChangeInternal() { return ORBIS_OK; } -int PS4_SYSV_ABI sceSaveDataCheckBackupData(const OrbisSaveDataCheckBackupData* check) { - auto* mnt = Common::Singleton::Instance(); - const auto mount_dir = mnt->GetHostPath(check->dirName->data); - if (!std::filesystem::exists(mount_dir)) { - return ORBIS_SAVE_DATA_ERROR_NOT_FOUND; +Error PS4_SYSV_ABI sceSaveDataCheckBackupData(const OrbisSaveDataCheckBackupData* check) { + if (!g_initialized) { + LOG_INFO(Lib_SaveData, "called without initialize"); + return setNotInitializedError(); + } + if (check == nullptr || check->dirName == nullptr) { + LOG_INFO(Lib_SaveData, "called with invalid parameter"); + return Error::PARAMETER; } - LOG_INFO(Lib_SaveData, "called = {}", mount_dir.string()); + LOG_DEBUG(Lib_SaveData, "called with titleId={}", check->titleId->data.to_view()); - return ORBIS_OK; + const std::string_view title{check->titleId != nullptr ? std::string_view{check->titleId->data} + : std::string_view{g_game_serial}}; + + const auto save_path = + SaveInstance::MakeDirSavePath(check->userId, title, check->dirName->data); + + for (const auto& instance : g_mount_slots) { + if (instance.has_value() && instance->GetSavePath() == save_path) { + return Error::BUSY; + } + } + + if (Backup::IsBackupExecutingFor(save_path)) { + return Error::BACKUP_BUSY; + } + + const auto backup_path = Backup::MakeBackupPath(save_path); + if (!fs::exists(backup_path)) { + return Error::NOT_FOUND; + } + + if (check->param != nullptr) { + PSF sfo; + if (!sfo.Open(backup_path / "sce_sys" / "param.sfo")) { + LOG_ERROR(Lib_SaveData, "Failed to read SFO at {}", backup_path.string()); + return Error::INTERNAL; + } + check->param->FromSFO(sfo); + } + + if (check->icon != nullptr) { + const auto icon_path = backup_path / "sce_sys" / "icon0.png"; + if (fs::exists(icon_path) && check->icon->LoadIcon(icon_path) != Error::OK) { + return Error::INTERNAL; + } + } + + return Error::OK; } int PS4_SYSV_ABI sceSaveDataCheckBackupDataForCdlg() { @@ -96,9 +658,14 @@ int PS4_SYSV_ABI sceSaveDataCheckSaveDataVersionLatest() { return ORBIS_OK; } -int PS4_SYSV_ABI sceSaveDataClearProgress() { - LOG_ERROR(Lib_SaveData, "(STUBBED) called"); - return ORBIS_OK; +Error PS4_SYSV_ABI sceSaveDataClearProgress() { + if (!g_initialized) { + LOG_INFO(Lib_SaveData, "called without initialize"); + return setNotInitializedError(); + } + LOG_DEBUG(Lib_SaveData, "called"); + Backup::ClearProgress(); + return Error::OK; } int PS4_SYSV_ABI sceSaveDataCopy5() { @@ -146,15 +713,35 @@ int PS4_SYSV_ABI sceSaveDataDebugTarget() { return ORBIS_OK; } -int PS4_SYSV_ABI sceSaveDataDelete(const OrbisSaveDataDelete* del) { - const auto& mount_dir = Common::FS::GetUserPath(Common::FS::PathType::SaveDataDir) / - std::to_string(1) / game_serial / std::string(del->dirName->data); - LOG_INFO(Lib_SaveData, "called: dirname = {}, mount_dir = {}", (char*)del->dirName->data, - mount_dir.string()); - if (std::filesystem::exists(mount_dir) && std::filesystem::is_directory(mount_dir)) { - std::filesystem::remove_all(mount_dir); +Error PS4_SYSV_ABI sceSaveDataDelete(const OrbisSaveDataDelete* del) { + if (!g_initialized) { + LOG_INFO(Lib_SaveData, "called without initialize"); + return setNotInitializedError(); } - return ORBIS_OK; + if (del == nullptr) { + LOG_INFO(Lib_SaveData, "called with invalid parameter"); + return Error::PARAMETER; + } + const std::string_view dirName{del->dirName->data}; + LOG_DEBUG(Lib_SaveData, "called dirName: {}", dirName); + if (dirName.empty()) { + return Error::PARAMETER; + } + for (const auto& instance : g_mount_slots) { + if (instance.has_value() && instance->GetDirName() == dirName) { + return Error::BUSY; + } + } + const auto save_path = SaveInstance::MakeDirSavePath(del->userId, g_game_serial, dirName); + try { + if (fs::exists(save_path)) { + fs::remove_all(save_path); + } + } catch (const fs::filesystem_error& e) { + LOG_ERROR(Lib_SaveData, "Failed to delete save data: {}", e.what()); + return Error::INTERNAL; + } + return Error::OK; } int PS4_SYSV_ABI sceSaveDataDelete5() { @@ -177,46 +764,123 @@ int PS4_SYSV_ABI sceSaveDataDeleteUser() { return ORBIS_OK; } -int PS4_SYSV_ABI sceSaveDataDirNameSearch(const OrbisSaveDataDirNameSearchCond* cond, - OrbisSaveDataDirNameSearchResult* result) { - if (cond == nullptr) - return ORBIS_SAVE_DATA_ERROR_PARAMETER; - LOG_INFO(Lib_SaveData, "called"); - const auto& mount_dir = Common::FS::GetUserPath(Common::FS::PathType::SaveDataDir) / - std::to_string(cond->userId) / game_serial; - if (!mount_dir.empty() && std::filesystem::exists(mount_dir)) { - if (cond->dirName == nullptr || std::string_view(cond->dirName->data) - .empty()) { // look for all dirs if no dir is provided. - for (int i = 0; const auto& entry : std::filesystem::directory_iterator(mount_dir)) { - if (std::filesystem::is_directory(entry.path()) && - entry.path().filename().string() != "sdmemory") { - // sceSaveDataDirNameSearch does not search for dataMemory1/2 dirs. - // copy dir name to be used by sceSaveDataMount in read mode. - strncpy(result->dirNames[i].data, entry.path().filename().string().c_str(), 32); - i++; - result->hitNum = i; - result->dirNamesNum = i; - result->setNum = i; - } - } - } else { // Need a game to test. - LOG_ERROR(Lib_SaveData, "Check Me. sceSaveDataDirNameSearch: dirName = {}", - cond->dirName->data); - strncpy(result->dirNames[0].data, cond->dirName->data, 32); - result->hitNum = 1; - result->dirNamesNum = 1; - result->setNum = 1; +Error PS4_SYSV_ABI sceSaveDataDirNameSearch(const OrbisSaveDataDirNameSearchCond* cond, + OrbisSaveDataDirNameSearchResult* result) { + if (!g_initialized) { + LOG_INFO(Lib_SaveData, "called without initialize"); + return setNotInitializedError(); + } + if (cond == nullptr || result == nullptr || cond->key > OrbisSaveDataSortKey::FREE_BLOCKS || + cond->order > OrbisSaveDataSortOrder::DESCENT) { + LOG_INFO(Lib_SaveData, "called with invalid parameter"); + return Error::PARAMETER; + } + LOG_DEBUG(Lib_SaveData, "called"); + const std::string_view title_id{cond->titleId == nullptr + ? std::string_view{g_game_serial} + : std::string_view{cond->titleId->data}}; + const auto save_path = SaveInstance::MakeTitleSavePath(cond->userId, title_id); + + if (!fs::exists(save_path)) { + result->hitNum = 0; + if (g_fw_ver >= ElfInfo::FW_17) { + result->setNum = 0; + } + return Error::OK; + } + + std::vector dir_list; + + for (const auto& path : fs::directory_iterator{save_path}) { + auto dir_name = path.path().filename().string(); + // skip non-directories, sce_* and directories without param.sfo + if (fs::is_directory(path) && !dir_name.starts_with("sce_") && + fs::exists(SaveInstance::GetParamSFOPath(path))) { + dir_list.push_back(dir_name); + } + } + if (cond->dirName != nullptr) { + // Filter names + const auto pat = Common::ToLower(std::string_view{cond->dirName->data}); + if (!pat.empty()) { + std::erase_if(dir_list, [&](const std::string& dir_name) { + return !match(Common::ToLower(dir_name), pat); + }); + } + } + + std::unordered_map map_dir_sfo; + std::unordered_map map_max_blocks; + std::unordered_map map_free_size; + + for (const auto& dir_name : dir_list) { + const auto dir_path = SaveInstance::MakeDirSavePath(cond->userId, title_id, dir_name); + const auto sfo_path = SaveInstance::GetParamSFOPath(dir_path); + PSF sfo; + if (!sfo.Open(sfo_path)) { + LOG_ERROR(Lib_SaveData, "Failed to read SFO: {}", sfo_path.string()); + ASSERT_MSG(false, "Failed to read SFO"); } + map_dir_sfo.emplace(dir_name, std::move(sfo)); + + size_t size = Common::FS::GetDirectorySize(dir_path); + size_t total = SaveInstance::GetMaxBlocks(dir_path); + map_free_size.emplace(dir_name, total - size / OrbisSaveDataBlockSize); + map_max_blocks.emplace(dir_name, total); + } + +#define PROJ(x) [&](const auto& v) { return x; } + switch (cond->key) { + case OrbisSaveDataSortKey::DIRNAME: + std::ranges::stable_sort(dir_list); + break; + case OrbisSaveDataSortKey::USER_PARAM: + std::ranges::stable_sort( + dir_list, {}, + PROJ(map_dir_sfo.at(v).GetInteger(SaveParams::SAVEDATA_LIST_PARAM).value_or(0))); + break; + case OrbisSaveDataSortKey::BLOCKS: + std::ranges::stable_sort(dir_list, {}, PROJ(map_max_blocks.at(v))); + break; + case OrbisSaveDataSortKey::FREE_BLOCKS: + std::ranges::stable_sort(dir_list, {}, PROJ(map_free_size.at(v))); + break; + case OrbisSaveDataSortKey::MTIME: + std::ranges::stable_sort(dir_list, {}, PROJ(map_dir_sfo.at(v).GetLastWrite())); + break; + } +#undef PROJ + + if (cond->order == OrbisSaveDataSortOrder::DESCENT) { + std::ranges::reverse(dir_list); + } + + size_t max_count = std::min(static_cast(result->dirNamesNum), dir_list.size()); + if (g_fw_ver >= ElfInfo::FW_17) { + result->hitNum = dir_list.size(); + result->setNum = max_count; } else { - result->hitNum = 0; - result->dirNamesNum = 0; - result->setNum = 0; + result->hitNum = max_count; } - if (result->infos != nullptr) { - result->infos->blocks = ORBIS_SAVE_DATA_BLOCK_SIZE; - result->infos->freeBlocks = ORBIS_SAVE_DATA_BLOCK_SIZE; + + for (size_t i = 0; i < max_count; i++) { + auto& name_data = result->dirNames[i].data; + name_data.FromString(dir_list[i]); + + if (g_fw_ver >= ElfInfo::FW_17 && result->params != nullptr) { + auto& sfo = map_dir_sfo.at(dir_list[i]); + auto& param_data = result->params[i]; + param_data.FromSFO(sfo); + } + + if (g_fw_ver >= ElfInfo::FW_25 && result->infos != nullptr) { + auto& info = result->infos[i]; + info.blocks = map_max_blocks.at(dir_list[i]); + info.freeBlocks = map_free_size.at(dir_list[i]); + } } - return ORBIS_OK; + + return Error::OK; } int PS4_SYSV_ABI sceSaveDataDirNameSearchInternal() { @@ -279,15 +943,30 @@ int PS4_SYSV_ABI sceSaveDataGetEventInfo() { return ORBIS_OK; } -int PS4_SYSV_ABI sceSaveDataGetEventResult(const OrbisSaveDataEventParam* eventParam, - OrbisSaveDataEvent* event) { - // eventParam can be 0/null. - if (event == nullptr) - return ORBIS_SAVE_DATA_ERROR_NOT_INITIALIZED; +Error PS4_SYSV_ABI sceSaveDataGetEventResult(const OrbisSaveDataEventParam*, + OrbisSaveDataEvent* event) { + if (!g_initialized) { + LOG_INFO(Lib_SaveData, "called without initialize"); + return setNotInitializedError(); + } + if (event == nullptr) { + LOG_INFO(Lib_SaveData, "called with invalid parameter"); + return Error::PARAMETER; + } + LOG_TRACE(Lib_SaveData, "called"); - LOG_INFO(Lib_SaveData, "called: Todo."); - event->userId = 1; - return ORBIS_OK; + auto last_event = Backup::PopLastEvent(); + if (!last_event.has_value()) { + return Error::NOT_FOUND; + } + + event->type = last_event->origin; + event->errorCode = 0; + event->userId = last_event->user_id; + event->titleId.data.FromString(last_event->title_id); + event->dirName.data.FromString(last_event->dir_name); + + return Error::OK; } int PS4_SYSV_ABI sceSaveDataGetFormat() { @@ -300,65 +979,119 @@ int PS4_SYSV_ABI sceSaveDataGetMountedSaveDataCount() { return ORBIS_OK; } -int PS4_SYSV_ABI sceSaveDataGetMountInfo(const OrbisSaveDataMountPoint* mountPoint, - OrbisSaveDataMountInfo* info) { - LOG_INFO(Lib_SaveData, "called"); - info->blocks = ORBIS_SAVE_DATA_BLOCKS_MAX; - info->freeBlocks = ORBIS_SAVE_DATA_BLOCKS_MAX; - return ORBIS_OK; +Error PS4_SYSV_ABI sceSaveDataGetMountInfo(const OrbisSaveDataMountPoint* mountPoint, + OrbisSaveDataMountInfo* info) { + if (!g_initialized) { + LOG_INFO(Lib_SaveData, "called without initialize"); + return setNotInitializedError(); + } + if (mountPoint == nullptr || info == nullptr) { + LOG_INFO(Lib_SaveData, "called with invalid parameter"); + return Error::PARAMETER; + } + LOG_DEBUG(Lib_SaveData, "called"); + const std::string_view mount_point_str{mountPoint->data}; + for (const auto& instance : g_mount_slots) { + if (instance.has_value() && instance->GetMountPoint() == mount_point_str) { + const u32 blocks = instance->GetMaxBlocks(); + const u64 size = Common::FS::GetDirectorySize(instance->GetSavePath()); + info->blocks = blocks; + info->freeBlocks = blocks - size / OrbisSaveDataBlockSize; + return Error::OK; + } + } + return Error::NOT_MOUNTED; } -int PS4_SYSV_ABI sceSaveDataGetParam(const OrbisSaveDataMountPoint* mountPoint, - const OrbisSaveDataParamType paramType, void* paramBuf, - const size_t paramBufSize, size_t* gotSize) { - - if (mountPoint == nullptr) - return ORBIS_SAVE_DATA_ERROR_PARAMETER; - - auto* mnt = Common::Singleton::Instance(); - const auto mount_dir = mnt->GetHostPath(mountPoint->data); - Common::FS::IOFile file(mount_dir / "param.txt", Common::FS::FileAccessMode::Read); - OrbisSaveDataParam params; - file.Read(params); - - LOG_INFO(Lib_SaveData, "called"); +Error PS4_SYSV_ABI sceSaveDataGetParam(const OrbisSaveDataMountPoint* mountPoint, + OrbisSaveDataParamType paramType, void* paramBuf, + size_t paramBufSize, size_t* gotSize) { + if (!g_initialized) { + LOG_INFO(Lib_SaveData, "called without initialize"); + return setNotInitializedError(); + } + if (paramType > OrbisSaveDataParamType::MTIME || paramBuf == nullptr) { + LOG_INFO(Lib_SaveData, "called with invalid parameter"); + return Error::PARAMETER; + } + LOG_DEBUG(Lib_SaveData, "called: paramType = {}", magic_enum::enum_name(paramType)); + const PSF* param_sfo = nullptr; + + const std::string_view mount_point_str{mountPoint->data}; + for (const auto& instance : g_mount_slots) { + if (instance.has_value() && instance->GetMountPoint() == mount_point_str) { + param_sfo = &instance->GetParamSFO(); + break; + } + } + if (param_sfo == nullptr) { + return Error::NOT_MOUNTED; + } switch (paramType) { - case ORBIS_SAVE_DATA_PARAM_TYPE_ALL: { - memcpy(paramBuf, ¶ms, sizeof(OrbisSaveDataParam)); - *gotSize = sizeof(OrbisSaveDataParam); - } break; - case ORBIS_SAVE_DATA_PARAM_TYPE_TITLE: { - std::memcpy(paramBuf, ¶ms.title, ORBIS_SAVE_DATA_TITLE_MAXSIZE); - *gotSize = ORBIS_SAVE_DATA_TITLE_MAXSIZE; - } break; - case ORBIS_SAVE_DATA_PARAM_TYPE_SUB_TITLE: { - std::memcpy(paramBuf, ¶ms.subTitle, ORBIS_SAVE_DATA_SUBTITLE_MAXSIZE); - *gotSize = ORBIS_SAVE_DATA_SUBTITLE_MAXSIZE; - } break; - case ORBIS_SAVE_DATA_PARAM_TYPE_DETAIL: { - std::memcpy(paramBuf, ¶ms.detail, ORBIS_SAVE_DATA_DETAIL_MAXSIZE); - *gotSize = ORBIS_SAVE_DATA_DETAIL_MAXSIZE; - } break; - case ORBIS_SAVE_DATA_PARAM_TYPE_USER_PARAM: { - std::memcpy(paramBuf, ¶ms.userParam, sizeof(u32)); - *gotSize = sizeof(u32); + case OrbisSaveDataParamType::ALL: { + const auto param = static_cast(paramBuf); + ASSERT(paramBufSize == sizeof(OrbisSaveDataParam)); + param->FromSFO(*param_sfo); + if (gotSize != nullptr) { + *gotSize = sizeof(OrbisSaveDataParam); + } + break; + } + case OrbisSaveDataParamType::TITLE: + case OrbisSaveDataParamType::SUB_TITLE: + case OrbisSaveDataParamType::DETAIL: { + const auto param = static_cast(paramBuf); + std::string_view key; + if (paramType == OrbisSaveDataParamType::TITLE) { + key = SaveParams::MAINTITLE; + } else if (paramType == OrbisSaveDataParamType::SUB_TITLE) { + key = SaveParams::SUBTITLE; + } else if (paramType == OrbisSaveDataParamType::DETAIL) { + key = SaveParams::DETAIL; + } else { + UNREACHABLE(); + } + const size_t s = param_sfo->GetString(key).value_or("").copy(param, paramBufSize - 1); + param[s] = '\0'; // null terminate + if (gotSize != nullptr) { + *gotSize = s + 1; + } } break; - case ORBIS_SAVE_DATA_PARAM_TYPE_MTIME: { - std::memcpy(paramBuf, ¶ms.mtime, sizeof(time_t)); - *gotSize = sizeof(time_t); + case OrbisSaveDataParamType::USER_PARAM: { + const auto param = static_cast(paramBuf); + *param = param_sfo->GetInteger(SaveParams::SAVEDATA_LIST_PARAM).value_or(0); + if (gotSize != nullptr) { + *gotSize = sizeof(u32); + } } break; - default: { - UNREACHABLE_MSG("Unknown Param = {}", paramType); + case OrbisSaveDataParamType::MTIME: { + const auto param = static_cast(paramBuf); + const auto last_write = param_sfo->GetLastWrite().time_since_epoch(); + *param = chrono::duration_cast(last_write).count(); + if (gotSize != nullptr) { + *gotSize = sizeof(time_t); + } } break; + default: + UNREACHABLE(); } - return ORBIS_OK; + return Error::OK; } -int PS4_SYSV_ABI sceSaveDataGetProgress() { - LOG_ERROR(Lib_SaveData, "(STUBBED) called"); - return ORBIS_OK; +Error PS4_SYSV_ABI sceSaveDataGetProgress(float* progress) { + if (!g_initialized) { + LOG_INFO(Lib_SaveData, "called without initialize"); + return setNotInitializedError(); + } + if (progress == nullptr) { + LOG_INFO(Lib_SaveData, "called with invalid parameter"); + return Error::PARAMETER; + } + LOG_DEBUG(Lib_SaveData, "called"); + *progress = Backup::GetProgress(); + return Error::OK; } int PS4_SYSV_ABI sceSaveDataGetSaveDataCount() { @@ -366,44 +1099,56 @@ int PS4_SYSV_ABI sceSaveDataGetSaveDataCount() { return ORBIS_OK; } -int PS4_SYSV_ABI sceSaveDataGetSaveDataMemory(const u32 userId, void* buf, const size_t bufSize, - const int64_t offset) { - const auto& mount_dir = Common::FS::GetUserPath(Common::FS::PathType::SaveDataDir) / - std::to_string(userId) / game_serial / "sdmemory/save_mem1.sav"; - - Common::FS::IOFile file(mount_dir, Common::FS::FileAccessMode::Read); - if (!file.IsOpen()) { - return false; - } - file.Seek(offset); - size_t nbytes = file.ReadRaw(buf, bufSize); - LOG_INFO(Lib_SaveData, "called: bufSize = {}, offset = {}", bufSize, offset, nbytes); - - return ORBIS_OK; +Error PS4_SYSV_ABI sceSaveDataGetSaveDataMemory(const OrbisUserServiceUserId userId, void* buf, + const size_t bufSize, const int64_t offset) { + LOG_DEBUG(Lib_SaveData, "Redirecting to sceSaveDataGetSaveDataMemory2"); + OrbisSaveDataMemoryData data{}; + data.buf = buf; + data.bufSize = bufSize; + data.offset = offset; + OrbisSaveDataMemoryGet2 param{}; + param.userId = userId; + param.data = &data; + param.param = nullptr; + param.icon = nullptr; + return sceSaveDataGetSaveDataMemory2(¶m); } -int PS4_SYSV_ABI sceSaveDataGetSaveDataMemory2(OrbisSaveDataMemoryGet2* getParam) { - const auto& mount_dir = Common::FS::GetUserPath(Common::FS::PathType::SaveDataDir) / - std::to_string(getParam->userId) / game_serial / "sdmemory"; - if (getParam == nullptr) - return ORBIS_SAVE_DATA_ERROR_PARAMETER; - if (getParam->data != nullptr) { - Common::FS::IOFile file(mount_dir / "save_mem2.sav", Common::FS::FileAccessMode::Read); - if (!file.IsOpen()) { - return false; - } - file.Seek(getParam->data->offset); - file.ReadRaw(getParam->data->buf, getParam->data->bufSize); - LOG_INFO(Lib_SaveData, "called: bufSize = {}, offset = {}", getParam->data->bufSize, - getParam->data->offset); +Error PS4_SYSV_ABI sceSaveDataGetSaveDataMemory2(OrbisSaveDataMemoryGet2* getParam) { + if (!g_initialized) { + LOG_INFO(Lib_SaveData, "called without initialize"); + return setNotInitializedError(); } - - if (getParam->param != nullptr) { - Common::FS::IOFile file(mount_dir / "param.txt", Common::FS::FileAccessMode::Read); - file.ReadRaw(getParam->param, sizeof(OrbisSaveDataParam)); + if (getParam == nullptr) { + LOG_INFO(Lib_SaveData, "called with invalid parameter"); + return Error::PARAMETER; + } + if (!SaveMemory::IsSaveMemoryInitialized()) { + LOG_INFO(Lib_SaveData, "called without save memory initialized"); + return Error::MEMORY_NOT_READY; + } + if (SaveMemory::IsSaving()) { + LOG_TRACE(Lib_SaveData, "called while saving"); + return Error::BUSY_FOR_SAVING; + } + LOG_DEBUG(Lib_SaveData, "called"); + auto data = getParam->data; + if (data != nullptr) { + SaveMemory::ReadMemory(data->buf, data->bufSize, data->offset); + } + auto param = getParam->param; + if (param != nullptr) { + param->FromSFO(SaveMemory::GetParamSFO()); + } + auto icon = getParam->icon; + if (icon != nullptr) { + auto icon_mem = SaveMemory::GetIcon(); + size_t total = std::min(icon->bufSize, icon_mem.size()); + std::memcpy(icon->buf, icon_mem.data(), total); + icon->dataSize = total; } - return ORBIS_OK; + return Error::OK; } int PS4_SYSV_ABI sceSaveDataGetSaveDataRootDir() { @@ -431,25 +1176,22 @@ int PS4_SYSV_ABI sceSaveDataGetUpdatedDataCount() { return ORBIS_OK; } -int PS4_SYSV_ABI sceSaveDataInitialize() { - LOG_INFO(Lib_SaveData, "called"); - static auto* param_sfo = Common::Singleton::Instance(); - game_serial = std::string(param_sfo->GetString("CONTENT_ID"), 7, 9); - return ORBIS_OK; +Error PS4_SYSV_ABI sceSaveDataInitialize(void*) { + LOG_DEBUG(Lib_SaveData, "called"); + initialize(); + return Error::OK; } -int PS4_SYSV_ABI sceSaveDataInitialize2() { - LOG_INFO(Lib_SaveData, "called"); - static auto* param_sfo = Common::Singleton::Instance(); - game_serial = std::string(param_sfo->GetString("CONTENT_ID"), 7, 9); - return ORBIS_OK; +Error PS4_SYSV_ABI sceSaveDataInitialize2(void*) { + LOG_DEBUG(Lib_SaveData, "called"); + initialize(); + return Error::OK; } -int PS4_SYSV_ABI sceSaveDataInitialize3() { - LOG_INFO(Lib_SaveData, "called"); - static auto* param_sfo = Common::Singleton::Instance(); - game_serial = std::string(param_sfo->GetString("CONTENT_ID"), 7, 9); - return ORBIS_OK; +Error PS4_SYSV_ABI sceSaveDataInitialize3(void*) { + LOG_DEBUG(Lib_SaveData, "called"); + initialize(); + return Error::OK; } int PS4_SYSV_ABI sceSaveDataInitializeForCdlg() { @@ -467,100 +1209,69 @@ int PS4_SYSV_ABI sceSaveDataIsMounted() { return ORBIS_OK; } -int PS4_SYSV_ABI sceSaveDataLoadIcon(const OrbisSaveDataMountPoint* mountPoint, - OrbisSaveDataIcon* icon) { - auto* mnt = Common::Singleton::Instance(); - const auto mount_dir = mnt->GetHostPath(mountPoint->data); - LOG_INFO(Lib_SaveData, "called: dir = {}", mount_dir.string()); - - if (icon != nullptr) { - Common::FS::IOFile file(mount_dir / "save_data.png", Common::FS::FileAccessMode::Read); - icon->bufSize = file.GetSize(); - file.ReadRaw(icon->buf, icon->bufSize); - } - return ORBIS_OK; -} - -s32 saveDataMount(u32 user_id, char* dir_name, u32 mount_mode, - OrbisSaveDataMountResult* mount_result) { - const auto& mount_dir = Common::FS::GetUserPath(Common::FS::PathType::SaveDataDir) / - std::to_string(user_id) / game_serial / dir_name; - auto* mnt = Common::Singleton::Instance(); - switch (mount_mode) { - case ORBIS_SAVE_DATA_MOUNT_MODE_RDONLY: - case ORBIS_SAVE_DATA_MOUNT_MODE_RDWR: - case ORBIS_SAVE_DATA_MOUNT_MODE_RDWR | ORBIS_SAVE_DATA_MOUNT_MODE_DESTRUCT_OFF: - case ORBIS_SAVE_DATA_MOUNT_MODE_RDONLY | ORBIS_SAVE_DATA_MOUNT_MODE_DESTRUCT_OFF: { - is_rw_mode = (mount_mode == ORBIS_SAVE_DATA_MOUNT_MODE_RDWR) ? true : false; - if (!std::filesystem::exists(mount_dir)) { - return ORBIS_SAVE_DATA_ERROR_NOT_FOUND; - } - mount_result->mount_status = 0; - g_mount_point.copy(mount_result->mount_point.data, 16); - mnt->Mount(mount_dir, mount_result->mount_point.data); - } break; - case ORBIS_SAVE_DATA_MOUNT_MODE_CREATE: - case ORBIS_SAVE_DATA_MOUNT_MODE_CREATE | ORBIS_SAVE_DATA_MOUNT_MODE_RDONLY: - case ORBIS_SAVE_DATA_MOUNT_MODE_CREATE | ORBIS_SAVE_DATA_MOUNT_MODE_RDWR: - case ORBIS_SAVE_DATA_MOUNT_MODE_CREATE | ORBIS_SAVE_DATA_MOUNT_MODE_RDWR | - ORBIS_SAVE_DATA_MOUNT_MODE_DESTRUCT_OFF: - case ORBIS_SAVE_DATA_MOUNT_MODE_CREATE | ORBIS_SAVE_DATA_MOUNT_MODE_RDWR | - ORBIS_SAVE_DATA_MOUNT_MODE_COPY_ICON: - case ORBIS_SAVE_DATA_MOUNT_MODE_CREATE | ORBIS_SAVE_DATA_MOUNT_MODE_COPY_ICON: - case ORBIS_SAVE_DATA_MOUNT_MODE_CREATE | ORBIS_SAVE_DATA_MOUNT_MODE_DESTRUCT_OFF | - ORBIS_SAVE_DATA_MOUNT_MODE_COPY_ICON: - case ORBIS_SAVE_DATA_MOUNT_MODE_CREATE | ORBIS_SAVE_DATA_MOUNT_MODE_RDWR | - ORBIS_SAVE_DATA_MOUNT_MODE_DESTRUCT_OFF | ORBIS_SAVE_DATA_MOUNT_MODE_COPY_ICON: { - if (std::filesystem::exists(mount_dir)) { - return ORBIS_SAVE_DATA_ERROR_EXISTS; - } - if (std::filesystem::create_directories(mount_dir)) { - g_mount_point.copy(mount_result->mount_point.data, 16); - mnt->Mount(mount_dir, mount_result->mount_point.data); - mount_result->mount_status = 1; - } - } break; - case ORBIS_SAVE_DATA_MOUNT_MODE_CREATE2: - case ORBIS_SAVE_DATA_MOUNT_MODE_CREATE2 | ORBIS_SAVE_DATA_MOUNT_MODE_RDWR: - case ORBIS_SAVE_DATA_MOUNT_MODE_CREATE2 | ORBIS_SAVE_DATA_MOUNT_MODE_RDWR | - ORBIS_SAVE_DATA_MOUNT_MODE_COPY_ICON: - case ORBIS_SAVE_DATA_MOUNT_MODE_CREATE2 | ORBIS_SAVE_DATA_MOUNT_MODE_RDWR | - ORBIS_SAVE_DATA_MOUNT_MODE_DESTRUCT_OFF | ORBIS_SAVE_DATA_MOUNT_MODE_COPY_ICON: { - if (!std::filesystem::exists(mount_dir)) { - std::filesystem::create_directories(mount_dir); +Error PS4_SYSV_ABI sceSaveDataLoadIcon(const OrbisSaveDataMountPoint* mountPoint, + OrbisSaveDataIcon* icon) { + if (!g_initialized) { + LOG_INFO(Lib_SaveData, "called without initialize"); + return setNotInitializedError(); + } + if (mountPoint == nullptr || icon == nullptr || icon->buf == nullptr) { + LOG_INFO(Lib_SaveData, "called with invalid parameter"); + return Error::PARAMETER; + } + LOG_DEBUG(Lib_SaveData, "called"); + std::filesystem::path path; + const std::string_view mount_point_str{mountPoint->data}; + for (const auto& instance : g_mount_slots) { + if (instance.has_value() && instance->GetMountPoint() == mount_point_str) { + path = instance->GetIconPath(); + break; } - g_mount_point.copy(mount_result->mount_point.data, 16); - mnt->Mount(mount_dir, mount_result->mount_point.data); - mount_result->mount_status = 1; - } break; - default: - UNREACHABLE_MSG("Unknown mount mode = {}", mount_mode); } - mount_result->required_blocks = 0; + if (path.empty()) { + return Error::NOT_MOUNTED; + } + if (!fs::exists(path)) { + return Error::NOT_FOUND; + } - return ORBIS_OK; + return icon->LoadIcon(path); } -s32 PS4_SYSV_ABI sceSaveDataMount(const OrbisSaveDataMount* mount, - OrbisSaveDataMountResult* mount_result) { - if (mount == nullptr) { - return ORBIS_SAVE_DATA_ERROR_PARAMETER; +Error PS4_SYSV_ABI sceSaveDataMount(const OrbisSaveDataMount* mount, + OrbisSaveDataMountResult* mount_result) { + if (!g_initialized) { + LOG_INFO(Lib_SaveData, "called without initialize"); + return setNotInitializedError(); } - LOG_INFO(Lib_SaveData, "called: dirName = {}, mode = {}, blocks = {}", mount->dir_name->data, - mount->mount_mode, mount->blocks); - return saveDataMount(mount->user_id, (char*)mount->dir_name->data, mount->mount_mode, - mount_result); -} - -s32 PS4_SYSV_ABI sceSaveDataMount2(const OrbisSaveDataMount2* mount, - OrbisSaveDataMountResult* mount_result) { - if (mount == nullptr) { - return ORBIS_SAVE_DATA_ERROR_PARAMETER; + if (mount == nullptr && mount->dirName != nullptr) { + LOG_INFO(Lib_SaveData, "called with invalid parameter"); + return Error::PARAMETER; + } + LOG_DEBUG(Lib_SaveData, "called dirName: {}, mode: {:0b}, blocks: {}", + mount->dirName->data.to_view(), (int)mount->mountMode, mount->blocks); + + OrbisSaveDataMount2 mount_info{}; + mount_info.userId = mount->userId; + mount_info.dirName = mount->dirName; + mount_info.mountMode = mount->mountMode; + mount_info.blocks = mount->blocks; + return saveDataMount(&mount_info, mount_result); +} + +Error PS4_SYSV_ABI sceSaveDataMount2(const OrbisSaveDataMount2* mount, + OrbisSaveDataMountResult* mount_result) { + if (!g_initialized) { + LOG_INFO(Lib_SaveData, "called without initialize"); + return setNotInitializedError(); } - LOG_INFO(Lib_SaveData, "called: dirName = {}, mode = {}, blocks = {}", mount->dir_name->data, - mount->mount_mode, mount->blocks); - return saveDataMount(mount->user_id, (char*)mount->dir_name->data, mount->mount_mode, - mount_result); + if (mount == nullptr && mount->dirName != nullptr) { + LOG_INFO(Lib_SaveData, "called with invalid parameter"); + return Error::PARAMETER; + } + LOG_DEBUG(Lib_SaveData, "called dirName: {}, mode: {:0b}, blocks: {}", + mount->dirName->data.to_view(), (int)mount->mountMode, mount->blocks); + return saveDataMount(mount, mount_result); } int PS4_SYSV_ABI sceSaveDataMount5() { @@ -593,9 +1304,44 @@ int PS4_SYSV_ABI sceSaveDataRegisterEventCallback() { return ORBIS_OK; } -int PS4_SYSV_ABI sceSaveDataRestoreBackupData() { - LOG_ERROR(Lib_SaveData, "(STUBBED) called"); - return ORBIS_OK; +Error PS4_SYSV_ABI sceSaveDataRestoreBackupData(const OrbisSaveDataRestoreBackupData* restore) { + if (!g_initialized) { + LOG_INFO(Lib_SaveData, "called without initialize"); + return setNotInitializedError(); + } + if (restore == nullptr || restore->dirName == nullptr) { + LOG_INFO(Lib_SaveData, "called with invalid parameter"); + return Error::PARAMETER; + } + + const std::string_view dir_name{restore->dirName->data}; + LOG_DEBUG(Lib_SaveData, "called dirName: {}", dir_name); + + std::string_view title{restore->titleId != nullptr ? std::string_view{restore->titleId->data} + : std::string_view{g_game_serial}}; + + const auto save_path = SaveInstance::MakeDirSavePath(restore->userId, title, dir_name); + + for (const auto& instance : g_mount_slots) { + if (instance.has_value() && instance->GetSavePath() == save_path) { + return Error::BUSY; + } + } + if (Backup::IsBackupExecutingFor(save_path)) { + return Error::BACKUP_BUSY; + } + + const auto backup_path = Backup::MakeBackupPath(save_path); + if (!fs::exists(backup_path)) { + return Error::NOT_FOUND; + } + + const bool ok = Backup::Restore(save_path); + if (!ok) { + return Error::INTERNAL; + } + + return Error::OK; } int PS4_SYSV_ABI sceSaveDataRestoreBackupDataForCdlg() { @@ -608,17 +1354,41 @@ int PS4_SYSV_ABI sceSaveDataRestoreLoadSaveDataMemory() { return ORBIS_OK; } -int PS4_SYSV_ABI sceSaveDataSaveIcon(const OrbisSaveDataMountPoint* mountPoint, - const OrbisSaveDataIcon* icon) { - auto* mnt = Common::Singleton::Instance(); - const auto mount_dir = mnt->GetHostPath(mountPoint->data); - LOG_INFO(Lib_SaveData, "called = {}", mount_dir.string()); +Error PS4_SYSV_ABI sceSaveDataSaveIcon(const OrbisSaveDataMountPoint* mountPoint, + const OrbisSaveDataIcon* icon) { + if (!g_initialized) { + LOG_INFO(Lib_SaveData, "called without initialize"); + return setNotInitializedError(); + } + if (mountPoint == nullptr || icon == nullptr || icon->buf == nullptr) { + LOG_INFO(Lib_SaveData, "called with invalid parameter"); + return Error::PARAMETER; + } + LOG_DEBUG(Lib_SaveData, "called"); + std::filesystem::path path; + const std::string_view mount_point_str{mountPoint->data}; + for (const auto& instance : g_mount_slots) { + if (instance.has_value() && instance->GetMountPoint() == mount_point_str) { + if (instance->IsReadOnly()) { + return Error::BAD_MOUNTED; + } + path = instance->GetIconPath(); + break; + } + } + if (path.empty()) { + return Error::NOT_MOUNTED; + } - if (icon != nullptr) { - Common::FS::IOFile file(mount_dir / "save_data.png", Common::FS::FileAccessMode::Write); - file.WriteRaw(icon->buf, icon->bufSize); + try { + const Common::FS::IOFile file(path, Common::FS::FileAccessMode::Write); + file.WriteRaw(icon->buf, std::min(icon->bufSize, icon->dataSize)); + } catch (const fs::filesystem_error& e) { + LOG_ERROR(Lib_SaveData, "Failed to load icon: {}", e.what()); + return Error::INTERNAL; } - return ORBIS_OK; + + return Error::OK; } int PS4_SYSV_ABI sceSaveDataSetAutoUploadSetting() { @@ -631,50 +1401,59 @@ int PS4_SYSV_ABI sceSaveDataSetEventInfo() { return ORBIS_OK; } -int PS4_SYSV_ABI sceSaveDataSetParam(const OrbisSaveDataMountPoint* mountPoint, - OrbisSaveDataParamType paramType, const void* paramBuf, - size_t paramBufSize) { - if (paramBuf == nullptr) - return ORBIS_SAVE_DATA_ERROR_PARAMETER; - - auto* mnt = Common::Singleton::Instance(); - const auto mount_dir = mnt->GetHostPath(mountPoint->data) / "param.txt"; - OrbisSaveDataParam params; - if (std::filesystem::exists(mount_dir)) { - Common::FS::IOFile file(mount_dir, Common::FS::FileAccessMode::Read); - file.ReadRaw(¶ms, sizeof(OrbisSaveDataParam)); +Error PS4_SYSV_ABI sceSaveDataSetParam(const OrbisSaveDataMountPoint* mountPoint, + OrbisSaveDataParamType paramType, const void* paramBuf, + size_t paramBufSize) { + if (!g_initialized) { + LOG_INFO(Lib_SaveData, "called without initialize"); + return setNotInitializedError(); + } + if (paramType > OrbisSaveDataParamType::USER_PARAM || mountPoint == nullptr || + paramBuf == nullptr) { + LOG_INFO(Lib_SaveData, "called with invalid parameter"); + return Error::PARAMETER; + } + LOG_DEBUG(Lib_SaveData, "called: paramType = {}", magic_enum::enum_name(paramType)); + PSF* param_sfo = nullptr; + const std::string_view mount_point_str{mountPoint->data}; + for (auto& instance : g_mount_slots) { + if (instance.has_value() && instance->GetMountPoint() == mount_point_str) { + param_sfo = &instance->GetParamSFO(); + break; + } + } + if (param_sfo == nullptr) { + return Error::NOT_MOUNTED; } - - LOG_INFO(Lib_SaveData, "called"); switch (paramType) { - case ORBIS_SAVE_DATA_PARAM_TYPE_ALL: { - memcpy(¶ms, paramBuf, sizeof(OrbisSaveDataParam)); - } break; - case ORBIS_SAVE_DATA_PARAM_TYPE_TITLE: { - strncpy(params.title, static_cast(paramBuf), paramBufSize); + case OrbisSaveDataParamType::ALL: { + const auto param = static_cast(paramBuf); + ASSERT(paramBufSize == sizeof(OrbisSaveDataParam)); + param->ToSFO(*param_sfo); + return Error::OK; } break; - case ORBIS_SAVE_DATA_PARAM_TYPE_SUB_TITLE: { - strncpy(params.subTitle, static_cast(paramBuf), paramBufSize); + case OrbisSaveDataParamType::TITLE: { + const auto value = static_cast(paramBuf); + param_sfo->AddString(std::string{SaveParams::MAINTITLE}, {value}, true); } break; - case ORBIS_SAVE_DATA_PARAM_TYPE_DETAIL: { - strncpy(params.detail, static_cast(paramBuf), paramBufSize); + case OrbisSaveDataParamType::SUB_TITLE: { + const auto value = static_cast(paramBuf); + param_sfo->AddString(std::string{SaveParams::SUBTITLE}, {value}, true); } break; - case ORBIS_SAVE_DATA_PARAM_TYPE_USER_PARAM: { - params.userParam = *(static_cast(paramBuf)); + case OrbisSaveDataParamType::DETAIL: { + const auto value = static_cast(paramBuf); + param_sfo->AddString(std::string{SaveParams::DETAIL}, {value}, true); } break; - case ORBIS_SAVE_DATA_PARAM_TYPE_MTIME: { - params.mtime = *(static_cast(paramBuf)); + case OrbisSaveDataParamType::USER_PARAM: { + const auto value = static_cast(paramBuf); + param_sfo->AddInteger(std::string{SaveParams::SAVEDATA_LIST_PARAM}, *value, true); } break; - default: { - UNREACHABLE_MSG("Unknown Param = {}", paramType); - } + default: + UNREACHABLE(); } - Common::FS::IOFile file(mount_dir, Common::FS::FileAccessMode::Write); - file.WriteRaw(¶ms, sizeof(OrbisSaveDataParam)); - - return ORBIS_OK; + return Error::OK; } int PS4_SYSV_ABI sceSaveDataSetSaveDataLibraryUser() { @@ -682,90 +1461,134 @@ int PS4_SYSV_ABI sceSaveDataSetSaveDataLibraryUser() { return ORBIS_OK; } -int PS4_SYSV_ABI sceSaveDataSetSaveDataMemory(const u32 userId, const void* buf, - const size_t bufSize, const int64_t offset) { - LOG_INFO(Lib_SaveData, "called"); - const auto& mount_dir = Common::FS::GetUserPath(Common::FS::PathType::SaveDataDir) / - std::to_string(userId) / game_serial / "sdmemory/save_mem1.sav"; - - Common::FS::IOFile file(mount_dir, Common::FS::FileAccessMode::Write); - file.Seek(offset); - file.WriteRaw(buf, bufSize); - - return ORBIS_OK; +Error PS4_SYSV_ABI sceSaveDataSetSaveDataMemory(OrbisUserServiceUserId userId, void* buf, + size_t bufSize, int64_t offset) { + LOG_DEBUG(Lib_SaveData, "Redirecting to sceSaveDataSetSaveDataMemory2"); + OrbisSaveDataMemoryData data{}; + data.buf = buf; + data.bufSize = bufSize; + data.offset = offset; + OrbisSaveDataMemorySet2 setParam{}; + setParam.userId = userId; + setParam.data = &data; + setParam.param = nullptr; + setParam.icon = nullptr; + return sceSaveDataSetSaveDataMemory2(&setParam); } -int PS4_SYSV_ABI sceSaveDataSetSaveDataMemory2(const OrbisSaveDataMemorySet2* setParam) { - LOG_INFO(Lib_SaveData, "called: dataNum = {}, slotId= {}", setParam->dataNum, setParam->slotId); - const auto& mount_dir = Common::FS::GetUserPath(Common::FS::PathType::SaveDataDir) / - std::to_string(setParam->userId) / game_serial / "sdmemory"; - if (setParam->data != nullptr) { - Common::FS::IOFile file(mount_dir / "save_mem2.sav", Common::FS::FileAccessMode::Write); - if (!file.IsOpen()) - return -1; - file.Seek(setParam->data->offset); - file.WriteRaw(setParam->data->buf, setParam->data->bufSize); +Error PS4_SYSV_ABI sceSaveDataSetSaveDataMemory2(const OrbisSaveDataMemorySet2* setParam) { + if (!g_initialized) { + LOG_INFO(Lib_SaveData, "called without initialize"); + return setNotInitializedError(); + } + if (setParam == nullptr) { + LOG_INFO(Lib_SaveData, "called with invalid parameter"); + return Error::PARAMETER; + } + if (!SaveMemory::IsSaveMemoryInitialized()) { + LOG_INFO(Lib_SaveData, "called without save memory initialized"); + return Error::MEMORY_NOT_READY; + } + if (SaveMemory::IsSaving()) { + LOG_TRACE(Lib_SaveData, "called while saving"); + return Error::BUSY_FOR_SAVING; + } + LOG_DEBUG(Lib_SaveData, "called"); + auto data = setParam->data; + if (data != nullptr) { + SaveMemory::WriteMemory(data->buf, data->bufSize, data->offset); + } + auto param = setParam->param; + if (param != nullptr) { + param->ToSFO(SaveMemory::GetParamSFO()); + SaveMemory::SaveSFO(); + } + auto icon = setParam->icon; + if (icon != nullptr) { + SaveMemory::WriteIcon(icon->buf, icon->bufSize); } - if (setParam->param != nullptr) { - Common::FS::IOFile file(mount_dir / "param.txt", Common::FS::FileAccessMode::Write); - file.WriteRaw((void*)setParam->param, sizeof(OrbisSaveDataParam)); + SaveMemory::TriggerSaveWithoutEvent(); + return Error::OK; +} + +Error PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory(OrbisUserServiceUserId userId, size_t memorySize, + OrbisSaveDataParam* param) { + LOG_DEBUG(Lib_SaveData, "called: userId = {}, memorySize = {}", userId, memorySize); + OrbisSaveDataMemorySetup2 setupParam{}; + setupParam.userId = userId; + setupParam.memorySize = memorySize; + setupParam.initParam = nullptr; + setupParam.initIcon = nullptr; + OrbisSaveDataMemorySetupResult result{}; + const auto res = sceSaveDataSetupSaveDataMemory2(&setupParam, &result); + if (res != Error::OK) { + return res; + } + if (param != nullptr) { + OrbisSaveDataMemorySet2 setParam{}; + setParam.userId = userId; + setParam.data = nullptr; + setParam.param = param; + setParam.icon = nullptr; + sceSaveDataSetSaveDataMemory2(&setParam); } + return Error::OK; +} - if (setParam->icon != nullptr) { - Common::FS::IOFile file(mount_dir / "save_icon.png", Common::FS::FileAccessMode::Write); - file.WriteRaw(setParam->icon->buf, setParam->icon->bufSize); +Error PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory2(const OrbisSaveDataMemorySetup2* setupParam, + OrbisSaveDataMemorySetupResult* result) { + if (!g_initialized) { + LOG_INFO(Lib_SaveData, "called without initialize"); + return setNotInitializedError(); + } + if (setupParam == nullptr) { + LOG_INFO(Lib_SaveData, "called with invalid parameter"); + return Error::PARAMETER; } + LOG_DEBUG(Lib_SaveData, "called"); - return ORBIS_OK; -} + SaveMemory::SetDirectories(setupParam->userId, g_game_serial); -int PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory(u32 userId, size_t memorySize, - OrbisSaveDataParam* param) { + const auto& save_path = SaveMemory::GetSavePath(); + for (const auto& instance : g_mount_slots) { + if (instance.has_value() && instance->GetSavePath() == save_path) { + return Error::BUSY; + } + } - LOG_INFO(Lib_SaveData, "called:userId = {}, memorySize = {}", userId, memorySize); + try { + size_t existed_size = SaveMemory::CreateSaveMemory(setupParam->memorySize); + if (existed_size == 0) { // Just created + if (g_fw_ver >= ElfInfo::FW_45 && setupParam->initParam != nullptr) { + auto& sfo = SaveMemory::GetParamSFO(); + setupParam->initParam->ToSFO(sfo); + } + SaveMemory::SaveSFO(); - const auto& mount_dir = Common::FS::GetUserPath(Common::FS::PathType::SaveDataDir) / - std::to_string(userId) / game_serial / "sdmemory"; + auto init_icon = setupParam->initIcon; + if (g_fw_ver >= ElfInfo::FW_45 && init_icon != nullptr) { + SaveMemory::SetIcon(init_icon->buf, init_icon->bufSize); + } else { + SaveMemory::SetIcon(nullptr, 0); + } + } + if (g_fw_ver >= ElfInfo::FW_45 && result != nullptr) { + result->existedMemorySize = existed_size; + } + } catch (const fs::filesystem_error& e) { + LOG_ERROR(Lib_SaveData, "Failed to create/load save memory: {}", e.what()); - if (std::filesystem::exists(mount_dir)) { - return ORBIS_SAVE_DATA_ERROR_EXISTS; - } - std::filesystem::create_directories(mount_dir); - std::vector buf(memorySize); - Common::FS::IOFile::WriteBytes(mount_dir / "save_mem1.sav", buf); - return ORBIS_OK; -} + const MsgDialog::MsgDialogState dialog{MsgDialog::MsgDialogState::UserState{ + .type = MsgDialog::ButtonType::OK, + .msg = "Failed to create or load save memory:\n" + std::string{e.what()}, + }}; + MsgDialog::ShowMsgDialog(dialog); -int PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory2(const OrbisSaveDataMemorySetup2* setupParam, - OrbisSaveDataMemorySetupResult* result) { - if (setupParam == nullptr) { - return ORBIS_SAVE_DATA_ERROR_PARAMETER; - } - LOG_INFO(Lib_SaveData, "called"); - // if (setupParam->option == 1) { // check this later. - const auto& mount_dir = Common::FS::GetUserPath(Common::FS::PathType::SaveDataDir) / - std::to_string(setupParam->userId) / game_serial / "sdmemory"; - if (std::filesystem::exists(mount_dir) && - std::filesystem::exists(mount_dir / "save_mem2.sav")) { - Common::FS::IOFile file(mount_dir / "save_mem2.sav", Common::FS::FileAccessMode::Read); - if (!file.IsOpen()) - return -1; - // Bunny - CUSA07988 has a null result, having null result is checked and valid. - if (result != nullptr) - result->existedMemorySize = file.GetSize(); // Assign the saved data size. - // do not return ORBIS_SAVE_DATA_ERROR_EXISTS, as it will not trigger - // sceSaveDataGetSaveDataMemory2. - } else { - std::filesystem::create_directories(mount_dir); - std::vector buf(setupParam->memorySize); // check if > 0x1000000 (16.77mb) or x2? - Common::FS::IOFile::WriteBytes(mount_dir / "save_mem2.sav", buf); - std::vector paramBuf(sizeof(OrbisSaveDataParam)); - Common::FS::IOFile::WriteBytes(mount_dir / "param.txt", paramBuf); - std::vector iconBuf(setupParam->iconMemorySize); - Common::FS::IOFile::WriteBytes(mount_dir / "save_icon.png", iconBuf); + return Error::INTERNAL; } - return ORBIS_OK; + + return Error::OK; } int PS4_SYSV_ABI sceSaveDataShutdownStart() { @@ -783,14 +1606,44 @@ int PS4_SYSV_ABI sceSaveDataSyncCloudList() { return ORBIS_OK; } -int PS4_SYSV_ABI sceSaveDataSyncSaveDataMemory(OrbisSaveDataMemorySync* syncParam) { - LOG_ERROR(Lib_SaveData, "(STUBBED) called: option = {}", syncParam->option); - return ORBIS_OK; +Error PS4_SYSV_ABI sceSaveDataSyncSaveDataMemory(OrbisSaveDataMemorySync* syncParam) { + if (!g_initialized) { + LOG_INFO(Lib_SaveData, "called without initialize"); + return setNotInitializedError(); + } + if (syncParam == nullptr) { + LOG_INFO(Lib_SaveData, "called with invalid parameter"); + return Error::PARAMETER; + } + if (!SaveMemory::IsSaveMemoryInitialized()) { + LOG_INFO(Lib_SaveData, "called without save memory initialized"); + return Error::MEMORY_NOT_READY; + } + LOG_DEBUG(Lib_SaveData, "called"); + bool ok = SaveMemory::TriggerSave(); + if (!ok) { + return Error::BUSY_FOR_SAVING; + } + return Error::OK; } -int PS4_SYSV_ABI sceSaveDataTerminate() { - LOG_ERROR(Lib_SaveData, "(STUBBED) called"); - return ORBIS_OK; +Error PS4_SYSV_ABI sceSaveDataTerminate() { + LOG_DEBUG(Lib_SaveData, "called"); + if (!g_initialized) { + return setNotInitializedError(); + } + for (auto& instance : g_mount_slots) { + if (instance.has_value()) { + if (g_fw_ver >= ElfInfo::FW_40) { + return Error::BUSY; + } + instance->Umount(); + instance.reset(); + } + } + g_initialized = false; + Backup::StopThread(); + return Error::OK; } int PS4_SYSV_ABI sceSaveDataTransferringMount() { @@ -798,19 +1651,9 @@ int PS4_SYSV_ABI sceSaveDataTransferringMount() { return ORBIS_OK; } -s32 PS4_SYSV_ABI sceSaveDataUmount(const OrbisSaveDataMountPoint* mountPoint) { - LOG_INFO(Lib_SaveData, "mountPoint = {}", mountPoint->data); - if (std::string_view(mountPoint->data).empty()) { - return ORBIS_SAVE_DATA_ERROR_NOT_MOUNTED; - } - const auto& mount_dir = Common::FS::GetUserPath(Common::FS::PathType::SaveDataDir) / - std::to_string(1) / game_serial / mountPoint->data; - auto* mnt = Common::Singleton::Instance(); - const auto& guest_path = mnt->GetHostPath(mountPoint->data); - if (guest_path.empty()) - return ORBIS_SAVE_DATA_ERROR_NOT_MOUNTED; - mnt->Unmount(mount_dir, mountPoint->data); - return ORBIS_OK; +Error PS4_SYSV_ABI sceSaveDataUmount(const OrbisSaveDataMountPoint* mountPoint) { + LOG_DEBUG(Lib_SaveData, "called"); + return Umount(mountPoint); } int PS4_SYSV_ABI sceSaveDataUmountSys() { @@ -818,37 +1661,9 @@ int PS4_SYSV_ABI sceSaveDataUmountSys() { return ORBIS_OK; } -int PS4_SYSV_ABI sceSaveDataUmountWithBackup(const OrbisSaveDataMountPoint* mountPoint) { - LOG_INFO(Lib_SaveData, "called mount = {}, is_rw_mode = {}", std::string(mountPoint->data), - is_rw_mode); - auto* mnt = Common::Singleton::Instance(); - const auto mount_dir = mnt->GetHostPath(mountPoint->data); - if (!std::filesystem::exists(mount_dir)) { - return ORBIS_SAVE_DATA_ERROR_NOT_FOUND; - } - // leave disabled for now. and just unmount. - - /* if (is_rw_mode) { // backup is done only when mount mode is ReadWrite. - auto backup_path = mount_dir; - std::string save_data_dir = (mount_dir.string() + "_backup"); - backup_path.replace_filename(save_data_dir); - - std::filesystem::create_directories(backup_path); - - for (const auto& entry : std::filesystem::recursive_directory_iterator(mount_dir)) { - const auto& path = entry.path(); - if (std::filesystem::is_regular_file(path)) { - std::filesystem::copy(path, save_data_dir, - std::filesystem::copy_options::overwrite_existing); - } - } - }*/ - const auto& guest_path = mnt->GetHostPath(mountPoint->data); - if (guest_path.empty()) - return ORBIS_SAVE_DATA_ERROR_NOT_MOUNTED; - - mnt->Unmount(mount_dir, mountPoint->data); - return ORBIS_OK; +Error PS4_SYSV_ABI sceSaveDataUmountWithBackup(const OrbisSaveDataMountPoint* mountPoint) { + LOG_DEBUG(Lib_SaveData, "called"); + return Umount(mountPoint, true); } int PS4_SYSV_ABI sceSaveDataUnregisterEventCallback() { diff --git a/src/core/libraries/save_data/savedata.h b/src/core/libraries/save_data/savedata.h index 9b3cf900fcf..13b3dd59e04 100644 --- a/src/core/libraries/save_data/savedata.h +++ b/src/core/libraries/save_data/savedata.h @@ -3,259 +3,81 @@ #pragma once +#include "common/cstring.h" #include "common/types.h" namespace Core::Loader { class SymbolsResolver; } +class PSF; + namespace Libraries::SaveData { -constexpr int ORBIS_SAVE_DATA_DIRNAME_DATA_MAXSIZE = - 32; // Maximum size for a save data directory name -constexpr int ORBIS_SAVE_DATA_MOUNT_POINT_DATA_MAXSIZE = 16; // Maximum size for a mount point name +constexpr size_t OrbisSaveDataTitleMaxsize = 128; // Maximum title name size +constexpr size_t OrbisSaveDataSubtitleMaxsize = 128; // Maximum subtitle name size +constexpr size_t OrbisSaveDataDetailMaxsize = 1024; // Maximum detail name size -struct OrbisSaveDataDirName { - char data[ORBIS_SAVE_DATA_DIRNAME_DATA_MAXSIZE]; -}; +enum class Error : u32; +enum class OrbisSaveDataParamType : u32; -struct OrbisSaveDataMount2 { - s32 user_id; - s32 unk1; - const OrbisSaveDataDirName* dir_name; - u64 blocks; - u32 mount_mode; - u8 reserved[32]; - s32 unk2; -}; +using OrbisUserServiceUserId = s32; -struct OrbisSaveDataMountPoint { - char data[ORBIS_SAVE_DATA_MOUNT_POINT_DATA_MAXSIZE]; -}; - -struct OrbisSaveDataMountResult { - OrbisSaveDataMountPoint mount_point; - u64 required_blocks; - u32 unused; - u32 mount_status; - u8 reserved[28]; - s32 unk1; -}; +// Maximum size for a title ID (4 uppercase letters + 5 digits) +constexpr int OrbisSaveDataTitleIdDataSize = 10; +// Maximum save directory name size +constexpr int OrbisSaveDataDirnameDataMaxsize = 32; -constexpr int ORBIS_SAVE_DATA_TITLE_ID_DATA_SIZE = 10; struct OrbisSaveDataTitleId { - char data[ORBIS_SAVE_DATA_TITLE_ID_DATA_SIZE]; - char padding[6]; + Common::CString data; + std::array _pad; }; -constexpr int ORBIS_SAVE_DATA_FINGERPRINT_DATA_SIZE = 65; -struct OrbisSaveDataFingerprint { - char data[ORBIS_SAVE_DATA_FINGERPRINT_DATA_SIZE]; - char padding[15]; -}; - -struct OrbisSaveDataMount { - s32 user_id; - s32 pad; - const OrbisSaveDataTitleId* titleId; - const OrbisSaveDataDirName* dir_name; - const OrbisSaveDataFingerprint* fingerprint; - u64 blocks; - u32 mount_mode; - u8 reserved[32]; +struct OrbisSaveDataDirName { + Common::CString data; }; -typedef u32 OrbisSaveDataParamType; - -constexpr int ORBIS_SAVE_DATA_TITLE_MAXSIZE = 128; -constexpr int ORBIS_SAVE_DATA_SUBTITLE_MAXSIZE = 128; -constexpr int ORBIS_SAVE_DATA_DETAIL_MAXSIZE = 1024; struct OrbisSaveDataParam { - char title[ORBIS_SAVE_DATA_TITLE_MAXSIZE]; - char subTitle[ORBIS_SAVE_DATA_SUBTITLE_MAXSIZE]; - char detail[ORBIS_SAVE_DATA_DETAIL_MAXSIZE]; + Common::CString title; + Common::CString subTitle; + Common::CString detail; u32 userParam; int : 32; time_t mtime; - u8 reserved[32]; -}; - -struct OrbisSaveDataIcon { - void* buf; - size_t bufSize; - size_t dataSize; - u8 reserved[32]; -}; - -typedef u32 OrbisSaveDataSaveDataMemoryOption; -#define ORBIS_SAVE_DATA_MEMORY_OPTION_NONE (0x00000000) -#define ORBIS_SAVE_DATA_MEMORY_OPTION_SET_PARAM (0x00000001 << 0) -#define ORBIS_SAVE_DATA_MEMORY_OPTION_DOUBLE_BUFFER (0x00000001 << 1) - -struct OrbisSaveDataMemorySetup2 { - OrbisSaveDataSaveDataMemoryOption option; - s32 userId; - size_t memorySize; - size_t iconMemorySize; - const OrbisSaveDataParam* initParam; - const OrbisSaveDataIcon* initIcon; - u32 slotId; - u8 reserved[20]; -}; - -struct OrbisSaveDataMemorySetupResult { - size_t existedMemorySize; - u8 reserved[16]; -}; - -typedef u32 OrbisSaveDataEventType; -#define SCE_SAVE_DATA_EVENT_TYPE_INVALID (0) -#define SCE_SAVE_DATA_EVENT_TYPE_UMOUNT_BACKUP_END (1) -#define SCE_SAVE_DATA_EVENT_TYPE_BACKUP_END (2) -#define SCE_SAVE_DATA_EVENT_TYPE_SAVE_DATA_MEMORY_SYNC_END (3) - -struct OrbisSaveDataEvent { - OrbisSaveDataEventType type; - s32 errorCode; - s32 userId; - u8 padding[4]; - OrbisSaveDataTitleId titleId; - OrbisSaveDataDirName dirName; - u8 reserved[40]; -}; - -struct OrbisSaveDataMemoryData { - void* buf; - size_t bufSize; - off_t offset; - u8 reserved[40]; -}; - -struct OrbisSaveDataMemoryGet2 { - s32 userId; - u8 padding[4]; - OrbisSaveDataMemoryData* data; - OrbisSaveDataParam* param; - OrbisSaveDataIcon* icon; - u32 slotId; - u8 reserved[28]; -}; - -struct OrbisSaveDataMemorySet2 { - s32 userId; - u8 padding[4]; - const OrbisSaveDataMemoryData* data; - const OrbisSaveDataParam* param; - const OrbisSaveDataIcon* icon; - u32 dataNum; - u8 slotId; - u8 reserved[24]; -}; - -struct OrbisSaveDataCheckBackupData { - s32 userId; - int : 32; - const OrbisSaveDataTitleId* titleId; - const OrbisSaveDataDirName* dirName; - OrbisSaveDataParam* param; - OrbisSaveDataIcon* icon; - u8 reserved[32]; -}; - -struct OrbisSaveDataMountInfo { - u64 blocks; - u64 freeBlocks; - u8 reserved[32]; -}; - -#define ORBIS_SAVE_DATA_BLOCK_SIZE (32768) -#define ORBIS_SAVE_DATA_BLOCKS_MIN2 (96) -#define ORBIS_SAVE_DATA_BLOCKS_MAX (32768) - -// savedataMount2 mountModes (ORed values) -constexpr int ORBIS_SAVE_DATA_MOUNT_MODE_RDONLY = 1; -constexpr int ORBIS_SAVE_DATA_MOUNT_MODE_RDWR = 2; -constexpr int ORBIS_SAVE_DATA_MOUNT_MODE_CREATE = 4; -constexpr int ORBIS_SAVE_DATA_MOUNT_MODE_DESTRUCT_OFF = 8; -constexpr int ORBIS_SAVE_DATA_MOUNT_MODE_COPY_ICON = 16; -constexpr int ORBIS_SAVE_DATA_MOUNT_MODE_CREATE2 = 32; -typedef struct _OrbisSaveDataEventParam OrbisSaveDataEventParam; - -typedef u32 OrbisSaveDataSortKey; -#define ORBIS_SAVE_DATA_SORT_KEY_DIRNAME (0) -#define ORBIS_SAVE_DATA_SORT_KEY_USER_PARAM (1) -#define ORBIS_SAVE_DATA_SORT_KEY_BLOCKS (2) -#define ORBIS_SAVE_DATA_SORT_KEY_MTIME (3) -#define ORBIS_SAVE_DATA_SORT_KEY_FREE_BLOCKS (5) - -typedef u32 OrbisSaveDataSortOrder; -#define ORBIS_SAVE_DATA_SORT_ORDER_ASCENT (0) -#define ORBIS_SAVE_DATA_SORT_ORDER_DESCENT (1) - -struct OrbisSaveDataDirNameSearchCond { - s32 userId; - int : 32; - const OrbisSaveDataTitleId* titleId; - const OrbisSaveDataDirName* dirName; - OrbisSaveDataSortKey key; - OrbisSaveDataSortOrder order; - u8 reserved[32]; -}; - -struct OrbisSaveDataSearchInfo { - u64 blocks; - u64 freeBlocks; - u8 reserved[32]; -}; - -struct OrbisSaveDataDirNameSearchResult { - u32 hitNum; - int : 32; - OrbisSaveDataDirName* dirNames; - u32 dirNamesNum; - u32 setNum; - OrbisSaveDataParam* params; - OrbisSaveDataSearchInfo* infos; - u8 reserved[12]; - int : 32; -}; - -struct OrbisSaveDataDelete { - s32 userId; - int : 32; - const OrbisSaveDataTitleId* titleId; - const OrbisSaveDataDirName* dirName; - u32 unused; - u8 reserved[32]; - int : 32; -}; - -typedef u32 OrbisSaveDataMemorySyncOption; - -#define SCE_SAVE_DATA_MEMORY_SYNC_OPTION_NONE (0x00000000) -#define SCE_SAVE_DATA_MEMORY_SYNC_OPTION_BLOCKING (0x00000001 << 0) - -struct OrbisSaveDataMemorySync { - s32 userId; - u32 slotId; - OrbisSaveDataMemorySyncOption option; - u8 reserved[28]; -}; - -constexpr int ORBIS_SAVE_DATA_PARAM_TYPE_ALL = 0; -constexpr int ORBIS_SAVE_DATA_PARAM_TYPE_TITLE = 1; -constexpr int ORBIS_SAVE_DATA_PARAM_TYPE_SUB_TITLE = 2; -constexpr int ORBIS_SAVE_DATA_PARAM_TYPE_DETAIL = 3; -constexpr int ORBIS_SAVE_DATA_PARAM_TYPE_USER_PARAM = 4; -constexpr int ORBIS_SAVE_DATA_PARAM_TYPE_MTIME = 5; + std::array _reserved; + + void FromSFO(const PSF& sfo); + + void ToSFO(PSF& sfo) const; +}; + +struct OrbisSaveDataBackup; +struct OrbisSaveDataCheckBackupData; +struct OrbisSaveDataDelete; +struct OrbisSaveDataDirNameSearchCond; +struct OrbisSaveDataDirNameSearchResult; +struct OrbisSaveDataEvent; +struct OrbisSaveDataEventParam; +struct OrbisSaveDataIcon; +struct OrbisSaveDataMemoryGet2; +struct OrbisSaveDataMemorySet2; +struct OrbisSaveDataMemorySetup2; +struct OrbisSaveDataMemorySetupResult; +struct OrbisSaveDataMemorySync; +struct OrbisSaveDataMount2; +struct OrbisSaveDataMount; +struct OrbisSaveDataMountInfo; +struct OrbisSaveDataMountPoint; +struct OrbisSaveDataMountResult; +struct OrbisSaveDataRestoreBackupData; int PS4_SYSV_ABI sceSaveDataAbort(); -int PS4_SYSV_ABI sceSaveDataBackup(); +Error PS4_SYSV_ABI sceSaveDataBackup(const OrbisSaveDataBackup* backup); int PS4_SYSV_ABI sceSaveDataBindPsnAccount(); int PS4_SYSV_ABI sceSaveDataBindPsnAccountForSystemBackup(); int PS4_SYSV_ABI sceSaveDataChangeDatabase(); int PS4_SYSV_ABI sceSaveDataChangeInternal(); -int PS4_SYSV_ABI sceSaveDataCheckBackupData(const OrbisSaveDataCheckBackupData* check); +Error PS4_SYSV_ABI sceSaveDataCheckBackupData(const OrbisSaveDataCheckBackupData* check); int PS4_SYSV_ABI sceSaveDataCheckBackupDataForCdlg(); int PS4_SYSV_ABI sceSaveDataCheckBackupDataInternal(); int PS4_SYSV_ABI sceSaveDataCheckCloudData(); @@ -263,7 +85,7 @@ int PS4_SYSV_ABI sceSaveDataCheckIpmiIfSize(); int PS4_SYSV_ABI sceSaveDataCheckSaveDataBroken(); int PS4_SYSV_ABI sceSaveDataCheckSaveDataVersion(); int PS4_SYSV_ABI sceSaveDataCheckSaveDataVersionLatest(); -int PS4_SYSV_ABI sceSaveDataClearProgress(); +Error PS4_SYSV_ABI sceSaveDataClearProgress(); int PS4_SYSV_ABI sceSaveDataCopy5(); int PS4_SYSV_ABI sceSaveDataCreateUploadData(); int PS4_SYSV_ABI sceSaveDataDebug(); @@ -273,13 +95,13 @@ int PS4_SYSV_ABI sceSaveDataDebugCreateSaveDataRoot(); int PS4_SYSV_ABI sceSaveDataDebugGetThreadId(); int PS4_SYSV_ABI sceSaveDataDebugRemoveSaveDataRoot(); int PS4_SYSV_ABI sceSaveDataDebugTarget(); -int PS4_SYSV_ABI sceSaveDataDelete(const OrbisSaveDataDelete* del); +Error PS4_SYSV_ABI sceSaveDataDelete(const OrbisSaveDataDelete* del); int PS4_SYSV_ABI sceSaveDataDelete5(); int PS4_SYSV_ABI sceSaveDataDeleteAllUser(); int PS4_SYSV_ABI sceSaveDataDeleteCloudData(); int PS4_SYSV_ABI sceSaveDataDeleteUser(); -int PS4_SYSV_ABI sceSaveDataDirNameSearch(const OrbisSaveDataDirNameSearchCond* cond, - OrbisSaveDataDirNameSearchResult* result); +Error PS4_SYSV_ABI sceSaveDataDirNameSearch(const OrbisSaveDataDirNameSearchCond* cond, + OrbisSaveDataDirNameSearchResult* result); int PS4_SYSV_ABI sceSaveDataDirNameSearchInternal(); int PS4_SYSV_ABI sceSaveDataDownload(); int PS4_SYSV_ABI sceSaveDataGetAllSize(); @@ -292,70 +114,70 @@ int PS4_SYSV_ABI sceSaveDataGetClientThreadPriority(); int PS4_SYSV_ABI sceSaveDataGetCloudQuotaInfo(); int PS4_SYSV_ABI sceSaveDataGetDataBaseFilePath(); int PS4_SYSV_ABI sceSaveDataGetEventInfo(); -int PS4_SYSV_ABI sceSaveDataGetEventResult(const OrbisSaveDataEventParam* eventParam, - OrbisSaveDataEvent* event); +Error PS4_SYSV_ABI sceSaveDataGetEventResult(const OrbisSaveDataEventParam* eventParam, + OrbisSaveDataEvent* event); int PS4_SYSV_ABI sceSaveDataGetFormat(); int PS4_SYSV_ABI sceSaveDataGetMountedSaveDataCount(); -int PS4_SYSV_ABI sceSaveDataGetMountInfo(const OrbisSaveDataMountPoint* mountPoint, - OrbisSaveDataMountInfo* info); -int PS4_SYSV_ABI sceSaveDataGetParam(const OrbisSaveDataMountPoint* mountPoint, - const OrbisSaveDataParamType paramType, void* paramBuf, - const size_t paramBufSize, size_t* gotSize); -int PS4_SYSV_ABI sceSaveDataGetProgress(); +Error PS4_SYSV_ABI sceSaveDataGetMountInfo(const OrbisSaveDataMountPoint* mountPoint, + OrbisSaveDataMountInfo* info); +Error PS4_SYSV_ABI sceSaveDataGetParam(const OrbisSaveDataMountPoint* mountPoint, + OrbisSaveDataParamType paramType, void* paramBuf, + size_t paramBufSize, size_t* gotSize); +Error PS4_SYSV_ABI sceSaveDataGetProgress(float* progress); int PS4_SYSV_ABI sceSaveDataGetSaveDataCount(); -int PS4_SYSV_ABI sceSaveDataGetSaveDataMemory(const u32 userId, void* buf, const size_t bufSize, - const int64_t offset); -int PS4_SYSV_ABI sceSaveDataGetSaveDataMemory2(OrbisSaveDataMemoryGet2* getParam); +Error PS4_SYSV_ABI sceSaveDataGetSaveDataMemory(OrbisUserServiceUserId userId, void* buf, + size_t bufSize, int64_t offset); +Error PS4_SYSV_ABI sceSaveDataGetSaveDataMemory2(OrbisSaveDataMemoryGet2* getParam); int PS4_SYSV_ABI sceSaveDataGetSaveDataRootDir(); int PS4_SYSV_ABI sceSaveDataGetSaveDataRootPath(); int PS4_SYSV_ABI sceSaveDataGetSaveDataRootUsbPath(); int PS4_SYSV_ABI sceSaveDataGetSavePoint(); int PS4_SYSV_ABI sceSaveDataGetUpdatedDataCount(); -int PS4_SYSV_ABI sceSaveDataInitialize(); -int PS4_SYSV_ABI sceSaveDataInitialize2(); -int PS4_SYSV_ABI sceSaveDataInitialize3(); +Error PS4_SYSV_ABI sceSaveDataInitialize(void*); +Error PS4_SYSV_ABI sceSaveDataInitialize2(void*); +Error PS4_SYSV_ABI sceSaveDataInitialize3(void*); int PS4_SYSV_ABI sceSaveDataInitializeForCdlg(); int PS4_SYSV_ABI sceSaveDataIsDeletingUsbDb(); int PS4_SYSV_ABI sceSaveDataIsMounted(); -int PS4_SYSV_ABI sceSaveDataLoadIcon(const OrbisSaveDataMountPoint* mountPoint, - OrbisSaveDataIcon* icon); -int PS4_SYSV_ABI sceSaveDataMount(const OrbisSaveDataMount* mount, - OrbisSaveDataMountResult* mount_result); -s32 PS4_SYSV_ABI sceSaveDataMount2(const OrbisSaveDataMount2* mount, - OrbisSaveDataMountResult* mount_result); +Error PS4_SYSV_ABI sceSaveDataLoadIcon(const OrbisSaveDataMountPoint* mountPoint, + OrbisSaveDataIcon* icon); +Error PS4_SYSV_ABI sceSaveDataMount(const OrbisSaveDataMount* mount, + OrbisSaveDataMountResult* mount_result); +Error PS4_SYSV_ABI sceSaveDataMount2(const OrbisSaveDataMount2* mount, + OrbisSaveDataMountResult* mount_result); int PS4_SYSV_ABI sceSaveDataMount5(); int PS4_SYSV_ABI sceSaveDataMountInternal(); int PS4_SYSV_ABI sceSaveDataMountSys(); int PS4_SYSV_ABI sceSaveDataPromote5(); int PS4_SYSV_ABI sceSaveDataRebuildDatabase(); int PS4_SYSV_ABI sceSaveDataRegisterEventCallback(); -int PS4_SYSV_ABI sceSaveDataRestoreBackupData(); +Error PS4_SYSV_ABI sceSaveDataRestoreBackupData(const OrbisSaveDataRestoreBackupData* restore); int PS4_SYSV_ABI sceSaveDataRestoreBackupDataForCdlg(); int PS4_SYSV_ABI sceSaveDataRestoreLoadSaveDataMemory(); -int PS4_SYSV_ABI sceSaveDataSaveIcon(const OrbisSaveDataMountPoint* mountPoint, - const OrbisSaveDataIcon* icon); +Error PS4_SYSV_ABI sceSaveDataSaveIcon(const OrbisSaveDataMountPoint* mountPoint, + const OrbisSaveDataIcon* icon); int PS4_SYSV_ABI sceSaveDataSetAutoUploadSetting(); int PS4_SYSV_ABI sceSaveDataSetEventInfo(); -int PS4_SYSV_ABI sceSaveDataSetParam(const OrbisSaveDataMountPoint* mountPoint, - OrbisSaveDataParamType paramType, const void* paramBuf, - size_t paramBufSize); +Error PS4_SYSV_ABI sceSaveDataSetParam(const OrbisSaveDataMountPoint* mountPoint, + OrbisSaveDataParamType paramType, const void* paramBuf, + size_t paramBufSize); int PS4_SYSV_ABI sceSaveDataSetSaveDataLibraryUser(); -int PS4_SYSV_ABI sceSaveDataSetSaveDataMemory(const u32 userId, const void* buf, - const size_t bufSize, const int64_t offset); -int PS4_SYSV_ABI sceSaveDataSetSaveDataMemory2(const OrbisSaveDataMemorySet2* setParam); -int PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory(u32 userId, size_t memorySize, - OrbisSaveDataParam* param); -int PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory2(const OrbisSaveDataMemorySetup2* setupParam, - OrbisSaveDataMemorySetupResult* result); +Error PS4_SYSV_ABI sceSaveDataSetSaveDataMemory(OrbisUserServiceUserId userId, void* buf, + size_t bufSize, int64_t offset); +Error PS4_SYSV_ABI sceSaveDataSetSaveDataMemory2(const OrbisSaveDataMemorySet2* setParam); +Error PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory(OrbisUserServiceUserId userId, size_t memorySize, + OrbisSaveDataParam* param); +Error PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory2(const OrbisSaveDataMemorySetup2* setupParam, + OrbisSaveDataMemorySetupResult* result); int PS4_SYSV_ABI sceSaveDataShutdownStart(); int PS4_SYSV_ABI sceSaveDataSupportedFakeBrokenStatus(); int PS4_SYSV_ABI sceSaveDataSyncCloudList(); -int PS4_SYSV_ABI sceSaveDataSyncSaveDataMemory(OrbisSaveDataMemorySync* syncParam); -int PS4_SYSV_ABI sceSaveDataTerminate(); +Error PS4_SYSV_ABI sceSaveDataSyncSaveDataMemory(OrbisSaveDataMemorySync* syncParam); +Error PS4_SYSV_ABI sceSaveDataTerminate(); int PS4_SYSV_ABI sceSaveDataTransferringMount(); -int PS4_SYSV_ABI sceSaveDataUmount(const OrbisSaveDataMountPoint* mountPoint); +Error PS4_SYSV_ABI sceSaveDataUmount(const OrbisSaveDataMountPoint* mountPoint); int PS4_SYSV_ABI sceSaveDataUmountSys(); -int PS4_SYSV_ABI sceSaveDataUmountWithBackup(const OrbisSaveDataMountPoint* mountPoint); +Error PS4_SYSV_ABI sceSaveDataUmountWithBackup(const OrbisSaveDataMountPoint* mountPoint); int PS4_SYSV_ABI sceSaveDataUnregisterEventCallback(); int PS4_SYSV_ABI sceSaveDataUpload(); int PS4_SYSV_ABI Func_02E4C4D201716422(); diff --git a/src/core/libraries/system/commondialog.cpp b/src/core/libraries/system/commondialog.cpp index e32e3bb3f14..cb9ce0442d4 100644 --- a/src/core/libraries/system/commondialog.cpp +++ b/src/core/libraries/system/commondialog.cpp @@ -8,6 +8,9 @@ namespace Libraries::CommonDialog { +bool g_isInitialized = false; +bool g_isUsed = false; + int PS4_SYSV_ABI _ZN3sce16CommonDialogUtil12getSelfAppIdEv() { LOG_ERROR(Lib_CommonDlg, "(STUBBED) called"); return ORBIS_OK; @@ -83,14 +86,19 @@ int PS4_SYSV_ABI _ZTVN3sce16CommonDialogUtil6ClientE() { return ORBIS_OK; } -int PS4_SYSV_ABI sceCommonDialogInitialize() { - LOG_ERROR(Lib_CommonDlg, "(DUMMY) called"); - return ORBIS_OK; +Error PS4_SYSV_ABI sceCommonDialogInitialize() { + if (g_isInitialized) { + LOG_INFO(Lib_CommonDlg, "already initialized"); + return Error::ALREADY_SYSTEM_INITIALIZED; + } + LOG_DEBUG(Lib_CommonDlg, "initialized"); + g_isInitialized = true; + return Error::OK; } -int PS4_SYSV_ABI sceCommonDialogIsUsed() { - LOG_ERROR(Lib_CommonDlg, "(STUBBED) called"); - return ORBIS_OK; +bool PS4_SYSV_ABI sceCommonDialogIsUsed() { + LOG_TRACE(Lib_CommonDlg, "called"); + return g_isUsed; } int PS4_SYSV_ABI Func_0FF577E4E8457883() { diff --git a/src/core/libraries/system/commondialog.h b/src/core/libraries/system/commondialog.h index 110e9cc99e2..6b8ea3d9537 100644 --- a/src/core/libraries/system/commondialog.h +++ b/src/core/libraries/system/commondialog.h @@ -11,9 +11,44 @@ class SymbolsResolver; namespace Libraries::CommonDialog { -struct OrbisCommonDialogBaseParam { +enum class Status : u32 { + NONE = 0, + INITIALIZED = 1, + RUNNING = 2, + FINISHED = 3, +}; + +enum class Result : u32 { + OK = 0, + USER_CANCELED = 1, +}; + +enum class Error : u32 { + OK = 0, + NOT_SYSTEM_INITIALIZED = 0x80B80001, + ALREADY_SYSTEM_INITIALIZED = 0x80B80002, + NOT_INITIALIZED = 0x80B80003, + ALREADY_INITIALIZED = 0x80B80004, + NOT_FINISHED = 0x80B80005, + INVALID_STATE = 0x80B80006, + RESULT_NONE = 0x80B80007, + BUSY = 0x80B80008, + OUT_OF_MEMORY = 0x80B80009, + PARAM_INVALID = 0x80B8000A, + NOT_RUNNING = 0x80B8000B, + ALREADY_CLOSE = 0x80B8000C, + ARG_NULL = 0x80B8000D, + UNEXPECTED_FATAL = 0x80B8000E, + NOT_SUPPORTED = 0x80B8000F, + INHIBIT_SHAREPLAY_CLIENT = 0x80B80010, +}; + +extern bool g_isInitialized; +extern bool g_isUsed; + +struct BaseParam { std::size_t size; - u8 reserved[36]; + std::array reserved; u32 magic; }; @@ -32,8 +67,8 @@ int PS4_SYSV_ABI _ZNK3sce16CommonDialogUtil6Client8getAppIdEv(); int PS4_SYSV_ABI _ZNK3sce16CommonDialogUtil6Client8isFinishEv(); int PS4_SYSV_ABI _ZNK3sce16CommonDialogUtil6Client9getResultEv(); int PS4_SYSV_ABI _ZTVN3sce16CommonDialogUtil6ClientE(); -int PS4_SYSV_ABI sceCommonDialogInitialize(); -int PS4_SYSV_ABI sceCommonDialogIsUsed(); +Error PS4_SYSV_ABI sceCommonDialogInitialize(); +bool PS4_SYSV_ABI sceCommonDialogIsUsed(); int PS4_SYSV_ABI Func_0FF577E4E8457883(); int PS4_SYSV_ABI Func_41716C2CE379416C(); int PS4_SYSV_ABI Func_483A427D8F6E0748(); diff --git a/src/core/libraries/system/msgdialog.cpp b/src/core/libraries/system/msgdialog.cpp index 452feec9342..7d924e4ad8c 100644 --- a/src/core/libraries/system/msgdialog.cpp +++ b/src/core/libraries/system/msgdialog.cpp @@ -1,79 +1,152 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include +#include + +#include "common/assert.h" #include "common/logging/log.h" -#include "core/libraries/error_codes.h" #include "core/libraries/libs.h" #include "core/libraries/system/msgdialog.h" - -#include +#include "imgui_internal.h" +#include "msgdialog_ui.h" namespace Libraries::MsgDialog { -int PS4_SYSV_ABI sceMsgDialogClose() { - LOG_ERROR(Lib_MsgDlg, "(STUBBED) called"); - return ORBIS_OK; +using CommonDialog::Error; +using CommonDialog::Result; +using CommonDialog::Status; + +static auto g_status = Status::NONE; +static MsgDialogState g_state{}; +static DialogResult g_result{}; +static MsgDialogUi g_msg_dialog_ui; + +Error PS4_SYSV_ABI sceMsgDialogClose() { + LOG_DEBUG(Lib_MsgDlg, "called"); + if (g_status != Status::RUNNING) { + return Error::NOT_RUNNING; + } + g_msg_dialog_ui.Finish(ButtonId::INVALID); + return Error::OK; } -int PS4_SYSV_ABI sceMsgDialogGetResult() { - LOG_ERROR(Lib_MsgDlg, "(STUBBED) called"); - return ORBIS_OK; +Error PS4_SYSV_ABI sceMsgDialogGetResult(DialogResult* result) { + LOG_DEBUG(Lib_MsgDlg, "called"); + if (g_status != Status::FINISHED) { + return Error::NOT_FINISHED; + } + if (result == nullptr) { + return Error::ARG_NULL; + } + *result = g_result; + return Error::OK; } -int PS4_SYSV_ABI sceMsgDialogGetStatus() { - LOG_ERROR(Lib_MsgDlg, "(STUBBED) called"); - return ORBIS_OK; +Status PS4_SYSV_ABI sceMsgDialogGetStatus() { + LOG_TRACE(Lib_MsgDlg, "called status={}", magic_enum::enum_name(g_status)); + return g_status; } -int PS4_SYSV_ABI sceMsgDialogInitialize() { - LOG_ERROR(Lib_MsgDlg, "(STUBBED) called"); - return ORBIS_OK; +Error PS4_SYSV_ABI sceMsgDialogInitialize() { + LOG_DEBUG(Lib_MsgDlg, "called"); + if (!CommonDialog::g_isInitialized) { + return Error::NOT_SYSTEM_INITIALIZED; + } + if (g_status != Status::NONE) { + return Error::ALREADY_INITIALIZED; + } + if (CommonDialog::g_isUsed) { + return Error::BUSY; + } + g_status = Status::INITIALIZED; + CommonDialog::g_isUsed = true; + + return Error::OK; } -s32 PS4_SYSV_ABI sceMsgDialogOpen(const OrbisMsgDialogParam* param) { - LOG_ERROR(Lib_MsgDlg, "(STUBBED) called"); - switch (param->mode) { - case ORBIS_MSG_DIALOG_MODE_USER_MSG: - LOG_INFO(Lib_MsgDlg, "sceMsgDialogOpen userMsg type = %s msg = %s", - magic_enum::enum_name(param->userMsgParam->buttonType), param->userMsgParam->msg); - break; - case ORBIS_MSG_DIALOG_MODE_PROGRESS_BAR: - LOG_INFO(Lib_MsgDlg, "sceMsgDialogOpen progressBar type = %s msg = %s", - magic_enum::enum_name(param->progBarParam->barType), param->progBarParam->msg); - break; - case ORBIS_MSG_DIALOG_MODE_SYSTEM_MSG: - LOG_INFO(Lib_MsgDlg, "sceMsgDialogOpen systemMsg type: %s", - magic_enum::enum_name(param->sysMsgParam->sysMsgType)); - break; - default: - break; - } - return ORBIS_OK; +Error PS4_SYSV_ABI sceMsgDialogOpen(const OrbisParam* param) { + if (g_status != Status::INITIALIZED && g_status != Status::FINISHED) { + LOG_INFO(Lib_MsgDlg, "called without initialize"); + return Error::INVALID_STATE; + } + if (param == nullptr) { + LOG_DEBUG(Lib_MsgDlg, "called param:(NULL)"); + return Error::ARG_NULL; + } + LOG_DEBUG(Lib_MsgDlg, "called param->mode: {}", magic_enum::enum_name(param->mode)); + ASSERT(param->size == sizeof(OrbisParam)); + ASSERT(param->baseParam.size == sizeof(CommonDialog::BaseParam)); + g_result = {}; + g_state = MsgDialogState{*param}; + g_status = Status::RUNNING; + g_msg_dialog_ui = MsgDialogUi(&g_state, &g_status, &g_result); + return Error::OK; } -int PS4_SYSV_ABI sceMsgDialogProgressBarInc() { - LOG_ERROR(Lib_MsgDlg, "(STUBBED) called"); - return ORBIS_OK; +Error PS4_SYSV_ABI sceMsgDialogProgressBarInc(OrbisMsgDialogProgressBarTarget target, u32 delta) { + LOG_DEBUG(Lib_MsgDlg, "called"); + if (g_status != Status::RUNNING) { + return Error::NOT_RUNNING; + } + if (g_state.GetMode() != MsgDialogMode::PROGRESS_BAR) { + return Error::NOT_SUPPORTED; + } + if (target != OrbisMsgDialogProgressBarTarget::DEFAULT) { + return Error::PARAM_INVALID; + } + g_state.GetState().progress += delta; + return Error::OK; } -int PS4_SYSV_ABI sceMsgDialogProgressBarSetMsg() { - LOG_ERROR(Lib_MsgDlg, "(STUBBED) called"); - return ORBIS_OK; +Error PS4_SYSV_ABI sceMsgDialogProgressBarSetMsg(OrbisMsgDialogProgressBarTarget target, + const char* msg) { + LOG_DEBUG(Lib_MsgDlg, "called"); + if (g_status != Status::RUNNING) { + return Error::NOT_RUNNING; + } + if (g_state.GetMode() != MsgDialogMode::PROGRESS_BAR) { + return Error::NOT_SUPPORTED; + } + if (target != OrbisMsgDialogProgressBarTarget::DEFAULT) { + return Error::PARAM_INVALID; + } + g_state.GetState().msg = msg; + return Error::OK; } -int PS4_SYSV_ABI sceMsgDialogProgressBarSetValue() { - LOG_ERROR(Lib_MsgDlg, "(STUBBED) called"); - return ORBIS_OK; +Error PS4_SYSV_ABI sceMsgDialogProgressBarSetValue(OrbisMsgDialogProgressBarTarget target, + u32 value) { + LOG_DEBUG(Lib_MsgDlg, "called"); + if (g_status != Status::RUNNING) { + return Error::NOT_RUNNING; + } + if (g_state.GetMode() != MsgDialogMode::PROGRESS_BAR) { + return Error::NOT_SUPPORTED; + } + if (target != OrbisMsgDialogProgressBarTarget::DEFAULT) { + return Error::PARAM_INVALID; + } + g_state.GetState().progress = value; + return Error::OK; } -int PS4_SYSV_ABI sceMsgDialogTerminate() { - LOG_ERROR(Lib_MsgDlg, "(STUBBED) called"); - return ORBIS_OK; +Error PS4_SYSV_ABI sceMsgDialogTerminate() { + LOG_DEBUG(Lib_MsgDlg, "called"); + if (g_status == Status::RUNNING) { + sceMsgDialogClose(); + } + if (g_status == Status::NONE) { + return Error::NOT_INITIALIZED; + } + g_status = Status::NONE; + CommonDialog::g_isUsed = false; + return Error::OK; } -int PS4_SYSV_ABI sceMsgDialogUpdateStatus() { - LOG_ERROR(Lib_MsgDlg, "(STUBBED) called"); - return ORBIS_OK; +Status PS4_SYSV_ABI sceMsgDialogUpdateStatus() { + LOG_TRACE(Lib_MsgDlg, "called status={}", magic_enum::enum_name(g_status)); + return g_status; } void RegisterlibSceMsgDialog(Core::Loader::SymbolsResolver* sym) { diff --git a/src/core/libraries/system/msgdialog.h b/src/core/libraries/system/msgdialog.h index 28d379138cb..b8a1f3f0797 100644 --- a/src/core/libraries/system/msgdialog.h +++ b/src/core/libraries/system/msgdialog.h @@ -3,7 +3,6 @@ #pragma once -#include "common/types.h" #include "core/libraries/system/commondialog.h" namespace Core::Loader { @@ -12,95 +11,23 @@ class SymbolsResolver; namespace Libraries::MsgDialog { -using OrbisUserServiceUserId = s32; - -enum OrbisCommonDialogStatus { - ORBIS_COMMON_DIALOG_STATUS_NONE = 0, - ORBIS_COMMON_DIALOG_STATUS_INITIALIZED = 1, - ORBIS_COMMON_DIALOG_STATUS_RUNNING = 2, - ORBIS_COMMON_DIALOG_STATUS_FINISHED = 3 -}; - -enum OrbisMsgDialogMode { - ORBIS_MSG_DIALOG_MODE_USER_MSG = 1, - ORBIS_MSG_DIALOG_MODE_PROGRESS_BAR = 2, - ORBIS_MSG_DIALOG_MODE_SYSTEM_MSG = 3, -}; - -enum OrbisMsgDialogButtonType { - ORBIS_MSG_DIALOG_BUTTON_TYPE_OK = 0, - ORBIS_MSG_DIALOG_BUTTON_TYPE_YESNO = 1, - ORBIS_MSG_DIALOG_BUTTON_TYPE_NONE = 2, - ORBIS_MSG_DIALOG_BUTTON_TYPE_OK_CANCEL = 3, - ORBIS_MSG_DIALOG_BUTTON_TYPE_WAIT = 5, - ORBIS_MSG_DIALOG_BUTTON_TYPE_WAIT_CANCEL = 6, - ORBIS_MSG_DIALOG_BUTTON_TYPE_YESNO_FOCUS_NO = 7, - ORBIS_MSG_DIALOG_BUTTON_TYPE_OK_CANCEL_FOCUS_CANCEL = 8, - ORBIS_MSG_DIALOG_BUTTON_TYPE_2BUTTONS = 9, -}; - -enum OrbisMsgDialogProgressBarType { - ORBIS_MSG_DIALOG_PROGRESSBAR_TYPE_PERCENTAGE = 0, - ORBIS_MSG_DIALOG_PROGRESSBAR_TYPE_PERCENTAGE_CANCEL = 1, -}; - -enum OrbisMsgDialogSystemMessageType { - ORBIS_MSG_DIALOG_SYSMSG_TYPE_TRC_EMPTY_STORE = 0, - ORBIS_MSG_DIALOG_SYSMSG_TYPE_TRC_PSN_CHAT_RESTRICTION = 1, - ORBIS_MSG_DIALOG_SYSMSG_TYPE_TRC_PSN_UGC_RESTRICTION = 2, - ORBIS_MSG_DIALOG_SYSMSG_TYPE_CAMERA_NOT_CONNECTED = 4, - ORBIS_MSG_DIALOG_SYSMSG_TYPE_WARNING_PROFILE_PICTURE_AND_NAME_NOT_SHARED = 5, -}; - -struct OrbisMsgDialogButtonsParam { - const char* msg1; - const char* msg2; - char reserved[32]; -}; - -struct OrbisMsgDialogUserMessageParam { - OrbisMsgDialogButtonType buttonType; - s32 : 32; - const char* msg; - OrbisMsgDialogButtonsParam* buttonsParam; - char reserved[24]; -}; - -struct OrbisMsgDialogProgressBarParam { - OrbisMsgDialogProgressBarType barType; - int32_t : 32; - const char* msg; - char reserved[64]; -}; - -struct OrbisMsgDialogSystemMessageParam { - OrbisMsgDialogSystemMessageType sysMsgType; - char reserved[32]; -}; - -struct OrbisMsgDialogParam { - CommonDialog::OrbisCommonDialogBaseParam baseParam; - std::size_t size; - OrbisMsgDialogMode mode; - s32 : 32; - OrbisMsgDialogUserMessageParam* userMsgParam; - OrbisMsgDialogProgressBarParam* progBarParam; - OrbisMsgDialogSystemMessageParam* sysMsgParam; - OrbisUserServiceUserId userId; - char reserved[40]; - s32 : 32; -}; - -int PS4_SYSV_ABI sceMsgDialogClose(); -int PS4_SYSV_ABI sceMsgDialogGetResult(); -int PS4_SYSV_ABI sceMsgDialogGetStatus(); -int PS4_SYSV_ABI sceMsgDialogInitialize(); -s32 PS4_SYSV_ABI sceMsgDialogOpen(const OrbisMsgDialogParam* param); -int PS4_SYSV_ABI sceMsgDialogProgressBarInc(); -int PS4_SYSV_ABI sceMsgDialogProgressBarSetMsg(); -int PS4_SYSV_ABI sceMsgDialogProgressBarSetValue(); -int PS4_SYSV_ABI sceMsgDialogTerminate(); -int PS4_SYSV_ABI sceMsgDialogUpdateStatus(); +struct DialogResult; +struct OrbisParam; +enum class OrbisMsgDialogProgressBarTarget : u32; + +CommonDialog::Error PS4_SYSV_ABI sceMsgDialogClose(); +CommonDialog::Error PS4_SYSV_ABI sceMsgDialogGetResult(DialogResult* result); +CommonDialog::Status PS4_SYSV_ABI sceMsgDialogGetStatus(); +CommonDialog::Error PS4_SYSV_ABI sceMsgDialogInitialize(); +CommonDialog::Error PS4_SYSV_ABI sceMsgDialogOpen(const OrbisParam* param); +CommonDialog::Error PS4_SYSV_ABI sceMsgDialogProgressBarInc(OrbisMsgDialogProgressBarTarget, + u32 delta); +CommonDialog::Error PS4_SYSV_ABI sceMsgDialogProgressBarSetMsg(OrbisMsgDialogProgressBarTarget, + const char* msg); +CommonDialog::Error PS4_SYSV_ABI sceMsgDialogProgressBarSetValue(OrbisMsgDialogProgressBarTarget, + u32 value); +CommonDialog::Error PS4_SYSV_ABI sceMsgDialogTerminate(); +CommonDialog::Status PS4_SYSV_ABI sceMsgDialogUpdateStatus(); void RegisterlibSceMsgDialog(Core::Loader::SymbolsResolver* sym); } // namespace Libraries::MsgDialog diff --git a/src/core/libraries/system/msgdialog_ui.cpp b/src/core/libraries/system/msgdialog_ui.cpp new file mode 100644 index 00000000000..15d6f4dbd50 --- /dev/null +++ b/src/core/libraries/system/msgdialog_ui.cpp @@ -0,0 +1,294 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include + +#include +#include "common/assert.h" +#include "imgui/imgui_std.h" +#include "msgdialog_ui.h" + +using namespace ImGui; +using namespace Libraries::CommonDialog; +using namespace Libraries::MsgDialog; + +static constexpr ImVec2 BUTTON_SIZE{100.0f, 30.0f}; +static constexpr float PROGRESS_BAR_WIDTH{0.8f}; + +struct { + int count = 0; + const char* text1; + const char* text2; +} static constexpr user_button_texts[] = { + {1, "OK"}, // 0 OK + {2, "Yes", "No"}, // 1 YESNO + {0}, // 2 NONE + {2, "OK", "Cancel"}, // 3 OK_CANCEL + {}, // 4 !!NOP + {1, "Wait"}, // 5 WAIT + {2, "Wait", "Cancel"}, // 6 WAIT_CANCEL + {2, "Yes", "No"}, // 7 YESNO_FOCUS_NO + {2, "OK", "Cancel"}, // 8 OK_CANCEL_FOCUS_CANCEL + {0xFF}, // 9 TWO_BUTTONS +}; +static_assert(std::size(user_button_texts) == static_cast(ButtonType::TWO_BUTTONS) + 1); + +MsgDialogState::MsgDialogState(const OrbisParam& param) { + this->mode = param.mode; + switch (mode) { + case MsgDialogMode::USER_MSG: { + ASSERT(param.userMsgParam); + const auto& v = *param.userMsgParam; + auto state = UserState{ + .type = v.buttonType, + .msg = std::string(v.msg), + }; + if (v.buttonType == ButtonType::TWO_BUTTONS) { + ASSERT(v.buttonsParam); + state.btn_param1 = std::string(v.buttonsParam->msg1); + state.btn_param2 = std::string(v.buttonsParam->msg2); + } + this->state = state; + } break; + case MsgDialogMode::PROGRESS_BAR: { + ASSERT(param.progBarParam); + const auto& v = *param.progBarParam; + this->state = ProgressState{ + .type = v.barType, + .msg = std::string(v.msg), + .progress = 0, + }; + } break; + case MsgDialogMode::SYSTEM_MSG: { + ASSERT(param.sysMsgParam); + const auto& v = *param.sysMsgParam; + this->state = SystemState{ + .type = v.sysMsgType, + }; + } break; + default: + UNREACHABLE_MSG("Unknown dialog mode"); + } +} + +MsgDialogState::MsgDialogState(UserState mode) { + this->mode = MsgDialogMode::USER_MSG; + this->state = mode; +} + +MsgDialogState::MsgDialogState(ProgressState mode) { + this->mode = MsgDialogMode::PROGRESS_BAR; + this->state = mode; +} + +MsgDialogState::MsgDialogState(SystemState mode) { + this->mode = MsgDialogMode::SYSTEM_MSG; + this->state = mode; +} + +void MsgDialogUi::DrawUser() { + const auto& [button_type, msg, btn_param1, btn_param2] = + state->GetState(); + const auto ws = GetWindowSize(); + if (!msg.empty()) { + DrawCenteredText(&msg.front(), &msg.back() + 1, + GetContentRegionAvail() - ImVec2{0.0f, 15.0f + BUTTON_SIZE.y}); + } + ASSERT(button_type <= ButtonType::TWO_BUTTONS); + auto [count, text1, text2] = user_button_texts[static_cast(button_type)]; + if (count == 0xFF) { // TWO_BUTTONS -> User defined message + count = 2; + text1 = btn_param1.c_str(); + text2 = btn_param2.c_str(); + } + const bool focus_first = button_type < ButtonType::YESNO_FOCUS_NO; + SetCursorPos({ + ws.x / 2.0f - BUTTON_SIZE.x / 2.0f * static_cast(count), + ws.y - 10.0f - BUTTON_SIZE.y, + }); + BeginGroup(); + if (count > 0) { + // First button at the right, so we render the second button first + if (count == 2) { + PushID(2); + if (Button(text2, BUTTON_SIZE)) { + switch (button_type) { + case ButtonType::OK_CANCEL: + case ButtonType::WAIT_CANCEL: + case ButtonType::OK_CANCEL_FOCUS_CANCEL: + Finish(ButtonId::INVALID, Result::USER_CANCELED); + break; + default: + Finish(ButtonId::BUTTON2); + break; + } + } + if ((first_render || IsKeyPressed(ImGuiKey_GamepadFaceRight)) && !focus_first) { + SetItemCurrentNavFocus(); + } + PopID(); + SameLine(); + } + PushID(1); + if (Button(text1, BUTTON_SIZE)) { + Finish(ButtonId::BUTTON1); + } + if ((first_render || IsKeyPressed(ImGuiKey_GamepadFaceRight)) && focus_first) { + SetItemCurrentNavFocus(); + } + PopID(); + SameLine(); + } + EndGroup(); +} + +void MsgDialogUi::DrawProgressBar() { + const auto& [bar_type, msg, progress_bar_value] = + state->GetState(); + DrawCenteredText(msg.c_str()); + const auto ws = GetWindowSize(); + SetCursorPos({ + ws.x * ((1 - PROGRESS_BAR_WIDTH) / 2.0f), + ws.y - 10.0f - BUTTON_SIZE.y, + }); + const bool has_cancel = bar_type == ProgressBarType::PERCENTAGE_CANCEL; + float bar_width = PROGRESS_BAR_WIDTH * ws.x; + if (has_cancel) { + bar_width -= BUTTON_SIZE.x - 10.0f; + } + BeginGroup(); + ProgressBar(static_cast(progress_bar_value) / 100.0f, {bar_width, BUTTON_SIZE.y}); + if (has_cancel) { + SameLine(); + if (Button("Cancel", BUTTON_SIZE)) { + Finish(ButtonId::INVALID, Result::USER_CANCELED); + } + if (first_render) { + SetItemCurrentNavFocus(); + } + } + EndGroup(); +} + +struct { + const char* text; +} static constexpr system_message_texts[] = { + "No product available in the store.", // TRC_EMPTY_STORE + "PSN chat restriction.", // TRC_PSN_CHAT_RESTRICTION + "User-generated Media restriction", // TRC_PSN_UGC_RESTRICTION + nullptr, // !!NOP + "Camera not connected.", // CAMERA_NOT_CONNECTED + "Warning: profile picture and name are not set", // WARNING_PROFILE_PICTURE_AND_NAME_NOT_SHARED +}; +static_assert(std::size(system_message_texts) == + static_cast(SystemMessageType::WARNING_PROFILE_PICTURE_AND_NAME_NOT_SHARED) + 1); + +void MsgDialogUi::DrawSystemMessage() { + // TODO: Implement go to settings & user profile + const auto& [msg_type] = state->GetState(); + ASSERT(msg_type <= SystemMessageType::WARNING_PROFILE_PICTURE_AND_NAME_NOT_SHARED); + auto [msg] = system_message_texts[static_cast(msg_type)]; + DrawCenteredText(msg); + const auto ws = GetWindowSize(); + SetCursorPos({ + ws.x / 2.0f - BUTTON_SIZE.x / 2.0f, + ws.y - 10.0f - BUTTON_SIZE.y, + }); + if (Button("OK", BUTTON_SIZE)) { + Finish(ButtonId::OK); + } + if (first_render) { + SetItemCurrentNavFocus(); + } +} + +MsgDialogUi::MsgDialogUi(MsgDialogState* state, Status* status, DialogResult* result) + : state(state), status(status), result(result) { + if (status && *status == Status::RUNNING) { + first_render = true; + AddLayer(this); + } +} +MsgDialogUi::~MsgDialogUi() { + Finish(ButtonId::INVALID); +} +MsgDialogUi::MsgDialogUi(MsgDialogUi&& other) noexcept + : Layer(other), state(other.state), status(other.status), result(other.result) { + other.state = nullptr; + other.status = nullptr; + other.result = nullptr; +} +MsgDialogUi& MsgDialogUi::operator=(MsgDialogUi other) { + using std::swap; + swap(state, other.state); + swap(status, other.status); + swap(result, other.result); + if (status && *status == Status::RUNNING) { + first_render = true; + AddLayer(this); + } + return *this; +} + +void MsgDialogUi::Finish(ButtonId buttonId, Result r) { + if (result) { + result->result = r; + result->buttonId = buttonId; + } + if (status) { + *status = Status::FINISHED; + } + state = nullptr; + status = nullptr; + result = nullptr; + RemoveLayer(this); +} + +void MsgDialogUi::Draw() { + if (status == nullptr || *status != Status::RUNNING) { + return; + } + const auto& io = GetIO(); + + const ImVec2 window_size{ + std::min(io.DisplaySize.x, 500.0f), + std::min(io.DisplaySize.y, 300.0f), + }; + + CentralizeWindow(); + SetNextWindowSize(window_size); + SetNextWindowCollapsed(false); + if (first_render || !io.NavActive) { + SetNextWindowFocus(); + } + KeepNavHighlight(); + if (Begin("Message Dialog##MessageDialog", nullptr, + ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings)) { + switch (state->GetMode()) { + case MsgDialogMode::USER_MSG: + DrawUser(); + break; + case MsgDialogMode::PROGRESS_BAR: + DrawProgressBar(); + break; + case MsgDialogMode::SYSTEM_MSG: + DrawSystemMessage(); + break; + } + } + End(); + + first_render = false; +} + +DialogResult Libraries::MsgDialog::ShowMsgDialog(MsgDialogState state, bool block) { + DialogResult result{}; + Status status = Status::RUNNING; + MsgDialogUi dialog(&state, &status, &result); + if (block) { + while (status == Status::RUNNING) { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + } + return result; +} diff --git a/src/core/libraries/system/msgdialog_ui.h b/src/core/libraries/system/msgdialog_ui.h new file mode 100644 index 00000000000..d24ec067c51 --- /dev/null +++ b/src/core/libraries/system/msgdialog_ui.h @@ -0,0 +1,181 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +#include "common/fixed_value.h" +#include "common/types.h" +#include "core/libraries/system/commondialog.h" +#include "imgui/imgui_layer.h" + +namespace Libraries::MsgDialog { + +using OrbisUserServiceUserId = s32; + +enum class MsgDialogMode : u32 { + USER_MSG = 1, + PROGRESS_BAR = 2, + SYSTEM_MSG = 3, +}; + +enum class ButtonId : u32 { + INVALID = 0, + OK = 1, + YES = 1, + NO = 2, + BUTTON1 = 1, + BUTTON2 = 2, +}; + +enum class ButtonType : u32 { + OK = 0, + YESNO = 1, + NONE = 2, + OK_CANCEL = 3, + WAIT = 5, + WAIT_CANCEL = 6, + YESNO_FOCUS_NO = 7, + OK_CANCEL_FOCUS_CANCEL = 8, + TWO_BUTTONS = 9, +}; + +enum class ProgressBarType : u32 { + PERCENTAGE = 0, + PERCENTAGE_CANCEL = 1, +}; + +enum class SystemMessageType : u32 { + TRC_EMPTY_STORE = 0, + TRC_PSN_CHAT_RESTRICTION = 1, + TRC_PSN_UGC_RESTRICTION = 2, + CAMERA_NOT_CONNECTED = 4, + WARNING_PROFILE_PICTURE_AND_NAME_NOT_SHARED = 5, +}; + +enum class OrbisMsgDialogProgressBarTarget : u32 { + DEFAULT = 0, +}; + +struct ButtonsParam { + const char* msg1{}; + const char* msg2{}; + std::array reserved{}; +}; + +struct UserMessageParam { + ButtonType buttonType{}; + s32 : 32; + const char* msg{}; + ButtonsParam* buttonsParam{}; + std::array reserved{}; +}; + +struct ProgressBarParam { + ProgressBarType barType{}; + s32 : 32; + const char* msg{}; + std::array reserved{}; +}; + +struct SystemMessageParam { + SystemMessageType sysMsgType{}; + std::array reserved{}; +}; + +struct OrbisParam { + CommonDialog::BaseParam baseParam; + std::size_t size; + MsgDialogMode mode; + s32 : 32; + UserMessageParam* userMsgParam; + ProgressBarParam* progBarParam; + SystemMessageParam* sysMsgParam; + OrbisUserServiceUserId userId; + std::array reserved; + s32 : 32; +}; + +struct DialogResult { + FixedValue mode{}; + CommonDialog::Result result{CommonDialog::Result::OK}; + ButtonId buttonId{ButtonId::INVALID}; + std::array reserved{}; +}; + +// State is used to copy all the data from the param struct +class MsgDialogState { +public: + struct UserState { + ButtonType type{}; + std::string msg{}; + std::string btn_param1{}; + std::string btn_param2{}; + }; + struct ProgressState { + ProgressBarType type{}; + std::string msg{}; + u32 progress{}; + }; + struct SystemState { + SystemMessageType type{}; + }; + +private: + OrbisUserServiceUserId user_id{}; + MsgDialogMode mode{}; + std::variant state{std::monostate{}}; + +public: + explicit MsgDialogState(const OrbisParam& param); + + explicit MsgDialogState(UserState mode); + explicit MsgDialogState(ProgressState mode); + explicit MsgDialogState(SystemState mode); + + MsgDialogState() = default; + + [[nodiscard]] OrbisUserServiceUserId GetUserId() const { + return user_id; + } + + [[nodiscard]] MsgDialogMode GetMode() const { + return mode; + } + + template + [[nodiscard]] T& GetState() { + return std::get(state); + } +}; + +class MsgDialogUi final : public ImGui::Layer { + bool first_render{false}; + MsgDialogState* state{}; + CommonDialog::Status* status{}; + DialogResult* result{}; + + void DrawUser(); + void DrawProgressBar(); + void DrawSystemMessage(); + +public: + explicit MsgDialogUi(MsgDialogState* state = nullptr, CommonDialog::Status* status = nullptr, + DialogResult* result = nullptr); + ~MsgDialogUi() override; + MsgDialogUi(const MsgDialogUi& other) = delete; + MsgDialogUi(MsgDialogUi&& other) noexcept; + MsgDialogUi& operator=(MsgDialogUi other); + + void Finish(ButtonId buttonId, CommonDialog::Result r = CommonDialog::Result::OK); + + void Draw() override; +}; + +// Utility function to show a message dialog +// !!! This function can block !!! +DialogResult ShowMsgDialog(MsgDialogState state, bool block = true); + +}; // namespace Libraries::MsgDialog \ No newline at end of file diff --git a/src/core/libraries/system/savedatadialog.cpp b/src/core/libraries/system/savedatadialog.cpp deleted file mode 100644 index d842fd11ae7..00000000000 --- a/src/core/libraries/system/savedatadialog.cpp +++ /dev/null @@ -1,84 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "common/logging/log.h" -#include "core/libraries/error_codes.h" -#include "core/libraries/libs.h" -#include "core/libraries/system/savedatadialog.h" - -namespace Libraries::SaveDataDialog { - -int PS4_SYSV_ABI sceSaveDataDialogClose() { - LOG_ERROR(Lib_SaveDataDialog, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceSaveDataDialogGetResult() { - LOG_ERROR(Lib_SaveDataDialog, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceSaveDataDialogGetStatus() { - LOG_ERROR(Lib_SaveDataDialog, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceSaveDataDialogInitialize() { - LOG_ERROR(Lib_SaveDataDialog, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceSaveDataDialogIsReadyToDisplay() { - LOG_ERROR(Lib_SaveDataDialog, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceSaveDataDialogOpen() { - LOG_ERROR(Lib_SaveDataDialog, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceSaveDataDialogProgressBarInc() { - LOG_ERROR(Lib_SaveDataDialog, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceSaveDataDialogProgressBarSetValue() { - LOG_ERROR(Lib_SaveDataDialog, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceSaveDataDialogTerminate() { - LOG_ERROR(Lib_SaveDataDialog, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceSaveDataDialogUpdateStatus() { - LOG_ERROR(Lib_SaveDataDialog, "(STUBBED) called"); - return 3; // SCE_COMMON_DIALOG_STATUS_FINISHED -} - -void RegisterlibSceSaveDataDialog(Core::Loader::SymbolsResolver* sym) { - LIB_FUNCTION("fH46Lag88XY", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1, - sceSaveDataDialogClose); - LIB_FUNCTION("yEiJ-qqr6Cg", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1, - sceSaveDataDialogGetResult); - LIB_FUNCTION("ERKzksauAJA", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1, - sceSaveDataDialogGetStatus); - LIB_FUNCTION("s9e3+YpRnzw", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1, - sceSaveDataDialogInitialize); - LIB_FUNCTION("en7gNVnh878", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1, - sceSaveDataDialogIsReadyToDisplay); - LIB_FUNCTION("4tPhsP6FpDI", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1, - sceSaveDataDialogOpen); - LIB_FUNCTION("V-uEeFKARJU", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1, - sceSaveDataDialogProgressBarInc); - LIB_FUNCTION("hay1CfTmLyA", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1, - sceSaveDataDialogProgressBarSetValue); - LIB_FUNCTION("YuH2FA7azqQ", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1, - sceSaveDataDialogTerminate); - LIB_FUNCTION("KK3Bdg1RWK0", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1, - sceSaveDataDialogUpdateStatus); -}; - -} // namespace Libraries::SaveDataDialog diff --git a/src/core/libraries/system/savedatadialog.h b/src/core/libraries/system/savedatadialog.h deleted file mode 100644 index e8fe7c75f72..00000000000 --- a/src/core/libraries/system/savedatadialog.h +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include "common/types.h" - -namespace Core::Loader { -class SymbolsResolver; -} - -namespace Libraries::SaveDataDialog { - -int PS4_SYSV_ABI sceSaveDataDialogClose(); -int PS4_SYSV_ABI sceSaveDataDialogGetResult(); -int PS4_SYSV_ABI sceSaveDataDialogGetStatus(); -int PS4_SYSV_ABI sceSaveDataDialogInitialize(); -int PS4_SYSV_ABI sceSaveDataDialogIsReadyToDisplay(); -int PS4_SYSV_ABI sceSaveDataDialogOpen(); -int PS4_SYSV_ABI sceSaveDataDialogProgressBarInc(); -int PS4_SYSV_ABI sceSaveDataDialogProgressBarSetValue(); -int PS4_SYSV_ABI sceSaveDataDialogTerminate(); -int PS4_SYSV_ABI sceSaveDataDialogUpdateStatus(); - -void RegisterlibSceSaveDataDialog(Core::Loader::SymbolsResolver* sym); -} // namespace Libraries::SaveDataDialog diff --git a/src/core/libraries/system/systemservice.cpp b/src/core/libraries/system/systemservice.cpp index d99ec7c7cf2..8002e2bfc1e 100644 --- a/src/core/libraries/system/systemservice.cpp +++ b/src/core/libraries/system/systemservice.cpp @@ -1717,7 +1717,7 @@ int PS4_SYSV_ABI sceSystemServiceGetAppType() { s32 PS4_SYSV_ABI sceSystemServiceGetDisplaySafeAreaInfo(OrbisSystemServiceDisplaySafeAreaInfo* info) { - LOG_INFO(Lib_SystemService, "called"); + LOG_DEBUG(Lib_SystemService, "called"); if (info == nullptr) { LOG_ERROR(Lib_SystemService, "OrbisSystemServiceDisplaySafeAreaInfo is null"); return ORBIS_SYSTEM_SERVICE_ERROR_PARAMETER; diff --git a/src/core/libraries/system/userservice.cpp b/src/core/libraries/system/userservice.cpp index 8c48b3111c7..cd7a721c0ff 100644 --- a/src/core/libraries/system/userservice.cpp +++ b/src/core/libraries/system/userservice.cpp @@ -565,7 +565,7 @@ int PS4_SYSV_ABI sceUserServiceGetLoginFlag() { } s32 PS4_SYSV_ABI sceUserServiceGetLoginUserIdList(OrbisUserServiceLoginUserIdList* userIdList) { - LOG_INFO(Lib_UserService, "called"); + LOG_DEBUG(Lib_UserService, "called"); if (userIdList == nullptr) { LOG_ERROR(Lib_UserService, "user_id is null"); return ORBIS_USER_SERVICE_ERROR_INVALID_ARGUMENT; diff --git a/src/core/libraries/videoout/driver.cpp b/src/core/libraries/videoout/driver.cpp index 91694cfafc8..fa7577907b5 100644 --- a/src/core/libraries/videoout/driver.cpp +++ b/src/core/libraries/videoout/driver.cpp @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include #include #include "common/assert.h" @@ -160,13 +161,7 @@ int VideoOutDriver::UnregisterBuffers(VideoOutPort* port, s32 attributeIndex) { return ORBIS_OK; } -std::chrono::microseconds VideoOutDriver::Flip(const Request& req) { - if (!req) { - return std::chrono::microseconds{0}; - } - - const auto start = std::chrono::high_resolution_clock::now(); - +void VideoOutDriver::Flip(const Request& req) { // Whatever the game is rendering show splash if it is active if (!renderer->ShowSplash(req.frame)) { // Present the frame. @@ -202,9 +197,11 @@ std::chrono::microseconds VideoOutDriver::Flip(const Request& req) { port->buffer_labels[req.index] = 0; port->SignalVoLabel(); } +} - const auto end = std::chrono::high_resolution_clock::now(); - return std::chrono::duration_cast(end - start); +void VideoOutDriver::DrawBlankFrame() { + const auto empty_frame = renderer->PrepareBlankFrame(false); + renderer->Present(empty_frame); } bool VideoOutDriver::SubmitFlip(VideoOutPort* port, s32 index, s64 flip_arg, @@ -260,8 +257,13 @@ void VideoOutDriver::SubmitFlipInternal(VideoOutPort* port, s32 index, s64 flip_ } void VideoOutDriver::PresentThread(std::stop_token token) { - static constexpr std::chrono::milliseconds VblankPeriod{16}; + static constexpr std::chrono::nanoseconds VblankPeriod{16666667}; + const auto vblank_period = VblankPeriod / Config::vblankDiv(); + Common::SetCurrentThreadName("PresentThread"); + Common::SetCurrentThreadRealtime(vblank_period); + + Common::AccurateTimer timer{vblank_period}; const auto receive_request = [this] -> Request { std::scoped_lock lk{mutex}; @@ -273,18 +275,22 @@ void VideoOutDriver::PresentThread(std::stop_token token) { return {}; }; - auto vblank_period = VblankPeriod / Config::vblankDiv(); auto delay = std::chrono::microseconds{0}; while (!token.stop_requested()) { - // Sleep for most of the vblank duration. - std::this_thread::sleep_for(vblank_period - delay); + timer.Start(); // Check if it's time to take a request. auto& vblank_status = main_port.vblank_status; if (vblank_status.count % (main_port.flip_rate + 1) == 0) { const auto request = receive_request(); - delay = Flip(request); - FRAME_END; + if (!request) { + if (!main_port.is_open) { + DrawBlankFrame(); + } + } else { + Flip(request); + FRAME_END; + } } { @@ -303,6 +309,8 @@ void VideoOutDriver::PresentThread(std::stop_token token) { Kernel::SceKernelEvent::Filter::VideoOut, nullptr); } } + + timer.End(); } } diff --git a/src/core/libraries/videoout/driver.h b/src/core/libraries/videoout/driver.h index 6fc74e012d2..2e478b9ee62 100644 --- a/src/core/libraries/videoout/driver.h +++ b/src/core/libraries/videoout/driver.h @@ -101,7 +101,8 @@ class VideoOutDriver { } }; - std::chrono::microseconds Flip(const Request& req); + void Flip(const Request& req); + void DrawBlankFrame(); // Used when there is no flip request to keep ImGui up to date void SubmitFlipInternal(VideoOutPort* port, s32 index, s64 flip_arg, bool is_eop = false); void PresentThread(std::stop_token token); diff --git a/src/core/libraries/videoout/video_out.cpp b/src/core/libraries/videoout/video_out.cpp index a6c1a762358..631f77732f6 100644 --- a/src/core/libraries/videoout/video_out.cpp +++ b/src/core/libraries/videoout/video_out.cpp @@ -140,8 +140,8 @@ s32 PS4_SYSV_ABI sceVideoOutSubmitFlip(s32 handle, s32 bufferIndex, s32 flipMode return ORBIS_VIDEO_OUT_ERROR_INVALID_INDEX; } - LOG_INFO(Lib_VideoOut, "bufferIndex = {}, flipMode = {}, flipArg = {}", bufferIndex, flipMode, - flipArg); + LOG_DEBUG(Lib_VideoOut, "bufferIndex = {}, flipMode = {}, flipArg = {}", bufferIndex, flipMode, + flipArg); if (!driver->SubmitFlip(port, bufferIndex, flipArg)) { LOG_ERROR(Lib_VideoOut, "Flip queue is full"); diff --git a/src/core/linker.cpp b/src/core/linker.cpp index a4d73455012..4e4fa28d20e 100644 --- a/src/core/linker.cpp +++ b/src/core/linker.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include "common/alignment.h" +#include "common/arch.h" #include "common/assert.h" #include "common/config.h" #include "common/logging/log.h" @@ -27,6 +28,7 @@ static PS4_SYSV_ABI void ProgramExitFunc() { } static void RunMainEntry(VAddr addr, EntryParams* params, ExitFunc exit_func) { +#ifdef ARCH_X86_64 // reinterpret_cast(addr)(params, exit_func); // can't be used, stack has to have // a specific layout asm volatile("andq $-16, %%rsp\n" // Align to 16 bytes @@ -46,6 +48,9 @@ static void RunMainEntry(VAddr addr, EntryParams* params, ExitFunc exit_func) { : : "r"(addr), "r"(params), "r"(exit_func) : "rax", "rsi", "rdi"); +#else + UNIMPLEMENTED_MSG("Missing RunMainEntry() implementation for target CPU architecture."); +#endif } Linker::Linker() : memory{Memory::Instance()} {} @@ -70,10 +75,14 @@ void Linker::Execute() { // Configure used flexible memory size. if (const auto* proc_param = GetProcParam()) { - if (proc_param->entry_count >= 3) { + if (proc_param->size >= + offsetof(OrbisProcParam, mem_param) + sizeof(OrbisKernelMemParam*)) { if (const auto* mem_param = proc_param->mem_param) { - if (const auto* flexible_size = mem_param->flexible_memory_size) { - memory->SetupMemoryRegions(*flexible_size); + if (mem_param->size >= + offsetof(OrbisKernelMemParam, flexible_memory_size) + sizeof(u64*)) { + if (const auto* flexible_size = mem_param->flexible_memory_size) { + memory->SetupMemoryRegions(*flexible_size); + } } } } @@ -82,8 +91,7 @@ void Linker::Execute() { // Init primary thread. Common::SetCurrentThreadName("GAME_MainThread"); Libraries::Kernel::pthreadInitSelfMainThread(); - InitializeThreadPatchStack(); - InitTlsForThread(true); + EnsureThreadInitialized(true); // Start shared library modules for (auto& m : m_modules) { @@ -103,7 +111,7 @@ void Linker::Execute() { } } - CleanupThreadPatchStack(); + SetTcbBase(nullptr); } s32 Linker::LoadModule(const std::filesystem::path& elf_name, bool is_dynamic) { @@ -324,6 +332,17 @@ void* Linker::TlsGetAddr(u64 module_index, u64 offset) { return addr + offset; } +thread_local std::once_flag init_tls_flag; + +void Linker::EnsureThreadInitialized(bool is_primary) { + std::call_once(init_tls_flag, [this, is_primary] { +#ifdef ARCH_X86_64 + InitializeThreadPatchStack(); +#endif + InitTlsForThread(is_primary); + }); +} + void Linker::InitTlsForThread(bool is_primary) { static constexpr size_t TcbSize = 0x40; static constexpr size_t TlsAllocAlign = 0x20; diff --git a/src/core/linker.h b/src/core/linker.h index ed1fe400c84..18454f602ae 100644 --- a/src/core/linker.h +++ b/src/core/linker.h @@ -98,7 +98,6 @@ class Linker { } void* TlsGetAddr(u64 module_index, u64 offset); - void InitTlsForThread(bool is_primary = false); s32 LoadModule(const std::filesystem::path& elf_name, bool is_dynamic = false); Module* FindByAddress(VAddr address); @@ -109,8 +108,17 @@ class Linker { void Execute(); void DebugDump(); + template + ReturnType ExecuteGuest(PS4_SYSV_ABI ReturnType (*func)(FuncArgs...), CallArgs&&... args) { + // Make sure TLS is initialized for the thread before entering guest. + EnsureThreadInitialized(); + return func(std::forward(args)...); + } + private: const Module* FindExportedModule(const ModuleInfo& m, const LibraryInfo& l); + void EnsureThreadInitialized(bool is_primary = false); + void InitTlsForThread(bool is_primary); MemoryManager* memory; std::mutex mutex; diff --git a/src/core/memory.cpp b/src/core/memory.cpp index 18535a6e847..ebda00357ce 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -7,6 +7,7 @@ #include "core/libraries/error_codes.h" #include "core/libraries/kernel/memory_management.h" #include "core/memory.h" +#include "video_core/renderer_vulkan/vk_instance.h" #include "video_core/renderer_vulkan/vk_rasterizer.h" namespace Core { @@ -57,9 +58,11 @@ PAddr MemoryManager::Allocate(PAddr search_start, PAddr search_end, size_t size, auto dmem_area = FindDmemArea(search_start); const auto is_suitable = [&] { - const auto area_addr = dmem_area->second.base; - const auto aligned_addr = alignment > 0 ? Common::AlignUp(area_addr, alignment) : area_addr; - const auto remaining_size = dmem_area->second.size - (aligned_addr - area_addr); + const auto aligned_base = alignment > 0 ? Common::AlignUp(dmem_area->second.base, alignment) + : dmem_area->second.base; + const auto alignment_size = aligned_base - dmem_area->second.base; + const auto remaining_size = + dmem_area->second.size >= alignment_size ? dmem_area->second.size - alignment_size : 0; return dmem_area->second.is_free && remaining_size >= size; }; while (!is_suitable() && dmem_area->second.GetEnd() <= search_end) { @@ -99,7 +102,7 @@ void MemoryManager::Free(PAddr phys_addr, size_t size) { } } for (const auto& [addr, size] : remove_list) { - UnmapMemory(addr, size); + UnmapMemoryImpl(addr, size); } // Mark region as free and attempt to coalesce it with neighbours. @@ -122,7 +125,7 @@ int MemoryManager::Reserve(void** out_addr, VAddr virtual_addr, size_t size, Mem const auto& vma = FindVMA(mapped_addr)->second; // If the VMA is mapped, unmap the region first. if (vma.IsMapped()) { - UnmapMemory(mapped_addr, size); + UnmapMemoryImpl(mapped_addr, size); } const size_t remaining_size = vma.base + vma.size - mapped_addr; ASSERT_MSG(vma.type == VMAType::Free && remaining_size >= size); @@ -231,7 +234,10 @@ int MemoryManager::MapFile(void** out_addr, VAddr virtual_addr, size_t size, Mem void MemoryManager::UnmapMemory(VAddr virtual_addr, size_t size) { std::scoped_lock lk{mutex}; + UnmapMemoryImpl(virtual_addr, size); +} +void MemoryManager::UnmapMemoryImpl(VAddr virtual_addr, size_t size) { const auto it = FindVMA(virtual_addr); const auto& vma_base = it->second; ASSERT_MSG(vma_base.Contains(virtual_addr, size), @@ -287,6 +293,61 @@ int MemoryManager::QueryProtection(VAddr addr, void** start, void** end, u32* pr return ORBIS_OK; } +int MemoryManager::Protect(VAddr addr, size_t size, MemoryProt prot) { + std::scoped_lock lk{mutex}; + + // Find the virtual memory area that contains the specified address range. + auto it = FindVMA(addr); + if (it == vma_map.end() || !it->second.Contains(addr, size)) { + LOG_ERROR(Core, "Address range not mapped"); + return ORBIS_KERNEL_ERROR_EINVAL; + } + + VirtualMemoryArea& vma = it->second; + if (vma.type == VMAType::Free) { + LOG_ERROR(Core, "Cannot change protection on free memory region"); + return ORBIS_KERNEL_ERROR_EINVAL; + } + + // Validate protection flags + constexpr static MemoryProt valid_flags = MemoryProt::NoAccess | MemoryProt::CpuRead | + MemoryProt::CpuReadWrite | MemoryProt::GpuRead | + MemoryProt::GpuWrite | MemoryProt::GpuReadWrite; + + MemoryProt invalid_flags = prot & ~valid_flags; + if (u32(invalid_flags) != 0 && u32(invalid_flags) != u32(MemoryProt::NoAccess)) { + LOG_ERROR(Core, "Invalid protection flags: prot = {:#x}, invalid flags = {:#x}", u32(prot), + u32(invalid_flags)); + return ORBIS_KERNEL_ERROR_EINVAL; + } + + // Change protection + vma.prot = prot; + + // Set permissions + Core::MemoryPermission perms{}; + + if (True(prot & MemoryProt::CpuRead)) { + perms |= Core::MemoryPermission::Read; + } + if (True(prot & MemoryProt::CpuReadWrite)) { + perms |= Core::MemoryPermission::ReadWrite; + } + if (True(prot & MemoryProt::GpuRead)) { + perms |= Core::MemoryPermission::Read; + } + if (True(prot & MemoryProt::GpuWrite)) { + perms |= Core::MemoryPermission::Write; + } + if (True(prot & MemoryProt::GpuReadWrite)) { + perms |= Core::MemoryPermission::ReadWrite; + } + + impl.Protect(addr, size, perms); + + return ORBIS_OK; +} + int MemoryManager::VirtualQuery(VAddr addr, int flags, ::Libraries::Kernel::OrbisVirtualQueryInfo* info) { std::scoped_lock lk{mutex}; @@ -303,15 +364,20 @@ int MemoryManager::VirtualQuery(VAddr addr, int flags, const auto& vma = it->second; info->start = vma.base; info->end = vma.base + vma.size; + info->offset = vma.phys_base; + info->protection = static_cast(vma.prot); info->is_flexible.Assign(vma.type == VMAType::Flexible); info->is_direct.Assign(vma.type == VMAType::Direct); - info->is_commited.Assign(vma.type != VMAType::Free); + info->is_stack.Assign(vma.type == VMAType::Stack); + info->is_pooled.Assign(vma.type == VMAType::Pooled); + info->is_committed.Assign(vma.type != VMAType::Free && vma.type != VMAType::Reserved); vma.name.copy(info->name.data(), std::min(info->name.size(), vma.name.size())); if (vma.type == VMAType::Direct) { const auto dmem_it = FindDmemArea(vma.phys_base); ASSERT(dmem_it != dmem_map.end()); - info->offset = vma.phys_base; info->memory_type = dmem_it->second.memory_type; + } else { + info->memory_type = ::Libraries::Kernel::SCE_KERNEL_WB_ONION; } return ORBIS_OK; @@ -353,9 +419,9 @@ int MemoryManager::DirectQueryAvailable(PAddr search_start, PAddr search_end, si const auto aligned_base = alignment > 0 ? Common::AlignUp(dmem_area->second.base, alignment) : dmem_area->second.base; + const auto alignment_size = aligned_base - dmem_area->second.base; const auto remaining_size = - dmem_area->second.size - (aligned_base - dmem_area->second.base); - + dmem_area->second.size >= alignment_size ? dmem_area->second.size - alignment_size : 0; if (remaining_size > max_size) { paddr = aligned_base; max_size = remaining_size; diff --git a/src/core/memory.h b/src/core/memory.h index c8638626a35..73ffab503ba 100644 --- a/src/core/memory.h +++ b/src/core/memory.h @@ -30,6 +30,7 @@ enum class MemoryProt : u32 { GpuWrite = 32, GpuReadWrite = 38, }; +DECLARE_ENUM_FLAG_OPERATORS(MemoryProt) enum class MemoryMapFlags : u32 { NoFlags = 0, @@ -163,9 +164,12 @@ class MemoryManager { int QueryProtection(VAddr addr, void** start, void** end, u32* prot); - int VirtualQuery(VAddr addr, int flags, Libraries::Kernel::OrbisVirtualQueryInfo* info); + int Protect(VAddr addr, size_t size, MemoryProt prot); - int DirectMemoryQuery(PAddr addr, bool find_next, Libraries::Kernel::OrbisQueryInfo* out_info); + int VirtualQuery(VAddr addr, int flags, ::Libraries::Kernel::OrbisVirtualQueryInfo* info); + + int DirectMemoryQuery(PAddr addr, bool find_next, + ::Libraries::Kernel::OrbisQueryInfo* out_info); int DirectQueryAvailable(PAddr search_start, PAddr search_end, size_t alignment, PAddr* phys_addr_out, size_t* size_out); @@ -214,11 +218,13 @@ class MemoryManager { DMemHandle Split(DMemHandle dmem_handle, size_t offset_in_area); + void UnmapMemoryImpl(VAddr virtual_addr, size_t size); + private: AddressSpace impl; DMemMap dmem_map; VMAMap vma_map; - std::recursive_mutex mutex; + std::mutex mutex; size_t total_direct_size{}; size_t total_flexible_size{}; size_t flexible_usage{}; diff --git a/src/core/module.cpp b/src/core/module.cpp index f48848bbd33..e62c57785aa 100644 --- a/src/core/module.cpp +++ b/src/core/module.cpp @@ -1,16 +1,15 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include #include "common/alignment.h" +#include "common/arch.h" #include "common/assert.h" #include "common/logging/log.h" -#ifdef ENABLE_QT_GUI -#include "qt_gui/memory_patcher.h" -#endif +#include "common/memory_patcher.h" #include "common/string_util.h" #include "core/aerolib/aerolib.h" #include "core/cpu_patches.h" +#include "core/linker.h" #include "core/loader/dwarf.h" #include "core/memory.h" #include "core/module.h" @@ -71,8 +70,9 @@ Module::~Module() = default; s32 Module::Start(size_t args, const void* argp, void* param) { LOG_INFO(Core_Linker, "Module started : {}", name); + auto* linker = Common::Singleton::Instance(); const VAddr addr = dynamic_info.init_virtual_addr + GetBaseAddress(); - return reinterpret_cast(addr)(args, argp, param); + return linker->ExecuteGuest(reinterpret_cast(addr), args, argp, param); } void Module::LoadModuleToMemory(u32& max_tls_index) { @@ -93,9 +93,11 @@ void Module::LoadModuleToMemory(u32& max_tls_index) { LoadOffset += CODE_BASE_INCR * (1 + aligned_base_size / CODE_BASE_INCR); LOG_INFO(Core_Linker, "Loading module {} to {}", name, fmt::ptr(*out_addr)); +#ifdef ARCH_X86_64 // Initialize trampoline generator. void* trampoline_addr = std::bit_cast(base_virtual_addr + aligned_base_size); - Xbyak::CodeGenerator c(TrampolineSize, trampoline_addr); + RegisterPatchModule(*out_addr, aligned_base_size, trampoline_addr, TrampolineSize); +#endif LOG_INFO(Core_Linker, "======== Load Module to Memory ========"); LOG_INFO(Core_Linker, "base_virtual_addr ......: {:#018x}", base_virtual_addr); @@ -134,9 +136,11 @@ void Module::LoadModuleToMemory(u32& max_tls_index) { LOG_INFO(Core_Linker, "segment_mode ..........: {}", segment_mode); add_segment(elf_pheader[i]); +#ifdef ARCH_X86_64 if (elf_pheader[i].p_flags & PF_EXEC) { - PatchInstructions(segment_addr, segment_file_size, c); + PrePatchInstructions(segment_addr, segment_file_size); } +#endif break; } case PT_DYNAMIC: @@ -196,7 +200,6 @@ void Module::LoadModuleToMemory(u32& max_tls_index) { const VAddr entry_addr = base_virtual_addr + elf.GetElfEntry(); LOG_INFO(Core_Linker, "program entry addr ..........: {:#018x}", entry_addr); -#ifdef ENABLE_QT_GUI if (MemoryPatcher::g_eboot_address == 0) { if (name == "eboot") { MemoryPatcher::g_eboot_address = base_virtual_addr; @@ -204,7 +207,6 @@ void Module::LoadModuleToMemory(u32& max_tls_index) { MemoryPatcher::OnGameLoaded(); } } -#endif } void Module::LoadDynamicInfo() { diff --git a/src/core/signals.cpp b/src/core/signals.cpp new file mode 100644 index 00000000000..87f56c85ad3 --- /dev/null +++ b/src/core/signals.cpp @@ -0,0 +1,146 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/arch.h" +#include "common/assert.h" +#include "common/decoder.h" +#include "common/signal_context.h" +#include "core/signals.h" + +#ifdef _WIN32 +#include +#else +#include +#ifdef ARCH_X86_64 +#include +#endif +#endif + +namespace Core { + +#if defined(_WIN32) + +static LONG WINAPI SignalHandler(EXCEPTION_POINTERS* pExp) noexcept { + const auto* signals = Signals::Instance(); + + bool handled = false; + switch (pExp->ExceptionRecord->ExceptionCode) { + case EXCEPTION_ACCESS_VIOLATION: + handled = signals->DispatchAccessViolation( + pExp, reinterpret_cast(pExp->ExceptionRecord->ExceptionInformation[1])); + break; + case EXCEPTION_ILLEGAL_INSTRUCTION: + handled = signals->DispatchIllegalInstruction(pExp); + break; + default: + break; + } + + return handled ? EXCEPTION_CONTINUE_EXECUTION : EXCEPTION_CONTINUE_SEARCH; +} + +#else + +static std::string DisassembleInstruction(void* code_address) { + char buffer[256] = ""; + +#ifdef ARCH_X86_64 + ZydisDecodedInstruction instruction; + ZydisDecodedOperand operands[ZYDIS_MAX_OPERAND_COUNT]; + const auto status = + Common::Decoder::Instance()->decodeInstruction(instruction, operands, code_address); + if (ZYAN_SUCCESS(status)) { + ZydisFormatter formatter; + ZydisFormatterInit(&formatter, ZYDIS_FORMATTER_STYLE_INTEL); + ZydisFormatterFormatInstruction(&formatter, &instruction, operands, + instruction.operand_count_visible, buffer, sizeof(buffer), + reinterpret_cast(code_address), ZYAN_NULL); + } +#endif + + return buffer; +} + +static void SignalHandler(int sig, siginfo_t* info, void* raw_context) { + const auto* signals = Signals::Instance(); + + auto* code_address = Common::GetRip(raw_context); + + switch (sig) { + case SIGSEGV: + case SIGBUS: { + const bool is_write = Common::IsWriteError(raw_context); + if (!signals->DispatchAccessViolation(raw_context, info->si_addr)) { + UNREACHABLE_MSG("Unhandled access violation at code address {}: {} address {}", + fmt::ptr(code_address), is_write ? "Write to" : "Read from", + fmt::ptr(info->si_addr)); + } + break; + } + case SIGILL: + if (!signals->DispatchIllegalInstruction(raw_context)) { + UNREACHABLE_MSG("Unhandled illegal instruction at code address {}: {}", + fmt::ptr(code_address), DisassembleInstruction(code_address)); + } + break; + default: + break; + } +} + +#endif + +SignalDispatch::SignalDispatch() { +#if defined(_WIN32) + ASSERT_MSG(handle = AddVectoredExceptionHandler(0, SignalHandler), + "Failed to register exception handler."); +#else + struct sigaction action {}; + action.sa_sigaction = SignalHandler; + action.sa_flags = SA_SIGINFO | SA_ONSTACK; + sigemptyset(&action.sa_mask); + + ASSERT_MSG(sigaction(SIGSEGV, &action, nullptr) == 0 && + sigaction(SIGBUS, &action, nullptr) == 0, + "Failed to register access violation signal handler."); + ASSERT_MSG(sigaction(SIGILL, &action, nullptr) == 0, + "Failed to register illegal instruction signal handler."); +#endif +} + +SignalDispatch::~SignalDispatch() { +#if defined(_WIN32) + ASSERT_MSG(RemoveVectoredExceptionHandler(handle), "Failed to remove exception handler."); +#else + struct sigaction action {}; + action.sa_handler = SIG_DFL; + action.sa_flags = 0; + sigemptyset(&action.sa_mask); + + ASSERT_MSG(sigaction(SIGSEGV, &action, nullptr) == 0 && + sigaction(SIGBUS, &action, nullptr) == 0, + "Failed to remove access violation signal handler."); + ASSERT_MSG(sigaction(SIGILL, &action, nullptr) == 0, + "Failed to remove illegal instruction signal handler."); +#endif +} + +bool SignalDispatch::DispatchAccessViolation(void* context, void* fault_address) const { + for (const auto& [handler, _] : access_violation_handlers) { + if (handler(context, fault_address)) { + return true; + } + } + return false; +} + +bool SignalDispatch::DispatchIllegalInstruction(void* context) const { + for (const auto& [handler, _] : illegal_instruction_handlers) { + if (handler(context)) { + return true; + } + } + return false; +} + +} // namespace Core \ No newline at end of file diff --git a/src/core/signals.h b/src/core/signals.h new file mode 100644 index 00000000000..6ee525e1015 --- /dev/null +++ b/src/core/signals.h @@ -0,0 +1,56 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include "common/singleton.h" + +namespace Core { + +using AccessViolationHandler = bool (*)(void* context, void* fault_address); +using IllegalInstructionHandler = bool (*)(void* context); + +/// Receives OS signals and dispatches to the appropriate handlers. +class SignalDispatch { +public: + SignalDispatch(); + ~SignalDispatch(); + + /// Registers a handler for memory access violation signals. + void RegisterAccessViolationHandler(const AccessViolationHandler& handler, u32 priority) { + access_violation_handlers.emplace(handler, priority); + } + + /// Registers a handler for illegal instruction signals. + void RegisterIllegalInstructionHandler(const IllegalInstructionHandler& handler, u32 priority) { + illegal_instruction_handlers.emplace(handler, priority); + } + + /// Dispatches an access violation signal, returning whether it was successfully handled. + bool DispatchAccessViolation(void* context, void* fault_address) const; + + /// Dispatches an illegal instruction signal, returning whether it was successfully handled. + bool DispatchIllegalInstruction(void* context) const; + +private: + template + struct HandlerEntry { + T handler; + u32 priority; + + std::strong_ordering operator<=>(const HandlerEntry& right) const { + return priority <=> right.priority; + } + }; + std::set> access_violation_handlers; + std::set> illegal_instruction_handlers; + +#ifdef _WIN32 + void* handle{}; +#endif +}; + +using Signals = Common::Singleton; + +} // namespace Core \ No newline at end of file diff --git a/src/core/tls.cpp b/src/core/tls.cpp index 3216d0fe435..eb07e7a72c3 100644 --- a/src/core/tls.cpp +++ b/src/core/tls.cpp @@ -2,13 +2,19 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include +#include "common/arch.h" #include "common/assert.h" #include "common/types.h" #include "core/tls.h" #ifdef _WIN32 #include -#elif defined(__APPLE__) +#elif defined(__APPLE__) && defined(ARCH_X86_64) +#include +#include +#include +#include +#elif !defined(ARCH_X86_64) #include #endif @@ -16,12 +22,20 @@ namespace Core { #ifdef _WIN32 +// Windows + static DWORD slot = 0; +static std::once_flag slot_alloc_flag; static void AllocTcbKey() { slot = TlsAlloc(); } +u32 GetTcbKey() { + std::call_once(slot_alloc_flag, &AllocTcbKey); + return slot; +} + void SetTcbBase(void* image_address) { const BOOL result = TlsSetValue(GetTcbKey(), image_address); ASSERT(result != 0); @@ -31,28 +45,103 @@ Tcb* GetTcbBase() { return reinterpret_cast(TlsGetValue(GetTcbKey())); } -#elif defined(__APPLE__) +#elif defined(__APPLE__) && defined(ARCH_X86_64) -static pthread_key_t slot = 0; +// Apple x86_64 -static void AllocTcbKey() { - ASSERT(pthread_key_create(&slot, nullptr) == 0); +// Reserve space in the 32-bit address range for allocating TCB pages. +asm(".zerofill TCB_SPACE,TCB_SPACE,__guest_system,0x3FC000"); + +static constexpr u64 ldt_region_base = 0x4000; +static constexpr u64 ldt_region_size = 0x3FC000; +static constexpr u16 ldt_block_size = 0x1000; +static constexpr u16 ldt_index_base = 8; +static constexpr u16 ldt_index_total = (ldt_region_size - ldt_region_base) / ldt_block_size; + +static boost::icl::interval_set free_ldts{}; +static std::mutex free_ldts_lock; +static std::once_flag ldt_region_init_flag; + +static u16 GetLdtIndex() { + sel_t selector; + asm volatile("mov %%fs, %0" : "=r"(selector)); + return selector.index; +} + +static void InitLdtRegion() { + const void* result = + mmap(reinterpret_cast(ldt_region_base), ldt_region_size, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0); + ASSERT_MSG(result != MAP_FAILED, "Failed to map memory region for LDT entries."); + + free_ldts += + boost::icl::interval::right_open(ldt_index_base, ldt_index_base + ldt_index_total); +} + +static void** SetupThreadLdt() { + std::call_once(ldt_region_init_flag, InitLdtRegion); + + // Allocate a new LDT index for the current thread. + u16 ldt_index; + { + std::unique_lock lock{free_ldts_lock}; + ASSERT_MSG(!free_ldts.empty(), "Out of LDT space."); + ldt_index = first(*free_ldts.begin()); + free_ldts -= ldt_index; + } + const u64 addr = ldt_region_base + (ldt_index - ldt_index_base) * ldt_block_size; + + // Create an LDT entry for the TCB. + const ldt_entry ldt{.data{ + .base00 = static_cast(addr), + .base16 = static_cast(addr >> 16), + .base24 = static_cast(addr >> 24), + .limit00 = static_cast(ldt_block_size - 1), + .limit16 = 0, + .type = DESC_DATA_WRITE, + .dpl = 3, // User accessible + .present = 1, // Segment present + .stksz = DESC_DATA_32B, + .granular = DESC_GRAN_BYTE, + }}; + int ret = i386_set_ldt(ldt_index, &ldt, 1); + ASSERT_MSG(ret == ldt_index, + "Failed to set LDT for TLS area: expected {}, but syscall returned {}", ldt_index, + ret); + + // Set the FS segment to the created LDT. + const sel_t sel{ + .rpl = USER_PRIV, + .ti = SEL_LDT, + .index = ldt_index, + }; + asm volatile("mov %0, %%fs" ::"r"(sel)); + + return reinterpret_cast(addr); +} + +static void FreeThreadLdt() { + std::unique_lock lock{free_ldts_lock}; + free_ldts += GetLdtIndex(); } void SetTcbBase(void* image_address) { - ASSERT(pthread_setspecific(GetTcbKey(), image_address) == 0); + if (image_address != nullptr) { + *SetupThreadLdt() = image_address; + } else { + FreeThreadLdt(); + } } Tcb* GetTcbBase() { - return reinterpret_cast(pthread_getspecific(GetTcbKey())); + Tcb* tcb; + asm volatile("mov %%fs:0x0, %0" : "=r"(tcb)); + return tcb; } -#else - -// Placeholder for code compatibility. -static constexpr u32 slot = 0; +#elif defined(ARCH_X86_64) -static void AllocTcbKey() {} +// Other POSIX x86_64 void SetTcbBase(void* image_address) { asm volatile("wrgsbase %0" ::"r"(image_address) : "memory"); @@ -64,13 +153,32 @@ Tcb* GetTcbBase() { return tcb; } -#endif +#else + +// POSIX non-x86_64 +// Just sets up a simple thread-local variable to store it, then instruction translation can point +// code to it. +static pthread_key_t slot = 0; static std::once_flag slot_alloc_flag; -u32 GetTcbKey() { +static void AllocTcbKey() { + ASSERT(pthread_key_create(&slot, nullptr) == 0); +} + +pthread_key_t GetTcbKey() { std::call_once(slot_alloc_flag, &AllocTcbKey); return slot; } +void SetTcbBase(void* image_address) { + ASSERT(pthread_setspecific(GetTcbKey(), image_address) == 0); +} + +Tcb* GetTcbBase() { + return static_cast(pthread_getspecific(GetTcbKey())); +} + +#endif + } // namespace Core diff --git a/src/core/tls.h b/src/core/tls.h index 9829c8d9a4a..f5bf3318410 100644 --- a/src/core/tls.h +++ b/src/core/tls.h @@ -22,8 +22,10 @@ struct Tcb { void* tcb_thread; }; +#ifdef _WIN32 /// Gets the thread local storage key for the TCB block. u32 GetTcbKey(); +#endif /// Sets the data pointer to the TCB block. void SetTcbBase(void* image_address); diff --git a/src/emulator.cpp b/src/emulator.cpp index 85a4d745a4f..4a2e38ff8a9 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -8,8 +8,10 @@ #include "common/logging/backend.h" #include "common/logging/log.h" #ifdef ENABLE_QT_GUI -#include "qt_gui/memory_patcher.h" +#include "common/memory_patcher.h" #endif +#include "common/assert.h" +#include "common/elf_info.h" #include "common/ntapi.h" #include "common/path_util.h" #include "common/polyfill_thread.h" @@ -19,14 +21,15 @@ #include "core/file_format/playgo_chunk.h" #include "core/file_format/psf.h" #include "core/file_format/splash.h" +#include "core/file_format/trp.h" #include "core/file_sys/fs.h" #include "core/libraries/disc_map/disc_map.h" #include "core/libraries/kernel/thread_management.h" #include "core/libraries/libc_internal/libc_internal.h" #include "core/libraries/libs.h" #include "core/libraries/ngs2/ngs2.h" +#include "core/libraries/np_trophy/np_trophy.h" #include "core/libraries/rtc/rtc.h" -#include "core/libraries/videoout/video_out.h" #include "core/linker.h" #include "core/memory.h" #include "emulator.h" @@ -41,9 +44,10 @@ Emulator::Emulator() { const auto config_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir); Config::load(config_dir / "config.toml"); - // Initialize NT API functions + // Initialize NT API functions and set high priority #ifdef _WIN32 Common::NtApi::Initialize(); + SetPriorityClass(GetCurrentProcess(), ABOVE_NORMAL_PRIORITY_CLASS); #endif // Start logger. @@ -64,7 +68,8 @@ Emulator::Emulator() { LOG_INFO(Config, "Vulkan vkValidationSync: {}", Config::vkValidationSyncEnabled()); LOG_INFO(Config, "Vulkan vkValidationGpu: {}", Config::vkValidationGpuEnabled()); LOG_INFO(Config, "Vulkan rdocEnable: {}", Config::isRdocEnabled()); - LOG_INFO(Config, "Vulkan rdocMarkersEnable: {}", Config::isMarkersEnabled()); + LOG_INFO(Config, "Vulkan rdocMarkersEnable: {}", Config::vkMarkersEnabled()); + LOG_INFO(Config, "Vulkan crashDiagnostics: {}", Config::vkCrashDiagnosticEnabled()); // Defer until after logging is initialized. memory = Core::Memory::Instance(); @@ -87,24 +92,40 @@ void Emulator::Run(const std::filesystem::path& file) { // Certain games may use /hostapp as well such as CUSA001100 mnt->Mount(file.parent_path(), "/hostapp"); + auto& game_info = Common::ElfInfo::Instance(); + // Loading param.sfo file if exists std::string id; std::string title; std::string app_version; + u32 fw_version; + std::filesystem::path sce_sys_folder = file.parent_path() / "sce_sys"; if (std::filesystem::is_directory(sce_sys_folder)) { for (const auto& entry : std::filesystem::directory_iterator(sce_sys_folder)) { if (entry.path().filename() == "param.sfo") { auto* param_sfo = Common::Singleton::Instance(); - param_sfo->open(sce_sys_folder.string() + "/param.sfo", {}); - id = std::string(param_sfo->GetString("CONTENT_ID"), 7, 9); + const bool success = param_sfo->Open(sce_sys_folder / "param.sfo"); + ASSERT_MSG(success, "Failed to open param.sfo"); + const auto content_id = param_sfo->GetString("CONTENT_ID"); + ASSERT_MSG(content_id.has_value(), "Failed to get CONTENT_ID"); + id = std::string(*content_id, 7, 9); + Libraries::NpTrophy::game_serial = id; + const auto trophyDir = + Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / id / "TrophyFiles"; + if (!std::filesystem::exists(trophyDir)) { + TRP trp; + if (!trp.Extract(file.parent_path())) { + LOG_ERROR(Loader, "Couldn't extract trophies"); + } + } #ifdef ENABLE_QT_GUI MemoryPatcher::g_game_serial = id; #endif - title = param_sfo->GetString("TITLE"); + title = param_sfo->GetString("TITLE").value_or("Unknown title"); LOG_INFO(Loader, "Game id: {} Title: {}", id, title); - u32 fw_version = param_sfo->GetInteger("SYSTEM_VER"); - app_version = param_sfo->GetString("APP_VER"); + fw_version = param_sfo->GetInteger("SYSTEM_VER").value_or(0x4700000); + app_version = param_sfo->GetString("APP_VER").value_or("Unknown version"); LOG_INFO(Loader, "Fw: {:#x} App Version: {}", fw_version, app_version); } else if (entry.path().filename() == "playgo-chunk.dat") { auto* playgo = Common::Singleton::Instance(); @@ -125,13 +146,20 @@ void Emulator::Run(const std::filesystem::path& file) { } } + game_info.initialized = true; + game_info.game_serial = id; + game_info.title = title; + game_info.app_ver = app_version; + game_info.firmware_ver = fw_version & 0xFFF00000; + game_info.raw_firmware_ver = fw_version; + std::string game_title = fmt::format("{} - {} <{}>", id, title, app_version); std::string window_title = ""; if (Common::isRelease) { window_title = fmt::format("shadPS4 v{} | {}", Common::VERSION, game_title); } else { - window_title = - fmt::format("shadPS4 v{} {} | {}", Common::VERSION, Common::g_scm_desc, game_title); + window_title = fmt::format("shadPS4 v{} {} {} | {}", Common::VERSION, Common::g_scm_branch, + Common::g_scm_desc, game_title); } window = std::make_unique( Config::getScreenWidth(), Config::getScreenHeight(), controller, window_title); @@ -194,7 +222,7 @@ void Emulator::Run(const std::filesystem::path& file) { } void Emulator::LoadSystemModules(const std::filesystem::path& file) { - constexpr std::array ModulesToLoad{ + constexpr std::array ModulesToLoad{ {{"libSceNgs2.sprx", &Libraries::Ngs2::RegisterlibSceNgs2}, {"libSceFiber.sprx", nullptr}, {"libSceUlt.sprx", nullptr}, @@ -203,8 +231,11 @@ void Emulator::LoadSystemModules(const std::filesystem::path& file) { {"libSceLibcInternal.sprx", &Libraries::LibcInternal::RegisterlibSceLibcInternal}, {"libSceDiscMap.sprx", &Libraries::DiscMap::RegisterlibSceDiscMap}, {"libSceRtc.sprx", &Libraries::Rtc::RegisterlibSceRtc}, - {"libSceJpegEnc.sprx", nullptr}}, - }; + {"libSceJpegEnc.sprx", nullptr}, + {"libSceFont.sprx", nullptr}, + {"libSceRazorCpu.sprx", nullptr}, + {"libSceCesCs.sprx", nullptr}, + {"libSceRudp.sprx", nullptr}}}; std::vector found_modules; const auto& sys_module_path = Common::FS::GetUserPath(Common::FS::PathType::SysModuleDir); diff --git a/src/images/play_icon.png b/src/images/play_icon.png index c67831a1e47..2815be39d85 100644 Binary files a/src/images/play_icon.png and b/src/images/play_icon.png differ diff --git a/src/images/themes_icon.png b/src/images/themes_icon.png index 822ef3af0c6..cc711011e53 100644 Binary files a/src/images/themes_icon.png and b/src/images/themes_icon.png differ diff --git a/src/imgui/imgui_config.h b/src/imgui/imgui_config.h new file mode 100644 index 00000000000..2094d56bc9a --- /dev/null +++ b/src/imgui/imgui_config.h @@ -0,0 +1,32 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +// WARNING: All includes from this file must be relative to allow Dear_ImGui project to compile +// without having this project include paths. + +#include + +extern void assert_fail_debug_msg(const char* msg); + +#define ImDrawIdx std::uint32_t + +#define IM_STRINGIZE(x) IM_STRINGIZE2(x) +#define IM_STRINGIZE2(x) #x +#define IM_ASSERT(_EXPR) \ + ([&]() { \ + if (!(_EXPR)) [[unlikely]] { \ + assert_fail_debug_msg(#_EXPR " at " __FILE__ ":" IM_STRINGIZE(__LINE__)); \ + } \ + }()) + +#define IMGUI_USE_WCHAR32 +#define IMGUI_ENABLE_STB_TRUETYPE +#define IMGUI_DEFINE_MATH_OPERATORS + +#define IM_VEC2_CLASS_EXTRA \ + constexpr ImVec2(float _v) : x(_v), y(_v) {} + +#define IM_VEC4_CLASS_EXTRA \ + constexpr ImVec4(float _v) : x(_v), y(_v), z(_v), w(_v) {} \ No newline at end of file diff --git a/src/imgui/imgui_layer.h b/src/imgui/imgui_layer.h new file mode 100644 index 00000000000..a6c7e2a4a79 --- /dev/null +++ b/src/imgui/imgui_layer.h @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +namespace ImGui { + +class Layer { +public: + virtual ~Layer() = default; + static void AddLayer(Layer* layer); + static void RemoveLayer(Layer* layer); + + virtual void Draw() = 0; +}; + +} // namespace ImGui \ No newline at end of file diff --git a/src/imgui/imgui_std.h b/src/imgui/imgui_std.h new file mode 100644 index 00000000000..ec1e2f79d75 --- /dev/null +++ b/src/imgui/imgui_std.h @@ -0,0 +1,69 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +#include "imgui_internal.h" + +#define IM_COL32_GRAY(x) IM_COL32(x, x, x, 0xFF) + +namespace ImGui { + +namespace Easing { + +inline float FastInFastOutCubic(float x) { + constexpr float c4 = 1.587401f; // 4^(1/3) + constexpr float c05 = 0.7937f; // 0.5^(1/3) + return std::pow(c4 * x - c05, 3.0f) + 0.5f; +} + +} // namespace Easing + +inline void CentralizeWindow() { + const auto display_size = GetIO().DisplaySize; + SetNextWindowPos(display_size / 2.0f, ImGuiCond_Always, {0.5f}); +} + +inline void KeepNavHighlight() { + GetCurrentContext()->NavDisableHighlight = false; +} + +inline void SetItemCurrentNavFocus(const ImGuiID id = -1) { + const auto ctx = GetCurrentContext(); + SetFocusID(id == -1 ? ctx->LastItemData.ID : id, ctx->CurrentWindow); + ctx->NavInitResult.Clear(); + ctx->NavDisableHighlight = false; +} + +inline void DrawPrettyBackground() { + const double time = GetTime() / 1.5f; + const float x = ((float)std::cos(time) + 1.0f) / 2.0f; + const float d = Easing::FastInFastOutCubic(x); + u8 top_left = ImLerp(0x13, 0x05, d); + u8 top_right = ImLerp(0x00, 0x07, d); + u8 bottom_right = ImLerp(0x03, 0x27, d); + u8 bottom_left = ImLerp(0x05, 0x00, d); + + auto& window = *GetCurrentWindowRead(); + auto inner_pos = window.DC.CursorPos - window.WindowPadding; + auto inner_size = GetContentRegionAvail() + window.WindowPadding * 2.0f; + GetWindowDrawList()->AddRectFilledMultiColor( + inner_pos, inner_pos + inner_size, IM_COL32_GRAY(top_left), IM_COL32_GRAY(top_right), + IM_COL32_GRAY(bottom_right), IM_COL32_GRAY(bottom_left)); +} + +static void DrawCenteredText(const char* text, const char* text_end = nullptr, + ImVec2 content = GetContentRegionAvail()) { + auto pos = GetCursorPos(); + const auto text_size = CalcTextSize(text, text_end, false, content.x - 40.0f); + PushTextWrapPos(content.x); + SetCursorPos(pos + (content - text_size) / 2.0f); + TextEx(text, text_end, ImGuiTextFlags_NoWidthForLargeClippedText); + PopTextWrapPos(); + SetCursorPos(pos + content); +} + +} // namespace ImGui diff --git a/src/imgui/imgui_texture.h b/src/imgui/imgui_texture.h new file mode 100644 index 00000000000..1a38066d019 --- /dev/null +++ b/src/imgui/imgui_texture.h @@ -0,0 +1,45 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +namespace ImGui { + +namespace Core::TextureManager { +struct Inner; +} // namespace Core::TextureManager + +class RefCountedTexture { + Core::TextureManager::Inner* inner; + + explicit RefCountedTexture(Core::TextureManager::Inner* inner); + +public: + struct Image { + ImTextureID im_id; + u32 width; + u32 height; + }; + + static RefCountedTexture DecodePngTexture(std::vector data); + + static RefCountedTexture DecodePngFile(std::filesystem::path path); + + RefCountedTexture(); + + RefCountedTexture(const RefCountedTexture& other); + RefCountedTexture(RefCountedTexture&& other) noexcept; + RefCountedTexture& operator=(const RefCountedTexture& other); + RefCountedTexture& operator=(RefCountedTexture&& other) noexcept; + + virtual ~RefCountedTexture(); + + [[nodiscard]] Image GetTexture() const; + + explicit(false) operator bool() const; +}; + +}; // namespace ImGui \ No newline at end of file diff --git a/src/imgui/layer/video_info.cpp b/src/imgui/layer/video_info.cpp new file mode 100644 index 00000000000..55cfaf895d9 --- /dev/null +++ b/src/imgui/layer/video_info.cpp @@ -0,0 +1,123 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include + +#include "common/config.h" +#include "common/types.h" +#include "imgui_internal.h" +#include "video_info.h" + +using namespace ImGui; + +struct FrameInfo { + u32 num; + float delta; +}; + +static bool show = false; +static bool show_advanced = false; + +static u32 current_frame = 0; +constexpr float TARGET_FPS = 60.0f; +constexpr u32 FRAME_BUFFER_SIZE = 1024; +constexpr float BAR_WIDTH_MULT = 1.4f; +constexpr float BAR_HEIGHT_MULT = 1.25f; +constexpr float FRAME_GRAPH_PADDING_Y = 3.0f; +static std::array frame_list; +static float frame_graph_height = 50.0f; + +static void DrawSimple() { + const auto io = GetIO(); + Text("Frame time: %.3f ms (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate); +} + +static void DrawAdvanced() { + const auto& ctx = *GetCurrentContext(); + const auto& io = ctx.IO; + const auto& window = *ctx.CurrentWindow; + auto& draw_list = *window.DrawList; + + Text("Frame time: %.3f ms (%.1f FPS)", io.DeltaTime * 1000.0f, io.Framerate); + + SeparatorText("Frame graph"); + const float full_width = GetContentRegionAvail().x; + { // Frame graph - inspired by + // https://asawicki.info/news_1758_an_idea_for_visualization_of_frame_times + auto pos = GetCursorScreenPos(); + const ImVec2 size{full_width, frame_graph_height + FRAME_GRAPH_PADDING_Y * 2.0f}; + ItemSize(size); + if (!ItemAdd({pos, pos + size}, GetID("FrameGraph"))) { + return; + } + + float target_dt = 1.0f / (TARGET_FPS * (float)Config::vblankDiv()); + float cur_pos_x = pos.x + full_width; + pos.y += FRAME_GRAPH_PADDING_Y; + const float final_pos_y = pos.y + frame_graph_height; + + draw_list.AddRectFilled({pos.x, pos.y - FRAME_GRAPH_PADDING_Y}, + {pos.x + full_width, final_pos_y + FRAME_GRAPH_PADDING_Y}, + IM_COL32(0x33, 0x33, 0x33, 0xFF)); + draw_list.PushClipRect({pos.x, pos.y}, {pos.x + full_width, final_pos_y}, true); + for (u32 i = 0; i < FRAME_BUFFER_SIZE; ++i) { + const auto& frame_info = frame_list[(current_frame - i) % FRAME_BUFFER_SIZE]; + const float dt_factor = target_dt / frame_info.delta; + + const float width = std::ceil(BAR_WIDTH_MULT / dt_factor); + const float height = + std::min(std::log2(BAR_HEIGHT_MULT / dt_factor) / 3.0f, 1.0f) * frame_graph_height; + + ImU32 color; + if (dt_factor >= 0.95f) { // BLUE + color = IM_COL32(0x33, 0x33, 0xFF, 0xFF); + } else if (dt_factor >= 0.5f) { // GREEN <> YELLOW + float t = 1.0f - (dt_factor - 0.5f) * 2.0f; + int r = (int)(0xFF * t); + color = IM_COL32(r, 0xFF, 0, 0xFF); + } else { // YELLOW <> RED + float t = dt_factor * 2.0f; + int g = (int)(0xFF * t); + color = IM_COL32(0xFF, g, 0, 0xFF); + } + draw_list.AddRectFilled({cur_pos_x - width, final_pos_y - height}, + {cur_pos_x, final_pos_y}, color); + cur_pos_x -= width; + if (cur_pos_x < width) { + break; + } + } + draw_list.PopClipRect(); + } +} + +void Layers::VideoInfo::Draw() { + const auto io = GetIO(); + + const FrameInfo frame_info{ + .num = ++current_frame, + .delta = io.DeltaTime, + }; + frame_list[current_frame % FRAME_BUFFER_SIZE] = frame_info; + + if (IsKeyPressed(ImGuiKey_F10, false)) { + const bool changed_ctrl = io.KeyCtrl != show_advanced; + show_advanced = io.KeyCtrl; + show = changed_ctrl || !show; + } + + if (show) { + if (show_advanced) { + if (Begin("Video debug info", &show, 0)) { + DrawAdvanced(); + } + } else { + if (Begin("Video Info", nullptr, + ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoDecoration | + ImGuiWindowFlags_AlwaysAutoResize)) { + DrawSimple(); + } + } + End(); + } +} diff --git a/src/imgui/layer/video_info.h b/src/imgui/layer/video_info.h new file mode 100644 index 00000000000..8a8af554e38 --- /dev/null +++ b/src/imgui/layer/video_info.h @@ -0,0 +1,22 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "imgui/imgui_layer.h" + +namespace Vulkan { +class RendererVulkan; +} +namespace ImGui::Layers { + +class VideoInfo : public Layer { + ::Vulkan::RendererVulkan* renderer{}; + +public: + explicit VideoInfo(::Vulkan::RendererVulkan* renderer) : renderer(renderer) {} + + void Draw() override; +}; + +} // namespace ImGui::Layers diff --git a/src/imgui/renderer/CMakeLists.txt b/src/imgui/renderer/CMakeLists.txt new file mode 100644 index 00000000000..b5f51ef625e --- /dev/null +++ b/src/imgui/renderer/CMakeLists.txt @@ -0,0 +1,32 @@ +# SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +# SPDX-License-Identifier: GPL-2.0-or-later + +project(ImGui_Resources) + +add_executable(Dear_ImGui_FontEmbed ${CMAKE_SOURCE_DIR}/externals/dear_imgui/misc/fonts/binary_to_compressed_c.cpp) + +set(FONT_LIST + NotoSansJP-Regular.ttf +) + +set(OutputList "") +FOREACH (FONT_FILE ${FONT_LIST}) + string(REGEX REPLACE "-" "_" fontname ${FONT_FILE}) + string(TOLOWER ${fontname} fontname) + string(REGEX REPLACE ".ttf" "" fontname_cpp ${fontname}) + set(fontname_cpp "imgui_font_${fontname_cpp}") + + MESSAGE(STATUS "Embedding font ${FONT_FILE}") + set(OUTPUT "generated_fonts/imgui_fonts/${fontname}") + add_custom_command( + OUTPUT "${OUTPUT}.g.cpp" + COMMAND ${CMAKE_COMMAND} -E make_directory "generated_fonts/imgui_fonts" + COMMAND $ -nostatic "${CMAKE_CURRENT_SOURCE_DIR}/fonts/${FONT_FILE}" ${fontname_cpp} > "${OUTPUT}.g.cpp" + DEPENDS Dear_ImGui_FontEmbed "fonts/${FONT_FILE}" + USES_TERMINAL + ) + list(APPEND OutputList "${OUTPUT}.g.cpp") +ENDFOREACH () + +add_library(ImGui_Resources STATIC ${OutputList}) +set(IMGUI_RESOURCES_INCLUDE ${CMAKE_CURRENT_BINARY_DIR}/generated_fonts PARENT_SCOPE) diff --git a/src/imgui/renderer/fonts/NotoSansJP-Regular.ttf b/src/imgui/renderer/fonts/NotoSansJP-Regular.ttf new file mode 100644 index 00000000000..b2dad730d76 Binary files /dev/null and b/src/imgui/renderer/fonts/NotoSansJP-Regular.ttf differ diff --git a/src/imgui/renderer/imgui_core.cpp b/src/imgui/renderer/imgui_core.cpp new file mode 100644 index 00000000000..b972d99d01c --- /dev/null +++ b/src/imgui/renderer/imgui_core.cpp @@ -0,0 +1,210 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include + +#include "common/config.h" +#include "common/path_util.h" +#include "imgui/imgui_layer.h" +#include "imgui_core.h" +#include "imgui_impl_sdl3.h" +#include "imgui_impl_vulkan.h" +#include "imgui_internal.h" +#include "sdl_window.h" +#include "texture_manager.h" +#include "video_core/renderer_vulkan/renderer_vulkan.h" + +#include "imgui_fonts/notosansjp_regular.ttf.g.cpp" + +static void CheckVkResult(const vk::Result err) { + LOG_ERROR(ImGui, "Vulkan error {}", vk::to_string(err)); +} + +static std::vector layers; + +// Update layers before rendering to allow layer changes to be applied during rendering. +// Using deque to keep the order of changes in case a Layer is removed then added again between +// frames. +std::deque>& GetChangeLayers() { + static std::deque>* change_layers = + new std::deque>; + return *change_layers; +} + +static std::mutex change_layers_mutex{}; + +namespace ImGui { + +namespace Core { + +void Initialize(const ::Vulkan::Instance& instance, const Frontend::WindowSDL& window, + const u32 image_count, vk::Format surface_format, + const vk::AllocationCallbacks* allocator) { + + const auto config_path = GetUserPath(Common::FS::PathType::UserDir) / "imgui.ini"; + const auto log_path = GetUserPath(Common::FS::PathType::LogDir) / "imgui_log.txt"; + + CreateContext(); + ImGuiIO& io = GetIO(); + io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; + io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; + io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; + io.DisplaySize = ImVec2((float)window.getWidth(), (float)window.getHeight()); + io.IniFilename = SDL_strdup(config_path.string().c_str()); + io.LogFilename = SDL_strdup(log_path.string().c_str()); + + ImFontGlyphRangesBuilder rb{}; + rb.AddRanges(io.Fonts->GetGlyphRangesDefault()); + rb.AddRanges(io.Fonts->GetGlyphRangesGreek()); + rb.AddRanges(io.Fonts->GetGlyphRangesKorean()); + rb.AddRanges(io.Fonts->GetGlyphRangesJapanese()); + rb.AddRanges(io.Fonts->GetGlyphRangesCyrillic()); + ImVector ranges{}; + rb.BuildRanges(&ranges); + ImFontConfig font_cfg{}; + font_cfg.OversampleH = 2; + font_cfg.OversampleV = 1; + io.Fonts->AddFontFromMemoryCompressedTTF(imgui_font_notosansjp_regular_compressed_data, + imgui_font_notosansjp_regular_compressed_size, 16.0f, + &font_cfg, ranges.Data); + + StyleColorsDark(); + + Sdl::Init(window.GetSdlWindow()); + + const Vulkan::InitInfo vk_info{ + .instance = instance.GetInstance(), + .physical_device = instance.GetPhysicalDevice(), + .device = instance.GetDevice(), + .queue_family = instance.GetPresentQueueFamilyIndex(), + .queue = instance.GetPresentQueue(), + .image_count = image_count, + .min_allocation_size = 1024 * 1024, + .pipeline_rendering_create_info{ + .colorAttachmentCount = 1, + .pColorAttachmentFormats = &surface_format, + }, + .allocator = allocator, + .check_vk_result_fn = &CheckVkResult, + }; + Vulkan::Init(vk_info); + + TextureManager::StartWorker(); +} + +void OnResize() { + Sdl::OnResize(); +} + +void Shutdown(const vk::Device& device) { + device.waitIdle(); + + TextureManager::StopWorker(); + + const ImGuiIO& io = GetIO(); + const auto ini_filename = (void*)io.IniFilename; + const auto log_filename = (void*)io.LogFilename; + + Vulkan::Shutdown(); + Sdl::Shutdown(); + DestroyContext(); + + SDL_free(ini_filename); + SDL_free(log_filename); +} + +bool ProcessEvent(SDL_Event* event) { + Sdl::ProcessEvent(event); + switch (event->type) { + // Don't block release/up events + case SDL_EVENT_MOUSE_MOTION: + case SDL_EVENT_MOUSE_WHEEL: + case SDL_EVENT_MOUSE_BUTTON_DOWN: + return GetIO().WantCaptureMouse; + case SDL_EVENT_TEXT_INPUT: + case SDL_EVENT_KEY_DOWN: + return GetIO().WantCaptureKeyboard; + case SDL_EVENT_GAMEPAD_BUTTON_DOWN: + case SDL_EVENT_GAMEPAD_AXIS_MOTION: + case SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN: + case SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION: + return GetIO().NavActive; + default: + return false; + } +} + +void NewFrame() { + { + std::scoped_lock lock{change_layers_mutex}; + while (!GetChangeLayers().empty()) { + const auto [to_be_added, layer] = GetChangeLayers().front(); + if (to_be_added) { + layers.push_back(layer); + } else { + const auto [begin, end] = std::ranges::remove(layers, layer); + layers.erase(begin, end); + } + GetChangeLayers().pop_front(); + } + } + + Sdl::NewFrame(); + ImGui::NewFrame(); + + for (auto* layer : layers) { + layer->Draw(); + } +} + +void Render(const vk::CommandBuffer& cmdbuf, ::Vulkan::Frame* frame) { + ImGui::Render(); + ImDrawData* draw_data = GetDrawData(); + if (draw_data->CmdListsCount == 0) { + return; + } + + if (Config::vkMarkersEnabled()) { + cmdbuf.beginDebugUtilsLabelEXT(vk::DebugUtilsLabelEXT{ + .pLabelName = "ImGui Render", + }); + } + + vk::RenderingAttachmentInfo color_attachments[1]{ + { + .imageView = frame->image_view, + .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eLoad, + .storeOp = vk::AttachmentStoreOp::eStore, + }, + }; + vk::RenderingInfo render_info = {}; + render_info.renderArea = vk::Rect2D{ + .offset = {0, 0}, + .extent = {frame->width, frame->height}, + }; + render_info.layerCount = 1; + render_info.colorAttachmentCount = 1; + render_info.pColorAttachments = color_attachments; + cmdbuf.beginRendering(render_info); + Vulkan::RenderDrawData(*draw_data, cmdbuf); + cmdbuf.endRendering(); + if (Config::vkMarkersEnabled()) { + cmdbuf.endDebugUtilsLabelEXT(); + } +} + +} // namespace Core + +void Layer::AddLayer(Layer* layer) { + std::scoped_lock lock{change_layers_mutex}; + GetChangeLayers().emplace_back(true, layer); +} + +void Layer::RemoveLayer(Layer* layer) { + std::scoped_lock lock{change_layers_mutex}; + GetChangeLayers().emplace_back(false, layer); +} + +} // namespace ImGui diff --git a/src/imgui/renderer/imgui_core.h b/src/imgui/renderer/imgui_core.h new file mode 100644 index 00000000000..9ad708f818f --- /dev/null +++ b/src/imgui/renderer/imgui_core.h @@ -0,0 +1,31 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "video_core/renderer_vulkan/vk_instance.h" +#include "vulkan/vulkan_handles.hpp" + +union SDL_Event; + +namespace Vulkan { +struct Frame; +} + +namespace ImGui::Core { + +void Initialize(const Vulkan::Instance& instance, const Frontend::WindowSDL& window, + u32 image_count, vk::Format surface_format, + const vk::AllocationCallbacks* allocator = nullptr); + +void OnResize(); + +void Shutdown(const vk::Device& device); + +bool ProcessEvent(SDL_Event* event); + +void NewFrame(); + +void Render(const vk::CommandBuffer& cmdbuf, Vulkan::Frame* frame); + +} // namespace ImGui::Core diff --git a/src/imgui/renderer/imgui_impl_sdl3.cpp b/src/imgui/renderer/imgui_impl_sdl3.cpp new file mode 100644 index 00000000000..bb194bff772 --- /dev/null +++ b/src/imgui/renderer/imgui_impl_sdl3.cpp @@ -0,0 +1,789 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +// Based on imgui_impl_sdl3.cpp from Dear ImGui repository + +#include +#include "imgui_impl_sdl3.h" + +// SDL +#include +#if defined(__APPLE__) +#include +#endif +#ifdef _WIN32 +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include +#endif + +namespace ImGui::Sdl { + +// SDL Data +struct SdlData { + SDL_Window* window{}; + SDL_WindowID window_id{}; + Uint64 time{}; + const char* clipboard_text_data{}; + + // IME handling + SDL_Window* ime_window{}; + + // Mouse handling + Uint32 mouse_window_id{}; + int mouse_buttons_down{}; + SDL_Cursor* mouse_cursors[ImGuiMouseCursor_COUNT]{}; + SDL_Cursor* mouse_last_cursor{}; + int mouse_pending_leave_frame{}; + + // Gamepad handling + ImVector gamepads{}; + GamepadMode gamepad_mode{}; + bool want_update_gamepads_list{}; +}; + +// Backend data stored in io.BackendPlatformUserData to allow support for multiple Dear ImGui +// contexts It is STRONGLY preferred that you use docking branch with multi-viewports (== single +// Dear ImGui context + multiple windows) instead of multiple Dear ImGui contexts. +static SdlData* GetBackendData() { + return ImGui::GetCurrentContext() ? (SdlData*)ImGui::GetIO().BackendPlatformUserData : nullptr; +} + +static const char* GetClipboardText(ImGuiContext*) { + SdlData* bd = GetBackendData(); + if (bd->clipboard_text_data) + SDL_free((void*)bd->clipboard_text_data); + const char* sdl_clipboard_text = SDL_GetClipboardText(); + bd->clipboard_text_data = sdl_clipboard_text; + return bd->clipboard_text_data; +} + +static void SetClipboardText(ImGuiContext*, const char* text) { + SDL_SetClipboardText(text); +} + +static void PlatformSetImeData(ImGuiContext*, ImGuiViewport* viewport, ImGuiPlatformImeData* data) { + SdlData* bd = GetBackendData(); + auto window_id = (SDL_WindowID)(intptr_t)viewport->PlatformHandle; + SDL_Window* window = SDL_GetWindowFromID(window_id); + if ((!data->WantVisible || bd->ime_window != window) && bd->ime_window != nullptr) { + SDL_StopTextInput(bd->ime_window); + bd->ime_window = nullptr; + } + if (data->WantVisible) { + SDL_Rect r; + r.x = (int)data->InputPos.x; + r.y = (int)data->InputPos.y; + r.w = 1; + r.h = (int)data->InputLineHeight; + SDL_SetTextInputArea(window, &r, 0); + SDL_StartTextInput(window); + bd->ime_window = window; + } +} + +static ImGuiKey KeyEventToImGuiKey(SDL_Keycode keycode, SDL_Scancode scancode) { + // Keypad doesn't have individual key values in SDL3 + switch (scancode) { + case SDL_SCANCODE_KP_0: + return ImGuiKey_Keypad0; + case SDL_SCANCODE_KP_1: + return ImGuiKey_Keypad1; + case SDL_SCANCODE_KP_2: + return ImGuiKey_Keypad2; + case SDL_SCANCODE_KP_3: + return ImGuiKey_Keypad3; + case SDL_SCANCODE_KP_4: + return ImGuiKey_Keypad4; + case SDL_SCANCODE_KP_5: + return ImGuiKey_Keypad5; + case SDL_SCANCODE_KP_6: + return ImGuiKey_Keypad6; + case SDL_SCANCODE_KP_7: + return ImGuiKey_Keypad7; + case SDL_SCANCODE_KP_8: + return ImGuiKey_Keypad8; + case SDL_SCANCODE_KP_9: + return ImGuiKey_Keypad9; + case SDL_SCANCODE_KP_PERIOD: + return ImGuiKey_KeypadDecimal; + case SDL_SCANCODE_KP_DIVIDE: + return ImGuiKey_KeypadDivide; + case SDL_SCANCODE_KP_MULTIPLY: + return ImGuiKey_KeypadMultiply; + case SDL_SCANCODE_KP_MINUS: + return ImGuiKey_KeypadSubtract; + case SDL_SCANCODE_KP_PLUS: + return ImGuiKey_KeypadAdd; + case SDL_SCANCODE_KP_ENTER: + return ImGuiKey_KeypadEnter; + case SDL_SCANCODE_KP_EQUALS: + return ImGuiKey_KeypadEqual; + default: + break; + } + switch (keycode) { + case SDLK_TAB: + return ImGuiKey_Tab; + case SDLK_LEFT: + return ImGuiKey_LeftArrow; + case SDLK_RIGHT: + return ImGuiKey_RightArrow; + case SDLK_UP: + return ImGuiKey_UpArrow; + case SDLK_DOWN: + return ImGuiKey_DownArrow; + case SDLK_PAGEUP: + return ImGuiKey_PageUp; + case SDLK_PAGEDOWN: + return ImGuiKey_PageDown; + case SDLK_HOME: + return ImGuiKey_Home; + case SDLK_END: + return ImGuiKey_End; + case SDLK_INSERT: + return ImGuiKey_Insert; + case SDLK_DELETE: + return ImGuiKey_Delete; + case SDLK_BACKSPACE: + return ImGuiKey_Backspace; + case SDLK_SPACE: + return ImGuiKey_Space; + case SDLK_RETURN: + return ImGuiKey_Enter; + case SDLK_ESCAPE: + return ImGuiKey_Escape; + case SDLK_APOSTROPHE: + return ImGuiKey_Apostrophe; + case SDLK_COMMA: + return ImGuiKey_Comma; + case SDLK_MINUS: + return ImGuiKey_Minus; + case SDLK_PERIOD: + return ImGuiKey_Period; + case SDLK_SLASH: + return ImGuiKey_Slash; + case SDLK_SEMICOLON: + return ImGuiKey_Semicolon; + case SDLK_EQUALS: + return ImGuiKey_Equal; + case SDLK_LEFTBRACKET: + return ImGuiKey_LeftBracket; + case SDLK_BACKSLASH: + return ImGuiKey_Backslash; + case SDLK_RIGHTBRACKET: + return ImGuiKey_RightBracket; + case SDLK_GRAVE: + return ImGuiKey_GraveAccent; + case SDLK_CAPSLOCK: + return ImGuiKey_CapsLock; + case SDLK_SCROLLLOCK: + return ImGuiKey_ScrollLock; + case SDLK_NUMLOCKCLEAR: + return ImGuiKey_NumLock; + case SDLK_PRINTSCREEN: + return ImGuiKey_PrintScreen; + case SDLK_PAUSE: + return ImGuiKey_Pause; + case SDLK_LCTRL: + return ImGuiKey_LeftCtrl; + case SDLK_LSHIFT: + return ImGuiKey_LeftShift; + case SDLK_LALT: + return ImGuiKey_LeftAlt; + case SDLK_LGUI: + return ImGuiKey_LeftSuper; + case SDLK_RCTRL: + return ImGuiKey_RightCtrl; + case SDLK_RSHIFT: + return ImGuiKey_RightShift; + case SDLK_RALT: + return ImGuiKey_RightAlt; + case SDLK_RGUI: + return ImGuiKey_RightSuper; + case SDLK_APPLICATION: + return ImGuiKey_Menu; + case SDLK_0: + return ImGuiKey_0; + case SDLK_1: + return ImGuiKey_1; + case SDLK_2: + return ImGuiKey_2; + case SDLK_3: + return ImGuiKey_3; + case SDLK_4: + return ImGuiKey_4; + case SDLK_5: + return ImGuiKey_5; + case SDLK_6: + return ImGuiKey_6; + case SDLK_7: + return ImGuiKey_7; + case SDLK_8: + return ImGuiKey_8; + case SDLK_9: + return ImGuiKey_9; + case SDLK_A: + return ImGuiKey_A; + case SDLK_B: + return ImGuiKey_B; + case SDLK_C: + return ImGuiKey_C; + case SDLK_D: + return ImGuiKey_D; + case SDLK_E: + return ImGuiKey_E; + case SDLK_F: + return ImGuiKey_F; + case SDLK_G: + return ImGuiKey_G; + case SDLK_H: + return ImGuiKey_H; + case SDLK_I: + return ImGuiKey_I; + case SDLK_J: + return ImGuiKey_J; + case SDLK_K: + return ImGuiKey_K; + case SDLK_L: + return ImGuiKey_L; + case SDLK_M: + return ImGuiKey_M; + case SDLK_N: + return ImGuiKey_N; + case SDLK_O: + return ImGuiKey_O; + case SDLK_P: + return ImGuiKey_P; + case SDLK_Q: + return ImGuiKey_Q; + case SDLK_R: + return ImGuiKey_R; + case SDLK_S: + return ImGuiKey_S; + case SDLK_T: + return ImGuiKey_T; + case SDLK_U: + return ImGuiKey_U; + case SDLK_V: + return ImGuiKey_V; + case SDLK_W: + return ImGuiKey_W; + case SDLK_X: + return ImGuiKey_X; + case SDLK_Y: + return ImGuiKey_Y; + case SDLK_Z: + return ImGuiKey_Z; + case SDLK_F1: + return ImGuiKey_F1; + case SDLK_F2: + return ImGuiKey_F2; + case SDLK_F3: + return ImGuiKey_F3; + case SDLK_F4: + return ImGuiKey_F4; + case SDLK_F5: + return ImGuiKey_F5; + case SDLK_F6: + return ImGuiKey_F6; + case SDLK_F7: + return ImGuiKey_F7; + case SDLK_F8: + return ImGuiKey_F8; + case SDLK_F9: + return ImGuiKey_F9; + case SDLK_F10: + return ImGuiKey_F10; + case SDLK_F11: + return ImGuiKey_F11; + case SDLK_F12: + return ImGuiKey_F12; + case SDLK_F13: + return ImGuiKey_F13; + case SDLK_F14: + return ImGuiKey_F14; + case SDLK_F15: + return ImGuiKey_F15; + case SDLK_F16: + return ImGuiKey_F16; + case SDLK_F17: + return ImGuiKey_F17; + case SDLK_F18: + return ImGuiKey_F18; + case SDLK_F19: + return ImGuiKey_F19; + case SDLK_F20: + return ImGuiKey_F20; + case SDLK_F21: + return ImGuiKey_F21; + case SDLK_F22: + return ImGuiKey_F22; + case SDLK_F23: + return ImGuiKey_F23; + case SDLK_F24: + return ImGuiKey_F24; + case SDLK_AC_BACK: + return ImGuiKey_AppBack; + case SDLK_AC_FORWARD: + return ImGuiKey_AppForward; + default: + break; + } + return ImGuiKey_None; +} + +static void UpdateKeyModifiers(SDL_Keymod sdl_key_mods) { + ImGuiIO& io = ImGui::GetIO(); + io.AddKeyEvent(ImGuiMod_Ctrl, (sdl_key_mods & SDL_KMOD_CTRL) != 0); + io.AddKeyEvent(ImGuiMod_Shift, (sdl_key_mods & SDL_KMOD_SHIFT) != 0); + io.AddKeyEvent(ImGuiMod_Alt, (sdl_key_mods & SDL_KMOD_ALT) != 0); + io.AddKeyEvent(ImGuiMod_Super, (sdl_key_mods & SDL_KMOD_GUI) != 0); +} + +static ImGuiViewport* GetViewportForWindowId(SDL_WindowID window_id) { + SdlData* bd = GetBackendData(); + return (window_id == bd->window_id) ? ImGui::GetMainViewport() : nullptr; +} + +// You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to +// use your inputs. +// - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application, or +// clear/overwrite your copy of the mouse data. +// - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main +// application, or clear/overwrite your copy of the keyboard data. Generally you may always pass all +// inputs to dear imgui, and hide them from your application based on those two flags. If you have +// multiple SDL events and some of them are not meant to be used by dear imgui, you may need to +// filter events based on their windowID field. +bool ProcessEvent(const SDL_Event* event) { + SdlData* bd = GetBackendData(); + IM_ASSERT(bd != nullptr && + "Context or backend not initialized! Did you call ImGui_ImplSDL3_Init()?"); + ImGuiIO& io = ImGui::GetIO(); + + switch (event->type) { + case SDL_EVENT_MOUSE_MOTION: { + if (GetViewportForWindowId(event->motion.windowID) == NULL) + return false; + ImVec2 mouse_pos((float)event->motion.x, (float)event->motion.y); + io.AddMouseSourceEvent(event->motion.which == SDL_TOUCH_MOUSEID + ? ImGuiMouseSource_TouchScreen + : ImGuiMouseSource_Mouse); + io.AddMousePosEvent(mouse_pos.x, mouse_pos.y); + return true; + } + case SDL_EVENT_MOUSE_WHEEL: { + if (GetViewportForWindowId(event->wheel.windowID) == NULL) + return false; + // IMGUI_DEBUG_LOG("wheel %.2f %.2f, precise %.2f %.2f\n", (float)event->wheel.x, + // (float)event->wheel.y, event->wheel.preciseX, event->wheel.preciseY); + float wheel_x = -event->wheel.x; + float wheel_y = event->wheel.y; +#ifdef __EMSCRIPTEN__ + wheel_x /= 100.0f; +#endif + io.AddMouseSourceEvent(event->wheel.which == SDL_TOUCH_MOUSEID + ? ImGuiMouseSource_TouchScreen + : ImGuiMouseSource_Mouse); + io.AddMouseWheelEvent(wheel_x, wheel_y); + return true; + } + case SDL_EVENT_MOUSE_BUTTON_DOWN: + case SDL_EVENT_MOUSE_BUTTON_UP: { + if (GetViewportForWindowId(event->button.windowID) == NULL) + return false; + int mouse_button = -1; + if (event->button.button == SDL_BUTTON_LEFT) { + mouse_button = 0; + } + if (event->button.button == SDL_BUTTON_RIGHT) { + mouse_button = 1; + } + if (event->button.button == SDL_BUTTON_MIDDLE) { + mouse_button = 2; + } + if (event->button.button == SDL_BUTTON_X1) { + mouse_button = 3; + } + if (event->button.button == SDL_BUTTON_X2) { + mouse_button = 4; + } + if (mouse_button == -1) + break; + io.AddMouseSourceEvent(event->button.which == SDL_TOUCH_MOUSEID + ? ImGuiMouseSource_TouchScreen + : ImGuiMouseSource_Mouse); + io.AddMouseButtonEvent(mouse_button, (event->type == SDL_EVENT_MOUSE_BUTTON_DOWN)); + bd->mouse_buttons_down = (event->type == SDL_EVENT_MOUSE_BUTTON_DOWN) + ? (bd->mouse_buttons_down | (1 << mouse_button)) + : (bd->mouse_buttons_down & ~(1 << mouse_button)); + return true; + } + case SDL_EVENT_TEXT_INPUT: { + if (GetViewportForWindowId(event->text.windowID) == NULL) + return false; + io.AddInputCharactersUTF8(event->text.text); + return true; + } + case SDL_EVENT_KEY_DOWN: + case SDL_EVENT_KEY_UP: { + if (GetViewportForWindowId(event->key.windowID) == NULL) + return false; + // IMGUI_DEBUG_LOG("SDL_EVENT_KEY_%d: key=%d, scancode=%d, mod=%X\n", (event->type == + // SDL_EVENT_KEY_DOWN) ? "DOWN" : "UP", event->key.key, event->key.scancode, + // event->key.mod); + UpdateKeyModifiers((SDL_Keymod)event->key.mod); + ImGuiKey key = KeyEventToImGuiKey(event->key.key, event->key.scancode); + io.AddKeyEvent(key, (event->type == SDL_EVENT_KEY_DOWN)); + io.SetKeyEventNativeData( + key, event->key.key, event->key.scancode, + event->key.scancode); // To support legacy indexing (<1.87 user code). Legacy backend + // uses SDLK_*** as indices to IsKeyXXX() functions. + return true; + } + case SDL_EVENT_WINDOW_MOUSE_ENTER: { + if (GetViewportForWindowId(event->window.windowID) == NULL) + return false; + bd->mouse_window_id = event->window.windowID; + bd->mouse_pending_leave_frame = 0; + return true; + } + // - In some cases, when detaching a window from main viewport SDL may send + // SDL_WINDOWEVENT_ENTER one frame too late, + // causing SDL_WINDOWEVENT_LEAVE on previous frame to interrupt drag operation by clear mouse + // position. This is why we delay process the SDL_WINDOWEVENT_LEAVE events by one frame. See + // issue #5012 for details. + // FIXME: Unconfirmed whether this is still needed with SDL3. + case SDL_EVENT_WINDOW_MOUSE_LEAVE: { + if (GetViewportForWindowId(event->window.windowID) == NULL) + return false; + bd->mouse_pending_leave_frame = ImGui::GetFrameCount() + 1; + return true; + } + case SDL_EVENT_WINDOW_FOCUS_GAINED: + case SDL_EVENT_WINDOW_FOCUS_LOST: { + if (GetViewportForWindowId(event->window.windowID) == NULL) + return false; + io.AddFocusEvent(event->type == SDL_EVENT_WINDOW_FOCUS_GAINED); + return true; + } + case SDL_EVENT_GAMEPAD_ADDED: + case SDL_EVENT_GAMEPAD_REMOVED: { + bd->want_update_gamepads_list = true; + return true; + } + } + return false; +} + +static void SetupPlatformHandles(ImGuiViewport* viewport, SDL_Window* window) { + viewport->PlatformHandle = (void*)(intptr_t)SDL_GetWindowID(window); + viewport->PlatformHandleRaw = nullptr; +#if defined(_WIN32) && !defined(__WINRT__) + viewport->PlatformHandleRaw = (HWND)SDL_GetPointerProperty( + SDL_GetWindowProperties(window), SDL_PROP_WINDOW_WIN32_HWND_POINTER, nullptr); +#elif defined(__APPLE__) && defined(SDL_VIDEO_DRIVER_COCOA) + viewport->PlatformHandleRaw = SDL_GetPointerProperty( + SDL_GetWindowProperties(window), SDL_PROP_WINDOW_COCOA_WINDOW_POINTER, nullptr); +#endif +} + +bool Init(SDL_Window* window) { + ImGuiIO& io = ImGui::GetIO(); + IMGUI_CHECKVERSION(); + IM_ASSERT(io.BackendPlatformUserData == nullptr && "Already initialized a platform backend!"); + + // Setup backend capabilities flags + SdlData* bd = IM_NEW(SdlData)(); + io.BackendPlatformUserData = (void*)bd; + io.BackendPlatformName = "imgui_impl_sdl3_shadps4"; + io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors; // We can honor GetMouseCursor() values + io.BackendFlags |= ImGuiBackendFlags_HasSetMousePos; // We can honor io.WantSetMousePos requests + // (optional, rarely used) + + bd->window = window; + bd->window_id = SDL_GetWindowID(window); + + ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); + platform_io.Platform_SetClipboardTextFn = SetClipboardText; + platform_io.Platform_GetClipboardTextFn = GetClipboardText; + platform_io.Platform_SetImeDataFn = PlatformSetImeData; + + // Gamepad handling + bd->gamepad_mode = ImGui_ImplSDL3_GamepadMode_AutoFirst; + bd->want_update_gamepads_list = true; + + // Load mouse cursors +#define CURSOR(left, right) \ + bd->mouse_cursors[ImGuiMouseCursor_##left] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_##right) + CURSOR(Arrow, DEFAULT); + CURSOR(TextInput, TEXT); + CURSOR(ResizeAll, MOVE); + CURSOR(ResizeNS, NS_RESIZE); + CURSOR(ResizeEW, EW_RESIZE); + CURSOR(ResizeNESW, NESW_RESIZE); + CURSOR(ResizeNWSE, NWSE_RESIZE); + CURSOR(Hand, POINTER); + CURSOR(NotAllowed, NOT_ALLOWED); +#undef CURSOR + + // Set platform dependent data in viewport + // Our mouse update function expect PlatformHandle to be filled for the main viewport + ImGuiViewport* main_viewport = ImGui::GetMainViewport(); + SetupPlatformHandles(main_viewport, window); + + // From 2.0.5: Set SDL hint to receive mouse click events on window focus, otherwise SDL doesn't + // emit the event. Without this, when clicking to gain focus, our widgets wouldn't activate even + // though they showed as hovered. (This is unfortunately a global SDL setting, so enabling it + // might have a side-effect on your application. It is unlikely to make a difference, but if + // your app absolutely needs to ignore the initial on-focus click: you can ignore + // SDL_EVENT_MOUSE_BUTTON_DOWN events coming right after a SDL_WINDOWEVENT_FOCUS_GAINED) +#ifdef SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH + SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1"); +#endif + + // From 2.0.22: Disable auto-capture, this is preventing drag and drop across multiple windows + // (see #5710) +#ifdef SDL_HINT_MOUSE_AUTO_CAPTURE + SDL_SetHint(SDL_HINT_MOUSE_AUTO_CAPTURE, "0"); +#endif + + return true; +} + +static void CloseGamepads(); + +void Shutdown() { + SdlData* bd = GetBackendData(); + IM_ASSERT(bd != nullptr && "No platform backend to shutdown, or already shutdown?"); + ImGuiIO& io = ImGui::GetIO(); + + if (bd->clipboard_text_data) { + SDL_free((void*)bd->clipboard_text_data); + } + for (ImGuiMouseCursor cursor_n = 0; cursor_n < ImGuiMouseCursor_COUNT; cursor_n++) + SDL_DestroyCursor(bd->mouse_cursors[cursor_n]); + CloseGamepads(); + + io.BackendPlatformName = nullptr; + io.BackendPlatformUserData = nullptr; + io.BackendFlags &= ~(ImGuiBackendFlags_HasMouseCursors | ImGuiBackendFlags_HasSetMousePos | + ImGuiBackendFlags_HasGamepad); + IM_DELETE(bd); +} + +static void UpdateMouseData() { + SdlData* bd = GetBackendData(); + ImGuiIO& io = ImGui::GetIO(); + + // We forward mouse input when hovered or captured (via SDL_EVENT_MOUSE_MOTION) or when focused + // (below) + // SDL_CaptureMouse() let the OS know e.g. that our imgui drag outside the SDL window boundaries + // shouldn't e.g. trigger other operations outside + SDL_CaptureMouse((bd->mouse_buttons_down != 0) ? SDL_TRUE : SDL_FALSE); + SDL_Window* focused_window = SDL_GetKeyboardFocus(); + const bool is_app_focused = (bd->window == focused_window); + + if (is_app_focused) { + // (Optional) Set OS mouse position from Dear ImGui if requested (rarely used, only when + // ImGuiConfigFlags_NavEnableSetMousePos is enabled by user) + if (io.WantSetMousePos) + SDL_WarpMouseInWindow(bd->window, io.MousePos.x, io.MousePos.y); + + // (Optional) Fallback to provide mouse position when focused (SDL_EVENT_MOUSE_MOTION + // already provides this when hovered or captured) + if (bd->mouse_buttons_down == 0) { + // Single-viewport mode: mouse position in client window coordinates (io.MousePos is + // (0,0) when the mouse is on the upper-left corner of the app window) + float mouse_x_global, mouse_y_global; + int window_x, window_y; + SDL_GetGlobalMouseState(&mouse_x_global, &mouse_y_global); + SDL_GetWindowPosition(focused_window, &window_x, &window_y); + io.AddMousePosEvent(mouse_x_global - (float)window_x, mouse_y_global - (float)window_y); + } + } +} + +static void UpdateMouseCursor() { + ImGuiIO& io = ImGui::GetIO(); + if (io.ConfigFlags & ImGuiConfigFlags_NoMouseCursorChange) + return; + SdlData* bd = GetBackendData(); + + ImGuiMouseCursor imgui_cursor = ImGui::GetMouseCursor(); + if (io.MouseDrawCursor || imgui_cursor == ImGuiMouseCursor_None) { + // Hide OS mouse cursor if imgui is drawing it or if it wants no cursor + SDL_HideCursor(); + } else { + // Show OS mouse cursor + SDL_Cursor* expected_cursor = bd->mouse_cursors[imgui_cursor] + ? bd->mouse_cursors[imgui_cursor] + : bd->mouse_cursors[ImGuiMouseCursor_Arrow]; + if (bd->mouse_last_cursor != expected_cursor) { + SDL_SetCursor(expected_cursor); // SDL function doesn't have an early out (see #6113) + bd->mouse_last_cursor = expected_cursor; + } + SDL_ShowCursor(); + } +} + +static void CloseGamepads() { + SdlData* bd = GetBackendData(); + if (bd->gamepad_mode != ImGui_ImplSDL3_GamepadMode_Manual) + for (SDL_Gamepad* gamepad : bd->gamepads) + SDL_CloseGamepad(gamepad); + bd->gamepads.resize(0); +} + +void SetGamepadMode(GamepadMode mode, SDL_Gamepad** manual_gamepads_array, + int manual_gamepads_count) { + SdlData* bd = GetBackendData(); + CloseGamepads(); + if (mode == ImGui_ImplSDL3_GamepadMode_Manual) { + IM_ASSERT(manual_gamepads_array != nullptr && manual_gamepads_count > 0); + for (int n = 0; n < manual_gamepads_count; n++) + bd->gamepads.push_back(manual_gamepads_array[n]); + } else { + IM_ASSERT(manual_gamepads_array == nullptr && manual_gamepads_count <= 0); + bd->want_update_gamepads_list = true; + } + bd->gamepad_mode = mode; +} + +static void UpdateGamepadButton(SdlData* bd, ImGuiIO& io, ImGuiKey key, + SDL_GamepadButton button_no) { + bool merged_value = false; + for (SDL_Gamepad* gamepad : bd->gamepads) + merged_value |= SDL_GetGamepadButton(gamepad, button_no) != 0; + io.AddKeyEvent(key, merged_value); +} + +static inline float Saturate(float v) { + return v < 0.0f ? 0.0f : v > 1.0f ? 1.0f : v; +} +static void UpdateGamepadAnalog(SdlData* bd, ImGuiIO& io, ImGuiKey key, SDL_GamepadAxis axis_no, + float v0, float v1) { + float merged_value = 0.0f; + for (SDL_Gamepad* gamepad : bd->gamepads) { + float vn = Saturate((float)(SDL_GetGamepadAxis(gamepad, axis_no) - v0) / (float)(v1 - v0)); + if (merged_value < vn) + merged_value = vn; + } + io.AddKeyAnalogEvent(key, merged_value > 0.1f, merged_value); +} + +static void UpdateGamepads() { + ImGuiIO& io = ImGui::GetIO(); + SdlData* bd = GetBackendData(); + + // Update list of gamepads to use + if (bd->want_update_gamepads_list && bd->gamepad_mode != ImGui_ImplSDL3_GamepadMode_Manual) { + CloseGamepads(); + int sdl_gamepads_count = 0; + const SDL_JoystickID* sdl_gamepads = SDL_GetGamepads(&sdl_gamepads_count); + for (int n = 0; n < sdl_gamepads_count; n++) + if (SDL_Gamepad* gamepad = SDL_OpenGamepad(sdl_gamepads[n])) { + bd->gamepads.push_back(gamepad); + if (bd->gamepad_mode == ImGui_ImplSDL3_GamepadMode_AutoFirst) + break; + } + bd->want_update_gamepads_list = false; + } + + // FIXME: Technically feeding gamepad shouldn't depend on this now that they are regular inputs. + if ((io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) == 0) + return; + io.BackendFlags &= ~ImGuiBackendFlags_HasGamepad; + if (bd->gamepads.Size == 0) + return; + io.BackendFlags |= ImGuiBackendFlags_HasGamepad; + + // Update gamepad inputs + const int thumb_dead_zone = 8000; // SDL_gamepad.h suggests using this value. + UpdateGamepadButton(bd, io, ImGuiKey_GamepadStart, SDL_GAMEPAD_BUTTON_START); + UpdateGamepadButton(bd, io, ImGuiKey_GamepadBack, SDL_GAMEPAD_BUTTON_BACK); + /*UpdateGamepadButton(bd, io, ImGuiKey_GamepadFaceLeft, + SDL_GAMEPAD_BUTTON_WEST); // Xbox X, PS Square*/ // Disable to avoid menu toggle + UpdateGamepadButton(bd, io, ImGuiKey_GamepadFaceRight, + SDL_GAMEPAD_BUTTON_EAST); // Xbox B, PS Circle + UpdateGamepadButton(bd, io, ImGuiKey_GamepadFaceUp, + SDL_GAMEPAD_BUTTON_NORTH); // Xbox Y, PS Triangle + UpdateGamepadButton(bd, io, ImGuiKey_GamepadFaceDown, + SDL_GAMEPAD_BUTTON_SOUTH); // Xbox A, PS Cross + UpdateGamepadButton(bd, io, ImGuiKey_GamepadDpadLeft, SDL_GAMEPAD_BUTTON_DPAD_LEFT); + UpdateGamepadButton(bd, io, ImGuiKey_GamepadDpadRight, SDL_GAMEPAD_BUTTON_DPAD_RIGHT); + UpdateGamepadButton(bd, io, ImGuiKey_GamepadDpadUp, SDL_GAMEPAD_BUTTON_DPAD_UP); + UpdateGamepadButton(bd, io, ImGuiKey_GamepadDpadDown, SDL_GAMEPAD_BUTTON_DPAD_DOWN); + UpdateGamepadButton(bd, io, ImGuiKey_GamepadL1, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER); + UpdateGamepadButton(bd, io, ImGuiKey_GamepadR1, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER); + UpdateGamepadAnalog(bd, io, ImGuiKey_GamepadL2, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, 0.0f, 32767); + UpdateGamepadAnalog(bd, io, ImGuiKey_GamepadR2, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, 0.0f, 32767); + UpdateGamepadButton(bd, io, ImGuiKey_GamepadL3, SDL_GAMEPAD_BUTTON_LEFT_STICK); + UpdateGamepadButton(bd, io, ImGuiKey_GamepadR3, SDL_GAMEPAD_BUTTON_RIGHT_STICK); + UpdateGamepadAnalog(bd, io, ImGuiKey_GamepadLStickLeft, SDL_GAMEPAD_AXIS_LEFTX, + -thumb_dead_zone, -32768); + UpdateGamepadAnalog(bd, io, ImGuiKey_GamepadLStickRight, SDL_GAMEPAD_AXIS_LEFTX, + +thumb_dead_zone, +32767); + UpdateGamepadAnalog(bd, io, ImGuiKey_GamepadLStickUp, SDL_GAMEPAD_AXIS_LEFTY, -thumb_dead_zone, + -32768); + UpdateGamepadAnalog(bd, io, ImGuiKey_GamepadLStickDown, SDL_GAMEPAD_AXIS_LEFTY, + +thumb_dead_zone, +32767); + UpdateGamepadAnalog(bd, io, ImGuiKey_GamepadRStickLeft, SDL_GAMEPAD_AXIS_RIGHTX, + -thumb_dead_zone, -32768); + UpdateGamepadAnalog(bd, io, ImGuiKey_GamepadRStickRight, SDL_GAMEPAD_AXIS_RIGHTX, + +thumb_dead_zone, +32767); + UpdateGamepadAnalog(bd, io, ImGuiKey_GamepadRStickUp, SDL_GAMEPAD_AXIS_RIGHTY, -thumb_dead_zone, + -32768); + UpdateGamepadAnalog(bd, io, ImGuiKey_GamepadRStickDown, SDL_GAMEPAD_AXIS_RIGHTY, + +thumb_dead_zone, +32767); +} + +void NewFrame() { + SdlData* bd = GetBackendData(); + IM_ASSERT(bd != nullptr && "No platform backend to shutdown, or already shutdown?"); + ImGuiIO& io = ImGui::GetIO(); + + // Setup time step (we don't use SDL_GetTicks() because it is using millisecond resolution) + // (Accept SDL_GetPerformanceCounter() not returning a monotonically increasing value. Happens + // in VMs and Emscripten, see #6189, #6114, #3644) + static Uint64 frequency = SDL_GetPerformanceFrequency(); + Uint64 current_time = SDL_GetPerformanceCounter(); + if (current_time <= bd->time) + current_time = bd->time + 1; + io.DeltaTime = bd->time > 0 ? (float)((double)(current_time - bd->time) / (double)frequency) + : (float)(1.0f / 60.0f); + bd->time = current_time; + + if (bd->mouse_pending_leave_frame && bd->mouse_pending_leave_frame >= ImGui::GetFrameCount() && + bd->mouse_buttons_down == 0) { + bd->mouse_window_id = 0; + bd->mouse_pending_leave_frame = 0; + io.AddMousePosEvent(-FLT_MAX, -FLT_MAX); + } + + UpdateMouseData(); + UpdateMouseCursor(); + + // Update game controllers (if enabled and available) + UpdateGamepads(); +} + +void OnResize() { + SdlData* bd = GetBackendData(); + ImGuiIO& io = ImGui::GetIO(); + + int w, h; + int display_w, display_h; + SDL_GetWindowSize(bd->window, &w, &h); + if (SDL_GetWindowFlags(bd->window) & SDL_WINDOW_MINIMIZED) { + w = h = 0; + } + SDL_GetWindowSizeInPixels(bd->window, &display_w, &display_h); + io.DisplaySize = ImVec2((float)w, (float)h); + if (w > 0 && h > 0) { + io.DisplayFramebufferScale = {(float)display_w / (float)w, (float)display_h / (float)h}; + } +} + +} // namespace ImGui::Sdl diff --git a/src/imgui/renderer/imgui_impl_sdl3.h b/src/imgui/renderer/imgui_impl_sdl3.h new file mode 100644 index 00000000000..59b1a68567e --- /dev/null +++ b/src/imgui/renderer/imgui_impl_sdl3.h @@ -0,0 +1,31 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +// Based on imgui_impl_sdl3.h from Dear ImGui repository + +#pragma once + +struct SDL_Window; +struct SDL_Renderer; +struct SDL_Gamepad; +typedef union SDL_Event SDL_Event; + +namespace ImGui::Sdl { + +bool Init(SDL_Window* window); +void Shutdown(); +void NewFrame(); +bool ProcessEvent(const SDL_Event* event); +void OnResize(); + +// Gamepad selection automatically starts in AutoFirst mode, picking first available SDL_Gamepad. +// You may override this. When using manual mode, caller is responsible for opening/closing gamepad. +enum GamepadMode { + ImGui_ImplSDL3_GamepadMode_AutoFirst, + ImGui_ImplSDL3_GamepadMode_AutoAll, + ImGui_ImplSDL3_GamepadMode_Manual +}; +void SetGamepadMode(GamepadMode mode, SDL_Gamepad** manual_gamepads_array = NULL, + int manual_gamepads_count = -1); + +}; // namespace ImGui::Sdl diff --git a/src/imgui/renderer/imgui_impl_vulkan.cpp b/src/imgui/renderer/imgui_impl_vulkan.cpp new file mode 100644 index 00000000000..cf8c5ea4eea --- /dev/null +++ b/src/imgui/renderer/imgui_impl_vulkan.cpp @@ -0,0 +1,1307 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +// Based on imgui_impl_vulkan.cpp from Dear ImGui repository + +#include +#include + +#include + +#include "imgui_impl_vulkan.h" + +#ifndef IM_MAX +#define IM_MAX(A, B) (((A) >= (B)) ? (A) : (B)) +#endif + +#define IDX_SIZE sizeof(ImDrawIdx) + +namespace ImGui::Vulkan { + +struct RenderBuffer { + vk::DeviceMemory buffer_memory{}; + vk::DeviceSize buffer_size{}; + vk::Buffer buffer{}; +}; + +// Reusable buffers used for rendering 1 current in-flight frame, for RenderDrawData() +struct FrameRenderBuffers { + RenderBuffer vertex; + RenderBuffer index; +}; + +// Each viewport will hold 1 WindowRenderBuffers +struct WindowRenderBuffers { + uint32_t index{}; + uint32_t count{}; + std::vector frame_render_buffers{}; +}; + +// Vulkan data +struct VkData { + const InitInfo init_info; + vk::DeviceSize buffer_memory_alignment = 256; + vk::PipelineCreateFlags pipeline_create_flags{}; + vk::DescriptorPool descriptor_pool{}; + vk::DescriptorSetLayout descriptor_set_layout{}; + vk::PipelineLayout pipeline_layout{}; + vk::Pipeline pipeline{}; + vk::ShaderModule shader_module_vert{}; + vk::ShaderModule shader_module_frag{}; + + std::mutex command_pool_mutex; + vk::CommandPool command_pool{}; + vk::Sampler simple_sampler{}; + + // Font data + vk::DeviceMemory font_memory{}; + vk::Image font_image{}; + vk::ImageView font_view{}; + vk::DescriptorSet font_descriptor_set{}; + vk::CommandBuffer font_command_buffer{}; + + // Render buffers + WindowRenderBuffers render_buffers{}; + + VkData(const InitInfo init_info) : init_info(init_info) { + render_buffers.count = init_info.image_count; + render_buffers.frame_render_buffers.resize(render_buffers.count); + } +}; + +//----------------------------------------------------------------------------- +// SHADERS +//----------------------------------------------------------------------------- + +// backends/vulkan/glsl_shader.vert, compiled with: +// # glslangValidator -V -x -o glsl_shader.vert.u32 glsl_shader.vert +/* +#version 450 core +layout(location = 0) in vec2 aPos; +layout(location = 1) in vec2 aUV; +layout(location = 2) in vec4 aColor; +layout(push_constant) uniform uPushConstant { vec2 uScale; vec2 uTranslate; } pc; + +out gl_PerVertex { vec4 gl_Position; }; +layout(location = 0) out struct { vec4 Color; vec2 UV; } Out; + +void main() +{ + Out.Color = aColor; + Out.UV = aUV; + gl_Position = vec4(aPos * pc.uScale + pc.uTranslate, 0, 1); +} +*/ +static uint32_t glsl_shader_vert_spv[] = { + 0x07230203, 0x00010000, 0x00080001, 0x0000002e, 0x00000000, 0x00020011, 0x00000001, 0x0006000b, + 0x00000001, 0x4c534c47, 0x6474732e, 0x3035342e, 0x00000000, 0x0003000e, 0x00000000, 0x00000001, + 0x000a000f, 0x00000000, 0x00000004, 0x6e69616d, 0x00000000, 0x0000000b, 0x0000000f, 0x00000015, + 0x0000001b, 0x0000001c, 0x00030003, 0x00000002, 0x000001c2, 0x00040005, 0x00000004, 0x6e69616d, + 0x00000000, 0x00030005, 0x00000009, 0x00000000, 0x00050006, 0x00000009, 0x00000000, 0x6f6c6f43, + 0x00000072, 0x00040006, 0x00000009, 0x00000001, 0x00005655, 0x00030005, 0x0000000b, 0x0074754f, + 0x00040005, 0x0000000f, 0x6c6f4361, 0x0000726f, 0x00030005, 0x00000015, 0x00565561, 0x00060005, + 0x00000019, 0x505f6c67, 0x65567265, 0x78657472, 0x00000000, 0x00060006, 0x00000019, 0x00000000, + 0x505f6c67, 0x7469736f, 0x006e6f69, 0x00030005, 0x0000001b, 0x00000000, 0x00040005, 0x0000001c, + 0x736f5061, 0x00000000, 0x00060005, 0x0000001e, 0x73755075, 0x6e6f4368, 0x6e617473, 0x00000074, + 0x00050006, 0x0000001e, 0x00000000, 0x61635375, 0x0000656c, 0x00060006, 0x0000001e, 0x00000001, + 0x61725475, 0x616c736e, 0x00006574, 0x00030005, 0x00000020, 0x00006370, 0x00040047, 0x0000000b, + 0x0000001e, 0x00000000, 0x00040047, 0x0000000f, 0x0000001e, 0x00000002, 0x00040047, 0x00000015, + 0x0000001e, 0x00000001, 0x00050048, 0x00000019, 0x00000000, 0x0000000b, 0x00000000, 0x00030047, + 0x00000019, 0x00000002, 0x00040047, 0x0000001c, 0x0000001e, 0x00000000, 0x00050048, 0x0000001e, + 0x00000000, 0x00000023, 0x00000000, 0x00050048, 0x0000001e, 0x00000001, 0x00000023, 0x00000008, + 0x00030047, 0x0000001e, 0x00000002, 0x00020013, 0x00000002, 0x00030021, 0x00000003, 0x00000002, + 0x00030016, 0x00000006, 0x00000020, 0x00040017, 0x00000007, 0x00000006, 0x00000004, 0x00040017, + 0x00000008, 0x00000006, 0x00000002, 0x0004001e, 0x00000009, 0x00000007, 0x00000008, 0x00040020, + 0x0000000a, 0x00000003, 0x00000009, 0x0004003b, 0x0000000a, 0x0000000b, 0x00000003, 0x00040015, + 0x0000000c, 0x00000020, 0x00000001, 0x0004002b, 0x0000000c, 0x0000000d, 0x00000000, 0x00040020, + 0x0000000e, 0x00000001, 0x00000007, 0x0004003b, 0x0000000e, 0x0000000f, 0x00000001, 0x00040020, + 0x00000011, 0x00000003, 0x00000007, 0x0004002b, 0x0000000c, 0x00000013, 0x00000001, 0x00040020, + 0x00000014, 0x00000001, 0x00000008, 0x0004003b, 0x00000014, 0x00000015, 0x00000001, 0x00040020, + 0x00000017, 0x00000003, 0x00000008, 0x0003001e, 0x00000019, 0x00000007, 0x00040020, 0x0000001a, + 0x00000003, 0x00000019, 0x0004003b, 0x0000001a, 0x0000001b, 0x00000003, 0x0004003b, 0x00000014, + 0x0000001c, 0x00000001, 0x0004001e, 0x0000001e, 0x00000008, 0x00000008, 0x00040020, 0x0000001f, + 0x00000009, 0x0000001e, 0x0004003b, 0x0000001f, 0x00000020, 0x00000009, 0x00040020, 0x00000021, + 0x00000009, 0x00000008, 0x0004002b, 0x00000006, 0x00000028, 0x00000000, 0x0004002b, 0x00000006, + 0x00000029, 0x3f800000, 0x00050036, 0x00000002, 0x00000004, 0x00000000, 0x00000003, 0x000200f8, + 0x00000005, 0x0004003d, 0x00000007, 0x00000010, 0x0000000f, 0x00050041, 0x00000011, 0x00000012, + 0x0000000b, 0x0000000d, 0x0003003e, 0x00000012, 0x00000010, 0x0004003d, 0x00000008, 0x00000016, + 0x00000015, 0x00050041, 0x00000017, 0x00000018, 0x0000000b, 0x00000013, 0x0003003e, 0x00000018, + 0x00000016, 0x0004003d, 0x00000008, 0x0000001d, 0x0000001c, 0x00050041, 0x00000021, 0x00000022, + 0x00000020, 0x0000000d, 0x0004003d, 0x00000008, 0x00000023, 0x00000022, 0x00050085, 0x00000008, + 0x00000024, 0x0000001d, 0x00000023, 0x00050041, 0x00000021, 0x00000025, 0x00000020, 0x00000013, + 0x0004003d, 0x00000008, 0x00000026, 0x00000025, 0x00050081, 0x00000008, 0x00000027, 0x00000024, + 0x00000026, 0x00050051, 0x00000006, 0x0000002a, 0x00000027, 0x00000000, 0x00050051, 0x00000006, + 0x0000002b, 0x00000027, 0x00000001, 0x00070050, 0x00000007, 0x0000002c, 0x0000002a, 0x0000002b, + 0x00000028, 0x00000029, 0x00050041, 0x00000011, 0x0000002d, 0x0000001b, 0x0000000d, 0x0003003e, + 0x0000002d, 0x0000002c, 0x000100fd, 0x00010038}; + +// backends/vulkan/glsl_shader.frag, compiled with: +// # glslangValidator -V -x -o glsl_shader.frag.u32 glsl_shader.frag +/* +#version 450 core +layout(location = 0) out vec4 fColor; +layout(set=0, binding=0) uniform sampler2D sTexture; +layout(location = 0) in struct { vec4 Color; vec2 UV; } In; +void main() +{ + fColor = In.Color * texture(sTexture, In.UV.st); +} +*/ +static uint32_t glsl_shader_frag_spv[] = { + 0x07230203, 0x00010000, 0x00080001, 0x0000001e, 0x00000000, 0x00020011, 0x00000001, 0x0006000b, + 0x00000001, 0x4c534c47, 0x6474732e, 0x3035342e, 0x00000000, 0x0003000e, 0x00000000, 0x00000001, + 0x0007000f, 0x00000004, 0x00000004, 0x6e69616d, 0x00000000, 0x00000009, 0x0000000d, 0x00030010, + 0x00000004, 0x00000007, 0x00030003, 0x00000002, 0x000001c2, 0x00040005, 0x00000004, 0x6e69616d, + 0x00000000, 0x00040005, 0x00000009, 0x6c6f4366, 0x0000726f, 0x00030005, 0x0000000b, 0x00000000, + 0x00050006, 0x0000000b, 0x00000000, 0x6f6c6f43, 0x00000072, 0x00040006, 0x0000000b, 0x00000001, + 0x00005655, 0x00030005, 0x0000000d, 0x00006e49, 0x00050005, 0x00000016, 0x78655473, 0x65727574, + 0x00000000, 0x00040047, 0x00000009, 0x0000001e, 0x00000000, 0x00040047, 0x0000000d, 0x0000001e, + 0x00000000, 0x00040047, 0x00000016, 0x00000022, 0x00000000, 0x00040047, 0x00000016, 0x00000021, + 0x00000000, 0x00020013, 0x00000002, 0x00030021, 0x00000003, 0x00000002, 0x00030016, 0x00000006, + 0x00000020, 0x00040017, 0x00000007, 0x00000006, 0x00000004, 0x00040020, 0x00000008, 0x00000003, + 0x00000007, 0x0004003b, 0x00000008, 0x00000009, 0x00000003, 0x00040017, 0x0000000a, 0x00000006, + 0x00000002, 0x0004001e, 0x0000000b, 0x00000007, 0x0000000a, 0x00040020, 0x0000000c, 0x00000001, + 0x0000000b, 0x0004003b, 0x0000000c, 0x0000000d, 0x00000001, 0x00040015, 0x0000000e, 0x00000020, + 0x00000001, 0x0004002b, 0x0000000e, 0x0000000f, 0x00000000, 0x00040020, 0x00000010, 0x00000001, + 0x00000007, 0x00090019, 0x00000013, 0x00000006, 0x00000001, 0x00000000, 0x00000000, 0x00000000, + 0x00000001, 0x00000000, 0x0003001b, 0x00000014, 0x00000013, 0x00040020, 0x00000015, 0x00000000, + 0x00000014, 0x0004003b, 0x00000015, 0x00000016, 0x00000000, 0x0004002b, 0x0000000e, 0x00000018, + 0x00000001, 0x00040020, 0x00000019, 0x00000001, 0x0000000a, 0x00050036, 0x00000002, 0x00000004, + 0x00000000, 0x00000003, 0x000200f8, 0x00000005, 0x00050041, 0x00000010, 0x00000011, 0x0000000d, + 0x0000000f, 0x0004003d, 0x00000007, 0x00000012, 0x00000011, 0x0004003d, 0x00000014, 0x00000017, + 0x00000016, 0x00050041, 0x00000019, 0x0000001a, 0x0000000d, 0x00000018, 0x0004003d, 0x0000000a, + 0x0000001b, 0x0000001a, 0x00050057, 0x00000007, 0x0000001c, 0x00000017, 0x0000001b, 0x00050085, + 0x00000007, 0x0000001d, 0x00000012, 0x0000001c, 0x0003003e, 0x00000009, 0x0000001d, 0x000100fd, + 0x00010038}; + +// Backend data stored in io.BackendRendererUserData to allow support for multiple Dear ImGui +// contexts It is STRONGLY preferred that you use docking branch with multi-viewports (== single +// Dear ImGui context + multiple windows) instead of multiple Dear ImGui contexts. +static VkData* GetBackendData() { + return ImGui::GetCurrentContext() ? (VkData*)ImGui::GetIO().BackendRendererUserData : nullptr; +} + +static uint32_t FindMemoryType(vk::MemoryPropertyFlags properties, uint32_t type_bits) { + VkData* bd = GetBackendData(); + const InitInfo& v = bd->init_info; + const auto prop = v.physical_device.getMemoryProperties(); + for (uint32_t i = 0; i < prop.memoryTypeCount; i++) + if ((prop.memoryTypes[i].propertyFlags & properties) == properties && type_bits & (1 << i)) + return i; + return 0xFFFFFFFF; // Unable to find memoryType +} + +template +static T CheckVkResult(vk::ResultValue res) { + if (res.result == vk::Result::eSuccess) { + return res.value; + } + const VkData* bd = GetBackendData(); + if (!bd) { + return res.value; + } + const InitInfo& v = bd->init_info; + if (v.check_vk_result_fn) { + v.check_vk_result_fn(res.result); + } + return res.value; +} + +static void CheckVkErr(vk::Result res) { + if (res == vk::Result::eSuccess) { + return; + } + const VkData* bd = GetBackendData(); + if (!bd) { + return; + } + const InitInfo& v = bd->init_info; + if (v.check_vk_result_fn) { + v.check_vk_result_fn(res); + } +} + +// Same as IM_MEMALIGN(). 'alignment' must be a power of two. +static inline vk::DeviceSize AlignBufferSize(vk::DeviceSize size, vk::DeviceSize alignment) { + return (size + alignment - 1) & ~(alignment - 1); +} + +void UploadTextureData::Upload() { + VkData* bd = GetBackendData(); + const InitInfo& v = bd->init_info; + + vk::SubmitInfo submit_info{ + .commandBufferCount = 1, + .pCommandBuffers = &command_buffer, + }; + CheckVkErr(v.queue.submit({submit_info})); + CheckVkErr(v.queue.waitIdle()); + + v.device.destroyBuffer(upload_buffer, v.allocator); + v.device.freeMemory(upload_buffer_memory, v.allocator); + { + std::unique_lock lk(bd->command_pool_mutex); + v.device.freeCommandBuffers(bd->command_pool, {command_buffer}); + } + upload_buffer = VK_NULL_HANDLE; + upload_buffer_memory = VK_NULL_HANDLE; +} + +void UploadTextureData::Destroy() { + VkData* bd = GetBackendData(); + const InitInfo& v = bd->init_info; + + CheckVkErr(v.device.waitIdle()); + RemoveTexture(descriptor_set); + descriptor_set = VK_NULL_HANDLE; + + v.device.destroyImageView(image_view, v.allocator); + image_view = VK_NULL_HANDLE; + v.device.destroyImage(image, v.allocator); + image = VK_NULL_HANDLE; + v.device.freeMemory(image_memory, v.allocator); + image_memory = VK_NULL_HANDLE; +} + +// Register a texture +vk::DescriptorSet AddTexture(vk::ImageView image_view, vk::ImageLayout image_layout, + vk::Sampler sampler) { + VkData* bd = GetBackendData(); + const InitInfo& v = bd->init_info; + + if (sampler == VK_NULL_HANDLE) { + sampler = bd->simple_sampler; + } + + // Create Descriptor Set: + vk::DescriptorSet descriptor_set; + { + vk::DescriptorSetAllocateInfo alloc_info{ + .sType = vk::StructureType::eDescriptorSetAllocateInfo, + .descriptorPool = bd->descriptor_pool, + .descriptorSetCount = 1, + .pSetLayouts = &bd->descriptor_set_layout, + }; + descriptor_set = CheckVkResult(v.device.allocateDescriptorSets(alloc_info)).front(); + } + + // Update the Descriptor Set: + { + vk::DescriptorImageInfo desc_image[1]{ + { + .sampler = sampler, + .imageView = image_view, + .imageLayout = image_layout, + }, + }; + vk::WriteDescriptorSet write_desc[1]{ + { + .sType = vk::StructureType::eWriteDescriptorSet, + .dstSet = descriptor_set, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eCombinedImageSampler, + .pImageInfo = desc_image, + }, + }; + v.device.updateDescriptorSets({write_desc}, {}); + } + return descriptor_set; +} +UploadTextureData UploadTexture(const void* data, vk::Format format, u32 width, u32 height, + size_t size) { + ImGuiIO& io = GetIO(); + VkData* bd = GetBackendData(); + const InitInfo& v = bd->init_info; + + UploadTextureData info{}; + { + std::unique_lock lk(bd->command_pool_mutex); + info.command_buffer = + CheckVkResult(v.device.allocateCommandBuffers(vk::CommandBufferAllocateInfo{ + .commandPool = bd->command_pool, + .commandBufferCount = 1, + })) + .front(); + CheckVkErr(info.command_buffer.begin(vk::CommandBufferBeginInfo{ + .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit, + })); + } + + // Create Image + { + vk::ImageCreateInfo image_info{ + .imageType = vk::ImageType::e2D, + .format = format, + .extent{ + .width = width, + .height = height, + .depth = 1, + }, + .mipLevels = 1, + .arrayLayers = 1, + .samples = vk::SampleCountFlagBits::e1, + .tiling = vk::ImageTiling::eOptimal, + .usage = vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled, + .sharingMode = vk::SharingMode::eExclusive, + .initialLayout = vk::ImageLayout::eUndefined, + }; + info.image = CheckVkResult(v.device.createImage(image_info, v.allocator)); + auto req = v.device.getImageMemoryRequirements(info.image); + vk::MemoryAllocateInfo alloc_info{ + .allocationSize = IM_MAX(v.min_allocation_size, req.size), + .memoryTypeIndex = + FindMemoryType(vk::MemoryPropertyFlagBits::eDeviceLocal, req.memoryTypeBits), + }; + info.image_memory = CheckVkResult(v.device.allocateMemory(alloc_info, v.allocator)); + CheckVkErr(v.device.bindImageMemory(info.image, info.image_memory, 0)); + } + + // Create Image View + { + vk::ImageViewCreateInfo view_info{ + .image = info.image, + .viewType = vk::ImageViewType::e2D, + .format = format, + .subresourceRange{ + .aspectMask = vk::ImageAspectFlagBits::eColor, + .levelCount = 1, + .layerCount = 1, + }, + }; + info.image_view = CheckVkResult(v.device.createImageView(view_info, v.allocator)); + } + + // Create descriptor set (ImTextureID) + info.descriptor_set = AddTexture(info.image_view, vk::ImageLayout::eShaderReadOnlyOptimal); + + // Create Upload Buffer + { + vk::BufferCreateInfo buffer_info{ + .size = size, + .usage = vk::BufferUsageFlagBits::eTransferSrc, + .sharingMode = vk::SharingMode::eExclusive, + }; + info.upload_buffer = CheckVkResult(v.device.createBuffer(buffer_info, v.allocator)); + auto req = v.device.getBufferMemoryRequirements(info.upload_buffer); + auto alignemtn = IM_MAX(bd->buffer_memory_alignment, req.alignment); + vk::MemoryAllocateInfo alloc_info{ + .allocationSize = IM_MAX(v.min_allocation_size, req.size), + .memoryTypeIndex = + FindMemoryType(vk::MemoryPropertyFlagBits::eHostVisible, req.memoryTypeBits), + }; + info.upload_buffer_memory = CheckVkResult(v.device.allocateMemory(alloc_info, v.allocator)); + CheckVkErr(v.device.bindBufferMemory(info.upload_buffer, info.upload_buffer_memory, 0)); + } + + // Upload to Buffer + { + char* map = (char*)CheckVkResult(v.device.mapMemory(info.upload_buffer_memory, 0, size)); + memcpy(map, data, size); + vk::MappedMemoryRange range[1]{ + { + .memory = info.upload_buffer_memory, + .size = size, + }, + }; + CheckVkErr(v.device.flushMappedMemoryRanges(range)); + v.device.unmapMemory(info.upload_buffer_memory); + } + + // Copy to Image + { + vk::ImageMemoryBarrier copy_barrier[1]{ + { + .sType = vk::StructureType::eImageMemoryBarrier, + .dstAccessMask = vk::AccessFlagBits::eTransferWrite, + .oldLayout = vk::ImageLayout::eUndefined, + .newLayout = vk::ImageLayout::eTransferDstOptimal, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = info.image, + .subresourceRange{ + .aspectMask = vk::ImageAspectFlagBits::eColor, + .levelCount = 1, + .layerCount = 1, + }, + }, + }; + info.command_buffer.pipelineBarrier(vk::PipelineStageFlagBits::eHost, + vk::PipelineStageFlagBits::eTransfer, {}, {}, {}, + {copy_barrier}); + + vk::BufferImageCopy region{ + .imageSubresource{ + .aspectMask = vk::ImageAspectFlagBits::eColor, + .layerCount = 1, + }, + .imageExtent{ + .width = width, + .height = height, + .depth = 1, + }, + }; + info.command_buffer.copyBufferToImage(info.upload_buffer, info.image, + vk::ImageLayout::eTransferDstOptimal, {region}); + + vk::ImageMemoryBarrier use_barrier[1]{{ + .sType = vk::StructureType::eImageMemoryBarrier, + .srcAccessMask = vk::AccessFlagBits::eTransferWrite, + .dstAccessMask = vk::AccessFlagBits::eShaderRead, + .oldLayout = vk::ImageLayout::eTransferDstOptimal, + .newLayout = vk::ImageLayout::eShaderReadOnlyOptimal, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = info.image, + .subresourceRange{ + .aspectMask = vk::ImageAspectFlagBits::eColor, + .levelCount = 1, + .layerCount = 1, + }, + }}; + info.command_buffer.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, + vk::PipelineStageFlagBits::eFragmentShader, {}, {}, {}, + {use_barrier}); + } + + CheckVkErr(info.command_buffer.end()); + + return info; +} + +void RemoveTexture(vk::DescriptorSet descriptor_set) { + VkData* bd = GetBackendData(); + const InitInfo& v = bd->init_info; + v.device.freeDescriptorSets(bd->descriptor_pool, {descriptor_set}); +} + +static void CreateOrResizeBuffer(RenderBuffer& rb, size_t new_size, vk::BufferUsageFlagBits usage) { + VkData* bd = GetBackendData(); + IM_ASSERT(bd != nullptr); + const InitInfo& v = bd->init_info; + if (rb.buffer != VK_NULL_HANDLE) { + v.device.destroyBuffer(rb.buffer, v.allocator); + } + if (rb.buffer_memory != VK_NULL_HANDLE) { + v.device.freeMemory(rb.buffer_memory, v.allocator); + } + + const vk::DeviceSize buffer_size_aligned = + AlignBufferSize(IM_MAX(v.min_allocation_size, new_size), bd->buffer_memory_alignment); + vk::BufferCreateInfo buffer_info{ + .sType = vk::StructureType::eBufferCreateInfo, + .size = buffer_size_aligned, + .usage = usage, + .sharingMode = vk::SharingMode::eExclusive, + }; + rb.buffer = CheckVkResult(v.device.createBuffer(buffer_info, v.allocator)); + + const vk::MemoryRequirements req = v.device.getBufferMemoryRequirements(rb.buffer); + bd->buffer_memory_alignment = IM_MAX(bd->buffer_memory_alignment, req.alignment); + vk::MemoryAllocateInfo alloc_info{ + .sType = vk::StructureType::eMemoryAllocateInfo, + .allocationSize = req.size, + .memoryTypeIndex = + FindMemoryType(vk::MemoryPropertyFlagBits::eHostVisible, req.memoryTypeBits), + }; + rb.buffer_memory = CheckVkResult(v.device.allocateMemory(alloc_info, v.allocator)); + + CheckVkErr(v.device.bindBufferMemory(rb.buffer, rb.buffer_memory, 0)); + rb.buffer_size = buffer_size_aligned; +} + +static void SetupRenderState(ImDrawData& draw_data, vk::Pipeline pipeline, vk::CommandBuffer cmdbuf, + FrameRenderBuffers& frb, int fb_width, int fb_height) { + VkData* bd = GetBackendData(); + + // Bind pipeline: + cmdbuf.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline); + + // Bind Vertex And Index Buffer: + if (draw_data.TotalVtxCount > 0) { + vk::Buffer vertex_buffers[1] = {frb.vertex.buffer}; + vk::DeviceSize vertex_offset[1] = {0}; + cmdbuf.bindVertexBuffers(0, {vertex_buffers}, vertex_offset); + cmdbuf.bindIndexBuffer(frb.index.buffer, 0, + IDX_SIZE == 2 ? vk::IndexType::eUint16 : vk::IndexType::eUint32); + } + + // Setup viewport: + { + vk::Viewport viewport{ + .x = 0, + .y = 0, + .width = (float)fb_width, + .height = (float)fb_height, + .minDepth = 0.0f, + .maxDepth = 1.0f, + }; + cmdbuf.setViewport(0, {viewport}); + } + + // Setup scale and translation: + // Our visible imgui space lies from draw_data->DisplayPps (top left) to + // draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single + // viewport apps. + { + float scale[2]; + scale[0] = 2.0f / draw_data.DisplaySize.x; + scale[1] = 2.0f / draw_data.DisplaySize.y; + float translate[2]; + translate[0] = -1.0f - draw_data.DisplayPos.x * scale[0]; + translate[1] = -1.0f - draw_data.DisplayPos.y * scale[1]; + cmdbuf.pushConstants(bd->pipeline_layout, vk::ShaderStageFlagBits::eVertex, + sizeof(float) * 0, sizeof(float) * 2, scale); + cmdbuf.pushConstants(bd->pipeline_layout, vk::ShaderStageFlagBits::eVertex, + sizeof(float) * 2, sizeof(float) * 2, translate); + } +} + +// Render function +void RenderDrawData(ImDrawData& draw_data, vk::CommandBuffer command_buffer, + vk::Pipeline pipeline) { + // Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != + // framebuffer coordinates) + int fb_width = (int)(draw_data.DisplaySize.x * draw_data.FramebufferScale.x); + int fb_height = (int)(draw_data.DisplaySize.y * draw_data.FramebufferScale.y); + if (fb_width <= 0 || fb_height <= 0) { + return; + } + + VkData* bd = GetBackendData(); + const InitInfo& v = bd->init_info; + if (pipeline == VK_NULL_HANDLE) { + pipeline = bd->pipeline; + } + + // Allocate array to store enough vertex/index buffers + WindowRenderBuffers& wrb = bd->render_buffers; + wrb.index = (wrb.index + 1) % wrb.count; + FrameRenderBuffers& frb = wrb.frame_render_buffers[wrb.index]; + + if (draw_data.TotalVtxCount > 0) { + // Create or resize the vertex/index buffers + size_t vertex_size = AlignBufferSize(draw_data.TotalVtxCount * sizeof(ImDrawVert), + bd->buffer_memory_alignment); + size_t index_size = + AlignBufferSize(draw_data.TotalIdxCount * IDX_SIZE, bd->buffer_memory_alignment); + if (frb.vertex.buffer == VK_NULL_HANDLE || frb.vertex.buffer_size < vertex_size) { + CreateOrResizeBuffer(frb.vertex, vertex_size, vk::BufferUsageFlagBits::eVertexBuffer); + } + if (frb.index.buffer == VK_NULL_HANDLE || frb.index.buffer_size < index_size) { + CreateOrResizeBuffer(frb.index, index_size, vk::BufferUsageFlagBits::eIndexBuffer); + } + + // Upload vertex/index data into a single contiguous GPU buffer + ImDrawVert* vtx_dst = nullptr; + ImDrawIdx* idx_dst = nullptr; + vtx_dst = (ImDrawVert*)CheckVkResult( + v.device.mapMemory(frb.vertex.buffer_memory, 0, vertex_size, vk::MemoryMapFlags{})); + idx_dst = (ImDrawIdx*)CheckVkResult( + v.device.mapMemory(frb.index.buffer_memory, 0, index_size, vk::MemoryMapFlags{})); + for (int n = 0; n < draw_data.CmdListsCount; n++) { + const ImDrawList* cmd_list = draw_data.CmdLists[n]; + memcpy(vtx_dst, cmd_list->VtxBuffer.Data, + cmd_list->VtxBuffer.Size * sizeof(ImDrawVert)); + memcpy(idx_dst, cmd_list->IdxBuffer.Data, cmd_list->IdxBuffer.Size * IDX_SIZE); + vtx_dst += cmd_list->VtxBuffer.Size; + idx_dst += cmd_list->IdxBuffer.Size; + } + vk::MappedMemoryRange range[2]{ + { + .sType = vk::StructureType::eMappedMemoryRange, + .memory = frb.vertex.buffer_memory, + .size = VK_WHOLE_SIZE, + }, + { + .sType = vk::StructureType::eMappedMemoryRange, + .memory = frb.index.buffer_memory, + .size = VK_WHOLE_SIZE, + }, + }; + CheckVkErr(v.device.flushMappedMemoryRanges({range})); + v.device.unmapMemory(frb.vertex.buffer_memory); + v.device.unmapMemory(frb.index.buffer_memory); + } + + // Setup desired Vulkan state + SetupRenderState(draw_data, pipeline, command_buffer, frb, fb_width, fb_height); + + // Will project scissor/clipping rectangles into framebuffer space + + // (0,0) unless using multi-viewports + ImVec2 clip_off = draw_data.DisplayPos; + // (1,1) unless using retina display which are often (2,2) + ImVec2 clip_scale = draw_data.FramebufferScale; + + // Render command lists + // (Because we merged all buffers into a single one, we maintain our own offset into them) + int global_vtx_offset = 0; + int global_idx_offset = 0; + for (int n = 0; n < draw_data.CmdListsCount; n++) { + const ImDrawList* cmd_list = draw_data.CmdLists[n]; + for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) { + const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i]; + if (pcmd->UserCallback != nullptr) { + // User callback, registered via ImDrawList::AddCallback() + // (ImDrawCallback_ResetRenderState is a special callback value used by the user to + // request the renderer to reset render state.) + if (pcmd->UserCallback == ImDrawCallback_ResetRenderState) { + SetupRenderState(draw_data, pipeline, command_buffer, frb, fb_width, fb_height); + } else { + pcmd->UserCallback(cmd_list, pcmd); + } + } else { + // Project scissor/clipping rectangles into framebuffer space + ImVec2 clip_min((pcmd->ClipRect.x - clip_off.x) * clip_scale.x, + (pcmd->ClipRect.y - clip_off.y) * clip_scale.y); + ImVec2 clip_max((pcmd->ClipRect.z - clip_off.x) * clip_scale.x, + (pcmd->ClipRect.w - clip_off.y) * clip_scale.y); + + // Clamp to viewport as vk::CmdSetScissor() won't accept values that are off bounds + if (clip_min.x < 0.0f) { + clip_min.x = 0.0f; + } + if (clip_min.y < 0.0f) { + clip_min.y = 0.0f; + } + if (clip_max.x > fb_width) { + clip_max.x = (float)fb_width; + } + if (clip_max.y > fb_height) { + clip_max.y = (float)fb_height; + } + if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y) + continue; + + // Apply scissor/clipping rectangle + vk::Rect2D scissor{ + .offset{ + .x = (int32_t)(clip_min.x), + .y = (int32_t)(clip_min.y), + }, + .extent{ + .width = (uint32_t)(clip_max.x - clip_min.x), + .height = (uint32_t)(clip_max.y - clip_min.y), + }, + }; + command_buffer.setScissor(0, 1, &scissor); + + // Bind DescriptorSet with font or user texture + vk::DescriptorSet desc_set[1]{(VkDescriptorSet)pcmd->TextureId}; + if (sizeof(ImTextureID) < sizeof(ImU64)) { + // We don't support texture switches if ImTextureID hasn't been redefined to be + // 64-bit. Do a flaky check that other textures haven't been used. + IM_ASSERT(pcmd->TextureId == (ImTextureID)bd->font_descriptor_set); + desc_set[0] = bd->font_descriptor_set; + } + command_buffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, + bd->pipeline_layout, 0, {desc_set}, {}); + + // Draw + command_buffer.drawIndexed(pcmd->ElemCount, 1, pcmd->IdxOffset + global_idx_offset, + pcmd->VtxOffset + global_vtx_offset, 0); + } + } + global_idx_offset += cmd_list->IdxBuffer.Size; + global_vtx_offset += cmd_list->VtxBuffer.Size; + } + // vk::Rect2D scissor = {{0, 0}, {(uint32_t)fb_width, (uint32_t)fb_height}}; + // command_buffer.setScissor(0, 1, &scissor); +} + +static void DestroyFontsTexture(); + +static bool CreateFontsTexture() { + ImGuiIO& io = ImGui::GetIO(); + VkData* bd = GetBackendData(); + const InitInfo& v = bd->init_info; + + // Destroy existing texture (if any) + if (bd->font_view || bd->font_image || bd->font_memory || bd->font_descriptor_set) { + CheckVkErr(v.queue.waitIdle()); + DestroyFontsTexture(); + } + + // Create command buffer + if (bd->font_command_buffer == VK_NULL_HANDLE) { + vk::CommandBufferAllocateInfo info{ + .sType = vk::StructureType::eCommandBufferAllocateInfo, + .commandPool = bd->command_pool, + .commandBufferCount = 1, + }; + std::unique_lock lk(bd->command_pool_mutex); + bd->font_command_buffer = CheckVkResult(v.device.allocateCommandBuffers(info)).front(); + } + + // Start command buffer + { + CheckVkErr(bd->font_command_buffer.reset()); + vk::CommandBufferBeginInfo begin_info{}; + begin_info.sType = vk::StructureType::eCommandBufferBeginInfo; + begin_info.flags |= vk::CommandBufferUsageFlagBits::eOneTimeSubmit; + CheckVkErr(bd->font_command_buffer.begin(&begin_info)); + } + + unsigned char* pixels; + int width, height; + io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); + size_t upload_size = width * height * 4 * sizeof(char); + + // Create the Image: + { + vk::ImageCreateInfo info{ + .sType = vk::StructureType::eImageCreateInfo, + .imageType = vk::ImageType::e2D, + .format = vk::Format::eR8G8B8A8Unorm, + .extent{ + .width = static_cast(width), + .height = static_cast(height), + .depth = 1, + }, + .mipLevels = 1, + .arrayLayers = 1, + .samples = vk::SampleCountFlagBits::e1, + .tiling = vk::ImageTiling::eOptimal, + .usage = vk::ImageUsageFlagBits::eSampled | vk::ImageUsageFlagBits::eTransferDst, + .sharingMode = vk::SharingMode::eExclusive, + .initialLayout = vk::ImageLayout::eUndefined, + }; + bd->font_image = CheckVkResult(v.device.createImage(info, v.allocator)); + vk::MemoryRequirements req = v.device.getImageMemoryRequirements(bd->font_image); + vk::MemoryAllocateInfo alloc_info{ + .sType = vk::StructureType::eMemoryAllocateInfo, + .allocationSize = IM_MAX(v.min_allocation_size, req.size), + .memoryTypeIndex = + FindMemoryType(vk::MemoryPropertyFlagBits::eDeviceLocal, req.memoryTypeBits), + }; + bd->font_memory = CheckVkResult(v.device.allocateMemory(alloc_info, v.allocator)); + CheckVkErr(v.device.bindImageMemory(bd->font_image, bd->font_memory, 0)); + } + + // Create the Image View: + { + vk::ImageViewCreateInfo info{ + .sType = vk::StructureType::eImageViewCreateInfo, + .image = bd->font_image, + .viewType = vk::ImageViewType::e2D, + .format = vk::Format::eR8G8B8A8Unorm, + .subresourceRange{ + .aspectMask = vk::ImageAspectFlagBits::eColor, + .levelCount = 1, + .layerCount = 1, + }, + }; + bd->font_view = CheckVkResult(v.device.createImageView(info, v.allocator)); + } + + // Create the Descriptor Set: + bd->font_descriptor_set = AddTexture(bd->font_view, vk::ImageLayout::eShaderReadOnlyOptimal); + + // Create the Upload Buffer: + vk::DeviceMemory upload_buffer_memory{}; + vk::Buffer upload_buffer{}; + { + vk::BufferCreateInfo buffer_info{ + .sType = vk::StructureType::eBufferCreateInfo, + .size = upload_size, + .usage = vk::BufferUsageFlagBits::eTransferSrc, + .sharingMode = vk::SharingMode::eExclusive, + }; + upload_buffer = CheckVkResult(v.device.createBuffer(buffer_info, v.allocator)); + vk::MemoryRequirements req = v.device.getBufferMemoryRequirements(upload_buffer); + bd->buffer_memory_alignment = IM_MAX(bd->buffer_memory_alignment, req.alignment); + vk::MemoryAllocateInfo alloc_info{ + .sType = vk::StructureType::eMemoryAllocateInfo, + .allocationSize = IM_MAX(v.min_allocation_size, req.size), + .memoryTypeIndex = + FindMemoryType(vk::MemoryPropertyFlagBits::eHostVisible, req.memoryTypeBits), + }; + upload_buffer_memory = CheckVkResult(v.device.allocateMemory(alloc_info, v.allocator)); + CheckVkErr(v.device.bindBufferMemory(upload_buffer, upload_buffer_memory, 0)); + } + + // Upload to Buffer: + { + char* map = (char*)CheckVkResult(v.device.mapMemory(upload_buffer_memory, 0, upload_size)); + memcpy(map, pixels, upload_size); + vk::MappedMemoryRange range[1]{ + { + .sType = vk::StructureType::eMappedMemoryRange, + .memory = upload_buffer_memory, + .size = upload_size, + }, + }; + CheckVkErr(v.device.flushMappedMemoryRanges({range})); + v.device.unmapMemory(upload_buffer_memory); + } + + // Copy to Image: + { + vk::ImageMemoryBarrier copy_barrier[1]{ + { + .sType = vk::StructureType::eImageMemoryBarrier, + .dstAccessMask = vk::AccessFlagBits::eTransferWrite, + .oldLayout = vk::ImageLayout::eUndefined, + .newLayout = vk::ImageLayout::eTransferDstOptimal, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = bd->font_image, + .subresourceRange{ + .aspectMask = vk::ImageAspectFlagBits::eColor, + .levelCount = 1, + .layerCount = 1, + }, + }, + }; + bd->font_command_buffer.pipelineBarrier(vk::PipelineStageFlagBits::eHost, + vk::PipelineStageFlagBits::eTransfer, {}, {}, {}, + {copy_barrier}); + + vk::BufferImageCopy region{ + .imageSubresource{ + .aspectMask = vk::ImageAspectFlagBits::eColor, + .layerCount = 1, + }, + .imageExtent{ + .width = static_cast(width), + .height = static_cast(height), + .depth = 1, + }, + }; + bd->font_command_buffer.copyBufferToImage(upload_buffer, bd->font_image, + vk::ImageLayout::eTransferDstOptimal, {region}); + + vk::ImageMemoryBarrier use_barrier[1]{{ + .sType = vk::StructureType::eImageMemoryBarrier, + .srcAccessMask = vk::AccessFlagBits::eTransferWrite, + .dstAccessMask = vk::AccessFlagBits::eShaderRead, + .oldLayout = vk::ImageLayout::eTransferDstOptimal, + .newLayout = vk::ImageLayout::eShaderReadOnlyOptimal, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = bd->font_image, + .subresourceRange{ + .aspectMask = vk::ImageAspectFlagBits::eColor, + .levelCount = 1, + .layerCount = 1, + }, + }}; + bd->font_command_buffer.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, + vk::PipelineStageFlagBits::eFragmentShader, {}, {}, + {}, {use_barrier}); + } + + // Store our identifier + io.Fonts->SetTexID((ImTextureID)bd->font_descriptor_set); + + // End command buffer + vk::SubmitInfo end_info = {}; + end_info.sType = vk::StructureType::eSubmitInfo; + end_info.commandBufferCount = 1; + end_info.pCommandBuffers = &bd->font_command_buffer; + CheckVkErr(bd->font_command_buffer.end()); + CheckVkErr(v.queue.submit({end_info})); + + CheckVkErr(v.queue.waitIdle()); + + v.device.destroyBuffer(upload_buffer, v.allocator); + v.device.freeMemory(upload_buffer_memory, v.allocator); + + return true; +} + +// You probably never need to call this, as it is called by CreateFontsTexture() +// and Shutdown(). +static void DestroyFontsTexture() { + ImGuiIO& io = ImGui::GetIO(); + VkData* bd = GetBackendData(); + const InitInfo& v = bd->init_info; + + if (bd->font_descriptor_set) { + RemoveTexture(bd->font_descriptor_set); + bd->font_descriptor_set = VK_NULL_HANDLE; + io.Fonts->SetTexID(nullptr); + } + + if (bd->font_view) { + v.device.destroyImageView(bd->font_view, v.allocator); + bd->font_view = VK_NULL_HANDLE; + } + if (bd->font_image) { + v.device.destroyImage(bd->font_image, v.allocator); + bd->font_image = VK_NULL_HANDLE; + } + if (bd->font_memory) { + v.device.freeMemory(bd->font_memory, v.allocator); + bd->font_memory = VK_NULL_HANDLE; + } +} + +static void DestroyFrameRenderBuffers(vk::Device device, RenderBuffer& rb, + const vk::AllocationCallbacks* allocator) { + if (rb.buffer) { + device.destroyBuffer(rb.buffer, allocator); + rb.buffer = VK_NULL_HANDLE; + } + if (rb.buffer_memory) { + device.freeMemory(rb.buffer_memory, allocator); + rb.buffer_memory = VK_NULL_HANDLE; + } + rb.buffer_size = 0; +} + +static void DestroyWindowRenderBuffers(vk::Device device, WindowRenderBuffers& buffers, + const vk::AllocationCallbacks* allocator) { + for (uint32_t n = 0; n < buffers.count; n++) { + auto& frb = buffers.frame_render_buffers[n]; + DestroyFrameRenderBuffers(device, frb.index, allocator); + DestroyFrameRenderBuffers(device, frb.vertex, allocator); + } + buffers = {}; +} + +static void CreateShaderModules(vk::Device device, const vk::AllocationCallbacks* allocator) { + // Create the shader modules + VkData* bd = GetBackendData(); + if (bd->shader_module_vert == VK_NULL_HANDLE) { + vk::ShaderModuleCreateInfo vert_info{ + .sType = vk::StructureType::eShaderModuleCreateInfo, + .codeSize = sizeof(glsl_shader_vert_spv), + .pCode = (uint32_t*)glsl_shader_vert_spv, + }; + bd->shader_module_vert = CheckVkResult(device.createShaderModule(vert_info, allocator)); + } + if (bd->shader_module_frag == VK_NULL_HANDLE) { + vk::ShaderModuleCreateInfo frag_info{ + .sType = vk::StructureType::eShaderModuleCreateInfo, + .codeSize = sizeof(glsl_shader_frag_spv), + .pCode = (uint32_t*)glsl_shader_frag_spv, + }; + bd->shader_module_frag = CheckVkResult(device.createShaderModule(frag_info, allocator)); + } +} + +static void CreatePipeline(vk::Device device, const vk::AllocationCallbacks* allocator, + vk::PipelineCache pipeline_cache, vk::RenderPass render_pass, + vk::Pipeline* pipeline, uint32_t subpass) { + VkData* bd = GetBackendData(); + const InitInfo& v = bd->init_info; + + CreateShaderModules(device, allocator); + + vk::PipelineShaderStageCreateInfo stage[2]{ + { + .sType = vk::StructureType::ePipelineShaderStageCreateInfo, + .stage = vk::ShaderStageFlagBits::eVertex, + .module = bd->shader_module_vert, + .pName = "main", + }, + { + .sType = vk::StructureType::ePipelineShaderStageCreateInfo, + .stage = vk::ShaderStageFlagBits::eFragment, + .module = bd->shader_module_frag, + .pName = "main", + }, + }; + + vk::VertexInputBindingDescription binding_desc[1]{ + { + .stride = sizeof(ImDrawVert), + .inputRate = vk::VertexInputRate::eVertex, + }, + }; + + vk::VertexInputAttributeDescription attribute_desc[3]{ + { + .location = 0, + .binding = binding_desc[0].binding, + .format = vk::Format::eR32G32Sfloat, + .offset = offsetof(ImDrawVert, pos), + }, + { + .location = 1, + .binding = binding_desc[0].binding, + .format = vk::Format::eR32G32Sfloat, + .offset = offsetof(ImDrawVert, uv), + }, + { + .location = 2, + .binding = binding_desc[0].binding, + .format = vk::Format::eR8G8B8A8Unorm, + .offset = offsetof(ImDrawVert, col), + }, + }; + + vk::PipelineVertexInputStateCreateInfo vertex_info{ + .sType = vk::StructureType::ePipelineVertexInputStateCreateInfo, + .vertexBindingDescriptionCount = 1, + .pVertexBindingDescriptions = binding_desc, + .vertexAttributeDescriptionCount = 3, + .pVertexAttributeDescriptions = attribute_desc, + }; + + vk::PipelineInputAssemblyStateCreateInfo ia_info{ + .sType = vk::StructureType::ePipelineInputAssemblyStateCreateInfo, + .topology = vk::PrimitiveTopology::eTriangleList, + }; + + vk::PipelineViewportStateCreateInfo viewport_info{ + .sType = vk::StructureType::ePipelineViewportStateCreateInfo, + .viewportCount = 1, + .scissorCount = 1, + }; + + vk::PipelineRasterizationStateCreateInfo raster_info{ + .sType = vk::StructureType::ePipelineRasterizationStateCreateInfo, + .polygonMode = vk::PolygonMode::eFill, + .cullMode = vk::CullModeFlagBits::eNone, + .frontFace = vk::FrontFace::eCounterClockwise, + .lineWidth = 1.0f, + }; + + vk::PipelineMultisampleStateCreateInfo ms_info{ + .sType = vk::StructureType::ePipelineMultisampleStateCreateInfo, + .rasterizationSamples = vk::SampleCountFlagBits::e1, + }; + + vk::PipelineColorBlendAttachmentState color_attachment[1]{ + { + .blendEnable = VK_TRUE, + .srcColorBlendFactor = vk::BlendFactor::eSrcAlpha, + .dstColorBlendFactor = vk::BlendFactor::eOneMinusSrcAlpha, + .colorBlendOp = vk::BlendOp::eAdd, + .srcAlphaBlendFactor = vk::BlendFactor::eOne, + .dstAlphaBlendFactor = vk::BlendFactor::eOneMinusSrcAlpha, + .alphaBlendOp = vk::BlendOp::eAdd, + .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | + vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA, + }, + }; + + vk::PipelineDepthStencilStateCreateInfo depth_info{ + .sType = vk::StructureType::ePipelineDepthStencilStateCreateInfo, + }; + + vk::PipelineColorBlendStateCreateInfo blend_info{ + .sType = vk::StructureType::ePipelineColorBlendStateCreateInfo, + .attachmentCount = 1, + .pAttachments = color_attachment, + }; + + vk::DynamicState dynamic_states[2]{ + vk::DynamicState::eViewport, + vk::DynamicState::eScissor, + }; + vk::PipelineDynamicStateCreateInfo dynamic_state{ + .sType = vk::StructureType::ePipelineDynamicStateCreateInfo, + .dynamicStateCount = (uint32_t)IM_ARRAYSIZE(dynamic_states), + .pDynamicStates = dynamic_states, + }; + + vk::GraphicsPipelineCreateInfo info{ + .sType = vk::StructureType::eGraphicsPipelineCreateInfo, + .pNext = &v.pipeline_rendering_create_info, + .flags = bd->pipeline_create_flags, + .stageCount = 2, + .pStages = stage, + .pVertexInputState = &vertex_info, + .pInputAssemblyState = &ia_info, + .pViewportState = &viewport_info, + .pRasterizationState = &raster_info, + .pMultisampleState = &ms_info, + .pDepthStencilState = &depth_info, + .pColorBlendState = &blend_info, + .pDynamicState = &dynamic_state, + .layout = bd->pipeline_layout, + .renderPass = render_pass, + .subpass = subpass, + }; + + *pipeline = + CheckVkResult(device.createGraphicsPipelines(pipeline_cache, {info}, allocator)).front(); +} + +bool CreateDeviceObjects() { + VkData* bd = GetBackendData(); + const InitInfo& v = bd->init_info; + vk::Result err; + + if (!bd->descriptor_pool) { + // large enough descriptor pool + vk::DescriptorPoolSize pool_sizes[]{ + {vk::DescriptorType::eSampler, 1000}, + {vk::DescriptorType::eCombinedImageSampler, 1000}, + {vk::DescriptorType::eSampledImage, 1000}, + {vk::DescriptorType::eStorageImage, 1000}, + {vk::DescriptorType::eUniformTexelBuffer, 1000}, + {vk::DescriptorType::eStorageTexelBuffer, 1000}, + {vk::DescriptorType::eUniformBuffer, 1000}, + {vk::DescriptorType::eStorageBuffer, 1000}, + {vk::DescriptorType::eUniformBufferDynamic, 1000}, + {vk::DescriptorType::eStorageBufferDynamic, 1000}, + {vk::DescriptorType::eInputAttachment, 1000}, + }; + + vk::DescriptorPoolCreateInfo pool_info{ + .sType = vk::StructureType::eDescriptorPoolCreateInfo, + .flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet, + .maxSets = 1000, + .poolSizeCount = std::size(pool_sizes), + .pPoolSizes = pool_sizes, + }; + + bd->descriptor_pool = CheckVkResult(v.device.createDescriptorPool(pool_info)); + } + + if (!bd->descriptor_set_layout) { + vk::DescriptorSetLayoutBinding binding[1]{ + { + .descriptorType = vk::DescriptorType::eCombinedImageSampler, + .descriptorCount = 1, + .stageFlags = vk::ShaderStageFlagBits::eFragment, + }, + }; + vk::DescriptorSetLayoutCreateInfo info{ + .sType = vk::StructureType::eDescriptorSetLayoutCreateInfo, + .bindingCount = 1, + .pBindings = binding, + }; + bd->descriptor_set_layout = + CheckVkResult(v.device.createDescriptorSetLayout(info, v.allocator)); + } + + if (!bd->pipeline_layout) { + // Constants: we are using 'vec2 offset' and 'vec2 scale' instead of a full 3d projection + // matrix + vk::PushConstantRange push_constants[1]{ + { + .stageFlags = vk::ShaderStageFlagBits::eVertex, + .offset = sizeof(float) * 0, + .size = sizeof(float) * 4, + }, + }; + vk::DescriptorSetLayout set_layout[1] = {bd->descriptor_set_layout}; + vk::PipelineLayoutCreateInfo layout_info{ + .sType = vk::StructureType::ePipelineLayoutCreateInfo, + .setLayoutCount = 1, + .pSetLayouts = set_layout, + .pushConstantRangeCount = 1, + .pPushConstantRanges = push_constants, + }; + bd->pipeline_layout = + CheckVkResult(v.device.createPipelineLayout(layout_info, v.allocator)); + } + + CreatePipeline(v.device, v.allocator, v.pipeline_cache, nullptr, &bd->pipeline, v.subpass); + + if (bd->command_pool == VK_NULL_HANDLE) { + vk::CommandPoolCreateInfo info{ + .sType = vk::StructureType::eCommandPoolCreateInfo, + .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, + .queueFamilyIndex = v.queue_family, + }; + std::unique_lock lk(bd->command_pool_mutex); + bd->command_pool = CheckVkResult(v.device.createCommandPool(info, v.allocator)); + } + + if (!bd->simple_sampler) { + // Bilinear sampling is required by default. Set 'io.Fonts->Flags |= + // ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow + // point/nearest sampling. + vk::SamplerCreateInfo info{ + .sType = vk::StructureType::eSamplerCreateInfo, + .magFilter = vk::Filter::eLinear, + .minFilter = vk::Filter::eLinear, + .mipmapMode = vk::SamplerMipmapMode::eLinear, + .addressModeU = vk::SamplerAddressMode::eRepeat, + .addressModeV = vk::SamplerAddressMode::eRepeat, + .addressModeW = vk::SamplerAddressMode::eRepeat, + .maxAnisotropy = 1.0f, + .minLod = -1000, + .maxLod = 1000, + }; + bd->simple_sampler = CheckVkResult(v.device.createSampler(info, v.allocator)); + } + + return true; +} + +void ImGuiImplVulkanDestroyDeviceObjects() { + VkData* bd = GetBackendData(); + const InitInfo& v = bd->init_info; + DestroyWindowRenderBuffers(v.device, bd->render_buffers, v.allocator); + DestroyFontsTexture(); + + if (bd->font_command_buffer) { + std::unique_lock lk(bd->command_pool_mutex); + v.device.freeCommandBuffers(bd->command_pool, {bd->font_command_buffer}); + bd->font_command_buffer = VK_NULL_HANDLE; + } + if (bd->command_pool) { + std::unique_lock lk(bd->command_pool_mutex); + v.device.destroyCommandPool(bd->command_pool, v.allocator); + bd->command_pool = VK_NULL_HANDLE; + } + if (bd->shader_module_vert) { + v.device.destroyShaderModule(bd->shader_module_vert, v.allocator); + bd->shader_module_vert = VK_NULL_HANDLE; + } + if (bd->shader_module_frag) { + v.device.destroyShaderModule(bd->shader_module_frag, v.allocator); + bd->shader_module_frag = VK_NULL_HANDLE; + } + if (bd->simple_sampler) { + v.device.destroySampler(bd->simple_sampler, v.allocator); + bd->simple_sampler = VK_NULL_HANDLE; + } + if (bd->descriptor_set_layout) { + v.device.destroyDescriptorSetLayout(bd->descriptor_set_layout, v.allocator); + bd->descriptor_set_layout = VK_NULL_HANDLE; + } + if (bd->pipeline_layout) { + v.device.destroyPipelineLayout(bd->pipeline_layout, v.allocator); + bd->pipeline_layout = VK_NULL_HANDLE; + } + if (bd->pipeline) { + v.device.destroyPipeline(bd->pipeline, v.allocator); + bd->pipeline = VK_NULL_HANDLE; + } +} + +bool Init(InitInfo info) { + + IM_ASSERT(info.instance != VK_NULL_HANDLE); + IM_ASSERT(info.physical_device != VK_NULL_HANDLE); + IM_ASSERT(info.device != VK_NULL_HANDLE); + IM_ASSERT(info.image_count >= 2); + + ImGuiIO& io = ImGui::GetIO(); + IMGUI_CHECKVERSION(); + IM_ASSERT(io.BackendRendererUserData == nullptr && "Already initialized a renderer backend!"); + + // Setup backend capabilities flags + auto* bd = IM_NEW(VkData)(info); + io.BackendRendererUserData = (void*)bd; + io.BackendRendererName = "imgui_impl_vulkan_shadps4"; + // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. + io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; + + CreateDeviceObjects(); + CreateFontsTexture(); + + return true; +} + +void Shutdown() { + VkData* bd = GetBackendData(); + IM_ASSERT(bd != nullptr && "No renderer backend to shutdown, or already shutdown?"); + ImGuiIO& io = ImGui::GetIO(); + + ImGuiImplVulkanDestroyDeviceObjects(); + io.BackendRendererName = nullptr; + io.BackendRendererUserData = nullptr; + io.BackendFlags &= ~ImGuiBackendFlags_RendererHasVtxOffset; + IM_DELETE(bd); +} + +} // namespace ImGui::Vulkan diff --git a/src/imgui/renderer/imgui_impl_vulkan.h b/src/imgui/renderer/imgui_impl_vulkan.h new file mode 100644 index 00000000000..ca76fda6d9b --- /dev/null +++ b/src/imgui/renderer/imgui_impl_vulkan.h @@ -0,0 +1,63 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +// Based on imgui_impl_vulkan.h from Dear ImGui repository + +#pragma once + +#define VULKAN_HPP_NO_EXCEPTIONS +#include "common/types.h" +#include "video_core/renderer_vulkan/vk_common.h" + +struct ImDrawData; + +namespace ImGui::Vulkan { + +struct InitInfo { + vk::Instance instance; + vk::PhysicalDevice physical_device; + vk::Device device; + uint32_t queue_family; + vk::Queue queue; + uint32_t image_count; // >= 2 + vk::DeviceSize min_allocation_size; // Minimum allocation size + vk::PipelineCache pipeline_cache; + uint32_t subpass; + vk::PipelineRenderingCreateInfoKHR pipeline_rendering_create_info; + + // (Optional) Allocation, Logging + const vk::AllocationCallbacks* allocator{}; + void (*check_vk_result_fn)(vk::Result err); +}; + +// Prepare all resources needed for uploading textures +// Caller should clean up the returned data. +struct UploadTextureData { + vk::Image image; + vk::ImageView image_view; + vk::DescriptorSet descriptor_set; + vk::DeviceMemory image_memory; + + vk::CommandBuffer command_buffer; // Submit to the queue + vk::Buffer upload_buffer; + vk::DeviceMemory upload_buffer_memory; + + void Upload(); + + void Destroy(); +}; + +vk::DescriptorSet AddTexture(vk::ImageView image_view, vk::ImageLayout image_layout, + vk::Sampler sampler = VK_NULL_HANDLE); + +UploadTextureData UploadTexture(const void* data, vk::Format format, u32 width, u32 height, + size_t size); + +void RemoveTexture(vk::DescriptorSet descriptor_set); + +bool Init(InitInfo info); +void Shutdown(); +void RenderDrawData(ImDrawData& draw_data, vk::CommandBuffer command_buffer, + vk::Pipeline pipeline = VK_NULL_HANDLE); + +} // namespace ImGui::Vulkan \ No newline at end of file diff --git a/src/imgui/renderer/texture_manager.cpp b/src/imgui/renderer/texture_manager.cpp new file mode 100644 index 00000000000..ba4a05d015c --- /dev/null +++ b/src/imgui/renderer/texture_manager.cpp @@ -0,0 +1,243 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include + +#include + +#include "common/assert.h" +#include "common/io_file.h" +#include "common/polyfill_thread.h" +#include "imgui_impl_vulkan.h" +#include "texture_manager.h" + +namespace ImGui { + +namespace Core::TextureManager { +struct Inner { + std::atomic_int count = 0; + ImTextureID texture_id = nullptr; + u32 width = 0; + u32 height = 0; + + Vulkan::UploadTextureData upload_data; + + ~Inner(); +}; +} // namespace Core::TextureManager + +using namespace Core::TextureManager; + +RefCountedTexture::RefCountedTexture(Inner* inner) : inner(inner) { + ++inner->count; +} + +RefCountedTexture RefCountedTexture::DecodePngTexture(std::vector data) { + const auto core = new Inner; + Core::TextureManager::DecodePngTexture(std::move(data), core); + return RefCountedTexture(core); +} + +RefCountedTexture RefCountedTexture::DecodePngFile(std::filesystem::path path) { + const auto core = new Inner; + Core::TextureManager::DecodePngFile(std::move(path), core); + return RefCountedTexture(core); +} + +RefCountedTexture::RefCountedTexture() : inner(nullptr) {} + +RefCountedTexture::RefCountedTexture(const RefCountedTexture& other) : inner(other.inner) { + if (inner != nullptr) { + ++inner->count; + } +} + +RefCountedTexture::RefCountedTexture(RefCountedTexture&& other) noexcept : inner(other.inner) { + other.inner = nullptr; +} + +RefCountedTexture& RefCountedTexture::operator=(const RefCountedTexture& other) { + if (this == &other) + return *this; + inner = other.inner; + if (inner != nullptr) { + ++inner->count; + } + return *this; +} + +RefCountedTexture& RefCountedTexture::operator=(RefCountedTexture&& other) noexcept { + if (this == &other) + return *this; + std::swap(inner, other.inner); + return *this; +} + +RefCountedTexture::~RefCountedTexture() { + if (inner != nullptr) { + if (inner->count.fetch_sub(1) == 1) { + delete inner; + } + } +} +RefCountedTexture::Image RefCountedTexture::GetTexture() const { + if (inner == nullptr) { + return {}; + } + return Image{ + .im_id = inner->texture_id, + .width = inner->width, + .height = inner->height, + }; +} +RefCountedTexture::operator bool() const { + return inner != nullptr && inner->texture_id != nullptr; +} + +struct Job { + Inner* core; + std::vector data; + std::filesystem::path path; +}; + +struct UploadJob { + Inner* core = nullptr; + Vulkan::UploadTextureData data; + int tick = 0; // Used to skip the first frame when destroying to await the current frame to draw +}; + +static bool g_is_worker_running = false; +static std::jthread g_worker_thread; +static std::condition_variable g_worker_cv; + +static std::mutex g_job_list_mtx; +static std::deque g_job_list; + +static std::mutex g_upload_mtx; +static std::deque g_upload_list; + +namespace Core::TextureManager { + +Inner::~Inner() { + if (upload_data.descriptor_set != nullptr) { + std::unique_lock lk{g_upload_mtx}; + g_upload_list.emplace_back(UploadJob{ + .data = this->upload_data, + .tick = 2, + }); + } +} + +void WorkerLoop() { + std::mutex mtx; + while (g_is_worker_running) { + std::unique_lock lk{mtx}; + g_worker_cv.wait(lk); + if (!g_is_worker_running) { + break; + } + while (true) { + g_job_list_mtx.lock(); + if (g_job_list.empty()) { + g_job_list_mtx.unlock(); + break; + } + auto [core, png_raw, path] = std::move(g_job_list.front()); + g_job_list.pop_front(); + g_job_list_mtx.unlock(); + + if (!path.empty()) { // Decode PNG from file + Common::FS::IOFile file(path, Common::FS::FileAccessMode::Read); + if (!file.IsOpen()) { + LOG_ERROR(ImGui, "Failed to open PNG file: {}", path.string()); + continue; + } + png_raw.resize(file.GetSize()); + file.Seek(0); + file.ReadRaw(png_raw.data(), png_raw.size()); + file.Close(); + } + + int width, height; + const stbi_uc* pixels = + stbi_load_from_memory(png_raw.data(), png_raw.size(), &width, &height, nullptr, 4); + + auto texture = Vulkan::UploadTexture(pixels, vk::Format::eR8G8B8A8Unorm, width, height, + width * height * 4 * sizeof(stbi_uc)); + + core->upload_data = texture; + core->width = width; + core->height = height; + + std::unique_lock upload_lk{g_upload_mtx}; + g_upload_list.emplace_back(UploadJob{ + .core = core, + }); + } + } +} + +void StartWorker() { + ASSERT(!g_is_worker_running); + g_worker_thread = std::jthread(WorkerLoop); + g_is_worker_running = true; +} + +void StopWorker() { + ASSERT(g_is_worker_running); + g_is_worker_running = false; + g_worker_cv.notify_one(); +} + +void DecodePngTexture(std::vector data, Inner* core) { + ++core->count; + Job job{ + .core = core, + .data = std::move(data), + }; + std::unique_lock lk{g_job_list_mtx}; + g_job_list.push_back(std::move(job)); + g_worker_cv.notify_one(); +} + +void DecodePngFile(std::filesystem::path path, Inner* core) { + ++core->count; + Job job{ + .core = core, + .path = std::move(path), + }; + std::unique_lock lk{g_job_list_mtx}; + g_job_list.push_back(std::move(job)); + g_worker_cv.notify_one(); +} + +void Submit() { + UploadJob upload; + { + std::unique_lock lk{g_upload_mtx}; + if (g_upload_list.empty()) { + return; + } + // Upload one texture at a time to avoid slow down + upload = g_upload_list.front(); + g_upload_list.pop_front(); + if (upload.tick > 0) { + --upload.tick; + g_upload_list.emplace_back(upload); + return; + } + } + if (upload.core != nullptr) { + upload.core->upload_data.Upload(); + upload.core->texture_id = upload.core->upload_data.descriptor_set; + if (upload.core->count.fetch_sub(1) == 1) { + delete upload.core; + } + } else { + upload.data.Destroy(); + } +} +} // namespace Core::TextureManager + +} // namespace ImGui \ No newline at end of file diff --git a/src/imgui/renderer/texture_manager.h b/src/imgui/renderer/texture_manager.h new file mode 100644 index 00000000000..4fa7b99241c --- /dev/null +++ b/src/imgui/renderer/texture_manager.h @@ -0,0 +1,30 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +#include "common/types.h" +#include "imgui/imgui_texture.h" + +namespace vk { +class CommandBuffer; +} + +namespace ImGui::Core::TextureManager { + +struct Inner; + +void StartWorker(); + +void StopWorker(); + +void DecodePngTexture(std::vector data, Inner* core); + +void DecodePngFile(std::filesystem::path path, Inner* core); + +void Submit(); + +}; // namespace ImGui::Core::TextureManager \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 9df14f13844..cea92be07b5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include +#include "common/memory_patcher.h" #include "emulator.h" int main(int argc, char* argv[]) { @@ -9,8 +10,22 @@ int main(int argc, char* argv[]) { fmt::print("Usage: {} \n", argv[0]); return -1; } + // check if eboot file exists + if (!std::filesystem::exists(argv[1])) { + fmt::print("Eboot.bin file not found\n"); + return -1; + } + + for (int i = 0; i < argc; i++) { + std::string curArg = argv[i]; + if (curArg == "-p") { + std::string patchFile = argv[i + 1]; + MemoryPatcher::patchFile = patchFile; + } + } Core::Emulator emulator; emulator.Run(argv[1]); + return 0; } diff --git a/src/qt_gui/cheats_patches.cpp b/src/qt_gui/cheats_patches.cpp index fd0bc4e14f3..a58106cb310 100644 --- a/src/qt_gui/cheats_patches.cpp +++ b/src/qt_gui/cheats_patches.cpp @@ -28,9 +28,9 @@ #include #include #include "cheats_patches.h" +#include "common/memory_patcher.h" #include "common/path_util.h" #include "core/module.h" -#include "qt_gui/memory_patcher.h" using namespace Common::FS; @@ -147,13 +147,13 @@ void CheatsPatches::setupUI() { controlLayout->addWidget(downloadComboBox); QPushButton* downloadButton = new QPushButton(tr("Download Cheats")); - connect(downloadButton, &QPushButton::clicked, [=]() { + connect(downloadButton, &QPushButton::clicked, [this, downloadComboBox]() { QString source = downloadComboBox->currentData().toString(); downloadCheats(source, m_gameSerial, m_gameVersion, true); }); QPushButton* deleteCheatButton = new QPushButton(tr("Delete File")); - connect(deleteCheatButton, &QPushButton::clicked, [=]() { + connect(deleteCheatButton, &QPushButton::clicked, [this, CHEATS_DIR_QString]() { QStringListModel* model = qobject_cast(listView_selectFile->model()); if (!model) { return; @@ -232,7 +232,7 @@ void CheatsPatches::setupUI() { patchesControlLayout->addWidget(patchesComboBox); QPushButton* patchesButton = new QPushButton(tr("Download Patches")); - connect(patchesButton, &QPushButton::clicked, [=]() { + connect(patchesButton, &QPushButton::clicked, [this]() { QString selectedOption = patchesComboBox->currentData().toString(); downloadPatches(selectedOption, true); }); @@ -444,8 +444,8 @@ QCheckBox* CheatsPatches::findCheckBoxByName(const QString& name) { return nullptr; } -void CheatsPatches::downloadCheats(const QString& source, const QString& m_gameSerial, - const QString& m_gameVersion, const bool showMessageBox) { +void CheatsPatches::downloadCheats(const QString& source, const QString& gameSerial, + const QString& gameVersion, const bool showMessageBox) { QDir dir(Common::FS::GetUserPath(Common::FS::PathType::CheatsDir)); if (!dir.exists()) { dir.mkpath("."); @@ -455,7 +455,7 @@ void CheatsPatches::downloadCheats(const QString& source, const QString& m_gameS if (source == "GoldHEN") { url = "https://raw.githubusercontent.com/GoldHEN/GoldHEN_Cheat_Repository/main/json.txt"; } else if (source == "wolf2022") { - url = "https://wolf2022.ir/trainer/" + m_gameSerial + "_" + m_gameVersion + ".json"; + url = "https://wolf2022.ir/trainer/" + gameSerial + "_" + gameVersion + ".json"; } else if (source == "shadPS4") { url = "https://raw.githubusercontent.com/shadps4-emu/ps4_cheats/main/" "CHEATS_JSON.txt"; @@ -468,7 +468,7 @@ void CheatsPatches::downloadCheats(const QString& source, const QString& m_gameS QNetworkRequest request(url); QNetworkReply* reply = manager->get(request); - connect(reply, &QNetworkReply::finished, [=]() { + connect(reply, &QNetworkReply::finished, [=, this]() { if (reply->error() == QNetworkReply::NoError) { QByteArray jsonData = reply->readAll(); bool foundFiles = false; @@ -476,7 +476,7 @@ void CheatsPatches::downloadCheats(const QString& source, const QString& m_gameS if (source == "GoldHEN" || source == "shadPS4") { QString textContent(jsonData); QRegularExpression regex( - QString("%1_%2[^=]*\.json").arg(m_gameSerial).arg(m_gameVersion)); + QString("%1_%2[^=]*\\.json").arg(gameSerial).arg(gameVersion)); QRegularExpressionMatchIterator matches = regex.globalMatch(textContent); QString baseUrl; @@ -519,7 +519,7 @@ void CheatsPatches::downloadCheats(const QString& source, const QString& m_gameS QNetworkRequest fileRequest(fileUrl); QNetworkReply* fileReply = manager->get(fileRequest); - connect(fileReply, &QNetworkReply::finished, [=]() { + connect(fileReply, &QNetworkReply::finished, [=, this]() { if (fileReply->error() == QNetworkReply::NoError) { QByteArray fileData = fileReply->readAll(); QFile localFile(localFilePath); @@ -669,105 +669,86 @@ void CheatsPatches::populateFileListPatches() { void CheatsPatches::downloadPatches(const QString repository, const bool showMessageBox) { QString url; if (repository == "GoldHEN") { - url = "https://github.com/GoldHEN/GoldHEN_Patch_Repository/tree/main/" - "patches/xml"; + url = "https://api.github.com/repos/illusion0001/PS4-PS5-Game-Patch/contents/patches/xml"; } if (repository == "shadPS4") { - url = "https://github.com/shadps4-emu/ps4_cheats/tree/main/" - "PATCHES"; + url = "https://api.github.com/repos/shadps4-emu/ps4_cheats/contents/PATCHES"; } QNetworkAccessManager* manager = new QNetworkAccessManager(this); QNetworkRequest request(url); + request.setRawHeader("Accept", "application/vnd.github.v3+json"); QNetworkReply* reply = manager->get(request); connect(reply, &QNetworkReply::finished, [=]() { if (reply->error() == QNetworkReply::NoError) { - QByteArray htmlData = reply->readAll(); + QByteArray jsonData = reply->readAll(); reply->deleteLater(); - // Parsear HTML e extrair JSON usando QRegularExpression - QString htmlString = QString::fromUtf8(htmlData); - QRegularExpression jsonRegex( - R"()"); - QRegularExpressionMatch match = jsonRegex.match(htmlString); - - if (match.hasMatch()) { - QByteArray jsonData = match.captured(1).toUtf8(); - QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData); - QJsonObject jsonObj = jsonDoc.object(); - QJsonArray itemsArray = - jsonObj["payload"].toObject()["tree"].toObject()["items"].toArray(); - - QDir dir(Common::FS::GetUserPath(Common::FS::PathType::PatchesDir)); - QString fullPath = dir.filePath(repository); - if (!dir.exists(fullPath)) { - dir.mkpath(fullPath); + QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData); + QJsonArray itemsArray = jsonDoc.array(); + + if (itemsArray.isEmpty()) { + if (showMessageBox) { + QMessageBox::warning(this, tr("Error"), + tr("Failed to parse JSON data from HTML.")); } - dir.setPath(fullPath); - - foreach (const QJsonValue& value, itemsArray) { - QJsonObject fileObj = value.toObject(); - QString fileName = fileObj["name"].toString(); - QString filePath = fileObj["path"].toString(); - - if (fileName.endsWith(".xml")) { - QString fileUrl; - if (repository == "GoldHEN") { - fileUrl = QString("https://raw.githubusercontent.com/GoldHEN/" - "GoldHEN_Patch_Repository/main/%1") - .arg(filePath); - } - if (repository == "shadPS4") { - fileUrl = QString("https://raw.githubusercontent.com/shadps4-emu/" - "ps4_cheats/main/%1") - .arg(filePath); - } - QNetworkRequest fileRequest(fileUrl); - QNetworkReply* fileReply = manager->get(fileRequest); + return; + } - connect(fileReply, &QNetworkReply::finished, [=]() { - if (fileReply->error() == QNetworkReply::NoError) { - QByteArray fileData = fileReply->readAll(); - QFile localFile(dir.filePath(fileName)); - if (localFile.open(QIODevice::WriteOnly)) { - localFile.write(fileData); - localFile.close(); - } else { - if (showMessageBox) { - QMessageBox::warning( - this, tr("Error"), - QString(tr("Failed to save:") + "\n%1").arg(fileName)); - } - } + QDir dir(Common::FS::GetUserPath(Common::FS::PathType::PatchesDir)); + QString fullPath = dir.filePath(repository); + if (!dir.exists(fullPath)) { + dir.mkpath(fullPath); + } + dir.setPath(fullPath); + + foreach (const QJsonValue& value, itemsArray) { + QJsonObject fileObj = value.toObject(); + QString fileName = fileObj["name"].toString(); + QString filePath = fileObj["path"].toString(); + QString downloadUrl = fileObj["download_url"].toString(); + + if (fileName.endsWith(".xml")) { + QNetworkRequest fileRequest(downloadUrl); + QNetworkReply* fileReply = manager->get(fileRequest); + + connect(fileReply, &QNetworkReply::finished, [=]() { + if (fileReply->error() == QNetworkReply::NoError) { + QByteArray fileData = fileReply->readAll(); + QFile localFile(dir.filePath(fileName)); + if (localFile.open(QIODevice::WriteOnly)) { + localFile.write(fileData); + localFile.close(); } else { if (showMessageBox) { QMessageBox::warning( this, tr("Error"), - QString(tr("Failed to download:") + "\n%1").arg(fileUrl)); + QString(tr("Failed to save:") + "\n%1").arg(fileName)); } } - fileReply->deleteLater(); - }); - } - } - if (showMessageBox) { - QMessageBox::information(this, tr("Download Complete"), - QString(tr("DownloadComplete_MSG"))); - } - - // Create the files.json file with the identification of which file to open - createFilesJson(repository); - populateFileListPatches(); - - } else { - if (showMessageBox) { - QMessageBox::warning(this, tr("Error"), - tr("Failed to parse JSON data from HTML.")); + } else { + if (showMessageBox) { + QMessageBox::warning( + this, tr("Error"), + QString(tr("Failed to download:") + "\n%1").arg(downloadUrl)); + } + } + fileReply->deleteLater(); + }); } } + if (showMessageBox) { + QMessageBox::information(this, tr("Download Complete"), + QString(tr("DownloadComplete_MSG"))); + } + // Create the files.json file with the identification of which file to open + createFilesJson(repository); + populateFileListPatches(); } else { if (showMessageBox) { - QMessageBox::warning(this, tr("Error"), tr("Failed to retrieve HTML page.")); + QMessageBox::warning(this, tr("Error"), + QString(tr("Failed to retrieve HTML page.") + "\n%1") + .arg(reply->errorString())); } } emit downloadFinished(); @@ -864,7 +845,7 @@ void CheatsPatches::addCheatsToLayout(const QJsonArray& modsArray, const QJsonAr rightLayout->addWidget(cheatCheckBox); m_cheatCheckBoxes.append(cheatCheckBox); connect(cheatCheckBox, &QCheckBox::toggled, - [=](bool checked) { applyCheat(modName, checked); }); + [this, modName](bool checked) { applyCheat(modName, checked); }); } else if (modType == "button") { QPushButton* cheatButton = new QPushButton(modName); cheatButton->adjustSize(); @@ -880,7 +861,8 @@ void CheatsPatches::addCheatsToLayout(const QJsonArray& modsArray, const QJsonAr buttonLayout->addStretch(); rightLayout->addLayout(buttonLayout); - connect(cheatButton, &QPushButton::clicked, [=]() { applyCheat(modName, true); }); + connect(cheatButton, &QPushButton::clicked, + [this, modName]() { applyCheat(modName, true); }); } } @@ -1093,7 +1075,7 @@ void CheatsPatches::addPatchesToLayout(const QString& filePath) { patchCheckBox->installEventFilter(this); connect(patchCheckBox, &QCheckBox::toggled, - [=](bool checked) { applyPatch(patchName, checked); }); + [this, patchName](bool checked) { applyPatch(patchName, checked); }); patchName.clear(); patchAuthor.clear(); @@ -1158,6 +1140,13 @@ void CheatsPatches::applyCheat(const QString& modName, bool enabled) { if (!m_cheats.contains(modName)) return; + if (MemoryPatcher::g_eboot_address == 0 && enabled) { + QMessageBox::critical(this, tr("Error"), + tr("Can't apply cheats before the game is started")); + uncheckAllCheatCheckBoxes(); + return; + } + Cheat cheat = m_cheats[modName]; for (const MemoryMod& memoryMod : cheat.memoryMods) { @@ -1167,16 +1156,9 @@ void CheatsPatches::applyCheat(const QString& modName, bool enabled) { std::string offsetStr = memoryMod.offset.toStdString(); std::string valueStr = value.toStdString(); - if (MemoryPatcher::g_eboot_address == 0) { - MemoryPatcher::patchInfo addingPatch; - addingPatch.modNameStr = modNameStr; - addingPatch.offsetStr = offsetStr; - addingPatch.valueStr = valueStr; - addingPatch.isOffset = true; + if (MemoryPatcher::g_eboot_address == 0) + return; - MemoryPatcher::AddPatchToQueue(addingPatch); - continue; - } // Determine if the hint field is present bool isHintPresent = m_cheats[modName].hasHint; MemoryPatcher::PatchMemory(modNameStr, offsetStr, valueStr, !isHintPresent, false); @@ -1196,7 +1178,8 @@ void CheatsPatches::applyPatch(const QString& patchName, bool enabled) { QString patchValue = lineObject["Value"].toString(); QString maskOffsetStr = lineObject["Offset"].toString(); - patchValue = MemoryPatcher::convertValueToHex(type, patchValue); + patchValue = QString::fromStdString( + MemoryPatcher::convertValueToHex(type.toStdString(), patchValue.toStdString())); bool littleEndian = false; diff --git a/src/qt_gui/game_grid_frame.cpp b/src/qt_gui/game_grid_frame.cpp index 3d5ab14ec81..9fba0c47cbe 100644 --- a/src/qt_gui/game_grid_frame.cpp +++ b/src/qt_gui/game_grid_frame.cpp @@ -114,8 +114,8 @@ void GameGridFrame::SetGridBackgroundImage(int row, int column) { QWidget* item = this->cellWidget(row, column); if (item) { QString pic1Path = QString::fromStdString((*m_games_shared)[itemID].pic_path); - const auto blurredPic1Path = Common::FS::GetUserPath(Common::FS::PathType::UserDir) / - "game_data" / (*m_games_shared)[itemID].serial / "pic1.png"; + const auto blurredPic1Path = Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / + (*m_games_shared)[itemID].serial / "pic1.png"; #ifdef _WIN32 const auto blurredPic1PathQt = QString::fromStdWString(blurredPic1Path.wstring()); #else @@ -128,7 +128,8 @@ void GameGridFrame::SetGridBackgroundImage(int row, int column) { backgroundImage = m_game_list_utils.BlurImage(image, image.rect(), 16); std::filesystem::path img_path = - std::filesystem::path("user/game_data/") / (*m_games_shared)[itemID].serial; + Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / + (*m_games_shared)[itemID].serial; std::filesystem::create_directories(img_path); if (!backgroundImage.save(blurredPic1PathQt, "PNG")) { // qDebug() << "Error: Unable to save image."; diff --git a/src/qt_gui/game_info.h b/src/qt_gui/game_info.h index ae02114a23f..a4bcd20ee27 100644 --- a/src/qt_gui/game_info.h +++ b/src/qt_gui/game_info.h @@ -27,21 +27,36 @@ class GameInfoClass : public QObject { game.path = filePath; PSF psf; - if (psf.open(game.path + "/sce_sys/param.sfo", {})) { + if (psf.Open(std::filesystem::path(game.path) / "sce_sys" / "param.sfo")) { game.icon_path = game.path + "/sce_sys/icon0.png"; QString iconpath = QString::fromStdString(game.icon_path); game.icon = QImage(iconpath); game.pic_path = game.path + "/sce_sys/pic1.png"; - game.name = psf.GetString("TITLE"); - game.serial = psf.GetString("TITLE_ID"); - game.region = GameListUtils::GetRegion(psf.GetString("CONTENT_ID").at(0)).toStdString(); - u32 fw_int = psf.GetInteger("SYSTEM_VER"); - QString fw = QString::number(fw_int, 16); - QString fw_ = fw.length() > 7 ? QString::number(fw_int, 16).left(3).insert(2, '.') - : fw.left(3).insert(1, '.'); - game.fw = (fw_int == 0) ? "0.00" : fw_.toStdString(); - game.version = psf.GetString("APP_VER"); - game.category = psf.GetString("CATEGORY"); + if (const auto title = psf.GetString("TITLE"); title.has_value()) { + game.name = *title; + } + if (const auto title_id = psf.GetString("TITLE_ID"); title_id.has_value()) { + game.serial = *title_id; + } + if (const auto content_id = psf.GetString("CONTENT_ID"); + content_id.has_value() && !content_id->empty()) { + game.region = GameListUtils::GetRegion(content_id->at(0)).toStdString(); + } + if (const auto fw_int_opt = psf.GetInteger("SYSTEM_VER"); fw_int_opt.has_value()) { + auto fw_int = *fw_int_opt; + if (fw_int == 0) { + game.fw = "0.00"; + } else { + QString fw = QString::number(fw_int, 16); + QString fw_ = fw.length() > 7 + ? QString::number(fw_int, 16).left(3).insert(2, '.') + : fw.left(3).insert(1, '.'); + game.fw = fw_.toStdString(); + } + } + if (auto app_ver = psf.GetString("APP_VER"); app_ver.has_value()) { + game.version = *app_ver; + } } return game; } diff --git a/src/qt_gui/game_list_frame.cpp b/src/qt_gui/game_list_frame.cpp index 2699c961550..b17da127e82 100644 --- a/src/qt_gui/game_list_frame.cpp +++ b/src/qt_gui/game_list_frame.cpp @@ -23,24 +23,16 @@ GameListFrame::GameListFrame(std::shared_ptr game_info_get, QWidg this->horizontalHeader()->setSortIndicatorShown(true); this->horizontalHeader()->setStretchLastSection(true); this->setContextMenuPolicy(Qt::CustomContextMenu); - this->setColumnCount(9); - this->setColumnWidth(1, 250); - this->setColumnWidth(2, 110); - this->setColumnWidth(3, 80); - this->setColumnWidth(4, 90); - this->setColumnWidth(5, 80); - this->setColumnWidth(6, 80); - this->setColumnWidth(7, 80); + this->setColumnCount(8); + this->setColumnWidth(1, 300); // Name + this->setColumnWidth(2, 120); // Serial + this->setColumnWidth(3, 90); // Region + this->setColumnWidth(4, 90); // Firmware + this->setColumnWidth(5, 90); // Size + this->setColumnWidth(6, 90); // Version QStringList headers; - headers << "Icon" - << "Name" - << "Serial" - << "Region" - << "Firmware" - << "Size" - << "Version" - << "Category" - << "Path"; + headers << tr("Icon") << tr("Name") << tr("Serial") << tr("Region") << tr("Firmware") + << tr("Size") << tr("Version") << tr("Path"); this->setHorizontalHeaderLabels(headers); this->horizontalHeader()->setSortIndicatorShown(true); this->horizontalHeader()->setSectionResizeMode(0, QHeaderView::ResizeToContents); @@ -87,8 +79,7 @@ void GameListFrame::PopulateGameList() { SetTableItem(i, 4, QString::fromStdString(m_game_info->m_games[i].fw)); SetTableItem(i, 5, QString::fromStdString(m_game_info->m_games[i].size)); SetTableItem(i, 6, QString::fromStdString(m_game_info->m_games[i].version)); - SetTableItem(i, 7, QString::fromStdString(m_game_info->m_games[i].category)); - SetTableItem(i, 8, QString::fromStdString(m_game_info->m_games[i].path)); + SetTableItem(i, 7, QString::fromStdString(m_game_info->m_games[i].path)); } } @@ -99,9 +90,8 @@ void GameListFrame::SetListBackgroundImage(QTableWidgetItem* item) { } QString pic1Path = QString::fromStdString(m_game_info->m_games[item->row()].pic_path); - const auto blurredPic1Path = Common::FS::GetUserPath(Common::FS::PathType::UserDir) / - "game_data" / m_game_info->m_games[item->row()].serial / - "pic1.png"; + const auto blurredPic1Path = Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / + m_game_info->m_games[item->row()].serial / "pic1.png"; #ifdef _WIN32 const auto blurredPic1PathQt = QString::fromStdWString(blurredPic1Path.wstring()); #else @@ -114,7 +104,8 @@ void GameListFrame::SetListBackgroundImage(QTableWidgetItem* item) { backgroundImage = m_game_list_utils.BlurImage(image, image.rect(), 16); std::filesystem::path img_path = - std::filesystem::path("user/game_data/") / m_game_info->m_games[item->row()].serial; + Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / + m_game_info->m_games[item->row()].serial; std::filesystem::create_directories(img_path); if (!backgroundImage.save(blurredPic1PathQt, "PNG")) { // qDebug() << "Error: Unable to save image."; @@ -168,7 +159,7 @@ void GameListFrame::SetTableItem(int row, int column, QString itemStr) { QVBoxLayout* layout = new QVBoxLayout(widget); QLabel* label = new QLabel(itemStr, widget); - label->setStyleSheet("color: white; font-size: 15px; font-weight: bold;"); + label->setStyleSheet("color: white; font-size: 16px; font-weight: bold;"); // Create shadow effect QGraphicsDropShadowEffect* shadowEffect = new QGraphicsDropShadowEffect(); diff --git a/src/qt_gui/game_list_utils.h b/src/qt_gui/game_list_utils.h index 7911ce46f1b..476c900359d 100644 --- a/src/qt_gui/game_list_utils.h +++ b/src/qt_gui/game_list_utils.h @@ -14,7 +14,6 @@ struct GameInfo { std::string serial = "Unknown"; std::string version = "Unknown"; std::string region = "Unknown"; - std::string category = "Unknown"; std::string fw = "Unknown"; }; diff --git a/src/qt_gui/gui_context_menus.h b/src/qt_gui/gui_context_menus.h index fb1994bb0f8..a2f7f28ffe3 100644 --- a/src/qt_gui/gui_context_menus.h +++ b/src/qt_gui/gui_context_menus.h @@ -80,8 +80,8 @@ class GuiContextMenus : public QObject { if (selected == &openSfoViewer) { PSF psf; - if (psf.open(m_games[itemID].path + "/sce_sys/param.sfo", {})) { - int rows = psf.map_strings.size() + psf.map_integers.size(); + if (psf.Open(std::filesystem::path(m_games[itemID].path) / "sce_sys" / "param.sfo")) { + int rows = psf.GetEntries().size(); QTableWidget* tableWidget = new QTableWidget(rows, 2); tableWidget->setAttribute(Qt::WA_DeleteOnClose); connect(widget->parent(), &QWidget::destroyed, tableWidget, @@ -90,23 +90,45 @@ class GuiContextMenus : public QObject { tableWidget->verticalHeader()->setVisible(false); // Hide vertical header int row = 0; - for (const auto& pair : psf.map_strings) { + for (const auto& entry : psf.GetEntries()) { QTableWidgetItem* keyItem = - new QTableWidgetItem(QString::fromStdString(pair.first)); - QTableWidgetItem* valueItem = - new QTableWidgetItem(QString::fromStdString(pair.second)); - - tableWidget->setItem(row, 0, keyItem); - tableWidget->setItem(row, 1, valueItem); - keyItem->setFlags(keyItem->flags() & ~Qt::ItemIsEditable); - valueItem->setFlags(valueItem->flags() & ~Qt::ItemIsEditable); - row++; - } - for (const auto& pair : psf.map_integers) { - QTableWidgetItem* keyItem = - new QTableWidgetItem(QString::fromStdString(pair.first)); - QTableWidgetItem* valueItem = new QTableWidgetItem( - QString("0x").append(QString::number(pair.second, 16))); + new QTableWidgetItem(QString::fromStdString(entry.key)); + QTableWidgetItem* valueItem; + switch (entry.param_fmt) { + case PSFEntryFmt::Binary: { + const auto bin = psf.GetBinary(entry.key); + if (!bin.has_value()) { + valueItem = new QTableWidgetItem(QString("Unknown")); + } else { + std::string text; + text.reserve(bin->size() * 2); + for (const auto& c : *bin) { + static constexpr char hex[] = "0123456789ABCDEF"; + text.push_back(hex[c >> 4 & 0xF]); + text.push_back(hex[c & 0xF]); + } + valueItem = new QTableWidgetItem(QString::fromStdString(text)); + } + } break; + case PSFEntryFmt::Text: { + auto text = psf.GetString(entry.key); + if (!text.has_value()) { + valueItem = new QTableWidgetItem(QString("Unknown")); + } else { + valueItem = + new QTableWidgetItem(QString::fromStdString(std::string{*text})); + } + } break; + case PSFEntryFmt::Integer: { + auto integer = psf.GetInteger(entry.key); + if (!integer.has_value()) { + valueItem = new QTableWidgetItem(QString("Unknown")); + } else { + valueItem = + new QTableWidgetItem(QString("0x") + QString::number(*integer, 16)); + } + } break; + } tableWidget->setItem(row, 0, keyItem); tableWidget->setItem(row, 1, valueItem); diff --git a/src/qt_gui/main.cpp b/src/qt_gui/main.cpp index 02957c6d536..68a674f5e7d 100644 --- a/src/qt_gui/main.cpp +++ b/src/qt_gui/main.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include "common/config.h" +#include "common/memory_patcher.h" #include "core/file_sys/fs.h" #include "emulator.h" #include "game_install_dialog.h" @@ -16,7 +17,6 @@ int main(int argc, char* argv[]) { // Load configurations and initialize Qt application const auto user_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir); Config::load(user_dir / "config.toml"); - std::filesystem::create_directory(user_dir / "game_data"); // Check if elf or eboot.bin path was passed as a command line argument bool has_command_line_argument = argc > 1; @@ -37,6 +37,13 @@ int main(int argc, char* argv[]) { // Check for command line arguments if (has_command_line_argument) { Core::Emulator emulator; + for (int i = 0; i < argc; i++) { + std::string curArg = argv[i]; + if (curArg == "-p") { + std::string patchFile = argv[i + 1]; + MemoryPatcher::patchFile = patchFile; + } + } emulator.Run(argv[1]); } diff --git a/src/qt_gui/main_window.cpp b/src/qt_gui/main_window.cpp index 988e01a5056..c6036066505 100644 --- a/src/qt_gui/main_window.cpp +++ b/src/qt_gui/main_window.cpp @@ -34,12 +34,12 @@ bool MainWindow::Init() { CreateActions(); CreateRecentGameActions(); ConfigureGuiFromSettings(); + LoadTranslation(); CreateDockWindows(); CreateConnects(); SetLastUsedTheme(); SetLastIconSizeBullet(); GetPhysicalDevices(); - LoadTranslation(); // show ui setMinimumSize(350, minimumSizeHint().height()); setWindowTitle(QString::fromStdString("shadPS4 v" + std::string(Common::VERSION))); @@ -88,6 +88,7 @@ void MainWindow::AddUiWidgets() { ui->toolBar->addWidget(ui->playButton); ui->toolBar->addWidget(ui->pauseButton); ui->toolBar->addWidget(ui->stopButton); + ui->toolBar->addWidget(ui->refreshButton); ui->toolBar->addWidget(ui->settingsButton); ui->toolBar->addWidget(ui->controllerButton); QFrame* line = new QFrame(this); @@ -103,7 +104,7 @@ void MainWindow::CreateDockWindows() { QWidget* phCentralWidget = new QWidget(this); setCentralWidget(phCentralWidget); - m_dock_widget.reset(new QDockWidget("Game List", this)); + m_dock_widget.reset(new QDockWidget(tr("Game List"), this)); m_game_list_frame.reset(new GameListFrame(m_game_info, this)); m_game_list_frame->setObjectName("gamelist"); m_game_grid_frame.reset(new GameGridFrame(m_game_info, this)); @@ -177,6 +178,7 @@ void MainWindow::CreateConnects() { connect(ui->mw_searchbar, &QLineEdit::textChanged, this, &MainWindow::SearchGameTable); connect(ui->exitAct, &QAction::triggered, this, &QWidget::close); connect(ui->refreshGameListAct, &QAction::triggered, this, &MainWindow::RefreshGameTable); + connect(ui->refreshButton, &QPushButton::clicked, this, &MainWindow::RefreshGameTable); connect(ui->showGameListAct, &QAction::triggered, this, &MainWindow::ShowGameList); connect(this, &MainWindow::ExtractionFinished, this, &MainWindow::RefreshGameTable); @@ -507,6 +509,10 @@ void MainWindow::StartGame() { if (gamePath != "") { AddRecentFiles(gamePath); Core::Emulator emulator; + if (!std::filesystem::exists(gamePath.toUtf8().constData())) { + QMessageBox::critical(nullptr, tr("Run Game"), QString(tr("Eboot.bin file not found"))); + return; + } emulator.Run(gamePath.toUtf8().constData()); } } @@ -575,7 +581,7 @@ void MainWindow::SaveWindowState() const { void MainWindow::InstallPkg() { QFileDialog dialog; dialog.setFileMode(QFileDialog::ExistingFiles); - dialog.setNameFilter(tr("PKG File (*.PKG)")); + dialog.setNameFilter(tr("PKG File (*.PKG *.pkg)")); if (dialog.exec()) { QStringList fileNames = dialog.selectedFiles(); int nPkg = fileNames.size(); @@ -608,6 +614,11 @@ void MainWindow::BootGame() { path = std::filesystem::path(fileNames[0].toStdWString()); #endif Core::Emulator emulator; + if (!std::filesystem::exists(path)) { + QMessageBox::critical(nullptr, tr("Run Game"), + QString(tr("Eboot.bin file not found"))); + return; + } emulator.Run(path); } } @@ -625,9 +636,19 @@ void MainWindow::InstallDragDropPkg(std::filesystem::path file, int pkgNum, int QMessageBox msgBox; msgBox.setWindowTitle(tr("PKG Extraction")); - psf.open("", pkg.sfo); + if (!psf.Open(pkg.sfo)) { + QMessageBox::critical(this, tr("PKG ERROR"), + "Could not read SFO. Check log for details"); + return; + } - std::string content_id = psf.GetString("CONTENT_ID"); + std::string content_id; + if (auto value = psf.GetString("CONTENT_ID"); value.has_value()) { + content_id = std::string{*value}; + } else { + QMessageBox::critical(this, tr("PKG ERROR"), "PSF file there is no CONTENT_ID"); + return; + } std::string entitlement_label = Common::SplitString(content_id, '-')[2]; auto addon_extract_path = Common::FS::GetUserPath(Common::FS::PathType::AddonsDir) / @@ -636,9 +657,21 @@ void MainWindow::InstallDragDropPkg(std::filesystem::path file, int pkgNum, int auto category = psf.GetString("CATEGORY"); if (pkgType.contains("PATCH")) { - QString pkg_app_version = QString::fromStdString(psf.GetString("APP_VER")); - psf.open(extract_path.string() + "/sce_sys/param.sfo", {}); - QString game_app_version = QString::fromStdString(psf.GetString("APP_VER")); + QString pkg_app_version; + if (auto app_ver = psf.GetString("APP_VER"); app_ver.has_value()) { + pkg_app_version = QString::fromStdString(std::string{*app_ver}); + } else { + QMessageBox::critical(this, tr("PKG ERROR"), "PSF file there is no APP_VER"); + return; + } + psf.Open(extract_path / "sce_sys" / "param.sfo"); + QString game_app_version; + if (auto app_ver = psf.GetString("APP_VER"); app_ver.has_value()) { + game_app_version = QString::fromStdString(std::string{*app_ver}); + } else { + QMessageBox::critical(this, tr("PKG ERROR"), "PSF file there is no APP_VER"); + return; + } double appD = game_app_version.toDouble(); double pkgD = pkg_app_version.toDouble(); if (pkgD == appD) { @@ -852,6 +885,7 @@ void MainWindow::SetUiIcons(bool isWhite) { ui->playButton->setIcon(RecolorIcon(ui->playButton->icon(), isWhite)); ui->pauseButton->setIcon(RecolorIcon(ui->pauseButton->icon(), isWhite)); ui->stopButton->setIcon(RecolorIcon(ui->stopButton->icon(), isWhite)); + ui->refreshButton->setIcon(RecolorIcon(ui->refreshButton->icon(), isWhite)); ui->settingsButton->setIcon(RecolorIcon(ui->settingsButton->icon(), isWhite)); ui->controllerButton->setIcon(RecolorIcon(ui->controllerButton->icon(), isWhite)); ui->refreshGameListAct->setIcon(RecolorIcon(ui->refreshGameListAct->icon(), isWhite)); @@ -912,6 +946,10 @@ void MainWindow::CreateRecentGameActions() { QString gamePath = action->text(); AddRecentFiles(gamePath); // Update the list. Core::Emulator emulator; + if (!std::filesystem::exists(gamePath.toUtf8().constData())) { + QMessageBox::critical(nullptr, tr("Run Game"), QString(tr("Eboot.bin file not found"))); + return; + } emulator.Run(gamePath.toUtf8().constData()); }); } @@ -946,4 +984,4 @@ void MainWindow::OnLanguageChanged(const std::string& locale) { Config::setEmulatorLanguage(locale); LoadTranslation(); -} \ No newline at end of file +} diff --git a/src/qt_gui/main_window_themes.cpp b/src/qt_gui/main_window_themes.cpp index c89fa5a0099..35e64ef7417 100644 --- a/src/qt_gui/main_window_themes.cpp +++ b/src/qt_gui/main_window_themes.cpp @@ -8,13 +8,13 @@ void WindowThemes::SetWindowTheme(Theme theme, QLineEdit* mw_searchbar) { switch (theme) { case Theme::Dark: - mw_searchbar->setStyleSheet("background-color: #1e1e1e; /* Dark background */" - "color: #ffffff; /* White text */" - "border: 1px solid #ffffff; /* White border */" + mw_searchbar->setStyleSheet("background-color: #1e1e1e;" // Dark background + "color: #ffffff;" // White text + "border: 2px solid #ffffff;" // White border "padding: 5px;"); - themePalette.setColor(QPalette::Window, QColor(53, 53, 53)); + themePalette.setColor(QPalette::Window, QColor(50, 50, 50)); themePalette.setColor(QPalette::WindowText, Qt::white); - themePalette.setColor(QPalette::Base, QColor(25, 25, 25)); + themePalette.setColor(QPalette::Base, QColor(20, 20, 20)); themePalette.setColor(QPalette::AlternateBase, QColor(25, 25, 25)); themePalette.setColor(QPalette::AlternateBase, QColor(53, 53, 53)); themePalette.setColor(QPalette::ToolTipBase, Qt::white); @@ -30,8 +30,9 @@ void WindowThemes::SetWindowTheme(Theme theme, QLineEdit* mw_searchbar) { break; case Theme::Light: - mw_searchbar->setStyleSheet("background-color: #ffffff; /* Light gray background */" - "color: #000000; /* Black text */" + mw_searchbar->setStyleSheet("background-color: #ffffff;" // Light gray background + "color: #000000;" // Black text + "border: 2px solid #000000;" // Black border "padding: 5px;"); themePalette.setColor(QPalette::Window, QColor(240, 240, 240)); // Light gray themePalette.setColor(QPalette::WindowText, Qt::black); // Black @@ -49,9 +50,9 @@ void WindowThemes::SetWindowTheme(Theme theme, QLineEdit* mw_searchbar) { break; case Theme::Green: - mw_searchbar->setStyleSheet("background-color: #354535; /* Dark green background */" - "color: #ffffff; /* White text */" - "border: 1px solid #ffffff; /* White border */" + mw_searchbar->setStyleSheet("background-color: #1e1e1e;" // Dark background + "color: #ffffff;" // White text + "border: 2px solid #ffffff;" // White border "padding: 5px;"); themePalette.setColor(QPalette::Window, QColor(53, 69, 53)); // Dark green background themePalette.setColor(QPalette::WindowText, Qt::white); // White text @@ -72,9 +73,9 @@ void WindowThemes::SetWindowTheme(Theme theme, QLineEdit* mw_searchbar) { break; case Theme::Blue: - mw_searchbar->setStyleSheet("background-color: #283c5a; /* Dark blue background */" - "color: #ffffff; /* White text */" - "border: 1px solid #ffffff; /* White border */" + mw_searchbar->setStyleSheet("background-color: #1e1e1e;" // Dark background + "color: #ffffff;" // White text + "border: 2px solid #ffffff;" // White border "padding: 5px;"); themePalette.setColor(QPalette::Window, QColor(40, 60, 90)); // Dark blue background themePalette.setColor(QPalette::WindowText, Qt::white); // White text @@ -95,9 +96,9 @@ void WindowThemes::SetWindowTheme(Theme theme, QLineEdit* mw_searchbar) { break; case Theme::Violet: - mw_searchbar->setStyleSheet("background-color: #643278; /* Violet background */" - "color: #ffffff; /* White text */" - "border: 1px solid #ffffff; /* White border */" + mw_searchbar->setStyleSheet("background-color: #1e1e1e;" // Dark background + "color: #ffffff;" // White text + "border: 2px solid #ffffff;" // White border "padding: 5px;"); themePalette.setColor(QPalette::Window, QColor(100, 50, 120)); // Violet background themePalette.setColor(QPalette::WindowText, Qt::white); // White text diff --git a/src/qt_gui/main_window_ui.h b/src/qt_gui/main_window_ui.h index 498cec735c0..6ddc4155e91 100644 --- a/src/qt_gui/main_window_ui.h +++ b/src/qt_gui/main_window_ui.h @@ -7,8 +7,6 @@ #include #include -QT_BEGIN_NAMESPACE - class Ui_MainWindow { public: QAction* bootInstallPkgAct; @@ -40,6 +38,7 @@ class Ui_MainWindow { QPushButton* playButton; QPushButton* pauseButton; QPushButton* stopButton; + QPushButton* refreshButton; QPushButton* settingsButton; QPushButton* controllerButton; @@ -178,6 +177,10 @@ class Ui_MainWindow { stopButton->setFlat(true); stopButton->setIcon(QIcon(":images/stop_icon.png")); stopButton->setIconSize(QSize(40, 40)); + refreshButton = new QPushButton(centralWidget); + refreshButton->setFlat(true); + refreshButton->setIcon(QIcon(":images/refresh_icon.png")); + refreshButton->setIconSize(QSize(32, 32)); settingsButton = new QPushButton(centralWidget); settingsButton->setFlat(true); settingsButton->setIcon(QIcon(":images/settings_icon.png")); @@ -264,8 +267,8 @@ class Ui_MainWindow { menuView->addAction(menuGame_List_Mode->menuAction()); menuView->addAction(menuGame_List_Icons->menuAction()); menuView->addAction(menuThemes->menuAction()); - menuThemes->addAction(setThemeLight); menuThemes->addAction(setThemeDark); + menuThemes->addAction(setThemeLight); menuThemes->addAction(setThemeGreen); menuThemes->addAction(setThemeBlue); menuThemes->addAction(setThemeViolet); @@ -354,6 +357,4 @@ class Ui_MainWindow { namespace Ui { class MainWindow : public Ui_MainWindow {}; -} // namespace Ui - -QT_END_NAMESPACE \ No newline at end of file +} // namespace Ui \ No newline at end of file diff --git a/src/qt_gui/pkg_viewer.cpp b/src/qt_gui/pkg_viewer.cpp index 49005c720aa..8f20f6929b9 100644 --- a/src/qt_gui/pkg_viewer.cpp +++ b/src/qt_gui/pkg_viewer.cpp @@ -109,13 +109,17 @@ void PKGViewer::ProcessPKGInfo() { path = std::filesystem::path(m_pkg_list[i].toStdWString()); #endif package.Open(path); - psf.open("", package.sfo); - QString title_name = QString::fromStdString(psf.GetString("TITLE")); - QString title_id = QString::fromStdString(psf.GetString("TITLE_ID")); - QString app_type = game_list_util.GetAppType(psf.GetInteger("APP_TYPE")); - QString app_version = QString::fromStdString(psf.GetString("APP_VER")); - QString title_category = QString::fromStdString(psf.GetString("CATEGORY")); - QString pkg_size = game_list_util.FormatSize(package.GetPkgHeader().pkg_size); + psf.Open(package.sfo); + QString title_name = + QString::fromStdString(std::string{psf.GetString("TITLE").value_or("Unknown")}); + QString title_id = + QString::fromStdString(std::string{psf.GetString("TITLE_ID").value_or("Unknown")}); + QString app_type = GameListUtils::GetAppType(psf.GetInteger("APP_TYPE").value_or(0)); + QString app_version = + QString::fromStdString(std::string{psf.GetString("APP_VER").value_or("Unknown")}); + QString title_category = + QString::fromStdString(std::string{psf.GetString("CATEGORY").value_or("Unknown")}); + QString pkg_size = GameListUtils::FormatSize(package.GetPkgHeader().pkg_size); pkg_content_flag = package.GetPkgHeader().pkg_content_flags; QString flagss = ""; for (const auto& flag : package.flagNames) { @@ -126,11 +130,17 @@ void PKGViewer::ProcessPKGInfo() { } } - u32 fw_int = psf.GetInteger("SYSTEM_VER"); - QString fw = QString::number(fw_int, 16); - QString fw_ = fw.length() > 7 ? QString::number(fw_int, 16).left(3).insert(2, '.') + QString fw_ = "Unknown"; + if (const auto fw_int_opt = psf.GetInteger("SYSTEM_VER"); fw_int_opt.has_value()) { + const u32 fw_int = *fw_int_opt; + if (fw_int == 0) { + fw_ = "0.00"; + } else { + QString fw = QString::number(fw_int, 16); + fw_ = fw.length() > 7 ? QString::number(fw_int, 16).left(3).insert(2, '.') : fw.left(3).insert(1, '.'); - fw_ = (fw_int == 0) ? "0.00" : fw_; + } + } char region = package.GetPkgHeader().pkg_content_id[0]; QString pkg_info = ""; if (title_category == "gd" && !flagss.contains("PATCH")) { diff --git a/src/qt_gui/pkg_viewer.h b/src/qt_gui/pkg_viewer.h index 9598328a079..265a03b92d7 100644 --- a/src/qt_gui/pkg_viewer.h +++ b/src/qt_gui/pkg_viewer.h @@ -33,7 +33,6 @@ class PKGViewer : public QMainWindow { PKGHeader pkgheader; PKGEntry entry; PSFHeader header; - PSFEntry psfentry; char pkgTitleID[9]; std::vector pkg; u64 pkgSize = 0; diff --git a/src/qt_gui/settings_dialog.cpp b/src/qt_gui/settings_dialog.cpp index d572915c618..720c68b78ef 100644 --- a/src/qt_gui/settings_dialog.cpp +++ b/src/qt_gui/settings_dialog.cpp @@ -4,6 +4,8 @@ #include #include +#include "common/logging/backend.h" +#include "common/logging/filter.h" #include "main_window.h" #include "settings_dialog.h" #include "ui_settings_dialog.h" @@ -78,11 +80,20 @@ SettingsDialog::SettingsDialog(std::span physical_devices, QWidge Config::setDefaultValues(); LoadValuesFromConfig(); } + if (Common::Log::IsActive()) { + Common::Log::Filter filter; + filter.ParseFilterString(Config::getLogFilter()); + Common::Log::SetGlobalFilter(filter); + } }); - connect(ui->tabWidgetSettings, &QTabWidget::currentChanged, this, [this]() { - ui->buttonBox->button(QDialogButtonBox::StandardButton::Close)->setFocus(); - }); + ui->buttonBox->button(QDialogButtonBox::Save)->setText(tr("Save")); + ui->buttonBox->button(QDialogButtonBox::Apply)->setText(tr("Apply")); + ui->buttonBox->button(QDialogButtonBox::RestoreDefaults)->setText(tr("Restore Defaults")); + ui->buttonBox->button(QDialogButtonBox::Close)->setText(tr("Close")); + + connect(ui->tabWidgetSettings, &QTabWidget::currentChanged, this, + [this]() { ui->buttonBox->button(QDialogButtonBox::Close)->setFocus(); }); // GENERAL TAB { diff --git a/src/qt_gui/translations/ar.ts b/src/qt_gui/translations/ar.ts new file mode 100644 index 00000000000..12b1e7ba6a5 --- /dev/null +++ b/src/qt_gui/translations/ar.ts @@ -0,0 +1,991 @@ + + + + + + AboutDialog + + + About shadPS4 + حول shadPS4 + + + + shadPS4 + shadPS4 + + + + shadPS4 is an experimental open-source emulator for the PlayStation 4. + shadPS4 هو محاكي تجريبي مفتوح المصدر لجهاز PlayStation 4. + + + + This software should not be used to play games you have not legally obtained. + يجب عدم استخدام هذا البرنامج لتشغيل الألعاب التي لم تحصل عليها بشكل قانوني. + + + + ElfViewer + + + Open Folder + فتح المجلد + + + + GameInfoClass + + + Loading game list, please wait :3 + جارٍ تحميل قائمة الألعاب، يرجى الانتظار :3 + + + + Cancel + إلغاء + + + + Loading... + ...جارٍ التحميل + + + + GameInstallDialog + + + shadPS4 - Choose directory + shadPS4 - اختر المجلد + + + + Directory to install games + مجلد تثبيت الألعاب + + + + Browse + تصفح + + + + Error + خطأ + + + + The value for location to install games is not valid. + قيمة موقع تثبيت الألعاب غير صالحة. + + + + GuiContextMenus + + + Create Shortcut + إنشاء اختصار + + + + Open Game Folder + فتح مجلد اللعبة + + + + Cheats / Patches + الغش / التصحيحات + + + + SFO Viewer + عارض SFO + + + + Trophy Viewer + عارض الجوائز + + + + Copy info + نسخ المعلومات + + + + Copy Name + نسخ الاسم + + + + Copy Serial + نسخ الرقم التسلسلي + + + + Copy All + نسخ الكل + + + + Shortcut creation + إنشاء اختصار + + + + Shortcut created successfully!\n %1 + تم إنشاء الاختصار بنجاح!\n %1 + + + + Error + خطأ + + + + Error creating shortcut!\n %1 + !\n %1 خطأ في إنشاء الاختصار + + + + Install PKG + PKG تثبيت + + + + MainWindow + + + Open/Add Elf Folder + Elf فتح/إضافة مجلد + + + + Install Packages (PKG) + (PKG) تثبيت الحزم + + + + Boot Game + تشغيل اللعبة + + + + About shadPS4 + shadPS4 حول + + + + Configure... + ...تكوين + + + + Install application from a .pkg file + .pkg تثبيت التطبيق من ملف + + + + Recent Games + الألعاب الأخيرة + + + + Exit + خروج + + + + Exit shadPS4 + الخروج من shadPS4 + + + + Exit the application. + الخروج من التطبيق. + + + + Show Game List + إظهار قائمة الألعاب + + + + Game List Refresh + تحديث قائمة الألعاب + + + + Tiny + صغير جدًا + + + + Small + صغير + + + + Medium + متوسط + + + + Large + كبير + + + + List View + عرض القائمة + + + + Grid View + عرض الشبكة + + + + Elf Viewer + عارض Elf + + + + Game Install Directory + دليل تثبيت اللعبة + + + + Download Cheats/Patches + تنزيل الغش/التصحيحات + + + + Dump Game List + تفريغ قائمة الألعاب + + + + PKG Viewer + عارض PKG + + + + Search... + ...بحث + + + + File + ملف + + + + View + عرض + + + + Game List Icons + أيقونات قائمة الألعاب + + + + Game List Mode + وضع قائمة الألعاب + + + + Settings + الإعدادات + + + + Utils + الأدوات + + + + Themes + السمات + + + + About + حول + + + + Dark + داكن + + + + Light + فاتح + + + + Green + أخضر + + + + Blue + أزرق + + + + Violet + بنفسجي + + + + toolBar + شريط الأدوات + + + + PKGViewer + + + Open Folder + فتح المجلد + + + + TrophyViewer + + + Trophy Viewer + عارض الجوائز + + + + SettingsDialog + + + Settings + الإعدادات + + + + General + عام + + + + System + النظام + + + + Console Language + لغة وحدة التحكم + + + + Emulator Language + لغة المحاكي + + + + Emulator + المحاكي + + + + Enable Fullscreen + تمكين ملء الشاشة + + + + Show Splash + إظهار شاشة البداية + + + + Is PS4 Pro + PS4 Pro هل هو + + + + Username + اسم المستخدم + + + + Logger + المسجل + + + + Log Type + نوع السجل + + + + Log Filter + مرشح السجل + + + + Graphics + الرسومات + + + + Graphics Device + جهاز الرسومات + + + + Width + العرض + + + + Height + الارتفاع + + + + Vblank Divider + Vblank مقسم + + + + Advanced + متقدم + + + + Enable Shaders Dumping + تمكين تفريغ الشيدرات + + + + Enable NULL GPU + تمكين وحدة معالجة الرسومات الفارغة + + + + Enable PM4 Dumping + PM4 تمكين تفريغ + + + + Debug + تصحيح الأخطاء + + + + Enable Debug Dumping + تمكين تفريغ التصحيح + + + + Enable Vulkan Validation Layers + Vulkan تمكين طبقات التحقق من + + + + Enable Vulkan Synchronization Validation + Vulkan تمكين التحقق من تزامن + + + + Enable RenderDoc Debugging + RenderDoc تمكين تصحيح أخطاء + + + + MainWindow + + + Game List + ققائمة الألعاب + + + + * Unsupported Vulkan Version + * إصدار Vulkan غير مدعوم + + + + Download Cheats For All Installed Games + تنزيل الغش لجميع الألعاب المثبتة + + + + Download Patches For All Games + تنزيل التصحيحات لجميع الألعاب + + + + Download Complete + اكتمل التنزيل + + + + You have downloaded cheats for all the games you have installed. + لقد قمت بتنزيل الغش لجميع الألعاب التي قمت بتثبيتها. + + + + Patches Downloaded Successfully! + !تم تنزيل التصحيحات بنجاح + + + + All Patches available for all games have been downloaded. + .تم تنزيل جميع التصحيحات المتاحة لجميع الألعاب + + + + Games: + :الألعاب + + + + PKG File (*.PKG) + PKG (*.PKG) ملف + + + + ELF files (*.bin *.elf *.oelf) + ELF (*.bin *.elf *.oelf) ملفات + + + + Game Boot + تشغيل اللعبة + + + + Only one file can be selected! + !يمكن تحديد ملف واحد فقط + + + + PKG Extraction + PKG استخراج + + + + Patch detected! + تم اكتشاف تصحيح! + + + + PKG and Game versions match: + :واللعبة تتطابق إصدارات PKG + + + + Would you like to overwrite? + هل ترغب في الكتابة فوق الملف الموجود؟ + + + + PKG Version %1 is older than installed version: + :أقدم من الإصدار المثبت PKG Version %1 + + + + Game is installed: + :اللعبة مثبتة + + + + Would you like to install Patch: + :هل ترغب في تثبيت التصحيح + + + + DLC Installation + تثبيت المحتوى القابل للتنزيل + + + + Would you like to install DLC: %1? + هل ترغب في تثبيت المحتوى القابل للتنزيل: 1%؟ + + + + DLC already installed: + :المحتوى القابل للتنزيل مثبت بالفعل + + + + Game already installed + اللعبة مثبتة بالفعل + + + + PKG is a patch, please install the game first! + !PKG هو تصحيح، يرجى تثبيت اللعبة أولاً + + + + PKG ERROR + PKG خطأ في + + + + Extracting PKG %1/%2 + PKG %1/%2 جاري استخراج + + + + Extraction Finished + اكتمل الاستخراج + + + + Game successfully installed at %1 + تم تثبيت اللعبة بنجاح في %1 + + + + File doesn't appear to be a valid PKG file + يبدو أن الملف ليس ملف PKG صالحًا + + + + CheatsPatches + + + Cheats / Patches + الغش / التصحيحات + + + + defaultTextEdit_MSG + الغش والتصحيحات هي ميزات تجريبية. + استخدمها بحذر. + + قم بتنزيل الغش بشكل فردي عن طريق اختيار المستودع والنقر على زر التنزيل. + في علامة تبويب التصحيحات، يمكنك تنزيل جميع التصحيحات دفعة واحدة، واختيار ما تريد استخدامه، وحفظ اختياراتك. + + نظرًا لأننا لا نقوم بتطوير الغش/التصحيحات، + يرجى الإبلاغ عن أي مشاكل إلى مؤلف الغش. + + هل قمت بإنشاء غش جديد؟ قم بزيارة: + https://github.com/shadps4-emu/ps4_cheats + + + + No Image Available + لا تتوفر صورة + + + + Serial: + الرقم التسلسلي: + + + + Version: + الإصدار: + + + + Size: + الحجم: + + + + Select Cheat File: + اختر ملف الغش: + + + + Repository: + المستودع: + + + + Download Cheats + تنزيل الغش + + + + Delete File + حذف الملف + + + + No files selected. + لم يتم اختيار أي ملفات. + + + + You can delete the cheats you don't want after downloading them. + يمكنك حذف الغش الذي لا تريده بعد تنزيله. + + + + Do you want to delete the selected file?\n%1 + هل تريد حذف الملف المحدد؟ + %1 + + + + Select Patch File: + اختر ملف التصحيح: + + + + Download Patches + تنزيل التصحيحات + + + + Save + حفظ + + + + Cheats + الغش + + + + Patches + التصحيحات + + + + Error + خطأ + + + + No patch selected. + لم يتم اختيار أي تصحيح. + + + + Unable to open files.json for reading. + تعذر فتح files.json للقراءة. + + + + No patch file found for the current serial. + لم يتم العثور على ملف تصحيح للرقم التسلسلي الحالي. + + + + Unable to open the file for reading. + تعذر فتح الملف للقراءة. + + + + Unable to open the file for writing. + تعذر فتح الملف للكتابة. + + + + Failed to parse XML: + :فشل في تحليل XML + + + + Success + نجاح + + + + Options saved successfully. + تم حفظ الخيارات بنجاح. + + + + Invalid Source + مصدر غير صالح + + + + The selected source is invalid. + المصدر المحدد غير صالح. + + + + File Exists + الملف موجود + + + + File already exists. Do you want to replace it? + الملف موجود بالفعل. هل تريد استبداله؟ + + + + Failed to save file: + :فشل في حفظ الملف + + + + Failed to download file: + :فشل في تنزيل الملف + + + + Cheats Not Found + لم يتم العثور على الغش + + + + CheatsNotFound_MSG + لم يتم العثور على غش لهذه اللعبة في هذا الإصدار من المستودع المحدد. حاول استخدام مستودع آخر أو إصدار آخر من اللعبة. + + + + Cheats Downloaded Successfully + تم تنزيل الغش بنجاح + + + + CheatsDownloadedSuccessfully_MSG + لقد نجحت في تنزيل الغش لهذا الإصدار من اللعبة من المستودع المحدد. يمكنك محاولة التنزيل من مستودع آخر. إذا كان متاحًا، يمكنك اختياره عن طريق تحديد الملف من القائمة. + + + + Failed to save: + :فشل في الحفظ + + + + Failed to download: + :فشل في التنزيل + + + + Download Complete + اكتمل التنزيل + + + + DownloadComplete_MSG + تم تنزيل التصحيحات بنجاح! تم تنزيل جميع التصحيحات لجميع الألعاب، ولا داعي لتنزيلها بشكل فردي لكل لعبة كما هو الحال مع الغش. إذا لم يظهر التحديث، قد يكون السبب أنه غير متوفر للإصدار وسيريال اللعبة المحدد. قد تحتاج إلى تحديث اللعبة. + + + + Failed to parse JSON data from HTML. + فشل في تحليل بيانات JSON من HTML. + + + + Failed to retrieve HTML page. + .HTML فشل في استرجاع صفحة + + + + Failed to open file: + :فشل في فتح الملف + + + + XML ERROR: + :خطأ في XML + + + + Failed to open files.json for writing + فشل في فتح files.json للكتابة + + + + Author: + :المؤلف + + + + Directory does not exist: + :المجلد غير موجود + + + + Failed to open files.json for reading. + فشل في فتح files.json للقراءة. + + + + Name: + :الاسم + + + + Can't apply cheats before the game is started + لا يمكن تطبيق الغش قبل بدء اللعبة. + + + + SettingsDialog + + + Save + حفظ + + + + Apply + تطبيق + + + + Restore Defaults + استعادة الإعدادات الافتراضية + + + + Close + إغلاق + + + + GameListFrame + + + Icon + أيقونة + + + + Name + اسم + + + + Serial + سيريال + + + + Region + منطقة + + + + Firmware + البرمجيات الثابتة + + + + Size + حجم + + + + Version + إصدار + + + + Path + مسار + + + diff --git a/src/qt_gui/translations/da_DK.ts b/src/qt_gui/translations/da_DK.ts index c67d29b1d3d..bb405ec0ae2 100644 --- a/src/qt_gui/translations/da_DK.ts +++ b/src/qt_gui/translations/da_DK.ts @@ -500,6 +500,11 @@ MainWindow + + + Game List + Spiloversigt + * Unsupported Vulkan Version @@ -851,7 +856,7 @@ DownloadComplete_MSG - Patcher hentet med succes! Alle patches til alle spil er blevet hentet, der er ikke behov for at hente dem individuelt for hvert spil, som det sker med snyd. + Patcher hentet med succes! Alle patches til alle spil er blevet hentet, der er ikke behov for at hente dem individuelt for hvert spil, som det sker med snyd. Hvis opdateringen ikke vises, kan det være, at den ikke findes for den specifikke serie og version af spillet. Det kan være nødvendigt at opdatere spillet. @@ -898,5 +903,76 @@ Name: Navn: + + + Can't apply cheats before the game is started + Kan ikke anvende snyd før spillet er startet. + + + SettingsDialog + + + Save + Gem + + + + Apply + Anvend + + + + Restore Defaults + Gendan standardindstillinger + + + + Close + Luk + + + + GameListFrame + + + Icon + Ikon + + + + Name + Navn + + + + Serial + Seriel + + + + Region + Region + + + + Firmware + Firmware + + + + Size + Størrelse + + + + Version + Version + + + + Path + Sti + + \ No newline at end of file diff --git a/src/qt_gui/translations/de.ts b/src/qt_gui/translations/de.ts index c833f2e28f0..1482686cec0 100644 --- a/src/qt_gui/translations/de.ts +++ b/src/qt_gui/translations/de.ts @@ -6,7 +6,7 @@ About shadPS4 - About shadPS4 + Über shadPS4 @@ -16,12 +16,12 @@ shadPS4 is an experimental open-source emulator for the PlayStation 4. - shadPS4 is an experimental open-source emulator for the PlayStation 4. + shadPS4 ist ein experimenteller Open-Source-Emulator für die Playstation 4. This software should not be used to play games you have not legally obtained. - This software should not be used to play games you have not legally obtained. + Diese Software soll nicht dazu benutzt werden illegal kopierte Spiele zu spielen. @@ -29,7 +29,7 @@ Open Folder - Open Folder + Ordner öffnen @@ -37,17 +37,17 @@ Loading game list, please wait :3 - Loading game list, please wait :3 + Lade Spielliste, bitte warten :3 Cancel - Cancel + Abbrechen Loading... - Loading... + Lade... @@ -55,27 +55,27 @@ shadPS4 - Choose directory - shadPS4 - Choose directory + shadPS4 - Wähle Ordner Directory to install games - Directory to install games + Installationsverzeichnis für Spiele Browse - Browse + Durchsuchen Error - Error + Fehler The value for location to install games is not valid. - The value for location to install games is not valid. + Der ausgewählte Ordner ist nicht gültig. @@ -83,12 +83,12 @@ Create Shortcut - Create Shortcut + Verknüpfung erstellen Open Game Folder - Open Game Folder + Spieleordner öffnen @@ -98,57 +98,57 @@ SFO Viewer - SFO Viewer + SFO anzeigen Trophy Viewer - Trophy Viewer + Trophäen anzeigen Copy info - Copy info + Infos kopieren Copy Name - Copy Name + Namen kopieren Copy Serial - Copy Serial + Seriennummer kopieren Copy All - Copy All + Alles kopieren Shortcut creation - Shortcut creation + Verknüpfungserstellung Shortcut created successfully!\n %1 - Shortcut created successfully!\n %1 + Verknüpfung erfolgreich erstellt!\n %1 Error - Error + Fehler Error creating shortcut!\n %1 - Error creating shortcut!\n %1 + Fehler beim Erstellen der Verknüpfung!\n %1 Install PKG - Install PKG + PKG installieren @@ -156,102 +156,102 @@ Open/Add Elf Folder - Open/Add Elf Folder + Elf-Ordner öffnen/hinzufügen Install Packages (PKG) - Install Packages (PKG) + Pakete installieren (PKG) Boot Game - Boot Game + Spiel starten About shadPS4 - About shadPS4 + Über shadPS4 Configure... - Configure... + Konfigurieren... Install application from a .pkg file - Install application from a .pkg file + Installiere Anwendung aus .pkg-Datei Recent Games - Recent Games + Zuletzt gespielt Exit - Exit + Beenden Exit shadPS4 - Exit shadPS4 + shadPS4 beenden Exit the application. - Exit the application. + Die Anwendung beenden. Show Game List - Show Game List + Spielliste anzeigen Game List Refresh - Game List Refresh + Spielliste aktualisieren Tiny - Tiny + Winzig Small - Small + Klein Medium - Medium + Mittel Large - Large + Groß List View - List View + Listenansicht Grid View - Grid View + Gitteransicht Elf Viewer - Elf Viewer + Elf-Ansicht Game Install Directory - Game Install Directory + Installationsverzeichnis für Spiele @@ -261,27 +261,27 @@ Dump Game List - Dump Game List + Spielliste ausgeben PKG Viewer - PKG Viewer + PKG-Ansicht Search... - Search... + Suchen... File - File + Datei View - View + Ansicht @@ -291,52 +291,52 @@ Game List Mode - Game List Mode + Spiellisten-Symoble Settings - Settings + Einstellungen Utils - Utils + Werkzeuge Themes - Themes + Stile About - About + Über Dark - Dark + Dunkel Light - Light + Hell Green - Green + Grün Blue - Blue + Blau Violet - Violet + Violett @@ -349,7 +349,7 @@ Open Folder - Open Folder + Ordner öffnen @@ -357,7 +357,7 @@ Trophy Viewer - Trophy Viewer + Trophäenansicht @@ -365,12 +365,12 @@ Settings - Settings + Einstellungen General - General + Allgemein @@ -380,12 +380,12 @@ Console Language - Console Language + Konsolensprache Emulator Language - Emulator Language + Emulatorsprache @@ -395,22 +395,22 @@ Enable Fullscreen - Enable Fullscreen + Vollbild aktivieren Show Splash - Show Splash + Startbildschirm anzeigen Is PS4 Pro - Is PS4 Pro + Ist PS4 Pro Username - Username + Benutzername @@ -420,57 +420,57 @@ Log Type - Log Type + Logtyp Log Filter - Log Filter + Log-Filter Graphics - Graphics + Grafik Graphics Device - Graphics Device + Grafikgerät Width - Width + Breite Height - Height + Höhe Vblank Divider - Vblank Divider + Vblank-Teiler Advanced - Advanced + Erweitert Enable Shaders Dumping - Enable Shaders Dumping + Shader-Dumping aktivieren Enable NULL GPU - Enable NULL GPU + NULL GPU aktivieren Enable PM4 Dumping - Enable PM4 Dumping + PM4-Dumping aktivieren @@ -480,26 +480,31 @@ Enable Debug Dumping - Enable Debug Dumping + Debug-Dumping aktivieren Enable Vulkan Validation Layers - Enable Vulkan Validation Layers + Vulkan Validations-Ebenen aktivieren Enable Vulkan Synchronization Validation - Enable Vulkan Synchronization Validation + Vulkan Synchronisations-Validierung aktivieren Enable RenderDoc Debugging - Enable RenderDoc Debugging + RenderDoc-Debugging aktivieren MainWindow + + + Game List + Spieleliste + * Unsupported Vulkan Version @@ -578,7 +583,7 @@ Would you like to overwrite? - Würden Sie gerne überschreiben? + Willst du überschreiben? @@ -593,7 +598,7 @@ Would you like to install Patch: - Möchten Sie den Patch installieren: + Willst du den Patch installieren: @@ -603,7 +608,7 @@ Would you like to install DLC: %1? - Würden Sie gerne DLC installieren: %1? + Willst du den DLC installieren: %1? @@ -623,7 +628,7 @@ PKG ERROR - PKG-ERROR + PKG-FEHLER @@ -656,7 +661,7 @@ defaultTextEdit_MSG - Cheats/Patches sind experimentell.\nVerwenden Sie sie mit Vorsicht.\n\nLaden Sie Cheats einzeln herunter, indem Sie das Repository auswählen und auf die Download-Schaltfläche klicken.\nAuf der Registerkarte Patches können Sie alle Patches auf einmal herunterladen, auswählen, welche Sie verwenden möchten, und die Auswahl speichern.\n\nDa wir die Cheats/Patches nicht entwickeln,\nbitte melden Sie Probleme an den Cheat-Autor.\n\nHaben Sie einen neuen Cheat erstellt? Besuchen Sie:\nhttps://github.com/shadps4-emu/ps4_cheats + Cheats/Patches sind experimentell.\nVerwende sie mit Vorsicht.\n\nLade Cheats einzeln herunter, indem du das Repository auswählst und auf die Download-Schaltfläche klickst.\nAuf der Registerkarte Patches kannst du alle Patches auf einmal herunterladen, auswählen, welche du verwenden möchtest, und die Auswahl speichern.\n\nDa wir die Cheats/Patches nicht entwickeln,\nbitte melde Probleme an den Cheat-Autor.\n\nHast du einen neuen Cheat erstellt? Besuche:\nhttps://github.com/shadps4-emu/ps4_cheats @@ -706,12 +711,12 @@ You can delete the cheats you don't want after downloading them. - Sie können die Cheats, die Sie nicht möchten, nach dem Herunterladen löschen. + Du kannst die Cheats, die du nicht möchtest, nach dem Herunterladen löschen. Do you want to delete the selected file?\n%1 - Wollen Sie die ausgewählte Datei löschen?\n%1 + Willst du die ausgewählte Datei löschen?\n%1 @@ -801,7 +806,7 @@ File already exists. Do you want to replace it? - Datei existiert bereits. Möchten Sie sie ersetzen? + Datei existiert bereits. Möchtest du sie ersetzen? @@ -821,7 +826,7 @@ CheatsNotFound_MSG - Keine Cheats für dieses Spiel in dieser Version des gewählten Repositories gefunden. Versuchen Sie es mit einem anderen Repository oder einer anderen Version des Spiels. + Keine Cheats für dieses Spiel in dieser Version des gewählten Repositories gefunden. Versuche es mit einem anderen Repository oder einer anderen Version des Spiels. @@ -831,7 +836,7 @@ CheatsDownloadedSuccessfully_MSG - Sie haben erfolgreich Cheats für diese Version des Spiels aus dem gewählten Repository heruntergeladen. Sie können versuchen, von einem anderen Repository herunterzuladen. Wenn verfügbar, können Sie es auswählen, indem Sie die Datei aus der Liste auswählen. + Du hast erfolgreich Cheats für diese Version des Spiels aus dem gewählten Repository heruntergeladen. Du kannst auch versuchen, Cheats von einem anderen Repository herunterzuladen. Wenn verfügbar, kannst du sie auswählen, indem du die Datei aus der Liste auswählst. @@ -851,7 +856,7 @@ DownloadComplete_MSG - Patches erfolgreich heruntergeladen! Alle Patches für alle Spiele wurden heruntergeladen, es ist nicht notwendig, sie einzeln für jedes Spiel herunterzuladen, wie es bei Cheats der Fall ist. + Patches erfolgreich heruntergeladen! Alle Patches für alle Spiele wurden heruntergeladen, es ist nicht notwendig, sie einzeln für jedes Spiel herunterzuladen, wie es bei Cheats der Fall ist. Wenn der Patch nicht angezeigt wird, könnte es sein, dass er für die spezifische Seriennummer und Version des Spiels nicht existiert. Möglicherweise müssen Sie das Spiel aktualisieren. @@ -898,5 +903,76 @@ Name: Name: + + + Can't apply cheats before the game is started + Kann keine Cheats anwenden, bevor das Spiel gestartet ist. + + + SettingsDialog + + + Save + Speichern + + + + Apply + Übernehmen + + + + Restore Defaults + Werkseinstellungen wiederherstellen + + + + Close + Schließen + + + + GameListFrame + + + Icon + Symbol + + + + Name + Name + + + + Serial + Seriennummer + + + + Region + Region + + + + Firmware + Firmware + + + + Size + Größe + + + + Version + Version + + + + Path + Pfad + + \ No newline at end of file diff --git a/src/qt_gui/translations/el.ts b/src/qt_gui/translations/el.ts index ef831fb09d6..4a3aa54ff64 100644 --- a/src/qt_gui/translations/el.ts +++ b/src/qt_gui/translations/el.ts @@ -500,6 +500,11 @@ MainWindow + + + Game List + Λίστα παιχνιδιών + * Unsupported Vulkan Version @@ -851,7 +856,7 @@ DownloadComplete_MSG - Τα Patches κατεβάστηκαν επιτυχώς! Όλα τα Patches για όλα τα παιχνίδια έχουν κατέβει, δεν είναι απαραίτητο να τα κατεβάσετε ένα-ένα για κάθε παιχνίδι, όπως με τα Cheats. + Τα Patches κατεβάστηκαν επιτυχώς! Όλα τα Patches για όλα τα παιχνίδια έχουν κατέβει, δεν είναι απαραίτητο να τα κατεβάσετε ένα-ένα για κάθε παιχνίδι, όπως με τα Cheats. Εάν η ενημέρωση δεν εμφανίζεται, μπορεί να μην υπάρχει για τον συγκεκριμένο σειριακό αριθμό και έκδοση του παιχνιδιού. Μπορεί να χρειαστεί να ενημερώσετε το παιχνίδι. @@ -898,5 +903,76 @@ Name: Όνομα: + + + Can't apply cheats before the game is started + Δεν μπορείτε να εφαρμόσετε cheats πριν ξεκινήσει το παιχνίδι. + + + SettingsDialog + + + Save + Αποθήκευση + + + + Apply + Εφαρμογή + + + + Restore Defaults + Επαναφορά Προεπιλογών + + + + Close + Κλείσιμο + + + + GameListFrame + + + Icon + Εικονίδιο + + + + Name + Όνομα + + + + Serial + Σειριακός αριθμός + + + + Region + Περιοχή + + + + Firmware + Λογισμικό + + + + Size + Μέγεθος + + + + Version + Έκδοση + + + + Path + Διαδρομή + + \ No newline at end of file diff --git a/src/qt_gui/translations/en.ts b/src/qt_gui/translations/en.ts index b3c3b699be3..9696610bca7 100644 --- a/src/qt_gui/translations/en.ts +++ b/src/qt_gui/translations/en.ts @@ -500,6 +500,11 @@ MainWindow + + + Game List + Game List + * Unsupported Vulkan Version @@ -851,7 +856,7 @@ DownloadComplete_MSG - Patches Downloaded Successfully! All Patches available for all games have been downloaded, there is no need to download them individually for each game as happens in Cheats. + Patches Downloaded Successfully! All Patches available for all games have been downloaded, there is no need to download them individually for each game as happens in Cheats. If the patch does not appear, it may be that it does not exist for the specific serial and version of the game. It may be necessary to update the game. @@ -898,5 +903,76 @@ Name: Name: - + + + Can't apply cheats before the game is started + Can't apply cheats before the game is started. + + + + SettingsDialog + + + Save + Save + + + + Apply + Apply + + + + Restore Defaults + Restore Defaults + + + + Close + Close + + + + GameListFrame + + + Icon + Icon + + + + Name + Name + + + + Serial + Serial + + + + Region + Region + + + + Firmware + Firmware + + + + Size + Size + + + + Version + Version + + + + Path + Path + + \ No newline at end of file diff --git a/src/qt_gui/translations/es_ES.ts b/src/qt_gui/translations/es_ES.ts index c34dc3d446d..8690b2e8833 100644 --- a/src/qt_gui/translations/es_ES.ts +++ b/src/qt_gui/translations/es_ES.ts @@ -21,7 +21,7 @@ This software should not be used to play games you have not legally obtained. - Este software no debe utilizarse para jugar juegos que no hayas obtenido legalmente. + Este software no debe utilizarse para jugar juegos que hayas obtenido ilegalmente. @@ -118,7 +118,7 @@ Copy Serial - Copiar serial + Copiar número de serie @@ -296,7 +296,7 @@ Settings - Configuraciones + Configuración @@ -341,7 +341,7 @@ toolBar - barra de herramientas + Barra de herramientas @@ -365,7 +365,7 @@ Settings - Configuraciones + Configuración @@ -500,6 +500,11 @@ MainWindow + + + Game List + Lista de juegos + * Unsupported Vulkan Version @@ -533,7 +538,7 @@ All Patches available for all games have been downloaded. - Todos los parches disponibles para todos los juegos han sido descargados. + Todos los parches disponibles han sido descargados para todos los juegos. @@ -643,7 +648,7 @@ File doesn't appear to be a valid PKG file - El archivo no parece ser un archivo PKG válido + El archivo parece no ser un archivo PKG válido @@ -666,7 +671,7 @@ Serial: - Serie: + Número de serie: @@ -706,7 +711,7 @@ You can delete the cheats you don't want after downloading them. - Puedes eliminar los trucos que no quieras después de descargarlos. + Puedes eliminar los trucos que no quieras una vez descargados. @@ -736,7 +741,7 @@ Patches - Parche + Parches @@ -756,7 +761,7 @@ No patch file found for the current serial. - No se encontró ningún archivo de parche para la serie actual. + No se encontró ningún archivo de parche para el número de serie actual. @@ -851,7 +856,7 @@ DownloadComplete_MSG - ¡Parches descargados exitosamente! Todos los parches disponibles para todos los juegos han sido descargados, no es necesario descargarlos individualmente para cada juego como ocurre con los trucos. + ¡Parches descargados exitosamente! Todos los parches disponibles para todos los juegos han sido descargados, no es necesario descargarlos individualmente para cada juego como ocurre con los trucos. Si el parche no aparece, puede ser que no exista para el número de serie y versión específicos del juego. Puede ser necesario actualizar el juego. @@ -898,5 +903,76 @@ Name: Nombre: + + + Can't apply cheats before the game is started + No se pueden aplicar trucos antes de que se inicie el juego. + + + + SettingsDialog + + + Save + Guardar + + + + Apply + Aplicar + + + + Restore Defaults + Restaurar Valores Predeterminados + + + + Close + Cerrar + + + + GameListFrame + + + Icon + Icono + + + + Name + Nombre + + + + Serial + Numero de serie + + + + Region + Región + + + + Firmware + Firmware + + + + Size + Tamaño + + + + Version + Versión + + + + Path + Ruta + \ No newline at end of file diff --git a/src/qt_gui/translations/fa_IR.ts b/src/qt_gui/translations/fa_IR.ts new file mode 100644 index 00000000000..08965306962 --- /dev/null +++ b/src/qt_gui/translations/fa_IR.ts @@ -0,0 +1,978 @@ + + + + AboutDialog + + + About shadPS4 + درباره ShadPS4 + + + + shadPS4 + ShadPS4 + + + + shadPS4 is an experimental open-source emulator for the PlayStation 4. + یک شبیه ساز متن باز برای پلی استیشن 4 است. + + + + This software should not be used to play games you have not legally obtained. + این برنامه نباید برای بازی هایی که شما به صورت غیرقانونی به دست آوردید استفاده شود. + + + + ElfViewer + + + Open Folder + فولدر را بازکن + + + + GameInfoClass + + + Loading game list, please wait :3 + درحال بارگیری لیست بازی ها,لطفا کمی صبرکنید :3 + + + + Cancel + لغو + + + + Loading... + ...درحال بارگیری + + + + GameInstallDialog + + + shadPS4 - Choose directory + ShadPS4 - انتخاب محل نصب بازی + + + + Directory to install games + محل نصب بازی ها + + + + Browse + انتخاب دستی + + + + Error + ارور + + + + The value for location to install games is not valid. + .مکان داده شده برای نصب بازی درست نمی باشد + + + + GuiContextMenus + + + Create Shortcut + ساخت شورتکات + + + + Open Game Folder + بازکردن محل نصب بازی + + + + Cheats / Patches + چیت/پچ ها + + + + SFO Viewer + SFO مشاهده + + + + Trophy Viewer + مشاهده تروفی ها + + + + Copy info + کپی کردن اطلاعات + + + + Copy Name + کپی کردن نام + + + + Copy Serial + کپی کردن سریال + + + + Copy All + کپی کردن تمامی مقادیر + + + + Shortcut creation + سازنده شورتکات + + + + Shortcut created successfully!\n %1 + شورتکات با موفقیت ساخته شد! \n %1 + + + + Error + ارور + + + + Error creating shortcut!\n %1 + مشکلی در هنگام ساخت شورتکات بوجود آمد!\n %1 + + + + Install PKG + نصب PKG + + + + MainWindow + + + Open/Add Elf Folder + ELF بازکردن/ساختن پوشه + + + + Install Packages (PKG) + نصب بسته (PKG) + + + + Boot Game + اجرای بازی + + + + About shadPS4 + ShadPS4 درباره + + + + Configure... + ...تنظیمات + + + + Install application from a .pkg file + .PKG نصب بازی از فایل + + + + Recent Games + بازی های اخیر + + + + Exit + خروج + + + + Exit shadPS4 + ShadPS4 بستن + + + + Exit the application. + بستن برنامه + + + + Show Game List + نشان دادن بازی ها + + + + Game List Refresh + رفرش لیست بازی ها + + + + Tiny + کوچک ترین + + + + Small + کوچک + + + + Medium + متوسط + + + + Large + بزرگ + + + + List View + لیستی + + + + Grid View + شبکه ای (چهارخونه) + + + + Elf Viewer + Elf Viewer + + + + Game Install Directory + محل نصب بازی + + + + Download Cheats/Patches + دانلود چیت/پچ + + + + Dump Game List + استخراج لیست بازی ها + + + + PKG Viewer + PKG مشاهده گر + + + + Search... + جست و جو... + + + + File + فایل + + + + View + شخصی سازی + + + + Game List Icons + آیکون ها + + + + Game List Mode + حالت نمایش لیست بازی ها + + + + Settings + تنظیمات + + + + Utils + ابزارها + + + + Themes + تم ها + + + + About + درباره ما + + + + Dark + تیره + + + + Light + روشن + + + + Green + سبز + + + + Blue + آبی + + + + Violet + بنفش + + + + toolBar + نوار ابزار + + + + PKGViewer + + + Open Folder + بازکردن پوشه + + + + TrophyViewer + + + Trophy Viewer + تروفی ها + + + + SettingsDialog + + + Settings + تنظیمات + + + + General + عمومی + + + + System + سیستم + + + + Console Language + زبان کنسول + + + + Emulator Language + زبان شبیه ساز + + + + Emulator + شبیه ساز + + + + Enable Fullscreen + تمام صفحه + + + + Show Splash + Splash نمایش + + + + Is PS4 Pro + PS4 Pro حالت + + + + Username + نام کاربری + + + + Logger + Logger + + + + Log Type + Log نوع + + + + Log Filter + Log فیلتر + + + + Graphics + گرافیک + + + + Graphics Device + کارت گرافیک مورداستفاده + + + + Width + عرض + + + + Height + طول + + + + Vblank Divider + Vblank Divider + + + + Advanced + ...بیشتر + + + + Enable Shaders Dumping + Shaders Dumping فعال کردن + + + + Enable NULL GPU + NULL GPU فعال کردن + + + + Enable PM4 Dumping + PM4 Dumping فعال کردن + + + + Debug + Debug + + + + Enable Debug Dumping + Debug Dumping + + + + Enable Vulkan Validation Layers + Vulkan Validation Layers + + + + Enable Vulkan Synchronization Validation + Vulkan Synchronization Validation + + + + Enable RenderDoc Debugging + RenderDoc Debugging + + + + MainWindow + + + Game List + لیست بازی + + + + * Unsupported Vulkan Version + شما پشتیبانی نمیشود Vulkan ورژن* + + + + Download Cheats For All Installed Games + دانلود چیت برای همه بازی ها + + + + Download Patches For All Games + دانلود پچ برای همه بازی ها + + + + Download Complete + دانلود کامل شد✅ + + + + You have downloaded cheats for all the games you have installed. + چیت برای همه بازی های شما دانلودشد✅ + + + + Patches Downloaded Successfully! + پچ ها با موفقیت دانلود شد✅ + + + + All Patches available for all games have been downloaded. + ✅تمام پچ های موجود برای همه بازی های شما دانلود شد + + + + Games: + بازی ها: + + + + PKG File (*.PKG) + PKG فایل (*.PKG) + + + + ELF files (*.bin *.elf *.oelf) + ELF فایل های (*.bin *.elf *.oelf) + + + + Game Boot + اجرای بازی + + + + Only one file can be selected! + فقط یک فایل انتخاب کنید! + + + + PKG Extraction + PKG استخراج فایل + + + + Patch detected! + پچ شناسایی شد! + + + + PKG and Game versions match: + و نسخه بازی همخوانی دارد PKG فایل: + + + + Would you like to overwrite? + آیا مایل به جایگزینی فایل هستید؟ + + + + PKG Version %1 is older than installed version: + نسخه فایل PKG %1 قدیمی تر از نسخه نصب شده است: + + + + Game is installed: + بازی نصب شد: + + + + Would you like to install Patch: + آیا مایل به نصب پچ هستید: + + + + DLC Installation + نصب DLC + + + + Would you like to install DLC: %1? + آیا مایل به نصب DLC هستید: %1 + + + + DLC already installed: + قبلا نصب شده DLC این: + + + + Game already installed + این بازی قبلا نصب شده + + + + PKG is a patch, please install the game first! + فایل انتخاب شده یک پچ است, لطفا اول بازی را نصب کنید + + + + PKG ERROR + PKG ارور فایل + + + + Extracting PKG %1/%2 + درحال استخراج PKG %1/%2 + + + + Extraction Finished + استخراج به پایان رسید + + + + Game successfully installed at %1 + بازی با موفقیت در %1 نصب شد + + + + File doesn't appear to be a valid PKG file + این فایل یک PKG درست به نظر نمی آید + + + + CheatsPatches + + + Cheats / Patches + چیت / پچ ها + + + + defaultTextEdit_MSG + defaultTextEdit_MSG + + + + No Image Available + تصویری موجود نمی باشد + + + + Serial: + سریال: + + + + Version: + ورژن: + + + + Size: + حجم: + + + + Select Cheat File: + فایل چیت را انتخاب کنید: + + + + Repository: + :منبع + + + + Download Cheats + دانلود چیت ها + + + + Delete File + پاک کردن فایل + + + + No files selected. + فایلی انتخاب نشده. + + + + You can delete the cheats you don't want after downloading them. + شما میتوانید بعد از دانلود چیت هایی که نمیخواهید را پاک کنید + + + + Do you want to delete the selected file?\n%1 + آیا میخواهید فایل های انتخاب شده را پاک کنید؟ \n%1 + + + + Select Patch File: + فایل پچ را انتخاب کنید + + + + Download Patches + دانلود کردن پچ ها + + + + Save + ذخیره + + + + Cheats + چیت ها + + + + Patches + پچ ها + + + + Error + ارور + + + + No patch selected. + هیچ پچ انتخاب نشده + + + + Unable to open files.json for reading. + .json مشکل در خواندن فایل + + + + No patch file found for the current serial. + هیچ فایل پچ برای سریال بازی شما پیدا نشد. + + + + Unable to open the file for reading. + خطا در خواندن فایل + + + + Unable to open the file for writing. + خطا در نوشتن فایل + + + + Failed to parse XML: + انجام نشد XML تجزیه فایل: + + + + Success + عملیات موفق بود + + + + Options saved successfully. + تغییرات با موفقیت ذخیره شد✅ + + + + Invalid Source + منبع نامعتبر❌ + + + + The selected source is invalid. + منبع انتخاب شده نامعتبر است + + + + File Exists + فایل وجود دارد + + + + File already exists. Do you want to replace it? + فایل از قبل وجود دارد. آیا می خواهید آن را جایگزین کنید؟ + + + + Failed to save file: + ذخیره فایل موفقیت آمیز نبود: + + + + Failed to download file: + خطا در دانلود فایل: + + + + Cheats Not Found + چیت یافت نشد + + + + CheatsNotFound_MSG + متاسفانه هیچ چیتی از منبع انتخاب شده پیدا نشد! شما میتوانید منابع دیگری را برای دانلود انتخاب و یا چیت های خود را به صورت دستی واردکنید. + + + + Cheats Downloaded Successfully + دانلود چیت ها موفقیت آمیز بود✅ + + + + CheatsDownloadedSuccessfully_MSG + تمامی چیت های موجود برای این بازی از منبع انتخاب شده دانلود شد! شما همچنان میتوانید چیت های دیگری را ازمنابع مختلف دانلود کنید و درصورت موجود بودن از آنها استفاده کنید. + + + + Failed to save: + خطا در ذخیره اطلاعات: + + + + Failed to download: + خطا در دانلود❌ + + + + Download Complete + پچ ها با موفقیت بارگیری شدند! تمام وصله های موجود برای همه بازی ها دانلود شده اند، نیازی به دانلود جداگانه آنها برای هر بازی نیست، همانطور که در Cheats اتفاق می افتد. اگر پچ ظاهر نشد، ممکن است برای سریال و نسخه خاصی از بازی وجود نداشته باشد. ممکن است نیاز به آپدیت بازی باشد.✅ + + + + DownloadComplete_MSG + دانلود با موفقیت به اتمام رسید✅ + + + + Failed to parse JSON data from HTML. + HTML از JSON خطا در تجزیه اطلاعات. + + + + Failed to retrieve HTML page. + HTML خطا دربازیابی صفحه + + + + Failed to open file: + خطا در اجرای فایل: + + + + XML ERROR: + XML ERROR: + + + + Failed to open files.json for writing + .json خطا در نوشتن فایل + + + + Author: + تولید کننده: + + + + Directory does not exist: + پوشه وجود ندارد: + + + + Failed to open files.json for reading. + .json خطا در خواندن فایل + + + + Name: + نام: + + + + Can't apply cheats before the game is started + قبل از شروع بازی نمی توانید تقلب ها را اعمال کنید. + + + + SettingsDialog + + + Save + ذخیره + + + + Apply + اعمال + + + + Restore Defaults + بازیابی پیش فرض ها + + + + Close + بستن + + + + GameListFrame + + + Icon + آیکون + + + + Name + نام + + + + Serial + سریال + + + + Region + منطقه + + + + Firmware + فریمور + + + + Size + اندازه + + + + Version + نسخه + + + + Path + مسیر + + + \ No newline at end of file diff --git a/src/qt_gui/translations/fi.ts b/src/qt_gui/translations/fi.ts index d667dd37983..50e24fce69d 100644 --- a/src/qt_gui/translations/fi.ts +++ b/src/qt_gui/translations/fi.ts @@ -500,6 +500,11 @@ MainWindow + + + Game List + Pelilista + * Unsupported Vulkan Version @@ -851,7 +856,7 @@ DownloadComplete_MSG - Korjaukset ladattu onnistuneesti! Kaikki saatavilla olevat korjaukset kaikille peleille on ladattu, eikä niitä tarvitse ladata yksittäin jokaiselle pelille kuten huijauksissa. + Korjaukset ladattu onnistuneesti! Kaikki saatavilla olevat korjaukset kaikille peleille on ladattu, eikä niitä tarvitse ladata yksittäin jokaiselle pelille kuten huijauksissa. Jos päivitystä ei näy, se saattaa olla, että sitä ei ole saatavilla tietylle sarjanumerolle ja peliversiolle. Saattaa olla tarpeen päivittää peli. @@ -898,5 +903,76 @@ Name: Nimi: + + + Can't apply cheats before the game is started + Ei voi käyttää huijauksia ennen kuin peli on aloitettu. + + + SettingsDialog + + + Save + Tallenna + + + + Apply + Ota käyttöön + + + + Restore Defaults + Palauta oletukset + + + + Close + Sulje + + + + GameListFrame + + + Icon + Ikoni + + + + Name + Nimi + + + + Serial + Sarjanumero + + + + Region + Alue + + + + Firmware + Ohjelmisto + + + + Size + Koko + + + + Version + Versio + + + + Path + Polku + + \ No newline at end of file diff --git a/src/qt_gui/translations/fr.ts b/src/qt_gui/translations/fr.ts index 388912d2399..5ba5e7e2e16 100644 --- a/src/qt_gui/translations/fr.ts +++ b/src/qt_gui/translations/fr.ts @@ -21,7 +21,7 @@ This software should not be used to play games you have not legally obtained. - Ce logiciel ne doit pas être utilisé pour jouer à des jeux que vous n'avez pas obtenus légalement. + Ce logiciel ne doit pas être utilisé pour jouer à des jeux que vous n'avez pas obtenus légalement. @@ -60,7 +60,7 @@ Directory to install games - Répertoire d'installation des jeux + Répertoire d'installation des jeux @@ -75,7 +75,7 @@ The value for location to install games is not valid. - Le répertoire d'installation des jeux n'est pas valide. + Le répertoire d'installation des jeux n'est pas valide. @@ -118,7 +118,7 @@ Copy Serial - Copier le numéro de série + Copier le N° de série @@ -201,7 +201,7 @@ Exit the application. - Fermer l'application. + Fermer l'application. @@ -291,7 +291,7 @@ Game List Mode - Mode d'affichage + Mode d'affichage @@ -301,7 +301,7 @@ Utils - Utilitaire + Utilitaires @@ -316,12 +316,12 @@ Dark - Noir + Sombre Light - Blanc + Clair @@ -341,7 +341,7 @@ toolBar - Bare d'outils + Bare d'outils @@ -385,7 +385,7 @@ Emulator Language - Langage de l'émulateur + Langage de l'émulateur @@ -400,7 +400,7 @@ Show Splash - Afficher l'image du jeu + Afficher l'image du jeu @@ -410,7 +410,7 @@ Username - Nom d'utilisateur + Nom d'utilisateur @@ -500,6 +500,11 @@ MainWindow + + + Game List + Liste de jeux + * Unsupported Vulkan Version @@ -533,7 +538,7 @@ All Patches available for all games have been downloaded. - Tous les patchs disponibles pour les jeux ont été téléchargés. + Tous les patchs disponibles ont été téléchargés. @@ -851,7 +856,7 @@ DownloadComplete_MSG - Patchs téléchargés avec succès ! Tous les patches disponibles pour tous les jeux ont été téléchargés, il n'est pas nécessaire de les télécharger individuellement pour chaque jeu comme c'est le cas pour les Cheats. + Patchs téléchargés avec succès ! Tous les patches disponibles pour tous les jeux ont été téléchargés, il n'est pas nécessaire de les télécharger individuellement pour chaque jeu comme c'est le cas pour les Cheats. Si le correctif n'apparaît pas, il se peut qu'il n'existe pas pour le numéro de série et la version spécifiques du jeu. Il peut être nécessaire de mettre à jour le jeu. @@ -898,5 +903,76 @@ Name: Nom : + + + Can't apply cheats before the game is started + Impossible d'appliquer les Cheats avant que le jeu ne commence. + - + + SettingsDialog + + + Save + Enregistrer + + + + Apply + Appliquer + + + + Restore Defaults + Restaurer les paramètres par défaut + + + + Close + Fermer + + + + GameListFrame + + + Icon + Icône + + + + Name + Nom + + + + Serial + Série + + + + Region + Région + + + + Firmware + Firmware + + + + Size + Taille + + + + Version + Version + + + + Path + Répertoire + + + \ No newline at end of file diff --git a/src/qt_gui/translations/hu_HU.ts b/src/qt_gui/translations/hu_HU.ts index e5fb25a5dbe..0f69822e796 100644 --- a/src/qt_gui/translations/hu_HU.ts +++ b/src/qt_gui/translations/hu_HU.ts @@ -500,6 +500,11 @@ MainWindow + + + Game List + Játéklista + * Unsupported Vulkan Version @@ -851,7 +856,7 @@ DownloadComplete_MSG - Frissítések sikeresen letöltve! Minden elérhető frissítés letöltésre került, nem szükséges egyesével letölteni őket minden játékhoz, mint a csalások esetében. + Frissítések sikeresen letöltve! Minden elérhető frissítés letöltésre került, nem szükséges egyesével letölteni őket minden játékhoz, mint a csalások esetében. Ha a javítás nem jelenik meg, lehet, hogy nem létezik a játék adott sorozatszámához és verziójához. Lehet, hogy frissítenie kell a játékot. @@ -898,5 +903,76 @@ Name: Név: + + + Can't apply cheats before the game is started + Nem lehet csalásokat alkalmazni, mielőtt a játék elindul. + + + + SettingsDialog + + + Save + Mentés + + + + Apply + Alkalmaz + + + + Restore Defaults + Alapértelmezett értékek visszaállítása + + + + Close + Bezárás + + + + GameListFrame + + + Icon + Ikon + + + + Name + Név + + + + Serial + Sorozatszám + + + + Region + Régió + + + + Firmware + Firmware + + + + Size + Méret + + + + Version + Verzió + + + + Path + Útvonal + diff --git a/src/qt_gui/translations/id.ts b/src/qt_gui/translations/id.ts index b8ce27cde53..6108ffa20b4 100644 --- a/src/qt_gui/translations/id.ts +++ b/src/qt_gui/translations/id.ts @@ -500,6 +500,11 @@ MainWindow + + + Game List + Daftar game + * Unsupported Vulkan Version @@ -851,7 +856,7 @@ DownloadComplete_MSG - Patch Berhasil Diunduh! Semua Patch yang tersedia untuk semua game telah diunduh, tidak perlu mengunduhnya satu per satu seperti yang terjadi pada Cheat. + Patch Berhasil Diunduh! Semua Patch yang tersedia untuk semua game telah diunduh, tidak perlu mengunduhnya satu per satu seperti yang terjadi pada Cheat. Jika patch tidak muncul, mungkin patch tersebut tidak ada untuk nomor seri dan versi game yang spesifik. Mungkin perlu memperbarui game. @@ -898,5 +903,76 @@ Name: Nama: + + + Can't apply cheats before the game is started + Tidak bisa menerapkan cheat sebelum permainan dimulai. + + + + SettingsDialog + + + Save + Simpan + + + + Apply + Terapkan + + + + Restore Defaults + Kembalikan Pengaturan Default + + + + Close + Tutup + + + + GameListFrame + + + Icon + Ikon + + + + Name + Nama + + + + Serial + Serial + + + + Region + Wilayah + + + + Firmware + Firmware + + + + Size + Ukuran + + + + Version + Versi + + + + Path + Jalur + \ No newline at end of file diff --git a/src/qt_gui/translations/it.ts b/src/qt_gui/translations/it.ts index 380a8e43b8d..39cb35dd3f5 100644 --- a/src/qt_gui/translations/it.ts +++ b/src/qt_gui/translations/it.ts @@ -500,6 +500,11 @@ MainWindow + + + Game List + Elenco giochi + * Unsupported Vulkan Version @@ -851,7 +856,7 @@ DownloadComplete_MSG - Patch scaricata con successo! Vengono scaricate tutte le patch disponibili per tutti i giochi, non è necessario scaricarle singolarmente per ogni gioco come nel caso dei trucchi. + Patch scaricata con successo! Vengono scaricate tutte le patch disponibili per tutti i giochi, non è necessario scaricarle singolarmente per ogni gioco come nel caso dei trucchi. Se la patch non appare, potrebbe essere che non esista per il numero di serie e la versione specifica del gioco. Potrebbe essere necessario aggiornare il gioco. @@ -898,5 +903,76 @@ Name: Nome: + + + Can't apply cheats before the game is started + Non è possibile applicare i trucchi prima dell'inizio del gioco. + + + SettingsDialog + + + Save + Salva + + + + Apply + Applica + + + + Restore Defaults + Ripristina Impostazioni Predefinite + + + + Close + Chiudi + + + + GameListFrame + + + Icon + Icona + + + + Name + Nome + + + + Serial + Seriale + + + + Region + Regione + + + + Firmware + Firmware + + + + Size + Dimensione + + + + Version + Versione + + + + Path + Percorso + + diff --git a/src/qt_gui/translations/ja_JP.ts b/src/qt_gui/translations/ja_JP.ts index 3d62de0def2..680d4ebd579 100644 --- a/src/qt_gui/translations/ja_JP.ts +++ b/src/qt_gui/translations/ja_JP.ts @@ -500,6 +500,11 @@ MainWindow + + + Game List + ゲームリスト + * Unsupported Vulkan Version @@ -851,7 +856,7 @@ DownloadComplete_MSG - パッチが正常にダウンロードされました! すべてのゲームに利用可能なパッチがダウンロードされました。チートとは異なり、各ゲームごとに個別にダウンロードする必要はありません。 + パッチが正常にダウンロードされました! すべてのゲームに利用可能なパッチがダウンロードされました。チートとは異なり、各ゲームごとに個別にダウンロードする必要はありません。 パッチが表示されない場合、特定のシリアル番号とバージョンのゲームには存在しない可能性があります。ゲームを更新する必要があるかもしれません。 @@ -898,5 +903,76 @@ Name: 名前: - + + + Can't apply cheats before the game is started + ゲームが開始される前にチートを適用することはできません。 + + + + SettingsDialog + + + Save + 保存 + + + + Apply + 適用 + + + + Restore Defaults + デフォルトに戻す + + + + Close + 閉じる + + + + GameListFrame + + + Icon + アイコン + + + + Name + 名前 + + + + Serial + シリアル + + + + Region + 地域 + + + + Firmware + ファームウェア + + + + Size + サイズ + + + + Version + バージョン + + + + Path + パス + + \ No newline at end of file diff --git a/src/qt_gui/translations/ko_KR.ts b/src/qt_gui/translations/ko_KR.ts index f7f171dc667..a167311b907 100644 --- a/src/qt_gui/translations/ko_KR.ts +++ b/src/qt_gui/translations/ko_KR.ts @@ -500,6 +500,11 @@ MainWindow + + + Game List + Game List + * Unsupported Vulkan Version @@ -851,7 +856,7 @@ DownloadComplete_MSG - Patches Downloaded Successfully! All Patches available for all games have been downloaded, there is no need to download them individually for each game as happens in Cheats. + Patches Downloaded Successfully! All Patches available for all games have been downloaded, there is no need to download them individually for each game as happens in Cheats. If the patch does not appear, it may be that it does not exist for the specific serial and version of the game. It may be necessary to update the game. @@ -898,5 +903,76 @@ Name: Name: - + + + Can't apply cheats before the game is started + Can't apply cheats before the game is started. + + + + SettingsDialog + + + Save + Save + + + + Apply + Apply + + + + Restore Defaults + Restore Defaults + + + + Close + Close + + + + GameListFrame + + + Icon + Icon + + + + Name + Name + + + + Serial + Serial + + + + Region + Region + + + + Firmware + Firmware + + + + Size + Size + + + + Version + Version + + + + Path + Path + + \ No newline at end of file diff --git a/src/qt_gui/translations/lt_LT.ts b/src/qt_gui/translations/lt_LT.ts index 7aa4402e683..2c86ec0a003 100644 --- a/src/qt_gui/translations/lt_LT.ts +++ b/src/qt_gui/translations/lt_LT.ts @@ -500,6 +500,11 @@ MainWindow + + + Game List + Žaidimų sąrašas + * Unsupported Vulkan Version @@ -851,7 +856,7 @@ DownloadComplete_MSG - Pataisos sėkmingai atsisiųstos! Visos pataisos visiems žaidimams buvo atsisiųstos, nebėra reikalo jas atsisiųsti atskirai kiekvienam žaidimui, kaip tai vyksta su sukčiavimais. + Pataisos sėkmingai atsisiųstos! Visos pataisos visiems žaidimams buvo atsisiųstos, nebėra reikalo jas atsisiųsti atskirai kiekvienam žaidimui, kaip tai vyksta su sukčiavimais. Jei pleistras nepasirodo, gali būti, kad jo nėra tam tikram žaidimo serijos numeriui ir versijai. Gali prireikti atnaujinti žaidimą. @@ -898,5 +903,76 @@ Name: Pavadinimas: + + + Can't apply cheats before the game is started + Negalima taikyti sukčiavimų prieš pradedant žaidimą. + + + + SettingsDialog + + + Save + Įrašyti + + + + Apply + Taikyti + + + + Restore Defaults + Atkurti numatytuosius nustatymus + + + + Close + Uždaryti + + + + GameListFrame + + + Icon + Ikona + + + + Name + Vardas + + + + Serial + Serijinis numeris + + + + Region + Regionas + + + + Firmware + Firmvare + + + + Size + Dydis + + + + Version + Versija + + + + Path + Kelias + \ No newline at end of file diff --git a/src/qt_gui/translations/nb.ts b/src/qt_gui/translations/nb.ts index 76cad45b29c..b62791e05e7 100644 --- a/src/qt_gui/translations/nb.ts +++ b/src/qt_gui/translations/nb.ts @@ -500,6 +500,11 @@ MainWindow + + + Game List + Spilliste + * Unsupported Vulkan Version @@ -851,7 +856,7 @@ DownloadComplete_MSG - Oppdateringer lastet ned vellykket! Alle oppdateringer tilgjengelige for alle spill har blitt lastet ned, det er ikke nødvendig å laste dem ned individuelt for hvert spill som skjer med jukser. + Oppdateringer lastet ned vellykket! Alle oppdateringer tilgjengelige for alle spill har blitt lastet ned, det er ikke nødvendig å laste dem ned individuelt for hvert spill som skjer med jukser. Hvis oppdateringen ikke vises, kan det hende at den ikke finnes for den spesifikke serienummeret og versjonen av spillet. Det kan være nødvendig å oppdatere spillet. @@ -898,5 +903,76 @@ Name: Navn: - + + + Can't apply cheats before the game is started + Kan ikke bruke juksetriks før spillet er startet. + + + + SettingsDialog + + + Save + Lag + + + + Apply + Bruk + + + + Restore Defaults + Gjenopprett standardinnstillinger + + + + Close + Lukk + + + + GameListFrame + + + Icon + Ikon + + + + Name + Navn + + + + Serial + Serienummer + + + + Region + Region + + + + Firmware + Firmware + + + + Size + Størrelse + + + + Version + Versjon + + + + Path + Sti + + \ No newline at end of file diff --git a/src/qt_gui/translations/nl.ts b/src/qt_gui/translations/nl.ts index b00460479a8..3d5edfc5d3d 100644 --- a/src/qt_gui/translations/nl.ts +++ b/src/qt_gui/translations/nl.ts @@ -500,6 +500,11 @@ MainWindow + + + Game List + Lijst met spellen + * Unsupported Vulkan Version @@ -851,7 +856,7 @@ DownloadComplete_MSG - Patches succesvol gedownload! Alle beschikbare patches voor alle spellen zijn gedownload. Het is niet nodig om ze afzonderlijk te downloaden voor elk spel dat cheats heeft. + Patches succesvol gedownload! Alle beschikbare patches voor alle spellen zijn gedownload. Het is niet nodig om ze afzonderlijk te downloaden voor elk spel dat cheats heeft. Als de patch niet verschijnt, kan het zijn dat deze niet bestaat voor het specifieke serienummer en de versie van het spel. Het kan nodig zijn om het spel bij te werken. @@ -898,5 +903,76 @@ Name: Naam: + + + Can't apply cheats before the game is started + Je kunt geen cheats toepassen voordat het spel is gestart. + + + SettingsDialog + + + Save + Opslaan + + + + Apply + Toepassen + + + + Restore Defaults + Standaardinstellingen herstellen + + + + Close + Sluiten + + + + GameListFrame + + + Icon + Pictogram + + + + Name + Naam + + + + Serial + Serienummer + + + + Region + Regio + + + + Firmware + Firmware + + + + Size + Grootte + + + + Version + Versie + + + + Path + Pad + + \ No newline at end of file diff --git a/src/qt_gui/translations/pl_PL.ts b/src/qt_gui/translations/pl_PL.ts index 80f0aa5c41d..af8330bbdda 100644 --- a/src/qt_gui/translations/pl_PL.ts +++ b/src/qt_gui/translations/pl_PL.ts @@ -93,7 +93,7 @@ Cheats / Patches - Kody / Patche + Kody / poprawki @@ -251,12 +251,12 @@ Game Install Directory - Katalog zainstalowanej gry + Katalog zainstalowanych gier Download Cheats/Patches - Pobierz Kody / Patche + Pobierz kody / poprawki @@ -349,7 +349,7 @@ Open Folder - Open Folder + Otwórz folder @@ -450,7 +450,7 @@ Vblank Divider - Dzielnik pionowego blankingu (Vblank) + Dzielnik przerwy pionowej (Vblank) @@ -500,6 +500,11 @@ MainWindow + + + Game List + Lista gier + * Unsupported Vulkan Version @@ -563,7 +568,7 @@ PKG Extraction - Ekstrakcja PKG + Wypakowywanie PKG @@ -628,12 +633,12 @@ Extracting PKG %1/%2 - Ekstrakcja PKG %1/%2 + Wypakowywanie PKG %1/%2 Extraction Finished - Ekstrakcja zakończona + Wypakowywanie zakończone @@ -651,7 +656,7 @@ Cheats / Patches - Kody / Poprawki + Kody / poprawki @@ -771,7 +776,7 @@ Failed to parse XML: - Nie udało się sparsować XML: + Nie udało się przeanalizować XML: @@ -821,7 +826,7 @@ CheatsNotFound_MSG - Nie znaleziono kodów do tej gry w tej wersji wybranego repozytorium.Spróbuj innego repozytorium lub innej wersji gry. + Nie znaleziono kodów do tej gry w tej wersji wybranego repozytorium. Spróbuj innego repozytorium lub innej wersji gry. @@ -851,12 +856,12 @@ DownloadComplete_MSG - Poprawki zostały pomyślnie pobrane! Wszystkie dostępne poprawki dla wszystkich gier zostały pobrane. Nie ma potrzeby pobierania ich osobno dla każdej gry, która ma kody. + Poprawki zostały pomyślnie pobrane! Wszystkie dostępne poprawki dla wszystkich gier zostały pobrane. Nie ma potrzeby pobierania ich osobno dla każdej gry, która ma kody. Jeśli poprawka się nie pojawia, możliwe, że nie istnieje dla konkretnego numeru seryjnego i wersji gry. Może być konieczne zaktualizowanie gry. Failed to parse JSON data from HTML. - Nie udało się sparsować danych JSON z HTML. + Nie udało się przeanalizować danych JSON z HTML. @@ -896,7 +901,78 @@ Failed to parse JSON: - Nie udało się sparsować JSON: + Nie udało się przeanlizować JSON: + + + Can't apply cheats before the game is started + Nie można zastosować kodów przed uruchomieniem gry. + - \ No newline at end of file + + SettingsDialog + + + Save + Zapisz + + + + Apply + Zastosuj + + + + Restore Defaults + Przywróć ustawienia domyślne + + + + Close + Zamknij + + + + GameListFrame + + + Icon + Ikona + + + + Name + Nazwa + + + + Serial + Numer seryjny + + + + Region + Region + + + + Firmware + Oprogramowanie + + + + Size + Rozmiar + + + + Version + Wersja + + + + Path + Ścieżka + + + diff --git a/src/qt_gui/translations/pt_BR.ts b/src/qt_gui/translations/pt_BR.ts index 8b4538b9db4..e774a30b4a5 100644 --- a/src/qt_gui/translations/pt_BR.ts +++ b/src/qt_gui/translations/pt_BR.ts @@ -500,6 +500,11 @@ MainWindow + + + Game List + Lista de Jogos + * Unsupported Vulkan Version @@ -851,7 +856,7 @@ DownloadComplete_MSG - Patches Baixados com Sucesso! Todos os patches disponíveis para todos os jogos foram baixados, não é necessário baixá-los individualmente para cada jogo como acontece com os Cheats. + Patches Baixados com Sucesso! Todos os patches disponíveis para todos os jogos foram baixados, não é necessário baixá-los individualmente para cada jogo como acontece com os Cheats. Se o patch não aparecer, pode ser que ele não exista para o número de série e a versão específicos do jogo. Pode ser necessário atualizar o jogo. @@ -897,6 +902,77 @@ Name: Nome: + + + + Can't apply cheats before the game is started + Não é possível aplicar cheats antes que o jogo comece. + + + + SettingsDialog + + + Save + Salvar + + + + Apply + Aplicar + + + + Restore Defaults + Restaurar Padrões + + + + Close + Fechar + + + + GameListFrame + + + Icon + Icone + + + + Name + Nome + + + + Serial + Serial + + + + Region + Região + + + + Firmware + Firmware + + + + Size + Tamanho + + + + Version + Versão + + + + Path + Diretório \ No newline at end of file diff --git a/src/qt_gui/translations/ro_RO.ts b/src/qt_gui/translations/ro_RO.ts index 8b2fda0c134..56df113f564 100644 --- a/src/qt_gui/translations/ro_RO.ts +++ b/src/qt_gui/translations/ro_RO.ts @@ -499,6 +499,11 @@ + + + Game List + Lista jocurilor + MainWindow @@ -851,7 +856,7 @@ DownloadComplete_MSG - Patches descărcate cu succes! Toate Patches disponibile pentru toate jocurile au fost descărcate; nu este nevoie să le descarci individual pentru fiecare joc, așa cum se întâmplă cu Cheats. + Patches descărcate cu succes! Toate Patches disponibile pentru toate jocurile au fost descărcate; nu este nevoie să le descarci individual pentru fiecare joc, așa cum se întâmplă cu Cheats. Dacă patch-ul nu apare, este posibil să nu existe pentru seria și versiunea specifică a jocului. Poate fi necesar să actualizați jocul. @@ -898,5 +903,76 @@ Name: Nume: + + + Can't apply cheats before the game is started + Nu poți aplica cheats înainte ca jocul să înceapă. + + + SettingsDialog + + + Save + Salvează + + + + Apply + Aplică + + + + Restore Defaults + Restabilește Impozitivele + + + + Close + Închide + + + + GameListFrame + + + Icon + Icon + + + + Name + Nume + + + + Serial + Serie + + + + Region + Regiune + + + + Firmware + Firmware + + + + Size + Dimensiune + + + + Version + Versiune + + + + Path + Drum + + \ No newline at end of file diff --git a/src/qt_gui/translations/ru_RU.ts b/src/qt_gui/translations/ru_RU.ts index 9e3446ad4cc..1eac3e515f5 100644 --- a/src/qt_gui/translations/ru_RU.ts +++ b/src/qt_gui/translations/ru_RU.ts @@ -500,6 +500,11 @@ MainWindow + + + Game List + Список игр + * Unsupported Vulkan Version @@ -851,7 +856,7 @@ DownloadComplete_MSG - Патчи успешно скачаны! Все доступные патчи для всех игр были скачаны, нет необходимости скачивать их по отдельности для каждой игры, как это происходит с читами. + Патчи успешно скачаны! Все доступные патчи для всех игр были скачаны, нет необходимости скачивать их по отдельности для каждой игры, как это происходит с читами. Если патч не появляется, возможно, его не существует для конкретного серийного номера и версии игры. Возможно, потребуется обновить игру. @@ -898,5 +903,76 @@ Name: Имя: + + + Can't apply cheats before the game is started + Невозможно применить читы до начала игрыs + + + + SettingsDialog + + + Save + Сохранить + + + + Apply + Применить + + + + Restore Defaults + Восстановить умолчания + + + + Close + Закрыть + + + + GameListFrame + + + Icon + Иконка + + + + Name + Название + + + + Serial + Серийный номер + + + + Region + Регион + + + + Firmware + Прошивка + + + + Size + Размер + + + + Version + Версия + + + + Path + Путь + \ No newline at end of file diff --git a/src/qt_gui/translations/sq.ts b/src/qt_gui/translations/sq.ts new file mode 100644 index 00000000000..85110c6ae58 --- /dev/null +++ b/src/qt_gui/translations/sq.ts @@ -0,0 +1,978 @@ + + + + AboutDialog + + + About shadPS4 + Rreth shadPS4 + + + + shadPS4 + shadPS4 + + + + shadPS4 is an experimental open-source emulator for the PlayStation 4. + shadPS4 është një emulator eksperimental me burim të hapur për PlayStation 4. + + + + This software should not be used to play games you have not legally obtained. + Ky program nuk duhet përdorur për të luajtur lojëra që nuk ke marrë ligjërisht. + + + + ElfViewer + + + Open Folder + Hap Dosjen + + + + GameInfoClass + + + Loading game list, please wait :3 + Po ngarkohet lista e lojërave, të lutem prit :3 + + + + Cancel + Anulo + + + + Loading... + Duke ngarkuar... + + + + GameInstallDialog + + + shadPS4 - Choose directory + shadPS4 - Përzgjidh dosjen + + + + Directory to install games + Dosja ku do instalohen lojërat + + + + Browse + Shfleto + + + + Error + Gabim + + + + The value for location to install games is not valid. + Vlera për vendndodhjen e instalimit të lojërave nuk është e vlefshme. + + + + GuiContextMenus + + + Create Shortcut + Krijo Shkurtore + + + + Open Game Folder + Hap Dosjen e Lojës + + + + Cheats / Patches + Mashtrime / Arna + + + + SFO Viewer + Shikuesi i SFO + + + + Trophy Viewer + Shikuesi i Trofeve + + + + Copy info + Kopjo informacionin + + + + Copy Name + Kopjo Emrin + + + + Copy Serial + Kopjo Serikun + + + + Copy All + Kopjo të Gjitha + + + + Shortcut creation + Krijim i shkurtores + + + + Shortcut created successfully!\n %1 + Shkurtorja u krijua me sukses!\n %1 + + + + Error + Gabim + + + + Error creating shortcut!\n %1 + Gabim në krijimin e shkurtores!\n %1 + + + + Install PKG + Instalo PKG + + + + MainWindow + + + Open/Add Elf Folder + Hap/Shto Dosje ELF + + + + Install Packages (PKG) + Instalo Paketat (PKG) + + + + Boot Game + Nis Lojën + + + + About shadPS4 + Rreth shadPS4 + + + + Configure... + Formëso... + + + + Install application from a .pkg file + Instalo aplikacionin nga një skedar .pkg + + + + Recent Games + Lojërat e fundit + + + + Exit + Dil + + + + Exit shadPS4 + Dil nga shadPS4 + + + + Exit the application. + Dil nga aplikacioni. + + + + Show Game List + Shfaq Listën e Lojërave + + + + Game List Refresh + Rifresko Listën e Lojërave + + + + Tiny + Të vockla + + + + Small + Të vogla + + + + Medium + Të mesme + + + + Large + Të mëdha + + + + List View + Pamja e Listës + + + + Grid View + Pamja e Rrjetës + + + + Elf Viewer + Shikuesi i Elf + + + + Game Install Directory + Dosja e Instalimit të Lojës + + + + Download Cheats/Patches + Shkarko Mashtrime/Arna + + + + Dump Game List + Zbraz Listën e Lojërave + + + + PKG Viewer + Shikuesi i PKG + + + + Search... + Kërko... + + + + File + Skedari + + + + View + Pamja + + + + Game List Icons + Ikonat e Listës së Lojërave + + + + Game List Mode + Mënyra e Listës së Lojërave + + + + Settings + Cilësimet + + + + Utils + Shërbimet + + + + Themes + Motivet + + + + About + Rreth + + + + Dark + E errët + + + + Light + E çelët + + + + Green + E gjelbër + + + + Blue + E kaltër + + + + Violet + Vjollcë + + + + toolBar + Shiriti i veglave + + + + PKGViewer + + + Open Folder + Hap Dosjen + + + + TrophyViewer + + + Trophy Viewer + Shikuesi i Trofeve + + + + SettingsDialog + + + Settings + Cilësimet + + + + General + Të përgjithshme + + + + System + Sistemi + + + + Console Language + Gjuha e Konsolës + + + + Emulator Language + Gjuha e emulatorit + + + + Emulator + Emulatori + + + + Enable Fullscreen + Aktivizo Ekranin e plotë + + + + Show Splash + Shfaq Pamjen e nisjes + + + + Is PS4 Pro + Mënyra PS4 Pro + + + + Username + Nofka + + + + Logger + Regjistruesi i të dhënave + + + + Log Type + Lloji i Ditarit + + + + Log Filter + Filtri i Ditarit + + + + Graphics + Grafika + + + + Graphics Device + Pajisja e Grafikës + + + + Width + Gjerësia + + + + Height + Lartësia + + + + Vblank Divider + Ndarës Vblank + + + + Advanced + Të përparuara + + + + Enable Shaders Dumping + Aktivizo Zbrazjen e Shaders-ave + + + + Enable NULL GPU + Aktivizo GPU-në NULL + + + + Enable PM4 Dumping + Aktivizo Zbrazjen PM4 + + + + Debug + Korrigjim + + + + Enable Debug Dumping + Aktivizo Zbrazjen për Korrigjim + + + + Enable Vulkan Validation Layers + Aktivizo Shtresat e Vlefshmërisë Vulkan + + + + Enable Vulkan Synchronization Validation + Aktivizo Vërtetimin e Sinkronizimit Vulkan + + + + Enable RenderDoc Debugging + Aktivizo Korrigjimin RenderDoc + + + + MainWindow + + + Game List + Lista e lojërave + + + + * Unsupported Vulkan Version + * Version i pambështetur i Vulkan + + + + Download Cheats For All Installed Games + Shkarko Mashtrime Për Të Gjitha Lojërat e Instaluara + + + + Download Patches For All Games + Shkarko Arna Për Të Gjitha Lojërat e Instaluara + + + + Download Complete + Shkarkimi Përfundoi + + + + You have downloaded cheats for all the games you have installed. + Ke shkarkuar mashtrimet për të gjitha lojërat që ke instaluar. + + + + Patches Downloaded Successfully! + Arnat u shkarkuan me sukses! + + + + All Patches available for all games have been downloaded. + Të gjitha arnat e ofruara për të gjitha lojërat janë shkarkuar. + + + + Games: + Lojërat: + + + + PKG File (*.PKG) + Skedar PKG (*.PKG) + + + + ELF files (*.bin *.elf *.oelf) + Skedarë ELF (*.bin *.elf *.oelf) + + + + Game Boot + Nis Lojën + + + + Only one file can be selected! + Mund të përzgjidhet vetëm një skedar! + + + + PKG Extraction + Nxjerrja e PKG-së + + + + Patch detected! + U zbulua një arnë! + + + + PKG and Game versions match: + PKG-ja dhe versioni i Lojës përputhen: + + + + Would you like to overwrite? + Dëshiron të mbishkruash? + + + + PKG Version %1 is older than installed version: + Versioni %1 i PKG-së është më i vjetër se versioni i instaluar: + + + + Game is installed: + Loja është instaluar: + + + + Would you like to install Patch: + Dëshiron të instalosh Arnën: + + + + DLC Installation + Instalimi i DLC-ve + + + + Would you like to install DLC: %1? + Dëshiron të instalosh DLC-në: %1? + + + + DLC already installed: + DLC-ja është instaluar tashmë: + + + + Game already installed + Loja është instaluar tashmë + + + + PKG is a patch, please install the game first! + PKG-ja është një arnë, të lutem instalo lojën fillimisht! + + + + PKG ERROR + GABIM PKG + + + + Extracting PKG %1/%2 + Po nxirret PKG-ja %1/%2 + + + + Extraction Finished + Nxjerrja Përfundoi + + + + Game successfully installed at %1 + Loja u instalua me sukses në %1 + + + + File doesn't appear to be a valid PKG file + Skedari nuk duket si skedar PKG i vlefshëm + + + + CheatsPatches + + + Cheats / Patches + Mashtrime / Arna + + + + defaultTextEdit_MSG + Mashtrimet/Arnat janë eksperimentale.\nPërdori me kujdes.\n\nShkarko mashtrimet individualisht duke zgjedhur depon dhe duke klikuar butonin e shkarkimit.\nNë skedën Arna, mund t'i shkarkosh të gjitha arnat menjëherë, të zgjidhësh cilat dëshiron të përdorësh dhe të ruash zgjedhjen tënde.\n\nMeqenëse ne nuk zhvillojmë Mashtrimet/Arnat,\ntë lutem raporto problemet te autori i mashtrimit.\n\nKe krijuar një mashtrim të ri? Vizito:\nhttps://github.com/shadps4-emu/ps4_cheats + + + + No Image Available + Nuk ofrohet asnjë imazh + + + + Serial: + Seriku: + + + + Version: + Versioni: + + + + Size: + Madhësia: + + + + Select Cheat File: + Përzgjidh Skedarin e Mashtrimit: + + + + Repository: + Depo: + + + + Download Cheats + Shkarko Mashtrimet + + + + Delete File + Fshi Skedarin + + + + No files selected. + Nuk u zgjodh asnjë skedar. + + + + You can delete the cheats you don't want after downloading them. + Mund t'i fshish mashtrimet që nuk dëshiron pasi t'i kesh shkarkuar. + + + + Do you want to delete the selected file?\n%1 + Dëshiron të fshish skedarin e përzgjedhur?\n%1 + + + + Select Patch File: + Përzgjidh Skedarin e Arnës: + + + + Download Patches + Shkarko Arnat + + + + Save + Ruaj + + + + Cheats + Mashtrime + + + + Patches + Arna + + + + Error + Gabim + + + + No patch selected. + Asnjë arnë e përzgjedhur. + + + + Unable to open files.json for reading. + files.json nuk mund të hapet për lexim. + + + + No patch file found for the current serial. + Nuk u gjet asnjë skedar patch për serikun aktual. + + + + Unable to open the file for reading. + Skedari nuk mund të hapet për lexim. + + + + Unable to open the file for writing. + Skedari nuk mund të hapet për shkrim. + + + + Failed to parse XML: + Analiza e XML-së dështoi: + + + + Success + Sukses + + + + Options saved successfully. + Rregullimet u ruajtën me sukses. + + + + Invalid Source + Burim i pavlefshëm + + + + The selected source is invalid. + Burimi i përzgjedhur është i pavlefshëm. + + + + File Exists + Skedari Ekziston + + + + File already exists. Do you want to replace it? + Skedari ekziston tashmë. Dëshiron ta zëvendësosh? + + + + Failed to save file: + Ruajtja e skedarit dështoi: + + + + Failed to download file: + Shkarkimi i skedarit dështoi: + + + + Cheats Not Found + Mashtrimet nuk u gjetën + + + + CheatsNotFound_MSG + Nuk u gjetën mashtrime për këtë lojë në këtë version të depove të përzgjedhura, provo një depo tjetër ose një version tjetër të lojës. + + + + Cheats Downloaded Successfully + Mashtrimet u shkarkuan me sukses + + + + CheatsDownloadedSuccessfully_MSG + Ke shkarkuar me sukses mashtrimet për këtë version të lojës nga depoja e përzgjedhur. Mund të provosh të shkarkosh nga një depo tjetër, nëse ofrohet do të jetë e mundur gjithashtu ta përdorësh duke përzgjedhur skedarin nga lista. + + + + Failed to save: + Ruajtja dështoi: + + + + Failed to download: + Shkarkimi dështoi: + + + + Download Complete + Shkarkimi përfundoi + + + + DownloadComplete_MSG + Arnat u shkarkuan me sukses! Të gjitha arnat e ofruara për të gjitha lojërat janë shkarkuar, nuk ka nevojë t'i shkarkosh ato individualisht për secilën lojë siç ndodh me Mashtrimet. Nëse patch-i nuk shfaqet, mund të mos ekzistojë për numrin e serisë dhe versionin specifik të lojës. Mund të jetë e nevojshme të përditësosh lojën. + + + + Failed to parse JSON data from HTML. + Analiza e të dhënave JSON nga HTML dështoi. + + + + Failed to retrieve HTML page. + Gjetja e faqes HTML dështoi. + + + + Failed to open file: + Hapja e skedarit dështoi: + + + + XML ERROR: + GABIM XML: + + + + Failed to open files.json for writing + Hapja e files.json për shkrim dështoi + + + + Author: + Autori: + + + + Directory does not exist: + Dosja nuk ekziston: + + + + Failed to open files.json for reading. + Hapja e files.json për lexim dështoi. + + + + Name: + Emri: + + + + Can't apply cheats before the game is started + Nuk mund të zbatohen mashtrime para se të fillojë loja. + + + + SettingsDialog + + + Save + Ruaj + + + + Apply + Zbato + + + + Restore Defaults + Rikthe paracaktimet + + + + Close + Mbyll + + + + GameListFrame + + + Icon + Ikona + + + + Name + Emri + + + + Serial + Seriku + + + + Region + Rajoni + + + + Firmware + Firmueri + + + + Size + Madhësia + + + + Version + Versioni + + + + Path + Shtegu + + + diff --git a/src/qt_gui/translations/tr_TR.ts b/src/qt_gui/translations/tr_TR.ts index e11a2d960a3..83f62c85e3d 100644 --- a/src/qt_gui/translations/tr_TR.ts +++ b/src/qt_gui/translations/tr_TR.ts @@ -500,6 +500,11 @@ MainWindow + + + Game List + Oyun Listesi + * Unsupported Vulkan Version @@ -851,7 +856,7 @@ DownloadComplete_MSG - Yamalar başarıyla indirildi! Tüm oyunlar için mevcut tüm yamalar indirildi, her oyun için ayrı ayrı indirme yapmanız gerekmez, hilelerle olduğu gibi. + Yamalar başarıyla indirildi! Tüm oyunlar için mevcut tüm yamalar indirildi, her oyun için ayrı ayrı indirme yapmanız gerekmez, hilelerle olduğu gibi. Yamanın görünmemesi durumunda, belirli seri numarası ve oyun sürümü için mevcut olmayabilir. Oyunu güncellemeniz gerekebilir. @@ -969,5 +974,76 @@ Apply Changes Değişiklikleri Uygula + + + Can't apply cheats before the game is started + Hileleri oyuna başlamadan önce uygulayamazsınız. + + + SettingsDialog + + + Save + Kaydet + + + + Apply + Uygula + + + + Restore Defaults + Varsayılanları Geri Yükle + + + + Close + Kapat + + + + GameListFrame + + + Icon + Simge + + + + Name + Ad + + + + Serial + Seri Numarası + + + + Region + Bölge + + + + Firmware + Yazılım + + + + Size + Boyut + + + + Version + Sürüm + + + + Path + Yol + + \ No newline at end of file diff --git a/src/qt_gui/translations/vi_VN.ts b/src/qt_gui/translations/vi_VN.ts index aead45a63c4..017e61af52d 100644 --- a/src/qt_gui/translations/vi_VN.ts +++ b/src/qt_gui/translations/vi_VN.ts @@ -500,6 +500,11 @@ MainWindow + + + Game List + Danh sách trò chơi + * Unsupported Vulkan Version @@ -851,7 +856,7 @@ DownloadComplete_MSG - Bản vá đã tải xuống thành công! Tất cả các bản vá có sẵn cho tất cả các trò chơi đã được tải xuống, không cần tải xuống riêng lẻ cho mỗi trò chơi như trong Cheat. + Bản vá đã tải xuống thành công! Tất cả các bản vá có sẵn cho tất cả các trò chơi đã được tải xuống, không cần tải xuống riêng lẻ cho mỗi trò chơi như trong Cheat. Nếu bản vá không xuất hiện, có thể là nó không tồn tại cho số seri và phiên bản cụ thể của trò chơi. Có thể bạn cần phải cập nhật trò chơi. @@ -898,5 +903,76 @@ Name: Tên: + + + Can't apply cheats before the game is started + Không thể áp dụng cheat trước khi trò chơi bắt đầu. + + + SettingsDialog + + + Save + Lưu + + + + Apply + Áp dụng + + + + Restore Defaults + Khôi phục cài đặt mặc định + + + + Close + Đóng + + + + GameListFrame + + + Icon + Biểu tượng + + + + Name + Tên + + + + Serial + Số seri + + + + Region + Khu vực + + + + Firmware + Phần mềm + + + + Size + Kích thước + + + + Version + Phiên bản + + + + Path + Đường dẫn + + \ No newline at end of file diff --git a/src/qt_gui/translations/zh_CN.ts b/src/qt_gui/translations/zh_CN.ts index a1b2523b83b..f8675ed01d7 100644 --- a/src/qt_gui/translations/zh_CN.ts +++ b/src/qt_gui/translations/zh_CN.ts @@ -6,7 +6,7 @@ About shadPS4 - About shadPS4 + 关于 shadPS4 @@ -16,12 +16,12 @@ shadPS4 is an experimental open-source emulator for the PlayStation 4. - shadPS4 is an experimental open-source emulator for the PlayStation 4. + shadPS4 是一款实验性质的开源 PlayStation 4模拟器软件。 This software should not be used to play games you have not legally obtained. - This software should not be used to play games you have not legally obtained. + 本软件不得用于运行未经合法授权而获得的游戏。 @@ -29,7 +29,7 @@ Open Folder - Open Folder + 打开文件夹 @@ -37,17 +37,17 @@ Loading game list, please wait :3 - Loading game list, please wait :3 + 加载游戏列表中, 请稍等 :3 Cancel - Cancel + 取消 Loading... - Loading... + 加载中... @@ -55,27 +55,27 @@ shadPS4 - Choose directory - shadPS4 - Choose directory + shadPS4 - 选择文件目录 Directory to install games - Directory to install games + 要安装游戏的目录 Browse - Browse + 浏览 Error - Error + 错误 The value for location to install games is not valid. - The value for location to install games is not valid. + 游戏安装位置无效。 @@ -83,72 +83,72 @@ Create Shortcut - Create Shortcut + 创建快捷方式 Open Game Folder - Open Game Folder + 打开游戏文件夹 Cheats / Patches - Zuòbì / Bǔdīng + 作弊码 / 补丁 SFO Viewer - SFO Viewer + SFO 查看器 Trophy Viewer - Trophy Viewer + Trophy 查看器 Copy info - Copy info + 复制信息 Copy Name - Copy Name + 复制名称 Copy Serial - Copy Serial + 复制序列号 Copy All - Copy All + 复制全部 Shortcut creation - Shortcut creation + 创建快捷方式 Shortcut created successfully!\n %1 - Shortcut created successfully!\n %1 + 创建快捷方式成功!\n %1 Error - Error + 错误 Error creating shortcut!\n %1 - Error creating shortcut!\n %1 + 创建快捷方式出错!\n %1 Install PKG - Install PKG + 安装 PKG @@ -156,162 +156,162 @@ Open/Add Elf Folder - Open/Add Elf Folder + 打开/添加Elf文件夹 Install Packages (PKG) - Install Packages (PKG) + 安装 Packages (PKG) Boot Game - Boot Game + 启动游戏 About shadPS4 - About shadPS4 + 关于 shadPS4 Configure... - Configure... + 设置... Install application from a .pkg file - Install application from a .pkg file + 从 .pkg 文件安装应用程序 Recent Games - Recent Games + 最近启动的游戏 Exit - Exit + 退出 Exit shadPS4 - Exit shadPS4 + 退出 shadPS4 Exit the application. - Exit the application. + 退出应用程序. Show Game List - Show Game List + 显示游戏列表 Game List Refresh - Game List Refresh + 刷新游戏列表 Tiny - Tiny + 微小 Small - Small + Medium - Medium + 中等 Large - Large + 巨大 List View - List View + 列表视图 Grid View - Grid View + 表格视图 Elf Viewer - Elf Viewer + Elf 查看器 Game Install Directory - Game Install Directory + 游戏安装目录 Download Cheats/Patches - Xiàzài Zuòbì / Bǔdīng + 下载作弊码/补丁 Dump Game List - Dump Game List + 转储游戏列表 PKG Viewer - PKG Viewer + PKG 查看器 Search... - Search... + 搜索... File - File + 文件 View - View + 显示 Game List Icons - Game List Icons + 游戏列表图标 Game List Mode - Game List Mode + 游戏列表模式 Settings - Settings + 设置 Utils - Utils + 工具 Themes - Themes + 主题 About - About + 关于 @@ -341,7 +341,7 @@ toolBar - toolBar + 工具栏 @@ -349,7 +349,7 @@ Open Folder - Open Folder + 打开文件夹 @@ -357,7 +357,7 @@ Trophy Viewer - Trophy Viewer + Trophy 查看器 @@ -365,87 +365,87 @@ Settings - Settings + 设置 General - General + 通用 System - System + 系统 Console Language - Console Language + 主机语言 Emulator Language - Emulator Language + 模拟器语言 Emulator - Emulator + 模拟器 Enable Fullscreen - Enable Fullscreen + 启用全屏 Show Splash - Show Splash + 显示Splash Is PS4 Pro - Is PS4 Pro + 是否是 PS4 Pro Username - Username + 用户名 Logger - Logger + 日志 Log Type - Log Type + 日志类型 Log Filter - Log Filter + 日志过滤 Graphics - Graphics + 图像 Graphics Device - Graphics Device + 图像设备 Width - Width + 宽带 Height - Height + 高度 @@ -455,51 +455,56 @@ Advanced - Advanced + 高级 Enable Shaders Dumping - Enable Shaders Dumping + 启用着色器转储 Enable NULL GPU - Enable NULL GPU + 启用 NULL GPU Enable PM4 Dumping - Enable PM4 Dumping + 启用 PM4 转储 Debug - Debug + 调试 Enable Debug Dumping - Enable Debug Dumping + 启用调试转储 Enable Vulkan Validation Layers - Enable Vulkan Validation Layers + 启用 Vulkan 验证层 Enable Vulkan Synchronization Validation - Enable Vulkan Synchronization Validation + 启用 Vulkan 同步验证 Enable RenderDoc Debugging - Enable RenderDoc Debugging + 启用 RenderDoc 调试 MainWindow + + + Game List + 游戏列表 + * Unsupported Vulkan Version @@ -851,7 +856,7 @@ DownloadComplete_MSG - 补丁下载成功!所有可用的补丁已下载完成,无需像作弊码那样单独下载每个游戏的补丁。 + 补丁下载成功!所有可用的补丁已下载完成,无需像作弊码那样单独下载每个游戏的补丁。如果补丁没有出现,可能是该补丁不存在于特定的序列号和游戏版本中。可能需要更新游戏。 @@ -898,5 +903,76 @@ Name: 名称: - + + + Can't apply cheats before the game is started + 在游戏开始之前无法应用作弊。 + + + + SettingsDialog + + + Save + 保存 + + + + Apply + 应用 + + + + Restore Defaults + 恢复默认 + + + + Close + 关闭 + + + + GameListFrame + + + Icon + 图标 + + + + Name + 名称 + + + + Serial + 序列号 + + + + Region + 区域 + + + + Firmware + 固件 + + + + Size + 大小 + + + + Version + 版本 + + + + Path + 路径 + + \ No newline at end of file diff --git a/src/qt_gui/translations/zh_TW.ts b/src/qt_gui/translations/zh_TW.ts index 3836ed18a97..77fe691fe90 100644 --- a/src/qt_gui/translations/zh_TW.ts +++ b/src/qt_gui/translations/zh_TW.ts @@ -500,6 +500,11 @@ MainWindow + + + Game List + 遊戲列表 + * Unsupported Vulkan Version @@ -851,7 +856,7 @@ DownloadComplete_MSG - 修補檔下載成功!所有遊戲的修補檔已下載完成,無需像作弊碼那樣為每個遊戲單獨下載。 + 修補檔下載成功!所有遊戲的修補檔已下載完成,無需像作弊碼那樣為每個遊戲單獨下載。如果補丁未顯示,可能是該補丁不適用於特定的序號和遊戲版本。可能需要更新遊戲。 @@ -898,5 +903,76 @@ Name: 名稱: + + + Can't apply cheats before the game is started + 在遊戲開始之前無法應用作弊。 + + + SettingsDialog + + + Save + 儲存 + + + + Apply + 應用 + + + + Restore Defaults + 還原預設值 + + + + Close + 關閉 + + + + GameListFrame + + + Icon + 圖示 + + + + Name + 名稱 + + + + Serial + 序號 + + + + Region + 區域 + + + + Firmware + 固件 + + + + Size + 大小 + + + + Version + 版本 + + + + Path + 路徑 + + \ No newline at end of file diff --git a/src/qt_gui/trophy_viewer.cpp b/src/qt_gui/trophy_viewer.cpp index 57dce6b4e3d..8b96948cbdc 100644 --- a/src/qt_gui/trophy_viewer.cpp +++ b/src/qt_gui/trophy_viewer.cpp @@ -9,7 +9,8 @@ TrophyViewer::TrophyViewer(QString trophyPath, QString gameTrpPath) : QMainWindo this->setAttribute(Qt::WA_DeleteOnClose); tabWidget = new QTabWidget(this); gameTrpPath_ = gameTrpPath; - headers << "Trophy" + headers << "Unlocked" + << "Trophy" << "Name" << "Description" << "ID" @@ -21,11 +22,11 @@ TrophyViewer::TrophyViewer(QString trophyPath, QString gameTrpPath) : QMainWindo void TrophyViewer::PopulateTrophyWidget(QString title) { #ifdef _WIN32 - const auto trophyDir = Common::FS::GetUserPath(Common::FS::PathType::UserDir) / "game_data" / + const auto trophyDir = Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / title.toStdWString() / "TrophyFiles"; const auto trophyDirQt = QString::fromStdWString(trophyDir.wstring()); #else - const auto trophyDir = Common::FS::GetUserPath(Common::FS::PathType::UserDir) / "game_data" / + const auto trophyDir = Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / title.toStdString() / "TrophyFiles"; const auto trophyDirQt = QString::fromStdString(trophyDir.string()); #endif @@ -61,6 +62,7 @@ void TrophyViewer::PopulateTrophyWidget(QString title) { QStringList trpId; QStringList trpHidden; + QStringList trpUnlocked; QStringList trpType; QStringList trpPid; QStringList trophyNames; @@ -81,6 +83,15 @@ void TrophyViewer::PopulateTrophyWidget(QString title) { trpHidden.append(reader.attributes().value("hidden").toString()); trpType.append(reader.attributes().value("ttype").toString()); trpPid.append(reader.attributes().value("pid").toString()); + if (reader.attributes().hasAttribute("unlockstate")) { + if (reader.attributes().value("unlockstate").toString() == "unlocked") { + trpUnlocked.append("unlocked"); + } else { + trpUnlocked.append("locked"); + } + } else { + trpUnlocked.append("locked"); + } } if (reader.name().toString() == "name" && !trpId.isEmpty()) { @@ -93,7 +104,7 @@ void TrophyViewer::PopulateTrophyWidget(QString title) { } QTableWidget* tableWidget = new QTableWidget(this); tableWidget->setShowGrid(false); - tableWidget->setColumnCount(7); + tableWidget->setColumnCount(8); tableWidget->setHorizontalHeaderLabels(headers); tableWidget->setSelectionBehavior(QAbstractItemView::SelectRows); tableWidget->setSelectionMode(QAbstractItemView::SingleSelection); @@ -105,21 +116,22 @@ void TrophyViewer::PopulateTrophyWidget(QString title) { QTableWidgetItem* item = new QTableWidgetItem(); item->setData(Qt::DecorationRole, icon); item->setFlags(item->flags() & ~Qt::ItemIsEditable); - tableWidget->setItem(row, 0, item); + tableWidget->setItem(row, 1, item); if (!trophyNames.isEmpty() && !trophyDetails.isEmpty()) { - SetTableItem(tableWidget, row, 1, trophyNames[row]); - SetTableItem(tableWidget, row, 2, trophyDetails[row]); - SetTableItem(tableWidget, row, 3, trpId[row]); - SetTableItem(tableWidget, row, 4, trpHidden[row]); - SetTableItem(tableWidget, row, 5, GetTrpType(trpType[row].at(0))); - SetTableItem(tableWidget, row, 6, trpPid[row]); + SetTableItem(tableWidget, row, 0, trpUnlocked[row]); + SetTableItem(tableWidget, row, 2, trophyNames[row]); + SetTableItem(tableWidget, row, 3, trophyDetails[row]); + SetTableItem(tableWidget, row, 4, trpId[row]); + SetTableItem(tableWidget, row, 5, trpHidden[row]); + SetTableItem(tableWidget, row, 6, GetTrpType(trpType[row].at(0))); + SetTableItem(tableWidget, row, 7, trpPid[row]); } tableWidget->verticalHeader()->resizeSection(row, icon.height()); row++; } tableWidget->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents); int width = 16; - for (int i = 0; i < 7; i++) { + for (int i = 0; i < 8; i++) { width += tableWidget->horizontalHeader()->sectionSize(i); } tableWidget->resize(width, 720); diff --git a/src/sdl_window.cpp b/src/sdl_window.cpp index b83afd299c7..f3418c8f90b 100644 --- a/src/sdl_window.cpp +++ b/src/sdl_window.cpp @@ -9,6 +9,7 @@ #include "common/config.h" #include "common/version.h" #include "core/libraries/pad/pad.h" +#include "imgui/renderer/imgui_core.h" #include "input/controller.h" #include "sdl_window.h" #include "video_core/renderdoc.h" @@ -80,6 +81,10 @@ void WindowSDL::waitEvent() { return; } + if (ImGui::Core::ProcessEvent(&event)) { + return; + } + switch (event.type) { case SDL_EVENT_WINDOW_RESIZED: case SDL_EVENT_WINDOW_MAXIMIZED: @@ -115,6 +120,7 @@ void WindowSDL::waitEvent() { void WindowSDL::onResize() { SDL_GetWindowSizeInPixels(window, &width, &height); + ImGui::Core::OnResize(); } void WindowSDL::onKeyPress(const SDL_Event* event) { @@ -194,11 +200,6 @@ void WindowSDL::onKeyPress(const SDL_Event* event) { ax = Input::GetAxis(-0x80, 0x80, axisvalue); break; case SDLK_S: - if (event->key.mod == SDL_KMOD_LCTRL) { - // Trigger rdoc capture - VideoCore::TriggerCapture(); - break; - } axis = Input::Axis::LeftY; if (event->type == SDL_EVENT_KEY_DOWN) { axisvalue += 127; @@ -287,6 +288,12 @@ void WindowSDL::onKeyPress(const SDL_Event* event) { } } break; + case SDLK_F12: + if (event->type == SDL_EVENT_KEY_DOWN) { + // Trigger rdoc capture + VideoCore::TriggerCapture(); + } + break; default: break; } diff --git a/src/sdl_window.h b/src/sdl_window.h index 11ee92896cd..2a5aeb38c05 100644 --- a/src/sdl_window.h +++ b/src/sdl_window.h @@ -58,6 +58,10 @@ class WindowSDL { return is_open; } + [[nodiscard]] SDL_Window* GetSdlWindow() const { + return window; + } + WindowSystemInfo getWindowInfo() const { return window_info; } diff --git a/src/shader_recompiler/backend/bindings.h b/src/shader_recompiler/backend/bindings.h index 1b53c74eb17..510b0c0ec79 100644 --- a/src/shader_recompiler/backend/bindings.h +++ b/src/shader_recompiler/backend/bindings.h @@ -9,10 +9,10 @@ namespace Shader::Backend { struct Bindings { u32 unified{}; - u32 uniform_buffer{}; - u32 storage_buffer{}; - u32 texture{}; - u32 image{}; + u32 buffer{}; + u32 user_data{}; + + auto operator<=>(const Bindings&) const = default; }; } // namespace Shader::Backend diff --git a/src/shader_recompiler/backend/spirv/emit_spirv.cpp b/src/shader_recompiler/backend/spirv/emit_spirv.cpp index 98eac081905..8aa292b1c7f 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv.cpp @@ -184,6 +184,9 @@ void DefineEntryPoint(const IR::Program& program, EmitContext& ctx, Id main) { ctx.AddCapability(spv::Capability::Float16); ctx.AddCapability(spv::Capability::Int16); } + if (info.uses_fp64) { + ctx.AddCapability(spv::Capability::Float64); + } ctx.AddCapability(spv::Capability::Int64); if (info.has_storage_images || info.has_image_buffers) { ctx.AddCapability(spv::Capability::StorageImageExtendedFormats); @@ -208,9 +211,12 @@ void DefineEntryPoint(const IR::Program& program, EmitContext& ctx, Id main) { if (info.uses_group_quad) { ctx.AddCapability(spv::Capability::GroupNonUniformQuad); } + if (info.uses_group_ballot) { + ctx.AddCapability(spv::Capability::GroupNonUniformBallot); + } switch (program.info.stage) { case Stage::Compute: { - const std::array workgroup_size{program.info.workgroup_size}; + const std::array workgroup_size{ctx.runtime_info.cs_info.workgroup_size}; execution_model = spv::ExecutionModel::GLCompute; ctx.AddExecutionMode(main, spv::ExecutionMode::LocalSize, workgroup_size[0], workgroup_size[1], workgroup_size[2]); @@ -258,8 +264,9 @@ void PatchPhiNodes(const IR::Program& program, EmitContext& ctx) { } } // Anonymous namespace -std::vector EmitSPIRV(const Profile& profile, const IR::Program& program, u32& binding) { - EmitContext ctx{profile, program.info, binding}; +std::vector EmitSPIRV(const Profile& profile, const RuntimeInfo& runtime_info, + const IR::Program& program, Bindings& binding) { + EmitContext ctx{profile, runtime_info, program.info, binding}; const Id main{DefineMain(ctx, program)}; DefineEntryPoint(program, ctx, main); if (program.info.stage == Stage::Vertex) { @@ -326,6 +333,10 @@ void EmitGetVccHi(EmitContext& ctx) { UNREACHABLE_MSG("Unreachable instruction"); } +void EmitGetM0(EmitContext& ctx) { + UNREACHABLE_MSG("Unreachable instruction"); +} + void EmitSetScc(EmitContext& ctx) { UNREACHABLE_MSG("Unreachable instruction"); } @@ -350,4 +361,8 @@ void EmitSetVccHi(EmitContext& ctx) { UNREACHABLE_MSG("Unreachable instruction"); } +void EmitSetM0(EmitContext& ctx) { + UNREACHABLE_MSG("Unreachable instruction"); +} + } // namespace Shader::Backend::SPIRV diff --git a/src/shader_recompiler/backend/spirv/emit_spirv.h b/src/shader_recompiler/backend/spirv/emit_spirv.h index 4c862185fe4..5b8da44961e 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv.h +++ b/src/shader_recompiler/backend/spirv/emit_spirv.h @@ -4,12 +4,13 @@ #pragma once #include +#include "shader_recompiler/backend/bindings.h" #include "shader_recompiler/ir/program.h" #include "shader_recompiler/profile.h" namespace Shader::Backend::SPIRV { -[[nodiscard]] std::vector EmitSPIRV(const Profile& profile, const IR::Program& program, - u32& binding); +[[nodiscard]] std::vector EmitSPIRV(const Profile& profile, const RuntimeInfo& runtime_info, + const IR::Program& program, Bindings& binding); } // namespace Shader::Backend::SPIRV diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp index 1d553dc5688..a58b2778fd7 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp @@ -152,4 +152,20 @@ Id EmitImageAtomicExchange32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id co return ImageAtomicU32(ctx, inst, handle, coords, value, &Sirit::Module::OpAtomicExchange); } +Id EmitDataAppend(EmitContext& ctx, u32 gds_addr, u32 binding) { + auto& buffer = ctx.buffers[binding]; + const Id ptr = ctx.OpAccessChain(buffer.pointer_type, buffer.id, ctx.u32_zero_value, + ctx.ConstU32(gds_addr)); + const auto [scope, semantics]{AtomicArgs(ctx)}; + return ctx.OpAtomicIIncrement(ctx.U32[1], ptr, scope, semantics); +} + +Id EmitDataConsume(EmitContext& ctx, u32 gds_addr, u32 binding) { + auto& buffer = ctx.buffers[binding]; + const Id ptr = ctx.OpAccessChain(buffer.pointer_type, buffer.id, ctx.u32_zero_value, + ctx.ConstU32(gds_addr)); + const auto [scope, semantics]{AtomicArgs(ctx)}; + return ctx.OpAtomicIDecrement(ctx.U32[1], ptr, scope, semantics); +} + } // namespace Shader::Backend::SPIRV diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_bitwise_conversion.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_bitwise_conversion.cpp index 03a0a00f087..02ac74e19d9 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_bitwise_conversion.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_bitwise_conversion.cpp @@ -14,8 +14,8 @@ Id EmitBitCastU32F32(EmitContext& ctx, Id value) { return ctx.OpBitcast(ctx.U32[1], value); } -void EmitBitCastU64F64(EmitContext&) { - UNREACHABLE_MSG("SPIR-V Instruction"); +Id EmitBitCastU64F64(EmitContext& ctx, Id value) { + return ctx.OpBitcast(ctx.U64, value); } Id EmitBitCastF16U16(EmitContext& ctx, Id value) { @@ -38,6 +38,10 @@ Id EmitUnpackUint2x32(EmitContext& ctx, Id value) { return ctx.OpBitcast(ctx.U32[2], value); } +Id EmitPackFloat2x32(EmitContext& ctx, Id value) { + return ctx.OpBitcast(ctx.F64[1], value); +} + Id EmitPackFloat2x16(EmitContext& ctx, Id value) { return ctx.OpBitcast(ctx.U32[1], value); } diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp index 7bdc98de943..92279c5fb0f 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp @@ -59,7 +59,7 @@ Id OutputAttrPointer(EmitContext& ctx, IR::Attribute attr, u32 element) { case IR::Attribute::Position2: case IR::Attribute::Position3: { const u32 index = u32(attr) - u32(IR::Attribute::Position1); - return VsOutputAttrPointer(ctx, ctx.info.vs_outputs[index][element]); + return VsOutputAttrPointer(ctx, ctx.runtime_info.vs_info.outputs[index][element]); } case IR::Attribute::RenderTarget0: case IR::Attribute::RenderTarget1: @@ -86,7 +86,14 @@ Id OutputAttrPointer(EmitContext& ctx, IR::Attribute attr, u32 element) { } // Anonymous namespace Id EmitGetUserData(EmitContext& ctx, IR::ScalarReg reg) { - return ctx.ConstU32(ctx.info.user_data[static_cast(reg)]); + const u32 index = ctx.binding.user_data + ctx.info.ud_mask.Index(reg); + const u32 half = PushData::UdRegsIndex + (index >> 2); + const Id ud_ptr{ctx.OpAccessChain(ctx.TypePointer(spv::StorageClass::PushConstant, ctx.U32[1]), + ctx.push_data_block, ctx.ConstU32(half), + ctx.ConstU32(index & 3))}; + const Id ud_reg{ctx.OpLoad(ctx.U32[1], ud_ptr)}; + ctx.Name(ud_reg, fmt::format("ud_{}", u32(reg))); + return ud_reg; } void EmitGetThreadBitScalarReg(EmitContext& ctx) { @@ -133,10 +140,6 @@ Id EmitReadConstBuffer(EmitContext& ctx, u32 handle, Id index) { return ctx.OpLoad(buffer.data_types->Get(1), ptr); } -Id EmitReadConstBufferU32(EmitContext& ctx, u32 handle, Id index) { - return ctx.OpBitcast(ctx.U32[1], EmitReadConstBuffer(ctx, handle, index)); -} - Id EmitReadStepRate(EmitContext& ctx, int rate_idx) { return ctx.OpLoad( ctx.U32[1], ctx.OpAccessChain(ctx.TypePointer(spv::StorageClass::PushConstant, ctx.U32[1]), @@ -222,12 +225,8 @@ void EmitSetAttribute(EmitContext& ctx, IR::Attribute attr, Id value, u32 elemen ctx.OpStore(pointer, ctx.OpBitcast(ctx.F32[1], value)); } -Id EmitLoadBufferU32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address) { - return EmitLoadBufferF32(ctx, inst, handle, address); -} - template -static Id EmitLoadBufferF32xN(EmitContext& ctx, u32 handle, Id address) { +static Id EmitLoadBufferU32xN(EmitContext& ctx, u32 handle, Id address) { auto& buffer = ctx.buffers[handle]; address = ctx.OpIAdd(ctx.U32[1], address, buffer.offset); const Id index = ctx.OpShiftRightLogical(ctx.U32[1], address, ctx.ConstU32(2u)); @@ -246,20 +245,20 @@ static Id EmitLoadBufferF32xN(EmitContext& ctx, u32 handle, Id address) { } } -Id EmitLoadBufferF32(EmitContext& ctx, IR::Inst*, u32 handle, Id address) { - return EmitLoadBufferF32xN<1>(ctx, handle, address); +Id EmitLoadBufferU32(EmitContext& ctx, IR::Inst*, u32 handle, Id address) { + return EmitLoadBufferU32xN<1>(ctx, handle, address); } -Id EmitLoadBufferF32x2(EmitContext& ctx, IR::Inst*, u32 handle, Id address) { - return EmitLoadBufferF32xN<2>(ctx, handle, address); +Id EmitLoadBufferU32x2(EmitContext& ctx, IR::Inst*, u32 handle, Id address) { + return EmitLoadBufferU32xN<2>(ctx, handle, address); } -Id EmitLoadBufferF32x3(EmitContext& ctx, IR::Inst*, u32 handle, Id address) { - return EmitLoadBufferF32xN<3>(ctx, handle, address); +Id EmitLoadBufferU32x3(EmitContext& ctx, IR::Inst*, u32 handle, Id address) { + return EmitLoadBufferU32xN<3>(ctx, handle, address); } -Id EmitLoadBufferF32x4(EmitContext& ctx, IR::Inst*, u32 handle, Id address) { - return EmitLoadBufferF32xN<4>(ctx, handle, address); +Id EmitLoadBufferU32x4(EmitContext& ctx, IR::Inst*, u32 handle, Id address) { + return EmitLoadBufferU32xN<4>(ctx, handle, address); } Id EmitLoadBufferFormatF32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address) { @@ -275,7 +274,7 @@ Id EmitLoadBufferFormatF32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id addr } template -static void EmitStoreBufferF32xN(EmitContext& ctx, u32 handle, Id address, Id value) { +static void EmitStoreBufferU32xN(EmitContext& ctx, u32 handle, Id address, Id value) { auto& buffer = ctx.buffers[handle]; address = ctx.OpIAdd(ctx.U32[1], address, buffer.offset); const Id index = ctx.OpShiftRightLogical(ctx.U32[1], address, ctx.ConstU32(2u)); @@ -287,29 +286,25 @@ static void EmitStoreBufferF32xN(EmitContext& ctx, u32 handle, Id address, Id va const Id index_i = ctx.OpIAdd(ctx.U32[1], index, ctx.ConstU32(i)); const Id ptr = ctx.OpAccessChain(buffer.pointer_type, buffer.id, ctx.u32_zero_value, index_i); - ctx.OpStore(ptr, ctx.OpCompositeExtract(ctx.F32[1], value, i)); + ctx.OpStore(ptr, ctx.OpCompositeExtract(buffer.data_types->Get(1), value, i)); } } } -void EmitStoreBufferF32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) { - EmitStoreBufferF32xN<1>(ctx, handle, address, value); -} - -void EmitStoreBufferF32x2(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) { - EmitStoreBufferF32xN<2>(ctx, handle, address, value); +void EmitStoreBufferU32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) { + EmitStoreBufferU32xN<1>(ctx, handle, address, value); } -void EmitStoreBufferF32x3(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) { - EmitStoreBufferF32xN<3>(ctx, handle, address, value); +void EmitStoreBufferU32x2(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) { + EmitStoreBufferU32xN<2>(ctx, handle, address, value); } -void EmitStoreBufferF32x4(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) { - EmitStoreBufferF32xN<4>(ctx, handle, address, value); +void EmitStoreBufferU32x3(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) { + EmitStoreBufferU32xN<3>(ctx, handle, address, value); } -void EmitStoreBufferU32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) { - EmitStoreBufferF32xN<1>(ctx, handle, address, value); +void EmitStoreBufferU32x4(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) { + EmitStoreBufferU32xN<4>(ctx, handle, address, value); } void EmitStoreBufferFormatF32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) { @@ -317,7 +312,7 @@ void EmitStoreBufferFormatF32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id a const Id tex_buffer = ctx.OpLoad(buffer.image_type, buffer.id); const Id coord = ctx.OpIAdd(ctx.U32[1], address, buffer.coord_offset); if (buffer.is_integer) { - value = ctx.OpBitcast(ctx.U32[4], value); + value = ctx.OpBitcast(buffer.result_type, value); } ctx.OpImageWrite(tex_buffer, coord, value); } diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp index 530f381d774..50d9cc8cb82 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp @@ -157,17 +157,20 @@ Id EmitImageFetch(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, const ImageOperands operands; operands.AddOffset(ctx, offset); operands.Add(spv::ImageOperandsMask::Lod, lod); - return ctx.OpBitcast( - ctx.F32[4], ctx.OpImageFetch(result_type, image, coords, operands.mask, operands.operands)); + const Id texel = + texture.is_storage + ? ctx.OpImageRead(result_type, image, coords, operands.mask, operands.operands) + : ctx.OpImageFetch(result_type, image, coords, operands.mask, operands.operands); + return ctx.OpBitcast(ctx.F32[4], texel); } -Id EmitImageQueryDimensions(EmitContext& ctx, IR::Inst* inst, u32 handle, Id lod, bool skip_mips) { +Id EmitImageQueryDimensions(EmitContext& ctx, IR::Inst* inst, u32 handle, Id lod, bool has_mips) { const auto& texture = ctx.images[handle & 0xFFFF]; const Id image = ctx.OpLoad(texture.image_type, texture.id); const auto type = ctx.info.images[handle & 0xFFFF].type; const Id zero = ctx.u32_zero_value; - const auto mips{[&] { return skip_mips ? zero : ctx.OpImageQueryLevels(ctx.U32[1], image); }}; - const bool uses_lod{type != AmdGpu::ImageType::Color2DMsaa}; + const auto mips{[&] { return has_mips ? ctx.OpImageQueryLevels(ctx.U32[1], image) : zero; }}; + const bool uses_lod{type != AmdGpu::ImageType::Color2DMsaa && !texture.is_storage}; const auto query{[&](Id type) { return uses_lod ? ctx.OpImageQuerySizeLod(type, image, lod) : ctx.OpImageQuerySize(type, image); @@ -178,6 +181,7 @@ Id EmitImageQueryDimensions(EmitContext& ctx, IR::Inst* inst, u32 handle, Id lod case AmdGpu::ImageType::Color1DArray: case AmdGpu::ImageType::Color2D: case AmdGpu::ImageType::Cube: + case AmdGpu::ImageType::Color2DMsaa: return ctx.OpCompositeConstruct(ctx.U32[4], query(ctx.U32[2]), zero, mips()); case AmdGpu::ImageType::Color2DArray: case AmdGpu::ImageType::Color3D: diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h b/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h index ce4d3f1378d..3bdea9c1a91 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h +++ b/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h @@ -36,12 +36,14 @@ void EmitGetVcc(EmitContext& ctx); void EmitGetSccLo(EmitContext& ctx); void EmitGetVccLo(EmitContext& ctx); void EmitGetVccHi(EmitContext& ctx); +void EmitGetM0(EmitContext& ctx); void EmitSetScc(EmitContext& ctx); void EmitSetExec(EmitContext& ctx); void EmitSetVcc(EmitContext& ctx); void EmitSetSccLo(EmitContext& ctx); void EmitSetVccLo(EmitContext& ctx); void EmitSetVccHi(EmitContext& ctx); +void EmitSetM0(EmitContext& ctx); void EmitFPCmpClass32(EmitContext& ctx); void EmitPrologue(EmitContext& ctx); void EmitEpilogue(EmitContext& ctx); @@ -62,25 +64,16 @@ void EmitGetGotoVariable(EmitContext& ctx); void EmitSetScc(EmitContext& ctx); Id EmitReadConst(EmitContext& ctx); Id EmitReadConstBuffer(EmitContext& ctx, u32 handle, Id index); -Id EmitReadConstBufferU32(EmitContext& ctx, u32 handle, Id index); -Id EmitLoadBufferF32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address); -Id EmitLoadBufferF32x2(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address); -Id EmitLoadBufferF32x3(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address); -Id EmitLoadBufferF32x4(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address); -Id EmitLoadBufferFormatF32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address); -Id EmitLoadBufferFormatF32x2(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address); -Id EmitLoadBufferFormatF32x3(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address); -Id EmitLoadBufferFormatF32x4(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address); Id EmitLoadBufferU32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address); -void EmitStoreBufferF32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value); -void EmitStoreBufferF32x2(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value); -void EmitStoreBufferF32x3(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value); -void EmitStoreBufferF32x4(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value); -void EmitStoreBufferFormatF32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value); -void EmitStoreBufferFormatF32x2(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value); -void EmitStoreBufferFormatF32x3(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value); -void EmitStoreBufferFormatF32x4(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value); +Id EmitLoadBufferU32x2(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address); +Id EmitLoadBufferU32x3(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address); +Id EmitLoadBufferU32x4(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address); +Id EmitLoadBufferFormatF32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address); void EmitStoreBufferU32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value); +void EmitStoreBufferU32x2(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value); +void EmitStoreBufferU32x3(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value); +void EmitStoreBufferU32x4(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value); +void EmitStoreBufferFormatF32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value); Id EmitBufferAtomicIAdd32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value); Id EmitBufferAtomicSMin32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value); Id EmitBufferAtomicUMin32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value); @@ -165,12 +158,13 @@ Id EmitSelectF32(EmitContext& ctx, Id cond, Id true_value, Id false_value); Id EmitSelectF64(EmitContext& ctx, Id cond, Id true_value, Id false_value); Id EmitBitCastU16F16(EmitContext& ctx, Id value); Id EmitBitCastU32F32(EmitContext& ctx, Id value); -void EmitBitCastU64F64(EmitContext& ctx); +Id EmitBitCastU64F64(EmitContext& ctx, Id value); Id EmitBitCastF16U16(EmitContext& ctx, Id value); Id EmitBitCastF32U32(EmitContext& ctx, Id value); void EmitBitCastF64U64(EmitContext& ctx); Id EmitPackUint2x32(EmitContext& ctx, Id value); Id EmitUnpackUint2x32(EmitContext& ctx, Id value); +Id EmitPackFloat2x32(EmitContext& ctx, Id value); Id EmitPackFloat2x16(EmitContext& ctx, Id value); Id EmitUnpackFloat2x16(EmitContext& ctx, Id value); Id EmitPackHalf2x16(EmitContext& ctx, Id value); @@ -276,6 +270,8 @@ Id EmitIMul32(EmitContext& ctx, Id a, Id b); Id EmitIMul64(EmitContext& ctx, Id a, Id b); Id EmitSDiv32(EmitContext& ctx, Id a, Id b); Id EmitUDiv32(EmitContext& ctx, Id a, Id b); +Id EmitSMod32(EmitContext& ctx, Id a, Id b); +Id EmitUMod32(EmitContext& ctx, Id a, Id b); Id EmitINeg32(EmitContext& ctx, Id value); Id EmitINeg64(EmitContext& ctx, Id value); Id EmitIAbs32(EmitContext& ctx, Id value); @@ -404,12 +400,13 @@ Id EmitImageAtomicAnd32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id EmitImageAtomicOr32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id value); Id EmitImageAtomicXor32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id value); Id EmitImageAtomicExchange32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id value); - Id EmitLaneId(EmitContext& ctx); Id EmitWarpId(EmitContext& ctx); Id EmitQuadShuffle(EmitContext& ctx, Id value, Id index); Id EmitReadFirstLane(EmitContext& ctx, Id value); Id EmitReadLane(EmitContext& ctx, Id value, u32 lane); Id EmitWriteLane(EmitContext& ctx, Id value, Id write_value, u32 lane); +Id EmitDataAppend(EmitContext& ctx, u32 gds_addr, u32 binding); +Id EmitDataConsume(EmitContext& ctx, u32 gds_addr, u32 binding); } // namespace Shader::Backend::SPIRV diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_integer.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_integer.cpp index a9becb1eb8c..02af92385db 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_integer.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_integer.cpp @@ -96,6 +96,14 @@ Id EmitUDiv32(EmitContext& ctx, Id a, Id b) { return ctx.OpUDiv(ctx.U32[1], a, b); } +Id EmitSMod32(EmitContext& ctx, Id a, Id b) { + return ctx.OpSMod(ctx.U32[1], a, b); +} + +Id EmitUMod32(EmitContext& ctx, Id a, Id b) { + return ctx.OpUMod(ctx.U32[1], a, b); +} + Id EmitINeg32(EmitContext& ctx, Id value) { return ctx.OpSNegate(ctx.U32[1], value); } diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_special.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_special.cpp index 3ed89692bd7..283c9b16f12 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_special.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_special.cpp @@ -10,7 +10,21 @@ void EmitPrologue(EmitContext& ctx) { ctx.DefineBufferOffsets(); } -void EmitEpilogue(EmitContext& ctx) {} +void ConvertDepthMode(EmitContext& ctx) { + const Id type{ctx.F32[1]}; + const Id position{ctx.OpLoad(ctx.F32[4], ctx.output_position)}; + const Id z{ctx.OpCompositeExtract(type, position, 2u)}; + const Id w{ctx.OpCompositeExtract(type, position, 3u)}; + const Id screen_depth{ctx.OpFMul(type, ctx.OpFAdd(type, z, w), ctx.Constant(type, 0.5f))}; + const Id vector{ctx.OpCompositeInsert(ctx.F32[4], screen_depth, position, 2u)}; + ctx.OpStore(ctx.output_position, vector); +} + +void EmitEpilogue(EmitContext& ctx) { + if (ctx.stage == Stage::Vertex && ctx.runtime_info.vs_info.emulate_depth_negative_one_to_one) { + ConvertDepthMode(ctx); + } +} void EmitDiscard(EmitContext& ctx) { ctx.OpDemoteToHelperInvocationEXT(); diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_warp.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_warp.cpp index c55763c5d98..2d13d09f0a3 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_warp.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_warp.cpp @@ -23,11 +23,12 @@ Id EmitQuadShuffle(EmitContext& ctx, Id value, Id index) { } Id EmitReadFirstLane(EmitContext& ctx, Id value) { - UNREACHABLE(); + return ctx.OpGroupNonUniformBroadcastFirst(ctx.U32[1], SubgroupScope(ctx), value); } Id EmitReadLane(EmitContext& ctx, Id value, u32 lane) { - UNREACHABLE(); + return ctx.OpGroupNonUniformBroadcast(ctx.U32[1], SubgroupScope(ctx), value, + ctx.ConstU32(lane)); } Id EmitWriteLane(EmitContext& ctx, Id value, Id write_value, u32 lane) { diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp index 4ae6e68d98a..06dc268738b 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp @@ -41,9 +41,10 @@ void Name(EmitContext& ctx, Id object, std::string_view format_str, Args&&... ar } // Anonymous namespace -EmitContext::EmitContext(const Profile& profile_, const Shader::Info& info_, u32& binding_) - : Sirit::Module(profile_.supported_spirv), info{info_}, profile{profile_}, stage{info.stage}, - binding{binding_} { +EmitContext::EmitContext(const Profile& profile_, const RuntimeInfo& runtime_info_, + const Info& info_, Bindings& binding_) + : Sirit::Module(profile_.supported_spirv), info{info_}, runtime_info{runtime_info_}, + profile{profile_}, stage{info.stage}, binding{binding_} { AddCapability(spv::Capability::Shader); DefineArithmeticTypes(); DefineInterfaces(); @@ -84,6 +85,9 @@ void EmitContext::DefineArithmeticTypes() { F16[1] = Name(TypeFloat(16), "f16_id"); U16 = Name(TypeUInt(16), "u16_id"); } + if (info.uses_fp64) { + F64[1] = Name(TypeFloat(64), "f64_id"); + } F32[1] = Name(TypeFloat(32), "f32_id"); S32[1] = Name(TypeSInt(32), "i32_id"); U32[1] = Name(TypeUInt(32), "u32_id"); @@ -93,6 +97,9 @@ void EmitContext::DefineArithmeticTypes() { if (info.uses_fp16) { F16[i] = Name(TypeVector(F16[1], i), fmt::format("f16vec{}_id", i)); } + if (info.uses_fp64) { + F64[i] = Name(TypeVector(F64[1], i), fmt::format("f64vec{}_id", i)); + } F32[i] = Name(TypeVector(F32[1], i), fmt::format("f32vec{}_id", i)); S32[i] = Name(TypeVector(S32[1], i), fmt::format("i32vec{}_id", i)); U32[i] = Name(TypeVector(U32[1], i), fmt::format("u32vec{}_id", i)); @@ -166,26 +173,29 @@ EmitContext::SpirvAttribute EmitContext::GetAttributeInfo(AmdGpu::NumberFormat f } void EmitContext::DefineBufferOffsets() { - for (auto& buffer : buffers) { + for (BufferDefinition& buffer : buffers) { const u32 binding = buffer.binding; - const u32 half = Shader::PushData::BufOffsetIndex + (binding >> 4); + const u32 half = PushData::BufOffsetIndex + (binding >> 4); const u32 comp = (binding & 0xf) >> 2; const u32 offset = (binding & 0x3) << 3; const Id ptr{OpAccessChain(TypePointer(spv::StorageClass::PushConstant, U32[1]), push_data_block, ConstU32(half), ConstU32(comp))}; const Id value{OpLoad(U32[1], ptr)}; buffer.offset = OpBitFieldUExtract(U32[1], value, ConstU32(offset), ConstU32(8U)); + Name(buffer.offset, fmt::format("buf{}_off", binding)); buffer.offset_dwords = OpShiftRightLogical(U32[1], buffer.offset, ConstU32(2U)); + Name(buffer.offset_dwords, fmt::format("buf{}_dword_off", binding)); } - for (auto& tex_buffer : texture_buffers) { + for (TextureBufferDefinition& tex_buffer : texture_buffers) { const u32 binding = tex_buffer.binding; - const u32 half = Shader::PushData::BufOffsetIndex + (binding >> 4); + const u32 half = PushData::BufOffsetIndex + (binding >> 4); const u32 comp = (binding & 0xf) >> 2; const u32 offset = (binding & 0x3) << 3; const Id ptr{OpAccessChain(TypePointer(spv::StorageClass::PushConstant, U32[1]), push_data_block, ConstU32(half), ConstU32(comp))}; const Id value{OpLoad(U32[1], ptr)}; - tex_buffer.coord_offset = OpBitFieldUExtract(U32[1], value, ConstU32(offset), ConstU32(8U)); + tex_buffer.coord_offset = OpBitFieldUExtract(U32[1], value, ConstU32(offset), ConstU32(6U)); + Name(tex_buffer.coord_offset, fmt::format("texbuf{}_off", binding)); } } @@ -247,7 +257,7 @@ void EmitContext::DefineInputs() { frag_coord = DefineVariable(F32[4], spv::BuiltIn::FragCoord, spv::StorageClass::Input); frag_depth = DefineVariable(F32[1], spv::BuiltIn::FragDepth, spv::StorageClass::Output); front_facing = DefineVariable(U1[1], spv::BuiltIn::FrontFacing, spv::StorageClass::Input); - for (const auto& input : info.ps_inputs) { + for (const auto& input : runtime_info.fs_info.inputs) { const u32 semantic = input.param_index; if (input.is_default && !input.is_flat) { input_params[semantic] = {MakeDefaultValue(*this, input.default_value), F32[1], @@ -323,16 +333,25 @@ void EmitContext::DefineOutputs() { void EmitContext::DefinePushDataBlock() { // Create push constants block for instance steps rates - const Id struct_type{Name(TypeStruct(U32[1], U32[1], U32[4], U32[4]), "AuxData")}; + const Id struct_type{Name( + TypeStruct(U32[1], U32[1], U32[4], U32[4], U32[4], U32[4], U32[4], U32[4]), "AuxData")}; Decorate(struct_type, spv::Decoration::Block); MemberName(struct_type, 0, "sr0"); MemberName(struct_type, 1, "sr1"); MemberName(struct_type, 2, "buf_offsets0"); MemberName(struct_type, 3, "buf_offsets1"); + MemberName(struct_type, 4, "ud_regs0"); + MemberName(struct_type, 5, "ud_regs1"); + MemberName(struct_type, 6, "ud_regs2"); + MemberName(struct_type, 7, "ud_regs3"); MemberDecorate(struct_type, 0, spv::Decoration::Offset, 0U); MemberDecorate(struct_type, 1, spv::Decoration::Offset, 4U); MemberDecorate(struct_type, 2, spv::Decoration::Offset, 8U); MemberDecorate(struct_type, 3, spv::Decoration::Offset, 24U); + MemberDecorate(struct_type, 4, spv::Decoration::Offset, 40U); + MemberDecorate(struct_type, 5, spv::Decoration::Offset, 56U); + MemberDecorate(struct_type, 6, spv::Decoration::Offset, 72U); + MemberDecorate(struct_type, 7, spv::Decoration::Offset, 88U); push_data_block = DefineVar(struct_type, spv::StorageClass::PushConstant); Name(push_data_block, "push_data"); interfaces.push_back(push_data_block); @@ -370,7 +389,7 @@ void EmitContext::DefineBuffers() { const Id struct_pointer_type{TypePointer(storage_class, struct_type)}; const Id pointer_type = TypePointer(storage_class, data_type); const Id id{AddGlobalVariable(struct_pointer_type, storage_class)}; - Decorate(id, spv::Decoration::Binding, binding); + Decorate(id, spv::Decoration::Binding, binding.unified++); Decorate(id, spv::Decoration::DescriptorSet, 0U); if (is_storage && !desc.is_written) { Decorate(id, spv::Decoration::NonWritable); @@ -379,7 +398,7 @@ void EmitContext::DefineBuffers() { buffers.push_back({ .id = id, - .binding = binding++, + .binding = binding.buffer++, .data_types = data_types, .pointer_type = pointer_type, }); @@ -397,12 +416,12 @@ void EmitContext::DefineTextureBuffers() { sampled, spv::ImageFormat::Unknown)}; const Id pointer_type{TypePointer(spv::StorageClass::UniformConstant, image_type)}; const Id id{AddGlobalVariable(pointer_type, spv::StorageClass::UniformConstant)}; - Decorate(id, spv::Decoration::Binding, binding); + Decorate(id, spv::Decoration::Binding, binding.unified++); Decorate(id, spv::Decoration::DescriptorSet, 0U); Name(id, fmt::format("{}_{}", desc.is_written ? "imgbuf" : "texbuf", desc.sgpr_base)); texture_buffers.push_back({ .id = id, - .binding = binding++, + .binding = binding.buffer++, .image_type = image_type, .result_type = sampled_type[4], .is_integer = is_integer, @@ -506,10 +525,13 @@ Id ImageType(EmitContext& ctx, const ImageResource& desc, Id sampled_type) { return ctx.TypeImage(sampled_type, spv::Dim::Dim2D, false, false, false, sampled, format); case AmdGpu::ImageType::Color2DArray: return ctx.TypeImage(sampled_type, spv::Dim::Dim2D, false, true, false, sampled, format); + case AmdGpu::ImageType::Color2DMsaa: + return ctx.TypeImage(sampled_type, spv::Dim::Dim2D, false, false, true, sampled, format); case AmdGpu::ImageType::Color3D: return ctx.TypeImage(sampled_type, spv::Dim::Dim3D, false, false, false, sampled, format); case AmdGpu::ImageType::Cube: - return ctx.TypeImage(sampled_type, spv::Dim::Cube, false, false, false, sampled, format); + return ctx.TypeImage(sampled_type, spv::Dim::Cube, false, desc.is_array, false, sampled, + format); default: break; } @@ -523,7 +545,7 @@ void EmitContext::DefineImagesAndSamplers() { const Id image_type{ImageType(*this, image_desc, sampled_type)}; const Id pointer_type{TypePointer(spv::StorageClass::UniformConstant, image_type)}; const Id id{AddGlobalVariable(pointer_type, spv::StorageClass::UniformConstant)}; - Decorate(id, spv::Decoration::Binding, binding); + Decorate(id, spv::Decoration::Binding, binding.unified++); Decorate(id, spv::Decoration::DescriptorSet, 0U); Name(id, fmt::format("{}_{}{}_{:02x}", stage, "img", image_desc.sgpr_base, image_desc.dword_offset)); @@ -533,9 +555,9 @@ void EmitContext::DefineImagesAndSamplers() { .sampled_type = image_desc.is_storage ? sampled_type : TypeSampledImage(image_type), .pointer_type = pointer_type, .image_type = image_type, + .is_storage = image_desc.is_storage, }); interfaces.push_back(id); - ++binding; } if (std::ranges::any_of(info.images, &ImageResource::is_atomic)) { image_u32 = TypePointer(spv::StorageClass::Image, U32[1]); @@ -547,13 +569,12 @@ void EmitContext::DefineImagesAndSamplers() { sampler_pointer_type = TypePointer(spv::StorageClass::UniformConstant, sampler_type); for (const auto& samp_desc : info.samplers) { const Id id{AddGlobalVariable(sampler_pointer_type, spv::StorageClass::UniformConstant)}; - Decorate(id, spv::Decoration::Binding, binding); + Decorate(id, spv::Decoration::Binding, binding.unified++); Decorate(id, spv::Decoration::DescriptorSet, 0U); Name(id, fmt::format("{}_{}{}_{:02x}", stage, "samp", samp_desc.sgpr_base, samp_desc.dword_offset)); samplers.push_back(id); interfaces.push_back(id); - ++binding; } } @@ -562,7 +583,7 @@ void EmitContext::DefineSharedMemory() { if (!info.uses_shared) { return; } - u32 shared_memory_size = info.shared_memory_size; + u32 shared_memory_size = runtime_info.cs_info.shared_memory_size; if (shared_memory_size == 0) { shared_memory_size = DefaultSharedMemSize; } diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.h b/src/shader_recompiler/backend/spirv/spirv_emit_context.h index d3646382f64..9029866b09c 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.h +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.h @@ -6,9 +6,10 @@ #include #include +#include "shader_recompiler/backend/bindings.h" +#include "shader_recompiler/info.h" #include "shader_recompiler/ir/program.h" #include "shader_recompiler/profile.h" -#include "shader_recompiler/runtime_info.h" namespace Shader::Backend::SPIRV { @@ -36,7 +37,8 @@ struct VectorIds { class EmitContext final : public Sirit::Module { public: - explicit EmitContext(const Profile& profile, const Shader::Info& info, u32& binding); + explicit EmitContext(const Profile& profile, const RuntimeInfo& runtime_info, const Info& info, + Bindings& binding); ~EmitContext(); Id Def(const IR::Value& value); @@ -125,6 +127,7 @@ class EmitContext final : public Sirit::Module { } const Info& info; + const RuntimeInfo& runtime_info; const Profile& profile; Stage stage{}; @@ -198,6 +201,7 @@ class EmitContext final : public Sirit::Module { Id sampled_type; Id pointer_type; Id image_type; + bool is_storage = false; }; struct BufferDefinition { @@ -214,11 +218,11 @@ class EmitContext final : public Sirit::Module { u32 binding; Id image_type; Id result_type; - bool is_integer; - bool is_storage; + bool is_integer = false; + bool is_storage = false; }; - u32& binding; + Bindings& binding; boost::container::small_vector buffers; boost::container::small_vector texture_buffers; boost::container::small_vector images; diff --git a/src/shader_recompiler/frontend/control_flow_graph.cpp b/src/shader_recompiler/frontend/control_flow_graph.cpp index 276bd9db030..9d481d32c43 100644 --- a/src/shader_recompiler/frontend/control_flow_graph.cpp +++ b/src/shader_recompiler/frontend/control_flow_graph.cpp @@ -23,7 +23,6 @@ struct Compare { static IR::Condition MakeCondition(const GcnInst& inst) { if (inst.IsCmpx()) { - ASSERT(inst.opcode == Opcode::V_CMPX_NE_U32); return IR::Condition::Execnz; } @@ -99,7 +98,7 @@ void CFG::EmitDivergenceLabels() { // with SAVEEXEC to mask the threads that didn't pass the condition // of initial branch. (inst.opcode == Opcode::S_ANDN2_B64 && inst.dst[0].field == OperandField::ExecLo) || - inst.opcode == Opcode::V_CMPX_NE_U32; + inst.IsCmpx(); }; const auto is_close_scope = [](const GcnInst& inst) { // Closing an EXEC scope can be either a branch instruction @@ -109,7 +108,7 @@ void CFG::EmitDivergenceLabels() { // Sometimes compiler might insert instructions between the SAVEEXEC and the branch. // Those instructions need to be wrapped in the condition as well so allow branch // as end scope instruction. - inst.opcode == Opcode::S_CBRANCH_EXECZ || + inst.opcode == Opcode::S_CBRANCH_EXECZ || inst.opcode == Opcode::S_ENDPGM || (inst.opcode == Opcode::S_ANDN2_B64 && inst.dst[0].field == OperandField::ExecLo); }; @@ -127,7 +126,8 @@ void CFG::EmitDivergenceLabels() { s32 curr_begin = -1; for (size_t index = GetIndex(start); index < end_index; index++) { const auto& inst = inst_list[index]; - if (is_close_scope(inst) && curr_begin != -1) { + const bool is_close = is_close_scope(inst); + if ((is_close || index == end_index - 1) && curr_begin != -1) { // If there are no instructions inside scope don't do anything. if (index - curr_begin == 1) { curr_begin = -1; @@ -138,8 +138,16 @@ void CFG::EmitDivergenceLabels() { const auto& save_inst = inst_list[curr_begin]; const Label label = index_to_pc[curr_begin] + save_inst.length; AddLabel(label); - // Add a label to the close scope instruction as well. - AddLabel(index_to_pc[index]); + // Add a label to the close scope instruction. + // There are 3 cases where we need to close a scope. + // * Close scope instruction inside the block + // * Close scope instruction at the end of the block (cbranch or endpgm) + // * Normal instruction at the end of the block + // For the last case we must NOT add a label as that would cause + // the instruction to be separated into its own basic block. + if (is_close) { + AddLabel(index_to_pc[index]); + } // Reset scope begin. curr_begin = -1; } @@ -194,7 +202,7 @@ void CFG::LinkBlocks() { const auto end_inst{block.end_inst}; // Handle divergence block inserted here. if (end_inst.opcode == Opcode::S_AND_SAVEEXEC_B64 || - end_inst.opcode == Opcode::S_ANDN2_B64 || end_inst.opcode == Opcode::V_CMPX_NE_U32) { + end_inst.opcode == Opcode::S_ANDN2_B64 || end_inst.IsCmpx()) { // Blocks are stored ordered by address in the set auto next_it = std::next(it); auto* target_block = &(*next_it); diff --git a/src/shader_recompiler/frontend/decode.cpp b/src/shader_recompiler/frontend/decode.cpp index b5c02d74713..99e3a16f1a4 100644 --- a/src/shader_recompiler/frontend/decode.cpp +++ b/src/shader_recompiler/frontend/decode.cpp @@ -5,6 +5,8 @@ #include "common/assert.h" #include "shader_recompiler/frontend/decode.h" +#include "magic_enum.hpp" + namespace Shader::Gcn { namespace bit { @@ -160,6 +162,8 @@ uint32_t GcnDecodeContext::getEncodingLength(InstEncoding encoding) { case InstEncoding::EXP: instLength = sizeof(uint64_t); break; + default: + break; } return instLength; } @@ -215,6 +219,8 @@ uint32_t GcnDecodeContext::getOpMapOffset(InstEncoding encoding) { case InstEncoding::VOP2: offset = (uint32_t)OpcodeMap::OP_MAP_VOP2; break; + default: + break; } return offset; } @@ -253,7 +259,9 @@ void GcnDecodeContext::updateInstructionMeta(InstEncoding encoding) { ASSERT_MSG(instFormat.src_type != ScalarType::Undefined && instFormat.dst_type != ScalarType::Undefined, - "TODO: Instruction format table not complete, please fix it manually."); + "Instruction format table incomplete for opcode {} ({}, encoding = {})", + magic_enum::enum_name(m_instruction.opcode), u32(m_instruction.opcode), + magic_enum::enum_name(encoding)); m_instruction.inst_class = instFormat.inst_class; m_instruction.category = instFormat.inst_category; @@ -307,6 +315,8 @@ void GcnDecodeContext::repairOperandType() { case Opcode::IMAGE_GATHER4_C: m_instruction.src[0].type = ScalarType::Any; break; + default: + break; } } @@ -359,6 +369,8 @@ void GcnDecodeContext::decodeInstruction32(InstEncoding encoding, GcnCodeSlice& case InstEncoding::VINTRP: decodeInstructionVINTRP(hexInstruction); break; + default: + break; } } @@ -383,6 +395,8 @@ void GcnDecodeContext::decodeInstruction64(InstEncoding encoding, GcnCodeSlice& case InstEncoding::EXP: decodeInstructionEXP(hexInstruction); break; + default: + break; } } @@ -489,9 +503,8 @@ void GcnDecodeContext::decodeInstructionVOP1(u32 hexInstruction) { OpcodeVOP1 vop1Op = static_cast(op); if (vop1Op == OpcodeVOP1::V_READFIRSTLANE_B32) { - m_instruction.dst[1].field = getOperandField(vdst); - m_instruction.dst[1].type = ScalarType::Uint32; - m_instruction.dst[1].code = vdst; + m_instruction.dst[0].field = getOperandField(vdst); + m_instruction.dst[0].type = ScalarType::Uint32; } } @@ -533,13 +546,15 @@ void GcnDecodeContext::decodeInstructionVOP2(u32 hexInstruction) { m_instruction.dst_count = 1; OpcodeVOP2 vop2Op = static_cast(op); - if (vop2Op == OpcodeVOP2::V_READLANE_B32 || vop2Op == OpcodeVOP2::V_WRITELANE_B32) { + if (vop2Op == OpcodeVOP2::V_READLANE_B32) { // vsrc1 is scalar for lane instructions m_instruction.src[1].field = getOperandField(vsrc1); // dst is sgpr - m_instruction.dst[1].field = OperandField::ScalarGPR; - m_instruction.dst[1].type = ScalarType::Uint32; - m_instruction.dst[1].code = vdst; + m_instruction.dst[0].field = getOperandField(vdst); + m_instruction.dst[0].type = ScalarType::Uint32; + } else if (vop2Op == OpcodeVOP2::V_WRITELANE_B32) { + m_instruction.src[1].field = getOperandField(vsrc1); + // dst is vgpr, as normal } else if (IsVop3BEncoding(m_instruction.opcode)) { m_instruction.dst[1].field = OperandField::VccLo; m_instruction.dst[1].type = ScalarType::Uint64; @@ -646,13 +661,11 @@ void GcnDecodeContext::decodeInstructionVOP3(uint64_t hexInstruction) { m_instruction.dst[1].field = getOperandField(vdst); m_instruction.dst[1].type = ScalarType::Uint64; m_instruction.dst[1].code = vdst; - } else if (vop3Op >= OpcodeVOP3::V_READLANE_B32 && vop3Op <= OpcodeVOP3::V_WRITELANE_B32) { - // vsrc1 is scalar for lane instructions - m_instruction.src[1].field = getOperandField(src1); - // dst is sgpr for lane instruction - m_instruction.dst[1].field = OperandField::ScalarGPR; - m_instruction.dst[1].type = ScalarType::Uint32; - m_instruction.dst[1].code = vdst; + } else if (vop3Op == OpcodeVOP3::V_READLANE_B32 || + vop3Op == OpcodeVOP3::V_READFIRSTLANE_B32) { + m_instruction.dst[0].field = getOperandField(vdst); + m_instruction.dst[0].type = ScalarType::Uint32; + // WRITELANE can be decoded like other VOP3's } } @@ -991,6 +1004,8 @@ u32 GcnDecodeContext::getMimgModifier(Opcode opcode) { flags.set(MimgModifier::Pcf, MimgModifier::CoarseDerivative, MimgModifier::LodClamp, MimgModifier::Offset); break; + default: + break; } return flags.raw(); @@ -1016,6 +1031,7 @@ void GcnDecodeContext::decodeInstructionMIMG(uint64_t hexInstruction) { m_instruction.control.mimg = *reinterpret_cast(&hexInstruction); m_instruction.control.mimg.mod = getMimgModifier(m_instruction.opcode); + ASSERT(m_instruction.control.mimg.r128 == 0); } void GcnDecodeContext::decodeInstructionDS(uint64_t hexInstruction) { @@ -1094,4 +1110,4 @@ void GcnDecodeContext::decodeInstructionEXP(uint64_t hexInstruction) { m_instruction.control.exp = *reinterpret_cast(&hexInstruction); } -} // namespace Shader::Gcn +} // namespace Shader::Gcn \ No newline at end of file diff --git a/src/shader_recompiler/frontend/format.cpp b/src/shader_recompiler/frontend/format.cpp index 8df3ac36406..90f10498a00 100644 --- a/src/shader_recompiler/frontend/format.cpp +++ b/src/shader_recompiler/frontend/format.cpp @@ -1786,8 +1786,7 @@ constexpr std::array InstructionFormatVOP3 = {{ constexpr std::array InstructionFormatVOP1 = {{ // 0 = V_NOP - {InstClass::VectorMisc, InstCategory::VectorALU, 0, 1, ScalarType::Undefined, - ScalarType::Undefined}, + {InstClass::VectorMisc, InstCategory::VectorALU, 0, 1, ScalarType::Any, ScalarType::Any}, // 1 = V_MOV_B32 {InstClass::VectorRegMov, InstCategory::VectorALU, 1, 1, ScalarType::Uint32, ScalarType::Uint32}, @@ -3603,8 +3602,8 @@ constexpr std::array InstructionFormatMIMG = {{ {InstClass::VectorMemImgSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Undefined, ScalarType::Undefined}, // 79 = IMAGE_GATHER4_C_LZ - {InstClass::VectorMemImgSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Undefined, - ScalarType::Undefined}, + {InstClass::VectorMemImgSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Uint32, + ScalarType::Uint32}, // 80 = IMAGE_GATHER4_O {InstClass::VectorMemImgSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Undefined, ScalarType::Undefined}, @@ -3656,8 +3655,8 @@ constexpr std::array InstructionFormatMIMG = {{ {}, {}, // 104 = IMAGE_SAMPLE_CD - {InstClass::VectorMemImgSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Undefined, - ScalarType::Undefined}, + {InstClass::VectorMemImgSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Float32, + ScalarType::Float32}, // 105 = IMAGE_SAMPLE_CD_CL {InstClass::VectorMemImgSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Undefined, ScalarType::Undefined}, @@ -3719,8 +3718,9 @@ InstFormat InstructionFormat(InstEncoding encoding, uint32_t opcode) { return InstructionFormatSOP2[opcode]; case InstEncoding::VOP2: return InstructionFormatVOP2[opcode]; + default: + UNREACHABLE(); } - UNREACHABLE(); return {}; } diff --git a/src/shader_recompiler/frontend/structured_control_flow.cpp b/src/shader_recompiler/frontend/structured_control_flow.cpp index fefc623fc94..bf5ba6bce33 100644 --- a/src/shader_recompiler/frontend/structured_control_flow.cpp +++ b/src/shader_recompiler/frontend/structured_control_flow.cpp @@ -602,13 +602,14 @@ class TranslatePass { Common::ObjectPool& block_pool_, Common::ObjectPool& stmt_pool_, Statement& root_stmt, IR::AbstractSyntaxList& syntax_list_, std::span inst_list_, - Info& info_, const Profile& profile_) + Info& info_, const RuntimeInfo& runtime_info_, const Profile& profile_) : stmt_pool{stmt_pool_}, inst_pool{inst_pool_}, block_pool{block_pool_}, - syntax_list{syntax_list_}, inst_list{inst_list_}, info{info_}, profile{profile_} { + syntax_list{syntax_list_}, inst_list{inst_list_}, info{info_}, + runtime_info{runtime_info_}, profile{profile_} { Visit(root_stmt, nullptr, nullptr); IR::Block& first_block{*syntax_list.front().data.block}; - Translator{&first_block, info, profile}.EmitPrologue(); + Translator{&first_block, info, runtime_info, profile}.EmitPrologue(); } private: @@ -637,7 +638,7 @@ class TranslatePass { const u32 start = stmt.block->begin_index; const u32 size = stmt.block->end_index - start + 1; Translate(current_block, stmt.block->begin, inst_list.subspan(start, size), - info, profile); + info, runtime_info, profile); } break; } @@ -817,19 +818,20 @@ class TranslatePass { const Block dummy_flow_block{.is_dummy = true}; std::span inst_list; Info& info; + const RuntimeInfo& runtime_info; const Profile& profile; }; } // Anonymous namespace IR::AbstractSyntaxList BuildASL(Common::ObjectPool& inst_pool, Common::ObjectPool& block_pool, CFG& cfg, Info& info, - const Profile& profile) { + const RuntimeInfo& runtime_info, const Profile& profile) { Common::ObjectPool stmt_pool{64}; GotoPass goto_pass{cfg, stmt_pool}; Statement& root{goto_pass.RootStatement()}; IR::AbstractSyntaxList syntax_list; - TranslatePass{inst_pool, block_pool, stmt_pool, root, - syntax_list, cfg.inst_list, info, profile}; + TranslatePass{inst_pool, block_pool, stmt_pool, root, syntax_list, + cfg.inst_list, info, runtime_info, profile}; ASSERT_MSG(!info.translation_failed, "Shader translation has failed"); return syntax_list; } diff --git a/src/shader_recompiler/frontend/structured_control_flow.h b/src/shader_recompiler/frontend/structured_control_flow.h index f5a540518f3..2119484e361 100644 --- a/src/shader_recompiler/frontend/structured_control_flow.h +++ b/src/shader_recompiler/frontend/structured_control_flow.h @@ -11,12 +11,14 @@ namespace Shader { struct Info; struct Profile; +struct RuntimeInfo; } // namespace Shader namespace Shader::Gcn { [[nodiscard]] IR::AbstractSyntaxList BuildASL(Common::ObjectPool& inst_pool, Common::ObjectPool& block_pool, CFG& cfg, - Info& info, const Profile& profile); + Info& info, const RuntimeInfo& runtime_info, + const Profile& profile); } // namespace Shader::Gcn diff --git a/src/shader_recompiler/frontend/translate/data_share.cpp b/src/shader_recompiler/frontend/translate/data_share.cpp index c487ed424cf..f5fce311c63 100644 --- a/src/shader_recompiler/frontend/translate/data_share.cpp +++ b/src/shader_recompiler/frontend/translate/data_share.cpp @@ -7,128 +7,89 @@ namespace Shader::Gcn { void Translator::EmitDataShare(const GcnInst& inst) { switch (inst.opcode) { - case Opcode::DS_SWIZZLE_B32: - return DS_SWIZZLE_B32(inst); - case Opcode::DS_READ_B32: - return DS_READ(32, false, false, inst); - case Opcode::DS_READ_B64: - return DS_READ(64, false, false, inst); - case Opcode::DS_READ2_B32: - return DS_READ(32, false, true, inst); - case Opcode::DS_READ2_B64: - return DS_READ(64, false, true, inst); - case Opcode::DS_WRITE_B32: - return DS_WRITE(32, false, false, false, inst); - case Opcode::DS_WRITE2ST64_B32: - return DS_WRITE(32, false, true, true, inst); - case Opcode::DS_WRITE_B64: - return DS_WRITE(64, false, false, false, inst); - case Opcode::DS_WRITE2_B32: - return DS_WRITE(32, false, true, false, inst); - case Opcode::DS_WRITE2_B64: - return DS_WRITE(64, false, true, false, inst); + // DS case Opcode::DS_ADD_U32: return DS_ADD_U32(inst, false); - case Opcode::DS_MIN_U32: - return DS_MIN_U32(inst, false, false); case Opcode::DS_MIN_I32: return DS_MIN_U32(inst, true, false); - case Opcode::DS_MAX_U32: - return DS_MAX_U32(inst, false, false); case Opcode::DS_MAX_I32: return DS_MAX_U32(inst, true, false); + case Opcode::DS_MIN_U32: + return DS_MIN_U32(inst, false, false); + case Opcode::DS_MAX_U32: + return DS_MAX_U32(inst, false, false); + case Opcode::DS_WRITE_B32: + return DS_WRITE(32, false, false, false, inst); + case Opcode::DS_WRITE2_B32: + return DS_WRITE(32, false, true, false, inst); + case Opcode::DS_WRITE2ST64_B32: + return DS_WRITE(32, false, true, true, inst); case Opcode::DS_ADD_RTN_U32: return DS_ADD_U32(inst, true); case Opcode::DS_MIN_RTN_U32: return DS_MIN_U32(inst, false, true); case Opcode::DS_MAX_RTN_U32: return DS_MAX_U32(inst, false, true); + case Opcode::DS_SWIZZLE_B32: + return DS_SWIZZLE_B32(inst); + case Opcode::DS_READ_B32: + return DS_READ(32, false, false, false, inst); + case Opcode::DS_READ2_B32: + return DS_READ(32, false, true, false, inst); + case Opcode::DS_READ2ST64_B32: + return DS_READ(32, false, true, true, inst); + case Opcode::DS_CONSUME: + return DS_CONSUME(inst); + case Opcode::DS_APPEND: + return DS_APPEND(inst); + case Opcode::DS_WRITE_B64: + return DS_WRITE(64, false, false, false, inst); + case Opcode::DS_WRITE2_B64: + return DS_WRITE(64, false, true, false, inst); + case Opcode::DS_READ_B64: + return DS_READ(64, false, false, false, inst); + case Opcode::DS_READ2_B64: + return DS_READ(64, false, true, false, inst); default: LogMissingOpcode(inst); } } -void Translator::DS_SWIZZLE_B32(const GcnInst& inst) { - const u8 offset0 = inst.control.ds.offset0; - const u8 offset1 = inst.control.ds.offset1; - const IR::U32 src{GetSrc(inst.src[1])}; - ASSERT(offset1 & 0x80); - const IR::U32 lane_id = ir.LaneId(); - const IR::U32 id_in_group = ir.BitwiseAnd(lane_id, ir.Imm32(0b11)); - const IR::U32 base = ir.ShiftLeftLogical(id_in_group, ir.Imm32(1)); - const IR::U32 index = - ir.IAdd(lane_id, ir.BitFieldExtract(ir.Imm32(offset0), base, ir.Imm32(2))); - SetDst(inst.dst[0], ir.QuadShuffle(src, index)); +// SOPP + +void Translator::S_BARRIER() { + ir.Barrier(); } -void Translator::DS_READ(int bit_size, bool is_signed, bool is_pair, const GcnInst& inst) { - const IR::U32 addr{ir.GetVectorReg(IR::VectorReg(inst.src[0].code))}; - IR::VectorReg dst_reg{inst.dst[0].code}; - if (is_pair) { - // Pair loads are either 32 or 64-bit - const u32 adj = bit_size == 32 ? 4 : 8; - const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset0 * adj))); - const IR::Value data0 = ir.LoadShared(bit_size, is_signed, addr0); - if (bit_size == 32) { - ir.SetVectorReg(dst_reg++, IR::U32{data0}); - } else { - ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(data0, 0)}); - ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(data0, 1)}); - } - const IR::U32 addr1 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset1 * adj))); - const IR::Value data1 = ir.LoadShared(bit_size, is_signed, addr1); - if (bit_size == 32) { - ir.SetVectorReg(dst_reg++, IR::U32{data1}); - } else { - ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(data1, 0)}); - ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(data1, 1)}); - } - } else if (bit_size == 64) { - const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset0))); - const IR::Value data = ir.LoadShared(bit_size, is_signed, addr0); - ir.SetVectorReg(dst_reg, IR::U32{ir.CompositeExtract(data, 0)}); - ir.SetVectorReg(dst_reg + 1, IR::U32{ir.CompositeExtract(data, 1)}); +// VOP2 + +void Translator::V_READFIRSTLANE_B32(const GcnInst& inst) { + const IR::U32 value{GetSrc(inst.src[0])}; + + if (info.stage != Stage::Compute) { + SetDst(inst.dst[0], value); } else { - const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset0))); - const IR::U32 data = IR::U32{ir.LoadShared(bit_size, is_signed, addr0)}; - ir.SetVectorReg(dst_reg, data); + SetDst(inst.dst[0], ir.ReadFirstLane(value)); } } -void Translator::DS_WRITE(int bit_size, bool is_signed, bool is_pair, bool stride64, - const GcnInst& inst) { - const IR::U32 addr{ir.GetVectorReg(IR::VectorReg(inst.src[0].code))}; - const IR::VectorReg data0{inst.src[1].code}; - const IR::VectorReg data1{inst.src[2].code}; - if (is_pair) { - const u32 adj = (bit_size == 32 ? 4 : 8) * (stride64 ? 64 : 1); - const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset0 * adj))); - if (bit_size == 32) { - ir.WriteShared(32, ir.GetVectorReg(data0), addr0); - } else { - ir.WriteShared( - 64, ir.CompositeConstruct(ir.GetVectorReg(data0), ir.GetVectorReg(data0 + 1)), - addr0); - } - const IR::U32 addr1 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset1 * adj))); - if (bit_size == 32) { - ir.WriteShared(32, ir.GetVectorReg(data1), addr1); - } else { - ir.WriteShared( - 64, ir.CompositeConstruct(ir.GetVectorReg(data1), ir.GetVectorReg(data1 + 1)), - addr1); - } - } else if (bit_size == 64) { - const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset0))); - const IR::Value data = - ir.CompositeConstruct(ir.GetVectorReg(data0), ir.GetVectorReg(data0 + 1)); - ir.WriteShared(bit_size, data, addr0); - } else { - const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset0))); - ir.WriteShared(bit_size, ir.GetVectorReg(data0), addr0); - } +void Translator::V_READLANE_B32(const GcnInst& inst) { + const IR::ScalarReg dst{inst.dst[0].code}; + const IR::U32 value{GetSrc(inst.src[0])}; + const IR::U32 lane{GetSrc(inst.src[1])}; + ir.SetScalarReg(dst, ir.ReadLane(value, lane)); +} + +void Translator::V_WRITELANE_B32(const GcnInst& inst) { + const IR::VectorReg dst{inst.dst[0].code}; + const IR::U32 value{GetSrc(inst.src[0])}; + const IR::U32 lane{GetSrc(inst.src[1])}; + const IR::U32 old_value{GetSrc(inst.dst[0])}; + ir.SetVectorReg(dst, ir.WriteLane(old_value, value, lane)); } +// DS + void Translator::DS_ADD_U32(const GcnInst& inst, bool rtn) { const IR::U32 addr{GetSrc(inst.src[0])}; const IR::U32 data{GetSrc(inst.src[1])}; @@ -162,32 +123,100 @@ void Translator::DS_MAX_U32(const GcnInst& inst, bool is_signed, bool rtn) { } } -void Translator::S_BARRIER() { - ir.Barrier(); +void Translator::DS_WRITE(int bit_size, bool is_signed, bool is_pair, bool stride64, + const GcnInst& inst) { + const IR::U32 addr{ir.GetVectorReg(IR::VectorReg(inst.src[0].code))}; + const IR::VectorReg data0{inst.src[1].code}; + const IR::VectorReg data1{inst.src[2].code}; + if (is_pair) { + const u32 adj = (bit_size == 32 ? 4 : 8) * (stride64 ? 64 : 1); + const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset0 * adj))); + if (bit_size == 32) { + ir.WriteShared(32, ir.GetVectorReg(data0), addr0); + } else { + ir.WriteShared( + 64, ir.CompositeConstruct(ir.GetVectorReg(data0), ir.GetVectorReg(data0 + 1)), + addr0); + } + const IR::U32 addr1 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset1 * adj))); + if (bit_size == 32) { + ir.WriteShared(32, ir.GetVectorReg(data1), addr1); + } else { + ir.WriteShared( + 64, ir.CompositeConstruct(ir.GetVectorReg(data1), ir.GetVectorReg(data1 + 1)), + addr1); + } + } else if (bit_size == 64) { + const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset0))); + const IR::Value data = + ir.CompositeConstruct(ir.GetVectorReg(data0), ir.GetVectorReg(data0 + 1)); + ir.WriteShared(bit_size, data, addr0); + } else { + const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset0))); + ir.WriteShared(bit_size, ir.GetVectorReg(data0), addr0); + } } -// TEMP: for Amplitude 2016 -// These are "warp instructions" and are stubbed to work outside of compute shaders. -// The game makes use of them and works fine when the assertions are removed. +void Translator::DS_SWIZZLE_B32(const GcnInst& inst) { + const u8 offset0 = inst.control.ds.offset0; + const u8 offset1 = inst.control.ds.offset1; + const IR::U32 src{GetSrc(inst.src[1])}; + ASSERT(offset1 & 0x80); + const IR::U32 lane_id = ir.LaneId(); + const IR::U32 id_in_group = ir.BitwiseAnd(lane_id, ir.Imm32(0b11)); + const IR::U32 base = ir.ShiftLeftLogical(id_in_group, ir.Imm32(1)); + const IR::U32 index = + ir.IAdd(lane_id, ir.BitFieldExtract(ir.Imm32(offset0), base, ir.Imm32(2))); + SetDst(inst.dst[0], ir.QuadShuffle(src, index)); +} -void Translator::V_READFIRSTLANE_B32(const GcnInst& inst) { - // ASSERT(info.stage != Stage::Compute); - SetDst(inst.dst[0], GetSrc(inst.src[0])); +void Translator::DS_READ(int bit_size, bool is_signed, bool is_pair, bool stride64, + const GcnInst& inst) { + const IR::U32 addr{ir.GetVectorReg(IR::VectorReg(inst.src[0].code))}; + IR::VectorReg dst_reg{inst.dst[0].code}; + if (is_pair) { + // Pair loads are either 32 or 64-bit + const u32 adj = (bit_size == 32 ? 4 : 8) * (stride64 ? 64 : 1); + const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset0 * adj))); + const IR::Value data0 = ir.LoadShared(bit_size, is_signed, addr0); + if (bit_size == 32) { + ir.SetVectorReg(dst_reg++, IR::U32{data0}); + } else { + ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(data0, 0)}); + ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(data0, 1)}); + } + const IR::U32 addr1 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset1 * adj))); + const IR::Value data1 = ir.LoadShared(bit_size, is_signed, addr1); + if (bit_size == 32) { + ir.SetVectorReg(dst_reg++, IR::U32{data1}); + } else { + ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(data1, 0)}); + ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(data1, 1)}); + } + } else if (bit_size == 64) { + const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset0))); + const IR::Value data = ir.LoadShared(bit_size, is_signed, addr0); + ir.SetVectorReg(dst_reg, IR::U32{ir.CompositeExtract(data, 0)}); + ir.SetVectorReg(dst_reg + 1, IR::U32{ir.CompositeExtract(data, 1)}); + } else { + const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset0))); + const IR::U32 data = IR::U32{ir.LoadShared(bit_size, is_signed, addr0)}; + ir.SetVectorReg(dst_reg, data); + } } -void Translator::V_READLANE_B32(const GcnInst& inst) { - const IR::ScalarReg dst{inst.dst[0].code}; - const IR::U32 value{GetSrc(inst.src[0])}; - const IR::U32 lane{GetSrc(inst.src[1])}; - ir.SetScalarReg(dst, ir.ReadLane(value, lane)); +void Translator::DS_APPEND(const GcnInst& inst) { + const u32 inst_offset = inst.control.ds.offset0; + const IR::U32 gds_offset = ir.IAdd(ir.GetM0(), ir.Imm32(inst_offset)); + const IR::U32 prev = ir.DataAppend(gds_offset); + SetDst(inst.dst[0], prev); } -void Translator::V_WRITELANE_B32(const GcnInst& inst) { - const IR::VectorReg dst{inst.dst[0].code}; - const IR::U32 value{GetSrc(inst.src[0])}; - const IR::U32 lane{GetSrc(inst.src[1])}; - const IR::U32 old_value{GetSrc(inst.dst[0])}; - ir.SetVectorReg(dst, ir.WriteLane(old_value, value, lane)); +void Translator::DS_CONSUME(const GcnInst& inst) { + const u32 inst_offset = inst.control.ds.offset0; + const IR::U32 gds_offset = ir.IAdd(ir.GetM0(), ir.Imm32(inst_offset)); + const IR::U32 prev = ir.DataConsume(gds_offset); + SetDst(inst.dst[0], prev); } } // namespace Shader::Gcn diff --git a/src/shader_recompiler/frontend/translate/export.cpp b/src/shader_recompiler/frontend/translate/export.cpp index d80de002c9f..7d901822d9e 100644 --- a/src/shader_recompiler/frontend/translate/export.cpp +++ b/src/shader_recompiler/frontend/translate/export.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include "shader_recompiler/frontend/translate/translate.h" +#include "shader_recompiler/runtime_info.h" namespace Shader::Gcn { @@ -19,12 +20,34 @@ void Translator::EmitExport(const GcnInst& inst) { IR::VectorReg(inst.src[3].code), }; + const auto swizzle = [&](u32 comp) { + if (!IR::IsMrt(attrib)) { + return comp; + } + const u32 index = u32(attrib) - u32(IR::Attribute::RenderTarget0); + switch (runtime_info.fs_info.mrt_swizzles[index]) { + case MrtSwizzle::Identity: + return comp; + case MrtSwizzle::Alt: + static constexpr std::array AltSwizzle = {2, 1, 0, 3}; + return AltSwizzle[comp]; + case MrtSwizzle::Reverse: + static constexpr std::array RevSwizzle = {3, 2, 1, 0}; + return RevSwizzle[comp]; + case MrtSwizzle::ReverseAlt: + static constexpr std::array AltRevSwizzle = {3, 0, 1, 2}; + return AltRevSwizzle[comp]; + default: + UNREACHABLE(); + } + }; + const auto unpack = [&](u32 idx) { const IR::Value value = ir.UnpackHalf2x16(ir.GetVectorReg(vsrc[idx])); const IR::F32 r = IR::F32{ir.CompositeExtract(value, 0)}; const IR::F32 g = IR::F32{ir.CompositeExtract(value, 1)}; - ir.SetAttribute(attrib, r, idx * 2); - ir.SetAttribute(attrib, g, idx * 2 + 1); + ir.SetAttribute(attrib, r, swizzle(idx * 2)); + ir.SetAttribute(attrib, g, swizzle(idx * 2 + 1)); }; // Components are float16 packed into a VGPR @@ -45,9 +68,12 @@ void Translator::EmitExport(const GcnInst& inst) { continue; } const IR::F32 comp = ir.GetVectorReg(vsrc[i]); - ir.SetAttribute(attrib, comp, i); + ir.SetAttribute(attrib, comp, swizzle(i)); } } + if (IR::IsMrt(attrib)) { + info.mrt_mask |= 1u << u8(attrib); + } } } // namespace Shader::Gcn diff --git a/src/shader_recompiler/frontend/translate/scalar_alu.cpp b/src/shader_recompiler/frontend/translate/scalar_alu.cpp index af258cd1964..1e572a97f96 100644 --- a/src/shader_recompiler/frontend/translate/scalar_alu.cpp +++ b/src/shader_recompiler/frontend/translate/scalar_alu.cpp @@ -17,67 +17,83 @@ void Translator::EmitScalarAlu(const GcnInst& inst) { } default: switch (inst.opcode) { - case Opcode::S_MOV_B32: - return S_MOV(inst); - case Opcode::S_MUL_I32: - return S_MUL_I32(inst); - case Opcode::S_AND_SAVEEXEC_B64: - return S_AND_SAVEEXEC_B64(inst); - case Opcode::S_MOV_B64: - return S_MOV_B64(inst); + // SOP2 + case Opcode::S_ADD_U32: + return S_ADD_U32(inst); + case Opcode::S_SUB_U32: + return S_SUB_U32(inst); + case Opcode::S_ADD_I32: + return S_ADD_I32(inst); + case Opcode::S_SUB_I32: + return S_SUB_U32(inst); + case Opcode::S_ADDC_U32: + return S_ADDC_U32(inst); + case Opcode::S_MIN_I32: + return S_MIN_U32(true, inst); + case Opcode::S_MIN_U32: + return S_MIN_U32(false, inst); + case Opcode::S_MAX_I32: + return S_MAX_U32(true, inst); + case Opcode::S_MAX_U32: + return S_MAX_U32(false, inst); + case Opcode::S_CSELECT_B32: + return S_CSELECT_B32(inst); + case Opcode::S_CSELECT_B64: + return S_CSELECT_B64(inst); + case Opcode::S_AND_B32: + return S_AND_B32(NegateMode::None, inst); + case Opcode::S_AND_B64: + return S_AND_B64(NegateMode::None, inst); + case Opcode::S_OR_B32: + return S_OR_B32(inst); case Opcode::S_OR_B64: return S_OR_B64(NegateMode::None, false, inst); - case Opcode::S_NOR_B64: - return S_OR_B64(NegateMode::Result, false, inst); + case Opcode::S_XOR_B32: + return S_XOR_B32(inst); case Opcode::S_XOR_B64: return S_OR_B64(NegateMode::None, true, inst); - case Opcode::S_XNOR_B64: - return S_OR_B64(NegateMode::Result, true, inst); + case Opcode::S_ANDN2_B32: + return S_AND_B32(NegateMode::Src1, inst); + case Opcode::S_ANDN2_B64: + return S_AND_B64(NegateMode::Src1, inst); case Opcode::S_ORN2_B64: return S_OR_B64(NegateMode::Src1, false, inst); - case Opcode::S_AND_B64: - return S_AND_B64(NegateMode::None, inst); + case Opcode::S_NAND_B32: + return S_AND_B32(NegateMode::Result, inst); case Opcode::S_NAND_B64: return S_AND_B64(NegateMode::Result, inst); - case Opcode::S_ANDN2_B64: - return S_AND_B64(NegateMode::Src1, inst); - case Opcode::S_NOT_B64: - return S_NOT_B64(inst); - case Opcode::S_ADD_I32: - return S_ADD_I32(inst); - case Opcode::S_AND_B32: - return S_AND_B32(inst); - case Opcode::S_ASHR_I32: - return S_ASHR_I32(inst); - case Opcode::S_OR_B32: - return S_OR_B32(inst); + case Opcode::S_NOR_B64: + return S_OR_B64(NegateMode::Result, false, inst); + case Opcode::S_XNOR_B64: + return S_OR_B64(NegateMode::Result, true, inst); case Opcode::S_LSHL_B32: return S_LSHL_B32(inst); case Opcode::S_LSHR_B32: return S_LSHR_B32(inst); - case Opcode::S_CSELECT_B32: - return S_CSELECT_B32(inst); - case Opcode::S_CSELECT_B64: - return S_CSELECT_B64(inst); - case Opcode::S_BFE_U32: - return S_BFE_U32(inst); + case Opcode::S_ASHR_I32: + return S_ASHR_I32(inst); case Opcode::S_BFM_B32: return S_BFM_B32(inst); - case Opcode::S_BREV_B32: - return S_BREV_B32(inst); - case Opcode::S_ADD_U32: - return S_ADD_U32(inst); - case Opcode::S_ADDC_U32: - return S_ADDC_U32(inst); - case Opcode::S_SUB_U32: - case Opcode::S_SUB_I32: - return S_SUB_U32(inst); - case Opcode::S_MIN_U32: - return S_MIN_U32(inst); - case Opcode::S_MAX_U32: - return S_MAX_U32(inst); + case Opcode::S_MUL_I32: + return S_MUL_I32(inst); + case Opcode::S_BFE_U32: + return S_BFE_U32(inst); + case Opcode::S_ABSDIFF_I32: + return S_ABSDIFF_I32(inst); + + // SOP1 + case Opcode::S_MOV_B32: + return S_MOV(inst); + case Opcode::S_MOV_B64: + return S_MOV_B64(inst); + case Opcode::S_NOT_B64: + return S_NOT_B64(inst); case Opcode::S_WQM_B64: break; + case Opcode::S_BREV_B32: + return S_BREV_B32(inst); + case Opcode::S_AND_SAVEEXEC_B64: + return S_AND_SAVEEXEC_B64(inst); default: LogMissingOpcode(inst); } @@ -119,6 +135,7 @@ void Translator::EmitSOPC(const GcnInst& inst) { void Translator::EmitSOPK(const GcnInst& inst) { switch (inst.opcode) { + // SOPK case Opcode::S_MOVK_I32: return S_MOVK(inst); @@ -157,165 +174,129 @@ void Translator::EmitSOPK(const GcnInst& inst) { } } -void Translator::S_MOVK(const GcnInst& inst) { - const auto simm16 = inst.control.sopk.simm; - if (simm16 & (1 << 15)) { - // TODO: need to verify the case of imm sign extension - UNREACHABLE(); - } - SetDst(inst.dst[0], ir.Imm32(simm16)); -} +// SOP2 -void Translator::S_ADDK_I32(const GcnInst& inst) { - const s32 simm16 = inst.control.sopk.simm; - SetDst(inst.dst[0], ir.IAdd(GetSrc(inst.dst[0]), ir.Imm32(simm16))); +void Translator::S_ADD_U32(const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + const IR::U32 src1{GetSrc(inst.src[1])}; + SetDst(inst.dst[0], ir.IAdd(src0, src1)); + // TODO: Carry out + ir.SetScc(ir.Imm1(false)); } -void Translator::S_MULK_I32(const GcnInst& inst) { - const s32 simm16 = inst.control.sopk.simm; - SetDst(inst.dst[0], ir.IMul(GetSrc(inst.dst[0]), ir.Imm32(simm16))); +void Translator::S_SUB_U32(const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + const IR::U32 src1{GetSrc(inst.src[1])}; + SetDst(inst.dst[0], ir.ISub(src0, src1)); + // TODO: Carry out + ir.SetScc(ir.Imm1(false)); } -void Translator::S_MOV(const GcnInst& inst) { - SetDst(inst.dst[0], GetSrc(inst.src[0])); +void Translator::S_ADD_I32(const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + const IR::U32 src1{GetSrc(inst.src[1])}; + SetDst(inst.dst[0], ir.IAdd(src0, src1)); + // TODO: Overflow flag } -void Translator::S_MUL_I32(const GcnInst& inst) { - SetDst(inst.dst[0], ir.IMul(GetSrc(inst.src[0]), GetSrc(inst.src[1]))); +void Translator::S_ADDC_U32(const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + const IR::U32 src1{GetSrc(inst.src[1])}; + const IR::U32 carry{ir.Select(ir.GetScc(), ir.Imm32(1U), ir.Imm32(0U))}; + SetDst(inst.dst[0], ir.IAdd(ir.IAdd(src0, src1), carry)); } -void Translator::S_CMP(ConditionOp cond, bool is_signed, const GcnInst& inst) { - const IR::U32 lhs = GetSrc(inst.src[0]); - const IR::U32 rhs = GetSrc(inst.src[1]); - const IR::U1 result = [&] { - switch (cond) { - case ConditionOp::EQ: - return ir.IEqual(lhs, rhs); - case ConditionOp::LG: - return ir.INotEqual(lhs, rhs); - case ConditionOp::GT: - return ir.IGreaterThan(lhs, rhs, is_signed); - case ConditionOp::GE: - return ir.IGreaterThanEqual(lhs, rhs, is_signed); - case ConditionOp::LT: - return ir.ILessThan(lhs, rhs, is_signed); - case ConditionOp::LE: - return ir.ILessThanEqual(lhs, rhs, is_signed); - default: - UNREACHABLE(); - } - }(); - ir.SetScc(result); +void Translator::S_MIN_U32(bool is_signed, const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + const IR::U32 src1{GetSrc(inst.src[1])}; + const IR::U32 result = ir.IMin(src0, src1, is_signed); + SetDst(inst.dst[0], result); + ir.SetScc(ir.IEqual(result, src0)); } -void Translator::S_CMPK(ConditionOp cond, bool is_signed, const GcnInst& inst) { - const s32 simm16 = inst.control.sopk.simm; - const IR::U32 lhs = GetSrc(inst.dst[0]); - const IR::U32 rhs = ir.Imm32(simm16); - const IR::U1 result = [&] { - switch (cond) { - case ConditionOp::EQ: - return ir.IEqual(lhs, rhs); - case ConditionOp::LG: - return ir.INotEqual(lhs, rhs); - case ConditionOp::GT: - return ir.IGreaterThan(lhs, rhs, is_signed); - case ConditionOp::GE: - return ir.IGreaterThanEqual(lhs, rhs, is_signed); - case ConditionOp::LT: - return ir.ILessThan(lhs, rhs, is_signed); - case ConditionOp::LE: - return ir.ILessThanEqual(lhs, rhs, is_signed); - default: - UNREACHABLE(); - } - }(); - ir.SetScc(result); +void Translator::S_MAX_U32(bool is_signed, const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + const IR::U32 src1{GetSrc(inst.src[1])}; + const IR::U32 result = ir.IMax(src0, src1, is_signed); + SetDst(inst.dst[0], result); + ir.SetScc(ir.IEqual(result, src0)); } -void Translator::S_AND_SAVEEXEC_B64(const GcnInst& inst) { - // This instruction normally operates on 64-bit data (EXEC, VCC, SGPRs) - // However here we flatten it to 1-bit EXEC and 1-bit VCC. For the destination - // SGPR we have a special IR opcode for SPGRs that act as thread masks. - const IR::U1 exec{ir.GetExec()}; - const IR::U1 src = [&] { - switch (inst.src[0].field) { - case OperandField::VccLo: - return ir.GetVcc(); - case OperandField::ScalarGPR: - return ir.GetThreadBitScalarReg(IR::ScalarReg(inst.src[0].code)); - default: - UNREACHABLE(); - } - }(); - - switch (inst.dst[0].field) { - case OperandField::ScalarGPR: - ir.SetThreadBitScalarReg(IR::ScalarReg(inst.dst[0].code), exec); - break; - case OperandField::VccLo: - ir.SetVcc(exec); - break; - default: - UNREACHABLE(); - } - - // Update EXEC. - const IR::U1 result = ir.LogicalAnd(exec, src); - ir.SetExec(result); - ir.SetScc(result); +void Translator::S_CSELECT_B32(const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + const IR::U32 src1{GetSrc(inst.src[1])}; + SetDst(inst.dst[0], IR::U32{ir.Select(ir.GetScc(), src0, src1)}); } -void Translator::S_MOV_B64(const GcnInst& inst) { - const IR::U1 src = [&] { - switch (inst.src[0].field) { +void Translator::S_CSELECT_B64(const GcnInst& inst) { + const auto get_src = [&](const InstOperand& operand) { + switch (operand.field) { case OperandField::VccLo: return ir.GetVcc(); case OperandField::ExecLo: return ir.GetExec(); case OperandField::ScalarGPR: - return ir.GetThreadBitScalarReg(IR::ScalarReg(inst.src[0].code)); + return ir.GetThreadBitScalarReg(IR::ScalarReg(operand.code)); case OperandField::ConstZero: return ir.Imm1(false); default: UNREACHABLE(); } - }(); + }; + const IR::U1 src0{get_src(inst.src[0])}; + const IR::U1 src1{get_src(inst.src[1])}; + const IR::U1 result{ir.Select(ir.GetScc(), src0, src1)}; switch (inst.dst[0].field) { - case OperandField::ScalarGPR: - ir.SetThreadBitScalarReg(IR::ScalarReg(inst.dst[0].code), src); - break; - case OperandField::ExecLo: - ir.SetExec(src); - break; case OperandField::VccLo: - ir.SetVcc(src); + ir.SetVcc(result); + break; + case OperandField::ScalarGPR: + ir.SetThreadBitScalarReg(IR::ScalarReg(inst.dst[0].code), result); break; default: UNREACHABLE(); } } -void Translator::S_OR_B64(NegateMode negate, bool is_xor, const GcnInst& inst) { +void Translator::S_AND_B32(NegateMode negate, const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + IR::U32 src1{GetSrc(inst.src[1])}; + if (negate == NegateMode::Src1) { + src1 = ir.BitwiseNot(src1); + } + IR::U32 result{ir.BitwiseAnd(src0, src1)}; + if (negate == NegateMode::Result) { + result = ir.BitwiseNot(result); + } + SetDst(inst.dst[0], result); + ir.SetScc(ir.INotEqual(result, ir.Imm32(0))); +} + +void Translator::S_AND_B64(NegateMode negate, const GcnInst& inst) { const auto get_src = [&](const InstOperand& operand) { switch (operand.field) { - case OperandField::ExecLo: - return ir.GetExec(); case OperandField::VccLo: return ir.GetVcc(); + case OperandField::ExecLo: + return ir.GetExec(); case OperandField::ScalarGPR: return ir.GetThreadBitScalarReg(IR::ScalarReg(operand.code)); + case OperandField::ConstZero: + return ir.Imm1(false); + case OperandField::SignedConstIntNeg: + ASSERT_MSG(-s32(operand.code) + SignedConstIntNegMin - 1 == -1, + "SignedConstIntNeg must be -1"); + return ir.Imm1(true); default: UNREACHABLE(); } }; - const IR::U1 src0{get_src(inst.src[0])}; IR::U1 src1{get_src(inst.src[1])}; if (negate == NegateMode::Src1) { src1 = ir.LogicalNot(src1); } - IR::U1 result = is_xor ? ir.LogicalXor(src0, src1) : ir.LogicalOr(src0, src1); + IR::U1 result = ir.LogicalAnd(src0, src1); if (negate == NegateMode::Result) { result = ir.LogicalNot(result); } @@ -327,30 +308,42 @@ void Translator::S_OR_B64(NegateMode negate, bool is_xor, const GcnInst& inst) { case OperandField::ScalarGPR: ir.SetThreadBitScalarReg(IR::ScalarReg(inst.dst[0].code), result); break; + case OperandField::ExecLo: + ir.SetExec(result); + break; default: UNREACHABLE(); } } -void Translator::S_AND_B64(NegateMode negate, const GcnInst& inst) { +void Translator::S_OR_B32(const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + const IR::U32 src1{GetSrc(inst.src[1])}; + const IR::U32 result{ir.BitwiseOr(src0, src1)}; + SetDst(inst.dst[0], result); + ir.SetScc(ir.INotEqual(result, ir.Imm32(0))); +} + +void Translator::S_OR_B64(NegateMode negate, bool is_xor, const GcnInst& inst) { const auto get_src = [&](const InstOperand& operand) { switch (operand.field) { - case OperandField::VccLo: - return ir.GetVcc(); case OperandField::ExecLo: return ir.GetExec(); + case OperandField::VccLo: + return ir.GetVcc(); case OperandField::ScalarGPR: return ir.GetThreadBitScalarReg(IR::ScalarReg(operand.code)); default: UNREACHABLE(); } }; + const IR::U1 src0{get_src(inst.src[0])}; IR::U1 src1{get_src(inst.src[1])}; if (negate == NegateMode::Src1) { src1 = ir.LogicalNot(src1); } - IR::U1 result = ir.LogicalAnd(src0, src1); + IR::U1 result = is_xor ? ir.LogicalXor(src0, src1) : ir.LogicalOr(src0, src1); if (negate == NegateMode::Result) { result = ir.LogicalNot(result); } @@ -362,114 +355,154 @@ void Translator::S_AND_B64(NegateMode negate, const GcnInst& inst) { case OperandField::ScalarGPR: ir.SetThreadBitScalarReg(IR::ScalarReg(inst.dst[0].code), result); break; - case OperandField::ExecLo: - ir.SetExec(result); - break; default: UNREACHABLE(); } } -void Translator::S_ADD_I32(const GcnInst& inst) { +void Translator::S_XOR_B32(const GcnInst& inst) { const IR::U32 src0{GetSrc(inst.src[0])}; const IR::U32 src1{GetSrc(inst.src[1])}; - SetDst(inst.dst[0], ir.IAdd(src0, src1)); - // TODO: Overflow flag + const IR::U32 result{ir.BitwiseXor(src0, src1)}; + SetDst(inst.dst[0], result); + ir.SetScc(ir.INotEqual(result, ir.Imm32(0))); } -void Translator::S_AND_B32(const GcnInst& inst) { +void Translator::S_LSHL_B32(const GcnInst& inst) { const IR::U32 src0{GetSrc(inst.src[0])}; const IR::U32 src1{GetSrc(inst.src[1])}; - const IR::U32 result{ir.BitwiseAnd(src0, src1)}; + const IR::U32 result = ir.ShiftLeftLogical(src0, ir.BitwiseAnd(src1, ir.Imm32(0x1F))); SetDst(inst.dst[0], result); ir.SetScc(ir.INotEqual(result, ir.Imm32(0))); } -void Translator::S_ASHR_I32(const GcnInst& inst) { +void Translator::S_LSHR_B32(const GcnInst& inst) { const IR::U32 src0{GetSrc(inst.src[0])}; const IR::U32 src1{GetSrc(inst.src[1])}; - const IR::U32 result{ir.ShiftRightArithmetic(src0, src1)}; + const IR::U32 result{ir.ShiftRightLogical(src0, src1)}; SetDst(inst.dst[0], result); ir.SetScc(ir.INotEqual(result, ir.Imm32(0))); } -void Translator::S_OR_B32(const GcnInst& inst) { +void Translator::S_ASHR_I32(const GcnInst& inst) { const IR::U32 src0{GetSrc(inst.src[0])}; const IR::U32 src1{GetSrc(inst.src[1])}; - const IR::U32 result{ir.BitwiseOr(src0, src1)}; + const IR::U32 result{ir.ShiftRightArithmetic(src0, src1)}; SetDst(inst.dst[0], result); ir.SetScc(ir.INotEqual(result, ir.Imm32(0))); } -void Translator::S_LSHR_B32(const GcnInst& inst) { +void Translator::S_BFM_B32(const GcnInst& inst) { + const IR::U32 src0{ir.BitwiseAnd(GetSrc(inst.src[0]), ir.Imm32(0x1F))}; + const IR::U32 src1{ir.BitwiseAnd(GetSrc(inst.src[1]), ir.Imm32(0x1F))}; + const IR::U32 mask{ir.ISub(ir.ShiftLeftLogical(ir.Imm32(1u), src0), ir.Imm32(1))}; + SetDst(inst.dst[0], ir.ShiftLeftLogical(mask, src1)); +} + +void Translator::S_MUL_I32(const GcnInst& inst) { + SetDst(inst.dst[0], ir.IMul(GetSrc(inst.src[0]), GetSrc(inst.src[1]))); +} + +void Translator::S_BFE_U32(const GcnInst& inst) { const IR::U32 src0{GetSrc(inst.src[0])}; const IR::U32 src1{GetSrc(inst.src[1])}; - const IR::U32 result{ir.ShiftRightLogical(src0, src1)}; + const IR::U32 offset{ir.BitwiseAnd(src1, ir.Imm32(0x1F))}; + const IR::U32 count{ir.BitFieldExtract(src1, ir.Imm32(16), ir.Imm32(7))}; + const IR::U32 result{ir.BitFieldExtract(src0, offset, count)}; SetDst(inst.dst[0], result); ir.SetScc(ir.INotEqual(result, ir.Imm32(0))); } -void Translator::S_CSELECT_B32(const GcnInst& inst) { +void Translator::S_ABSDIFF_I32(const GcnInst& inst) { const IR::U32 src0{GetSrc(inst.src[0])}; const IR::U32 src1{GetSrc(inst.src[1])}; - SetDst(inst.dst[0], IR::U32{ir.Select(ir.GetScc(), src0, src1)}); + const IR::U32 result{ir.IAbs(ir.ISub(src0, src1))}; + SetDst(inst.dst[0], result); + ir.SetScc(ir.INotEqual(result, ir.Imm32(0))); } -void Translator::S_CSELECT_B64(const GcnInst& inst) { - const auto get_src = [&](const InstOperand& operand) { - switch (operand.field) { +// SOPK + +void Translator::S_MOVK(const GcnInst& inst) { + const auto simm16 = inst.control.sopk.simm; + if (simm16 & (1 << 15)) { + // TODO: need to verify the case of imm sign extension + UNREACHABLE(); + } + SetDst(inst.dst[0], ir.Imm32(simm16)); +} + +void Translator::S_CMPK(ConditionOp cond, bool is_signed, const GcnInst& inst) { + const s32 simm16 = inst.control.sopk.simm; + const IR::U32 lhs = GetSrc(inst.dst[0]); + const IR::U32 rhs = ir.Imm32(simm16); + const IR::U1 result = [&] { + switch (cond) { + case ConditionOp::EQ: + return ir.IEqual(lhs, rhs); + case ConditionOp::LG: + return ir.INotEqual(lhs, rhs); + case ConditionOp::GT: + return ir.IGreaterThan(lhs, rhs, is_signed); + case ConditionOp::GE: + return ir.IGreaterThanEqual(lhs, rhs, is_signed); + case ConditionOp::LT: + return ir.ILessThan(lhs, rhs, is_signed); + case ConditionOp::LE: + return ir.ILessThanEqual(lhs, rhs, is_signed); + default: + UNREACHABLE(); + } + }(); + ir.SetScc(result); +} + +void Translator::S_ADDK_I32(const GcnInst& inst) { + const s32 simm16 = inst.control.sopk.simm; + SetDst(inst.dst[0], ir.IAdd(GetSrc(inst.dst[0]), ir.Imm32(simm16))); +} + +void Translator::S_MULK_I32(const GcnInst& inst) { + const s32 simm16 = inst.control.sopk.simm; + SetDst(inst.dst[0], ir.IMul(GetSrc(inst.dst[0]), ir.Imm32(simm16))); +} + +// SOP1 + +void Translator::S_MOV(const GcnInst& inst) { + SetDst(inst.dst[0], GetSrc(inst.src[0])); +} + +void Translator::S_MOV_B64(const GcnInst& inst) { + const IR::U1 src = [&] { + switch (inst.src[0].field) { case OperandField::VccLo: return ir.GetVcc(); case OperandField::ExecLo: return ir.GetExec(); case OperandField::ScalarGPR: - return ir.GetThreadBitScalarReg(IR::ScalarReg(operand.code)); + return ir.GetThreadBitScalarReg(IR::ScalarReg(inst.src[0].code)); case OperandField::ConstZero: return ir.Imm1(false); default: UNREACHABLE(); } - }; - const IR::U1 src0{get_src(inst.src[0])}; - const IR::U1 src1{get_src(inst.src[1])}; - const IR::U1 result{ir.Select(ir.GetScc(), src0, src1)}; + }(); switch (inst.dst[0].field) { - case OperandField::VccLo: - ir.SetVcc(result); - break; case OperandField::ScalarGPR: - ir.SetThreadBitScalarReg(IR::ScalarReg(inst.dst[0].code), result); + ir.SetThreadBitScalarReg(IR::ScalarReg(inst.dst[0].code), src); + break; + case OperandField::ExecLo: + ir.SetExec(src); + break; + case OperandField::VccLo: + ir.SetVcc(src); break; default: UNREACHABLE(); } } -void Translator::S_BFE_U32(const GcnInst& inst) { - const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::U32 src1{GetSrc(inst.src[1])}; - const IR::U32 offset{ir.BitwiseAnd(src1, ir.Imm32(0x1F))}; - const IR::U32 count{ir.BitFieldExtract(src1, ir.Imm32(16), ir.Imm32(7))}; - const IR::U32 result{ir.BitFieldExtract(src0, offset, count)}; - SetDst(inst.dst[0], result); - ir.SetScc(ir.INotEqual(result, ir.Imm32(0))); -} - -void Translator::S_LSHL_B32(const GcnInst& inst) { - const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::U32 src1{GetSrc(inst.src[1])}; - const IR::U32 result = ir.ShiftLeftLogical(src0, ir.BitwiseAnd(src1, ir.Imm32(0x1F))); - SetDst(inst.dst[0], result); - ir.SetScc(ir.INotEqual(result, ir.Imm32(0))); -} - -void Translator::S_BFM_B32(const GcnInst& inst) { - const IR::U32 src0{ir.BitwiseAnd(GetSrc(inst.src[0]), ir.Imm32(0x1F))}; - const IR::U32 src1{ir.BitwiseAnd(GetSrc(inst.src[1]), ir.Imm32(0x1F))}; - const IR::U32 mask{ir.ISub(ir.ShiftLeftLogical(ir.Imm32(1u), src0), ir.Imm32(1))}; - SetDst(inst.dst[0], ir.ShiftLeftLogical(mask, src1)); -} - void Translator::S_NOT_B64(const GcnInst& inst) { const auto get_src = [&](const InstOperand& operand) { switch (operand.field) { @@ -479,6 +512,8 @@ void Translator::S_NOT_B64(const GcnInst& inst) { return ir.GetExec(); case OperandField::ScalarGPR: return ir.GetThreadBitScalarReg(IR::ScalarReg(operand.code)); + case OperandField::ConstZero: + return ir.Imm1(false); default: UNREACHABLE(); } @@ -493,6 +528,9 @@ void Translator::S_NOT_B64(const GcnInst& inst) { case OperandField::ScalarGPR: ir.SetThreadBitScalarReg(IR::ScalarReg(inst.dst[0].code), result); break; + case OperandField::ExecLo: + ir.SetExec(result); + break; default: UNREACHABLE(); } @@ -502,22 +540,6 @@ void Translator::S_BREV_B32(const GcnInst& inst) { SetDst(inst.dst[0], ir.BitReverse(GetSrc(inst.src[0]))); } -void Translator::S_ADD_U32(const GcnInst& inst) { - const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::U32 src1{GetSrc(inst.src[1])}; - SetDst(inst.dst[0], ir.IAdd(src0, src1)); - // TODO: Carry out - ir.SetScc(ir.Imm1(false)); -} - -void Translator::S_SUB_U32(const GcnInst& inst) { - const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::U32 src1{GetSrc(inst.src[1])}; - SetDst(inst.dst[0], ir.ISub(src0, src1)); - // TODO: Carry out - ir.SetScc(ir.Imm1(false)); -} - void Translator::S_GETPC_B64(u32 pc, const GcnInst& inst) { // This only really exists to let resource tracking pass know // there is an inline cbuf. @@ -526,27 +548,63 @@ void Translator::S_GETPC_B64(u32 pc, const GcnInst& inst) { ir.SetScalarReg(dst + 1, ir.Imm32(0)); } -void Translator::S_ADDC_U32(const GcnInst& inst) { - const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::U32 src1{GetSrc(inst.src[1])}; - const IR::U32 carry{ir.Select(ir.GetScc(), ir.Imm32(1U), ir.Imm32(0U))}; - SetDst(inst.dst[0], ir.IAdd(ir.IAdd(src0, src1), carry)); -} +void Translator::S_AND_SAVEEXEC_B64(const GcnInst& inst) { + // This instruction normally operates on 64-bit data (EXEC, VCC, SGPRs) + // However here we flatten it to 1-bit EXEC and 1-bit VCC. For the destination + // SGPR we have a special IR opcode for SPGRs that act as thread masks. + const IR::U1 exec{ir.GetExec()}; + const IR::U1 src = [&] { + switch (inst.src[0].field) { + case OperandField::VccLo: + return ir.GetVcc(); + case OperandField::ScalarGPR: + return ir.GetThreadBitScalarReg(IR::ScalarReg(inst.src[0].code)); + default: + UNREACHABLE(); + } + }(); -void Translator::S_MAX_U32(const GcnInst& inst) { - const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::U32 src1{GetSrc(inst.src[1])}; - const IR::U32 result = ir.UMax(src0, src1); - SetDst(inst.dst[0], result); - ir.SetScc(ir.IEqual(result, src0)); + switch (inst.dst[0].field) { + case OperandField::ScalarGPR: + ir.SetThreadBitScalarReg(IR::ScalarReg(inst.dst[0].code), exec); + break; + case OperandField::VccLo: + ir.SetVcc(exec); + break; + default: + UNREACHABLE(); + } + + // Update EXEC. + const IR::U1 result = ir.LogicalAnd(exec, src); + ir.SetExec(result); + ir.SetScc(result); } -void Translator::S_MIN_U32(const GcnInst& inst) { - const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::U32 src1{GetSrc(inst.src[1])}; - const IR::U32 result = ir.UMin(src0, src1); - SetDst(inst.dst[0], result); - ir.SetScc(ir.IEqual(result, src0)); +// SOPC + +void Translator::S_CMP(ConditionOp cond, bool is_signed, const GcnInst& inst) { + const IR::U32 lhs = GetSrc(inst.src[0]); + const IR::U32 rhs = GetSrc(inst.src[1]); + const IR::U1 result = [&] { + switch (cond) { + case ConditionOp::EQ: + return ir.IEqual(lhs, rhs); + case ConditionOp::LG: + return ir.INotEqual(lhs, rhs); + case ConditionOp::GT: + return ir.IGreaterThan(lhs, rhs, is_signed); + case ConditionOp::GE: + return ir.IGreaterThanEqual(lhs, rhs, is_signed); + case ConditionOp::LT: + return ir.ILessThan(lhs, rhs, is_signed); + case ConditionOp::LE: + return ir.ILessThanEqual(lhs, rhs, is_signed); + default: + UNREACHABLE(); + } + }(); + ir.SetScc(result); } } // namespace Shader::Gcn diff --git a/src/shader_recompiler/frontend/translate/scalar_memory.cpp b/src/shader_recompiler/frontend/translate/scalar_memory.cpp index 29f2acc2771..a6f8cafd7a3 100644 --- a/src/shader_recompiler/frontend/translate/scalar_memory.cpp +++ b/src/shader_recompiler/frontend/translate/scalar_memory.cpp @@ -9,6 +9,7 @@ static constexpr u32 SQ_SRC_LITERAL = 0xFF; void Translator::EmitScalarMemory(const GcnInst& inst) { switch (inst.opcode) { + // SMRD case Opcode::S_LOAD_DWORDX4: return S_LOAD_DWORD(4, inst); case Opcode::S_LOAD_DWORDX8: @@ -30,6 +31,8 @@ void Translator::EmitScalarMemory(const GcnInst& inst) { } } +// SMRD + void Translator::S_LOAD_DWORD(int num_dwords, const GcnInst& inst) { const auto& smrd = inst.control.smrd; const u32 dword_offset = [&] -> u32 { diff --git a/src/shader_recompiler/frontend/translate/translate.cpp b/src/shader_recompiler/frontend/translate/translate.cpp index eb86310b84d..cfef5858a44 100644 --- a/src/shader_recompiler/frontend/translate/translate.cpp +++ b/src/shader_recompiler/frontend/translate/translate.cpp @@ -7,6 +7,7 @@ #include "shader_recompiler/exception.h" #include "shader_recompiler/frontend/fetch_shader.h" #include "shader_recompiler/frontend/translate/translate.h" +#include "shader_recompiler/info.h" #include "shader_recompiler/runtime_info.h" #include "video_core/amdgpu/resource.h" @@ -16,8 +17,9 @@ namespace Shader::Gcn { -Translator::Translator(IR::Block* block_, Info& info_, const Profile& profile_) - : ir{*block_, block_->begin()}, info{info_}, profile{profile_} {} +Translator::Translator(IR::Block* block_, Info& info_, const RuntimeInfo& runtime_info_, + const Profile& profile_) + : ir{*block_, block_->begin()}, info{info_}, runtime_info{runtime_info_}, profile{profile_} {} void Translator::EmitPrologue() { ir.Prologue(); @@ -25,7 +27,7 @@ void Translator::EmitPrologue() { // Initialize user data. IR::ScalarReg dst_sreg = IR::ScalarReg::S0; - for (u32 i = 0; i < info.num_user_data; i++) { + for (u32 i = 0; i < runtime_info.num_user_data; i++) { ir.SetScalarReg(dst_sreg, ir.GetUserData(dst_sreg)); ++dst_sreg; } @@ -36,15 +38,15 @@ void Translator::EmitPrologue() { // v0: vertex ID, always present ir.SetVectorReg(dst_vreg++, ir.GetAttributeU32(IR::Attribute::VertexId)); // v1: instance ID, step rate 0 - if (info.num_input_vgprs > 0) { + if (runtime_info.num_input_vgprs > 0) { ir.SetVectorReg(dst_vreg++, ir.GetAttributeU32(IR::Attribute::InstanceId0)); } // v2: instance ID, step rate 1 - if (info.num_input_vgprs > 1) { + if (runtime_info.num_input_vgprs > 1) { ir.SetVectorReg(dst_vreg++, ir.GetAttributeU32(IR::Attribute::InstanceId1)); } // v3: instance ID, plain - if (info.num_input_vgprs > 2) { + if (runtime_info.num_input_vgprs > 2) { ir.SetVectorReg(dst_vreg++, ir.GetAttributeU32(IR::Attribute::InstanceId)); } break; @@ -64,13 +66,13 @@ void Translator::EmitPrologue() { ir.SetVectorReg(dst_vreg++, ir.GetAttributeU32(IR::Attribute::LocalInvocationId, 1)); ir.SetVectorReg(dst_vreg++, ir.GetAttributeU32(IR::Attribute::LocalInvocationId, 2)); - if (info.tgid_enable[0]) { + if (runtime_info.cs_info.tgid_enable[0]) { ir.SetScalarReg(dst_sreg++, ir.GetAttributeU32(IR::Attribute::WorkgroupId, 0)); } - if (info.tgid_enable[1]) { + if (runtime_info.cs_info.tgid_enable[1]) { ir.SetScalarReg(dst_sreg++, ir.GetAttributeU32(IR::Attribute::WorkgroupId, 1)); } - if (info.tgid_enable[2]) { + if (runtime_info.cs_info.tgid_enable[2]) { ir.SetScalarReg(dst_sreg++, ir.GetAttributeU32(IR::Attribute::WorkgroupId, 2)); } break; @@ -151,10 +153,11 @@ T Translator::GetSrc(const InstOperand& operand) { break; case OperandField::M0: if constexpr (is_float) { - UNREACHABLE(); + value = ir.BitCast(ir.GetM0()); } else { - return m0_value; + value = ir.GetM0(); } + break; default: UNREACHABLE(); } @@ -168,10 +171,10 @@ T Translator::GetSrc(const InstOperand& operand) { } } else { if (operand.input_modifier.abs) { - LOG_WARNING(Render_Vulkan, "Input abs modifier on integer instruction"); + value = ir.IAbs(value); } if (operand.input_modifier.neg) { - UNREACHABLE(); + value = ir.INeg(value); } } return value; @@ -208,7 +211,7 @@ T Translator::GetSrc64(const InstOperand& operand) { const auto value_lo = ir.GetVectorReg(IR::VectorReg(operand.code)); const auto value_hi = ir.GetVectorReg(IR::VectorReg(operand.code + 1)); if constexpr (is_float) { - UNREACHABLE(); + value = ir.PackFloat2x32(ir.CompositeConstruct(value_lo, value_hi)); } else { value = ir.PackUint2x32(ir.CompositeConstruct(value_lo, value_hi)); } @@ -278,12 +281,15 @@ template IR::F64 Translator::GetSrc64(const InstOperand&); void Translator::SetDst(const InstOperand& operand, const IR::U32F32& value) { IR::U32F32 result = value; - if (operand.output_modifier.multiplier != 0.f) { - result = ir.FPMul(result, ir.Imm32(operand.output_modifier.multiplier)); - } - if (operand.output_modifier.clamp) { - result = ir.FPSaturate(value); + if (value.Type() == IR::Type::F32) { + if (operand.output_modifier.multiplier != 0.f) { + result = ir.FPMul(result, ir.Imm32(operand.output_modifier.multiplier)); + } + if (operand.output_modifier.clamp) { + result = ir.FPSaturate(value); + } } + switch (operand.field) { case OperandField::ScalarGPR: return ir.SetScalarReg(IR::ScalarReg(operand.code), result); @@ -294,8 +300,7 @@ void Translator::SetDst(const InstOperand& operand, const IR::U32F32& value) { case OperandField::VccHi: return ir.SetVccHi(result); case OperandField::M0: - m0_value = result; - break; + return ir.SetM0(result); default: UNREACHABLE(); } @@ -445,7 +450,6 @@ void Translator::EmitFlowControl(u32 pc, const GcnInst& inst) { } void Translator::LogMissingOpcode(const GcnInst& inst) { - const u32 opcode = u32(inst.opcode); LOG_ERROR(Render_Recompiler, "Unknown opcode {} ({}, category = {})", magic_enum::enum_name(inst.opcode), u32(inst.opcode), magic_enum::enum_name(inst.category)); @@ -453,11 +457,11 @@ void Translator::LogMissingOpcode(const GcnInst& inst) { } void Translate(IR::Block* block, u32 pc, std::span inst_list, Info& info, - const Profile& profile) { + const RuntimeInfo& runtime_info, const Profile& profile) { if (inst_list.empty()) { return; } - Translator translator{block, info, profile}; + Translator translator{block, info, runtime_info, profile}; for (const auto& inst : inst_list) { pc += inst.length; diff --git a/src/shader_recompiler/frontend/translate/translate.h b/src/shader_recompiler/frontend/translate/translate.h index 8d418421cfc..7559b85335b 100644 --- a/src/shader_recompiler/frontend/translate/translate.h +++ b/src/shader_recompiler/frontend/translate/translate.h @@ -5,9 +5,9 @@ #include #include "shader_recompiler/frontend/instruction.h" +#include "shader_recompiler/info.h" #include "shader_recompiler/ir/basic_block.h" #include "shader_recompiler/ir/ir_emitter.h" -#include "shader_recompiler/runtime_info.h" namespace Shader { struct Info; @@ -55,172 +55,209 @@ enum class NegateMode : u32 { class Translator { public: - explicit Translator(IR::Block* block_, Info& info, const Profile& profile); + explicit Translator(IR::Block* block_, Info& info, const RuntimeInfo& runtime_info, + const Profile& profile); // Instruction categories void EmitPrologue(); void EmitFetch(const GcnInst& inst); - void EmitDataShare(const GcnInst& inst); - void EmitVectorInterpolation(const GcnInst& inst); - void EmitScalarMemory(const GcnInst& inst); - void EmitVectorMemory(const GcnInst& inst); void EmitExport(const GcnInst& inst); void EmitFlowControl(u32 pc, const GcnInst& inst); void EmitScalarAlu(const GcnInst& inst); + void EmitScalarMemory(const GcnInst& inst); void EmitVectorAlu(const GcnInst& inst); + void EmitVectorInterpolation(const GcnInst& inst); + void EmitDataShare(const GcnInst& inst); + void EmitVectorMemory(const GcnInst& inst); // Instruction encodings void EmitSOPC(const GcnInst& inst); void EmitSOPK(const GcnInst& inst); // Scalar ALU - void S_MOVK(const GcnInst& inst); - void S_MOV(const GcnInst& inst); - void S_MUL_I32(const GcnInst& inst); - void S_CMP(ConditionOp cond, bool is_signed, const GcnInst& inst); - void S_AND_SAVEEXEC_B64(const GcnInst& inst); - void S_MOV_B64(const GcnInst& inst); - void S_OR_B64(NegateMode negate, bool is_xor, const GcnInst& inst); - void S_AND_B64(NegateMode negate, const GcnInst& inst); + // SOP2 + void S_ADD_U32(const GcnInst& inst); + void S_SUB_U32(const GcnInst& inst); void S_ADD_I32(const GcnInst& inst); - void S_AND_B32(const GcnInst& inst); - void S_ASHR_I32(const GcnInst& inst); - void S_OR_B32(const GcnInst& inst); - void S_LSHR_B32(const GcnInst& inst); + void S_ADDC_U32(const GcnInst& inst); + void S_MIN_U32(bool is_signed, const GcnInst& inst); + void S_MAX_U32(bool is_signed, const GcnInst& inst); void S_CSELECT_B32(const GcnInst& inst); void S_CSELECT_B64(const GcnInst& inst); - void S_BFE_U32(const GcnInst& inst); + void S_AND_B32(NegateMode negate, const GcnInst& inst); + void S_AND_B64(NegateMode negate, const GcnInst& inst); + void S_OR_B32(const GcnInst& inst); + void S_OR_B64(NegateMode negate, bool is_xor, const GcnInst& inst); + void S_XOR_B32(const GcnInst& inst); void S_LSHL_B32(const GcnInst& inst); + void S_LSHR_B32(const GcnInst& inst); + void S_ASHR_I32(const GcnInst& inst); void S_BFM_B32(const GcnInst& inst); + void S_MUL_I32(const GcnInst& inst); + void S_BFE_U32(const GcnInst& inst); + void S_ABSDIFF_I32(const GcnInst& inst); + + // SOPK + void S_MOVK(const GcnInst& inst); + void S_CMPK(ConditionOp cond, bool is_signed, const GcnInst& inst); + void S_ADDK_I32(const GcnInst& inst); + void S_MULK_I32(const GcnInst& inst); + + // SOP1 + void S_MOV(const GcnInst& inst); + void S_MOV_B64(const GcnInst& inst); void S_NOT_B64(const GcnInst& inst); void S_BREV_B32(const GcnInst& inst); - void S_ADD_U32(const GcnInst& inst); - void S_SUB_U32(const GcnInst& inst); void S_GETPC_B64(u32 pc, const GcnInst& inst); - void S_ADDC_U32(const GcnInst& inst); - void S_MULK_I32(const GcnInst& inst); - void S_ADDK_I32(const GcnInst& inst); - void S_MAX_U32(const GcnInst& inst); - void S_MIN_U32(const GcnInst& inst); - void S_CMPK(ConditionOp cond, bool is_signed, const GcnInst& inst); + void S_AND_SAVEEXEC_B64(const GcnInst& inst); + + // SOPC + void S_CMP(ConditionOp cond, bool is_signed, const GcnInst& inst); + + // SOPP + void S_BARRIER(); // Scalar Memory + // SMRD void S_LOAD_DWORD(int num_dwords, const GcnInst& inst); void S_BUFFER_LOAD_DWORD(int num_dwords, const GcnInst& inst); // Vector ALU - void V_MOV(const GcnInst& inst); - void V_SAD(const GcnInst& inst); - void V_MAC_F32(const GcnInst& inst); - void V_CVT_PKRTZ_F16_F32(const GcnInst& inst); - void V_CVT_F32_F16(const GcnInst& inst); - void V_CVT_F16_F32(const GcnInst& inst); - void V_MUL_F32(const GcnInst& inst); + // VOP2 void V_CNDMASK_B32(const GcnInst& inst); - void V_OR_B32(bool is_xor, const GcnInst& inst); - void V_AND_B32(const GcnInst& inst); - void V_LSHLREV_B32(const GcnInst& inst); + void V_READLANE_B32(const GcnInst& inst); + void V_WRITELANE_B32(const GcnInst& inst); + void V_ADD_F32(const GcnInst& inst); + void V_SUB_F32(const GcnInst& inst); + void V_SUBREV_F32(const GcnInst& inst); + void V_MUL_F32(const GcnInst& inst); + void V_MUL_I32_I24(const GcnInst& inst); + void V_MIN_F32(const GcnInst& inst, bool is_legacy = false); + void V_MAX_F32(const GcnInst& inst, bool is_legacy = false); + void V_MIN_I32(const GcnInst& inst); + void V_MIN_U32(const GcnInst& inst); + void V_MAX_U32(bool is_signed, const GcnInst& inst); + void V_LSHR_B32(const GcnInst& inst); + void V_LSHRREV_B32(const GcnInst& inst); + void V_ASHR_I32(const GcnInst& inst); + void V_ASHRREV_I32(const GcnInst& inst); void V_LSHL_B32(const GcnInst& inst); - void V_LSHL_B64(const GcnInst& inst); + void V_LSHLREV_B32(const GcnInst& inst); + void V_AND_B32(const GcnInst& inst); + void V_OR_B32(bool is_xor, const GcnInst& inst); + void V_BFM_B32(const GcnInst& inst); + void V_MAC_F32(const GcnInst& inst); + void V_MADMK_F32(const GcnInst& inst); + void V_BCNT_U32_B32(const GcnInst& inst); + void V_MBCNT_U32_B32(bool is_low, const GcnInst& inst); void V_ADD_I32(const GcnInst& inst); + void V_SUB_I32(const GcnInst& inst); + void V_SUBREV_I32(const GcnInst& inst); void V_ADDC_U32(const GcnInst& inst); + void V_LDEXP_F32(const GcnInst& inst); + void V_CVT_PKNORM_U16_F32(const GcnInst& inst); + void V_CVT_PKRTZ_F16_F32(const GcnInst& inst); + + // VOP1 + void V_MOV(const GcnInst& inst); + void V_READFIRSTLANE_B32(const GcnInst& inst); void V_CVT_F32_I32(const GcnInst& inst); void V_CVT_F32_U32(const GcnInst& inst); - void V_MAD_F32(const GcnInst& inst); - void V_FRACT_F32(const GcnInst& inst); - void V_ADD_F32(const GcnInst& inst); + void V_CVT_U32_F32(const GcnInst& inst); + void V_CVT_I32_F32(const GcnInst& inst); + void V_CVT_F16_F32(const GcnInst& inst); + void V_CVT_F32_F16(const GcnInst& inst); + void V_CVT_FLR_I32_F32(const GcnInst& inst); void V_CVT_OFF_F32_I4(const GcnInst& inst); - void V_MED3_F32(const GcnInst& inst); - void V_MED3_I32(const GcnInst& inst); + void V_CVT_F32_UBYTE(u32 index, const GcnInst& inst); + void V_FRACT_F32(const GcnInst& inst); + void V_TRUNC_F32(const GcnInst& inst); + void V_CEIL_F32(const GcnInst& inst); + void V_RNDNE_F32(const GcnInst& inst); void V_FLOOR_F32(const GcnInst& inst); - void V_SUB_F32(const GcnInst& inst); + void V_EXP_F32(const GcnInst& inst); + void V_LOG_F32(const GcnInst& inst); void V_RCP_F32(const GcnInst& inst); - void V_FMA_F32(const GcnInst& inst); - void V_CMP_F32(ConditionOp op, bool set_exec, const GcnInst& inst); - void V_MAX_F32(const GcnInst& inst, bool is_legacy = false); - void V_MAX_U32(bool is_signed, const GcnInst& inst); + void V_RCP_F64(const GcnInst& inst); void V_RSQ_F32(const GcnInst& inst); - void V_SIN_F32(const GcnInst& inst); - void V_LOG_F32(const GcnInst& inst); - void V_EXP_F32(const GcnInst& inst); void V_SQRT_F32(const GcnInst& inst); - void V_MIN_F32(const GcnInst& inst, bool is_legacy = false); - void V_MIN3_F32(const GcnInst& inst); - void V_MIN3_I32(const GcnInst& inst); - void V_MADMK_F32(const GcnInst& inst); - void V_CUBEMA_F32(const GcnInst& inst); - void V_CUBESC_F32(const GcnInst& inst); - void V_CUBETC_F32(const GcnInst& inst); - void V_CUBEID_F32(const GcnInst& inst); - void V_CVT_U32_F32(const GcnInst& inst); - void V_SUBREV_F32(const GcnInst& inst); - void V_SUBREV_I32(const GcnInst& inst); - void V_MAD_U64_U32(const GcnInst& inst); - void V_CMP_U32(ConditionOp op, bool is_signed, bool set_exec, const GcnInst& inst); - void V_LSHRREV_B32(const GcnInst& inst); - void V_MUL_HI_U32(bool is_signed, const GcnInst& inst); - void V_SAD_U32(const GcnInst& inst); - void V_BFE_U32(bool is_signed, const GcnInst& inst); - void V_MAD_I32_I24(const GcnInst& inst, bool is_signed = true); - void V_MUL_I32_I24(const GcnInst& inst); - void V_SUB_I32(const GcnInst& inst); - void V_LSHR_B32(const GcnInst& inst); - void V_ASHRREV_I32(const GcnInst& inst); - void V_ASHR_I32(const GcnInst& inst); - void V_MAD_U32_U24(const GcnInst& inst); - void V_RNDNE_F32(const GcnInst& inst); - void V_BCNT_U32_B32(const GcnInst& inst); + void V_SIN_F32(const GcnInst& inst); void V_COS_F32(const GcnInst& inst); - void V_MAX3_F32(const GcnInst& inst); - void V_MAX3_U32(const GcnInst& inst); - void V_CVT_I32_F32(const GcnInst& inst); - void V_MIN_I32(const GcnInst& inst); - void V_MUL_LO_U32(const GcnInst& inst); - void V_TRUNC_F32(const GcnInst& inst); - void V_CEIL_F32(const GcnInst& inst); - void V_MIN_U32(const GcnInst& inst); - void V_CMP_NE_U64(const GcnInst& inst); - void V_BFI_B32(const GcnInst& inst); void V_NOT_B32(const GcnInst& inst); - void V_CVT_F32_UBYTE(u32 index, const GcnInst& inst); void V_BFREV_B32(const GcnInst& inst); - void V_LDEXP_F32(const GcnInst& inst); - void V_CVT_FLR_I32_F32(const GcnInst& inst); - void V_CMP_CLASS_F32(const GcnInst& inst); + void V_FFBH_U32(const GcnInst& inst); void V_FFBL_B32(const GcnInst& inst); - void V_MBCNT_U32_B32(bool is_low, const GcnInst& inst); + void V_MOVRELD_B32(const GcnInst& inst); + void V_MOVRELS_B32(const GcnInst& inst); + void V_MOVRELSD_B32(const GcnInst& inst); - // Vector Memory - void BUFFER_LOAD(u32 num_dwords, bool is_typed, const GcnInst& inst); - void BUFFER_LOAD_FORMAT(u32 num_dwords, const GcnInst& inst); - void BUFFER_STORE(u32 num_dwords, bool is_typed, const GcnInst& inst); - void BUFFER_STORE_FORMAT(u32 num_dwords, const GcnInst& inst); - void BUFFER_ATOMIC(AtomicOp op, const GcnInst& inst); + // VOPC + void V_CMP_F32(ConditionOp op, bool set_exec, const GcnInst& inst); + void V_CMP_U32(ConditionOp op, bool is_signed, bool set_exec, const GcnInst& inst); + void V_CMP_NE_U64(const GcnInst& inst); + void V_CMP_CLASS_F32(const GcnInst& inst); + + // VOP3a + void V_MAD_F32(const GcnInst& inst); + void V_MAD_I32_I24(const GcnInst& inst, bool is_signed = false); + void V_MAD_U32_U24(const GcnInst& inst); + void V_CUBEID_F32(const GcnInst& inst); + void V_CUBESC_F32(const GcnInst& inst); + void V_CUBETC_F32(const GcnInst& inst); + void V_CUBEMA_F32(const GcnInst& inst); + void V_BFE_U32(bool is_signed, const GcnInst& inst); + void V_BFI_B32(const GcnInst& inst); + void V_FMA_F32(const GcnInst& inst); + void V_FMA_F64(const GcnInst& inst); + void V_MIN3_F32(const GcnInst& inst); + void V_MIN3_I32(const GcnInst& inst); + void V_MAX3_F32(const GcnInst& inst); + void V_MAX3_U32(bool is_signed, const GcnInst& inst); + void V_MED3_F32(const GcnInst& inst); + void V_MED3_I32(const GcnInst& inst); + void V_SAD(const GcnInst& inst); + void V_SAD_U32(const GcnInst& inst); + void V_CVT_PK_U8_F32(const GcnInst& inst); + void V_LSHL_B64(const GcnInst& inst); + void V_MUL_F64(const GcnInst& inst); + void V_MAX_F64(const GcnInst& inst); + void V_MUL_LO_U32(const GcnInst& inst); + void V_MUL_HI_U32(bool is_signed, const GcnInst& inst); + void V_MAD_U64_U32(const GcnInst& inst); // Vector interpolation + // VINTRP void V_INTERP_P2_F32(const GcnInst& inst); void V_INTERP_MOV_F32(const GcnInst& inst); // Data share - void DS_SWIZZLE_B32(const GcnInst& inst); - void DS_READ(int bit_size, bool is_signed, bool is_pair, const GcnInst& inst); - void DS_WRITE(int bit_size, bool is_signed, bool is_pair, bool stride64, const GcnInst& inst); + // DS void DS_ADD_U32(const GcnInst& inst, bool rtn); void DS_MIN_U32(const GcnInst& inst, bool is_signed, bool rtn); void DS_MAX_U32(const GcnInst& inst, bool is_signed, bool rtn); - void V_READFIRSTLANE_B32(const GcnInst& inst); - void V_READLANE_B32(const GcnInst& inst); - void V_WRITELANE_B32(const GcnInst& inst); - void S_BARRIER(); + void DS_WRITE(int bit_size, bool is_signed, bool is_pair, bool stride64, const GcnInst& inst); + void DS_SWIZZLE_B32(const GcnInst& inst); + void DS_READ(int bit_size, bool is_signed, bool is_pair, bool stride64, const GcnInst& inst); + void DS_APPEND(const GcnInst& inst); + void DS_CONSUME(const GcnInst& inst); + // Buffer Memory + // MUBUF / MTBUF + void BUFFER_LOAD(u32 num_dwords, bool is_typed, const GcnInst& inst); + void BUFFER_LOAD_FORMAT(u32 num_dwords, const GcnInst& inst); + void BUFFER_STORE(u32 num_dwords, bool is_typed, const GcnInst& inst); + void BUFFER_STORE_FORMAT(u32 num_dwords, const GcnInst& inst); + void BUFFER_ATOMIC(AtomicOp op, const GcnInst& inst); + + // Image Memory // MIMG + void IMAGE_LOAD(bool has_mip, const GcnInst& inst); + void IMAGE_STORE(const GcnInst& inst); void IMAGE_GET_RESINFO(const GcnInst& inst); + void IMAGE_ATOMIC(AtomicOp op, const GcnInst& inst); void IMAGE_SAMPLE(const GcnInst& inst); void IMAGE_GATHER(const GcnInst& inst); - void IMAGE_STORE(const GcnInst& inst); - void IMAGE_LOAD(bool has_mip, const GcnInst& inst); void IMAGE_GET_LOD(const GcnInst& inst); - void IMAGE_ATOMIC(AtomicOp op, const GcnInst& inst); private: template @@ -230,17 +267,21 @@ class Translator { void SetDst(const InstOperand& operand, const IR::U32F32& value); void SetDst64(const InstOperand& operand, const IR::U64F64& value_raw); + // Vector ALU Helprers + IR::U32 VMovRelSHelper(u32 src_vgprno, const IR::U32 m0); + void VMovRelDHelper(u32 dst_vgprno, const IR::U32 src_val, const IR::U32 m0); + void LogMissingOpcode(const GcnInst& inst); private: IR::IREmitter ir; Info& info; + const RuntimeInfo& runtime_info; const Profile& profile; - IR::U32 m0_value; bool opcode_missing = false; }; void Translate(IR::Block* block, u32 block_base, std::span inst_list, Info& info, - const Profile& profile); + const RuntimeInfo& runtime_info, const Profile& profile); } // namespace Shader::Gcn diff --git a/src/shader_recompiler/frontend/translate/vector_alu.cpp b/src/shader_recompiler/frontend/translate/vector_alu.cpp index 61a0440e3cc..debc39f3520 100644 --- a/src/shader_recompiler/frontend/translate/vector_alu.cpp +++ b/src/shader_recompiler/frontend/translate/vector_alu.cpp @@ -1,61 +1,120 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include "shader_recompiler/frontend/opcodes.h" #include "shader_recompiler/frontend/translate/translate.h" namespace Shader::Gcn { void Translator::EmitVectorAlu(const GcnInst& inst) { switch (inst.opcode) { - case Opcode::V_LSHLREV_B32: - return V_LSHLREV_B32(inst); - case Opcode::V_LSHL_B32: - return V_LSHL_B32(inst); - case Opcode::V_LSHL_B64: - return V_LSHL_B64(inst); - case Opcode::V_BFREV_B32: - return V_BFREV_B32(inst); - case Opcode::V_BFE_U32: - return V_BFE_U32(false, inst); - case Opcode::V_BFE_I32: - return V_BFE_U32(true, inst); - case Opcode::V_BFI_B32: - return V_BFI_B32(inst); + // VOP2 + case Opcode::V_CNDMASK_B32: + return V_CNDMASK_B32(inst); + case Opcode::V_READLANE_B32: + return V_READLANE_B32(inst); + case Opcode::V_WRITELANE_B32: + return V_WRITELANE_B32(inst); + case Opcode::V_ADD_F32: + return V_ADD_F32(inst); + case Opcode::V_SUB_F32: + return V_SUB_F32(inst); + case Opcode::V_SUBREV_F32: + return V_SUBREV_F32(inst); + case Opcode::V_MAC_LEGACY_F32: + return V_MAC_F32(inst); + case Opcode::V_MUL_LEGACY_F32: + return V_MUL_F32(inst); + case Opcode::V_MUL_F32: + return V_MUL_F32(inst); + case Opcode::V_MUL_I32_I24: + return V_MUL_I32_I24(inst); + case Opcode::V_MUL_U32_U24: + return V_MUL_I32_I24(inst); + case Opcode::V_MIN_LEGACY_F32: + return V_MIN_F32(inst, true); + case Opcode::V_MAX_LEGACY_F32: + return V_MAX_F32(inst, true); + case Opcode::V_MIN_F32: + return V_MIN_F32(inst, false); + case Opcode::V_MAX_F32: + return V_MAX_F32(inst); + case Opcode::V_MIN_I32: + return V_MIN_I32(inst); + case Opcode::V_MAX_I32: + return V_MAX_U32(true, inst); + case Opcode::V_MIN_U32: + return V_MIN_U32(inst); + case Opcode::V_MAX_U32: + return V_MAX_U32(false, inst); case Opcode::V_LSHR_B32: return V_LSHR_B32(inst); - case Opcode::V_ASHRREV_I32: - return V_ASHRREV_I32(inst); - case Opcode::V_ASHR_I32: - return V_ASHR_I32(inst); case Opcode::V_LSHRREV_B32: return V_LSHRREV_B32(inst); - case Opcode::V_NOT_B32: - return V_NOT_B32(inst); + case Opcode::V_ASHR_I32: + return V_ASHR_I32(inst); + case Opcode::V_ASHRREV_I32: + return V_ASHRREV_I32(inst); + case Opcode::V_LSHL_B32: + return V_LSHL_B32(inst); + case Opcode::V_LSHLREV_B32: + return V_LSHLREV_B32(inst); case Opcode::V_AND_B32: return V_AND_B32(inst); case Opcode::V_OR_B32: return V_OR_B32(false, inst); case Opcode::V_XOR_B32: return V_OR_B32(true, inst); - case Opcode::V_FFBL_B32: - return V_FFBL_B32(inst); - - case Opcode::V_MOV_B32: - return V_MOV(inst); + case Opcode::V_BFM_B32: + return V_BFM_B32(inst); + case Opcode::V_MAC_F32: + return V_MAC_F32(inst); + case Opcode::V_MADMK_F32: + return V_MADMK_F32(inst); + case Opcode::V_MADAK_F32: + return V_FMA_F32(inst); + case Opcode::V_BCNT_U32_B32: + return V_BCNT_U32_B32(inst); + case Opcode::V_MBCNT_LO_U32_B32: + return V_MBCNT_U32_B32(true, inst); + case Opcode::V_MBCNT_HI_U32_B32: + return V_MBCNT_U32_B32(false, inst); case Opcode::V_ADD_I32: return V_ADD_I32(inst); + case Opcode::V_SUB_I32: + return V_SUB_I32(inst); + case Opcode::V_SUBREV_I32: + return V_SUBREV_I32(inst); case Opcode::V_ADDC_U32: return V_ADDC_U32(inst); + case Opcode::V_LDEXP_F32: + return V_LDEXP_F32(inst); + case Opcode::V_CVT_PKNORM_U16_F32: + return V_CVT_PKNORM_U16_F32(inst); + case Opcode::V_CVT_PKRTZ_F16_F32: + return V_CVT_PKRTZ_F16_F32(inst); + + // VOP1 + case Opcode::V_MOV_B32: + return V_MOV(inst); + case Opcode::V_READFIRSTLANE_B32: + return V_READFIRSTLANE_B32(inst); case Opcode::V_CVT_F32_I32: return V_CVT_F32_I32(inst); case Opcode::V_CVT_F32_U32: return V_CVT_F32_U32(inst); - case Opcode::V_CVT_PKRTZ_F16_F32: - return V_CVT_PKRTZ_F16_F32(inst); - case Opcode::V_CVT_F32_F16: - return V_CVT_F32_F16(inst); + case Opcode::V_CVT_U32_F32: + return V_CVT_U32_F32(inst); + case Opcode::V_CVT_I32_F32: + return V_CVT_I32_F32(inst); case Opcode::V_CVT_F16_F32: return V_CVT_F16_F32(inst); + case Opcode::V_CVT_F32_F16: + return V_CVT_F32_F16(inst); + case Opcode::V_CVT_FLR_I32_F32: + return V_CVT_FLR_I32_F32(inst); + case Opcode::V_CVT_OFF_F32_I4: + return V_CVT_OFF_F32_I4(inst); case Opcode::V_CVT_F32_UBYTE0: return V_CVT_F32_UBYTE(0, inst); case Opcode::V_CVT_F32_UBYTE1: @@ -64,34 +123,55 @@ void Translator::EmitVectorAlu(const GcnInst& inst) { return V_CVT_F32_UBYTE(2, inst); case Opcode::V_CVT_F32_UBYTE3: return V_CVT_F32_UBYTE(3, inst); - case Opcode::V_CVT_OFF_F32_I4: - return V_CVT_OFF_F32_I4(inst); - case Opcode::V_MAD_U64_U32: - return V_MAD_U64_U32(inst); - case Opcode::V_CMP_GE_I32: - return V_CMP_U32(ConditionOp::GE, true, false, inst); - case Opcode::V_CMP_EQ_I32: - return V_CMP_U32(ConditionOp::EQ, true, false, inst); - case Opcode::V_CMP_LE_I32: - return V_CMP_U32(ConditionOp::LE, true, false, inst); - case Opcode::V_CMP_NE_I32: - return V_CMP_U32(ConditionOp::LG, true, false, inst); - case Opcode::V_CMP_NE_U32: - return V_CMP_U32(ConditionOp::LG, false, false, inst); - case Opcode::V_CMP_EQ_U32: - return V_CMP_U32(ConditionOp::EQ, false, false, inst); - case Opcode::V_CMP_F_U32: - return V_CMP_U32(ConditionOp::F, false, false, inst); - case Opcode::V_CMP_LT_U32: - return V_CMP_U32(ConditionOp::LT, false, false, inst); - case Opcode::V_CMP_GT_U32: - return V_CMP_U32(ConditionOp::GT, false, false, inst); - case Opcode::V_CMP_GE_U32: - return V_CMP_U32(ConditionOp::GE, false, false, inst); - case Opcode::V_CMP_TRU_U32: - return V_CMP_U32(ConditionOp::TRU, false, false, inst); - case Opcode::V_CMP_NEQ_F32: - return V_CMP_F32(ConditionOp::LG, false, inst); + case Opcode::V_FRACT_F32: + return V_FRACT_F32(inst); + case Opcode::V_TRUNC_F32: + return V_TRUNC_F32(inst); + case Opcode::V_CEIL_F32: + return V_CEIL_F32(inst); + case Opcode::V_RNDNE_F32: + return V_RNDNE_F32(inst); + case Opcode::V_FLOOR_F32: + return V_FLOOR_F32(inst); + case Opcode::V_EXP_F32: + return V_EXP_F32(inst); + case Opcode::V_LOG_F32: + return V_LOG_F32(inst); + case Opcode::V_RCP_F32: + return V_RCP_F32(inst); + case Opcode::V_RCP_F64: + return V_RCP_F64(inst); + case Opcode::V_RCP_IFLAG_F32: + return V_RCP_F32(inst); + case Opcode::V_RSQ_CLAMP_F32: + return V_RSQ_F32(inst); + case Opcode::V_RSQ_LEGACY_F32: + return V_RSQ_F32(inst); + case Opcode::V_RSQ_F32: + return V_RSQ_F32(inst); + case Opcode::V_SQRT_F32: + return V_SQRT_F32(inst); + case Opcode::V_SIN_F32: + return V_SIN_F32(inst); + case Opcode::V_COS_F32: + return V_COS_F32(inst); + case Opcode::V_NOT_B32: + return V_NOT_B32(inst); + case Opcode::V_BFREV_B32: + return V_BFREV_B32(inst); + case Opcode::V_FFBH_U32: + return V_FFBH_U32(inst); + case Opcode::V_FFBL_B32: + return V_FFBL_B32(inst); + case Opcode::V_MOVRELD_B32: + return V_MOVRELD_B32(inst); + case Opcode::V_MOVRELS_B32: + return V_MOVRELS_B32(inst); + case Opcode::V_MOVRELSD_B32: + return V_MOVRELSD_B32(inst); + + // VOPC + // V_CMP_{OP16}_F32 case Opcode::V_CMP_F_F32: return V_CMP_F32(ConditionOp::F, false, inst); case Opcode::V_CMP_LT_F32: @@ -106,145 +186,20 @@ void Translator::EmitVectorAlu(const GcnInst& inst) { return V_CMP_F32(ConditionOp::LG, false, inst); case Opcode::V_CMP_GE_F32: return V_CMP_F32(ConditionOp::GE, false, inst); + case Opcode::V_CMP_U_F32: + return V_CMP_F32(ConditionOp::U, false, inst); + case Opcode::V_CMP_NGE_F32: + return V_CMP_F32(ConditionOp::LT, false, inst); + case Opcode::V_CMP_NGT_F32: + return V_CMP_F32(ConditionOp::LE, false, inst); case Opcode::V_CMP_NLE_F32: return V_CMP_F32(ConditionOp::GT, false, inst); + case Opcode::V_CMP_NEQ_F32: + return V_CMP_F32(ConditionOp::LG, false, inst); case Opcode::V_CMP_NLT_F32: return V_CMP_F32(ConditionOp::GE, false, inst); - case Opcode::V_CMP_NGT_F32: - return V_CMP_F32(ConditionOp::LE, false, inst); - case Opcode::V_CMP_NGE_F32: - return V_CMP_F32(ConditionOp::LT, false, inst); - case Opcode::V_CMP_U_F32: - return V_CMP_F32(ConditionOp::U, false, inst); - case Opcode::V_CNDMASK_B32: - return V_CNDMASK_B32(inst); - case Opcode::V_MAX_I32: - return V_MAX_U32(true, inst); - case Opcode::V_MAX_U32: - return V_MAX_U32(false, inst); - case Opcode::V_MIN_I32: - return V_MIN_I32(inst); - case Opcode::V_CUBEMA_F32: - return V_CUBEMA_F32(inst); - case Opcode::V_CUBESC_F32: - return V_CUBESC_F32(inst); - case Opcode::V_CUBETC_F32: - return V_CUBETC_F32(inst); - case Opcode::V_CUBEID_F32: - return V_CUBEID_F32(inst); - case Opcode::V_CVT_U32_F32: - return V_CVT_U32_F32(inst); - case Opcode::V_CVT_I32_F32: - return V_CVT_I32_F32(inst); - case Opcode::V_CVT_FLR_I32_F32: - return V_CVT_FLR_I32_F32(inst); - case Opcode::V_SUBREV_I32: - return V_SUBREV_I32(inst); - case Opcode::V_MUL_HI_U32: - return V_MUL_HI_U32(false, inst); - case Opcode::V_MUL_LO_I32: - return V_MUL_LO_U32(inst); - case Opcode::V_SAD_U32: - return V_SAD_U32(inst); - case Opcode::V_SUB_I32: - return V_SUB_I32(inst); - case Opcode::V_MAD_I32_I24: - return V_MAD_I32_I24(inst); - case Opcode::V_MUL_I32_I24: - case Opcode::V_MUL_U32_U24: - return V_MUL_I32_I24(inst); - case Opcode::V_MAD_U32_U24: - return V_MAD_U32_U24(inst); - case Opcode::V_BCNT_U32_B32: - return V_BCNT_U32_B32(inst); - case Opcode::V_MUL_LO_U32: - return V_MUL_LO_U32(inst); - case Opcode::V_MIN_U32: - return V_MIN_U32(inst); - case Opcode::V_CMP_NE_U64: - return V_CMP_NE_U64(inst); - case Opcode::V_READFIRSTLANE_B32: - return V_READFIRSTLANE_B32(inst); - case Opcode::V_READLANE_B32: - return V_READLANE_B32(inst); - case Opcode::V_WRITELANE_B32: - return V_WRITELANE_B32(inst); - - case Opcode::V_MAD_F32: - return V_MAD_F32(inst); - case Opcode::V_MAC_F32: - return V_MAC_F32(inst); - case Opcode::V_MUL_F32: - return V_MUL_F32(inst); - case Opcode::V_RCP_F32: - return V_RCP_F32(inst); - case Opcode::V_LDEXP_F32: - return V_LDEXP_F32(inst); - case Opcode::V_FRACT_F32: - return V_FRACT_F32(inst); - case Opcode::V_ADD_F32: - return V_ADD_F32(inst); - case Opcode::V_MED3_F32: - return V_MED3_F32(inst); - case Opcode::V_MED3_I32: - return V_MED3_I32(inst); - case Opcode::V_FLOOR_F32: - return V_FLOOR_F32(inst); - case Opcode::V_SUB_F32: - return V_SUB_F32(inst); - case Opcode::V_FMA_F32: - case Opcode::V_MADAK_F32: - return V_FMA_F32(inst); - case Opcode::V_MAX_F32: - return V_MAX_F32(inst); - case Opcode::V_RSQ_F32: - return V_RSQ_F32(inst); - case Opcode::V_SIN_F32: - return V_SIN_F32(inst); - case Opcode::V_COS_F32: - return V_COS_F32(inst); - case Opcode::V_LOG_F32: - return V_LOG_F32(inst); - case Opcode::V_EXP_F32: - return V_EXP_F32(inst); - case Opcode::V_SQRT_F32: - return V_SQRT_F32(inst); - case Opcode::V_MIN_F32: - return V_MIN_F32(inst, false); - case Opcode::V_MIN3_F32: - return V_MIN3_F32(inst); - case Opcode::V_MIN3_I32: - return V_MIN3_I32(inst); - case Opcode::V_MIN_LEGACY_F32: - return V_MIN_F32(inst, true); - case Opcode::V_MADMK_F32: - return V_MADMK_F32(inst); - case Opcode::V_SUBREV_F32: - return V_SUBREV_F32(inst); - case Opcode::V_RNDNE_F32: - return V_RNDNE_F32(inst); - case Opcode::V_MAX3_F32: - return V_MAX3_F32(inst); - case Opcode::V_MAX3_U32: - return V_MAX3_U32(inst); - case Opcode::V_TRUNC_F32: - return V_TRUNC_F32(inst); - case Opcode::V_CEIL_F32: - return V_CEIL_F32(inst); - case Opcode::V_MUL_LEGACY_F32: - return V_MUL_F32(inst); - case Opcode::V_MAC_LEGACY_F32: - return V_MAC_F32(inst); - case Opcode::V_MAD_LEGACY_F32: - return V_MAD_F32(inst); - case Opcode::V_MAX_LEGACY_F32: - return V_MAX_F32(inst, true); - case Opcode::V_RSQ_LEGACY_F32: - case Opcode::V_RSQ_CLAMP_F32: - return V_RSQ_F32(inst); - case Opcode::V_RCP_IFLAG_F32: - return V_RCP_F32(inst); + // V_CMPX_{OP16}_F32 case Opcode::V_CMPX_F_F32: return V_CMP_F32(ConditionOp::F, true, inst); case Opcode::V_CMPX_LT_F32: @@ -273,19 +228,50 @@ void Translator::EmitVectorAlu(const GcnInst& inst) { return V_CMP_F32(ConditionOp::GE, true, inst); case Opcode::V_CMPX_TRU_F32: return V_CMP_F32(ConditionOp::TRU, true, inst); - case Opcode::V_CMP_CLASS_F32: - return V_CMP_CLASS_F32(inst); - case Opcode::V_CMP_LE_U32: - return V_CMP_U32(ConditionOp::LE, false, false, inst); - case Opcode::V_CMP_GT_I32: - return V_CMP_U32(ConditionOp::GT, true, false, inst); + // V_CMP_{OP8}_I32 case Opcode::V_CMP_LT_I32: return V_CMP_U32(ConditionOp::LT, true, false, inst); - case Opcode::V_CMPX_GT_I32: - return V_CMP_U32(ConditionOp::GT, true, true, inst); + case Opcode::V_CMP_EQ_I32: + return V_CMP_U32(ConditionOp::EQ, true, false, inst); + case Opcode::V_CMP_LE_I32: + return V_CMP_U32(ConditionOp::LE, true, false, inst); + case Opcode::V_CMP_GT_I32: + return V_CMP_U32(ConditionOp::GT, true, false, inst); + case Opcode::V_CMP_NE_I32: + return V_CMP_U32(ConditionOp::LG, true, false, inst); + case Opcode::V_CMP_GE_I32: + return V_CMP_U32(ConditionOp::GE, true, false, inst); + + // V_CMPX_{OP8}_I32 case Opcode::V_CMPX_LT_I32: return V_CMP_U32(ConditionOp::LT, true, true, inst); + case Opcode::V_CMPX_EQ_I32: + return V_CMP_U32(ConditionOp::EQ, true, true, inst); + case Opcode::V_CMPX_GT_I32: + return V_CMP_U32(ConditionOp::GT, true, true, inst); + case Opcode::V_CMPX_LG_I32: + return V_CMP_U32(ConditionOp::LG, true, true, inst); + + // V_CMP_{OP8}_U32 + case Opcode::V_CMP_F_U32: + return V_CMP_U32(ConditionOp::F, false, false, inst); + case Opcode::V_CMP_LT_U32: + return V_CMP_U32(ConditionOp::LT, false, false, inst); + case Opcode::V_CMP_EQ_U32: + return V_CMP_U32(ConditionOp::EQ, false, false, inst); + case Opcode::V_CMP_LE_U32: + return V_CMP_U32(ConditionOp::LE, false, false, inst); + case Opcode::V_CMP_GT_U32: + return V_CMP_U32(ConditionOp::GT, false, false, inst); + case Opcode::V_CMP_NE_U32: + return V_CMP_U32(ConditionOp::LG, false, false, inst); + case Opcode::V_CMP_GE_U32: + return V_CMP_U32(ConditionOp::GE, false, false, inst); + case Opcode::V_CMP_TRU_U32: + return V_CMP_U32(ConditionOp::TRU, false, false, inst); + + // V_CMPX_{OP8}_U32 case Opcode::V_CMPX_F_U32: return V_CMP_U32(ConditionOp::F, false, true, inst); case Opcode::V_CMPX_LT_U32: @@ -302,16 +288,74 @@ void Translator::EmitVectorAlu(const GcnInst& inst) { return V_CMP_U32(ConditionOp::GE, false, true, inst); case Opcode::V_CMPX_TRU_U32: return V_CMP_U32(ConditionOp::TRU, false, true, inst); - case Opcode::V_CMPX_LG_I32: - return V_CMP_U32(ConditionOp::LG, true, true, inst); + case Opcode::V_CMPX_LE_I32: + return V_CMP_U32(ConditionOp::LE, true, true, inst); + // V_CMP_{OP8}_U64 + case Opcode::V_CMP_NE_U64: + return V_CMP_NE_U64(inst); - case Opcode::V_MBCNT_LO_U32_B32: - return V_MBCNT_U32_B32(true, inst); - case Opcode::V_MBCNT_HI_U32_B32: - return V_MBCNT_U32_B32(false, inst); - //Rock Band 4 and Amplitude 2016 use this opcode but are playable with it skipped. - case Opcode::V_MOVRELS_B32: - return; + case Opcode::V_CMP_CLASS_F32: + return V_CMP_CLASS_F32(inst); + + // VOP3a + case Opcode::V_MAD_LEGACY_F32: + return V_MAD_F32(inst); + case Opcode::V_MAD_F32: + return V_MAD_F32(inst); + case Opcode::V_MAD_I32_I24: + return V_MAD_I32_I24(inst); + case Opcode::V_MAD_U32_U24: + return V_MAD_U32_U24(inst); + case Opcode::V_CUBEID_F32: + return V_CUBEID_F32(inst); + case Opcode::V_CUBESC_F32: + return V_CUBESC_F32(inst); + case Opcode::V_CUBETC_F32: + return V_CUBETC_F32(inst); + case Opcode::V_CUBEMA_F32: + return V_CUBEMA_F32(inst); + case Opcode::V_BFE_U32: + return V_BFE_U32(false, inst); + case Opcode::V_BFE_I32: + return V_BFE_U32(true, inst); + case Opcode::V_BFI_B32: + return V_BFI_B32(inst); + case Opcode::V_FMA_F32: + return V_FMA_F32(inst); + case Opcode::V_FMA_F64: + return V_FMA_F64(inst); + case Opcode::V_MIN3_F32: + return V_MIN3_F32(inst); + case Opcode::V_MIN3_I32: + return V_MIN3_I32(inst); + case Opcode::V_MAX3_F32: + return V_MAX3_F32(inst); + case Opcode::V_MAX3_I32: + return V_MAX3_U32(true, inst); + case Opcode::V_MAX3_U32: + return V_MAX3_U32(false, inst); + case Opcode::V_MED3_F32: + return V_MED3_F32(inst); + case Opcode::V_MED3_I32: + return V_MED3_I32(inst); + case Opcode::V_SAD_U32: + return V_SAD_U32(inst); + case Opcode::V_CVT_PK_U8_F32: + return V_CVT_PK_U8_F32(inst); + case Opcode::V_LSHL_B64: + return V_LSHL_B64(inst); + case Opcode::V_MUL_F64: + return V_MUL_F64(inst); + case Opcode::V_MAX_F64: + return V_MAX_F64(inst); + case Opcode::V_MUL_LO_U32: + return V_MUL_LO_U32(inst); + case Opcode::V_MUL_HI_U32: + return V_MUL_HI_U32(false, inst); + case Opcode::V_MUL_LO_I32: + return V_MUL_LO_U32(inst); + case Opcode::V_MAD_U64_U32: + return V_MAD_U64_U32(inst); case Opcode::V_NOP: return; default: @@ -319,74 +363,98 @@ void Translator::EmitVectorAlu(const GcnInst& inst) { } } -void Translator::V_MOV(const GcnInst& inst) { - SetDst(inst.dst[0], GetSrc(inst.src[0])); +// VOP2 + +void Translator::V_CNDMASK_B32(const GcnInst& inst) { + const IR::ScalarReg flag_reg{inst.src[2].code}; + const IR::U1 flag = inst.src[2].field == OperandField::ScalarGPR + ? ir.GetThreadBitScalarReg(flag_reg) + : ir.GetVcc(); + const IR::Value result = + ir.Select(flag, GetSrc(inst.src[1]), GetSrc(inst.src[0])); + SetDst(inst.dst[0], IR::U32F32{result}); } -void Translator::V_SAD(const GcnInst& inst) { - const IR::U32 abs_diff = ir.IAbs(ir.ISub(GetSrc(inst.src[0]), GetSrc(inst.src[1]))); - SetDst(inst.dst[0], ir.IAdd(abs_diff, GetSrc(inst.src[2]))); +void Translator::V_ADD_F32(const GcnInst& inst) { + const IR::F32 src0{GetSrc(inst.src[0])}; + const IR::F32 src1{GetSrc(inst.src[1])}; + SetDst(inst.dst[0], ir.FPAdd(src0, src1)); } -void Translator::V_MAC_F32(const GcnInst& inst) { - SetDst(inst.dst[0], ir.FPFma(GetSrc(inst.src[0]), GetSrc(inst.src[1]), - GetSrc(inst.dst[0]))); +void Translator::V_SUB_F32(const GcnInst& inst) { + const IR::F32 src0{GetSrc(inst.src[0])}; + const IR::F32 src1{GetSrc(inst.src[1])}; + SetDst(inst.dst[0], ir.FPSub(src0, src1)); } -void Translator::V_CVT_PKRTZ_F16_F32(const GcnInst& inst) { - const IR::VectorReg dst_reg{inst.dst[0].code}; - const IR::Value vec_f32 = - ir.CompositeConstruct(GetSrc(inst.src[0]), GetSrc(inst.src[1])); - ir.SetVectorReg(dst_reg, ir.PackHalf2x16(vec_f32)); +void Translator::V_SUBREV_F32(const GcnInst& inst) { + const IR::F32 src0{GetSrc(inst.src[0])}; + const IR::F32 src1{GetSrc(inst.src[1])}; + SetDst(inst.dst[0], ir.FPSub(src1, src0)); } -void Translator::V_CVT_F32_F16(const GcnInst& inst) { - const IR::U32 src0 = GetSrc(inst.src[0]); - const IR::U16 src0l = ir.UConvert(16, src0); - SetDst(inst.dst[0], ir.FPConvert(32, ir.BitCast(src0l))); +void Translator::V_MUL_F32(const GcnInst& inst) { + SetDst(inst.dst[0], ir.FPMul(GetSrc(inst.src[0]), GetSrc(inst.src[1]))); } -void Translator::V_CVT_F16_F32(const GcnInst& inst) { - const IR::F32 src0 = GetSrc(inst.src[0]); - const IR::F16 src0fp16 = ir.FPConvert(16, src0); - SetDst(inst.dst[0], ir.UConvert(32, ir.BitCast(src0fp16))); +void Translator::V_MUL_I32_I24(const GcnInst& inst) { + const IR::U32 src0{ir.BitFieldExtract(GetSrc(inst.src[0]), ir.Imm32(0), ir.Imm32(24), true)}; + const IR::U32 src1{ir.BitFieldExtract(GetSrc(inst.src[1]), ir.Imm32(0), ir.Imm32(24), true)}; + SetDst(inst.dst[0], ir.IMul(src0, src1)); } -void Translator::V_MUL_F32(const GcnInst& inst) { - SetDst(inst.dst[0], ir.FPMul(GetSrc(inst.src[0]), GetSrc(inst.src[1]))); +void Translator::V_MIN_F32(const GcnInst& inst, bool is_legacy) { + const IR::F32 src0{GetSrc(inst.src[0])}; + const IR::F32 src1{GetSrc(inst.src[1])}; + SetDst(inst.dst[0], ir.FPMin(src0, src1, is_legacy)); } -void Translator::V_CNDMASK_B32(const GcnInst& inst) { - const IR::VectorReg dst_reg{inst.dst[0].code}; - const IR::ScalarReg flag_reg{inst.src[2].code}; - const IR::U1 flag = inst.src[2].field == OperandField::ScalarGPR - ? ir.GetThreadBitScalarReg(flag_reg) - : ir.GetVcc(); - const IR::Value result = - ir.Select(flag, GetSrc(inst.src[1]), GetSrc(inst.src[0])); - ir.SetVectorReg(dst_reg, IR::U32F32{result}); +void Translator::V_MAX_F32(const GcnInst& inst, bool is_legacy) { + const IR::F32 src0{GetSrc(inst.src[0])}; + const IR::F32 src1{GetSrc(inst.src[1])}; + SetDst(inst.dst[0], ir.FPMax(src0, src1, is_legacy)); } -void Translator::V_OR_B32(bool is_xor, const GcnInst& inst) { +void Translator::V_MIN_I32(const GcnInst& inst) { const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::U32 src1{ir.GetVectorReg(IR::VectorReg(inst.src[1].code))}; - const IR::VectorReg dst_reg{inst.dst[0].code}; - ir.SetVectorReg(dst_reg, - is_xor ? ir.BitwiseXor(src0, src1) : IR::U32(ir.BitwiseOr(src0, src1))); + const IR::U32 src1{GetSrc(inst.src[1])}; + SetDst(inst.dst[0], ir.SMin(src0, src1)); } -void Translator::V_AND_B32(const GcnInst& inst) { +void Translator::V_MIN_U32(const GcnInst& inst) { const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::U32 src1{ir.GetVectorReg(IR::VectorReg(inst.src[1].code))}; - const IR::VectorReg dst_reg{inst.dst[0].code}; - ir.SetVectorReg(dst_reg, ir.BitwiseAnd(src0, src1)); + const IR::U32 src1{GetSrc(inst.src[1])}; + SetDst(inst.dst[0], ir.IMin(src0, src1, false)); } -void Translator::V_LSHLREV_B32(const GcnInst& inst) { +void Translator::V_MAX_U32(bool is_signed, const GcnInst& inst) { const IR::U32 src0{GetSrc(inst.src[0])}; const IR::U32 src1{GetSrc(inst.src[1])}; - const IR::VectorReg dst_reg{inst.dst[0].code}; - ir.SetVectorReg(dst_reg, ir.ShiftLeftLogical(src1, ir.BitwiseAnd(src0, ir.Imm32(0x1F)))); + SetDst(inst.dst[0], ir.IMax(src0, src1, is_signed)); +} + +void Translator::V_LSHR_B32(const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + const IR::U32 src1{GetSrc(inst.src[1])}; + SetDst(inst.dst[0], ir.ShiftRightLogical(src0, ir.BitwiseAnd(src1, ir.Imm32(0x1F)))); +} + +void Translator::V_LSHRREV_B32(const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + const IR::U32 src1{GetSrc(inst.src[1])}; + SetDst(inst.dst[0], ir.ShiftRightLogical(src1, ir.BitwiseAnd(src0, ir.Imm32(0x1F)))); +} + +void Translator::V_ASHR_I32(const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + const IR::U32 src1{GetSrc(inst.src[1])}; + SetDst(inst.dst[0], ir.ShiftRightArithmetic(src0, ir.BitwiseAnd(src1, ir.Imm32(0x1F)))); +} + +void Translator::V_ASHRREV_I32(const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + const IR::U32 src1{GetSrc(inst.src[1])}; + SetDst(inst.dst[0], ir.ShiftRightArithmetic(src1, ir.BitwiseAnd(src0, ir.Imm32(0x1F)))); } void Translator::V_LSHL_B32(const GcnInst& inst) { @@ -395,26 +463,98 @@ void Translator::V_LSHL_B32(const GcnInst& inst) { SetDst(inst.dst[0], ir.ShiftLeftLogical(src0, ir.BitwiseAnd(src1, ir.Imm32(0x1F)))); } -void Translator::V_LSHL_B64(const GcnInst& inst) { - const IR::U64 src0{GetSrc64(inst.src[0])}; - const IR::U64 src1{GetSrc64(inst.src[1])}; - const IR::VectorReg dst_reg{inst.dst[0].code}; - ASSERT_MSG(src0.IsImmediate() && src0.U64() == 0 && src1.IsImmediate() && src1.U64() == 0, - "V_LSHL_B64 with non-zero src0 or src1 is not supported"); - ir.SetVectorReg(dst_reg, ir.Imm32(0)); - ir.SetVectorReg(dst_reg + 1, ir.Imm32(0)); +void Translator::V_LSHLREV_B32(const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + const IR::U32 src1{GetSrc(inst.src[1])}; + SetDst(inst.dst[0], ir.ShiftLeftLogical(src1, ir.BitwiseAnd(src0, ir.Imm32(0x1F)))); +} + +void Translator::V_AND_B32(const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + const IR::U32 src1{ir.GetVectorReg(IR::VectorReg(inst.src[1].code))}; + SetDst(inst.dst[0], ir.BitwiseAnd(src0, src1)); +} + +void Translator::V_OR_B32(bool is_xor, const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + const IR::U32 src1{ir.GetVectorReg(IR::VectorReg(inst.src[1].code))}; + SetDst(inst.dst[0], is_xor ? ir.BitwiseXor(src0, src1) : IR::U32(ir.BitwiseOr(src0, src1))); +} + +void Translator::V_BFM_B32(const GcnInst& inst) { + // bitmask width + const IR::U32 src0{ir.BitFieldExtract(GetSrc(inst.src[0]), ir.Imm32(0), ir.Imm32(4))}; + // bitmask offset + const IR::U32 src1{ir.BitFieldExtract(GetSrc(inst.src[1]), ir.Imm32(0), ir.Imm32(4))}; + const IR::U32 ones = ir.ISub(ir.ShiftLeftLogical(ir.Imm32(1), src0), ir.Imm32(1)); + SetDst(inst.dst[0], ir.ShiftLeftLogical(ones, src1)); +} + +void Translator::V_MAC_F32(const GcnInst& inst) { + SetDst(inst.dst[0], ir.FPFma(GetSrc(inst.src[0]), GetSrc(inst.src[1]), + GetSrc(inst.dst[0]))); +} + +void Translator::V_MADMK_F32(const GcnInst& inst) { + const IR::F32 src0{GetSrc(inst.src[0])}; + const IR::F32 src1{GetSrc(inst.src[1])}; + const IR::F32 k{GetSrc(inst.src[2])}; + SetDst(inst.dst[0], ir.FPFma(src0, k, src1)); +} + +void Translator::V_BCNT_U32_B32(const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + const IR::U32 src1{GetSrc(inst.src[1])}; + SetDst(inst.dst[0], ir.IAdd(ir.BitCount(src0), src1)); +} + +void Translator::V_MBCNT_U32_B32(bool is_low, const GcnInst& inst) { + if (!is_low) { + // v_mbcnt_hi_u32_b32 v2, -1, 0 + if (inst.src[0].field == OperandField::SignedConstIntNeg && inst.src[0].code == 193 && + inst.src[1].field == OperandField::ConstZero) { + return; + } + // v_mbcnt_hi_u32_b32 vX, exec_hi, 0 + if (inst.src[0].field == OperandField::ExecHi && + inst.src[1].field == OperandField::ConstZero) { + return; + } + } else { + // v_mbcnt_lo_u32_b32 v2, -1, vX + // used combined with above to fetch lane id in non-compute stages + if (inst.src[0].field == OperandField::SignedConstIntNeg && inst.src[0].code == 193) { + SetDst(inst.dst[0], ir.LaneId()); + } + // v_mbcnt_lo_u32_b32 v20, exec_lo, vX + // used combined in above for append buffer indexing. + if (inst.src[0].field == OperandField::ExecLo) { + SetDst(inst.dst[0], ir.Imm32(0)); + } + } } void Translator::V_ADD_I32(const GcnInst& inst) { const IR::U32 src0{GetSrc(inst.src[0])}; const IR::U32 src1{ir.GetVectorReg(IR::VectorReg(inst.src[1].code))}; - const IR::VectorReg dst_reg{inst.dst[0].code}; - ir.SetVectorReg(dst_reg, ir.IAdd(src0, src1)); + SetDst(inst.dst[0], ir.IAdd(src0, src1)); // TODO: Carry } -void Translator::V_ADDC_U32(const GcnInst& inst) { +void Translator::V_SUB_I32(const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + const IR::U32 src1{GetSrc(inst.src[1])}; + SetDst(inst.dst[0], ir.ISub(src0, src1)); +} + +void Translator::V_SUBREV_I32(const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + const IR::U32 src1{GetSrc(inst.src[1])}; + SetDst(inst.dst[0], ir.ISub(src1, src0)); + // TODO: Carry-out +} +void Translator::V_ADDC_U32(const GcnInst& inst) { const auto src0 = GetSrc(inst.src[0]); const auto src1 = GetSrc(inst.src[1]); @@ -434,8 +574,7 @@ void Translator::V_ADDC_U32(const GcnInst& inst) { const IR::U32 scarry = IR::U32{ir.Select(carry, ir.Imm32(1), ir.Imm32(0))}; const IR::U32 result = ir.IAdd(ir.IAdd(src0, src1), scarry); - const IR::VectorReg dst_reg{inst.dst[0].code}; - ir.SetVectorReg(dst_reg, result); + SetDst(inst.dst[0], result); const IR::U1 less_src0 = ir.ILessThan(result, src0, false); const IR::U1 less_src1 = ir.ILessThan(result, src1, false); @@ -443,86 +582,203 @@ void Translator::V_ADDC_U32(const GcnInst& inst) { ir.SetVcc(did_overflow); } -void Translator::V_CVT_F32_I32(const GcnInst& inst) { - const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::VectorReg dst_reg{inst.dst[0].code}; - ir.SetVectorReg(dst_reg, ir.ConvertSToF(32, 32, src0)); +void Translator::V_LDEXP_F32(const GcnInst& inst) { + const IR::F32 src0{GetSrc(inst.src[0])}; + const IR::U32 src1{GetSrc(inst.src[1])}; + SetDst(inst.dst[0], ir.FPLdexp(src0, src1)); +} + +void Translator::V_CVT_PKNORM_U16_F32(const GcnInst& inst) { + const IR::F32 src0{GetSrc(inst.src[0])}; + const IR::F32 src1{GetSrc(inst.src[1])}; + const IR::U32 dst0 = ir.ConvertFToU(32, ir.FPMul(src0, ir.Imm32(65535.f))); + const IR::U32 dst1 = ir.ConvertFToU(32, ir.FPMul(src1, ir.Imm32(65535.f))); + const IR::VectorReg dst_reg{inst.dst[0].code}; + ir.SetVectorReg(dst_reg, ir.BitFieldInsert(dst0, dst1, ir.Imm32(16), ir.Imm32(16))); +} + +void Translator::V_CVT_PKRTZ_F16_F32(const GcnInst& inst) { + const IR::Value vec_f32 = + ir.CompositeConstruct(GetSrc(inst.src[0]), GetSrc(inst.src[1])); + SetDst(inst.dst[0], ir.PackHalf2x16(vec_f32)); +} + +// VOP1 + +void Translator::V_MOV(const GcnInst& inst) { + SetDst(inst.dst[0], GetSrc(inst.src[0])); +} + +void Translator::V_CVT_F32_I32(const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + SetDst(inst.dst[0], ir.ConvertSToF(32, 32, src0)); +} + +void Translator::V_CVT_F32_U32(const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + SetDst(inst.dst[0], ir.ConvertUToF(32, 32, src0)); +} + +void Translator::V_CVT_U32_F32(const GcnInst& inst) { + const IR::F32 src0{GetSrc(inst.src[0])}; + SetDst(inst.dst[0], ir.ConvertFToU(32, src0)); +} + +void Translator::V_CVT_I32_F32(const GcnInst& inst) { + const IR::F32 src0{GetSrc(inst.src[0])}; + SetDst(inst.dst[0], ir.ConvertFToS(32, src0)); +} + +void Translator::V_CVT_F16_F32(const GcnInst& inst) { + const IR::F32 src0 = GetSrc(inst.src[0]); + const IR::F16 src0fp16 = ir.FPConvert(16, src0); + SetDst(inst.dst[0], ir.UConvert(32, ir.BitCast(src0fp16))); +} + +void Translator::V_CVT_F32_F16(const GcnInst& inst) { + const IR::U32 src0 = GetSrc(inst.src[0]); + const IR::U16 src0l = ir.UConvert(16, src0); + SetDst(inst.dst[0], ir.FPConvert(32, ir.BitCast(src0l))); +} + +void Translator::V_CVT_FLR_I32_F32(const GcnInst& inst) { + const IR::F32 src0{GetSrc(inst.src[0])}; + SetDst(inst.dst[0], ir.ConvertFToI(32, true, ir.FPFloor(src0))); +} + +void Translator::V_CVT_OFF_F32_I4(const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + ASSERT(src0.IsImmediate()); + static constexpr std::array IntToFloat = { + 0.0f, 0.0625f, 0.1250f, 0.1875f, 0.2500f, 0.3125f, 0.3750f, 0.4375f, + -0.5000f, -0.4375f, -0.3750f, -0.3125f, -0.2500f, -0.1875f, -0.1250f, -0.0625f}; + SetDst(inst.dst[0], ir.Imm32(IntToFloat[src0.U32() & 0xF])); +} + +void Translator::V_CVT_F32_UBYTE(u32 index, const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + const IR::U32 byte = ir.BitFieldExtract(src0, ir.Imm32(8 * index), ir.Imm32(8)); + SetDst(inst.dst[0], ir.ConvertUToF(32, 32, byte)); +} + +void Translator::V_FRACT_F32(const GcnInst& inst) { + const IR::F32 src0{GetSrc(inst.src[0])}; + SetDst(inst.dst[0], ir.Fract(src0)); +} + +void Translator::V_TRUNC_F32(const GcnInst& inst) { + const IR::F32 src0{GetSrc(inst.src[0])}; + SetDst(inst.dst[0], ir.FPTrunc(src0)); } -void Translator::V_CVT_F32_U32(const GcnInst& inst) { - const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::VectorReg dst_reg{inst.dst[0].code}; - ir.SetVectorReg(dst_reg, ir.ConvertUToF(32, 32, src0)); +void Translator::V_CEIL_F32(const GcnInst& inst) { + const IR::F32 src0{GetSrc(inst.src[0])}; + SetDst(inst.dst[0], ir.FPCeil(src0)); } -void Translator::V_MAD_F32(const GcnInst& inst) { +void Translator::V_RNDNE_F32(const GcnInst& inst) { const IR::F32 src0{GetSrc(inst.src[0])}; - const IR::F32 src1{GetSrc(inst.src[1])}; - const IR::F32 src2{GetSrc(inst.src[2])}; - SetDst(inst.dst[0], ir.FPFma(src0, src1, src2)); + SetDst(inst.dst[0], ir.FPRoundEven(src0)); } -void Translator::V_FRACT_F32(const GcnInst& inst) { +void Translator::V_FLOOR_F32(const GcnInst& inst) { const IR::F32 src0{GetSrc(inst.src[0])}; - const IR::VectorReg dst_reg{inst.dst[0].code}; - ir.SetVectorReg(dst_reg, ir.Fract(src0)); + SetDst(inst.dst[0], ir.FPFloor(src0)); } -void Translator::V_ADD_F32(const GcnInst& inst) { +void Translator::V_EXP_F32(const GcnInst& inst) { const IR::F32 src0{GetSrc(inst.src[0])}; - const IR::F32 src1{GetSrc(inst.src[1])}; - SetDst(inst.dst[0], ir.FPAdd(src0, src1)); + SetDst(inst.dst[0], ir.FPExp2(src0)); } -void Translator::V_CVT_OFF_F32_I4(const GcnInst& inst) { - const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::VectorReg dst_reg{inst.dst[0].code}; - ir.SetVectorReg( - dst_reg, - ir.FPMul(ir.ConvertUToF(32, 32, ir.ISub(ir.BitwiseAnd(src0, ir.Imm32(0xF)), ir.Imm32(8))), - ir.Imm32(1.f / 16.f))); +void Translator::V_LOG_F32(const GcnInst& inst) { + const IR::F32 src0{GetSrc(inst.src[0])}; + SetDst(inst.dst[0], ir.FPLog2(src0)); } -void Translator::V_MED3_F32(const GcnInst& inst) { +void Translator::V_RCP_F32(const GcnInst& inst) { const IR::F32 src0{GetSrc(inst.src[0])}; - const IR::F32 src1{GetSrc(inst.src[1])}; - const IR::F32 src2{GetSrc(inst.src[2])}; - const IR::F32 mmx = ir.FPMin(ir.FPMax(src0, src1), src2); - SetDst(inst.dst[0], ir.FPMax(ir.FPMin(src0, src1), mmx)); + SetDst(inst.dst[0], ir.FPRecip(src0)); } -void Translator::V_MED3_I32(const GcnInst& inst) { - const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::U32 src1{GetSrc(inst.src[1])}; - const IR::U32 src2{GetSrc(inst.src[2])}; - const IR::U32 mmx = ir.SMin(ir.SMax(src0, src1), src2); - SetDst(inst.dst[0], ir.SMax(ir.SMin(src0, src1), mmx)); +void Translator::V_RCP_F64(const GcnInst& inst) { + const IR::F64 src0{GetSrc64(inst.src[0])}; + SetDst64(inst.dst[0], ir.FPRecip(src0)); } -void Translator::V_FLOOR_F32(const GcnInst& inst) { +void Translator::V_RSQ_F32(const GcnInst& inst) { const IR::F32 src0{GetSrc(inst.src[0])}; - const IR::VectorReg dst_reg{inst.dst[0].code}; - ir.SetVectorReg(dst_reg, ir.FPFloor(src0)); + SetDst(inst.dst[0], ir.FPRecipSqrt(src0)); } -void Translator::V_SUB_F32(const GcnInst& inst) { +void Translator::V_SQRT_F32(const GcnInst& inst) { const IR::F32 src0{GetSrc(inst.src[0])}; - const IR::F32 src1{GetSrc(inst.src[1])}; - SetDst(inst.dst[0], ir.FPSub(src0, src1)); + SetDst(inst.dst[0], ir.FPSqrt(src0)); } -void Translator::V_RCP_F32(const GcnInst& inst) { +void Translator::V_SIN_F32(const GcnInst& inst) { const IR::F32 src0{GetSrc(inst.src[0])}; - SetDst(inst.dst[0], ir.FPRecip(src0)); + SetDst(inst.dst[0], ir.FPSin(src0)); } -void Translator::V_FMA_F32(const GcnInst& inst) { +void Translator::V_COS_F32(const GcnInst& inst) { const IR::F32 src0{GetSrc(inst.src[0])}; - const IR::F32 src1{GetSrc(inst.src[1])}; - const IR::F32 src2{GetSrc(inst.src[2])}; - SetDst(inst.dst[0], ir.FPFma(src0, src1, src2)); + SetDst(inst.dst[0], ir.FPCos(src0)); } +void Translator::V_NOT_B32(const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + SetDst(inst.dst[0], ir.BitwiseNot(src0)); +} + +void Translator::V_BFREV_B32(const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + SetDst(inst.dst[0], ir.BitReverse(src0)); +} + +void Translator::V_FFBH_U32(const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + // Gcn wants the MSB position counting from the left, but SPIR-V counts from the rightmost (LSB) + // position + const IR::U32 msb_pos = ir.FindUMsb(src0); + const IR::U32 pos_from_left = ir.ISub(ir.Imm32(31), msb_pos); + // Select 0xFFFFFFFF if src0 was 0 + const IR::U1 cond = ir.INotEqual(src0, ir.Imm32(0)); + SetDst(inst.dst[0], IR::U32{ir.Select(cond, pos_from_left, ir.Imm32(~0U))}); +} + +void Translator::V_FFBL_B32(const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + SetDst(inst.dst[0], ir.FindILsb(src0)); +} + +void Translator::V_MOVRELD_B32(const GcnInst& inst) { + const IR::U32 src_val{GetSrc(inst.src[0])}; + u32 dst_vgprno = inst.dst[0].code - static_cast(IR::VectorReg::V0); + IR::U32 m0 = ir.GetM0(); + + VMovRelDHelper(dst_vgprno, src_val, m0); +} + +void Translator::V_MOVRELS_B32(const GcnInst& inst) { + u32 src_vgprno = inst.src[0].code - static_cast(IR::VectorReg::V0); + const IR::U32 m0 = ir.GetM0(); + + const IR::U32 src_val = VMovRelSHelper(src_vgprno, m0); + SetDst(inst.dst[0], src_val); +} + +void Translator::V_MOVRELSD_B32(const GcnInst& inst) { + u32 src_vgprno = inst.src[0].code - static_cast(IR::VectorReg::V0); + u32 dst_vgprno = inst.dst[0].code - static_cast(IR::VectorReg::V0); + IR::U32 m0 = ir.GetM0(); + + const IR::U32 src_val = VMovRelSHelper(src_vgprno, m0); + VMovRelDHelper(dst_vgprno, src_val, m0); +} + +// VOPC + void Translator::V_CMP_F32(ConditionOp op, bool set_exec, const GcnInst& inst) { const IR::F32 src0{GetSrc(inst.src[0])}; const IR::F32 src1{GetSrc(inst.src[1])}; @@ -564,122 +820,6 @@ void Translator::V_CMP_F32(ConditionOp op, bool set_exec, const GcnInst& inst) { } } -void Translator::V_MAX_F32(const GcnInst& inst, bool is_legacy) { - const IR::F32 src0{GetSrc(inst.src[0])}; - const IR::F32 src1{GetSrc(inst.src[1])}; - SetDst(inst.dst[0], ir.FPMax(src0, src1, is_legacy)); -} - -void Translator::V_MAX_U32(bool is_signed, const GcnInst& inst) { - const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::U32 src1{GetSrc(inst.src[1])}; - SetDst(inst.dst[0], ir.IMax(src0, src1, is_signed)); -} - -void Translator::V_RSQ_F32(const GcnInst& inst) { - const IR::F32 src0{GetSrc(inst.src[0])}; - SetDst(inst.dst[0], ir.FPRecipSqrt(src0)); -} - -void Translator::V_SIN_F32(const GcnInst& inst) { - const IR::F32 src0{GetSrc(inst.src[0])}; - SetDst(inst.dst[0], ir.FPSin(src0)); -} - -void Translator::V_LOG_F32(const GcnInst& inst) { - const IR::F32 src0{GetSrc(inst.src[0])}; - SetDst(inst.dst[0], ir.FPLog2(src0)); -} - -void Translator::V_EXP_F32(const GcnInst& inst) { - const IR::F32 src0{GetSrc(inst.src[0])}; - SetDst(inst.dst[0], ir.FPExp2(src0)); -} - -void Translator::V_SQRT_F32(const GcnInst& inst) { - const IR::F32 src0{GetSrc(inst.src[0])}; - SetDst(inst.dst[0], ir.FPSqrt(src0)); -} - -void Translator::V_MIN_F32(const GcnInst& inst, bool is_legacy) { - const IR::F32 src0{GetSrc(inst.src[0])}; - const IR::F32 src1{GetSrc(inst.src[1])}; - SetDst(inst.dst[0], ir.FPMin(src0, src1, is_legacy)); -} - -void Translator::V_MIN3_F32(const GcnInst& inst) { - const IR::F32 src0{GetSrc(inst.src[0])}; - const IR::F32 src1{GetSrc(inst.src[1])}; - const IR::F32 src2{GetSrc(inst.src[2])}; - SetDst(inst.dst[0], ir.FPMin(src0, ir.FPMin(src1, src2))); -} - -void Translator::V_MIN3_I32(const GcnInst& inst) { - const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::U32 src1{GetSrc(inst.src[1])}; - const IR::U32 src2{GetSrc(inst.src[2])}; - SetDst(inst.dst[0], ir.SMin(src0, ir.SMin(src1, src2))); -} - -void Translator::V_MADMK_F32(const GcnInst& inst) { - const IR::F32 src0{GetSrc(inst.src[0])}; - const IR::F32 src1{GetSrc(inst.src[1])}; - const IR::F32 k{GetSrc(inst.src[2])}; - SetDst(inst.dst[0], ir.FPFma(src0, k, src1)); -} - -void Translator::V_CUBEMA_F32(const GcnInst& inst) { - SetDst(inst.dst[0], ir.Imm32(1.f)); -} - -void Translator::V_CUBESC_F32(const GcnInst& inst) { - SetDst(inst.dst[0], GetSrc(inst.src[0])); -} - -void Translator::V_CUBETC_F32(const GcnInst& inst) { - SetDst(inst.dst[0], GetSrc(inst.src[1])); -} - -void Translator::V_CUBEID_F32(const GcnInst& inst) { - SetDst(inst.dst[0], GetSrc(inst.src[2])); -} - -void Translator::V_CVT_U32_F32(const GcnInst& inst) { - const IR::F32 src0{GetSrc(inst.src[0])}; - SetDst(inst.dst[0], ir.ConvertFToU(32, src0)); -} - -void Translator::V_SUBREV_F32(const GcnInst& inst) { - const IR::F32 src0{GetSrc(inst.src[0])}; - const IR::F32 src1{GetSrc(inst.src[1])}; - SetDst(inst.dst[0], ir.FPSub(src1, src0)); -} - -void Translator::V_SUBREV_I32(const GcnInst& inst) { - const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::U32 src1{GetSrc(inst.src[1])}; - SetDst(inst.dst[0], ir.ISub(src1, src0)); - // TODO: Carry-out -} - -void Translator::V_MAD_U64_U32(const GcnInst& inst) { - const auto src0 = GetSrc(inst.src[0]); - const auto src1 = GetSrc(inst.src[1]); - const auto src2 = GetSrc64(inst.src[2]); - - // const IR::U64 mul_result = ir.UConvert(64, ir.IMul(src0, src1)); - const IR::U64 mul_result = - ir.PackUint2x32(ir.CompositeConstruct(ir.IMul(src0, src1), ir.Imm32(0U))); - const IR::U64 sum_result = ir.IAdd(mul_result, src2); - - SetDst64(inst.dst[0], sum_result); - - const IR::U1 less_src0 = ir.ILessThan(sum_result, mul_result, false); - const IR::U1 less_src1 = ir.ILessThan(sum_result, src2, false); - const IR::U1 did_overflow = ir.LogicalOr(less_src0, less_src1); - ir.SetVcc(did_overflow); -} - void Translator::V_CMP_U32(ConditionOp op, bool is_signed, bool set_exec, const GcnInst& inst) { const IR::U32 src0{GetSrc(inst.src[0])}; const IR::U32 src1{GetSrc(inst.src[1])}; @@ -718,41 +858,68 @@ void Translator::V_CMP_U32(ConditionOp op, bool is_signed, bool set_exec, const } } -void Translator::V_LSHRREV_B32(const GcnInst& inst) { - const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::U32 src1{GetSrc(inst.src[1])}; - SetDst(inst.dst[0], ir.ShiftRightLogical(src1, ir.BitwiseAnd(src0, ir.Imm32(0x1F)))); -} - -void Translator::V_MUL_HI_U32(bool is_signed, const GcnInst& inst) { - const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::U32 src1{GetSrc(inst.src[1])}; - const IR::U32 hi{ir.CompositeExtract(ir.IMulExt(src0, src1, is_signed), 1)}; - SetDst(inst.dst[0], hi); +void Translator::V_CMP_NE_U64(const GcnInst& inst) { + const auto get_src = [&](const InstOperand& operand) { + switch (operand.field) { + case OperandField::VccLo: + return ir.GetVcc(); + case OperandField::ExecLo: + return ir.GetExec(); + case OperandField::ScalarGPR: + return ir.GetThreadBitScalarReg(IR::ScalarReg(operand.code)); + case OperandField::ConstZero: + return ir.Imm1(false); + default: + UNREACHABLE(); + } + }; + const IR::U1 src0{get_src(inst.src[0])}; + ASSERT(inst.src[1].field == OperandField::ConstZero); // src0 != 0 + switch (inst.dst[1].field) { + case OperandField::VccLo: + ir.SetVcc(src0); + break; + case OperandField::ScalarGPR: + ir.SetThreadBitScalarReg(IR::ScalarReg(inst.dst[1].code), src0); + break; + default: + UNREACHABLE(); + } } -void Translator::V_SAD_U32(const GcnInst& inst) { - const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::U32 src1{GetSrc(inst.src[1])}; - const IR::U32 src2{GetSrc(inst.src[2])}; - IR::U32 result; - if (src0.IsImmediate() && src0.U32() == 0U) { - result = src1; - } else if (src1.IsImmediate() && src1.U32() == 0U) { - result = src0; +void Translator::V_CMP_CLASS_F32(const GcnInst& inst) { + const IR::F32 src0{GetSrc(inst.src[0])}; + const IR::U32 src1{GetSrc(inst.src[1])}; + IR::U1 value; + if (src1.IsImmediate()) { + const auto class_mask = static_cast(src1.U32()); + if ((class_mask & IR::FloatClassFunc::NaN) == IR::FloatClassFunc::NaN) { + value = ir.FPIsNan(src0); + } else if ((class_mask & IR::FloatClassFunc::Infinity) == IR::FloatClassFunc::Infinity) { + value = ir.FPIsInf(src0); + } else { + UNREACHABLE(); + } } else { - const IR::U32 max{ir.IMax(src0, src1, false)}; - const IR::U32 min{ir.IMin(src0, src1, false)}; - result = ir.ISub(max, min); + // We don't know the type yet, delay its resolution. + value = ir.FPCmpClass32(src0, src1); + } + + switch (inst.dst[1].field) { + case OperandField::VccLo: + return ir.SetVcc(value); + default: + UNREACHABLE(); } - SetDst(inst.dst[0], ir.IAdd(result, src2)); } -void Translator::V_BFE_U32(bool is_signed, const GcnInst& inst) { - const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::U32 src1{ir.BitwiseAnd(GetSrc(inst.src[1]), ir.Imm32(0x1F))}; - const IR::U32 src2{ir.BitwiseAnd(GetSrc(inst.src[2]), ir.Imm32(0x1F))}; - SetDst(inst.dst[0], ir.BitFieldExtract(src0, src1, src2, is_signed)); +// VOP3a + +void Translator::V_MAD_F32(const GcnInst& inst) { + const IR::F32 src0{GetSrc(inst.src[0])}; + const IR::F32 src1{GetSrc(inst.src[1])}; + const IR::F32 src2{GetSrc(inst.src[2])}; + SetDst(inst.dst[0], ir.FPFma(src0, src1, src2)); } void Translator::V_MAD_I32_I24(const GcnInst& inst, bool is_signed) { @@ -764,54 +931,67 @@ void Translator::V_MAD_I32_I24(const GcnInst& inst, bool is_signed) { SetDst(inst.dst[0], ir.IAdd(ir.IMul(src0, src1), src2)); } -void Translator::V_MUL_I32_I24(const GcnInst& inst) { - const IR::U32 src0{ir.BitFieldExtract(GetSrc(inst.src[0]), ir.Imm32(0), ir.Imm32(24), true)}; - const IR::U32 src1{ir.BitFieldExtract(GetSrc(inst.src[1]), ir.Imm32(0), ir.Imm32(24), true)}; - SetDst(inst.dst[0], ir.IMul(src0, src1)); +void Translator::V_MAD_U32_U24(const GcnInst& inst) { + V_MAD_I32_I24(inst, false); } -void Translator::V_SUB_I32(const GcnInst& inst) { - const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::U32 src1{GetSrc(inst.src[1])}; - SetDst(inst.dst[0], ir.ISub(src0, src1)); +void Translator::V_CUBEID_F32(const GcnInst& inst) { + SetDst(inst.dst[0], GetSrc(inst.src[2])); } -void Translator::V_LSHR_B32(const GcnInst& inst) { - const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::U32 src1{GetSrc(inst.src[1])}; - SetDst(inst.dst[0], ir.ShiftRightLogical(src0, ir.BitwiseAnd(src1, ir.Imm32(0x1F)))); +void Translator::V_CUBESC_F32(const GcnInst& inst) { + SetDst(inst.dst[0], GetSrc(inst.src[0])); } -void Translator::V_ASHRREV_I32(const GcnInst& inst) { +void Translator::V_CUBETC_F32(const GcnInst& inst) { + SetDst(inst.dst[0], GetSrc(inst.src[1])); +} + +void Translator::V_CUBEMA_F32(const GcnInst& inst) { + SetDst(inst.dst[0], ir.Imm32(1.f)); +} + +void Translator::V_BFE_U32(bool is_signed, const GcnInst& inst) { const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::U32 src1{GetSrc(inst.src[1])}; - SetDst(inst.dst[0], ir.ShiftRightArithmetic(src1, ir.BitwiseAnd(src0, ir.Imm32(0x1F)))); + const IR::U32 src1{ir.BitwiseAnd(GetSrc(inst.src[1]), ir.Imm32(0x1F))}; + const IR::U32 src2{ir.BitwiseAnd(GetSrc(inst.src[2]), ir.Imm32(0x1F))}; + SetDst(inst.dst[0], ir.BitFieldExtract(src0, src1, src2, is_signed)); } -void Translator::V_ASHR_I32(const GcnInst& inst) { +void Translator::V_BFI_B32(const GcnInst& inst) { const IR::U32 src0{GetSrc(inst.src[0])}; const IR::U32 src1{GetSrc(inst.src[1])}; - SetDst(inst.dst[0], ir.ShiftRightArithmetic(src0, ir.BitwiseAnd(src1, ir.Imm32(0x1F)))); + const IR::U32 src2{GetSrc(inst.src[2])}; + SetDst(inst.dst[0], + ir.BitwiseOr(ir.BitwiseAnd(src0, src1), ir.BitwiseAnd(ir.BitwiseNot(src0), src2))); } -void Translator::V_MAD_U32_U24(const GcnInst& inst) { - V_MAD_I32_I24(inst, false); +void Translator::V_FMA_F32(const GcnInst& inst) { + const IR::F32 src0{GetSrc(inst.src[0])}; + const IR::F32 src1{GetSrc(inst.src[1])}; + const IR::F32 src2{GetSrc(inst.src[2])}; + SetDst(inst.dst[0], ir.FPFma(src0, src1, src2)); } -void Translator::V_RNDNE_F32(const GcnInst& inst) { +void Translator::V_FMA_F64(const GcnInst& inst) { + const IR::F64 src0{GetSrc64(inst.src[0])}; + const IR::F64 src1{GetSrc64(inst.src[1])}; + const IR::F64 src2{GetSrc64(inst.src[2])}; + SetDst64(inst.dst[0], ir.FPFma(src0, src1, src2)); +} + +void Translator::V_MIN3_F32(const GcnInst& inst) { const IR::F32 src0{GetSrc(inst.src[0])}; - SetDst(inst.dst[0], ir.FPRoundEven(src0)); + const IR::F32 src1{GetSrc(inst.src[1])}; + const IR::F32 src2{GetSrc(inst.src[2])}; + SetDst(inst.dst[0], ir.FPMin(src0, ir.FPMin(src1, src2))); } -void Translator::V_BCNT_U32_B32(const GcnInst& inst) { +void Translator::V_MIN3_I32(const GcnInst& inst) { const IR::U32 src0{GetSrc(inst.src[0])}; const IR::U32 src1{GetSrc(inst.src[1])}; - SetDst(inst.dst[0], ir.IAdd(ir.BitCount(src0), src1)); -} - -void Translator::V_COS_F32(const GcnInst& inst) { - const IR::F32 src0{GetSrc(inst.src[0])}; - SetDst(inst.dst[0], ir.FPCos(src0)); + const IR::U32 src2{GetSrc(inst.src[2])}; + SetDst(inst.dst[0], ir.SMin(src0, ir.SMin(src1, src2))); } void Translator::V_MAX3_F32(const GcnInst& inst) { @@ -821,150 +1001,140 @@ void Translator::V_MAX3_F32(const GcnInst& inst) { SetDst(inst.dst[0], ir.FPMax(src0, ir.FPMax(src1, src2))); } -void Translator::V_MAX3_U32(const GcnInst& inst) { +void Translator::V_MAX3_U32(bool is_signed, const GcnInst& inst) { const IR::U32 src0{GetSrc(inst.src[0])}; const IR::U32 src1{GetSrc(inst.src[1])}; const IR::U32 src2{GetSrc(inst.src[2])}; - SetDst(inst.dst[0], ir.UMax(src0, ir.UMax(src1, src2))); + SetDst(inst.dst[0], ir.IMax(src0, ir.IMax(src1, src2, is_signed), is_signed)); } -void Translator::V_CVT_I32_F32(const GcnInst& inst) { +void Translator::V_MED3_F32(const GcnInst& inst) { const IR::F32 src0{GetSrc(inst.src[0])}; - SetDst(inst.dst[0], ir.ConvertFToS(32, src0)); + const IR::F32 src1{GetSrc(inst.src[1])}; + const IR::F32 src2{GetSrc(inst.src[2])}; + const IR::F32 mmx = ir.FPMin(ir.FPMax(src0, src1), src2); + SetDst(inst.dst[0], ir.FPMax(ir.FPMin(src0, src1), mmx)); } -void Translator::V_MIN_I32(const GcnInst& inst) { +void Translator::V_MED3_I32(const GcnInst& inst) { const IR::U32 src0{GetSrc(inst.src[0])}; const IR::U32 src1{GetSrc(inst.src[1])}; - SetDst(inst.dst[0], ir.SMin(src0, src1)); + const IR::U32 src2{GetSrc(inst.src[2])}; + const IR::U32 mmx = ir.SMin(ir.SMax(src0, src1), src2); + SetDst(inst.dst[0], ir.SMax(ir.SMin(src0, src1), mmx)); } -void Translator::V_MUL_LO_U32(const GcnInst& inst) { +void Translator::V_SAD(const GcnInst& inst) { + const IR::U32 abs_diff = ir.IAbs(ir.ISub(GetSrc(inst.src[0]), GetSrc(inst.src[1]))); + SetDst(inst.dst[0], ir.IAdd(abs_diff, GetSrc(inst.src[2]))); +} + +void Translator::V_SAD_U32(const GcnInst& inst) { const IR::U32 src0{GetSrc(inst.src[0])}; const IR::U32 src1{GetSrc(inst.src[1])}; - SetDst(inst.dst[0], ir.IMul(src0, src1)); + const IR::U32 src2{GetSrc(inst.src[2])}; + IR::U32 result; + if (src0.IsImmediate() && src0.U32() == 0U) { + result = src1; + } else if (src1.IsImmediate() && src1.U32() == 0U) { + result = src0; + } else { + const IR::U32 max{ir.IMax(src0, src1, false)}; + const IR::U32 min{ir.IMin(src0, src1, false)}; + result = ir.ISub(max, min); + } + SetDst(inst.dst[0], ir.IAdd(result, src2)); } -void Translator::V_TRUNC_F32(const GcnInst& inst) { +void Translator::V_CVT_PK_U8_F32(const GcnInst& inst) { const IR::F32 src0{GetSrc(inst.src[0])}; - SetDst(inst.dst[0], ir.FPTrunc(src0)); + const IR::U32 src1{GetSrc(inst.src[1])}; + const IR::U32 src2{GetSrc(inst.src[2])}; + + const IR::U32 value_uint = ir.ConvertFToU(32, src0); + const IR::U32 offset = ir.ShiftLeftLogical(src1, ir.Imm32(3)); + SetDst(inst.dst[0], ir.BitFieldInsert(src2, value_uint, offset, ir.Imm32(8))); } -void Translator::V_CEIL_F32(const GcnInst& inst) { - const IR::F32 src0{GetSrc(inst.src[0])}; - SetDst(inst.dst[0], ir.FPCeil(src0)); +void Translator::V_LSHL_B64(const GcnInst& inst) { + const IR::U64 src0{GetSrc64(inst.src[0])}; + const IR::U64 src1{GetSrc64(inst.src[1])}; + const IR::VectorReg dst_reg{inst.dst[0].code}; + if (src0.IsImmediate() && src0.U64() == -1) { + ir.SetVectorReg(dst_reg, ir.Imm32(0xFFFFFFFF)); + ir.SetVectorReg(dst_reg + 1, ir.Imm32(0xFFFFFFFF)); + return; + } + ASSERT_MSG(src0.IsImmediate() && src0.U64() == 0 && src1.IsImmediate() && src1.U64() == 0, + "V_LSHL_B64 with non-zero src0 or src1 is not supported"); + ir.SetVectorReg(dst_reg, ir.Imm32(0)); + ir.SetVectorReg(dst_reg + 1, ir.Imm32(0)); } -void Translator::V_MIN_U32(const GcnInst& inst) { - const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::U32 src1{GetSrc(inst.src[1])}; - SetDst(inst.dst[0], ir.IMin(src0, src1, false)); +void Translator::V_MUL_F64(const GcnInst& inst) { + const IR::F64 src0{GetSrc64(inst.src[0])}; + const IR::F64 src1{GetSrc64(inst.src[1])}; + SetDst64(inst.dst[0], ir.FPMul(src0, src1)); } -void Translator::V_CMP_NE_U64(const GcnInst& inst) { - const auto get_src = [&](const InstOperand& operand) { - switch (operand.field) { - case OperandField::VccLo: - return ir.GetVcc(); - case OperandField::ExecLo: - return ir.GetExec(); - case OperandField::ScalarGPR: - return ir.GetThreadBitScalarReg(IR::ScalarReg(operand.code)); - case OperandField::ConstZero: - return ir.Imm1(false); - default: - UNREACHABLE(); - } - }; - const IR::U1 src0{get_src(inst.src[0])}; - ASSERT(inst.src[1].field == OperandField::ConstZero); // src0 != 0 - switch (inst.dst[1].field) { - case OperandField::VccLo: - ir.SetVcc(src0); - break; - case OperandField::ScalarGPR: - ir.SetThreadBitScalarReg(IR::ScalarReg(inst.dst[1].code), src0); - break; - default: - UNREACHABLE(); - } +void Translator::V_MAX_F64(const GcnInst& inst) { + const IR::F64 src0{GetSrc64(inst.src[0])}; + const IR::F64 src1{GetSrc64(inst.src[1])}; + SetDst64(inst.dst[0], ir.FPMax(src0, src1)); } -void Translator::V_BFI_B32(const GcnInst& inst) { +void Translator::V_MUL_LO_U32(const GcnInst& inst) { const IR::U32 src0{GetSrc(inst.src[0])}; const IR::U32 src1{GetSrc(inst.src[1])}; - const IR::U32 src2{GetSrc(inst.src[2])}; - SetDst(inst.dst[0], - ir.BitwiseOr(ir.BitwiseAnd(src0, src1), ir.BitwiseAnd(ir.BitwiseNot(src0), src2))); + SetDst(inst.dst[0], ir.IMul(src0, src1)); } -void Translator::V_NOT_B32(const GcnInst& inst) { +void Translator::V_MUL_HI_U32(bool is_signed, const GcnInst& inst) { const IR::U32 src0{GetSrc(inst.src[0])}; - SetDst(inst.dst[0], ir.BitwiseNot(src0)); + const IR::U32 src1{GetSrc(inst.src[1])}; + const IR::U32 hi{ir.CompositeExtract(ir.IMulExt(src0, src1, is_signed), 1)}; + SetDst(inst.dst[0], hi); } -void Translator::V_CVT_F32_UBYTE(u32 index, const GcnInst& inst) { - const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::U32 byte = ir.BitFieldExtract(src0, ir.Imm32(8 * index), ir.Imm32(8)); - SetDst(inst.dst[0], ir.ConvertUToF(32, 32, byte)); -} +void Translator::V_MAD_U64_U32(const GcnInst& inst) { + const auto src0 = GetSrc(inst.src[0]); + const auto src1 = GetSrc(inst.src[1]); + const auto src2 = GetSrc64(inst.src[2]); -void Translator::V_BFREV_B32(const GcnInst& inst) { - const IR::U32 src0{GetSrc(inst.src[0])}; - SetDst(inst.dst[0], ir.BitReverse(src0)); -} + // const IR::U64 mul_result = ir.UConvert(64, ir.IMul(src0, src1)); + const IR::U64 mul_result = + ir.PackUint2x32(ir.CompositeConstruct(ir.IMul(src0, src1), ir.Imm32(0U))); + const IR::U64 sum_result = ir.IAdd(mul_result, src2); -void Translator::V_LDEXP_F32(const GcnInst& inst) { - const IR::F32 src0{GetSrc(inst.src[0])}; - const IR::U32 src1{GetSrc(inst.src[1])}; - SetDst(inst.dst[0], ir.FPLdexp(src0, src1)); -} + SetDst64(inst.dst[0], sum_result); -void Translator::V_CVT_FLR_I32_F32(const GcnInst& inst) { - const IR::F32 src0{GetSrc(inst.src[0])}; - SetDst(inst.dst[0], ir.ConvertFToI(32, true, ir.FPFloor(src0))); + const IR::U1 less_src0 = ir.ILessThan(sum_result, mul_result, false); + const IR::U1 less_src1 = ir.ILessThan(sum_result, src2, false); + const IR::U1 did_overflow = ir.LogicalOr(less_src0, less_src1); + ir.SetVcc(did_overflow); } -void Translator::V_CMP_CLASS_F32(const GcnInst& inst) { - const IR::F32 src0{GetSrc(inst.src[0])}; - const IR::U32 src1{GetSrc(inst.src[1])}; - IR::U1 value; - if (src1.IsImmediate()) { - const auto class_mask = static_cast(src1.U32()); - if ((class_mask & IR::FloatClassFunc::NaN) == IR::FloatClassFunc::NaN) { - value = ir.FPIsNan(src0); - } else if ((class_mask & IR::FloatClassFunc::Infinity) == IR::FloatClassFunc::Infinity) { - value = ir.FPIsInf(src0); - } else { - UNREACHABLE(); - } - } else { - // We don't know the type yet, delay its resolution. - value = ir.FPCmpClass32(src0, src1); - } +// TODO: add range analysis pass to hopefully put an upper bound on m0, and only select one of +// [src_vgprno, src_vgprno + max_m0]. Same for dst regs we may write back to - switch (inst.dst[1].field) { - case OperandField::VccLo: - return ir.SetVcc(value); - default: - UNREACHABLE(); +IR::U32 Translator::VMovRelSHelper(u32 src_vgprno, const IR::U32 m0) { + // Read from VGPR0 by default when src_vgprno + m0 > num_allocated_vgprs + IR::U32 src_val = ir.GetVectorReg(IR::VectorReg::V0); + for (u32 i = src_vgprno; i < runtime_info.num_allocated_vgprs; i++) { + const IR::U1 cond = ir.IEqual(m0, ir.Imm32(i - src_vgprno)); + src_val = + IR::U32{ir.Select(cond, ir.GetVectorReg(IR::VectorReg::V0 + i), src_val)}; } + return src_val; } -void Translator::V_FFBL_B32(const GcnInst& inst) { - const IR::U32 src0{GetSrc(inst.src[0])}; - SetDst(inst.dst[0], ir.FindILsb(src0)); -} - -void Translator::V_MBCNT_U32_B32(bool is_low, const GcnInst& inst) { - const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::U32 src1{GetSrc(inst.src[1])}; - if (!is_low) { - ASSERT(src0.IsImmediate() && src0.U32() == ~0U && src1.IsImmediate() && src1.U32() == 0U); - return; +void Translator::VMovRelDHelper(u32 dst_vgprno, const IR::U32 src_val, const IR::U32 m0) { + for (u32 i = dst_vgprno; i < runtime_info.num_allocated_vgprs; i++) { + const IR::U1 cond = ir.IEqual(m0, ir.Imm32(i - dst_vgprno)); + const IR::U32 dst_val = + IR::U32{ir.Select(cond, src_val, ir.GetVectorReg(IR::VectorReg::V0 + i))}; + ir.SetVectorReg(IR::VectorReg::V0 + i, dst_val); } - ASSERT(src0.IsImmediate() && src0.U32() == ~0U); - SetDst(inst.dst[0], ir.LaneId()); } } // namespace Shader::Gcn diff --git a/src/shader_recompiler/frontend/translate/vector_interpolation.cpp b/src/shader_recompiler/frontend/translate/vector_interpolation.cpp index 4ff846cf8c4..431cb2f046c 100644 --- a/src/shader_recompiler/frontend/translate/vector_interpolation.cpp +++ b/src/shader_recompiler/frontend/translate/vector_interpolation.cpp @@ -5,22 +5,9 @@ namespace Shader::Gcn { -void Translator::V_INTERP_P2_F32(const GcnInst& inst) { - const IR::VectorReg dst_reg{inst.dst[0].code}; - auto& attr = info.ps_inputs.at(inst.control.vintrp.attr); - const IR::Attribute attrib{IR::Attribute::Param0 + attr.param_index}; - ir.SetVectorReg(dst_reg, ir.GetAttribute(attrib, inst.control.vintrp.chan)); -} - -void Translator::V_INTERP_MOV_F32(const GcnInst& inst) { - const IR::VectorReg dst_reg{inst.dst[0].code}; - auto& attr = info.ps_inputs.at(inst.control.vintrp.attr); - const IR::Attribute attrib{IR::Attribute::Param0 + attr.param_index}; - ir.SetVectorReg(dst_reg, ir.GetAttribute(attrib, inst.control.vintrp.chan)); -} - void Translator::EmitVectorInterpolation(const GcnInst& inst) { switch (inst.opcode) { + // VINTRP case Opcode::V_INTERP_P1_F32: return; case Opcode::V_INTERP_P2_F32: @@ -32,4 +19,18 @@ void Translator::EmitVectorInterpolation(const GcnInst& inst) { } } +// VINTRP + +void Translator::V_INTERP_P2_F32(const GcnInst& inst) { + auto& attr = runtime_info.fs_info.inputs.at(inst.control.vintrp.attr); + const IR::Attribute attrib{IR::Attribute::Param0 + attr.param_index}; + SetDst(inst.dst[0], ir.GetAttribute(attrib, inst.control.vintrp.chan)); +} + +void Translator::V_INTERP_MOV_F32(const GcnInst& inst) { + auto& attr = runtime_info.fs_info.inputs.at(inst.control.vintrp.attr); + const IR::Attribute attrib{IR::Attribute::Param0 + attr.param_index}; + SetDst(inst.dst[0], ir.GetAttribute(attrib, inst.control.vintrp.chan)); +} + } // namespace Shader::Gcn diff --git a/src/shader_recompiler/frontend/translate/vector_memory.cpp b/src/shader_recompiler/frontend/translate/vector_memory.cpp index 73530dadeb9..7ecc2e762fe 100644 --- a/src/shader_recompiler/frontend/translate/vector_memory.cpp +++ b/src/shader_recompiler/frontend/translate/vector_memory.cpp @@ -7,52 +7,7 @@ namespace Shader::Gcn { void Translator::EmitVectorMemory(const GcnInst& inst) { switch (inst.opcode) { - case Opcode::IMAGE_SAMPLE_LZ_O: - case Opcode::IMAGE_SAMPLE_O: - case Opcode::IMAGE_SAMPLE_C: - case Opcode::IMAGE_SAMPLE_C_LZ: - case Opcode::IMAGE_SAMPLE_LZ: - case Opcode::IMAGE_SAMPLE: - case Opcode::IMAGE_SAMPLE_L: - case Opcode::IMAGE_SAMPLE_C_O: - case Opcode::IMAGE_SAMPLE_B: - case Opcode::IMAGE_SAMPLE_C_LZ_O: - case Opcode::IMAGE_SAMPLE_D: - return IMAGE_SAMPLE(inst); - case Opcode::IMAGE_GATHER4_C: - case Opcode::IMAGE_GATHER4_LZ: - case Opcode::IMAGE_GATHER4_LZ_O: - return IMAGE_GATHER(inst); - case Opcode::IMAGE_ATOMIC_ADD: - return IMAGE_ATOMIC(AtomicOp::Add, inst); - case Opcode::IMAGE_ATOMIC_AND: - return IMAGE_ATOMIC(AtomicOp::And, inst); - case Opcode::IMAGE_ATOMIC_OR: - return IMAGE_ATOMIC(AtomicOp::Or, inst); - case Opcode::IMAGE_ATOMIC_XOR: - return IMAGE_ATOMIC(AtomicOp::Xor, inst); - case Opcode::IMAGE_ATOMIC_UMAX: - return IMAGE_ATOMIC(AtomicOp::Umax, inst); - case Opcode::IMAGE_ATOMIC_SMAX: - return IMAGE_ATOMIC(AtomicOp::Smax, inst); - case Opcode::IMAGE_ATOMIC_UMIN: - return IMAGE_ATOMIC(AtomicOp::Umin, inst); - case Opcode::IMAGE_ATOMIC_SMIN: - return IMAGE_ATOMIC(AtomicOp::Smin, inst); - case Opcode::IMAGE_ATOMIC_INC: - return IMAGE_ATOMIC(AtomicOp::Inc, inst); - case Opcode::IMAGE_ATOMIC_DEC: - return IMAGE_ATOMIC(AtomicOp::Dec, inst); - case Opcode::IMAGE_GET_LOD: - return IMAGE_GET_LOD(inst); - case Opcode::IMAGE_STORE: - return IMAGE_STORE(inst); - case Opcode::IMAGE_LOAD_MIP: - return IMAGE_LOAD(true, inst); - case Opcode::IMAGE_LOAD: - return IMAGE_LOAD(false, inst); - case Opcode::IMAGE_GET_RESINFO: - return IMAGE_GET_RESINFO(inst); + // MUBUF / MTBUF // Buffer load operations case Opcode::TBUFFER_LOAD_FORMAT_X: @@ -98,6 +53,8 @@ void Translator::EmitVectorMemory(const GcnInst& inst) { return BUFFER_STORE(2, true, inst); case Opcode::TBUFFER_STORE_FORMAT_XYZ: return BUFFER_STORE(3, true, inst); + case Opcode::TBUFFER_STORE_FORMAT_XYZW: + return BUFFER_STORE(4, true, inst); case Opcode::BUFFER_STORE_DWORD: return BUFFER_STORE(1, false, inst); @@ -113,240 +70,91 @@ void Translator::EmitVectorMemory(const GcnInst& inst) { return BUFFER_ATOMIC(AtomicOp::Add, inst); case Opcode::BUFFER_ATOMIC_SWAP: return BUFFER_ATOMIC(AtomicOp::Swap, inst); - default: - LogMissingOpcode(inst); - } -} - -void Translator::IMAGE_GET_RESINFO(const GcnInst& inst) { - IR::VectorReg dst_reg{inst.dst[0].code}; - const IR::ScalarReg tsharp_reg{inst.src[2].code * 4}; - const auto flags = ImageResFlags(inst.control.mimg.dmask); - const bool has_mips = flags.test(ImageResComponent::MipCount); - const IR::U32 lod = ir.GetVectorReg(IR::VectorReg(inst.src[0].code)); - const IR::Value tsharp = ir.GetScalarReg(tsharp_reg); - const IR::Value size = ir.ImageQueryDimension(tsharp, lod, ir.Imm1(has_mips)); - - if (flags.test(ImageResComponent::Width)) { - ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(size, 0)}); - } - if (flags.test(ImageResComponent::Height)) { - ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(size, 1)}); - } - if (flags.test(ImageResComponent::Depth)) { - ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(size, 2)}); - } - if (has_mips) { - ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(size, 3)}); - } -} - -void Translator::IMAGE_SAMPLE(const GcnInst& inst) { - const auto& mimg = inst.control.mimg; - if (mimg.da) { - LOG_WARNING(Render_Vulkan, "Image instruction declares an array"); - } - - IR::VectorReg addr_reg{inst.src[0].code}; - IR::VectorReg dest_reg{inst.dst[0].code}; - const IR::ScalarReg tsharp_reg{inst.src[2].code * 4}; - const IR::ScalarReg sampler_reg{inst.src[3].code * 4}; - const auto flags = MimgModifierFlags(mimg.mod); - - // Load first dword of T# and S#. We will use them as the handle that will guide resource - // tracking pass where to read the sharps. This will later also get patched to the SPIRV texture - // binding index. - const IR::Value handle = - ir.CompositeConstruct(ir.GetScalarReg(tsharp_reg), ir.GetScalarReg(sampler_reg)); - - // Load first address components as denoted in 8.2.4 VGPR Usage Sea Islands Series Instruction - // Set Architecture - const IR::U32 offset = - flags.test(MimgModifier::Offset) ? ir.GetVectorReg(addr_reg++) : IR::U32{}; - const IR::F32 bias = - flags.test(MimgModifier::LodBias) ? ir.GetVectorReg(addr_reg++) : IR::F32{}; - const IR::F32 dref = - flags.test(MimgModifier::Pcf) ? ir.GetVectorReg(addr_reg++) : IR::F32{}; - const IR::Value derivatives = [&] -> IR::Value { - if (!flags.test(MimgModifier::Derivative)) { - return {}; - } - addr_reg = addr_reg + 4; - return ir.CompositeConstruct( - ir.GetVectorReg(addr_reg - 4), ir.GetVectorReg(addr_reg - 3), - ir.GetVectorReg(addr_reg - 2), ir.GetVectorReg(addr_reg - 1)); - }(); - - // Now we can load body components as noted in Table 8.9 Image Opcodes with Sampler - // Since these are at most 4 dwords, we load them into a single uvec4 and place them - // in coords field of the instruction. Then the resource tracking pass will patch the - // IR instruction to fill in lod_clamp field. - const IR::Value body = ir.CompositeConstruct( - ir.GetVectorReg(addr_reg), ir.GetVectorReg(addr_reg + 1), - ir.GetVectorReg(addr_reg + 2), ir.GetVectorReg(addr_reg + 3)); - - // Derivatives are tricky because their number depends on the texture type which is located in - // T#. We don't have access to T# though until resource tracking pass. For now assume if - // derivatives are present, that a 2D image is bound. - const bool has_derivatives = flags.test(MimgModifier::Derivative); - const bool explicit_lod = flags.any(MimgModifier::Level0, MimgModifier::Lod); - - IR::TextureInstInfo info{}; - info.is_depth.Assign(flags.test(MimgModifier::Pcf)); - info.has_bias.Assign(flags.test(MimgModifier::LodBias)); - info.has_lod_clamp.Assign(flags.test(MimgModifier::LodClamp)); - info.force_level0.Assign(flags.test(MimgModifier::Level0)); - info.has_offset.Assign(flags.test(MimgModifier::Offset)); - info.explicit_lod.Assign(explicit_lod); - info.has_derivatives.Assign(has_derivatives); - - // Issue IR instruction, leaving unknown fields blank to patch later. - const IR::Value texel = [&]() -> IR::Value { - if (has_derivatives) { - return ir.ImageGradient(handle, body, derivatives, offset, {}, info); - } - if (!flags.test(MimgModifier::Pcf)) { - if (explicit_lod) { - return ir.ImageSampleExplicitLod(handle, body, offset, info); - } else { - return ir.ImageSampleImplicitLod(handle, body, bias, offset, info); - } - } - if (explicit_lod) { - return ir.ImageSampleDrefExplicitLod(handle, body, dref, offset, info); - } - return ir.ImageSampleDrefImplicitLod(handle, body, dref, bias, offset, info); - }(); - - for (u32 i = 0; i < 4; i++) { - if (((mimg.dmask >> i) & 1) == 0) { - continue; - } - IR::F32 value; - if (flags.test(MimgModifier::Pcf)) { - value = i < 3 ? IR::F32{texel} : ir.Imm32(1.0f); - } else { - value = IR::F32{ir.CompositeExtract(texel, i)}; - } - ir.SetVectorReg(dest_reg++, value); - } -} - -void Translator::IMAGE_GATHER(const GcnInst& inst) { - const auto& mimg = inst.control.mimg; - if (mimg.da) { - LOG_WARNING(Render_Vulkan, "Image instruction declares an array"); - } - - IR::VectorReg addr_reg{inst.src[0].code}; - IR::VectorReg dest_reg{inst.dst[0].code}; - const IR::ScalarReg tsharp_reg{inst.src[2].code * 4}; - const IR::ScalarReg sampler_reg{inst.src[3].code * 4}; - const auto flags = MimgModifierFlags(mimg.mod); - - // Load first dword of T# and S#. We will use them as the handle that will guide resource - // tracking pass where to read the sharps. This will later also get patched to the SPIRV texture - // binding index. - const IR::Value handle = - ir.CompositeConstruct(ir.GetScalarReg(tsharp_reg), ir.GetScalarReg(sampler_reg)); - - // Load first address components as denoted in 8.2.4 VGPR Usage Sea Islands Series Instruction - // Set Architecture - const IR::Value offset = - flags.test(MimgModifier::Offset) ? ir.GetVectorReg(addr_reg++) : IR::Value{}; - const IR::F32 bias = - flags.test(MimgModifier::LodBias) ? ir.GetVectorReg(addr_reg++) : IR::F32{}; - const IR::F32 dref = - flags.test(MimgModifier::Pcf) ? ir.GetVectorReg(addr_reg++) : IR::F32{}; - - // Derivatives are tricky because their number depends on the texture type which is located in - // T#. We don't have access to T# though until resource tracking pass. For now assume no - // derivatives are present, otherwise we don't know where coordinates are placed in the address - // stream. - ASSERT_MSG(!flags.test(MimgModifier::Derivative), "Derivative image instruction"); - - // Now we can load body components as noted in Table 8.9 Image Opcodes with Sampler - // Since these are at most 4 dwords, we load them into a single uvec4 and place them - // in coords field of the instruction. Then the resource tracking pass will patch the - // IR instruction to fill in lod_clamp field. - const IR::Value body = ir.CompositeConstruct( - ir.GetVectorReg(addr_reg), ir.GetVectorReg(addr_reg + 1), - ir.GetVectorReg(addr_reg + 2), ir.GetVectorReg(addr_reg + 3)); - - const bool explicit_lod = flags.any(MimgModifier::Level0, MimgModifier::Lod); - - IR::TextureInstInfo info{}; - info.is_depth.Assign(flags.test(MimgModifier::Pcf)); - info.has_bias.Assign(flags.test(MimgModifier::LodBias)); - info.has_lod_clamp.Assign(flags.test(MimgModifier::LodClamp)); - info.force_level0.Assign(flags.test(MimgModifier::Level0)); - // info.explicit_lod.Assign(explicit_lod); - info.gather_comp.Assign(std::bit_width(mimg.dmask) - 1); - - // Issue IR instruction, leaving unknown fields blank to patch later. - const IR::Value texel = [&]() -> IR::Value { - const IR::F32 lod = flags.test(MimgModifier::Level0) ? ir.Imm32(0.f) : IR::F32{}; - if (!flags.test(MimgModifier::Pcf)) { - return ir.ImageGather(handle, body, offset, info); - } - ASSERT(mimg.dmask & 1); // should be always 1st (R) component - return ir.ImageGatherDref(handle, body, offset, dref, info); - }(); - - // For gather4 instructions dmask selects which component to read and must have - // only one bit set to 1 - ASSERT_MSG(std::popcount(mimg.dmask) == 1, "Unexpected bits in gather dmask"); - for (u32 i = 0; i < 4; i++) { - const IR::F32 value = IR::F32{ir.CompositeExtract(texel, i)}; - ir.SetVectorReg(dest_reg++, value); - } -} + case Opcode::BUFFER_ATOMIC_SMIN: + return BUFFER_ATOMIC(AtomicOp::Smin, inst); + case Opcode::BUFFER_ATOMIC_UMIN: + return BUFFER_ATOMIC(AtomicOp::Umin, inst); + case Opcode::BUFFER_ATOMIC_SMAX: + return BUFFER_ATOMIC(AtomicOp::Smax, inst); + case Opcode::BUFFER_ATOMIC_UMAX: + return BUFFER_ATOMIC(AtomicOp::Umax, inst); + case Opcode::BUFFER_ATOMIC_AND: + return BUFFER_ATOMIC(AtomicOp::And, inst); + case Opcode::BUFFER_ATOMIC_OR: + return BUFFER_ATOMIC(AtomicOp::Or, inst); + case Opcode::BUFFER_ATOMIC_XOR: + return BUFFER_ATOMIC(AtomicOp::Xor, inst); + case Opcode::BUFFER_ATOMIC_INC: + return BUFFER_ATOMIC(AtomicOp::Inc, inst); + case Opcode::BUFFER_ATOMIC_DEC: + return BUFFER_ATOMIC(AtomicOp::Dec, inst); + + // MIMG + // Image load operations + case Opcode::IMAGE_LOAD: + return IMAGE_LOAD(false, inst); + case Opcode::IMAGE_LOAD_MIP: + return IMAGE_LOAD(true, inst); -void Translator::IMAGE_LOAD(bool has_mip, const GcnInst& inst) { - const auto& mimg = inst.control.mimg; - IR::VectorReg addr_reg{inst.src[0].code}; - IR::VectorReg dest_reg{inst.dst[0].code}; - const IR::ScalarReg tsharp_reg{inst.src[2].code * 4}; + // Buffer store operations + case Opcode::IMAGE_STORE: + return IMAGE_STORE(inst); - const IR::Value handle = ir.GetScalarReg(tsharp_reg); - const IR::Value body = - ir.CompositeConstruct(ir.GetVectorReg(addr_reg), ir.GetVectorReg(addr_reg + 1), - ir.GetVectorReg(addr_reg + 2), ir.GetVectorReg(addr_reg + 3)); + // Image misc operations + case Opcode::IMAGE_GET_RESINFO: + return IMAGE_GET_RESINFO(inst); - IR::TextureInstInfo info{}; - info.explicit_lod.Assign(has_mip); - const IR::Value texel = ir.ImageFetch(handle, body, {}, {}, {}, info); + // Image atomic operations + case Opcode::IMAGE_ATOMIC_ADD: + return IMAGE_ATOMIC(AtomicOp::Add, inst); + case Opcode::IMAGE_ATOMIC_SMIN: + return IMAGE_ATOMIC(AtomicOp::Smin, inst); + case Opcode::IMAGE_ATOMIC_UMIN: + return IMAGE_ATOMIC(AtomicOp::Umin, inst); + case Opcode::IMAGE_ATOMIC_SMAX: + return IMAGE_ATOMIC(AtomicOp::Smax, inst); + case Opcode::IMAGE_ATOMIC_UMAX: + return IMAGE_ATOMIC(AtomicOp::Umax, inst); + case Opcode::IMAGE_ATOMIC_AND: + return IMAGE_ATOMIC(AtomicOp::And, inst); + case Opcode::IMAGE_ATOMIC_OR: + return IMAGE_ATOMIC(AtomicOp::Or, inst); + case Opcode::IMAGE_ATOMIC_XOR: + return IMAGE_ATOMIC(AtomicOp::Xor, inst); + case Opcode::IMAGE_ATOMIC_INC: + return IMAGE_ATOMIC(AtomicOp::Inc, inst); + case Opcode::IMAGE_ATOMIC_DEC: + return IMAGE_ATOMIC(AtomicOp::Dec, inst); - for (u32 i = 0; i < 4; i++) { - if (((mimg.dmask >> i) & 1) == 0) { - continue; - } - IR::F32 value = IR::F32{ir.CompositeExtract(texel, i)}; - ir.SetVectorReg(dest_reg++, value); - } -} + case Opcode::IMAGE_SAMPLE: + case Opcode::IMAGE_SAMPLE_D: + case Opcode::IMAGE_SAMPLE_L: + case Opcode::IMAGE_SAMPLE_B: + case Opcode::IMAGE_SAMPLE_LZ: + case Opcode::IMAGE_SAMPLE_C: + case Opcode::IMAGE_SAMPLE_C_LZ: + case Opcode::IMAGE_SAMPLE_O: + case Opcode::IMAGE_SAMPLE_L_O: + case Opcode::IMAGE_SAMPLE_LZ_O: + case Opcode::IMAGE_SAMPLE_C_O: + case Opcode::IMAGE_SAMPLE_C_LZ_O: + case Opcode::IMAGE_SAMPLE_CD: + return IMAGE_SAMPLE(inst); -void Translator::IMAGE_STORE(const GcnInst& inst) { - const auto& mimg = inst.control.mimg; - IR::VectorReg addr_reg{inst.src[0].code}; - IR::VectorReg data_reg{inst.dst[0].code}; - const IR::ScalarReg tsharp_reg{inst.src[2].code * 4}; + // Image gather operations + case Opcode::IMAGE_GATHER4_LZ: + case Opcode::IMAGE_GATHER4_C: + case Opcode::IMAGE_GATHER4_C_LZ: + case Opcode::IMAGE_GATHER4_LZ_O: + return IMAGE_GATHER(inst); - const IR::Value handle = ir.GetScalarReg(tsharp_reg); - const IR::Value body = - ir.CompositeConstruct(ir.GetVectorReg(addr_reg), ir.GetVectorReg(addr_reg + 1), - ir.GetVectorReg(addr_reg + 2), ir.GetVectorReg(addr_reg + 3)); + // Image misc operations + case Opcode::IMAGE_GET_LOD: + return IMAGE_GET_LOD(inst); - boost::container::static_vector comps; - for (u32 i = 0; i < 4; i++) { - if (((mimg.dmask >> i) & 1) == 0) { - comps.push_back(ir.Imm32(0.f)); - continue; - } - comps.push_back(ir.GetVectorReg(data_reg++)); + default: + LogMissingOpcode(inst); } - const IR::Value value = ir.CompositeConstruct(comps[0], comps[1], comps[2], comps[3]); - ir.ImageWrite(handle, body, value, {}); } void Translator::BUFFER_LOAD(u32 num_dwords, bool is_typed, const GcnInst& inst) { @@ -384,11 +192,11 @@ void Translator::BUFFER_LOAD(u32 num_dwords, bool is_typed, const GcnInst& inst) const IR::Value value = ir.LoadBuffer(num_dwords, handle, address, info); const IR::VectorReg dst_reg{inst.src[1].code}; if (num_dwords == 1) { - ir.SetVectorReg(dst_reg, IR::F32{value}); + ir.SetVectorReg(dst_reg, IR::U32{value}); return; } for (u32 i = 0; i < num_dwords; i++) { - ir.SetVectorReg(dst_reg + i, IR::F32{ir.CompositeExtract(value, i)}); + ir.SetVectorReg(dst_reg + i, IR::U32{ir.CompositeExtract(value, i)}); } } @@ -452,21 +260,18 @@ void Translator::BUFFER_STORE(u32 num_dwords, bool is_typed, const GcnInst& inst const IR::VectorReg src_reg{inst.src[1].code}; switch (num_dwords) { case 1: - value = ir.GetVectorReg(src_reg); + value = ir.GetVectorReg(src_reg); break; case 2: - value = ir.CompositeConstruct(ir.GetVectorReg(src_reg), - ir.GetVectorReg(src_reg + 1)); + value = ir.CompositeConstruct(ir.GetVectorReg(src_reg), ir.GetVectorReg(src_reg + 1)); break; case 3: - value = ir.CompositeConstruct(ir.GetVectorReg(src_reg), - ir.GetVectorReg(src_reg + 1), - ir.GetVectorReg(src_reg + 2)); + value = ir.CompositeConstruct(ir.GetVectorReg(src_reg), ir.GetVectorReg(src_reg + 1), + ir.GetVectorReg(src_reg + 2)); break; case 4: - value = ir.CompositeConstruct( - ir.GetVectorReg(src_reg), ir.GetVectorReg(src_reg + 1), - ir.GetVectorReg(src_reg + 2), ir.GetVectorReg(src_reg + 3)); + value = ir.CompositeConstruct(ir.GetVectorReg(src_reg), ir.GetVectorReg(src_reg + 1), + ir.GetVectorReg(src_reg + 2), ir.GetVectorReg(src_reg + 3)); break; } const IR::Value handle = @@ -514,6 +319,15 @@ void Translator::BUFFER_ATOMIC(AtomicOp op, const GcnInst& inst) { const IR::VectorReg vaddr{inst.src[0].code}; const IR::VectorReg vdata{inst.src[1].code}; const IR::ScalarReg srsrc{inst.src[2].code * 4}; + const IR::Value address = [&] -> IR::Value { + if (mubuf.idxen && mubuf.offen) { + return ir.CompositeConstruct(ir.GetVectorReg(vaddr), ir.GetVectorReg(vaddr + 1)); + } + if (mubuf.idxen || mubuf.offen) { + return ir.GetVectorReg(vaddr); + } + return {}; + }(); const IR::U32 soffset{GetSrc(inst.src[3])}; ASSERT_MSG(soffset.IsImmediate() && soffset.U32() == 0, "Non immediate offset not supported"); @@ -523,7 +337,6 @@ void Translator::BUFFER_ATOMIC(AtomicOp op, const GcnInst& inst) { info.offset_enable.Assign(mubuf.offen); IR::Value vdata_val = ir.GetVectorReg(vdata); - const IR::U32 address = ir.GetVectorReg(vaddr); const IR::Value handle = ir.CompositeConstruct(ir.GetScalarReg(srsrc), ir.GetScalarReg(srsrc + 1), ir.GetScalarReg(srsrc + 2), ir.GetScalarReg(srsrc + 3)); @@ -562,19 +375,77 @@ void Translator::BUFFER_ATOMIC(AtomicOp op, const GcnInst& inst) { } } -void Translator::IMAGE_GET_LOD(const GcnInst& inst) { +// Image Memory +// MIMG + +void Translator::IMAGE_LOAD(bool has_mip, const GcnInst& inst) { const auto& mimg = inst.control.mimg; - IR::VectorReg dst_reg{inst.dst[0].code}; IR::VectorReg addr_reg{inst.src[0].code}; + IR::VectorReg dest_reg{inst.dst[0].code}; const IR::ScalarReg tsharp_reg{inst.src[2].code * 4}; const IR::Value handle = ir.GetScalarReg(tsharp_reg); - const IR::Value body = ir.CompositeConstruct( - ir.GetVectorReg(addr_reg), ir.GetVectorReg(addr_reg + 1), - ir.GetVectorReg(addr_reg + 2), ir.GetVectorReg(addr_reg + 3)); - const IR::Value lod = ir.ImageQueryLod(handle, body, {}); - ir.SetVectorReg(dst_reg++, IR::F32{ir.CompositeExtract(lod, 0)}); - ir.SetVectorReg(dst_reg++, IR::F32{ir.CompositeExtract(lod, 1)}); + const IR::Value body = + ir.CompositeConstruct(ir.GetVectorReg(addr_reg), ir.GetVectorReg(addr_reg + 1), + ir.GetVectorReg(addr_reg + 2), ir.GetVectorReg(addr_reg + 3)); + + IR::TextureInstInfo info{}; + info.explicit_lod.Assign(has_mip); + const IR::Value texel = ir.ImageFetch(handle, body, {}, {}, {}, info); + + for (u32 i = 0; i < 4; i++) { + if (((mimg.dmask >> i) & 1) == 0) { + continue; + } + IR::F32 value = IR::F32{ir.CompositeExtract(texel, i)}; + ir.SetVectorReg(dest_reg++, value); + } +} + +void Translator::IMAGE_STORE(const GcnInst& inst) { + const auto& mimg = inst.control.mimg; + IR::VectorReg addr_reg{inst.src[0].code}; + IR::VectorReg data_reg{inst.dst[0].code}; + const IR::ScalarReg tsharp_reg{inst.src[2].code * 4}; + + const IR::Value handle = ir.GetScalarReg(tsharp_reg); + const IR::Value body = + ir.CompositeConstruct(ir.GetVectorReg(addr_reg), ir.GetVectorReg(addr_reg + 1), + ir.GetVectorReg(addr_reg + 2), ir.GetVectorReg(addr_reg + 3)); + + boost::container::static_vector comps; + for (u32 i = 0; i < 4; i++) { + if (((mimg.dmask >> i) & 1) == 0) { + comps.push_back(ir.Imm32(0.f)); + continue; + } + comps.push_back(ir.GetVectorReg(data_reg++)); + } + const IR::Value value = ir.CompositeConstruct(comps[0], comps[1], comps[2], comps[3]); + ir.ImageWrite(handle, body, value, {}); +} + +void Translator::IMAGE_GET_RESINFO(const GcnInst& inst) { + IR::VectorReg dst_reg{inst.dst[0].code}; + const IR::ScalarReg tsharp_reg{inst.src[2].code * 4}; + const auto flags = ImageResFlags(inst.control.mimg.dmask); + const bool has_mips = flags.test(ImageResComponent::MipCount); + const IR::U32 lod = ir.GetVectorReg(IR::VectorReg(inst.src[0].code)); + const IR::Value tsharp = ir.GetScalarReg(tsharp_reg); + const IR::Value size = ir.ImageQueryDimension(tsharp, lod, ir.Imm1(has_mips)); + + if (flags.test(ImageResComponent::Width)) { + ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(size, 0)}); + } + if (flags.test(ImageResComponent::Height)) { + ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(size, 1)}); + } + if (flags.test(ImageResComponent::Depth)) { + ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(size, 2)}); + } + if (has_mips) { + ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(size, 3)}); + } } void Translator::IMAGE_ATOMIC(AtomicOp op, const GcnInst& inst) { @@ -621,4 +492,179 @@ void Translator::IMAGE_ATOMIC(AtomicOp op, const GcnInst& inst) { } } +void Translator::IMAGE_SAMPLE(const GcnInst& inst) { + const auto& mimg = inst.control.mimg; + IR::VectorReg addr_reg{inst.src[0].code}; + IR::VectorReg dest_reg{inst.dst[0].code}; + const IR::ScalarReg tsharp_reg{inst.src[2].code * 4}; + const IR::ScalarReg sampler_reg{inst.src[3].code * 4}; + const auto flags = MimgModifierFlags(mimg.mod); + + // Load first dword of T# and S#. We will use them as the handle that will guide resource + // tracking pass where to read the sharps. This will later also get patched to the SPIRV texture + // binding index. + const IR::Value handle = + ir.CompositeConstruct(ir.GetScalarReg(tsharp_reg), ir.GetScalarReg(sampler_reg)); + + // Load first address components as denoted in 8.2.4 VGPR Usage Sea Islands Series Instruction + // Set Architecture + const IR::U32 offset = + flags.test(MimgModifier::Offset) ? ir.GetVectorReg(addr_reg++) : IR::U32{}; + const IR::F32 bias = + flags.test(MimgModifier::LodBias) ? ir.GetVectorReg(addr_reg++) : IR::F32{}; + const IR::F32 dref = + flags.test(MimgModifier::Pcf) ? ir.GetVectorReg(addr_reg++) : IR::F32{}; + const IR::Value derivatives = [&] -> IR::Value { + if (!flags.test(MimgModifier::Derivative)) { + return {}; + } + addr_reg = addr_reg + 4; + return ir.CompositeConstruct( + ir.GetVectorReg(addr_reg - 4), ir.GetVectorReg(addr_reg - 3), + ir.GetVectorReg(addr_reg - 2), ir.GetVectorReg(addr_reg - 1)); + }(); + + // Now we can load body components as noted in Table 8.9 Image Opcodes with Sampler + // Since these are at most 4 dwords, we load them into a single uvec4 and place them + // in coords field of the instruction. Then the resource tracking pass will patch the + // IR instruction to fill in lod_clamp field. + const IR::Value body = ir.CompositeConstruct( + ir.GetVectorReg(addr_reg), ir.GetVectorReg(addr_reg + 1), + ir.GetVectorReg(addr_reg + 2), ir.GetVectorReg(addr_reg + 3)); + + // Derivatives are tricky because their number depends on the texture type which is located in + // T#. We don't have access to T# though until resource tracking pass. For now assume if + // derivatives are present, that a 2D image is bound. + const bool has_derivatives = flags.test(MimgModifier::Derivative); + const bool explicit_lod = flags.any(MimgModifier::Level0, MimgModifier::Lod); + + IR::TextureInstInfo info{}; + info.is_depth.Assign(flags.test(MimgModifier::Pcf)); + info.has_bias.Assign(flags.test(MimgModifier::LodBias)); + info.has_lod_clamp.Assign(flags.test(MimgModifier::LodClamp)); + info.force_level0.Assign(flags.test(MimgModifier::Level0)); + info.has_offset.Assign(flags.test(MimgModifier::Offset)); + info.explicit_lod.Assign(explicit_lod); + info.has_derivatives.Assign(has_derivatives); + info.is_array.Assign(mimg.da); + + // Issue IR instruction, leaving unknown fields blank to patch later. + const IR::Value texel = [&]() -> IR::Value { + if (has_derivatives) { + return ir.ImageGradient(handle, body, derivatives, offset, {}, info); + } + if (!flags.test(MimgModifier::Pcf)) { + if (explicit_lod) { + return ir.ImageSampleExplicitLod(handle, body, offset, info); + } else { + return ir.ImageSampleImplicitLod(handle, body, bias, offset, info); + } + } + if (explicit_lod) { + return ir.ImageSampleDrefExplicitLod(handle, body, dref, offset, info); + } + return ir.ImageSampleDrefImplicitLod(handle, body, dref, bias, offset, info); + }(); + + for (u32 i = 0; i < 4; i++) { + if (((mimg.dmask >> i) & 1) == 0) { + continue; + } + IR::F32 value; + if (flags.test(MimgModifier::Pcf)) { + value = i < 3 ? IR::F32{texel} : ir.Imm32(1.0f); + } else { + value = IR::F32{ir.CompositeExtract(texel, i)}; + } + ir.SetVectorReg(dest_reg++, value); + } +} + +void Translator::IMAGE_GATHER(const GcnInst& inst) { + const auto& mimg = inst.control.mimg; + if (mimg.da) { + LOG_WARNING(Render_Vulkan, "Image instruction declares an array"); + } + + IR::VectorReg addr_reg{inst.src[0].code}; + IR::VectorReg dest_reg{inst.dst[0].code}; + const IR::ScalarReg tsharp_reg{inst.src[2].code * 4}; + const IR::ScalarReg sampler_reg{inst.src[3].code * 4}; + const auto flags = MimgModifierFlags(mimg.mod); + + // Load first dword of T# and S#. We will use them as the handle that will guide resource + // tracking pass where to read the sharps. This will later also get patched to the SPIRV texture + // binding index. + const IR::Value handle = + ir.CompositeConstruct(ir.GetScalarReg(tsharp_reg), ir.GetScalarReg(sampler_reg)); + + // Load first address components as denoted in 8.2.4 VGPR Usage Sea Islands Series Instruction + // Set Architecture + const IR::Value offset = + flags.test(MimgModifier::Offset) ? ir.GetVectorReg(addr_reg++) : IR::Value{}; + const IR::F32 bias = + flags.test(MimgModifier::LodBias) ? ir.GetVectorReg(addr_reg++) : IR::F32{}; + const IR::F32 dref = + flags.test(MimgModifier::Pcf) ? ir.GetVectorReg(addr_reg++) : IR::F32{}; + + // Derivatives are tricky because their number depends on the texture type which is located in + // T#. We don't have access to T# though until resource tracking pass. For now assume no + // derivatives are present, otherwise we don't know where coordinates are placed in the address + // stream. + ASSERT_MSG(!flags.test(MimgModifier::Derivative), "Derivative image instruction"); + + // Now we can load body components as noted in Table 8.9 Image Opcodes with Sampler + // Since these are at most 4 dwords, we load them into a single uvec4 and place them + // in coords field of the instruction. Then the resource tracking pass will patch the + // IR instruction to fill in lod_clamp field. + const IR::Value body = ir.CompositeConstruct( + ir.GetVectorReg(addr_reg), ir.GetVectorReg(addr_reg + 1), + ir.GetVectorReg(addr_reg + 2), ir.GetVectorReg(addr_reg + 3)); + + const bool explicit_lod = flags.any(MimgModifier::Level0, MimgModifier::Lod); + + IR::TextureInstInfo info{}; + info.is_depth.Assign(flags.test(MimgModifier::Pcf)); + info.has_bias.Assign(flags.test(MimgModifier::LodBias)); + info.has_lod_clamp.Assign(flags.test(MimgModifier::LodClamp)); + info.force_level0.Assign(flags.test(MimgModifier::Level0)); + info.has_offset.Assign(flags.test(MimgModifier::Offset)); + // info.explicit_lod.Assign(explicit_lod); + info.gather_comp.Assign(std::bit_width(mimg.dmask) - 1); + info.is_array.Assign(mimg.da); + + // Issue IR instruction, leaving unknown fields blank to patch later. + const IR::Value texel = [&]() -> IR::Value { + const IR::F32 lod = flags.test(MimgModifier::Level0) ? ir.Imm32(0.f) : IR::F32{}; + if (!flags.test(MimgModifier::Pcf)) { + return ir.ImageGather(handle, body, offset, info); + } + ASSERT(mimg.dmask & 1); // should be always 1st (R) component + return ir.ImageGatherDref(handle, body, offset, dref, info); + }(); + + // For gather4 instructions dmask selects which component to read and must have + // only one bit set to 1 + ASSERT_MSG(std::popcount(mimg.dmask) == 1, "Unexpected bits in gather dmask"); + for (u32 i = 0; i < 4; i++) { + const IR::F32 value = IR::F32{ir.CompositeExtract(texel, i)}; + ir.SetVectorReg(dest_reg++, value); + } +} + +void Translator::IMAGE_GET_LOD(const GcnInst& inst) { + const auto& mimg = inst.control.mimg; + IR::VectorReg dst_reg{inst.dst[0].code}; + IR::VectorReg addr_reg{inst.src[0].code}; + const IR::ScalarReg tsharp_reg{inst.src[2].code * 4}; + + const IR::Value handle = ir.GetScalarReg(tsharp_reg); + const IR::Value body = ir.CompositeConstruct( + ir.GetVectorReg(addr_reg), ir.GetVectorReg(addr_reg + 1), + ir.GetVectorReg(addr_reg + 2), ir.GetVectorReg(addr_reg + 3)); + const IR::Value lod = ir.ImageQueryLod(handle, body, {}); + ir.SetVectorReg(dst_reg++, IR::F32{ir.CompositeExtract(lod, 0)}); + ir.SetVectorReg(dst_reg++, IR::F32{ir.CompositeExtract(lod, 1)}); +} + } // namespace Shader::Gcn diff --git a/src/shader_recompiler/info.h b/src/shader_recompiler/info.h new file mode 100644 index 00000000000..739214ec997 --- /dev/null +++ b/src/shader_recompiler/info.h @@ -0,0 +1,272 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later +#pragma once + +#include +#include +#include +#include "common/assert.h" +#include "common/types.h" +#include "shader_recompiler/backend/bindings.h" +#include "shader_recompiler/ir/attribute.h" +#include "shader_recompiler/ir/reg.h" +#include "shader_recompiler/ir/type.h" +#include "shader_recompiler/params.h" +#include "shader_recompiler/runtime_info.h" +#include "video_core/amdgpu/resource.h" + +namespace Shader { + +static constexpr size_t NumUserDataRegs = 16; + +enum class TextureType : u32 { + Color1D, + ColorArray1D, + Color2D, + ColorArray2D, + Color3D, + ColorCube, + Buffer, +}; +constexpr u32 NUM_TEXTURE_TYPES = 7; + +struct Info; + +struct BufferResource { + u32 sgpr_base; + u32 dword_offset; + IR::Type used_types; + AmdGpu::Buffer inline_cbuf; + bool is_gds_buffer{}; + bool is_instance_data{}; + bool is_written{}; + + bool IsStorage(AmdGpu::Buffer buffer) const noexcept { + static constexpr size_t MaxUboSize = 65536; + return buffer.GetSize() > MaxUboSize || is_written || is_gds_buffer; + } + + constexpr AmdGpu::Buffer GetSharp(const Info& info) const noexcept; +}; +using BufferResourceList = boost::container::small_vector; + +struct TextureBufferResource { + u32 sgpr_base; + u32 dword_offset; + AmdGpu::NumberFormat nfmt; + bool is_written{}; + + constexpr AmdGpu::Buffer GetSharp(const Info& info) const noexcept; +}; +using TextureBufferResourceList = boost::container::small_vector; + +struct ImageResource { + u32 sgpr_base; + u32 dword_offset; + AmdGpu::ImageType type; + AmdGpu::NumberFormat nfmt; + bool is_storage{}; + bool is_depth{}; + bool is_atomic{}; + bool is_array{}; + + constexpr AmdGpu::Image GetSharp(const Info& info) const noexcept; +}; +using ImageResourceList = boost::container::small_vector; + +struct SamplerResource { + u32 sgpr_base; + u32 dword_offset; + AmdGpu::Sampler inline_sampler{}; + u32 associated_image : 4; + u32 disable_aniso : 1; + + constexpr AmdGpu::Sampler GetSharp(const Info& info) const noexcept; +}; +using SamplerResourceList = boost::container::small_vector; + +struct PushData { + static constexpr u32 BufOffsetIndex = 2; + static constexpr u32 UdRegsIndex = 4; + + u32 step0; + u32 step1; + std::array buf_offsets; + std::array ud_regs; + + void AddOffset(u32 binding, u32 offset) { + ASSERT(offset < 256 && binding < buf_offsets.size()); + buf_offsets[binding] = offset; + } +}; +static_assert(sizeof(PushData) <= 128, + "PushData size is greater than minimum size guaranteed by Vulkan spec"); + +/** + * Contains general information generated by the shader recompiler for an input program. + */ +struct Info { + struct VsInput { + enum InstanceIdType : u8 { + None = 0, + OverStepRate0 = 1, + OverStepRate1 = 2, + Plain = 3, + }; + + AmdGpu::NumberFormat fmt; + u16 binding; + u16 num_components; + u8 sgpr_base; + u8 dword_offset; + InstanceIdType instance_step_rate; + s32 instance_data_buf; + }; + boost::container::static_vector vs_inputs{}; + + struct AttributeFlags { + bool Get(IR::Attribute attrib, u32 comp = 0) const { + return flags[Index(attrib)] & (1 << comp); + } + + bool GetAny(IR::Attribute attrib) const { + return flags[Index(attrib)]; + } + + void Set(IR::Attribute attrib, u32 comp = 0) { + flags[Index(attrib)] |= (1 << comp); + } + + u32 NumComponents(IR::Attribute attrib) const { + return 4; + } + + static size_t Index(IR::Attribute attrib) { + return static_cast(attrib); + } + + std::array flags; + }; + AttributeFlags loads{}; + AttributeFlags stores{}; + + struct UserDataMask { + void Set(IR::ScalarReg reg) noexcept { + mask |= 1 << static_cast(reg); + } + + u32 Index(IR::ScalarReg reg) const noexcept { + const u32 reg_mask = (1 << static_cast(reg)) - 1; + return std::popcount(mask & reg_mask); + } + + u32 NumRegs() const noexcept { + return std::popcount(mask); + } + + u32 mask; + }; + UserDataMask ud_mask{}; + + s8 vertex_offset_sgpr = -1; + s8 instance_offset_sgpr = -1; + + BufferResourceList buffers; + TextureBufferResourceList texture_buffers; + ImageResourceList images; + SamplerResourceList samplers; + + std::span user_data; + Stage stage; + + u64 pgm_hash{}; + VAddr pgm_base; + bool has_storage_images{}; + bool has_image_buffers{}; + bool has_texel_buffers{}; + bool has_discard{}; + bool has_image_gather{}; + bool has_image_query{}; + bool uses_lane_id{}; + bool uses_group_quad{}; + bool uses_group_ballot{}; + bool uses_shared{}; + bool uses_fp16{}; + bool uses_fp64{}; + bool uses_step_rates{}; + bool translation_failed{}; // indicates that shader has unsupported instructions + u8 mrt_mask{0u}; + + explicit Info(Stage stage_, ShaderParams params) + : stage{stage_}, pgm_hash{params.hash}, pgm_base{params.Base()}, + user_data{params.user_data} {} + + template + T ReadUd(u32 ptr_index, u32 dword_offset) const noexcept { + T data; + const u32* base = user_data.data(); + if (ptr_index != IR::NumScalarRegs) { + std::memcpy(&base, &user_data[ptr_index], sizeof(base)); + base = reinterpret_cast(VAddr(base) & 0xFFFFFFFFFFFFULL); + } + std::memcpy(&data, base + dword_offset, sizeof(T)); + return data; + } + + void PushUd(Backend::Bindings& bnd, PushData& push) const { + u32 mask = ud_mask.mask; + while (mask) { + const u32 index = std::countr_zero(mask); + ASSERT(bnd.user_data < NumUserDataRegs && index < NumUserDataRegs); + mask &= ~(1U << index); + push.ud_regs[bnd.user_data++] = user_data[index]; + } + } + + void AddBindings(Backend::Bindings& bnd) const { + bnd.buffer += buffers.size() + texture_buffers.size(); + bnd.unified += bnd.buffer + images.size() + samplers.size(); + bnd.user_data += ud_mask.NumRegs(); + } + + [[nodiscard]] std::pair GetDrawOffsets() const { + u32 vertex_offset = 0; + u32 instance_offset = 0; + if (vertex_offset_sgpr != -1) { + vertex_offset = user_data[vertex_offset_sgpr]; + } + if (instance_offset_sgpr != -1) { + instance_offset = user_data[instance_offset_sgpr]; + } + return {vertex_offset, instance_offset}; + } +}; + +constexpr AmdGpu::Buffer BufferResource::GetSharp(const Info& info) const noexcept { + return inline_cbuf ? inline_cbuf : info.ReadUd(sgpr_base, dword_offset); +} + +constexpr AmdGpu::Buffer TextureBufferResource::GetSharp(const Info& info) const noexcept { + return info.ReadUd(sgpr_base, dword_offset); +} + +constexpr AmdGpu::Image ImageResource::GetSharp(const Info& info) const noexcept { + return info.ReadUd(sgpr_base, dword_offset); +} + +constexpr AmdGpu::Sampler SamplerResource::GetSharp(const Info& info) const noexcept { + return inline_sampler ? inline_sampler : info.ReadUd(sgpr_base, dword_offset); +} + +} // namespace Shader + +template <> +struct fmt::formatter { + constexpr auto parse(format_parse_context& ctx) { + return ctx.begin(); + } + auto format(const Shader::Stage stage, format_context& ctx) const { + constexpr static std::array names = {"fs", "vs", "gs", "es", "hs", "ls", "cs"}; + return fmt::format_to(ctx.out(), "{}", names[static_cast(stage)]); + } +}; diff --git a/src/shader_recompiler/ir/basic_block.h b/src/shader_recompiler/ir/basic_block.h index 1eb11469c99..11ae969bc2d 100644 --- a/src/shader_recompiler/ir/basic_block.h +++ b/src/shader_recompiler/ir/basic_block.h @@ -147,6 +147,7 @@ class Block { /// Intrusively store the value of a register in the block. std::array ssa_sreg_values; + std::array ssa_sbit_values; std::array ssa_vreg_values; bool has_multiple_predecessors{false}; diff --git a/src/shader_recompiler/ir/ir_emitter.cpp b/src/shader_recompiler/ir/ir_emitter.cpp index 473ae4f66e3..a7edb6d9c01 100644 --- a/src/shader_recompiler/ir/ir_emitter.cpp +++ b/src/shader_recompiler/ir/ir_emitter.cpp @@ -217,6 +217,10 @@ U32 IREmitter::GetVccHi() { return Inst(Opcode::GetVccHi); } +U32 IREmitter::GetM0() { + return Inst(Opcode::GetM0); +} + void IREmitter::SetScc(const U1& value) { Inst(Opcode::SetScc, value); } @@ -241,6 +245,10 @@ void IREmitter::SetVccHi(const U32& value) { Inst(Opcode::SetVccHi, value); } +void IREmitter::SetM0(const U32& value) { + Inst(Opcode::SetM0, value); +} + F32 IREmitter::GetAttribute(IR::Attribute attribute, u32 comp) { return Inst(Opcode::GetAttribute, attribute, Imm32(comp)); } @@ -305,21 +313,21 @@ U32 IREmitter::ReadConst(const Value& base, const U32& offset) { return Inst(Opcode::ReadConst, base, offset); } -F32 IREmitter::ReadConstBuffer(const Value& handle, const U32& index) { - return Inst(Opcode::ReadConstBuffer, handle, index); +U32 IREmitter::ReadConstBuffer(const Value& handle, const U32& index) { + return Inst(Opcode::ReadConstBuffer, handle, index); } Value IREmitter::LoadBuffer(int num_dwords, const Value& handle, const Value& address, BufferInstInfo info) { switch (num_dwords) { case 1: - return Inst(Opcode::LoadBufferF32, Flags{info}, handle, address); + return Inst(Opcode::LoadBufferU32, Flags{info}, handle, address); case 2: - return Inst(Opcode::LoadBufferF32x2, Flags{info}, handle, address); + return Inst(Opcode::LoadBufferU32x2, Flags{info}, handle, address); case 3: - return Inst(Opcode::LoadBufferF32x3, Flags{info}, handle, address); + return Inst(Opcode::LoadBufferU32x3, Flags{info}, handle, address); case 4: - return Inst(Opcode::LoadBufferF32x4, Flags{info}, handle, address); + return Inst(Opcode::LoadBufferU32x4, Flags{info}, handle, address); default: UNREACHABLE_MSG("Invalid number of dwords {}", num_dwords); } @@ -333,17 +341,16 @@ void IREmitter::StoreBuffer(int num_dwords, const Value& handle, const Value& ad const Value& data, BufferInstInfo info) { switch (num_dwords) { case 1: - Inst(data.Type() == Type::F32 ? Opcode::StoreBufferF32 : Opcode::StoreBufferU32, - Flags{info}, handle, address, data); + Inst(Opcode::StoreBufferU32, Flags{info}, handle, address, data); break; case 2: - Inst(Opcode::StoreBufferF32x2, Flags{info}, handle, address, data); + Inst(Opcode::StoreBufferU32x2, Flags{info}, handle, address, data); break; case 3: - Inst(Opcode::StoreBufferF32x3, Flags{info}, handle, address, data); + Inst(Opcode::StoreBufferU32x3, Flags{info}, handle, address, data); break; case 4: - Inst(Opcode::StoreBufferF32x4, Flags{info}, handle, address, data); + Inst(Opcode::StoreBufferU32x4, Flags{info}, handle, address, data); break; default: UNREACHABLE_MSG("Invalid number of dwords {}", num_dwords); @@ -402,6 +409,14 @@ void IREmitter::StoreBufferFormat(const Value& handle, const Value& address, con Inst(Opcode::StoreBufferFormatF32, Flags{info}, handle, address, data); } +U32 IREmitter::DataAppend(const U32& counter) { + return Inst(Opcode::DataAppend, counter, Imm32(0)); +} + +U32 IREmitter::DataConsume(const U32& counter) { + return Inst(Opcode::DataConsume, counter, Imm32(0)); +} + U32 IREmitter::LaneId() { return Inst(Opcode::LaneId); } @@ -614,6 +629,10 @@ Value IREmitter::UnpackUint2x32(const U64& value) { return Inst(Opcode::UnpackUint2x32, value); } +F64 IREmitter::PackFloat2x32(const Value& vector) { + return Inst(Opcode::PackFloat2x32, vector); +} + U32 IREmitter::PackFloat2x16(const Value& vector) { return Inst(Opcode::PackFloat2x16, vector); } @@ -1040,6 +1059,10 @@ U32 IREmitter::IDiv(const U32& a, const U32& b, bool is_signed) { return Inst(is_signed ? Opcode::SDiv32 : Opcode::UDiv32, a, b); } +U32 IREmitter::IMod(const U32& a, const U32& b, bool is_signed) { + return Inst(is_signed ? Opcode::SMod32 : Opcode::UMod32, a, b); +} + U32U64 IREmitter::INeg(const U32U64& value) { switch (value.Type()) { case Type::U32: @@ -1056,6 +1079,10 @@ U32 IREmitter::IAbs(const U32& value) { } U32U64 IREmitter::ShiftLeftLogical(const U32U64& base, const U32& shift) { + if (shift.IsImmediate() && shift.U32() == 0) { + return base; + } + switch (base.Type()) { case Type::U32: return Inst(Opcode::ShiftLeftLogical32, base, shift); @@ -1067,6 +1094,10 @@ U32U64 IREmitter::ShiftLeftLogical(const U32U64& base, const U32& shift) { } U32U64 IREmitter::ShiftRightLogical(const U32U64& base, const U32& shift) { + if (shift.IsImmediate() && shift.U32() == 0) { + return base; + } + switch (base.Type()) { case Type::U32: return Inst(Opcode::ShiftRightLogical32, base, shift); @@ -1078,6 +1109,10 @@ U32U64 IREmitter::ShiftRightLogical(const U32U64& base, const U32& shift) { } U32U64 IREmitter::ShiftRightArithmetic(const U32U64& base, const U32& shift) { + if (shift.IsImmediate() && shift.U32() == 0) { + return base; + } + switch (base.Type()) { case Type::U32: return Inst(Opcode::ShiftRightArithmetic32, base, shift); @@ -1345,6 +1380,8 @@ U16U32U64 IREmitter::UConvert(size_t result_bitsize, const U16U32U64& value) { switch (value.Type()) { case Type::U16: return Inst(Opcode::ConvertU32U16, value); + default: + break; } default: break; diff --git a/src/shader_recompiler/ir/ir_emitter.h b/src/shader_recompiler/ir/ir_emitter.h index de8fe450d2f..958f2e88b9a 100644 --- a/src/shader_recompiler/ir/ir_emitter.h +++ b/src/shader_recompiler/ir/ir_emitter.h @@ -67,12 +67,14 @@ class IREmitter { [[nodiscard]] U1 GetVcc(); [[nodiscard]] U32 GetVccLo(); [[nodiscard]] U32 GetVccHi(); + [[nodiscard]] U32 GetM0(); void SetScc(const U1& value); void SetExec(const U1& value); void SetVcc(const U1& value); void SetSccLo(const U32& value); void SetVccLo(const U32& value); void SetVccHi(const U32& value); + void SetM0(const U32& value); [[nodiscard]] U1 Condition(IR::Condition cond); @@ -88,7 +90,7 @@ class IREmitter { [[nodiscard]] U32 SharedAtomicIMax(const U32& address, const U32& data, bool is_signed); [[nodiscard]] U32 ReadConst(const Value& base, const U32& offset); - [[nodiscard]] F32 ReadConstBuffer(const Value& handle, const U32& index); + [[nodiscard]] U32 ReadConstBuffer(const Value& handle, const U32& index); [[nodiscard]] Value LoadBuffer(int num_dwords, const Value& handle, const Value& address, BufferInstInfo info); @@ -118,6 +120,8 @@ class IREmitter { [[nodiscard]] Value BufferAtomicSwap(const Value& handle, const Value& address, const Value& value, BufferInstInfo info); + [[nodiscard]] U32 DataAppend(const U32& counter); + [[nodiscard]] U32 DataConsume(const U32& counter); [[nodiscard]] U32 LaneId(); [[nodiscard]] U32 WarpId(); [[nodiscard]] U32 QuadShuffle(const U32& value, const U32& index); @@ -138,6 +142,8 @@ class IREmitter { [[nodiscard]] U64 PackUint2x32(const Value& vector); [[nodiscard]] Value UnpackUint2x32(const U64& value); + [[nodiscard]] F64 PackFloat2x32(const Value& vector); + [[nodiscard]] U32 PackFloat2x16(const Value& vector); [[nodiscard]] Value UnpackFloat2x16(const U32& value); @@ -190,6 +196,7 @@ class IREmitter { [[nodiscard]] Value IMulExt(const U32& a, const U32& b, bool is_signed = false); [[nodiscard]] U32U64 IMul(const U32U64& a, const U32U64& b); [[nodiscard]] U32 IDiv(const U32& a, const U32& b, bool is_signed = false); + [[nodiscard]] U32 IMod(const U32& a, const U32& b, bool is_signed = false); [[nodiscard]] U32U64 INeg(const U32U64& value); [[nodiscard]] U32 IAbs(const U32& value); [[nodiscard]] U32U64 ShiftLeftLogical(const U32U64& base, const U32& shift); diff --git a/src/shader_recompiler/ir/microinstruction.cpp b/src/shader_recompiler/ir/microinstruction.cpp index d6ef49cf7a6..601c453d9aa 100644 --- a/src/shader_recompiler/ir/microinstruction.cpp +++ b/src/shader_recompiler/ir/microinstruction.cpp @@ -51,12 +51,11 @@ bool Inst::MayHaveSideEffects() const noexcept { case Opcode::Discard: case Opcode::DiscardCond: case Opcode::SetAttribute: - case Opcode::StoreBufferF32: - case Opcode::StoreBufferF32x2: - case Opcode::StoreBufferF32x3: - case Opcode::StoreBufferF32x4: - case Opcode::StoreBufferFormatF32: case Opcode::StoreBufferU32: + case Opcode::StoreBufferU32x2: + case Opcode::StoreBufferU32x3: + case Opcode::StoreBufferU32x4: + case Opcode::StoreBufferFormatF32: case Opcode::BufferAtomicIAdd32: case Opcode::BufferAtomicSMin32: case Opcode::BufferAtomicUMin32: @@ -68,6 +67,8 @@ bool Inst::MayHaveSideEffects() const noexcept { case Opcode::BufferAtomicOr32: case Opcode::BufferAtomicXor32: case Opcode::BufferAtomicSwap32: + case Opcode::DataAppend: + case Opcode::DataConsume: case Opcode::WriteSharedU128: case Opcode::WriteSharedU64: case Opcode::WriteSharedU32: diff --git a/src/shader_recompiler/ir/opcodes.inc b/src/shader_recompiler/ir/opcodes.inc index 1e33d6d4530..41cc553f99d 100644 --- a/src/shader_recompiler/ir/opcodes.inc +++ b/src/shader_recompiler/ir/opcodes.inc @@ -17,8 +17,7 @@ OPCODE(DiscardCond, Void, U1, // Constant memory operations OPCODE(ReadConst, U32, U32x2, U32, ) -OPCODE(ReadConstBuffer, F32, Opaque, U32, ) -OPCODE(ReadConstBufferU32, U32, Opaque, U32, ) +OPCODE(ReadConstBuffer, U32, Opaque, U32, ) // Barriers OPCODE(Barrier, Void, ) @@ -35,9 +34,9 @@ OPCODE(WriteSharedU128, Void, U32, // Shared atomic operations OPCODE(SharedAtomicIAdd32, U32, U32, U32, ) -OPCODE(SharedAtomicSMin32, U32, U32, U32, ) +OPCODE(SharedAtomicSMin32, U32, U32, U32, ) OPCODE(SharedAtomicUMin32, U32, U32, U32, ) -OPCODE(SharedAtomicSMax32, U32, U32, U32, ) +OPCODE(SharedAtomicSMax32, U32, U32, U32, ) OPCODE(SharedAtomicUMax32, U32, U32, U32, ) // Context getters/setters @@ -55,17 +54,19 @@ OPCODE(GetAttributeU32, U32, Attr OPCODE(SetAttribute, Void, Attribute, F32, U32, ) // Flags -OPCODE(GetScc, U1, Void, ) -OPCODE(GetExec, U1, Void, ) -OPCODE(GetVcc, U1, Void, ) -OPCODE(GetVccLo, U32, Void, ) -OPCODE(GetVccHi, U32, Void, ) -OPCODE(SetScc, Void, U1, ) -OPCODE(SetExec, Void, U1, ) -OPCODE(SetVcc, Void, U1, ) -OPCODE(SetSccLo, Void, U32, ) -OPCODE(SetVccLo, Void, U32, ) -OPCODE(SetVccHi, Void, U32, ) +OPCODE(GetScc, U1, Void, ) +OPCODE(GetExec, U1, Void, ) +OPCODE(GetVcc, U1, Void, ) +OPCODE(GetVccLo, U32, Void, ) +OPCODE(GetVccHi, U32, Void, ) +OPCODE(GetM0, U32, Void, ) +OPCODE(SetScc, Void, U1, ) +OPCODE(SetExec, Void, U1, ) +OPCODE(SetVcc, Void, U1, ) +OPCODE(SetSccLo, Void, U32, ) +OPCODE(SetVccLo, Void, U32, ) +OPCODE(SetVccHi, Void, U32, ) +OPCODE(SetM0, Void, U32, ) // Undefined OPCODE(UndefU1, U1, ) @@ -75,31 +76,29 @@ OPCODE(UndefU32, U32, OPCODE(UndefU64, U64, ) // Buffer operations -OPCODE(LoadBufferF32, F32, Opaque, Opaque, ) -OPCODE(LoadBufferF32x2, F32x2, Opaque, Opaque, ) -OPCODE(LoadBufferF32x3, F32x3, Opaque, Opaque, ) -OPCODE(LoadBufferF32x4, F32x4, Opaque, Opaque, ) -OPCODE(LoadBufferFormatF32, F32x4, Opaque, Opaque, ) OPCODE(LoadBufferU32, U32, Opaque, Opaque, ) -OPCODE(StoreBufferF32, Void, Opaque, Opaque, F32, ) -OPCODE(StoreBufferF32x2, Void, Opaque, Opaque, F32x2, ) -OPCODE(StoreBufferF32x3, Void, Opaque, Opaque, F32x3, ) -OPCODE(StoreBufferF32x4, Void, Opaque, Opaque, F32x4, ) -OPCODE(StoreBufferFormatF32, Void, Opaque, Opaque, F32x4, ) +OPCODE(LoadBufferU32x2, U32x2, Opaque, Opaque, ) +OPCODE(LoadBufferU32x3, U32x3, Opaque, Opaque, ) +OPCODE(LoadBufferU32x4, U32x4, Opaque, Opaque, ) +OPCODE(LoadBufferFormatF32, F32x4, Opaque, Opaque, ) OPCODE(StoreBufferU32, Void, Opaque, Opaque, U32, ) +OPCODE(StoreBufferU32x2, Void, Opaque, Opaque, U32x2, ) +OPCODE(StoreBufferU32x3, Void, Opaque, Opaque, U32x3, ) +OPCODE(StoreBufferU32x4, Void, Opaque, Opaque, U32x4, ) +OPCODE(StoreBufferFormatF32, Void, Opaque, Opaque, U32x4, ) // Buffer atomic operations -OPCODE(BufferAtomicIAdd32, U32, Opaque, Opaque, U32 ) -OPCODE(BufferAtomicSMin32, U32, Opaque, Opaque, U32 ) -OPCODE(BufferAtomicUMin32, U32, Opaque, Opaque, U32 ) -OPCODE(BufferAtomicSMax32, U32, Opaque, Opaque, U32 ) -OPCODE(BufferAtomicUMax32, U32, Opaque, Opaque, U32 ) -OPCODE(BufferAtomicInc32, U32, Opaque, Opaque, U32, ) -OPCODE(BufferAtomicDec32, U32, Opaque, Opaque, U32, ) -OPCODE(BufferAtomicAnd32, U32, Opaque, Opaque, U32, ) -OPCODE(BufferAtomicOr32, U32, Opaque, Opaque, U32, ) -OPCODE(BufferAtomicXor32, U32, Opaque, Opaque, U32, ) -OPCODE(BufferAtomicSwap32, U32, Opaque, Opaque, U32, ) +OPCODE(BufferAtomicIAdd32, U32, Opaque, Opaque, U32 ) +OPCODE(BufferAtomicSMin32, U32, Opaque, Opaque, U32 ) +OPCODE(BufferAtomicUMin32, U32, Opaque, Opaque, U32 ) +OPCODE(BufferAtomicSMax32, U32, Opaque, Opaque, U32 ) +OPCODE(BufferAtomicUMax32, U32, Opaque, Opaque, U32 ) +OPCODE(BufferAtomicInc32, U32, Opaque, Opaque, U32, ) +OPCODE(BufferAtomicDec32, U32, Opaque, Opaque, U32, ) +OPCODE(BufferAtomicAnd32, U32, Opaque, Opaque, U32, ) +OPCODE(BufferAtomicOr32, U32, Opaque, Opaque, U32, ) +OPCODE(BufferAtomicXor32, U32, Opaque, Opaque, U32, ) +OPCODE(BufferAtomicSwap32, U32, Opaque, Opaque, U32, ) // Vector utility OPCODE(CompositeConstructU32x2, U32x2, U32, U32, ) @@ -157,6 +156,7 @@ OPCODE(BitCastF32U32, F32, U32, OPCODE(BitCastF64U64, F64, U64, ) OPCODE(PackUint2x32, U64, U32x2, ) OPCODE(UnpackUint2x32, U32x2, U64, ) +OPCODE(PackFloat2x32, F64, F32x2, ) OPCODE(PackFloat2x16, U32, F16x2, ) OPCODE(UnpackFloat2x16, F16x2, U32, ) OPCODE(PackHalf2x16, U32, F32x2, ) @@ -244,6 +244,8 @@ OPCODE(SMulExt, U32x2, U32, OPCODE(UMulExt, U32x2, U32, U32, ) OPCODE(SDiv32, U32, U32, U32, ) OPCODE(UDiv32, U32, U32, U32, ) +OPCODE(SMod32, U32, U32, U32, ) +OPCODE(UMod32, U32, U32, U32, ) OPCODE(INeg32, U32, U32, ) OPCODE(INeg64, U64, U64, ) OPCODE(IAbs32, U32, U32, ) @@ -340,6 +342,8 @@ OPCODE(ImageAtomicExchange32, U32, Opaq OPCODE(LaneId, U32, ) OPCODE(WarpId, U32, ) OPCODE(QuadShuffle, U32, U32, U32 ) -OPCODE(ReadFirstLane, U32, U32, U32 ) +OPCODE(ReadFirstLane, U32, U32, ) OPCODE(ReadLane, U32, U32, U32 ) OPCODE(WriteLane, U32, U32, U32, U32 ) +OPCODE(DataAppend, U32, U32, U32 ) +OPCODE(DataConsume, U32, U32, U32 ) diff --git a/src/shader_recompiler/ir/passes/constant_propagation_pass.cpp b/src/shader_recompiler/ir/passes/constant_propagation_pass.cpp index 87a069338b1..775aed5b312 100644 --- a/src/shader_recompiler/ir/passes/constant_propagation_pass.cpp +++ b/src/shader_recompiler/ir/passes/constant_propagation_pass.cpp @@ -278,6 +278,12 @@ void ConstantPropagation(IR::Block& block, IR::Inst& inst) { case IR::Opcode::FPCmpClass32: FoldCmpClass(inst); return; + case IR::Opcode::ShiftLeftLogical32: + FoldWhenAllImmediates(inst, [](u32 a, u32 b) { return static_cast(a << b); }); + return; + case IR::Opcode::ShiftRightLogical32: + FoldWhenAllImmediates(inst, [](u32 a, u32 b) { return static_cast(a >> b); }); + return; case IR::Opcode::ShiftRightArithmetic32: FoldWhenAllImmediates(inst, [](s32 a, s32 b) { return static_cast(a >> b); }); return; @@ -347,7 +353,6 @@ void ConstantPropagation(IR::Block& block, IR::Inst& inst) { return; case IR::Opcode::INotEqual: FoldWhenAllImmediates(inst, [](u32 a, u32 b) { return a != b; }); - FoldBooleanConvert(inst); return; case IR::Opcode::BitwiseAnd32: FoldWhenAllImmediates(inst, [](u32 a, u32 b) { return a & b; }); diff --git a/src/shader_recompiler/ir/passes/lower_shared_mem_to_registers.cpp b/src/shader_recompiler/ir/passes/lower_shared_mem_to_registers.cpp index a87cf31b1c0..76bfcf9111f 100644 --- a/src/shader_recompiler/ir/passes/lower_shared_mem_to_registers.cpp +++ b/src/shader_recompiler/ir/passes/lower_shared_mem_to_registers.cpp @@ -21,8 +21,7 @@ void LowerSharedMemToRegisters(IR::Program& program) { const IR::Inst* prod = inst.Arg(0).InstRecursive(); const auto it = std::ranges::find_if(ds_writes, [&](const IR::Inst* write) { const IR::Inst* write_prod = write->Arg(0).InstRecursive(); - return write_prod->Arg(1).U32() == prod->Arg(1).U32() && - write_prod->Arg(0) == prod->Arg(0); + return write_prod->Arg(1).U32() == prod->Arg(1).U32(); }); ASSERT(it != ds_writes.end()); // Replace data read with value written. diff --git a/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp b/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp index f446ac476da..db0d75f0c2d 100644 --- a/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp +++ b/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp @@ -3,12 +3,11 @@ #include #include -#include "common/alignment.h" +#include "shader_recompiler/info.h" #include "shader_recompiler/ir/basic_block.h" #include "shader_recompiler/ir/breadth_first_search.h" #include "shader_recompiler/ir/ir_emitter.h" #include "shader_recompiler/ir/program.h" -#include "shader_recompiler/runtime_info.h" #include "video_core/amdgpu/resource.h" namespace Shader::Optimization { @@ -42,11 +41,10 @@ bool IsBufferAtomic(const IR::Inst& inst) { bool IsBufferStore(const IR::Inst& inst) { switch (inst.GetOpcode()) { - case IR::Opcode::StoreBufferF32: - case IR::Opcode::StoreBufferF32x2: - case IR::Opcode::StoreBufferF32x3: - case IR::Opcode::StoreBufferF32x4: case IR::Opcode::StoreBufferU32: + case IR::Opcode::StoreBufferU32x2: + case IR::Opcode::StoreBufferU32x3: + case IR::Opcode::StoreBufferU32x4: return true; default: return IsBufferAtomic(inst); @@ -55,25 +53,28 @@ bool IsBufferStore(const IR::Inst& inst) { bool IsBufferInstruction(const IR::Inst& inst) { switch (inst.GetOpcode()) { - case IR::Opcode::LoadBufferF32: - case IR::Opcode::LoadBufferF32x2: - case IR::Opcode::LoadBufferF32x3: - case IR::Opcode::LoadBufferF32x4: case IR::Opcode::LoadBufferU32: + case IR::Opcode::LoadBufferU32x2: + case IR::Opcode::LoadBufferU32x3: + case IR::Opcode::LoadBufferU32x4: case IR::Opcode::ReadConstBuffer: - case IR::Opcode::ReadConstBufferU32: return true; default: return IsBufferStore(inst); } } +bool IsDataRingInstruction(const IR::Inst& inst) { + return inst.GetOpcode() == IR::Opcode::DataAppend || + inst.GetOpcode() == IR::Opcode::DataConsume; +} + bool IsTextureBufferInstruction(const IR::Inst& inst) { return inst.GetOpcode() == IR::Opcode::LoadBufferFormatF32 || inst.GetOpcode() == IR::Opcode::StoreBufferFormatF32; } -static bool UseFP16(AmdGpu::DataFormat data_format, AmdGpu::NumberFormat num_format) { +bool UseFP16(AmdGpu::DataFormat data_format, AmdGpu::NumberFormat num_format) { switch (num_format) { case AmdGpu::NumberFormat::Float: switch (data_format) { @@ -97,26 +98,7 @@ static bool UseFP16(AmdGpu::DataFormat data_format, AmdGpu::NumberFormat num_for } IR::Type BufferDataType(const IR::Inst& inst, AmdGpu::NumberFormat num_format) { - switch (inst.GetOpcode()) { - case IR::Opcode::LoadBufferF32: - case IR::Opcode::LoadBufferF32x2: - case IR::Opcode::LoadBufferF32x3: - case IR::Opcode::LoadBufferF32x4: - case IR::Opcode::ReadConstBuffer: - case IR::Opcode::StoreBufferF32: - case IR::Opcode::StoreBufferF32x2: - case IR::Opcode::StoreBufferF32x3: - case IR::Opcode::StoreBufferF32x4: - return IR::Type::F32; - case IR::Opcode::LoadBufferU32: - case IR::Opcode::ReadConstBufferU32: - case IR::Opcode::StoreBufferU32: - case IR::Opcode::BufferAtomicIAdd32: - case IR::Opcode::BufferAtomicSwap32: - return IR::Type::U32; - default: - UNREACHABLE(); - } + return IR::Type::U32; } bool IsImageAtomicInstruction(const IR::Inst& inst) { @@ -191,6 +173,10 @@ class Descriptors { u32 Add(const BufferResource& desc) { const u32 index{Add(buffer_resources, desc, [&desc](const auto& existing) { + // Only one GDS binding can exist. + if (desc.is_gds_buffer && existing.is_gds_buffer) { + return true; + } return desc.sgpr_base == existing.sgpr_base && desc.dword_offset == existing.dword_offset && desc.inline_cbuf == existing.inline_cbuf; @@ -214,20 +200,17 @@ class Descriptors { u32 Add(const ImageResource& desc) { const u32 index{Add(image_resources, desc, [&desc](const auto& existing) { return desc.sgpr_base == existing.sgpr_base && - desc.dword_offset == existing.dword_offset && desc.type == existing.type && - desc.is_storage == existing.is_storage; + desc.dword_offset == existing.dword_offset; })}; + auto& image = image_resources[index]; + image.is_storage |= desc.is_storage; return index; } u32 Add(const SamplerResource& desc) { const u32 index{Add(sampler_resources, desc, [this, &desc](const auto& existing) { - if (desc.sgpr_base == existing.sgpr_base && - desc.dword_offset == existing.dword_offset) { - return true; - } - // Samplers with different bindings might still be the same. - return existing.GetSharp(info) == desc.GetSharp(info); + return desc.sgpr_base == existing.sgpr_base && + desc.dword_offset == existing.dword_offset; })}; return index; } @@ -396,25 +379,45 @@ void PatchBufferInstruction(IR::Block& block, IR::Inst& inst, Info& info, // Replace handle with binding index in buffer resource list. IR::IREmitter ir{block, IR::Block::InstructionList::s_iterator_to(inst)}; inst.SetArg(0, ir.Imm32(binding)); - ASSERT(!buffer.swizzle_enable && !buffer.add_tid_enable); + ASSERT(!buffer.add_tid_enable); // Address of constant buffer reads can be calculated at IR emittion time. - if (inst.GetOpcode() == IR::Opcode::ReadConstBuffer || - inst.GetOpcode() == IR::Opcode::ReadConstBufferU32) { + if (inst.GetOpcode() == IR::Opcode::ReadConstBuffer) { return; } + const IR::U32 index_stride = ir.Imm32(buffer.index_stride); + const IR::U32 element_size = ir.Imm32(buffer.element_size); + // Compute address of the buffer using the stride. IR::U32 address = ir.Imm32(inst_info.inst_offset.Value()); if (inst_info.index_enable) { const IR::U32 index = inst_info.offset_enable ? IR::U32{ir.CompositeExtract(inst.Arg(1), 0)} : IR::U32{inst.Arg(1)}; - address = ir.IAdd(address, ir.IMul(index, ir.Imm32(buffer.GetStride()))); + if (buffer.swizzle_enable) { + const IR::U32 stride_index_stride = + ir.Imm32(static_cast(buffer.stride * buffer.index_stride)); + const IR::U32 index_msb = ir.IDiv(index, index_stride); + const IR::U32 index_lsb = ir.IMod(index, index_stride); + address = ir.IAdd(address, ir.IAdd(ir.IMul(index_msb, stride_index_stride), + ir.IMul(index_lsb, element_size))); + } else { + address = ir.IAdd(address, ir.IMul(index, ir.Imm32(buffer.GetStride()))); + } } if (inst_info.offset_enable) { const IR::U32 offset = inst_info.index_enable ? IR::U32{ir.CompositeExtract(inst.Arg(1), 1)} : IR::U32{inst.Arg(1)}; - address = ir.IAdd(address, offset); + if (buffer.swizzle_enable) { + const IR::U32 element_size_index_stride = + ir.Imm32(buffer.element_size * buffer.index_stride); + const IR::U32 offset_msb = ir.IDiv(offset, element_size); + const IR::U32 offset_lsb = ir.IMod(offset, element_size); + address = ir.IAdd(address, + ir.IAdd(ir.IMul(offset_msb, element_size_index_stride), offset_lsb)); + } else { + address = ir.IAdd(address, offset); + } } inst.SetArg(1, address); } @@ -439,18 +442,29 @@ void PatchTextureBufferInstruction(IR::Block& block, IR::Inst& inst, Info& info, } IR::Value PatchCubeCoord(IR::IREmitter& ir, const IR::Value& s, const IR::Value& t, - const IR::Value& z, bool is_storage) { + const IR::Value& z, bool is_storage, bool is_array) { // When cubemap is written with imageStore it is treated like 2DArray. if (is_storage) { return ir.CompositeConstruct(s, t, z); } + + ASSERT(s.Type() == IR::Type::F32); // in case of fetched image need to adjust the code below + // We need to fix x and y coordinate, // because the s and t coordinate will be scaled and plus 1.5 by v_madak_f32. // We already force the scale value to be 1.0 when handling v_cubema_f32, // here we subtract 1.5 to recover the original value. const IR::Value x = ir.FPSub(IR::F32{s}, ir.Imm32(1.5f)); const IR::Value y = ir.FPSub(IR::F32{t}, ir.Imm32(1.5f)); - return ir.CompositeConstruct(x, y, z); + if (is_array) { + const IR::U32 array_index = ir.ConvertFToU(32, IR::F32{z}); + const IR::U32 face_id = ir.BitwiseAnd(array_index, ir.Imm32(7u)); + const IR::U32 slice_id = ir.ShiftRightLogical(array_index, ir.Imm32(3u)); + return ir.CompositeConstruct(x, y, ir.ConvertIToF(32, 32, false, face_id), + ir.ConvertIToF(32, 32, false, slice_id)); + } else { + return ir.CompositeConstruct(x, y, z); + } } void PatchImageInstruction(IR::Block& block, IR::Inst& inst, Info& info, Descriptors& descriptors) { @@ -471,25 +485,24 @@ void PatchImageInstruction(IR::Block& block, IR::Inst& inst, Info& info, Descrip // Read image sharp. const auto tsharp = TrackSharp(tsharp_handle); - const auto image = info.ReadUd(tsharp.sgpr_base, tsharp.dword_offset); const auto inst_info = inst.Flags(); + auto image = info.ReadUd(tsharp.sgpr_base, tsharp.dword_offset); if (!image.Valid()) { LOG_ERROR(Render_Vulkan, "Shader compiled with unbound image!"); - IR::IREmitter ir{block, IR::Block::InstructionList::s_iterator_to(inst)}; - inst.ReplaceUsesWith( - ir.CompositeConstruct(ir.Imm32(0.f), ir.Imm32(0.f), ir.Imm32(0.f), ir.Imm32(0.f))); - return; + image = AmdGpu::Image::Null(); } ASSERT(image.GetType() != AmdGpu::ImageType::Invalid); const bool is_storage = IsImageStorageInstruction(inst); + const auto type = image.IsPartialCubemap() ? AmdGpu::ImageType::Color2DArray : image.GetType(); u32 image_binding = descriptors.Add(ImageResource{ .sgpr_base = tsharp.sgpr_base, .dword_offset = tsharp.dword_offset, - .type = image.GetType(), + .type = type, .nfmt = static_cast(image.GetNumberFmt()), .is_storage = is_storage, .is_depth = bool(inst_info.is_depth), .is_atomic = IsImageAtomicInstruction(inst), + .is_array = bool(inst_info.is_array), }); // Read sampler sharp. This doesn't exist for IMAGE_LOAD/IMAGE_STORE instructions @@ -546,7 +559,8 @@ void PatchImageInstruction(IR::Block& block, IR::Inst& inst, Info& info, Descrip case AmdGpu::ImageType::Color3D: // x, y, z return {ir.CompositeConstruct(body->Arg(0), body->Arg(1), body->Arg(2)), body->Arg(3)}; case AmdGpu::ImageType::Cube: // x, y, face - return {PatchCubeCoord(ir, body->Arg(0), body->Arg(1), body->Arg(2), is_storage), + return {PatchCubeCoord(ir, body->Arg(0), body->Arg(1), body->Arg(2), is_storage, + inst_info.is_array), body->Arg(3)}; default: UNREACHABLE_MSG("Unknown image type {}", image.GetType()); @@ -585,7 +599,8 @@ void PatchImageInstruction(IR::Block& block, IR::Inst& inst, Info& info, Descrip } } if (inst_info.has_derivatives) { - ASSERT_MSG(image.GetType() == AmdGpu::ImageType::Color2D, + ASSERT_MSG(image.GetType() == AmdGpu::ImageType::Color2D || + image.GetType() == AmdGpu::ImageType::Color2DArray, "User derivatives only supported for 2D images"); } if (inst_info.has_lod_clamp) { @@ -612,6 +627,51 @@ void PatchImageInstruction(IR::Block& block, IR::Inst& inst, Info& info, Descrip } } +void PatchDataRingInstruction(IR::Block& block, IR::Inst& inst, Info& info, + Descriptors& descriptors) { + // Insert gds binding in the shader if it doesn't exist already. + // The buffer is used for append/consume counters. + constexpr static AmdGpu::Buffer GdsSharp{.base_address = 1}; + const u32 binding = descriptors.Add(BufferResource{ + .used_types = IR::Type::U32, + .inline_cbuf = GdsSharp, + .is_gds_buffer = true, + .is_written = true, + }); + + const auto pred = [](const IR::Inst* inst) -> std::optional { + if (inst->GetOpcode() == IR::Opcode::GetUserData) { + return inst; + } + return std::nullopt; + }; + + // Attempt to deduce the GDS address of counter at compile time. + const u32 gds_addr = [&] { + const IR::Value& gds_offset = inst.Arg(0); + if (gds_offset.IsImmediate()) { + // Nothing to do, offset is known. + return gds_offset.U32() & 0xFFFF; + } + const auto result = IR::BreadthFirstSearch(&inst, pred); + ASSERT_MSG(result, "Unable to track M0 source"); + + // M0 must be set by some user data register. + const IR::Inst* prod = gds_offset.InstRecursive(); + const u32 ud_reg = u32(result.value()->Arg(0).ScalarReg()); + u32 m0_val = info.user_data[ud_reg] >> 16; + if (prod->GetOpcode() == IR::Opcode::IAdd32) { + m0_val += prod->Arg(1).U32(); + } + return m0_val & 0xFFFF; + }(); + + // Patch instruction. + IR::IREmitter ir{block, IR::Block::InstructionList::s_iterator_to(inst)}; + inst.SetArg(0, ir.Imm32(gds_addr >> 2)); + inst.SetArg(1, ir.Imm32(binding)); +} + void ResourceTrackingPass(IR::Program& program) { // Iterate resource instructions and patch them after finding the sharp. auto& info = program.info; @@ -628,6 +688,10 @@ void ResourceTrackingPass(IR::Program& program) { } if (IsImageInstruction(inst)) { PatchImageInstruction(*block, inst, info, descriptors); + continue; + } + if (IsDataRingInstruction(inst)) { + PatchDataRingInstruction(*block, inst, info, descriptors); } } } diff --git a/src/shader_recompiler/ir/passes/shader_info_collection_pass.cpp b/src/shader_recompiler/ir/passes/shader_info_collection_pass.cpp index 7105f01f154..e995852d51e 100644 --- a/src/shader_recompiler/ir/passes/shader_info_collection_pass.cpp +++ b/src/shader_recompiler/ir/passes/shader_info_collection_pass.cpp @@ -8,24 +8,29 @@ namespace Shader::Optimization { void Visit(Info& info, IR::Inst& inst) { switch (inst.GetOpcode()) { case IR::Opcode::GetAttribute: - case IR::Opcode::GetAttributeU32: { + case IR::Opcode::GetAttributeU32: info.loads.Set(inst.Arg(0).Attribute(), inst.Arg(1).U32()); break; - } - case IR::Opcode::SetAttribute: { + case IR::Opcode::SetAttribute: info.stores.Set(inst.Arg(0).Attribute(), inst.Arg(2).U32()); break; - } + case IR::Opcode::GetUserData: + info.ud_mask.Set(inst.Arg(0).ScalarReg()); + break; case IR::Opcode::LoadSharedU32: case IR::Opcode::LoadSharedU64: case IR::Opcode::WriteSharedU32: case IR::Opcode::WriteSharedU64: info.uses_shared = true; break; + case IR::Opcode::ConvertF16F32: case IR::Opcode::ConvertF32F16: case IR::Opcode::BitCastF16U16: info.uses_fp16 = true; break; + case IR::Opcode::BitCastU64F64: + info.uses_fp64 = true; + break; case IR::Opcode::ImageWrite: info.has_storage_images = true; break; @@ -38,6 +43,11 @@ void Visit(Info& info, IR::Inst& inst) { case IR::Opcode::QuadShuffle: info.uses_group_quad = true; break; + case IR::Opcode::ReadLane: + case IR::Opcode::ReadFirstLane: + case IR::Opcode::WriteLane: + info.uses_group_ballot = true; + break; case IR::Opcode::Discard: case IR::Opcode::DiscardCond: info.has_discard = true; diff --git a/src/shader_recompiler/ir/passes/ssa_rewrite_pass.cpp b/src/shader_recompiler/ir/passes/ssa_rewrite_pass.cpp index 9edb157db5c..df73c1bc80f 100644 --- a/src/shader_recompiler/ir/passes/ssa_rewrite_pass.cpp +++ b/src/shader_recompiler/ir/passes/ssa_rewrite_pass.cpp @@ -33,6 +33,7 @@ struct ExecFlagTag : FlagTag {}; struct VccFlagTag : FlagTag {}; struct VccLoTag : FlagTag {}; struct VccHiTag : FlagTag {}; +struct M0Tag : FlagTag {}; struct GotoVariable : FlagTag { GotoVariable() = default; @@ -43,8 +44,17 @@ struct GotoVariable : FlagTag { u32 index; }; -using Variant = std::variant; +struct ThreadBitScalar : FlagTag { + ThreadBitScalar() = default; + explicit ThreadBitScalar(IR::ScalarReg sgpr_) : sgpr{sgpr_} {} + + auto operator<=>(const ThreadBitScalar&) const noexcept = default; + + IR::ScalarReg sgpr; +}; + +using Variant = std::variant; using ValueMap = std::unordered_map; struct DefTable { @@ -69,6 +79,13 @@ struct DefTable { goto_vars[variable.index].insert_or_assign(block, value); } + const IR::Value& Def(IR::Block* block, ThreadBitScalar variable) { + return block->ssa_sbit_values[RegIndex(variable.sgpr)]; + } + void SetDef(IR::Block* block, ThreadBitScalar variable, const IR::Value& value) { + block->ssa_sbit_values[RegIndex(variable.sgpr)] = value; + } + const IR::Value& Def(IR::Block* block, SccFlagTag) { return scc_flag[block]; } @@ -103,6 +120,12 @@ struct DefTable { void SetDef(IR::Block* block, VccFlagTag, const IR::Value& value) { vcc_flag.insert_or_assign(block, value); } + const IR::Value& Def(IR::Block* block, M0Tag) { + return m0_flag[block]; + } + void SetDef(IR::Block* block, M0Tag, const IR::Value& value) { + m0_flag.insert_or_assign(block, value); + } std::unordered_map goto_vars; ValueMap scc_flag; @@ -111,6 +134,7 @@ struct DefTable { ValueMap scc_lo_flag; ValueMap vcc_lo_flag; ValueMap vcc_hi_flag; + ValueMap m0_flag; }; IR::Opcode UndefOpcode(IR::ScalarReg) noexcept { @@ -129,6 +153,10 @@ IR::Opcode UndefOpcode(const VccHiTag) noexcept { return IR::Opcode::UndefU32; } +IR::Opcode UndefOpcode(const M0Tag) noexcept { + return IR::Opcode::UndefU32; +} + IR::Opcode UndefOpcode(const FlagTag) noexcept { return IR::Opcode::UndefU1; } @@ -161,7 +189,7 @@ class Pass { } template - IR::Value ReadVariable(Type variable, IR::Block* root_block, bool is_thread_bit = false) { + IR::Value ReadVariable(Type variable, IR::Block* root_block) { boost::container::small_vector, 64> stack{ ReadState(nullptr), ReadState(root_block), @@ -189,7 +217,7 @@ class Pass { } else if (!block->IsSsaSealed()) { // Incomplete CFG IR::Inst* phi{&*block->PrependNewInst(block->begin(), IR::Opcode::Phi)}; - phi->SetFlags(is_thread_bit ? IR::Type::U1 : IR::TypeOf(UndefOpcode(variable))); + phi->SetFlags(IR::TypeOf(UndefOpcode(variable))); incomplete_phis[block].insert_or_assign(variable, phi); stack.back().result = IR::Value{&*phi}; @@ -202,7 +230,7 @@ class Pass { } else { // Break potential cycles with operandless phi IR::Inst* const phi{&*block->PrependNewInst(block->begin(), IR::Opcode::Phi)}; - phi->SetFlags(is_thread_bit ? IR::Type::U1 : IR::TypeOf(UndefOpcode(variable))); + phi->SetFlags(IR::TypeOf(UndefOpcode(variable))); WriteVariable(variable, block, IR::Value{phi}); @@ -251,9 +279,7 @@ class Pass { template IR::Value AddPhiOperands(Type variable, IR::Inst& phi, IR::Block* block) { for (IR::Block* const imm_pred : block->ImmPredecessors()) { - const bool is_thread_bit = - std::is_same_v && phi.Flags() == IR::Type::U1; - phi.AddPhiOperand(imm_pred, ReadVariable(variable, imm_pred, is_thread_bit)); + phi.AddPhiOperand(imm_pred, ReadVariable(variable, imm_pred)); } return TryRemoveTrivialPhi(phi, block, UndefOpcode(variable)); } @@ -301,7 +327,11 @@ class Pass { void VisitInst(Pass& pass, IR::Block* block, IR::Inst& inst) { const IR::Opcode opcode{inst.GetOpcode()}; switch (opcode) { - case IR::Opcode::SetThreadBitScalarReg: + case IR::Opcode::SetThreadBitScalarReg: { + const IR::ScalarReg reg{inst.Arg(0).ScalarReg()}; + pass.WriteVariable(ThreadBitScalar{reg}, block, inst.Arg(1)); + break; + } case IR::Opcode::SetScalarRegister: { const IR::ScalarReg reg{inst.Arg(0).ScalarReg()}; pass.WriteVariable(reg, block, inst.Arg(1)); @@ -330,11 +360,18 @@ void VisitInst(Pass& pass, IR::Block* block, IR::Inst& inst) { case IR::Opcode::SetVccHi: pass.WriteVariable(VccHiTag{}, block, inst.Arg(0)); break; - case IR::Opcode::GetThreadBitScalarReg: + case IR::Opcode::SetM0: + pass.WriteVariable(M0Tag{}, block, inst.Arg(0)); + break; + case IR::Opcode::GetThreadBitScalarReg: { + const IR::ScalarReg reg{inst.Arg(0).ScalarReg()}; + const IR::Value value = pass.ReadVariable(ThreadBitScalar{reg}, block); + inst.ReplaceUsesWith(value); + break; + } case IR::Opcode::GetScalarRegister: { const IR::ScalarReg reg{inst.Arg(0).ScalarReg()}; - const bool thread_bit = opcode == IR::Opcode::GetThreadBitScalarReg; - const IR::Value value = pass.ReadVariable(reg, block, thread_bit); + const IR::Value value = pass.ReadVariable(reg, block); inst.ReplaceUsesWith(value); break; } @@ -362,6 +399,9 @@ void VisitInst(Pass& pass, IR::Block* block, IR::Inst& inst) { case IR::Opcode::GetVccHi: inst.ReplaceUsesWith(pass.ReadVariable(VccHiTag{}, block)); break; + case IR::Opcode::GetM0: + inst.ReplaceUsesWith(pass.ReadVariable(M0Tag{}, block)); + break; default: break; } diff --git a/src/shader_recompiler/ir/program.h b/src/shader_recompiler/ir/program.h index f7abba64101..84a1a2d406c 100644 --- a/src/shader_recompiler/ir/program.h +++ b/src/shader_recompiler/ir/program.h @@ -5,9 +5,9 @@ #include #include "shader_recompiler/frontend/instruction.h" +#include "shader_recompiler/info.h" #include "shader_recompiler/ir/abstract_syntax_list.h" #include "shader_recompiler/ir/basic_block.h" -#include "shader_recompiler/runtime_info.h" namespace Shader::IR { diff --git a/src/shader_recompiler/ir/reg.h b/src/shader_recompiler/ir/reg.h index fba04f33e7d..4783d08e552 100644 --- a/src/shader_recompiler/ir/reg.h +++ b/src/shader_recompiler/ir/reg.h @@ -59,6 +59,7 @@ union TextureInstInfo { BitField<5, 1, u32> has_offset; BitField<6, 2, u32> gather_comp; BitField<8, 1, u32> has_derivatives; + BitField<9, 1, u32> is_array; }; union BufferInstInfo { diff --git a/src/shader_recompiler/ir/value.cpp b/src/shader_recompiler/ir/value.cpp index 9cbb9e7cf14..86e5dd141b8 100644 --- a/src/shader_recompiler/ir/value.cpp +++ b/src/shader_recompiler/ir/value.cpp @@ -81,6 +81,7 @@ bool Value::operator==(const Value& other) const { case Type::F64x2: case Type::F64x3: case Type::F64x4: + default: break; } UNREACHABLE_MSG("Invalid type {}", type); diff --git a/src/shader_recompiler/params.h b/src/shader_recompiler/params.h new file mode 100644 index 00000000000..0dce9a0f3ec --- /dev/null +++ b/src/shader_recompiler/params.h @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include "common/types.h" + +namespace Shader { + +/** + * Compilation parameters used to identify and locate a guest shader program. + */ +struct ShaderParams { + static constexpr u32 NumShaderUserData = 16; + + std::span user_data; + std::span code; + u64 hash; + + VAddr Base() const noexcept { + return reinterpret_cast(code.data()); + } +}; + +} // namespace Shader diff --git a/src/shader_recompiler/recompiler.cpp b/src/shader_recompiler/recompiler.cpp index dfcf9ed1b24..12dbc6c1b30 100644 --- a/src/shader_recompiler/recompiler.cpp +++ b/src/shader_recompiler/recompiler.cpp @@ -6,6 +6,7 @@ #include "shader_recompiler/frontend/structured_control_flow.h" #include "shader_recompiler/ir/passes/ir_passes.h" #include "shader_recompiler/ir/post_order.h" +#include "shader_recompiler/recompiler.h" namespace Shader { @@ -27,29 +28,32 @@ IR::BlockList GenerateBlocks(const IR::AbstractSyntaxList& syntax_list) { return blocks; } -IR::Program TranslateProgram(Common::ObjectPool& inst_pool, - Common::ObjectPool& block_pool, std::span token, - Info& info, const Profile& profile) { +IR::Program TranslateProgram(std::span code, Pools& pools, Info& info, + const RuntimeInfo& runtime_info, const Profile& profile) { // Ensure first instruction is expected. constexpr u32 token_mov_vcchi = 0xBEEB03FF; - ASSERT_MSG(token[0] == token_mov_vcchi, "First instruction is not s_mov_b32 vcc_hi, #imm"); + ASSERT_MSG(code[0] == token_mov_vcchi, "First instruction is not s_mov_b32 vcc_hi, #imm"); - Gcn::GcnCodeSlice slice(token.data(), token.data() + token.size()); + Gcn::GcnCodeSlice slice(code.data(), code.data() + code.size()); Gcn::GcnDecodeContext decoder; // Decode and save instructions IR::Program program{info}; - program.ins_list.reserve(token.size()); + program.ins_list.reserve(code.size()); while (!slice.atEnd()) { program.ins_list.emplace_back(decoder.decodeInstruction(slice)); } + // Clear any previous pooled data. + pools.ReleaseContents(); + // Create control flow graph Common::ObjectPool gcn_block_pool{64}; Gcn::CFG cfg{gcn_block_pool, program.ins_list}; // Structurize control flow graph and create program. - program.syntax_list = Shader::Gcn::BuildASL(inst_pool, block_pool, cfg, program.info, profile); + program.syntax_list = Shader::Gcn::BuildASL(pools.inst_pool, pools.block_pool, cfg, + program.info, runtime_info, profile); program.blocks = GenerateBlocks(program.syntax_list); program.post_order_blocks = Shader::IR::PostOrder(program.syntax_list.front()); @@ -63,7 +67,6 @@ IR::Program TranslateProgram(Common::ObjectPool& inst_pool, Shader::Optimization::IdentityRemovalPass(program.blocks); Shader::Optimization::DeadCodeEliminationPass(program); Shader::Optimization::CollectShaderInfoPass(program); - LOG_DEBUG(Render_Vulkan, "{}", Shader::IR::DumpProgram(program)); return program; } diff --git a/src/shader_recompiler/recompiler.h b/src/shader_recompiler/recompiler.h index 3a229518972..f8acf6c9ed4 100644 --- a/src/shader_recompiler/recompiler.h +++ b/src/shader_recompiler/recompiler.h @@ -10,10 +10,24 @@ namespace Shader { struct Profile; +struct RuntimeInfo; -[[nodiscard]] IR::Program TranslateProgram(Common::ObjectPool& inst_pool, - Common::ObjectPool& block_pool, - std::span code, Info& info, - const Profile& profile); +struct Pools { + static constexpr u32 InstPoolSize = 8192; + static constexpr u32 BlockPoolSize = 32; + + Common::ObjectPool inst_pool; + Common::ObjectPool block_pool; + + explicit Pools() : inst_pool{InstPoolSize}, block_pool{BlockPoolSize} {} + + void ReleaseContents() { + inst_pool.ReleaseContents(); + block_pool.ReleaseContents(); + } +}; + +[[nodiscard]] IR::Program TranslateProgram(std::span code, Pools& pools, Info& info, + const RuntimeInfo& runtime_info, const Profile& profile); } // namespace Shader diff --git a/src/shader_recompiler/runtime_info.h b/src/shader_recompiler/runtime_info.h index 77c57e94768..1bb065544b3 100644 --- a/src/shader_recompiler/runtime_info.h +++ b/src/shader_recompiler/runtime_info.h @@ -3,20 +3,14 @@ #pragma once -#include -#include +#include #include + #include "common/assert.h" #include "common/types.h" -#include "shader_recompiler/ir/attribute.h" -#include "shader_recompiler/ir/reg.h" -#include "shader_recompiler/ir/type.h" -#include "video_core/amdgpu/resource.h" namespace Shader { -static constexpr size_t NumUserDataRegs = 16; - enum class Stage : u32 { Fragment, Vertex, @@ -29,21 +23,18 @@ enum class Stage : u32 { constexpr u32 MaxStageTypes = 6; [[nodiscard]] constexpr Stage StageFromIndex(size_t index) noexcept { - return static_cast(static_cast(Stage::Vertex) + index); + return static_cast(index); } -enum class TextureType : u32 { - Color1D, - ColorArray1D, - Color2D, - ColorArray2D, - Color3D, - ColorCube, - Buffer, +enum class MrtSwizzle : u8 { + Identity = 0, + Alt = 1, + Reverse = 2, + ReverseAlt = 3, }; -constexpr u32 NUM_TEXTURE_TYPES = 7; +static constexpr u32 MaxColorBuffers = 8; -enum class VsOutput : u32 { +enum class VsOutput : u8 { None, PointSprite, EdgeFlag, @@ -70,211 +61,71 @@ enum class VsOutput : u32 { }; using VsOutputMap = std::array; -struct Info; - -struct BufferResource { - u32 sgpr_base; - u32 dword_offset; - IR::Type used_types; - AmdGpu::Buffer inline_cbuf; - bool is_instance_data{}; - bool is_written{}; - - bool IsStorage(AmdGpu::Buffer buffer) const noexcept { - static constexpr size_t MaxUboSize = 65536; - return buffer.GetSize() > MaxUboSize || is_written; - } - - constexpr AmdGpu::Buffer GetSharp(const Info& info) const noexcept; -}; -using BufferResourceList = boost::container::small_vector; - -struct TextureBufferResource { - u32 sgpr_base; - u32 dword_offset; - AmdGpu::NumberFormat nfmt; - bool is_written{}; - - constexpr AmdGpu::Buffer GetSharp(const Info& info) const noexcept; -}; -using TextureBufferResourceList = boost::container::small_vector; - -struct ImageResource { - u32 sgpr_base; - u32 dword_offset; - AmdGpu::ImageType type; - AmdGpu::NumberFormat nfmt; - bool is_storage; - bool is_depth; - bool is_atomic{}; - - constexpr AmdGpu::Image GetSharp(const Info& info) const noexcept; -}; -using ImageResourceList = boost::container::small_vector; - -struct SamplerResource { - u32 sgpr_base; - u32 dword_offset; - AmdGpu::Sampler inline_sampler{}; - u32 associated_image : 4; - u32 disable_aniso : 1; - - constexpr AmdGpu::Sampler GetSharp(const Info& info) const noexcept; -}; -using SamplerResourceList = boost::container::small_vector; - -struct PushData { - static constexpr size_t BufOffsetIndex = 2; +struct VertexRuntimeInfo { + boost::container::static_vector outputs; + bool emulate_depth_negative_one_to_one{}; - u32 step0; - u32 step1; - std::array buf_offsets; - - void AddOffset(u32 binding, u32 offset) { - ASSERT(offset < 256 && binding < buf_offsets.size()); - buf_offsets[binding] = offset; + bool operator==(const VertexRuntimeInfo& other) const noexcept { + return emulate_depth_negative_one_to_one == other.emulate_depth_negative_one_to_one; } }; -struct Info { - struct VsInput { - enum InstanceIdType : u8 { - None = 0, - OverStepRate0 = 1, - OverStepRate1 = 2, - Plain = 3, - }; - - AmdGpu::NumberFormat fmt; - u16 binding; - u16 num_components; - u8 sgpr_base; - u8 dword_offset; - InstanceIdType instance_step_rate; - s32 instance_data_buf; - }; - boost::container::static_vector vs_inputs{}; - +struct FragmentRuntimeInfo { struct PsInput { - u32 param_index; + u8 param_index; bool is_default; bool is_flat; - u32 default_value; - }; - boost::container::static_vector ps_inputs{}; - - struct AttributeFlags { - bool Get(IR::Attribute attrib, u32 comp = 0) const { - return flags[Index(attrib)] & (1 << comp); - } - - bool GetAny(IR::Attribute attrib) const { - return flags[Index(attrib)]; - } - - void Set(IR::Attribute attrib, u32 comp = 0) { - flags[Index(attrib)] |= (1 << comp); - } - - u32 NumComponents(IR::Attribute attrib) const { - return 4; - } + u8 default_value; - static size_t Index(IR::Attribute attrib) { - return static_cast(attrib); - } - - std::array flags; + auto operator<=>(const PsInput&) const noexcept = default; }; - AttributeFlags loads{}; - AttributeFlags stores{}; - boost::container::static_vector vs_outputs; + boost::container::static_vector inputs; + std::array mrt_swizzles; - s8 vertex_offset_sgpr = -1; - s8 instance_offset_sgpr = -1; - - BufferResourceList buffers; - TextureBufferResourceList texture_buffers; - ImageResourceList images; - SamplerResourceList samplers; + bool operator==(const FragmentRuntimeInfo& other) const noexcept { + return std::ranges::equal(mrt_swizzles, other.mrt_swizzles) && + std::ranges::equal(inputs, other.inputs); + } +}; - std::array workgroup_size{}; +struct ComputeRuntimeInfo { + u32 shared_memory_size; + std::array workgroup_size; std::array tgid_enable; - u32 num_user_data; - u32 num_input_vgprs; - std::span user_data; - Stage stage; - - uintptr_t pgm_base{}; - u64 pgm_hash{}; - u32 shared_memory_size{}; - bool has_storage_images{}; - bool has_image_buffers{}; - bool has_texel_buffers{}; - bool has_discard{}; - bool has_image_gather{}; - bool has_image_query{}; - bool uses_lane_id{}; - bool uses_group_quad{}; - bool uses_shared{}; - bool uses_fp16{}; - bool uses_step_rates{}; - bool translation_failed{}; // indicates that shader has unsupported instructions - - template - T ReadUd(u32 ptr_index, u32 dword_offset) const noexcept { - T data; - const u32* base = user_data.data(); - if (ptr_index != IR::NumScalarRegs) { - std::memcpy(&base, &user_data[ptr_index], sizeof(base)); - } - std::memcpy(&data, base + dword_offset, sizeof(T)); - return data; - } - - size_t NumBindings() const noexcept { - return buffers.size() + texture_buffers.size() + images.size() + samplers.size(); + bool operator==(const ComputeRuntimeInfo& other) const noexcept { + return workgroup_size == other.workgroup_size && tgid_enable == other.tgid_enable; } +}; - [[nodiscard]] std::pair GetDrawOffsets() const noexcept { - u32 vertex_offset = 0; - u32 instance_offset = 0; - if (vertex_offset_sgpr != -1) { - vertex_offset = user_data[vertex_offset_sgpr]; - } - if (instance_offset_sgpr != -1) { - instance_offset = user_data[instance_offset_sgpr]; +/** + * Stores information relevant to shader compilation sourced from liverpool registers. + * It may potentially differ with the same shader module so must be checked. + * It's also possible to store any other custom information that needs to be part of shader key. + */ +struct RuntimeInfo { + Stage stage; + u32 num_user_data; + u32 num_input_vgprs; + u32 num_allocated_vgprs; + VertexRuntimeInfo vs_info; + FragmentRuntimeInfo fs_info; + ComputeRuntimeInfo cs_info; + + RuntimeInfo(Stage stage_) : stage{stage_} {} + + bool operator==(const RuntimeInfo& other) const noexcept { + switch (stage) { + case Stage::Fragment: + return fs_info == other.fs_info; + case Stage::Vertex: + return vs_info == other.vs_info; + case Stage::Compute: + return cs_info == other.cs_info; + default: + return true; } - return {vertex_offset, instance_offset}; } }; -constexpr AmdGpu::Buffer BufferResource::GetSharp(const Info& info) const noexcept { - return inline_cbuf ? inline_cbuf : info.ReadUd(sgpr_base, dword_offset); -} - -constexpr AmdGpu::Buffer TextureBufferResource::GetSharp(const Info& info) const noexcept { - return info.ReadUd(sgpr_base, dword_offset); -} - -constexpr AmdGpu::Image ImageResource::GetSharp(const Info& info) const noexcept { - return info.ReadUd(sgpr_base, dword_offset); -} - -constexpr AmdGpu::Sampler SamplerResource::GetSharp(const Info& info) const noexcept { - return inline_sampler ? inline_sampler : info.ReadUd(sgpr_base, dword_offset); -} - } // namespace Shader - -template <> -struct fmt::formatter { - constexpr auto parse(format_parse_context& ctx) { - return ctx.begin(); - } - auto format(const Shader::Stage stage, format_context& ctx) const { - constexpr static std::array names = {"fs", "vs", "gs", "es", "hs", "ls", "cs"}; - return fmt::format_to(ctx.out(), "{}", names[static_cast(stage)]); - } -}; diff --git a/src/video_core/renderer_vulkan/vk_shader_cache.h b/src/shader_recompiler/specialization.h similarity index 51% rename from src/video_core/renderer_vulkan/vk_shader_cache.h rename to src/shader_recompiler/specialization.h index 191e1b08ce6..0a3a696bc26 100644 --- a/src/video_core/renderer_vulkan/vk_shader_cache.h +++ b/src/shader_recompiler/specialization.h @@ -4,18 +4,12 @@ #pragma once #include -#include -#include -#include "common/object_pool.h" -#include "shader_recompiler/ir/basic_block.h" -#include "shader_recompiler/profile.h" -#include "shader_recompiler/runtime_info.h" -#include "video_core/amdgpu/liverpool.h" -#include "video_core/renderer_vulkan/vk_common.h" -namespace Vulkan { +#include "common/types.h" +#include "shader_recompiler/backend/bindings.h" +#include "shader_recompiler/info.h" -class Instance; +namespace Shader { struct BufferSpecialization { u16 stride : 14; @@ -25,43 +19,38 @@ struct BufferSpecialization { }; struct TextureBufferSpecialization { - bool is_integer; + bool is_integer = false; auto operator<=>(const TextureBufferSpecialization&) const = default; }; struct ImageSpecialization { - AmdGpu::ImageType type; - bool is_integer; + AmdGpu::ImageType type = AmdGpu::ImageType::Color2D; + bool is_integer = false; auto operator<=>(const ImageSpecialization&) const = default; }; +/** + * Alongside runtime information, this structure also checks bound resources + * for compatibility. Can be used as a key for storing shader permutations. + * Is separate from runtime information, because resource layout can only be deduced + * after the first compilation of a module. + */ struct StageSpecialization { - static constexpr size_t MaxStageResources = 32; + static constexpr size_t MaxStageResources = 64; const Shader::Info* info; + RuntimeInfo runtime_info; std::bitset bitset{}; boost::container::small_vector buffers; boost::container::small_vector tex_buffers; - boost::container::small_vector images; - u32 start_binding{}; + boost::container::small_vector images; + Backend::Bindings start{}; - void ForEachSharp(u32& binding, auto& spec_list, auto& desc_list, auto&& func) { - for (const auto& desc : desc_list) { - auto& spec = spec_list.emplace_back(); - const auto sharp = desc.GetSharp(*info); - if (!sharp) { - binding++; - continue; - } - bitset.set(binding++); - func(spec, desc, sharp); - } - } - - StageSpecialization(const Shader::Info& info_, u32 start_binding_) - : info{&info_}, start_binding{start_binding_} { + explicit StageSpecialization(const Shader::Info& info_, RuntimeInfo runtime_info_, + Backend::Bindings start_) + : info{&info_}, runtime_info{runtime_info_}, start{start_} { u32 binding{}; ForEachSharp(binding, buffers, info->buffers, [](auto& spec, const auto& desc, AmdGpu::Buffer sharp) { @@ -74,13 +63,30 @@ struct StageSpecialization { }); ForEachSharp(binding, images, info->images, [](auto& spec, const auto& desc, AmdGpu::Image sharp) { - spec.type = sharp.GetType(); + spec.type = sharp.IsPartialCubemap() ? AmdGpu::ImageType::Color2DArray + : sharp.GetType(); spec.is_integer = AmdGpu::IsInteger(sharp.GetNumberFmt()); }); } + void ForEachSharp(u32& binding, auto& spec_list, auto& desc_list, auto&& func) { + for (const auto& desc : desc_list) { + auto& spec = spec_list.emplace_back(); + const auto sharp = desc.GetSharp(*info); + if (!sharp) { + binding++; + continue; + } + bitset.set(binding++); + func(spec, desc, sharp); + } + } + bool operator==(const StageSpecialization& other) const { - if (start_binding != other.start_binding) { + if (start != other.start) { + return false; + } + if (runtime_info != other.runtime_info) { return false; } u32 binding{}; @@ -103,54 +109,4 @@ struct StageSpecialization { } }; -struct Program { - struct Module { - vk::ShaderModule module; - StageSpecialization spec; - }; - - Shader::Info info; - boost::container::small_vector modules; - - explicit Program(const Shader::Info& info_) : info{info_} {} -}; - -struct GuestProgram { - Shader::Stage stage; - std::span user_data; - std::span code; - u64 hash; - - explicit GuestProgram(const auto* pgm, Shader::Stage stage_) - : stage{stage_}, user_data{pgm->user_data}, code{pgm->Code()} { - const auto* bininfo = AmdGpu::Liverpool::GetBinaryInfo(*pgm); - hash = bininfo->shader_hash; - } -}; - -class ShaderCache { -public: - explicit ShaderCache(const Instance& instance, AmdGpu::Liverpool* liverpool); - ~ShaderCache() = default; - - std::tuple GetProgram(const GuestProgram& pgm, - u32& binding); - -private: - void DumpShader(std::span code, u64 hash, Shader::Stage stage, size_t perm_idx, - std::string_view ext); - vk::ShaderModule CompileModule(Shader::Info& info, std::span code, size_t perm_idx, - u32& binding); - Program* CreateProgram(const GuestProgram& pgm, u32& binding); - -private: - const Instance& instance; - AmdGpu::Liverpool* liverpool; - Shader::Profile profile{}; - tsl::robin_map program_cache; - Common::ObjectPool inst_pool; - Common::ObjectPool block_pool; - Common::ObjectPool program_pool; -}; - -} // namespace Vulkan +} // namespace Shader diff --git a/src/video_core/amdgpu/liverpool.cpp b/src/video_core/amdgpu/liverpool.cpp index 1889ef8ae7c..b8e2ac9880f 100644 --- a/src/video_core/amdgpu/liverpool.cpp +++ b/src/video_core/amdgpu/liverpool.cpp @@ -20,6 +20,20 @@ static const char* acb_task_name{"ACB_TASK"}; std::array Liverpool::ConstantEngine::constants_heap; +static std::span NextPacket(std::span span, size_t offset) { + if (offset > span.size()) { + LOG_ERROR( + Lib_GnmDriver, + ": packet length exceeds remaining submission size. Packet dword count={}, remaining " + "submission dwords={}", + offset, span.size()); + // Return empty subspan so check for next packet bails out + return {}; + } + + return span.subspan(offset); +} + Liverpool::Liverpool() { process_thread = std::jthread{std::bind_front(&Liverpool::Process, this)}; } @@ -150,7 +164,7 @@ Liverpool::Task Liverpool::ProcessCeUpdate(std::span ccb) { UNREACHABLE_MSG("Unknown PM4 type 3 opcode {:#x} with count {}", static_cast(opcode), count); } - ccb = ccb.subspan(header->type3.NumWords() + 1); + ccb = NextPacket(ccb, header->type3.NumWords() + 1); } TracyFiberLeave; @@ -184,7 +198,7 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::spantype3.NumWords(); @@ -207,11 +221,15 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::spanheader.count.Value() * 2; const std::string_view label{reinterpret_cast(&nop->data_block[1]), marker_sz}; - rasterizer->ScopeMarkerBegin(label); + if (rasterizer) { + rasterizer->ScopeMarkerBegin(label); + } break; } case PM4CmdNop::PayloadType::DebugMarkerPop: { - rasterizer->ScopeMarkerEnd(); + if (rasterizer) { + rasterizer->ScopeMarkerEnd(); + } break; } default: @@ -333,7 +351,6 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span(header); rasterizer->ScopeMarkerBegin(fmt::format("dcb:{}:DrawIndex2", cmd_address)); - rasterizer->Breadcrumb(u64(cmd_address)); rasterizer->Draw(true); rasterizer->ScopeMarkerEnd(); } @@ -349,7 +366,6 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span(header); rasterizer->ScopeMarkerBegin( fmt::format("dcb:{}:DrawIndexOffset2", cmd_address)); - rasterizer->Breadcrumb(u64(cmd_address)); rasterizer->Draw(true, draw_index_off->index_offset); rasterizer->ScopeMarkerEnd(); } @@ -362,12 +378,39 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span(header); rasterizer->ScopeMarkerBegin(fmt::format("dcb:{}:DrawIndexAuto", cmd_address)); - rasterizer->Breadcrumb(u64(cmd_address)); rasterizer->Draw(false); rasterizer->ScopeMarkerEnd(); } break; } + case PM4ItOpcode::DrawIndirect: { + const auto* draw_indirect = reinterpret_cast(header); + const auto offset = draw_indirect->data_offset; + const auto ib_address = mapped_queues[GfxQueueId].indirect_args_addr; + const auto size = sizeof(PM4CmdDrawIndirect::DrawInstancedArgs); + if (rasterizer) { + const auto cmd_address = reinterpret_cast(header); + rasterizer->ScopeMarkerBegin(fmt::format("dcb:{}:DrawIndirect", cmd_address)); + rasterizer->DrawIndirect(false, ib_address, offset, size); + rasterizer->ScopeMarkerEnd(); + } + break; + } + case PM4ItOpcode::DrawIndexIndirect: { + const auto* draw_index_indirect = + reinterpret_cast(header); + const auto offset = draw_index_indirect->data_offset; + const auto ib_address = mapped_queues[GfxQueueId].indirect_args_addr; + const auto size = sizeof(PM4CmdDrawIndexIndirect::DrawIndexInstancedArgs); + if (rasterizer) { + const auto cmd_address = reinterpret_cast(header); + rasterizer->ScopeMarkerBegin( + fmt::format("dcb:{}:DrawIndexIndirect", cmd_address)); + rasterizer->DrawIndirect(true, ib_address, offset, size); + rasterizer->ScopeMarkerEnd(); + } + break; + } case PM4ItOpcode::DispatchDirect: { const auto* dispatch_direct = reinterpret_cast(header); regs.cs_program.dim_x = dispatch_direct->dim_x; @@ -377,7 +420,6 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span(header); rasterizer->ScopeMarkerBegin(fmt::format("dcb:{}:Dispatch", cmd_address)); - rasterizer->Breadcrumb(u64(cmd_address)); rasterizer->DispatchDirect(); rasterizer->ScopeMarkerEnd(); } @@ -393,7 +435,6 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span(header); rasterizer->ScopeMarkerBegin( fmt::format("dcb:{}:DispatchIndirect", cmd_address)); - rasterizer->Breadcrumb(u64(cmd_address)); rasterizer->DispatchIndirect(ib_address, offset, size); rasterizer->ScopeMarkerEnd(); } @@ -428,6 +469,14 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span(header); event_eos->SignalFence(); + if (event_eos->command == PM4CmdEventWriteEos::Command::GdsStore) { + ASSERT(event_eos->size == 1); + if (rasterizer) { + rasterizer->Finish(); + const u32 value = rasterizer->ReadDataFromGds(event_eos->gds_index); + *event_eos->Address() = value; + } + } break; } case PM4ItOpcode::EventWriteEop: { @@ -437,6 +486,9 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span(header); + if (dma_data->src_sel == DmaDataSrc::Data && dma_data->dst_sel == DmaDataDst::Gds) { + rasterizer->InlineDataToGds(dma_data->dst_addr_lo, dma_data->data); + } break; } case PM4ItOpcode::WriteData: { @@ -491,13 +543,16 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::spanCpSync(); + } break; } default: UNREACHABLE_MSG("Unknown PM4 type 3 opcode {:#x} with count {}", static_cast(opcode), count); } - dcb = dcb.subspan(header->type3.NumWords() + 1); + dcb = NextPacket(dcb, header->type3.NumWords() + 1); break; } } @@ -560,7 +615,6 @@ Liverpool::Task Liverpool::ProcessCompute(std::span acb, int vqid) { if (rasterizer && (regs.cs_program.dispatch_initiator & 1)) { const auto cmd_address = reinterpret_cast(header); rasterizer->ScopeMarkerBegin(fmt::format("acb[{}]:{}:Dispatch", vqid, cmd_address)); - rasterizer->Breadcrumb(u64(cmd_address)); rasterizer->DispatchDirect(); rasterizer->ScopeMarkerEnd(); } @@ -599,7 +653,7 @@ Liverpool::Task Liverpool::ProcessCompute(std::span acb, int vqid) { static_cast(opcode), count); } - acb = acb.subspan(header->type3.NumWords() + 1); + acb = NextPacket(acb, header->type3.NumWords() + 1); } TracyFiberLeave; @@ -609,6 +663,12 @@ std::pair, std::span> Liverpool::CopyCmdBuffers( std::span dcb, std::span ccb) { auto& queue = mapped_queues[GfxQueueId]; + // std::vector resize can invalidate spans for commands in flight + ASSERT_MSG(queue.dcb_buffer.capacity() >= queue.dcb_buffer_offset + dcb.size(), + "dcb copy buffer out of reserved space"); + ASSERT_MSG(queue.ccb_buffer.capacity() >= queue.ccb_buffer_offset + ccb.size(), + "ccb copy buffer out of reserved space"); + queue.dcb_buffer.resize( std::max(queue.dcb_buffer.size(), queue.dcb_buffer_offset + dcb.size())); queue.ccb_buffer.resize( diff --git a/src/video_core/amdgpu/liverpool.h b/src/video_core/amdgpu/liverpool.h index 7f262e1f443..411b25ed163 100644 --- a/src/video_core/amdgpu/liverpool.h +++ b/src/video_core/amdgpu/liverpool.h @@ -18,6 +18,7 @@ #include "common/polyfill_thread.h" #include "common/types.h" #include "common/unique_function.h" +#include "shader_recompiler/params.h" #include "video_core/amdgpu/pixel_format.h" #include "video_core/amdgpu/resource.h" @@ -171,6 +172,15 @@ struct Liverpool { return bininfo; } + static constexpr Shader::ShaderParams GetParams(const auto& sh) { + auto* bininfo = GetBinaryInfo(sh); + return { + .user_data = sh.user_data, + .code = sh.Code(), + .hash = bininfo->shader_hash, + }; + } + union PsInputControl { u32 raw; BitField<0, 5, u32> input_offset; @@ -463,6 +473,11 @@ struct Liverpool { CullMode CullingMode() const { return static_cast(cull_front | cull_back << 1); } + + bool NeedsBias() const { + return enable_polygon_offset_back || enable_polygon_offset_front || + enable_polygon_offset_para; + } }; union VsOutputConfig { @@ -496,6 +511,11 @@ struct Liverpool { u32 GetMask(int buf_id) const { return (raw >> (buf_id * 4)) & 0xfu; } + + void SetMask(int buf_id, u32 mask) { + raw &= ~(0xf << (buf_id * 4)); + raw |= (mask << (buf_id * 4)); + } }; struct IndexBufferBase { @@ -1078,6 +1098,15 @@ struct Liverpool { submit_cv.notify_one(); } + void reserveCopyBufferSpace() { + GpuQueue& gfx_queue = mapped_queues[GfxQueueId]; + std::scoped_lock lk(gfx_queue.m_access); + + constexpr size_t GfxReservedSize = 2_MB >> 2; + gfx_queue.ccb_buffer.reserve(GfxReservedSize); + gfx_queue.dcb_buffer.reserve(GfxReservedSize); + } + private: struct Task { struct promise_type { diff --git a/src/video_core/amdgpu/pm4_cmds.h b/src/video_core/amdgpu/pm4_cmds.h index 50e4c93a1ed..5d9c0f9eee4 100644 --- a/src/video_core/amdgpu/pm4_cmds.h +++ b/src/video_core/amdgpu/pm4_cmds.h @@ -187,6 +187,11 @@ struct PM4CmdSetData { BitField<28, 4, u32> index; ///< Index for UCONFIG/CONTEXT on CI+ ///< Program to zero for other opcodes and on SI }; + u32 data[0]; + + [[nodiscard]] u32 Size() const { + return header.count << 2u; + } template static constexpr u32* SetContextReg(u32* cmdbuf, Args... data) { @@ -253,20 +258,6 @@ struct PM4CmdDrawIndexAuto { u32 draw_initiator; }; -struct PM4CmdDrawIndirect { - PM4Type3Header header; ///< header - u32 data_offset; ///< DWORD aligned offset - union { - u32 dw2; - BitField<0, 16, u32> base_vtx_loc; ///< base vertex location - }; - union { - u32 dw3; - BitField<0, 16, u32> start_inst_loc; ///< start instance location - }; - u32 draw_initiator; ///< Draw Initiator Register -}; - enum class DataSelect : u32 { None = 0, Data32Low = 1, @@ -364,6 +355,27 @@ struct PM4CmdEventWriteEop { } }; +struct PM4CmdAcquireMem { + PM4Type3Header header; + u32 cp_coher_cntl; + u32 cp_coher_size_lo; + u32 cp_coher_size_hi; + u32 cp_coher_base_lo; + u32 cp_coher_base_hi; + u32 poll_interval; +}; + +enum class DmaDataDst : u32 { + Memory = 0, + Gds = 1, +}; + +enum class DmaDataSrc : u32 { + Memory = 0, + Gds = 1, + Data = 2, +}; + struct PM4DmaData { PM4Type3Header header; union { @@ -371,11 +383,11 @@ struct PM4DmaData { BitField<12, 1, u32> src_atc; BitField<13, 2, u32> src_cache_policy; BitField<15, 1, u32> src_volatile; - BitField<20, 2, u32> dst_sel; + BitField<20, 2, DmaDataDst> dst_sel; BitField<24, 1, u32> dst_atc; BitField<25, 2, u32> dst_cache_policy; BitField<27, 1, u32> dst_volatile; - BitField<29, 2, u32> src_sel; + BitField<29, 2, DmaDataSrc> src_sel; BitField<31, 1, u32> cp_sync; }; union { @@ -470,6 +482,10 @@ struct PM4CmdWriteData { }; u32 data[0]; + u32 Size() const { + return (header.count.Value() - 2) * 4; + } + template void Address(T addr) { addr64 = static_cast(addr); @@ -484,7 +500,7 @@ struct PM4CmdWriteData { struct PM4CmdEventWriteEos { enum class Command : u32 { GdsStore = 1u, - SingalFence = 2u, + SignalFence = 2u, }; PM4Type3Header header; @@ -516,13 +532,17 @@ struct PM4CmdEventWriteEos { } void SignalFence() const { - switch (command.Value()) { - case Command::SingalFence: { + const auto cmd = command.Value(); + switch (cmd) { + case Command::SignalFence: { *Address() = DataDWord(); break; } + case Command::GdsStore: { + break; + } default: { - UNREACHABLE(); + UNREACHABLE_MSG("Unknown command {}", u32(cmd)); } } } @@ -740,4 +760,51 @@ struct PM4CmdDispatchIndirect { u32 dispatch_initiator; ///< Dispatch Initiator Register }; +struct PM4CmdDrawIndirect { + struct DrawInstancedArgs { + u32 vertex_count_per_instance; + u32 instance_count; + u32 start_vertex_location; + u32 start_instance_location; + }; + + PM4Type3Header header; ///< header + u32 data_offset; ///< Byte aligned offset where the required data structure starts + union { + u32 dw2; + BitField<0, 16, u32> base_vtx_loc; ///< Offset where the CP will write the + ///< BaseVertexLocation it fetched from memory + }; + union { + u32 dw3; + BitField<0, 16, u32> start_inst_loc; ///< Offset where the CP will write the + ///< StartInstanceLocation it fetched from memory + }; + u32 draw_initiator; ///< Draw Initiator Register +}; + +struct PM4CmdDrawIndexIndirect { + struct DrawIndexInstancedArgs { + u32 index_count_per_instance; + u32 instance_count; + u32 start_index_location; + u32 base_vertex_location; + u32 start_instance_location; + }; + + PM4Type3Header header; ///< header + u32 data_offset; ///< Byte aligned offset where the required data structure starts + union { + u32 dw2; + BitField<0, 16, u32> base_vtx_loc; ///< Offset where the CP will write the + ///< BaseVertexLocation it fetched from memory + }; + union { // NOTE: this one is undocumented in AMD spec, but Gnm driver writes this field + u32 dw3; + BitField<0, 16, u32> start_inst_loc; ///< Offset where the CP will write the + ///< StartInstanceLocation it fetched from memory + }; + u32 draw_initiator; ///< Draw Initiator Register +}; + } // namespace AmdGpu diff --git a/src/video_core/amdgpu/pm4_opcodes.h b/src/video_core/amdgpu/pm4_opcodes.h index 83c1deaa4e9..4b853138b20 100644 --- a/src/video_core/amdgpu/pm4_opcodes.h +++ b/src/video_core/amdgpu/pm4_opcodes.h @@ -32,6 +32,7 @@ enum class PM4ItOpcode : u32 { NumInstances = 0x2F, DrawIndexMultiAuto = 0x30, IndirectBufferConst = 0x33, + StrmoutBufferUpdate = 0x34, DrawIndexOffset2 = 0x35, WriteData = 0x37, DrawIndexIndirectMulti = 0x38, @@ -52,6 +53,7 @@ enum class PM4ItOpcode : u32 { DmaData = 0x50, ContextRegRmw = 0x51, AcquireMem = 0x58, + Rewind = 0x59, LoadShReg = 0x5F, LoadConfigReg = 0x60, LoadContextReg = 0x61, @@ -60,7 +62,9 @@ enum class PM4ItOpcode : u32 { SetContextRegIndirect = 0x73, SetShReg = 0x76, SetShRegOffset = 0x77, + SetQueueReg = 0x78, SetUconfigReg = 0x79, + LoadConstRam = 0x80, WriteConstRam = 0x81, DumpConstRam = 0x83, IncrementCeCounter = 0x84, diff --git a/src/video_core/amdgpu/resource.h b/src/video_core/amdgpu/resource.h index b85a3788b1d..fc572a04b9b 100644 --- a/src/video_core/amdgpu/resource.h +++ b/src/video_core/amdgpu/resource.h @@ -66,7 +66,7 @@ struct Buffer { } u32 GetStride() const noexcept { - return stride == 0 ? 1U : stride; + return stride; } u32 NumDwords() const noexcept { @@ -74,7 +74,7 @@ struct Buffer { } u32 GetSize() const noexcept { - return GetStride() * num_records; + return stride == 0 ? num_records : (stride * num_records); } }; static_assert(sizeof(Buffer) == 16); // 128bits @@ -176,6 +176,18 @@ struct Image { u64 lod_hw_cnt_en : 1; u64 : 43; + static constexpr Image Null() { + Image image{}; + image.data_format = u64(DataFormat::Format8_8_8_8); + image.dst_sel_x = 4; + image.dst_sel_y = 5; + image.dst_sel_z = 6; + image.dst_sel_w = 7; + image.tiling_index = u64(TilingMode::Texture_MicroTiled); + image.type = u64(ImageType::Color2D); + return image; + } + bool Valid() const { return (type & 0x8u) != 0; } @@ -226,10 +238,15 @@ struct Image { return pitch + 1; } - u32 NumLayers() const { + u32 NumLayers(bool is_array) const { u32 slices = GetType() == ImageType::Color3D ? 1 : depth + 1; if (GetType() == ImageType::Cube) { - slices *= 6; + if (is_array) { + slices = last_array + 1; + ASSERT(slices % 6 == 0); + } else { + slices = 6; + } } if (pow2pad) { slices = std::bit_ceil(slices); @@ -270,6 +287,11 @@ struct Image { bool IsTiled() const { return GetTilingMode() != TilingMode::Display_Linear; } + + bool IsPartialCubemap() const { + const auto viewed_slice = last_array - base_array + 1; + return GetType() == ImageType::Cube && viewed_slice < 6; + } }; static_assert(sizeof(Image) == 32); // 256bits diff --git a/src/video_core/buffer_cache/buffer.cpp b/src/video_core/buffer_cache/buffer.cpp index 372b6f74549..70295803440 100644 --- a/src/video_core/buffer_cache/buffer.cpp +++ b/src/video_core/buffer_cache/buffer.cpp @@ -9,7 +9,10 @@ #include "video_core/renderer_vulkan/vk_platform.h" #include "video_core/renderer_vulkan/vk_scheduler.h" +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnullability-completeness" #include +#pragma GCC diagnostic pop namespace VideoCore { @@ -88,10 +91,10 @@ void UniqueBuffer::Create(const vk::BufferCreateInfo& buffer_ci, MemoryUsage usa buffer = vk::Buffer{unsafe_buffer}; } -Buffer::Buffer(const Vulkan::Instance& instance_, MemoryUsage usage_, VAddr cpu_addr_, - vk::BufferUsageFlags flags, u64 size_bytes_) - : cpu_addr{cpu_addr_}, size_bytes{size_bytes_}, instance{&instance_}, usage{usage_}, - buffer{instance->GetDevice(), instance->GetAllocator()} { +Buffer::Buffer(const Vulkan::Instance& instance_, Vulkan::Scheduler& scheduler_, MemoryUsage usage_, + VAddr cpu_addr_, vk::BufferUsageFlags flags, u64 size_bytes_) + : cpu_addr{cpu_addr_}, size_bytes{size_bytes_}, instance{&instance_}, scheduler{&scheduler_}, + usage{usage_}, buffer{instance->GetDevice(), instance->GetAllocator()} { // Create buffer object. const vk::BufferCreateInfo buffer_ci = { .size = size_bytes, @@ -114,13 +117,6 @@ Buffer::Buffer(const Vulkan::Instance& instance_, MemoryUsage usage_, VAddr cpu_ vk::BufferView Buffer::View(u32 offset, u32 size, bool is_written, AmdGpu::DataFormat dfmt, AmdGpu::NumberFormat nfmt) { - const auto it{std::ranges::find_if(views, [=](const BufferView& view) { - return offset == view.offset && size == view.size && is_written == view.is_written && - dfmt == view.dfmt && nfmt == view.nfmt; - })}; - if (it != views.end()) { - return *it->handle; - } const vk::BufferUsageFlags2CreateInfoKHR usage_flags = { .usage = is_written ? vk::BufferUsageFlagBits2KHR::eStorageTexelBuffer : vk::BufferUsageFlagBits2KHR::eUniformTexelBuffer, @@ -132,23 +128,18 @@ vk::BufferView Buffer::View(u32 offset, u32 size, bool is_written, AmdGpu::DataF .offset = offset, .range = size, }; - views.push_back({ - .offset = offset, - .size = size, - .is_written = is_written, - .dfmt = dfmt, - .nfmt = nfmt, - .handle = instance->GetDevice().createBufferViewUnique(view_ci), - }); - return *views.back().handle; + const auto view = instance->GetDevice().createBufferView(view_ci); + scheduler->DeferOperation( + [view, device = instance->GetDevice()] { device.destroyBufferView(view); }); + return view; } constexpr u64 WATCHES_INITIAL_RESERVE = 0x4000; constexpr u64 WATCHES_RESERVE_CHUNK = 0x1000; -StreamBuffer::StreamBuffer(const Vulkan::Instance& instance, Vulkan::Scheduler& scheduler_, +StreamBuffer::StreamBuffer(const Vulkan::Instance& instance, Vulkan::Scheduler& scheduler, MemoryUsage usage, u64 size_bytes) - : Buffer{instance, usage, 0, AllFlags, size_bytes}, scheduler{scheduler_} { + : Buffer{instance, scheduler, usage, 0, AllFlags, size_bytes} { ReserveWatches(current_watches, WATCHES_INITIAL_RESERVE); ReserveWatches(previous_watches, WATCHES_INITIAL_RESERVE); const auto device = instance.GetDevice(); @@ -203,7 +194,7 @@ void StreamBuffer::Commit() { auto& watch = current_watches[current_watch_cursor++]; watch.upper_bound = offset; - watch.tick = scheduler.CurrentTick(); + watch.tick = scheduler->CurrentTick(); } void StreamBuffer::ReserveWatches(std::vector& watches, std::size_t grow_size) { @@ -217,7 +208,7 @@ void StreamBuffer::WaitPendingOperations(u64 requested_upper_bound) { while (requested_upper_bound > wait_bound && wait_cursor < *invalidation_mark) { auto& watch = previous_watches[wait_cursor]; wait_bound = watch.upper_bound; - scheduler.Wait(watch.tick); + scheduler->Wait(watch.tick); ++wait_cursor; } } diff --git a/src/video_core/buffer_cache/buffer.h b/src/video_core/buffer_cache/buffer.h index 26d48eaefe7..403d4ed85c4 100644 --- a/src/video_core/buffer_cache/buffer.h +++ b/src/video_core/buffer_cache/buffer.h @@ -73,8 +73,9 @@ struct UniqueBuffer { class Buffer { public: - explicit Buffer(const Vulkan::Instance& instance, MemoryUsage usage, VAddr cpu_addr_, - vk::BufferUsageFlags flags, u64 size_bytes_); + explicit Buffer(const Vulkan::Instance& instance, Vulkan::Scheduler& scheduler, + MemoryUsage usage, VAddr cpu_addr_, vk::BufferUsageFlags flags, + u64 size_bytes_); Buffer& operator=(const Buffer&) = delete; Buffer(const Buffer&) = delete; @@ -118,6 +119,25 @@ class Buffer { return buffer; } + std::optional GetBarrier(vk::AccessFlagBits2 dst_acess_mask, + vk::PipelineStageFlagBits2 dst_stage) { + if (dst_acess_mask == access_mask && stage == dst_stage) { + return {}; + } + + auto barrier = vk::BufferMemoryBarrier2{ + .srcStageMask = stage, + .srcAccessMask = access_mask, + .dstStageMask = dst_stage, + .dstAccessMask = dst_acess_mask, + .buffer = buffer.buffer, + .size = size_bytes, + }; + access_mask = dst_acess_mask; + stage = dst_stage; + return barrier; + } + public: VAddr cpu_addr = 0; bool is_picked{}; @@ -125,18 +145,12 @@ class Buffer { int stream_score = 0; size_t size_bytes = 0; std::span mapped_data; - const Vulkan::Instance* instance{}; + const Vulkan::Instance* instance; + Vulkan::Scheduler* scheduler; MemoryUsage usage; UniqueBuffer buffer; - struct BufferView { - u32 offset; - u32 size; - bool is_written; - AmdGpu::DataFormat dfmt; - AmdGpu::NumberFormat nfmt; - vk::UniqueBufferView handle; - }; - std::vector views; + vk::AccessFlagBits2 access_mask{vk::AccessFlagBits2::eNone}; + vk::PipelineStageFlagBits2 stage{vk::PipelineStageFlagBits2::eNone}; }; class StreamBuffer : public Buffer { @@ -175,7 +189,6 @@ class StreamBuffer : public Buffer { void WaitPendingOperations(u64 requested_upper_bound); private: - Vulkan::Scheduler& scheduler; u64 offset{}; u64 mapped_size{}; std::vector current_watches; diff --git a/src/video_core/buffer_cache/buffer_cache.cpp b/src/video_core/buffer_cache/buffer_cache.cpp index 0151f2c134d..caffee6ba0b 100644 --- a/src/video_core/buffer_cache/buffer_cache.cpp +++ b/src/video_core/buffer_cache/buffer_cache.cpp @@ -4,26 +4,38 @@ #include #include "common/alignment.h" #include "common/scope_exit.h" -#include "shader_recompiler/runtime_info.h" +#include "shader_recompiler/info.h" #include "video_core/amdgpu/liverpool.h" #include "video_core/buffer_cache/buffer_cache.h" #include "video_core/renderer_vulkan/liverpool_to_vk.h" #include "video_core/renderer_vulkan/vk_instance.h" #include "video_core/renderer_vulkan/vk_scheduler.h" +#include "video_core/texture_cache/texture_cache.h" namespace VideoCore { -static constexpr size_t StagingBufferSize = 512_MB; +static constexpr size_t NumVertexBuffers = 32; +static constexpr size_t GdsBufferSize = 64_KB; +static constexpr size_t StagingBufferSize = 1_GB; static constexpr size_t UboStreamBufferSize = 64_MB; BufferCache::BufferCache(const Vulkan::Instance& instance_, Vulkan::Scheduler& scheduler_, - const AmdGpu::Liverpool* liverpool_, PageManager& tracker_) - : instance{instance_}, scheduler{scheduler_}, liverpool{liverpool_}, tracker{tracker_}, + const AmdGpu::Liverpool* liverpool_, TextureCache& texture_cache_, + PageManager& tracker_) + : instance{instance_}, scheduler{scheduler_}, liverpool{liverpool_}, + texture_cache{texture_cache_}, tracker{tracker_}, staging_buffer{instance, scheduler, MemoryUsage::Upload, StagingBufferSize}, stream_buffer{instance, scheduler, MemoryUsage::Stream, UboStreamBufferSize}, + gds_buffer{instance, scheduler, MemoryUsage::Stream, 0, AllFlags, GdsBufferSize}, memory_tracker{&tracker} { + Vulkan::SetObjectName(instance.GetDevice(), gds_buffer.Handle(), "GDS Buffer"); + // Ensure the first slot is used for the null buffer - void(slot_buffers.insert(instance, MemoryUsage::DeviceLocal, 0, ReadFlags, 1)); + const auto null_id = + slot_buffers.insert(instance, scheduler, MemoryUsage::DeviceLocal, 0, ReadFlags, 1); + ASSERT(null_id.index == 0); + const vk::Buffer& null_buffer = slot_buffers[null_id].buffer; + Vulkan::SetObjectName(instance.GetDevice(), null_buffer, "Null Buffer"); } BufferCache::~BufferCache() = default; @@ -100,9 +112,9 @@ bool BufferCache::BindVertexBuffers(const Shader::Info& vs_info) { return false; } - std::array host_buffers; - std::array host_offsets; - boost::container::static_vector guest_buffers; + std::array host_buffers; + std::array host_offsets; + boost::container::static_vector guest_buffers; struct BufferRange { VAddr base_address; @@ -117,7 +129,7 @@ bool BufferCache::BindVertexBuffers(const Shader::Info& vs_info) { // Calculate buffers memory overlaps bool has_step_rate = false; - boost::container::static_vector ranges{}; + boost::container::static_vector ranges{}; for (const auto& input : vs_info.vs_inputs) { if (input.instance_step_rate == Shader::Info::VsInput::InstanceIdType::OverStepRate0 || input.instance_step_rate == Shader::Info::VsInput::InstanceIdType::OverStepRate1) { @@ -152,7 +164,7 @@ bool BufferCache::BindVertexBuffers(const Shader::Info& vs_info) { return lhv.base_address < rhv.base_address; }); - boost::container::static_vector ranges_merged{ranges[0]}; + boost::container::static_vector ranges_merged{ranges[0]}; for (auto range : ranges) { auto& prev_range = ranges_merged.back(); if (prev_range.end_address < range.base_address) { @@ -228,12 +240,32 @@ u32 BufferCache::BindIndexBuffer(bool& is_indexed, u32 index_offset) { return regs.num_indices; } +void BufferCache::InlineDataToGds(u32 gds_offset, u32 value) { + ASSERT_MSG(gds_offset % 4 == 0, "GDS offset must be dword aligned"); + scheduler.EndRendering(); + const auto cmdbuf = scheduler.CommandBuffer(); + const vk::BufferMemoryBarrier2 buf_barrier = { + .srcStageMask = vk::PipelineStageFlagBits2::eTransfer, + .srcAccessMask = vk::AccessFlagBits2::eTransferWrite, + .dstStageMask = vk::PipelineStageFlagBits2::eAllCommands, + .dstAccessMask = vk::AccessFlagBits2::eMemoryRead, + .buffer = gds_buffer.Handle(), + .offset = gds_offset, + .size = sizeof(u32), + }; + cmdbuf.pipelineBarrier2(vk::DependencyInfo{ + .dependencyFlags = vk::DependencyFlagBits::eByRegion, + .bufferMemoryBarrierCount = 1, + .pBufferMemoryBarriers = &buf_barrier, + }); + cmdbuf.updateBuffer(gds_buffer.Handle(), gds_offset, sizeof(u32), &value); +} + std::pair BufferCache::ObtainBuffer(VAddr device_addr, u32 size, bool is_written, bool is_texel_buffer) { - std::scoped_lock lk{mutex}; static constexpr u64 StreamThreshold = CACHING_PAGESIZE; const bool is_gpu_dirty = memory_tracker.IsRegionGpuModified(device_addr, size); - if (!is_written && !is_texel_buffer && size <= StreamThreshold && !is_gpu_dirty) { + if (!is_written && size <= StreamThreshold && !is_gpu_dirty) { // For small uniform buffers that have not been modified by gpu // use device local stream buffer to reduce renderpass breaks. const u64 offset = stream_buffer.Copy(device_addr, size, instance.UniformMinAlignment()); @@ -242,19 +274,20 @@ std::pair BufferCache::ObtainBuffer(VAddr device_addr, u32 size, b const BufferId buffer_id = FindBuffer(device_addr, size); Buffer& buffer = slot_buffers[buffer_id]; - SynchronizeBuffer(buffer, device_addr, size); + SynchronizeBuffer(buffer, device_addr, size, is_texel_buffer); if (is_written) { memory_tracker.MarkRegionAsGpuModified(device_addr, size); } return {&buffer, buffer.Offset(device_addr)}; } -std::pair BufferCache::ObtainTempBuffer(VAddr gpu_addr, u32 size) { +std::pair BufferCache::ObtainTempBuffer(VAddr gpu_addr, u32 size) { const u64 page = gpu_addr >> CACHING_PAGEBITS; const BufferId buffer_id = page_table[page]; if (buffer_id) { - const Buffer& buffer = slot_buffers[buffer_id]; + Buffer& buffer = slot_buffers[buffer_id]; if (buffer.IsInBounds(gpu_addr, size)) { + SynchronizeBuffer(buffer, gpu_addr, size, false); return {&buffer, buffer.Offset(gpu_addr)}; } } @@ -421,8 +454,8 @@ BufferId BufferCache::CreateBuffer(VAddr device_addr, u32 wanted_size) { wanted_size = static_cast(device_addr_end - device_addr); const OverlapResult overlap = ResolveOverlaps(device_addr, wanted_size); const u32 size = static_cast(overlap.end - overlap.begin); - const BufferId new_buffer_id = - slot_buffers.insert(instance, MemoryUsage::DeviceLocal, overlap.begin, AllFlags, size); + const BufferId new_buffer_id = slot_buffers.insert( + instance, scheduler, MemoryUsage::DeviceLocal, overlap.begin, AllFlags, size); auto& new_buffer = slot_buffers[new_buffer_id]; const size_t size_bytes = new_buffer.SizeBytes(); const auto cmdbuf = scheduler.CommandBuffer(); @@ -460,7 +493,9 @@ void BufferCache::ChangeRegister(BufferId buffer_id) { } } -bool BufferCache::SynchronizeBuffer(Buffer& buffer, VAddr device_addr, u32 size) { +void BufferCache::SynchronizeBuffer(Buffer& buffer, VAddr device_addr, u32 size, + bool is_texel_buffer) { + std::scoped_lock lk{mutex}; boost::container::small_vector copies; u64 total_size_bytes = 0; u64 largest_copy = 0; @@ -479,8 +514,13 @@ bool BufferCache::SynchronizeBuffer(Buffer& buffer, VAddr device_addr, u32 size) // Prevent uploading to gpu modified regions. // gpu_modified_ranges.ForEachNotInRange(device_addr_out, range_size, add_copy); }); + SCOPE_EXIT { + if (is_texel_buffer) { + SynchronizeBufferFromImage(buffer, device_addr, size); + } + }; if (total_size_bytes == 0) { - return true; + return; } vk::Buffer src_buffer = staging_buffer.Handle(); if (total_size_bytes < StagingBufferSize) { @@ -496,7 +536,11 @@ bool BufferCache::SynchronizeBuffer(Buffer& buffer, VAddr device_addr, u32 size) } else { // For large one time transfers use a temporary host buffer. // RenderDoc can lag quite a bit if the stream buffer is too large. - Buffer temp_buffer{instance, MemoryUsage::Upload, 0, vk::BufferUsageFlagBits::eTransferSrc, + Buffer temp_buffer{instance, + scheduler, + MemoryUsage::Upload, + 0, + vk::BufferUsageFlagBits::eTransferSrc, total_size_bytes}; src_buffer = temp_buffer.Handle(); u8* const staging = temp_buffer.mapped_data.data(); @@ -524,7 +568,61 @@ bool BufferCache::SynchronizeBuffer(Buffer& buffer, VAddr device_addr, u32 size) cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eAllCommands, vk::DependencyFlagBits::eByRegion, WRITE_BARRIER, {}, {}); - return false; +} + +bool BufferCache::SynchronizeBufferFromImage(Buffer& buffer, VAddr device_addr, u32 size) { + static constexpr FindFlags find_flags = + FindFlags::NoCreate | FindFlags::RelaxDim | FindFlags::RelaxFmt | FindFlags::RelaxSize; + ImageInfo info{}; + info.guest_address = device_addr; + info.guest_size_bytes = size; + const ImageId image_id = texture_cache.FindImage(info, find_flags); + if (!image_id) { + return false; + } + Image& image = texture_cache.GetImage(image_id); + if (False(image.flags & ImageFlagBits::GpuModified)) { + return false; + } + ASSERT_MSG(device_addr == image.info.guest_address, + "Texel buffer aliases image subresources {:x} : {:x}", device_addr, + image.info.guest_address); + boost::container::small_vector copies; + u32 offset = buffer.Offset(image.cpu_addr); + const u32 num_layers = image.info.resources.layers; + const u32 max_offset = offset + size; + for (u32 m = 0; m < image.info.resources.levels; m++) { + const u32 width = std::max(image.info.size.width >> m, 1u); + const u32 height = std::max(image.info.size.height >> m, 1u); + const u32 depth = + image.info.props.is_volume ? std::max(image.info.size.depth >> m, 1u) : 1u; + const auto& [mip_size, mip_pitch, mip_height, mip_ofs] = image.info.mips_layout[m]; + offset += mip_ofs * num_layers; + if (offset + (mip_size * num_layers) > max_offset) { + break; + } + copies.push_back({ + .bufferOffset = offset, + .bufferRowLength = static_cast(mip_pitch), + .bufferImageHeight = static_cast(mip_height), + .imageSubresource{ + .aspectMask = image.aspect_mask & ~vk::ImageAspectFlagBits::eStencil, + .mipLevel = m, + .baseArrayLayer = 0, + .layerCount = num_layers, + }, + .imageOffset = {0, 0, 0}, + .imageExtent = {width, height, depth}, + }); + } + if (!copies.empty()) { + scheduler.EndRendering(); + image.Transit(vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits2::eTransferRead, {}); + const auto cmdbuf = scheduler.CommandBuffer(); + cmdbuf.copyImageToBuffer(image.image, vk::ImageLayout::eTransferSrcOptimal, buffer.buffer, + copies); + } + return true; } void BufferCache::DeleteBuffer(BufferId buffer_id, bool do_not_mark) { diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h index a07470b8ecb..8927083cd2c 100644 --- a/src/video_core/buffer_cache/buffer_cache.h +++ b/src/video_core/buffer_cache/buffer_cache.h @@ -28,7 +28,7 @@ using BufferId = Common::SlotId; static constexpr BufferId NULL_BUFFER_ID{0}; -static constexpr u32 NUM_VERTEX_BUFFERS = 32; +class TextureCache; class BufferCache { public: @@ -53,9 +53,20 @@ class BufferCache { public: explicit BufferCache(const Vulkan::Instance& instance, Vulkan::Scheduler& scheduler, - const AmdGpu::Liverpool* liverpool, PageManager& tracker); + const AmdGpu::Liverpool* liverpool, TextureCache& texture_cache, + PageManager& tracker); ~BufferCache(); + /// Returns a pointer to GDS device local buffer. + [[nodiscard]] const Buffer* GetGdsBuffer() const noexcept { + return &gds_buffer; + } + + /// Retrieves the buffer with the specified id. + [[nodiscard]] Buffer& GetBuffer(BufferId id) { + return slot_buffers[id]; + } + /// Invalidates any buffer in the logical page range. void InvalidateMemory(VAddr device_addr, u64 size); @@ -65,12 +76,15 @@ class BufferCache { /// Bind host index buffer for the current draw. u32 BindIndexBuffer(bool& is_indexed, u32 index_offset); + /// Writes a value to GDS buffer. + void InlineDataToGds(u32 gds_offset, u32 value); + /// Obtains a buffer for the specified region. [[nodiscard]] std::pair ObtainBuffer(VAddr gpu_addr, u32 size, bool is_written, bool is_texel_buffer = false); /// Obtains a temporary buffer for usage in texture cache. - [[nodiscard]] std::pair ObtainTempBuffer(VAddr gpu_addr, u32 size); + [[nodiscard]] std::pair ObtainTempBuffer(VAddr gpu_addr, u32 size); /// Return true when a region is registered on the cache [[nodiscard]] bool IsRegionRegistered(VAddr addr, size_t size); @@ -116,17 +130,21 @@ class BufferCache { template void ChangeRegister(BufferId buffer_id); - bool SynchronizeBuffer(Buffer& buffer, VAddr device_addr, u32 size); + void SynchronizeBuffer(Buffer& buffer, VAddr device_addr, u32 size, bool is_texel_buffer); + + bool SynchronizeBufferFromImage(Buffer& buffer, VAddr device_addr, u32 size); void DeleteBuffer(BufferId buffer_id, bool do_not_mark = false); const Vulkan::Instance& instance; Vulkan::Scheduler& scheduler; const AmdGpu::Liverpool* liverpool; + TextureCache& texture_cache; PageManager& tracker; StreamBuffer staging_buffer; StreamBuffer stream_buffer; - std::recursive_mutex mutex; + Buffer gds_buffer; + std::mutex mutex; Common::SlotVector slot_buffers; MemoryTracker memory_tracker; PageTable page_table; diff --git a/src/video_core/page_manager.cpp b/src/video_core/page_manager.cpp index 18b8aee2180..fb09e70f2bf 100644 --- a/src/video_core/page_manager.cpp +++ b/src/video_core/page_manager.cpp @@ -2,20 +2,22 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include +#include #include "common/alignment.h" #include "common/assert.h" #include "common/error.h" +#include "common/signal_context.h" +#include "core/signals.h" #include "video_core/page_manager.h" #include "video_core/renderer_vulkan/vk_rasterizer.h" #ifndef _WIN64 -#include -#include -#include -#include #include #ifdef ENABLE_USERFAULTFD +#include #include +#include +#include #endif #else #include @@ -26,45 +28,7 @@ namespace VideoCore { constexpr size_t PAGESIZE = 4_KB; constexpr size_t PAGEBITS = 12; -#ifdef _WIN64 -struct PageManager::Impl { - Impl(Vulkan::Rasterizer* rasterizer_) { - rasterizer = rasterizer_; - - veh_handle = AddVectoredExceptionHandler(0, GuestFaultSignalHandler); - ASSERT_MSG(veh_handle, "Failed to register an exception handler"); - } - - void OnMap(VAddr address, size_t size) {} - - void OnUnmap(VAddr address, size_t size) {} - - void Protect(VAddr address, size_t size, bool allow_write) { - DWORD prot = allow_write ? PAGE_READWRITE : PAGE_READONLY; - DWORD old_prot{}; - BOOL result = VirtualProtect(std::bit_cast(address), size, prot, &old_prot); - ASSERT_MSG(result != 0, "Region protection failed"); - } - - static LONG WINAPI GuestFaultSignalHandler(EXCEPTION_POINTERS* pExp) noexcept { - const u32 ec = pExp->ExceptionRecord->ExceptionCode; - if (ec == EXCEPTION_ACCESS_VIOLATION) { - const auto info = pExp->ExceptionRecord->ExceptionInformation; - if (info[0] == 1) { // Write violation - const VAddr addr_aligned = Common::AlignDown(info[1], PAGESIZE); - rasterizer->InvalidateMemory(addr_aligned, PAGESIZE); - return EXCEPTION_CONTINUE_EXECUTION; - } /* else { - UNREACHABLE(); - }*/ - } - return EXCEPTION_CONTINUE_SEARCH; // pass further - } - - inline static Vulkan::Rasterizer* rasterizer; - void* veh_handle{}; -}; -#elif ENABLE_USERFAULTFD +#if ENABLE_USERFAULTFD struct PageManager::Impl { Impl(Vulkan::Rasterizer* rasterizer_) : rasterizer{rasterizer_} { uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK); @@ -163,52 +127,45 @@ struct PageManager::Impl { Impl(Vulkan::Rasterizer* rasterizer_) { rasterizer = rasterizer_; -#ifdef __APPLE__ - // Read-only memory write results in SIGBUS on Apple. - static constexpr int SignalType = SIGBUS; -#else - static constexpr int SignalType = SIGSEGV; -#endif - sigset_t signal_mask; - sigemptyset(&signal_mask); - sigaddset(&signal_mask, SignalType); - - using HandlerType = decltype(sigaction::sa_sigaction); - - struct sigaction guest_access_fault {}; - guest_access_fault.sa_flags = SA_SIGINFO | SA_ONSTACK; - guest_access_fault.sa_sigaction = &GuestFaultSignalHandler; - guest_access_fault.sa_mask = signal_mask; - sigaction(SignalType, &guest_access_fault, nullptr); + // Should be called first. + constexpr auto priority = std::numeric_limits::min(); + Core::Signals::Instance()->RegisterAccessViolationHandler(GuestFaultSignalHandler, + priority); } - void OnMap(VAddr address, size_t size) {} + void OnMap(VAddr address, size_t size) { + owned_ranges += boost::icl::interval::right_open(address, address + size); + } - void OnUnmap(VAddr address, size_t size) {} + void OnUnmap(VAddr address, size_t size) { + owned_ranges -= boost::icl::interval::right_open(address, address + size); + } void Protect(VAddr address, size_t size, bool allow_write) { +#ifdef _WIN32 + DWORD prot = allow_write ? PAGE_READWRITE : PAGE_READONLY; + DWORD old_prot{}; + BOOL result = VirtualProtect(std::bit_cast(address), size, prot, &old_prot); + ASSERT_MSG(result != 0, "Region protection failed"); +#else mprotect(reinterpret_cast(address), size, PROT_READ | (allow_write ? PROT_WRITE : 0)); +#endif } - static void GuestFaultSignalHandler(int sig, siginfo_t* info, void* raw_context) { - ucontext_t* ctx = reinterpret_cast(raw_context); - const VAddr address = reinterpret_cast(info->si_addr); -#ifdef __APPLE__ - const u32 err = ctx->uc_mcontext->__es.__err; -#else - const greg_t err = ctx->uc_mcontext.gregs[REG_ERR]; -#endif - if (err & 0x2) { - const VAddr addr_aligned = Common::AlignDown(address, PAGESIZE); + static bool GuestFaultSignalHandler(void* context, void* fault_address) { + const auto addr = reinterpret_cast(fault_address); + const bool is_write = Common::IsWriteError(context); + if (is_write && owned_ranges.find(addr) != owned_ranges.end()) { + const VAddr addr_aligned = Common::AlignDown(addr, PAGESIZE); rasterizer->InvalidateMemory(addr_aligned, PAGESIZE); - } else { - // Read not supported! - UNREACHABLE(); + return true; } + return false; } inline static Vulkan::Rasterizer* rasterizer; + inline static boost::icl::interval_set owned_ranges; }; #endif diff --git a/src/video_core/renderer_vulkan/liverpool_to_vk.cpp b/src/video_core/renderer_vulkan/liverpool_to_vk.cpp index 57593e919e9..c4b779fad15 100644 --- a/src/video_core/renderer_vulkan/liverpool_to_vk.cpp +++ b/src/video_core/renderer_vulkan/liverpool_to_vk.cpp @@ -199,8 +199,17 @@ vk::SamplerAddressMode ClampMode(AmdGpu::ClampMode mode) { return vk::SamplerAddressMode::eMirroredRepeat; case AmdGpu::ClampMode::ClampLastTexel: return vk::SamplerAddressMode::eClampToEdge; + case AmdGpu::ClampMode::MirrorOnceHalfBorder: + case AmdGpu::ClampMode::MirrorOnceBorder: + LOG_WARNING(Render_Vulkan, "Unimplemented clamp mode {}, using closest equivalent.", + static_cast(mode)); + [[fallthrough]]; case AmdGpu::ClampMode::MirrorOnceLastTexel: return vk::SamplerAddressMode::eMirrorClampToEdge; + case AmdGpu::ClampMode::ClampHalfBorder: + LOG_WARNING(Render_Vulkan, "Unimplemented clamp mode {}, using closest equivalent.", + static_cast(mode)); + [[fallthrough]]; case AmdGpu::ClampMode::ClampBorder: return vk::SamplerAddressMode::eClampToBorder; default: @@ -312,6 +321,7 @@ std::span GetAllFormats() { vk::Format::eD32SfloatS8Uint, vk::Format::eR4G4B4A4UnormPack16, vk::Format::eR5G6B5UnormPack16, + vk::Format::eR5G5B5A1UnormPack16, vk::Format::eR8G8B8A8Srgb, vk::Format::eR8G8B8A8Uint, vk::Format::eR8G8B8A8Unorm, @@ -386,6 +396,10 @@ vk::Format SurfaceFormat(AmdGpu::DataFormat data_format, AmdGpu::NumberFormat nu num_format == AmdGpu::NumberFormat::Unorm) { return vk::Format::eB5G6R5UnormPack16; } + if (data_format == AmdGpu::DataFormat::Format1_5_5_5 && + num_format == AmdGpu::NumberFormat::Unorm) { + return vk::Format::eR5G5B5A1UnormPack16; + } if (data_format == AmdGpu::DataFormat::Format8 && num_format == AmdGpu::NumberFormat::Unorm) { return vk::Format::eR8Unorm; } @@ -580,11 +594,10 @@ vk::Format SurfaceFormat(AmdGpu::DataFormat data_format, AmdGpu::NumberFormat nu vk::Format AdjustColorBufferFormat(vk::Format base_format, Liverpool::ColorBuffer::SwapMode comp_swap, bool is_vo_surface) { - ASSERT_MSG(comp_swap == Liverpool::ColorBuffer::SwapMode::Standard || - comp_swap == Liverpool::ColorBuffer::SwapMode::Alternate, - "Unsupported component swap mode {}", static_cast(comp_swap)); - const bool comp_swap_alt = comp_swap == Liverpool::ColorBuffer::SwapMode::Alternate; + const bool comp_swap_reverse = comp_swap == Liverpool::ColorBuffer::SwapMode::StandardReverse; + const bool comp_swap_alt_reverse = + comp_swap == Liverpool::ColorBuffer::SwapMode::AlternateReverse; if (comp_swap_alt) { switch (base_format) { case vk::Format::eR8G8B8A8Unorm: @@ -595,9 +608,23 @@ vk::Format AdjustColorBufferFormat(vk::Format base_format, return is_vo_surface ? vk::Format::eB8G8R8A8Unorm : vk::Format::eB8G8R8A8Srgb; case vk::Format::eB8G8R8A8Srgb: return is_vo_surface ? vk::Format::eR8G8B8A8Unorm : vk::Format::eR8G8B8A8Srgb; + case vk::Format::eA2B10G10R10UnormPack32: + return vk::Format::eA2R10G10B10UnormPack32; default: break; } + } else if (comp_swap_reverse) { + switch (base_format) { + case vk::Format::eR8G8B8A8Unorm: + return vk::Format::eA8B8G8R8UnormPack32; + case vk::Format::eR8G8B8A8Srgb: + return is_vo_surface ? vk::Format::eA8B8G8R8UnormPack32 + : vk::Format::eA8B8G8R8SrgbPack32; + default: + break; + } + } else if (comp_swap_alt_reverse) { + return base_format; } else { if (is_vo_surface && base_format == vk::Format::eR8G8B8A8Srgb) { return vk::Format::eR8G8B8A8Unorm; @@ -642,8 +669,8 @@ void EmitQuadToTriangleListIndices(u8* out_ptr, u32 num_vertices) { *out_data++ = i; *out_data++ = i + 1; *out_data++ = i + 2; - *out_data++ = i + 2; *out_data++ = i; + *out_data++ = i + 2; *out_data++ = i + 3; } } @@ -700,7 +727,7 @@ vk::ClearValue ColorBufferClearValue(const AmdGpu::Liverpool::ColorBuffer& color return {.color = color}; } -vk::SampleCountFlagBits NumSamples(u32 num_samples) { +vk::SampleCountFlagBits RawNumSamples(u32 num_samples) { switch (num_samples) { case 1: return vk::SampleCountFlagBits::e1; @@ -717,4 +744,14 @@ vk::SampleCountFlagBits NumSamples(u32 num_samples) { } } +vk::SampleCountFlagBits NumSamples(u32 num_samples, vk::SampleCountFlags supported_flags) { + vk::SampleCountFlagBits flags = RawNumSamples(num_samples); + // Half sample counts until supported, with a minimum of 1. + while (!(supported_flags & flags) && num_samples > 1) { + num_samples /= 2; + flags = RawNumSamples(num_samples); + } + return flags; +} + } // namespace Vulkan::LiverpoolToVK diff --git a/src/video_core/renderer_vulkan/liverpool_to_vk.h b/src/video_core/renderer_vulkan/liverpool_to_vk.h index 94f9073de92..f5d10d48f74 100644 --- a/src/video_core/renderer_vulkan/liverpool_to_vk.h +++ b/src/video_core/renderer_vulkan/liverpool_to_vk.h @@ -4,6 +4,7 @@ #pragma once #include +#include "common/assert.h" #include "video_core/amdgpu/liverpool.h" #include "video_core/amdgpu/pixel_format.h" #include "video_core/amdgpu/resource.h" @@ -51,8 +52,17 @@ vk::Format DepthFormat(Liverpool::DepthBuffer::ZFormat z_format, vk::ClearValue ColorBufferClearValue(const AmdGpu::Liverpool::ColorBuffer& color_buffer); -vk::SampleCountFlagBits NumSamples(u32 num_samples); +vk::SampleCountFlagBits NumSamples(u32 num_samples, vk::SampleCountFlags supported_flags); void EmitQuadToTriangleListIndices(u8* out_indices, u32 num_vertices); +static inline vk::Format PromoteFormatToDepth(vk::Format fmt) { + if (fmt == vk::Format::eR32Sfloat) { + return vk::Format::eD32Sfloat; + } else if (fmt == vk::Format::eR16Unorm) { + return vk::Format::eD16Unorm; + } + UNREACHABLE(); +} + } // namespace Vulkan::LiverpoolToVK diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp index b127080882d..d7954bf79ec 100644 --- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp +++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp @@ -6,12 +6,16 @@ #include "common/singleton.h" #include "core/file_format/splash.h" #include "core/libraries/system/systemservice.h" +#include "imgui/renderer/imgui_core.h" #include "sdl_window.h" #include "video_core/renderer_vulkan/renderer_vulkan.h" #include "video_core/renderer_vulkan/vk_rasterizer.h" #include "video_core/texture_cache/image.h" +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnullability-completeness" #include +#pragma GCC diagnostic pop namespace Vulkan { @@ -65,10 +69,12 @@ bool CanBlitToSwapchain(const vk::PhysicalDevice physical_device, vk::Format for RendererVulkan::RendererVulkan(Frontend::WindowSDL& window_, AmdGpu::Liverpool* liverpool_) : window{window_}, liverpool{liverpool_}, - instance{window, Config::getGpuId(), Config::vkValidationEnabled()}, draw_scheduler{instance}, - present_scheduler{instance}, flip_scheduler{instance}, swapchain{instance, window}, + instance{window, Config::getGpuId(), Config::vkValidationEnabled(), + Config::vkCrashDiagnosticEnabled()}, + draw_scheduler{instance}, present_scheduler{instance}, flip_scheduler{instance}, + swapchain{instance, window}, rasterizer{std::make_unique(instance, draw_scheduler, liverpool)}, - texture_cache{rasterizer->GetTextureCache()} { + texture_cache{rasterizer->GetTextureCache()}, video_info_ui{this} { const u32 num_images = swapchain.GetImageCount(); const vk::Device device = instance.GetDevice(); @@ -79,9 +85,14 @@ RendererVulkan::RendererVulkan(Frontend::WindowSDL& window_, AmdGpu::Liverpool* frame.present_done = device.createFence({.flags = vk::FenceCreateFlagBits::eSignaled}); free_queue.push(&frame); } + + // Setup ImGui + ImGui::Core::Initialize(instance, window, num_images, swapchain.GetSurfaceFormat().format); + ImGui::Layer::AddLayer(&video_info_ui); } RendererVulkan::~RendererVulkan() { + ImGui::Layer::RemoveLayer(&video_info_ui); draw_scheduler.Finish(); const vk::Device device = instance.GetDevice(); for (auto& frame : present_frames) { @@ -89,6 +100,7 @@ RendererVulkan::~RendererVulkan() { device.destroyImageView(frame.image_view); device.destroyFence(frame.present_done); } + ImGui::Core::Shutdown(device); } void RendererVulkan::RecreateFrame(Frame* frame, u32 width, u32 height) { @@ -190,7 +202,8 @@ Frame* RendererVulkan::PrepareFrameInternal(VideoCore::Image& image, bool is_eop scheduler.EndRendering(); const auto cmdbuf = scheduler.CommandBuffer(); - image.Transit(vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits::eTransferRead, cmdbuf); + image.Transit(vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits2::eTransferRead, {}, + cmdbuf); const std::array pre_barrier{ vk::ImageMemoryBarrier{ @@ -216,7 +229,7 @@ Frame* RendererVulkan::PrepareFrameInternal(VideoCore::Image& image, bool is_eop // Post-processing (Anti-aliasing, FSR etc) goes here. For now just blit to the frame image. cmdbuf.blitImage( - image.image, image.layout, frame->image, vk::ImageLayout::eTransferDstOptimal, + image.image, image.last_state.layout, frame->image, vk::ImageLayout::eTransferDstOptimal, MakeImageBlit(image.info.size.width, image.info.size.height, frame->width, frame->height), vk::Filter::eLinear); @@ -249,12 +262,17 @@ Frame* RendererVulkan::PrepareFrameInternal(VideoCore::Image& image, bool is_eop } void RendererVulkan::Present(Frame* frame) { + ImGui::Core::NewFrame(); + swapchain.AcquireNextImage(); const vk::Image swapchain_image = swapchain.Image(); auto& scheduler = present_scheduler; const auto cmdbuf = scheduler.CommandBuffer(); + + ImGui::Core::Render(cmdbuf, frame); + { auto* profiler_ctx = instance.GetProfilerContext(); TracyVkNamedZoneC(profiler_ctx, renderer_gpu_zone, cmdbuf, "Host frame", @@ -354,7 +372,7 @@ Frame* RendererVulkan::GetRenderFrame() { { std::unique_lock lock{free_mutex}; free_cv.wait(lock, [this] { return !free_queue.empty(); }); - LOG_INFO(Render_Vulkan, "Got render frame, remaining {}", free_queue.size() - 1); + LOG_DEBUG(Render_Vulkan, "Got render frame, remaining {}", free_queue.size() - 1); // Take the frame from the queue frame = free_queue.front(); diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.h b/src/video_core/renderer_vulkan/renderer_vulkan.h index eab9d527cd8..c8e566418fd 100644 --- a/src/video_core/renderer_vulkan/renderer_vulkan.h +++ b/src/video_core/renderer_vulkan/renderer_vulkan.h @@ -4,6 +4,8 @@ #pragma once #include + +#include "imgui/layer/video_info.h" #include "video_core/amdgpu/liverpool.h" #include "video_core/renderer_vulkan/vk_instance.h" #include "video_core/renderer_vulkan/vk_scheduler.h" @@ -103,6 +105,8 @@ class RendererVulkan { std::condition_variable_any frame_cv; std::optional splash_img; std::vector vo_buffers_addr; + + ImGui::Layers::VideoInfo video_info_ui; }; } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_common.cpp b/src/video_core/renderer_vulkan/vk_common.cpp index e9265ea9c87..0823fd23d7a 100644 --- a/src/video_core/renderer_vulkan/vk_common.cpp +++ b/src/video_core/renderer_vulkan/vk_common.cpp @@ -5,7 +5,10 @@ // Implement vma functions #define VMA_IMPLEMENTATION +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnullability-completeness" #include +#pragma GCC diagnostic pop // Store the dispatch loader here VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE diff --git a/src/video_core/renderer_vulkan/vk_common.h b/src/video_core/renderer_vulkan/vk_common.h index 7db6fb06d81..a2f9cbcaf7d 100644 --- a/src/video_core/renderer_vulkan/vk_common.h +++ b/src/video_core/renderer_vulkan/vk_common.h @@ -3,12 +3,17 @@ #pragma once +#if defined(__APPLE__) && !USE_SYSTEM_VULKAN_LOADER +#define VULKAN_HPP_ENABLE_DYNAMIC_LOADER_TOOL 0 +#endif + // Include vulkan-hpp header #define VK_ENABLE_BETA_EXTENSIONS #define VK_NO_PROTOTYPES #define VULKAN_HPP_DISPATCH_LOADER_DYNAMIC 1 #define VULKAN_HPP_NO_CONSTRUCTORS #define VULKAN_HPP_NO_STRUCT_SETTERS +#define VULKAN_HPP_HAS_SPACESHIP_OPERATOR #include #define VMA_STATIC_VULKAN_FUNCTIONS 0 diff --git a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp index 03950f7c087..3558bf78567 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include + #include "common/alignment.h" #include "video_core/buffer_cache/buffer_cache.h" #include "video_core/renderer_vulkan/vk_compute_pipeline.h" @@ -12,9 +13,11 @@ namespace Vulkan { ComputePipeline::ComputePipeline(const Instance& instance_, Scheduler& scheduler_, - vk::PipelineCache pipeline_cache, u64 compute_key_, - const Shader::Info& info_, vk::ShaderModule module) - : instance{instance_}, scheduler{scheduler_}, compute_key{compute_key_}, info{&info_} { + DescriptorHeap& desc_heap_, vk::PipelineCache pipeline_cache, + u64 compute_key_, const Shader::Info& info_, + vk::ShaderModule module) + : Pipeline{instance_, scheduler_, desc_heap_, pipeline_cache}, compute_key{compute_key_}, + info{&info_} { const vk::PipelineShaderStageCreateInfo shader_ci = { .stage = vk::ShaderStageFlagBits::eCompute, .module = module, @@ -66,8 +69,12 @@ ComputePipeline::ComputePipeline(const Instance& instance_, Scheduler& scheduler .size = sizeof(Shader::PushData), }; + uses_push_descriptors = binding < instance.MaxPushDescriptors(); + const auto flags = uses_push_descriptors + ? vk::DescriptorSetLayoutCreateFlagBits::ePushDescriptorKHR + : vk::DescriptorSetLayoutCreateFlagBits{}; const vk::DescriptorSetLayoutCreateInfo desc_layout_ci = { - .flags = vk::DescriptorSetLayoutCreateFlagBits::ePushDescriptorKHR, + .flags = flags, .bindingCount = static_cast(bindings.size()), .pBindings = bindings.data(), }; @@ -102,60 +109,67 @@ bool ComputePipeline::BindResources(VideoCore::BufferCache& buffer_cache, // Bind resource buffers and textures. boost::container::static_vector buffer_views; boost::container::static_vector buffer_infos; - boost::container::static_vector image_infos; - boost::container::small_vector set_writes; + boost::container::small_vector set_writes; + boost::container::small_vector buffer_barriers; Shader::PushData push_data{}; - u32 binding{}; + Shader::Backend::Bindings binding{}; + + image_infos.clear(); + info->PushUd(binding, push_data); for (const auto& desc : info->buffers) { - const auto vsharp = desc.GetSharp(*info); - const bool is_storage = desc.IsStorage(vsharp); - const VAddr address = vsharp.base_address; - // Most of the time when a metadata is updated with a shader it gets cleared. It means we - // can skip the whole dispatch and update the tracked state instead. Also, it is not - // intended to be consumed and in such rare cases (e.g. HTile introspection, CRAA) we will - // need its full emulation anyways. For cases of metadata read a warning will be logged. - if (desc.is_written) { - if (texture_cache.TouchMeta(address, true)) { - LOG_TRACE(Render_Vulkan, "Metadata update skipped"); - return false; - } + bool is_storage = true; + if (desc.is_gds_buffer) { + auto* vk_buffer = buffer_cache.GetGdsBuffer(); + buffer_infos.emplace_back(vk_buffer->Handle(), 0, vk_buffer->SizeBytes()); } else { - if (texture_cache.IsMeta(address)) { - LOG_WARNING(Render_Vulkan, "Unexpected metadata read by a CS shader (buffer)"); + const auto vsharp = desc.GetSharp(*info); + is_storage = desc.IsStorage(vsharp); + const VAddr address = vsharp.base_address; + // Most of the time when a metadata is updated with a shader it gets cleared. It means + // we can skip the whole dispatch and update the tracked state instead. Also, it is not + // intended to be consumed and in such rare cases (e.g. HTile introspection, CRAA) we + // will need its full emulation anyways. For cases of metadata read a warning will be + // logged. + if (desc.is_written) { + if (texture_cache.TouchMeta(address, true)) { + LOG_TRACE(Render_Vulkan, "Metadata update skipped"); + return false; + } + } else { + if (texture_cache.IsMeta(address)) { + LOG_WARNING(Render_Vulkan, "Unexpected metadata read by a CS shader (buffer)"); + } } - } - const u32 size = vsharp.GetSize(); - if (desc.is_written) { - texture_cache.InvalidateMemory(address, size); - } - const u32 alignment = - is_storage ? instance.StorageMinAlignment() : instance.UniformMinAlignment(); - const auto [vk_buffer, offset] = buffer_cache.ObtainBuffer(address, size, desc.is_written); - const u32 offset_aligned = Common::AlignDown(offset, alignment); - const u32 adjust = offset - offset_aligned; - if (adjust != 0) { + const u32 size = vsharp.GetSize(); + const u32 alignment = + is_storage ? instance.StorageMinAlignment() : instance.UniformMinAlignment(); + const auto [vk_buffer, offset] = + buffer_cache.ObtainBuffer(address, size, desc.is_written); + const u32 offset_aligned = Common::AlignDown(offset, alignment); + const u32 adjust = offset - offset_aligned; ASSERT(adjust % 4 == 0); - push_data.AddOffset(binding, adjust); + push_data.AddOffset(binding.buffer, adjust); + buffer_infos.emplace_back(vk_buffer->Handle(), offset_aligned, size + adjust); } - buffer_infos.emplace_back(vk_buffer->Handle(), offset_aligned, size + adjust); set_writes.push_back({ .dstSet = VK_NULL_HANDLE, - .dstBinding = binding++, + .dstBinding = binding.unified++, .dstArrayElement = 0, .descriptorCount = 1, .descriptorType = is_storage ? vk::DescriptorType::eStorageBuffer : vk::DescriptorType::eUniformBuffer, .pBufferInfo = &buffer_infos.back(), }); + ++binding.buffer; } for (const auto& desc : info->texture_buffers) { const auto vsharp = desc.GetSharp(*info); vk::BufferView& buffer_view = buffer_views.emplace_back(VK_NULL_HANDLE); - if (vsharp.GetDataFmt() != AmdGpu::DataFormat::FormatInvalid) { + const u32 size = vsharp.GetSize(); + if (vsharp.GetDataFmt() != AmdGpu::DataFormat::FormatInvalid && size != 0) { const VAddr address = vsharp.base_address; - const u32 size = vsharp.GetSize(); if (desc.is_written) { if (texture_cache.TouchMeta(address, true)) { LOG_TRACE(Render_Vulkan, "Metadata update skipped"); @@ -166,9 +180,6 @@ bool ComputePipeline::BindResources(VideoCore::BufferCache& buffer_cache, LOG_WARNING(Render_Vulkan, "Unexpected metadata read by a CS shader (buffer)"); } } - if (desc.is_written) { - texture_cache.InvalidateMemory(address, size); - } const u32 alignment = instance.TexelBufferMinAlignment(); const auto [vk_buffer, offset] = buffer_cache.ObtainBuffer(address, size, desc.is_written, true); @@ -177,56 +188,44 @@ bool ComputePipeline::BindResources(VideoCore::BufferCache& buffer_cache, "Texel buffer stride must match format stride"); const u32 offset_aligned = Common::AlignDown(offset, alignment); const u32 adjust = offset - offset_aligned; - if (adjust != 0) { - ASSERT(adjust % fmt_stride == 0); - push_data.AddOffset(binding, adjust / fmt_stride); - } + ASSERT(adjust % fmt_stride == 0); + push_data.AddOffset(binding.buffer, adjust / fmt_stride); buffer_view = vk_buffer->View(offset_aligned, size + adjust, desc.is_written, vsharp.GetDataFmt(), vsharp.GetNumberFmt()); + if (auto barrier = + vk_buffer->GetBarrier(desc.is_written ? vk::AccessFlagBits2::eShaderWrite + : vk::AccessFlagBits2::eShaderRead, + vk::PipelineStageFlagBits2::eComputeShader)) { + buffer_barriers.emplace_back(*barrier); + } + if (desc.is_written) { + texture_cache.InvalidateMemoryFromGPU(address, size); + } } set_writes.push_back({ .dstSet = VK_NULL_HANDLE, - .dstBinding = binding++, + .dstBinding = binding.unified++, .dstArrayElement = 0, .descriptorCount = 1, .descriptorType = desc.is_written ? vk::DescriptorType::eStorageTexelBuffer : vk::DescriptorType::eUniformTexelBuffer, .pTexelBufferView = &buffer_view, }); + ++binding.buffer; } - for (const auto& image_desc : info->images) { - const auto tsharp = image_desc.GetSharp(*info); - if (tsharp.GetDataFmt() != AmdGpu::DataFormat::FormatInvalid) { - VideoCore::ImageInfo image_info{tsharp}; - VideoCore::ImageViewInfo view_info{tsharp, image_desc.is_storage}; - const auto& image_view = texture_cache.FindTexture(image_info, view_info); - const auto& image = texture_cache.GetImage(image_view.image_id); - image_infos.emplace_back(VK_NULL_HANDLE, *image_view.image_view, image.layout); - } else { - image_infos.emplace_back(VK_NULL_HANDLE, VK_NULL_HANDLE, vk::ImageLayout::eGeneral); - } - set_writes.push_back({ - .dstSet = VK_NULL_HANDLE, - .dstBinding = binding++, - .dstArrayElement = 0, - .descriptorCount = 1, - .descriptorType = image_desc.is_storage ? vk::DescriptorType::eStorageImage - : vk::DescriptorType::eSampledImage, - .pImageInfo = &image_infos.back(), - }); + BindTextures(texture_cache, *info, binding, set_writes); - if (texture_cache.IsMeta(tsharp.Address())) { - LOG_WARNING(Render_Vulkan, "Unexpected metadata read by a CS shader (texture)"); - } - } for (const auto& sampler : info->samplers) { const auto ssharp = sampler.GetSharp(*info); + if (ssharp.force_degamma) { + LOG_WARNING(Render_Vulkan, "Texture requires gamma correction"); + } const auto vk_sampler = texture_cache.GetSampler(ssharp); image_infos.emplace_back(vk_sampler, VK_NULL_HANDLE, vk::ImageLayout::eGeneral); set_writes.push_back({ .dstSet = VK_NULL_HANDLE, - .dstBinding = binding++, + .dstBinding = binding.unified++, .dstArrayElement = 0, .descriptorCount = 1, .descriptorType = vk::DescriptorType::eSampler, @@ -239,9 +238,32 @@ bool ComputePipeline::BindResources(VideoCore::BufferCache& buffer_cache, } const auto cmdbuf = scheduler.CommandBuffer(); + + if (!buffer_barriers.empty()) { + const auto dependencies = vk::DependencyInfo{ + .dependencyFlags = vk::DependencyFlagBits::eByRegion, + .bufferMemoryBarrierCount = u32(buffer_barriers.size()), + .pBufferMemoryBarriers = buffer_barriers.data(), + }; + scheduler.EndRendering(); + cmdbuf.pipelineBarrier2(dependencies); + } + + if (uses_push_descriptors) { + cmdbuf.pushDescriptorSetKHR(vk::PipelineBindPoint::eCompute, *pipeline_layout, 0, + set_writes); + } else { + const auto desc_set = desc_heap.Commit(*desc_layout); + for (auto& set_write : set_writes) { + set_write.dstSet = desc_set; + } + instance.GetDevice().updateDescriptorSets(set_writes, {}); + cmdbuf.bindDescriptorSets(vk::PipelineBindPoint::eCompute, *pipeline_layout, 0, desc_set, + {}); + } + cmdbuf.pushConstants(*pipeline_layout, vk::ShaderStageFlagBits::eCompute, 0u, sizeof(push_data), &push_data); - cmdbuf.pushDescriptorSetKHR(vk::PipelineBindPoint::eCompute, *pipeline_layout, 0, set_writes); return true; } diff --git a/src/video_core/renderer_vulkan/vk_compute_pipeline.h b/src/video_core/renderer_vulkan/vk_compute_pipeline.h index 0132066c5b9..f1bc7285a9a 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pipeline.h +++ b/src/video_core/renderer_vulkan/vk_compute_pipeline.h @@ -3,9 +3,8 @@ #pragma once -#include -#include "shader_recompiler/runtime_info.h" #include "video_core/renderer_vulkan/vk_common.h" +#include "video_core/renderer_vulkan/vk_pipeline_common.h" namespace VideoCore { class BufferCache; @@ -16,29 +15,22 @@ namespace Vulkan { class Instance; class Scheduler; +class DescriptorHeap; -class ComputePipeline { +class ComputePipeline : public Pipeline { public: - explicit ComputePipeline(const Instance& instance, Scheduler& scheduler, - vk::PipelineCache pipeline_cache, u64 compute_key, - const Shader::Info& info, vk::ShaderModule module); + ComputePipeline(const Instance& instance, Scheduler& scheduler, DescriptorHeap& desc_heap, + vk::PipelineCache pipeline_cache, u64 compute_key, const Shader::Info& info, + vk::ShaderModule module); ~ComputePipeline(); - [[nodiscard]] vk::Pipeline Handle() const noexcept { - return *pipeline; - } - bool BindResources(VideoCore::BufferCache& buffer_cache, VideoCore::TextureCache& texture_cache) const; private: - const Instance& instance; - Scheduler& scheduler; - vk::UniquePipeline pipeline; - vk::UniquePipelineLayout pipeline_layout; - vk::UniqueDescriptorSetLayout desc_layout; u64 compute_key; const Shader::Info* info; + bool uses_push_descriptors{}; }; } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp index 1ab65737c4a..8edf2f50c30 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp @@ -17,11 +17,11 @@ namespace Vulkan { GraphicsPipeline::GraphicsPipeline(const Instance& instance_, Scheduler& scheduler_, - const GraphicsPipelineKey& key_, + DescriptorHeap& desc_heap_, const GraphicsPipelineKey& key_, vk::PipelineCache pipeline_cache, std::span infos, std::span modules) - : instance{instance_}, scheduler{scheduler_}, key{key_} { + : Pipeline{instance_, scheduler_, desc_heap_, pipeline_cache}, key{key_} { const vk::Device device = instance.GetDevice(); std::ranges::copy(infos, stages.begin()); BuildDescSetLayout(); @@ -41,8 +41,8 @@ GraphicsPipeline::GraphicsPipeline(const Instance& instance_, Scheduler& schedul }; pipeline_layout = instance.GetDevice().createPipelineLayoutUnique(layout_info); - boost::container::static_vector bindings; - boost::container::static_vector attributes; + boost::container::static_vector vertex_bindings; + boost::container::static_vector vertex_attributes; const auto& vs_info = stages[u32(Shader::Stage::Vertex)]; for (const auto& input : vs_info->vs_inputs) { if (input.instance_step_rate == Shader::Info::VsInput::InstanceIdType::OverStepRate0 || @@ -52,13 +52,13 @@ GraphicsPipeline::GraphicsPipeline(const Instance& instance_, Scheduler& schedul } const auto buffer = vs_info->ReadUd(input.sgpr_base, input.dword_offset); - attributes.push_back({ + vertex_attributes.push_back({ .location = input.binding, .binding = input.binding, .format = LiverpoolToVK::SurfaceFormat(buffer.GetDataFmt(), buffer.GetNumberFmt()), .offset = 0, }); - bindings.push_back({ + vertex_bindings.push_back({ .binding = input.binding, .stride = buffer.GetStride(), .inputRate = input.instance_step_rate == Shader::Info::VsInput::None @@ -68,10 +68,10 @@ GraphicsPipeline::GraphicsPipeline(const Instance& instance_, Scheduler& schedul } const vk::PipelineVertexInputStateCreateInfo vertex_input_info = { - .vertexBindingDescriptionCount = static_cast(bindings.size()), - .pVertexBindingDescriptions = bindings.data(), - .vertexAttributeDescriptionCount = static_cast(attributes.size()), - .pVertexAttributeDescriptions = attributes.data(), + .vertexBindingDescriptionCount = static_cast(vertex_bindings.size()), + .pVertexBindingDescriptions = vertex_bindings.data(), + .vertexAttributeDescriptionCount = static_cast(vertex_attributes.size()), + .pVertexAttributeDescriptions = vertex_attributes.data(), }; if (key.prim_type == Liverpool::PrimitiveType::RectList && !IsEmbeddedVs()) { @@ -83,8 +83,9 @@ GraphicsPipeline::GraphicsPipeline(const Instance& instance_, Scheduler& schedul .topology = LiverpoolToVK::PrimitiveType(key.prim_type), .primitiveRestartEnable = key.enable_primitive_restart != 0, }; - ASSERT_MSG(!key.enable_primitive_restart || key.primitive_restart_index == 0xFFFF, - "Primitive restart index other than 0xFFFF is not supported yet"); + ASSERT_MSG(!key.enable_primitive_restart || key.primitive_restart_index == 0xFFFF || + key.primitive_restart_index == 0xFFFFFFFF, + "Primitive restart index other than -1 is not supported yet"); const vk::PipelineRasterizationStateCreateInfo raster_state = { .depthClampEnable = false, @@ -95,14 +96,12 @@ GraphicsPipeline::GraphicsPipeline(const Instance& instance_, Scheduler& schedul ? vk::FrontFace::eClockwise : vk::FrontFace::eCounterClockwise, .depthBiasEnable = bool(key.depth_bias_enable), - .depthBiasConstantFactor = key.depth_bias_const_factor, - .depthBiasClamp = key.depth_bias_clamp, - .depthBiasSlopeFactor = key.depth_bias_slope_factor, .lineWidth = 1.0f, }; const vk::PipelineMultisampleStateCreateInfo multisampling = { - .rasterizationSamples = LiverpoolToVK::NumSamples(key.num_samples), + .rasterizationSamples = + LiverpoolToVK::NumSamples(key.num_samples, instance.GetFramebufferSampleCounts()), .sampleShadingEnable = false, }; @@ -125,7 +124,7 @@ GraphicsPipeline::GraphicsPipeline(const Instance& instance_, Scheduler& schedul }; const vk::PipelineViewportStateCreateInfo viewport_info = { - .pNext = &clip_control, + .pNext = instance.IsDepthClipControlSupported() ? &clip_control : nullptr, .viewportCount = 1, .pViewports = &viewport, .scissorCount = 1, @@ -133,9 +132,10 @@ GraphicsPipeline::GraphicsPipeline(const Instance& instance_, Scheduler& schedul }; boost::container::static_vector dynamic_states = { - vk::DynamicState::eViewport, - vk::DynamicState::eScissor, - vk::DynamicState::eBlendConstants, + vk::DynamicState::eViewport, vk::DynamicState::eScissor, + vk::DynamicState::eBlendConstants, vk::DynamicState::eDepthBounds, + vk::DynamicState::eDepthBias, vk::DynamicState::eStencilReference, + vk::DynamicState::eStencilCompareMask, vk::DynamicState::eStencilWriteMask, }; if (instance.IsColorWriteEnableSupported()) { @@ -152,39 +152,31 @@ GraphicsPipeline::GraphicsPipeline(const Instance& instance_, Scheduler& schedul }; const vk::PipelineDepthStencilStateCreateInfo depth_info = { - .depthTestEnable = key.depth.depth_enable, - .depthWriteEnable = key.depth.depth_write_enable, - .depthCompareOp = LiverpoolToVK::CompareOp(key.depth.depth_func), - .depthBoundsTestEnable = key.depth.depth_bounds_enable, - .stencilTestEnable = key.depth.stencil_enable, + .depthTestEnable = key.depth_stencil.depth_enable, + .depthWriteEnable = key.depth_stencil.depth_write_enable, + .depthCompareOp = LiverpoolToVK::CompareOp(key.depth_stencil.depth_func), + .depthBoundsTestEnable = key.depth_stencil.depth_bounds_enable, + .stencilTestEnable = key.depth_stencil.stencil_enable, .front{ .failOp = LiverpoolToVK::StencilOp(key.stencil.stencil_fail_front), .passOp = LiverpoolToVK::StencilOp(key.stencil.stencil_zpass_front), .depthFailOp = LiverpoolToVK::StencilOp(key.stencil.stencil_zfail_front), - .compareOp = LiverpoolToVK::CompareOp(key.depth.stencil_ref_func), - .compareMask = key.stencil_ref_front.stencil_mask, - .writeMask = key.stencil_ref_front.stencil_write_mask, - .reference = key.stencil_ref_front.stencil_test_val, + .compareOp = LiverpoolToVK::CompareOp(key.depth_stencil.stencil_ref_func), }, .back{ - .failOp = LiverpoolToVK::StencilOp(key.depth.backface_enable + .failOp = LiverpoolToVK::StencilOp(key.depth_stencil.backface_enable ? key.stencil.stencil_fail_back.Value() : key.stencil.stencil_fail_front.Value()), - .passOp = LiverpoolToVK::StencilOp(key.depth.backface_enable + .passOp = LiverpoolToVK::StencilOp(key.depth_stencil.backface_enable ? key.stencil.stencil_zpass_back.Value() : key.stencil.stencil_zpass_front.Value()), - .depthFailOp = LiverpoolToVK::StencilOp(key.depth.backface_enable + .depthFailOp = LiverpoolToVK::StencilOp(key.depth_stencil.backface_enable ? key.stencil.stencil_zfail_back.Value() : key.stencil.stencil_zfail_front.Value()), - .compareOp = LiverpoolToVK::CompareOp(key.depth.backface_enable - ? key.depth.stencil_bf_func.Value() - : key.depth.stencil_ref_func.Value()), - .compareMask = key.stencil_ref_back.stencil_mask, - .writeMask = key.stencil_ref_back.stencil_write_mask, - .reference = key.stencil_ref_back.stencil_test_val, + .compareOp = LiverpoolToVK::CompareOp(key.depth_stencil.backface_enable + ? key.depth_stencil.stencil_bf_func.Value() + : key.depth_stencil.stencil_ref_func.Value()), }, - .minDepthBounds = key.depth_bounds_min, - .maxDepthBounds = key.depth_bounds_max, }; auto stage = u32(Shader::Stage::Vertex); @@ -300,8 +292,9 @@ GraphicsPipeline::GraphicsPipeline(const Instance& instance_, Scheduler& schedul GraphicsPipeline::~GraphicsPipeline() = default; void GraphicsPipeline::BuildDescSetLayout() { - u32 binding{}; boost::container::small_vector bindings; + u32 binding{}; + for (const auto* stage : stages) { if (!stage) { continue; @@ -343,8 +336,12 @@ void GraphicsPipeline::BuildDescSetLayout() { }); } } + uses_push_descriptors = binding < instance.MaxPushDescriptors(); + const auto flags = uses_push_descriptors + ? vk::DescriptorSetLayoutCreateFlagBits::ePushDescriptorKHR + : vk::DescriptorSetLayoutCreateFlagBits{}; const vk::DescriptorSetLayoutCreateInfo desc_layout_ci = { - .flags = vk::DescriptorSetLayoutCreateFlagBits::ePushDescriptorKHR, + .flags = flags, .bindingCount = static_cast(bindings.size()), .pBindings = bindings.data(), }; @@ -357,10 +354,12 @@ void GraphicsPipeline::BindResources(const Liverpool::Regs& regs, // Bind resource buffers and textures. boost::container::static_vector buffer_views; boost::container::static_vector buffer_infos; - boost::container::static_vector image_infos; boost::container::small_vector set_writes; + boost::container::small_vector buffer_barriers; Shader::PushData push_data{}; - u32 binding{}; + Shader::Backend::Bindings binding{}; + + image_infos.clear(); for (const auto* stage : stages) { if (!stage) { @@ -370,6 +369,7 @@ void GraphicsPipeline::BindResources(const Liverpool::Regs& regs, push_data.step0 = regs.vgt_instance_step_rate_0; push_data.step1 = regs.vgt_instance_step_rate_1; } + stage->PushUd(binding, push_data); for (const auto& buffer : stage->buffers) { const auto vsharp = buffer.GetSharp(*stage); const bool is_storage = buffer.IsStorage(vsharp); @@ -385,88 +385,76 @@ void GraphicsPipeline::BindResources(const Liverpool::Regs& regs, buffer_cache.ObtainBuffer(address, size, buffer.is_written); const u32 offset_aligned = Common::AlignDown(offset, alignment); const u32 adjust = offset - offset_aligned; - if (adjust != 0) { - ASSERT(adjust % 4 == 0); - push_data.AddOffset(binding, adjust); - } + ASSERT(adjust % 4 == 0); + push_data.AddOffset(binding.buffer, adjust); buffer_infos.emplace_back(vk_buffer->Handle(), offset_aligned, size + adjust); - } else { + } else if (instance.IsNullDescriptorSupported()) { buffer_infos.emplace_back(VK_NULL_HANDLE, 0, VK_WHOLE_SIZE); + } else { + auto& null_buffer = buffer_cache.GetBuffer(VideoCore::NULL_BUFFER_ID); + buffer_infos.emplace_back(null_buffer.Handle(), 0, VK_WHOLE_SIZE); } set_writes.push_back({ .dstSet = VK_NULL_HANDLE, - .dstBinding = binding++, + .dstBinding = binding.unified++, .dstArrayElement = 0, .descriptorCount = 1, .descriptorType = is_storage ? vk::DescriptorType::eStorageBuffer : vk::DescriptorType::eUniformBuffer, .pBufferInfo = &buffer_infos.back(), }); + ++binding.buffer; } - for (const auto& tex_buffer : stage->texture_buffers) { - const auto vsharp = tex_buffer.GetSharp(*stage); + for (const auto& desc : stage->texture_buffers) { + const auto vsharp = desc.GetSharp(*stage); vk::BufferView& buffer_view = buffer_views.emplace_back(VK_NULL_HANDLE); - if (vsharp.GetDataFmt() != AmdGpu::DataFormat::FormatInvalid) { + const u32 size = vsharp.GetSize(); + if (vsharp.GetDataFmt() != AmdGpu::DataFormat::FormatInvalid && size != 0) { const VAddr address = vsharp.base_address; - const u32 size = vsharp.GetSize(); const u32 alignment = instance.TexelBufferMinAlignment(); const auto [vk_buffer, offset] = - buffer_cache.ObtainBuffer(address, size, tex_buffer.is_written, true); + buffer_cache.ObtainBuffer(address, size, desc.is_written, true); const u32 fmt_stride = AmdGpu::NumBits(vsharp.GetDataFmt()) >> 3; ASSERT_MSG(fmt_stride == vsharp.GetStride(), "Texel buffer stride must match format stride"); const u32 offset_aligned = Common::AlignDown(offset, alignment); const u32 adjust = offset - offset_aligned; - if (adjust != 0) { - ASSERT(adjust % fmt_stride == 0); - push_data.AddOffset(binding, adjust / fmt_stride); - } - buffer_view = vk_buffer->View(offset_aligned, size + adjust, tex_buffer.is_written, + ASSERT(adjust % fmt_stride == 0); + push_data.AddOffset(binding.buffer, adjust / fmt_stride); + buffer_view = vk_buffer->View(offset_aligned, size + adjust, desc.is_written, vsharp.GetDataFmt(), vsharp.GetNumberFmt()); + const auto dst_access = desc.is_written ? vk::AccessFlagBits2::eShaderWrite + : vk::AccessFlagBits2::eShaderRead; + if (auto barrier = vk_buffer->GetBarrier( + dst_access, vk::PipelineStageFlagBits2::eVertexShader)) { + buffer_barriers.emplace_back(*barrier); + } + if (desc.is_written) { + texture_cache.InvalidateMemoryFromGPU(address, size); + } } set_writes.push_back({ .dstSet = VK_NULL_HANDLE, - .dstBinding = binding++, + .dstBinding = binding.unified++, .dstArrayElement = 0, .descriptorCount = 1, - .descriptorType = tex_buffer.is_written ? vk::DescriptorType::eStorageTexelBuffer - : vk::DescriptorType::eUniformTexelBuffer, + .descriptorType = desc.is_written ? vk::DescriptorType::eStorageTexelBuffer + : vk::DescriptorType::eUniformTexelBuffer, .pTexelBufferView = &buffer_view, }); + ++binding.buffer; } - boost::container::static_vector tsharps; - for (const auto& image_desc : stage->images) { - const auto tsharp = image_desc.GetSharp(*stage); - if (tsharp) { - tsharps.emplace_back(tsharp); - VideoCore::ImageInfo image_info{tsharp}; - VideoCore::ImageViewInfo view_info{tsharp, image_desc.is_storage}; - const auto& image_view = texture_cache.FindTexture(image_info, view_info); - const auto& image = texture_cache.GetImage(image_view.image_id); - image_infos.emplace_back(VK_NULL_HANDLE, *image_view.image_view, image.layout); - } else { - image_infos.emplace_back(VK_NULL_HANDLE, VK_NULL_HANDLE, vk::ImageLayout::eGeneral); - } - set_writes.push_back({ - .dstSet = VK_NULL_HANDLE, - .dstBinding = binding++, - .dstArrayElement = 0, - .descriptorCount = 1, - .descriptorType = image_desc.is_storage ? vk::DescriptorType::eStorageImage - : vk::DescriptorType::eSampledImage, - .pImageInfo = &image_infos.back(), - }); + BindTextures(texture_cache, *stage, binding, set_writes); - if (texture_cache.IsMeta(tsharp.Address())) { - LOG_WARNING(Render_Vulkan, "Unexpected metadata read by a PS shader (texture)"); - } - } for (const auto& sampler : stage->samplers) { auto ssharp = sampler.GetSharp(*stage); + if (ssharp.force_degamma) { + LOG_WARNING(Render_Vulkan, "Texture requires gamma correction"); + } if (sampler.disable_aniso) { - const auto& tsharp = tsharps[sampler.associated_image]; + const auto& tsharp = stage->images[sampler.associated_image].GetSharp(*stage); if (tsharp.base_level == 0 && tsharp.last_level == 0) { ssharp.max_aniso.Assign(AmdGpu::AnisoRatio::One); } @@ -475,7 +463,7 @@ void GraphicsPipeline::BindResources(const Liverpool::Regs& regs, image_infos.emplace_back(vk_sampler, VK_NULL_HANDLE, vk::ImageLayout::eGeneral); set_writes.push_back({ .dstSet = VK_NULL_HANDLE, - .dstBinding = binding++, + .dstBinding = binding.unified++, .dstArrayElement = 0, .descriptorCount = 1, .descriptorType = vk::DescriptorType::eSampler, @@ -485,9 +473,30 @@ void GraphicsPipeline::BindResources(const Liverpool::Regs& regs, } const auto cmdbuf = scheduler.CommandBuffer(); + + if (!buffer_barriers.empty()) { + const auto dependencies = vk::DependencyInfo{ + .dependencyFlags = vk::DependencyFlagBits::eByRegion, + .bufferMemoryBarrierCount = u32(buffer_barriers.size()), + .pBufferMemoryBarriers = buffer_barriers.data(), + }; + scheduler.EndRendering(); + cmdbuf.pipelineBarrier2(dependencies); + } + if (!set_writes.empty()) { - cmdbuf.pushDescriptorSetKHR(vk::PipelineBindPoint::eGraphics, *pipeline_layout, 0, - set_writes); + if (uses_push_descriptors) { + cmdbuf.pushDescriptorSetKHR(vk::PipelineBindPoint::eGraphics, *pipeline_layout, 0, + set_writes); + } else { + const auto desc_set = desc_heap.Commit(*desc_layout); + for (auto& set_write : set_writes) { + set_write.dstSet = desc_set; + } + instance.GetDevice().updateDescriptorSets(set_writes, {}); + cmdbuf.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, *pipeline_layout, 0, + desc_set, {}); + } } cmdbuf.pushConstants(*pipeline_layout, vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment, 0U, diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.h b/src/video_core/renderer_vulkan/vk_graphics_pipeline.h index 3e51e652973..74817656a65 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.h +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.h @@ -5,7 +5,7 @@ #include "common/types.h" #include "video_core/renderer_vulkan/liverpool_to_vk.h" #include "video_core/renderer_vulkan/vk_common.h" -#include "video_core/renderer_vulkan/vk_compute_pipeline.h" +#include "video_core/renderer_vulkan/vk_pipeline_common.h" namespace VideoCore { class BufferCache; @@ -19,26 +19,22 @@ static constexpr u32 MaxShaderStages = 5; class Instance; class Scheduler; +class DescriptorHeap; using Liverpool = AmdGpu::Liverpool; struct GraphicsPipelineKey { std::array stage_hashes; std::array color_formats; + std::array mrt_swizzles; vk::Format depth_format; vk::Format stencil_format; - Liverpool::DepthControl depth; - float depth_bounds_min; - float depth_bounds_max; - float depth_bias_const_factor; - float depth_bias_slope_factor; - float depth_bias_clamp; + Liverpool::DepthControl depth_stencil; u32 depth_bias_enable; - u32 num_samples = 1; + u32 num_samples; + u32 mrt_mask; Liverpool::StencilControl stencil; - Liverpool::StencilRefMask stencil_ref_front; - Liverpool::StencilRefMask stencil_ref_back; Liverpool::PrimitiveType prim_type; u32 enable_primitive_restart; u32 primitive_restart_index; @@ -46,7 +42,7 @@ struct GraphicsPipelineKey { Liverpool::CullMode cull_mode; Liverpool::FrontFace front_face; Liverpool::ClipSpace clip_space; - Liverpool::ColorBufferMask cb_shader_mask{}; + Liverpool::ColorBufferMask cb_shader_mask; std::array blend_controls; std::array write_masks; @@ -55,25 +51,17 @@ struct GraphicsPipelineKey { } }; -class GraphicsPipeline { +class GraphicsPipeline : public Pipeline { public: - explicit GraphicsPipeline(const Instance& instance, Scheduler& scheduler, - const GraphicsPipelineKey& key, vk::PipelineCache pipeline_cache, - std::span stages, - std::span modules); + GraphicsPipeline(const Instance& instance, Scheduler& scheduler, DescriptorHeap& desc_heap, + const GraphicsPipelineKey& key, vk::PipelineCache pipeline_cache, + std::span stages, + std::span modules); ~GraphicsPipeline(); void BindResources(const Liverpool::Regs& regs, VideoCore::BufferCache& buffer_cache, VideoCore::TextureCache& texture_cache) const; - vk::Pipeline Handle() const noexcept { - return *pipeline; - } - - vk::PipelineLayout GetLayout() const { - return *pipeline_layout; - } - const Shader::Info& GetStage(Shader::Stage stage) const noexcept { return *stages[u32(stage)]; } @@ -87,21 +75,21 @@ class GraphicsPipeline { return key.write_masks; } + auto GetMrtMask() const { + return key.mrt_mask; + } + bool IsDepthEnabled() const { - return key.depth.depth_enable.Value(); + return key.depth_stencil.depth_enable.Value(); } private: void BuildDescSetLayout(); private: - const Instance& instance; - Scheduler& scheduler; - vk::UniquePipeline pipeline; - vk::UniquePipelineLayout pipeline_layout; - vk::UniqueDescriptorSetLayout desc_layout; std::array stages{}; GraphicsPipelineKey key; + bool uses_push_descriptors{}; }; } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_instance.cpp b/src/video_core/renderer_vulkan/vk_instance.cpp index e1a5cb4146b..52143907ca3 100644 --- a/src/video_core/renderer_vulkan/vk_instance.cpp +++ b/src/video_core/renderer_vulkan/vk_instance.cpp @@ -14,7 +14,10 @@ #include "video_core/renderer_vulkan/vk_instance.h" #include "video_core/renderer_vulkan/vk_platform.h" +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnullability-completeness" #include +#pragma GCC diagnostic pop namespace Vulkan { @@ -30,11 +33,16 @@ std::vector GetSupportedExtensions(vk::PhysicalDevice physical) { return supported_extensions; } -std::unordered_map GetFormatProperties( +std::unordered_map GetFormatProperties( vk::PhysicalDevice physical) { - std::unordered_map format_properties; + std::unordered_map format_properties; for (const auto& format : LiverpoolToVK::GetAllFormats()) { - format_properties.emplace(format, physical.getFormatProperties(format)); + vk::FormatProperties3 properties3{}; + vk::FormatProperties2 properties2 = { + .pNext = &properties3, + }; + physical.getFormatProperties2(format, &properties2); + format_properties.emplace(format, properties3); } return format_properties; } @@ -46,14 +54,15 @@ std::string GetReadableVersion(u32 version) { } // Anonymous namespace -Instance::Instance(bool enable_validation, bool dump_command_buffers) - : instance{CreateInstance(dl, Frontend::WindowSystemType::Headless, enable_validation, - dump_command_buffers)}, +Instance::Instance(bool enable_validation, bool enable_crash_diagnostic) + : instance{CreateInstance(Frontend::WindowSystemType::Headless, enable_validation, + enable_crash_diagnostic)}, physical_devices{instance->enumeratePhysicalDevices()} {} Instance::Instance(Frontend::WindowSDL& window, s32 physical_device_index, - bool enable_validation /*= false*/) - : instance{CreateInstance(dl, window.getWindowInfo().type, enable_validation, false)}, + bool enable_validation /*= false*/, bool enable_crash_diagnostic /*= false*/) + : instance{CreateInstance(window.getWindowInfo().type, enable_validation, + enable_crash_diagnostic)}, physical_devices{instance->enumeratePhysicalDevices()} { if (enable_validation) { debug_callback = CreateDebugCallback(*instance); @@ -118,11 +127,15 @@ Instance::Instance(Frontend::WindowSDL& window, s32 physical_device_index, // Check and log format support details. for (const auto& key : format_properties | std::views::keys) { const auto format = key; - if (!IsFormatSupported(format)) { + if (!IsImageFormatSupported(format)) { const auto alternative = GetAlternativeFormat(format); - if (IsFormatSupported(alternative)) { - LOG_WARNING(Render_Vulkan, "Format {} is not supported, falling back to {}", + if (IsImageFormatSupported(alternative)) { + LOG_WARNING(Render_Vulkan, + "Format {} is not supported for images, falling back to {}.", vk::to_string(format), vk::to_string(alternative)); + } else if (IsVertexFormatSupported(format)) { + LOG_WARNING(Render_Vulkan, "Format {} is only supported for vertex buffers.", + vk::to_string(format)); } else { LOG_ERROR(Render_Vulkan, "Format {} is not supported and no suitable alternative is supported.", @@ -168,8 +181,10 @@ bool Instance::CreateDevice() { vk::PhysicalDevicePortabilitySubsetFeaturesKHR>(); const vk::StructureChain properties_chain = physical_device.getProperties2< vk::PhysicalDeviceProperties2, vk::PhysicalDevicePortabilitySubsetPropertiesKHR, - vk::PhysicalDeviceExternalMemoryHostPropertiesEXT, vk::PhysicalDeviceVulkan11Properties>(); + vk::PhysicalDeviceExternalMemoryHostPropertiesEXT, vk::PhysicalDeviceVulkan11Properties, + vk::PhysicalDevicePushDescriptorPropertiesKHR>(); subgroup_size = properties_chain.get().subgroupSize; + push_descriptor_props = properties_chain.get(); LOG_INFO(Render_Vulkan, "Physical device subgroup size {}", subgroup_size); features = feature_chain.get().features; @@ -199,7 +214,7 @@ bool Instance::CreateDevice() { external_memory_host = add_extension(VK_EXT_EXTERNAL_MEMORY_HOST_EXTENSION_NAME); custom_border_color = add_extension(VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME); add_extension(VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME); - const bool depth_clip_control = add_extension(VK_EXT_DEPTH_CLIP_CONTROL_EXTENSION_NAME); + depth_clip_control = add_extension(VK_EXT_DEPTH_CLIP_CONTROL_EXTENSION_NAME); add_extension(VK_EXT_DEPTH_RANGE_UNRESTRICTED_EXTENSION_NAME); workgroup_memory_explicit_layout = add_extension(VK_KHR_WORKGROUP_MEMORY_EXPLICIT_LAYOUT_EXTENSION_NAME); @@ -212,21 +227,16 @@ bool Instance::CreateDevice() { const bool robustness = add_extension(VK_EXT_ROBUSTNESS_2_EXTENSION_NAME); const bool topology_restart = add_extension(VK_EXT_PRIMITIVE_TOPOLOGY_LIST_RESTART_EXTENSION_NAME); + const bool maintenance5 = add_extension(VK_KHR_MAINTENANCE_5_EXTENSION_NAME); // These extensions are promoted by Vulkan 1.3, but for greater compatibility we use Vulkan 1.2 // with extensions. tooling_info = add_extension(VK_EXT_TOOLING_INFO_EXTENSION_NAME); const bool maintenance4 = add_extension(VK_KHR_MAINTENANCE_4_EXTENSION_NAME); - const bool maintenance5 = add_extension(VK_KHR_MAINTENANCE_5_EXTENSION_NAME); + add_extension(VK_KHR_FORMAT_FEATURE_FLAGS_2_EXTENSION_NAME); add_extension(VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME); add_extension(VK_EXT_SHADER_DEMOTE_TO_HELPER_INVOCATION_EXTENSION_NAME); - const bool has_sync2 = add_extension(VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME); - - if (has_sync2) { - has_nv_checkpoints = Config::isMarkersEnabled() - ? add_extension(VK_NV_DEVICE_DIAGNOSTIC_CHECKPOINTS_EXTENSION_NAME) - : false; - } + add_extension(VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME); #ifdef __APPLE__ // Required by Vulkan spec if supported. @@ -272,6 +282,7 @@ bool Instance::CreateDevice() { vk::PhysicalDeviceFeatures2{ .features{ .robustBufferAccess = features.robustBufferAccess, + .imageCubeArray = features.imageCubeArray, .independentBlend = features.independentBlend, .geometryShader = features.geometryShader, .logicOp = features.logicOp, @@ -284,6 +295,7 @@ bool Instance::CreateDevice() { .shaderStorageImageExtendedFormats = features.shaderStorageImageExtendedFormats, .shaderStorageImageMultisample = features.shaderStorageImageMultisample, .shaderClipDistance = features.shaderClipDistance, + .shaderFloat64 = features.shaderFloat64, .shaderInt64 = features.shaderInt64, .shaderInt16 = features.shaderInt16, }, @@ -295,8 +307,10 @@ bool Instance::CreateDevice() { .shaderFloat16 = vk12_features.shaderFloat16, .scalarBlockLayout = vk12_features.scalarBlockLayout, .uniformBufferStandardLayout = vk12_features.uniformBufferStandardLayout, + .separateDepthStencilLayouts = vk12_features.separateDepthStencilLayouts, .hostQueryReset = vk12_features.hostQueryReset, .timelineSemaphore = vk12_features.timelineSemaphore, + .samplerMirrorClampToEdge = vk12_features.samplerMirrorClampToEdge, }, vk::PhysicalDeviceMaintenance4FeaturesKHR{ .maintenance4 = true, @@ -369,9 +383,12 @@ bool Instance::CreateDevice() { device_chain.unlink(); } if (robustness) { - device_chain.get().nullDescriptor = + null_descriptor = feature_chain.get().nullDescriptor; + device_chain.get().nullDescriptor = + null_descriptor; } else { + null_descriptor = false; device_chain.unlink(); } if (!vertex_input_dynamic_state) { @@ -479,7 +496,7 @@ void Instance::CollectToolingInfo() { } } -bool Instance::IsFormatSupported(const vk::Format format) const { +bool Instance::IsImageFormatSupported(const vk::Format format) const { if (format == vk::Format::eUndefined) [[unlikely]] { return true; } @@ -489,12 +506,26 @@ bool Instance::IsFormatSupported(const vk::Format format) const { UNIMPLEMENTED_MSG("Properties of format {} have not been queried.", vk::to_string(format)); } - constexpr vk::FormatFeatureFlags optimal_flags = vk::FormatFeatureFlagBits::eTransferSrc | - vk::FormatFeatureFlagBits::eTransferDst | - vk::FormatFeatureFlagBits::eSampledImage; + constexpr vk::FormatFeatureFlags2 optimal_flags = vk::FormatFeatureFlagBits2::eTransferSrc | + vk::FormatFeatureFlagBits2::eTransferDst | + vk::FormatFeatureFlagBits2::eSampledImage; return (it->second.optimalTilingFeatures & optimal_flags) == optimal_flags; } +bool Instance::IsVertexFormatSupported(const vk::Format format) const { + if (format == vk::Format::eUndefined) [[unlikely]] { + return true; + } + + const auto it = format_properties.find(format); + if (it == format_properties.end()) { + UNIMPLEMENTED_MSG("Properties of format {} have not been queried.", vk::to_string(format)); + } + + constexpr vk::FormatFeatureFlags2 optimal_flags = vk::FormatFeatureFlagBits2::eVertexBuffer; + return (it->second.bufferFeatures & optimal_flags) == optimal_flags; +} + vk::Format Instance::GetAlternativeFormat(const vk::Format format) const { if (format == vk::Format::eB5G6R5UnormPack16) { return vk::Format::eR5G6B5UnormPack16; @@ -505,11 +536,11 @@ vk::Format Instance::GetAlternativeFormat(const vk::Format format) const { } vk::Format Instance::GetSupportedFormat(const vk::Format format) const { - if (IsFormatSupported(format)) [[likely]] { + if (IsImageFormatSupported(format)) [[likely]] { return format; } const vk::Format alternative = GetAlternativeFormat(format); - if (IsFormatSupported(alternative)) [[likely]] { + if (IsImageFormatSupported(alternative)) [[likely]] { return alternative; } return format; @@ -517,7 +548,7 @@ vk::Format Instance::GetSupportedFormat(const vk::Format format) const { vk::ComponentMapping Instance::GetSupportedComponentSwizzle(vk::Format format, vk::ComponentMapping swizzle) const { - if (IsFormatSupported(format)) [[likely]] { + if (IsImageFormatSupported(format)) [[likely]] { return swizzle; } diff --git a/src/video_core/renderer_vulkan/vk_instance.h b/src/video_core/renderer_vulkan/vk_instance.h index 5f985d4aebb..1c94f586ee3 100644 --- a/src/video_core/renderer_vulkan/vk_instance.h +++ b/src/video_core/renderer_vulkan/vk_instance.h @@ -17,19 +17,13 @@ class WindowSDL; VK_DEFINE_HANDLE(VmaAllocator) -#ifdef __APPLE__ -#define VULKAN_LIBRARY_NAME "libMoltenVK.dylib" -#else -#define VULKAN_LIBRARY_NAME -#endif - namespace Vulkan { class Instance { public: - explicit Instance(bool validation = false, bool dump_command_buffers = false); + explicit Instance(bool validation = false, bool crash_diagnostic = false); explicit Instance(Frontend::WindowSDL& window, s32 physical_device_index, - bool enable_validation = false); + bool enable_validation = false, bool enable_crash_diagnostic = false); ~Instance(); /// Returns a formatted string for the driver version @@ -88,10 +82,6 @@ class Instance { return profiler_context; } - bool HasNvCheckpoints() const { - return has_nv_checkpoints; - } - /// Returns true when a known debugging tool is attached. bool HasDebuggingToolAttached() const { return has_renderdoc || has_nsight_graphics; @@ -127,6 +117,11 @@ class Instance { return external_memory_host; } + /// Returns true when VK_EXT_depth_clip_control is supported + bool IsDepthClipControlSupported() const { + return depth_clip_control; + } + /// Returns true when VK_EXT_color_write_enable is supported bool IsColorWriteEnableSupported() const { return color_write_en; @@ -137,6 +132,11 @@ class Instance { return vertex_input_dynamic_state; } + /// Returns true when the nullDescriptor feature of VK_EXT_robustness2 is supported. + bool IsNullDescriptorSupported() const { + return null_descriptor; + } + /// Returns the vendor ID of the physical device u32 GetVendorID() const { return properties.vendorID; @@ -212,6 +212,11 @@ class Instance { return properties.limits.maxTexelBufferElements; } + /// Returns the maximum number of push descriptors. + u32 MaxPushDescriptors() const { + return push_descriptor_props.maxPushDescriptors; + } + /// Returns true if shaders can declare the ClipDistance attribute bool IsShaderClipDistanceSupported() const { return features.shaderClipDistance; @@ -222,6 +227,13 @@ class Instance { return min_imported_host_pointer_alignment; } + /// Returns the sample count flags supported by framebuffers. + vk::SampleCountFlags GetFramebufferSampleCounts() const { + return properties.limits.framebufferColorSampleCounts & + properties.limits.framebufferDepthSampleCounts & + properties.limits.framebufferStencilSampleCounts; + } + private: /// Creates the logical device opportunistically enabling extensions bool CreateDevice(); @@ -233,18 +245,21 @@ class Instance { void CollectDeviceParameters(); void CollectToolingInfo(); - /// Determines if a format is supported. - [[nodiscard]] bool IsFormatSupported(vk::Format format) const; + /// Determines if a format is supported for images. + [[nodiscard]] bool IsImageFormatSupported(vk::Format format) const; + + /// Determines if a format is supported for vertex buffers. + [[nodiscard]] bool IsVertexFormatSupported(vk::Format format) const; /// Gets a commonly available alternative for an unsupported pixel format. vk::Format GetAlternativeFormat(const vk::Format format) const; private: - vk::DynamicLoader dl{VULKAN_LIBRARY_NAME}; vk::UniqueInstance instance; vk::PhysicalDevice physical_device; vk::UniqueDevice device; vk::PhysicalDeviceProperties properties; + vk::PhysicalDevicePushDescriptorPropertiesKHR push_descriptor_props; vk::PhysicalDeviceFeatures features; vk::DriverIdKHR driver_id; vk::UniqueDebugUtilsMessengerEXT debug_callback{}; @@ -254,7 +269,7 @@ class Instance { vk::Queue graphics_queue; std::vector physical_devices; std::vector available_extensions; - std::unordered_map format_properties; + std::unordered_map format_properties; TracyVkCtx profiler_context{}; u32 queue_family_index{0}; bool image_view_reinterpretation{true}; @@ -265,16 +280,17 @@ class Instance { bool fragment_shader_barycentric{}; bool shader_stencil_export{}; bool external_memory_host{}; + bool depth_clip_control{}; bool workgroup_memory_explicit_layout{}; bool color_write_en{}; bool vertex_input_dynamic_state{}; + bool null_descriptor{}; u64 min_imported_host_pointer_alignment{}; u32 subgroup_size{}; bool tooling_info{}; bool debug_utils_supported{}; bool has_nsight_graphics{}; bool has_renderdoc{}; - bool has_nv_checkpoints{}; }; } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index fa5475102d2..f00a8a32089 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -1,21 +1,141 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include "shader_recompiler/runtime_info.h" +#include + +#include "common/config.h" +#include "common/io_file.h" +#include "common/path_util.h" +#include "shader_recompiler/backend/spirv/emit_spirv.h" +#include "shader_recompiler/info.h" #include "video_core/renderer_vulkan/renderer_vulkan.h" #include "video_core/renderer_vulkan/vk_instance.h" #include "video_core/renderer_vulkan/vk_pipeline_cache.h" #include "video_core/renderer_vulkan/vk_scheduler.h" -#include "video_core/renderer_vulkan/vk_shader_cache.h" +#include "video_core/renderer_vulkan/vk_shader_util.h" extern std::unique_ptr renderer; namespace Vulkan { +using Shader::VsOutput; + +[[nodiscard]] inline u64 HashCombine(const u64 seed, const u64 hash) { + return seed ^ (hash + 0x9e3779b9 + (seed << 6) + (seed >> 2)); +} + +constexpr static std::array DescriptorHeapSizes = { + vk::DescriptorPoolSize{vk::DescriptorType::eUniformBuffer, 8192}, + vk::DescriptorPoolSize{vk::DescriptorType::eStorageBuffer, 1024}, + vk::DescriptorPoolSize{vk::DescriptorType::eUniformTexelBuffer, 128}, + vk::DescriptorPoolSize{vk::DescriptorType::eStorageTexelBuffer, 128}, + vk::DescriptorPoolSize{vk::DescriptorType::eSampledImage, 8192}, + vk::DescriptorPoolSize{vk::DescriptorType::eSampler, 1024}, +}; + +void GatherVertexOutputs(Shader::VertexRuntimeInfo& info, + const AmdGpu::Liverpool::VsOutputControl& ctl) { + const auto add_output = [&](VsOutput x, VsOutput y, VsOutput z, VsOutput w) { + if (x != VsOutput::None || y != VsOutput::None || z != VsOutput::None || + w != VsOutput::None) { + info.outputs.emplace_back(Shader::VsOutputMap{x, y, z, w}); + } + }; + // VS_OUT_MISC_VEC + add_output(ctl.use_vtx_point_size ? VsOutput::PointSprite : VsOutput::None, + ctl.use_vtx_edge_flag + ? VsOutput::EdgeFlag + : (ctl.use_vtx_gs_cut_flag ? VsOutput::GsCutFlag : VsOutput::None), + ctl.use_vtx_kill_flag + ? VsOutput::KillFlag + : (ctl.use_vtx_render_target_idx ? VsOutput::GsMrtIndex : VsOutput::None), + ctl.use_vtx_viewport_idx ? VsOutput::GsVpIndex : VsOutput::None); + // VS_OUT_CCDIST0 + add_output(ctl.IsClipDistEnabled(0) + ? VsOutput::ClipDist0 + : (ctl.IsCullDistEnabled(0) ? VsOutput::CullDist0 : VsOutput::None), + ctl.IsClipDistEnabled(1) + ? VsOutput::ClipDist1 + : (ctl.IsCullDistEnabled(1) ? VsOutput::CullDist1 : VsOutput::None), + ctl.IsClipDistEnabled(2) + ? VsOutput::ClipDist2 + : (ctl.IsCullDistEnabled(2) ? VsOutput::CullDist2 : VsOutput::None), + ctl.IsClipDistEnabled(3) + ? VsOutput::ClipDist3 + : (ctl.IsCullDistEnabled(3) ? VsOutput::CullDist3 : VsOutput::None)); + // VS_OUT_CCDIST1 + add_output(ctl.IsClipDistEnabled(4) + ? VsOutput::ClipDist4 + : (ctl.IsCullDistEnabled(4) ? VsOutput::CullDist4 : VsOutput::None), + ctl.IsClipDistEnabled(5) + ? VsOutput::ClipDist5 + : (ctl.IsCullDistEnabled(5) ? VsOutput::CullDist5 : VsOutput::None), + ctl.IsClipDistEnabled(6) + ? VsOutput::ClipDist6 + : (ctl.IsCullDistEnabled(6) ? VsOutput::CullDist6 : VsOutput::None), + ctl.IsClipDistEnabled(7) + ? VsOutput::ClipDist7 + : (ctl.IsCullDistEnabled(7) ? VsOutput::CullDist7 : VsOutput::None)); +} + +Shader::RuntimeInfo PipelineCache::BuildRuntimeInfo(Shader::Stage stage) { + auto info = Shader::RuntimeInfo{stage}; + const auto& regs = liverpool->regs; + switch (stage) { + case Shader::Stage::Vertex: { + info.num_user_data = regs.vs_program.settings.num_user_regs; + info.num_input_vgprs = regs.vs_program.settings.vgpr_comp_cnt; + info.num_allocated_vgprs = regs.vs_program.settings.num_vgprs * 4; + GatherVertexOutputs(info.vs_info, regs.vs_output_control); + info.vs_info.emulate_depth_negative_one_to_one = + !instance.IsDepthClipControlSupported() && + regs.clipper_control.clip_space == Liverpool::ClipSpace::MinusWToW; + break; + } + case Shader::Stage::Fragment: { + info.num_user_data = regs.ps_program.settings.num_user_regs; + info.num_allocated_vgprs = regs.ps_program.settings.num_vgprs * 4; + std::ranges::transform(graphics_key.mrt_swizzles, info.fs_info.mrt_swizzles.begin(), + [](Liverpool::ColorBuffer::SwapMode mode) { + return static_cast(mode); + }); + const auto& ps_inputs = regs.ps_inputs; + for (u32 i = 0; i < regs.num_interp; i++) { + info.fs_info.inputs.push_back({ + .param_index = u8(ps_inputs[i].input_offset.Value()), + .is_default = bool(ps_inputs[i].use_default), + .is_flat = bool(ps_inputs[i].flat_shade), + .default_value = u8(ps_inputs[i].default_value), + }); + } + break; + } + case Shader::Stage::Compute: { + const auto& cs_pgm = regs.cs_program; + info.num_user_data = cs_pgm.settings.num_user_regs; + info.num_allocated_vgprs = regs.cs_program.settings.num_vgprs * 4; + info.cs_info.workgroup_size = {cs_pgm.num_thread_x.full, cs_pgm.num_thread_y.full, + cs_pgm.num_thread_z.full}; + info.cs_info.tgid_enable = {cs_pgm.IsTgidEnabled(0), cs_pgm.IsTgidEnabled(1), + cs_pgm.IsTgidEnabled(2)}; + info.cs_info.shared_memory_size = cs_pgm.SharedMemSize(); + break; + } + default: + break; + } + return info; +} + PipelineCache::PipelineCache(const Instance& instance_, Scheduler& scheduler_, AmdGpu::Liverpool* liverpool_) : instance{instance_}, scheduler{scheduler_}, liverpool{liverpool_}, - shader_cache{std::make_unique(instance, liverpool)} { + desc_heap{instance, scheduler.GetMasterSemaphore(), DescriptorHeapSizes} { + profile = Shader::Profile{ + .supported_spirv = instance.ApiVersion() >= VK_API_VERSION_1_3 ? 0x00010600U : 0x00010500U, + .subgroup_size = instance.SubgroupSize(), + .support_explicit_workgroup_layout = true, + }; pipeline_cache = instance.GetDevice().createPipelineCacheUnique({}); } @@ -38,16 +158,19 @@ const GraphicsPipeline* PipelineCache::GetGraphicsPipeline() { LOG_TRACE(Render_Vulkan, "FMask decompression pass skipped"); return nullptr; } + if (regs.primitive_type == Liverpool::PrimitiveType::None) { + LOG_TRACE(Render_Vulkan, "Primitive type 'None' skipped"); + return nullptr; + } if (!RefreshGraphicsKey()) { return nullptr; } const auto [it, is_new] = graphics_pipelines.try_emplace(graphics_key); if (is_new) { - it.value() = std::make_unique(instance, scheduler, graphics_key, - *pipeline_cache, infos, modules); + it.value() = graphics_pipeline_pool.Create(instance, scheduler, desc_heap, graphics_key, + *pipeline_cache, infos, modules); } - const GraphicsPipeline* pipeline = it->second.get(); - return pipeline; + return it->second; } const ComputePipeline* PipelineCache::GetComputePipeline() { @@ -56,22 +179,20 @@ const ComputePipeline* PipelineCache::GetComputePipeline() { } const auto [it, is_new] = compute_pipelines.try_emplace(compute_key); if (is_new) { - it.value() = std::make_unique(instance, scheduler, *pipeline_cache, - compute_key, *infos[0], modules[0]); + it.value() = compute_pipeline_pool.Create(instance, scheduler, desc_heap, *pipeline_cache, + compute_key, *infos[0], modules[0]); } - const ComputePipeline* pipeline = it->second.get(); - return pipeline; + return it->second; } // 0x3f84ec55ULL - Amplitude V_MOVRELD_B32 -// 0xc7f34c4fULL - Amplitude V_CMPX_LE_I32 -// 0x28080e22ULL - -// 0x13a1d5fcULL - +// 0xc7f34c4fULL - Amplitude/Rock band 4 V_CMPX_LE_I32 +// 0x13a1d5fcULL - Rock Band 4 1.08 V_MOVRELD_B32 +// 0x28080e22ULL - Rock Band 4 Rivals (2.21) V_MOVRELD_B32 // 0xce54e4ddULL - Rock Band 4 V_MOVRELS_B32 - DO NOT USE, makes notes invisible bool ShouldSkipShader(u64 shader_hash, const char* shader_type) { - static constexpr std::array skip_hashes = {0x3f84ec55ULL, 0xc7f34c4fULL, 0x28080e22ULL, - 0x13a1d5fcULL}; + static constexpr std::array skip_hashes = {}; if (std::ranges::contains(skip_hashes, shader_hash)) { LOG_WARNING(Render_Vulkan, "Skipped {} shader hash {:#x}.", shader_type, shader_hash); return true; @@ -80,88 +201,76 @@ bool ShouldSkipShader(u64 shader_hash, const char* shader_type) { } bool PipelineCache::RefreshGraphicsKey() { + std::memset(&graphics_key, 0, sizeof(GraphicsPipelineKey)); + auto& regs = liverpool->regs; auto& key = graphics_key; - key.depth = regs.depth_control; - key.depth.depth_write_enable.Assign(regs.depth_control.depth_write_enable.Value() && - !regs.depth_render_control.depth_clear_enable); - key.depth_bounds_min = regs.depth_bounds_min; - key.depth_bounds_max = regs.depth_bounds_max; - key.depth_bias_enable = regs.polygon_control.enable_polygon_offset_back || - regs.polygon_control.enable_polygon_offset_front || - regs.polygon_control.enable_polygon_offset_para; - if (regs.polygon_control.enable_polygon_offset_front) { - key.depth_bias_const_factor = regs.poly_offset.front_offset; - key.depth_bias_slope_factor = regs.poly_offset.front_scale; - } else { - key.depth_bias_const_factor = regs.poly_offset.back_offset; - key.depth_bias_slope_factor = regs.poly_offset.back_scale; - } - key.depth_bias_clamp = regs.poly_offset.depth_bias; - key.stencil = regs.stencil_control; - key.stencil_ref_front = regs.stencil_ref_front; - key.stencil_ref_back = regs.stencil_ref_back; - key.prim_type = regs.primitive_type; - key.enable_primitive_restart = regs.enable_primitive_restart & 1; - key.primitive_restart_index = regs.primitive_restart_index; - key.polygon_mode = regs.polygon_control.PolyMode(); - key.cull_mode = regs.polygon_control.CullingMode(); - key.clip_space = regs.clipper_control.clip_space; - key.front_face = regs.polygon_control.front_face; - key.num_samples = regs.aa_config.NumSamples(); + key.depth_stencil = regs.depth_control; + key.depth_stencil.depth_write_enable.Assign(regs.depth_control.depth_write_enable.Value() && + !regs.depth_render_control.depth_clear_enable); + key.depth_bias_enable = regs.polygon_control.NeedsBias(); const auto& db = regs.depth_buffer; const auto ds_format = LiverpoolToVK::DepthFormat(db.z_info.format, db.stencil_info.format); - if (db.z_info.format != AmdGpu::Liverpool::DepthBuffer::ZFormat::Invalid) { key.depth_format = ds_format; } else { key.depth_format = vk::Format::eUndefined; } - if (key.depth.depth_enable) { - key.depth.depth_enable.Assign(key.depth_format != vk::Format::eUndefined); + if (regs.depth_control.depth_enable) { + key.depth_stencil.depth_enable.Assign(key.depth_format != vk::Format::eUndefined); } + key.stencil = regs.stencil_control; if (db.stencil_info.format != AmdGpu::Liverpool::DepthBuffer::StencilFormat::Invalid) { key.stencil_format = key.depth_format; } else { key.stencil_format = vk::Format::eUndefined; } - if (key.depth.stencil_enable) { - key.depth.stencil_enable.Assign(key.stencil_format != vk::Format::eUndefined); + if (key.depth_stencil.stencil_enable) { + key.depth_stencil.stencil_enable.Assign(key.stencil_format != vk::Format::eUndefined); } + key.prim_type = regs.primitive_type; + key.enable_primitive_restart = regs.enable_primitive_restart & 1; + key.primitive_restart_index = regs.primitive_restart_index; + key.polygon_mode = regs.polygon_control.PolyMode(); + key.cull_mode = regs.polygon_control.CullingMode(); + key.clip_space = regs.clipper_control.clip_space; + key.front_face = regs.polygon_control.front_face; + key.num_samples = regs.aa_config.NumSamples(); - const auto skip_cb_binding = + const bool skip_cb_binding = regs.color_control.mode == AmdGpu::Liverpool::ColorControl::OperationMode::Disable; // `RenderingInfo` is assumed to be initialized with a contiguous array of valid color - // attachments. This might be not a case as HW color buffers can be bound in an arbitrary order. - // We need to do some arrays compaction at this stage + // attachments. This might be not a case as HW color buffers can be bound in an arbitrary + // order. We need to do some arrays compaction at this stage key.color_formats.fill(vk::Format::eUndefined); key.blend_controls.fill({}); key.write_masks.fill({}); - int remapped_cb{}; - for (auto cb = 0u; cb < Liverpool::NumColorBuffers; ++cb) { + key.mrt_swizzles.fill(Liverpool::ColorBuffer::SwapMode::Standard); + + // First pass of bindings check to idenitfy formats and swizzles and pass them to rhe shader + // recompiler. + for (auto cb = 0u, remapped_cb = 0u; cb < Liverpool::NumColorBuffers; ++cb) { auto const& col_buf = regs.color_buffers[cb]; if (skip_cb_binding || !col_buf || !regs.color_target_mask.GetMask(cb)) { continue; } const auto base_format = LiverpoolToVK::SurfaceFormat(col_buf.info.format, col_buf.NumFormat()); - const auto is_vo_surface = renderer->IsVideoOutSurface(col_buf); + const bool is_vo_surface = renderer->IsVideoOutSurface(col_buf); key.color_formats[remapped_cb] = LiverpoolToVK::AdjustColorBufferFormat( base_format, col_buf.info.comp_swap.Value(), false /*is_vo_surface*/); - key.blend_controls[remapped_cb] = regs.blend_control[cb]; - key.blend_controls[remapped_cb].enable.Assign(key.blend_controls[remapped_cb].enable && - !col_buf.info.blend_bypass); - key.write_masks[remapped_cb] = vk::ColorComponentFlags{regs.color_target_mask.GetMask(cb)}; - key.cb_shader_mask = regs.color_shader_mask; + if (base_format == key.color_formats[remapped_cb]) { + key.mrt_swizzles[remapped_cb] = col_buf.info.comp_swap.Value(); + } ++remapped_cb; } - u32 binding{}; + Shader::Backend::Bindings binding{}; for (u32 i = 0; i < MaxShaderStages; i++) { if (!regs.stage_enable.IsStageEnabled(i)) { key.stage_hashes[i] = 0; @@ -176,6 +285,7 @@ bool PipelineCache::RefreshGraphicsKey() { } const auto* bininfo = Liverpool::GetBinaryInfo(*pgm); if (!bininfo->Valid()) { + LOG_WARNING(Render_Vulkan, "Invalid binary info structure!"); key.stage_hashes[i] = 0; infos[i] = nullptr; continue; @@ -183,23 +293,126 @@ bool PipelineCache::RefreshGraphicsKey() { if (ShouldSkipShader(bininfo->shader_hash, "graphics")) { return false; } - const auto stage = Shader::Stage{i}; - const GuestProgram guest_pgm{pgm, stage}; - std::tie(infos[i], modules[i], key.stage_hashes[i]) = - shader_cache->GetProgram(guest_pgm, binding); + const auto stage = Shader::StageFromIndex(i); + const auto params = Liverpool::GetParams(*pgm); + + if (stage != Shader::Stage::Vertex && stage != Shader::Stage::Fragment) { + return false; + } + + static bool TessMissingLogged = false; + if (auto* pgm = regs.ProgramForStage(3); + regs.stage_enable.IsStageEnabled(3) && pgm->Address() != 0) { + if (!TessMissingLogged) { + LOG_WARNING(Render_Vulkan, "Tess pipeline compilation skipped"); + TessMissingLogged = true; + } + return false; + } + + std::tie(infos[i], modules[i], key.stage_hashes[i]) = GetProgram(stage, params, binding); + } + + const auto* fs_info = infos[u32(Shader::Stage::Fragment)]; + key.mrt_mask = fs_info ? fs_info->mrt_mask : 0u; + + // Second pass to fill remain CB pipeline key data + for (auto cb = 0u, remapped_cb = 0u; cb < Liverpool::NumColorBuffers; ++cb) { + auto const& col_buf = regs.color_buffers[cb]; + if (skip_cb_binding || !col_buf || !regs.color_target_mask.GetMask(cb) || + (key.mrt_mask & (1u << cb)) == 0) { + key.color_formats[cb] = vk::Format::eUndefined; + key.mrt_swizzles[cb] = Liverpool::ColorBuffer::SwapMode::Standard; + continue; + } + + key.blend_controls[remapped_cb] = regs.blend_control[cb]; + key.blend_controls[remapped_cb].enable.Assign(key.blend_controls[remapped_cb].enable && + !col_buf.info.blend_bypass); + key.write_masks[remapped_cb] = vk::ColorComponentFlags{regs.color_target_mask.GetMask(cb)}; + key.cb_shader_mask.SetMask(remapped_cb, regs.color_shader_mask.GetMask(cb)); + + ++remapped_cb; } return true; } bool PipelineCache::RefreshComputeKey() { - u32 binding{}; + Shader::Backend::Bindings binding{}; const auto* cs_pgm = &liverpool->regs.cs_program; - const GuestProgram guest_pgm{cs_pgm, Shader::Stage::Compute}; - if (ShouldSkipShader(guest_pgm.hash, "compute")) { + const auto cs_params = Liverpool::GetParams(*cs_pgm); + if (ShouldSkipShader(cs_params.hash, "compute")) { return false; } - std::tie(infos[0], modules[0], compute_key) = shader_cache->GetProgram(guest_pgm, binding); + std::tie(infos[0], modules[0], compute_key) = + GetProgram(Shader::Stage::Compute, cs_params, binding); return true; } +vk::ShaderModule PipelineCache::CompileModule(Shader::Info& info, + const Shader::RuntimeInfo& runtime_info, + std::span code, size_t perm_idx, + Shader::Backend::Bindings& binding) { + LOG_INFO(Render_Vulkan, "Compiling {} shader {:#x} {}", info.stage, info.pgm_hash, + perm_idx != 0 ? "(permutation)" : ""); + if (Config::dumpShaders()) { + DumpShader(code, info.pgm_hash, info.stage, perm_idx, "bin"); + } + const auto ir_program = Shader::TranslateProgram(code, pools, info, runtime_info, profile); + const auto spv = Shader::Backend::SPIRV::EmitSPIRV(profile, runtime_info, ir_program, binding); + if (Config::dumpShaders()) { + DumpShader(spv, info.pgm_hash, info.stage, perm_idx, "spv"); + } + + const auto module = CompileSPV(spv, instance.GetDevice()); + const auto name = fmt::format("{}_{:#x}_{}", info.stage, info.pgm_hash, perm_idx); + Vulkan::SetObjectName(instance.GetDevice(), module, name); + return module; +} + +std::tuple PipelineCache::GetProgram( + Shader::Stage stage, Shader::ShaderParams params, Shader::Backend::Bindings& binding) { + const auto runtime_info = BuildRuntimeInfo(stage); + auto [it_pgm, new_program] = program_cache.try_emplace(params.hash); + if (new_program) { + Program* program = program_pool.Create(stage, params); + auto start = binding; + const auto module = CompileModule(program->info, runtime_info, params.code, 0, binding); + const auto spec = Shader::StageSpecialization(program->info, runtime_info, start); + program->AddPermut(module, std::move(spec)); + it_pgm.value() = program; + return std::make_tuple(&program->info, module, HashCombine(params.hash, 0)); + } + + Program* program = it_pgm->second; + const auto& info = program->info; + const auto spec = Shader::StageSpecialization(info, runtime_info, binding); + size_t perm_idx = program->modules.size(); + vk::ShaderModule module{}; + + const auto it = std::ranges::find(program->modules, spec, &Program::Module::spec); + if (it == program->modules.end()) { + auto new_info = Shader::Info(stage, params); + module = CompileModule(new_info, runtime_info, params.code, perm_idx, binding); + program->AddPermut(module, std::move(spec)); + } else { + info.AddBindings(binding); + module = it->module; + perm_idx = std::distance(program->modules.begin(), it); + } + return std::make_tuple(&info, module, HashCombine(params.hash, perm_idx)); +} + +void PipelineCache::DumpShader(std::span code, u64 hash, Shader::Stage stage, + size_t perm_idx, std::string_view ext) { + using namespace Common::FS; + const auto dump_dir = GetUserPath(PathType::ShaderDir) / "dumps"; + if (!std::filesystem::exists(dump_dir)) { + std::filesystem::create_directories(dump_dir); + } + const auto filename = fmt::format("{}_{:#018x}_{}.{}", stage, hash, perm_idx, ext); + const auto file = IOFile{dump_dir / filename, FileAccessMode::Write}; + file.WriteSpan(code); +} + } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.h b/src/video_core/renderer_vulkan/vk_pipeline_cache.h index 40853b74639..7e44bbf0956 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.h +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.h @@ -4,8 +4,12 @@ #pragma once #include +#include "shader_recompiler/profile.h" +#include "shader_recompiler/recompiler.h" +#include "shader_recompiler/specialization.h" #include "video_core/renderer_vulkan/vk_compute_pipeline.h" #include "video_core/renderer_vulkan/vk_graphics_pipeline.h" +#include "video_core/renderer_vulkan/vk_resource_pool.h" namespace Shader { struct Info; @@ -17,6 +21,22 @@ class Instance; class Scheduler; class ShaderCache; +struct Program { + struct Module { + vk::ShaderModule module; + Shader::StageSpecialization spec; + }; + + Shader::Info info; + boost::container::small_vector modules; + + explicit Program(Shader::Stage stage, Shader::ShaderParams params) : info{stage, params} {} + + void AddPermut(vk::ShaderModule module, const Shader::StageSpecialization&& spec) { + modules.emplace_back(module, std::move(spec)); + } +}; + class PipelineCache { static constexpr size_t MaxShaderStages = 5; @@ -29,19 +49,35 @@ class PipelineCache { const ComputePipeline* GetComputePipeline(); + std::tuple GetProgram( + Shader::Stage stage, Shader::ShaderParams params, Shader::Backend::Bindings& binding); + private: bool RefreshGraphicsKey(); bool RefreshComputeKey(); + void DumpShader(std::span code, u64 hash, Shader::Stage stage, size_t perm_idx, + std::string_view ext); + vk::ShaderModule CompileModule(Shader::Info& info, const Shader::RuntimeInfo& runtime_info, + std::span code, size_t perm_idx, + Shader::Backend::Bindings& binding); + Shader::RuntimeInfo BuildRuntimeInfo(Shader::Stage stage); + private: const Instance& instance; Scheduler& scheduler; AmdGpu::Liverpool* liverpool; + DescriptorHeap desc_heap; vk::UniquePipelineCache pipeline_cache; vk::UniquePipelineLayout pipeline_layout; - std::unique_ptr shader_cache; - tsl::robin_map> compute_pipelines; - tsl::robin_map> graphics_pipelines; + Shader::Profile profile{}; + Shader::Pools pools; + tsl::robin_map program_cache; + Common::ObjectPool program_pool; + Common::ObjectPool graphics_pipeline_pool; + Common::ObjectPool compute_pipeline_pool; + tsl::robin_map compute_pipelines; + tsl::robin_map graphics_pipelines; std::array infos{}; std::array modules{}; GraphicsPipelineKey graphics_key{}; diff --git a/src/video_core/renderer_vulkan/vk_pipeline_common.cpp b/src/video_core/renderer_vulkan/vk_pipeline_common.cpp new file mode 100644 index 00000000000..61e564150ac --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_pipeline_common.cpp @@ -0,0 +1,81 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include + +#include "shader_recompiler/info.h" +#include "video_core/renderer_vulkan/vk_instance.h" +#include "video_core/renderer_vulkan/vk_pipeline_common.h" +#include "video_core/renderer_vulkan/vk_scheduler.h" +#include "video_core/texture_cache/texture_cache.h" + +namespace Vulkan { + +boost::container::static_vector Pipeline::image_infos; + +Pipeline::Pipeline(const Instance& instance_, Scheduler& scheduler_, DescriptorHeap& desc_heap_, + vk::PipelineCache pipeline_cache) + : instance{instance_}, scheduler{scheduler_}, desc_heap{desc_heap_} {} + +Pipeline::~Pipeline() = default; + +void Pipeline::BindTextures(VideoCore::TextureCache& texture_cache, const Shader::Info& stage, + Shader::Backend::Bindings& binding, + DescriptorWrites& set_writes) const { + + using ImageBindingInfo = std::tuple; + boost::container::static_vector image_bindings; + + for (const auto& image_desc : stage.images) { + const auto tsharp = image_desc.GetSharp(stage); + if (tsharp.GetDataFmt() != AmdGpu::DataFormat::FormatInvalid) { + VideoCore::ImageInfo image_info{tsharp, image_desc}; + const auto image_id = texture_cache.FindImage(image_info); + auto& image = texture_cache.GetImage(image_id); + image.flags |= VideoCore::ImageFlagBits::Bound; + image_bindings.emplace_back(image_id, tsharp, image_desc); + } else { + image_bindings.emplace_back(VideoCore::ImageId{}, tsharp, image_desc); + } + + if (texture_cache.IsMeta(tsharp.Address())) { + LOG_WARNING(Render_Vulkan, "Unexpected metadata read by a PS shader (texture)"); + } + } + + // Second pass to re-bind images that were updated after binding + for (auto [image_id, tsharp, desc] : image_bindings) { + if (!image_id) { + if (instance.IsNullDescriptorSupported()) { + image_infos.emplace_back(VK_NULL_HANDLE, VK_NULL_HANDLE, vk::ImageLayout::eGeneral); + } else { + auto& null_image = texture_cache.GetImageView(VideoCore::NULL_IMAGE_VIEW_ID); + image_infos.emplace_back(VK_NULL_HANDLE, *null_image.image_view, + vk::ImageLayout::eGeneral); + } + } else { + auto& image = texture_cache.GetImage(image_id); + if (True(image.flags & VideoCore::ImageFlagBits::NeedsRebind)) { + image_id = texture_cache.FindImage(image.info); + } + VideoCore::ImageViewInfo view_info{tsharp, desc}; + auto& image_view = texture_cache.FindTexture(image_id, view_info); + image_infos.emplace_back(VK_NULL_HANDLE, *image_view.image_view, + texture_cache.GetImage(image_id).last_state.layout); + image.flags &= + ~(VideoCore::ImageFlagBits::NeedsRebind | VideoCore::ImageFlagBits::Bound); + } + + set_writes.push_back({ + .dstSet = VK_NULL_HANDLE, + .dstBinding = binding.unified++, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = desc.is_storage ? vk::DescriptorType::eStorageImage + : vk::DescriptorType::eSampledImage, + .pImageInfo = &image_infos.back(), + }); + } +} + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_pipeline_common.h b/src/video_core/renderer_vulkan/vk_pipeline_common.h new file mode 100644 index 00000000000..ab99e7b33ff --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_pipeline_common.h @@ -0,0 +1,49 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "shader_recompiler/backend/bindings.h" +#include "shader_recompiler/info.h" +#include "video_core/renderer_vulkan/vk_common.h" + +namespace VideoCore { +class BufferCache; +class TextureCache; +} // namespace VideoCore + +namespace Vulkan { + +class Instance; +class Scheduler; +class DescriptorHeap; + +class Pipeline { +public: + Pipeline(const Instance& instance, Scheduler& scheduler, DescriptorHeap& desc_heap, + vk::PipelineCache pipeline_cache); + virtual ~Pipeline(); + + vk::Pipeline Handle() const noexcept { + return *pipeline; + } + + vk::PipelineLayout GetLayout() const noexcept { + return *pipeline_layout; + } + + using DescriptorWrites = boost::container::small_vector; + void BindTextures(VideoCore::TextureCache& texture_cache, const Shader::Info& stage, + Shader::Backend::Bindings& binding, DescriptorWrites& set_writes) const; + +protected: + const Instance& instance; + Scheduler& scheduler; + DescriptorHeap& desc_heap; + vk::UniquePipeline pipeline; + vk::UniquePipelineLayout pipeline_layout; + vk::UniqueDescriptorSetLayout desc_layout; + static boost::container::static_vector image_infos; +}; + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_platform.cpp b/src/video_core/renderer_vulkan/vk_platform.cpp index c73a8139dfb..6abd00aaa54 100644 --- a/src/video_core/renderer_vulkan/vk_platform.cpp +++ b/src/video_core/renderer_vulkan/vk_platform.cpp @@ -17,13 +17,23 @@ #include "common/assert.h" #include "common/config.h" #include "common/logging/log.h" +#include "common/path_util.h" #include "sdl_window.h" #include "video_core/renderer_vulkan/vk_platform.h" +#if VULKAN_HPP_ENABLE_DYNAMIC_LOADER_TOOL +static vk::DynamicLoader dl; +#else +extern "C" { +VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL vkGetInstanceProcAddr(VkInstance instance, + const char* pName); +} +#endif + namespace Vulkan { static const char* const VALIDATION_LAYER_NAME = "VK_LAYER_KHRONOS_validation"; -static const char* const API_DUMP_LAYER_NAME = "VK_LAYER_LUNARG_api_dump"; +static const char* const CRASH_DIAGNOSTIC_LAYER_NAME = "VK_LAYER_LUNARG_crash_diagnostic"; static VKAPI_ATTR VkBool32 VKAPI_CALL DebugUtilsCallback( VkDebugUtilsMessageSeverityFlagBitsEXT severity, VkDebugUtilsMessageTypeFlagsEXT type, @@ -32,7 +42,8 @@ static VKAPI_ATTR VkBool32 VKAPI_CALL DebugUtilsCallback( switch (static_cast(callback_data->messageIdNumber)) { case 0x609a13b: // Vertex attribute at location not consumed by shader case 0xc81ad50e: - case 0x92d66fc1: // `pMultisampleState is NULL` for depth only passes (confirmed VL error) + case 0xb7c39078: + case 0x32868fde: // vkCreateBufferView(): pCreateInfo->range does not equal VK_WHOLE_SIZE return VK_FALSE; default: break; @@ -186,12 +197,14 @@ std::vector GetInstanceExtensions(Frontend::WindowSystemType window return extensions; } -vk::UniqueInstance CreateInstance(vk::DynamicLoader& dl, Frontend::WindowSystemType window_type, - bool enable_validation, bool dump_command_buffers) { +vk::UniqueInstance CreateInstance(Frontend::WindowSystemType window_type, bool enable_validation, + bool enable_crash_diagnostic) { LOG_INFO(Render_Vulkan, "Creating vulkan instance"); +#if VULKAN_HPP_ENABLE_DYNAMIC_LOADER_TOOL auto vkGetInstanceProcAddr = dl.getProcAddress("vkGetInstanceProcAddr"); +#endif VULKAN_HPP_DEFAULT_DISPATCHER.init(vkGetInstanceProcAddr); const u32 available_version = VULKAN_HPP_DEFAULT_DISPATCHER.vkEnumerateInstanceVersion @@ -216,12 +229,27 @@ vk::UniqueInstance CreateInstance(vk::DynamicLoader& dl, Frontend::WindowSystemT u32 num_layers = 0; std::array layers; + vk::Bool32 enable_force_barriers = vk::False; + const char* log_path{}; + +#if VULKAN_HPP_ENABLE_DYNAMIC_LOADER_TOOL if (enable_validation) { layers[num_layers++] = VALIDATION_LAYER_NAME; } - if (dump_command_buffers) { - layers[num_layers++] = API_DUMP_LAYER_NAME; + + if (enable_crash_diagnostic) { + layers[num_layers++] = CRASH_DIAGNOSTIC_LAYER_NAME; + static const auto crash_diagnostic_path = + Common::FS::GetUserPathString(Common::FS::PathType::LogDir); + log_path = crash_diagnostic_path.c_str(); + enable_force_barriers = vk::True; + } +#else + if (enable_validation || enable_crash_diagnostic) { + LOG_WARNING(Render_Vulkan, + "Skipping loading Vulkan layers as dynamic loading is not enabled."); } +#endif vk::Bool32 enable_sync = enable_validation && Config::vkValidationSyncEnabled() ? vk::True : vk::False; @@ -240,7 +268,7 @@ vk::UniqueInstance CreateInstance(vk::DynamicLoader& dl, Frontend::WindowSystemT }, vk::LayerSettingEXT{ .pLayerName = VALIDATION_LAYER_NAME, - .pSettingName = "sync_queue_submit", + .pSettingName = "syncval_submit_time_validation", .type = vk::LayerSettingTypeEXT::eBool32, .valueCount = 1, .pValues = &enable_sync, @@ -280,6 +308,20 @@ vk::UniqueInstance CreateInstance(vk::DynamicLoader& dl, Frontend::WindowSystemT .valueCount = 1, .pValues = &enable_gpuav, }, + vk::LayerSettingEXT{ + .pLayerName = "lunarg_crash_diagnostic", + .pSettingName = "output_path", + .type = vk::LayerSettingTypeEXT::eString, + .valueCount = 1, + .pValues = &log_path, + }, + vk::LayerSettingEXT{ + .pLayerName = "lunarg_crash_diagnostic", + .pSettingName = "sync_after_commands", + .type = vk::LayerSettingTypeEXT::eBool32, + .valueCount = 1, + .pValues = &enable_force_barriers, + }, }; vk::StructureChain instance_ci_chain = { diff --git a/src/video_core/renderer_vulkan/vk_platform.h b/src/video_core/renderer_vulkan/vk_platform.h index 582de383123..e38bd2fef58 100644 --- a/src/video_core/renderer_vulkan/vk_platform.h +++ b/src/video_core/renderer_vulkan/vk_platform.h @@ -21,8 +21,8 @@ constexpr u32 TargetVulkanApiVersion = VK_API_VERSION_1_2; vk::SurfaceKHR CreateSurface(vk::Instance instance, const Frontend::WindowSDL& emu_window); -vk::UniqueInstance CreateInstance(vk::DynamicLoader& dl, Frontend::WindowSystemType window_type, - bool enable_validation, bool dump_command_buffers); +vk::UniqueInstance CreateInstance(Frontend::WindowSystemType window_type, bool enable_validation, + bool enable_crash_diagnostic); vk::UniqueDebugUtilsMessengerEXT CreateDebugCallback(vk::Instance instance); diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index 9231c51044d..eac272726ac 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -17,7 +17,7 @@ namespace Vulkan { Rasterizer::Rasterizer(const Instance& instance_, Scheduler& scheduler_, AmdGpu::Liverpool* liverpool_) : instance{instance_}, scheduler{scheduler_}, page_manager{this}, - buffer_cache{instance, scheduler, liverpool_, page_manager}, + buffer_cache{instance, scheduler, liverpool_, texture_cache, page_manager}, texture_cache{instance, scheduler, buffer_cache, page_manager}, liverpool{liverpool_}, memory{Core::Memory::Instance()}, pipeline_cache{instance, scheduler, liverpool} { if (!Config::nullGpu()) { @@ -29,6 +29,19 @@ Rasterizer::Rasterizer(const Instance& instance_, Scheduler& scheduler_, Rasterizer::~Rasterizer() = default; +void Rasterizer::CpSync() { + scheduler.EndRendering(); + auto cmdbuf = scheduler.CommandBuffer(); + + const vk::MemoryBarrier ib_barrier{ + .srcAccessMask = vk::AccessFlagBits::eShaderWrite, + .dstAccessMask = vk::AccessFlagBits::eIndirectCommandRead, + }; + cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eComputeShader, + vk::PipelineStageFlagBits::eDrawIndirect, + vk::DependencyFlagBits::eByRegion, ib_barrier, {}, {}); +} + void Rasterizer::Draw(bool is_indexed, u32 index_offset) { RENDERER_TRACE; @@ -49,7 +62,7 @@ void Rasterizer::Draw(bool is_indexed, u32 index_offset) { buffer_cache.BindVertexBuffers(vs_info); const u32 num_indices = buffer_cache.BindIndexBuffer(is_indexed, index_offset); - BeginRendering(); + BeginRendering(*pipeline); UpdateDynamicState(*pipeline); const auto [vertex_offset, instance_offset] = vs_info.GetDrawOffsets(); @@ -66,6 +79,45 @@ void Rasterizer::Draw(bool is_indexed, u32 index_offset) { } } +void Rasterizer::DrawIndirect(bool is_indexed, VAddr address, u32 offset, u32 size) { + RENDERER_TRACE; + + const auto cmdbuf = scheduler.CommandBuffer(); + const auto& regs = liverpool->regs; + const GraphicsPipeline* pipeline = pipeline_cache.GetGraphicsPipeline(); + if (!pipeline) { + return; + } + + ASSERT_MSG(regs.primitive_type != AmdGpu::Liverpool::PrimitiveType::RectList, + "Unsupported primitive type for indirect draw"); + + try { + pipeline->BindResources(regs, buffer_cache, texture_cache); + } catch (...) { + UNREACHABLE(); + } + + const auto& vs_info = pipeline->GetStage(Shader::Stage::Vertex); + buffer_cache.BindVertexBuffers(vs_info); + const u32 num_indices = buffer_cache.BindIndexBuffer(is_indexed, 0); + + BeginRendering(*pipeline); + UpdateDynamicState(*pipeline); + + const auto [buffer, base] = buffer_cache.ObtainBuffer(address, size, true); + const auto total_offset = base + offset; + + // We can safely ignore both SGPR UD indices and results of fetch shader parsing, as vertex and + // instance offsets will be automatically applied by Vulkan from indirect args buffer. + + if (is_indexed) { + cmdbuf.drawIndexedIndirect(buffer->Handle(), total_offset, 1, 0); + } else { + cmdbuf.drawIndirect(buffer->Handle(), total_offset, 1, 0); + } +} + void Rasterizer::DispatchDirect() { RENDERER_TRACE; @@ -113,19 +165,6 @@ void Rasterizer::DispatchIndirect(VAddr address, u32 offset, u32 size) { cmdbuf.bindPipeline(vk::PipelineBindPoint::eCompute, pipeline->Handle()); const auto [buffer, base] = buffer_cache.ObtainBuffer(address, size, true); const auto total_offset = base + offset; - - // Emulate PFP-to-ME sync packet - const vk::BufferMemoryBarrier ib_barrier{ - .srcAccessMask = vk::AccessFlagBits::eShaderWrite, - .dstAccessMask = vk::AccessFlagBits::eIndirectCommandRead, - .buffer = buffer->Handle(), - .offset = total_offset, - .size = size, - }; - cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eComputeShader, - vk::PipelineStageFlagBits::eDrawIndirect, - vk::DependencyFlagBits::eByRegion, {}, ib_barrier, {}); - cmdbuf.dispatchIndirect(buffer->Handle(), total_offset); } @@ -136,10 +175,18 @@ u64 Rasterizer::Flush() { return current_tick; } -void Rasterizer::BeginRendering() { +void Rasterizer::Finish() { + scheduler.Finish(); +} + +void Rasterizer::BeginRendering(const GraphicsPipeline& pipeline) { const auto& regs = liverpool->regs; RenderState state; + if (regs.color_control.degamma_enable) { + LOG_WARNING(Render_Vulkan, "Color buffers require gamma correction"); + } + for (auto col_buf_id = 0u; col_buf_id < Liverpool::NumColorBuffers; ++col_buf_id) { const auto& col_buf = regs.color_buffers[col_buf_id]; if (!col_buf) { @@ -152,6 +199,13 @@ void Rasterizer::BeginRendering() { continue; } + // Skip stale color buffers if shader doesn't output to them. Otherwise it will perform + // an unnecessary transition and may result in state conflict if the resource is already + // bound for reading. + if ((pipeline.GetMrtMask() & (1 << col_buf_id)) == 0) { + continue; + } + const auto& hint = liverpool->last_cb_extent[col_buf_id]; VideoCore::ImageInfo image_info{col_buf, hint}; VideoCore::ImageViewInfo view_info{col_buf, false /*!!image.info.usage.vo_buffer*/}; @@ -193,7 +247,7 @@ void Rasterizer::BeginRendering() { state.depth_image = image.image; state.depth_attachment = { .imageView = *image_view.image_view, - .imageLayout = image.layout, + .imageLayout = image.last_state.layout, .loadOp = is_clear ? vk::AttachmentLoadOp::eClear : vk::AttachmentLoadOp::eLoad, .storeOp = is_clear ? vk::AttachmentStoreOp::eNone : vk::AttachmentStoreOp::eStore, .clearValue = vk::ClearValue{.depthStencil = {.depth = regs.depth_clear, @@ -208,6 +262,17 @@ void Rasterizer::BeginRendering() { scheduler.BeginRendering(state); } +void Rasterizer::InlineDataToGds(u32 gds_offset, u32 value) { + buffer_cache.InlineDataToGds(gds_offset, value); +} + +u32 Rasterizer::ReadDataFromGds(u32 gds_offset) { + auto* gds_buf = buffer_cache.GetGdsBuffer(); + u32 value; + std::memcpy(&value, gds_buf->mapped_data.data() + gds_offset, sizeof(u32)); + return value; +} + void Rasterizer::InvalidateMemory(VAddr addr, u64 size) { buffer_cache.InvalidateMemory(addr, size); texture_cache.InvalidateMemory(addr, size); @@ -239,6 +304,43 @@ void Rasterizer::UpdateDynamicState(const GraphicsPipeline& pipeline) { cmdbuf.setColorWriteEnableEXT(write_ens); cmdbuf.setColorWriteMaskEXT(0, write_masks); } + if (regs.depth_control.depth_bounds_enable) { + cmdbuf.setDepthBounds(regs.depth_bounds_min, regs.depth_bounds_max); + } + if (regs.polygon_control.NeedsBias()) { + if (regs.polygon_control.enable_polygon_offset_front) { + cmdbuf.setDepthBias(regs.poly_offset.front_offset, regs.poly_offset.depth_bias, + regs.poly_offset.front_scale); + } else { + cmdbuf.setDepthBias(regs.poly_offset.back_offset, regs.poly_offset.depth_bias, + regs.poly_offset.back_scale); + } + } + if (regs.depth_control.stencil_enable) { + const auto front = regs.stencil_ref_front; + const auto back = regs.stencil_ref_back; + if (front.stencil_test_val == back.stencil_test_val) { + cmdbuf.setStencilReference(vk::StencilFaceFlagBits::eFrontAndBack, + front.stencil_test_val); + } else { + cmdbuf.setStencilReference(vk::StencilFaceFlagBits::eFront, front.stencil_test_val); + cmdbuf.setStencilReference(vk::StencilFaceFlagBits::eBack, back.stencil_test_val); + } + if (front.stencil_write_mask == back.stencil_write_mask) { + cmdbuf.setStencilWriteMask(vk::StencilFaceFlagBits::eFrontAndBack, + front.stencil_write_mask); + } else { + cmdbuf.setStencilWriteMask(vk::StencilFaceFlagBits::eFront, front.stencil_write_mask); + cmdbuf.setStencilWriteMask(vk::StencilFaceFlagBits::eBack, back.stencil_write_mask); + } + if (front.stencil_mask == back.stencil_mask) { + cmdbuf.setStencilCompareMask(vk::StencilFaceFlagBits::eFrontAndBack, + front.stencil_mask); + } else { + cmdbuf.setStencilCompareMask(vk::StencilFaceFlagBits::eFront, front.stencil_mask); + cmdbuf.setStencilCompareMask(vk::StencilFaceFlagBits::eBack, back.stencil_mask); + } + } } void Rasterizer::UpdateViewportScissorState() { @@ -282,7 +384,7 @@ void Rasterizer::UpdateDepthStencilState() { } void Rasterizer::ScopeMarkerBegin(const std::string_view& str) { - if (Config::nullGpu() || !Config::isMarkersEnabled()) { + if (Config::nullGpu() || !Config::vkMarkersEnabled()) { return; } @@ -293,7 +395,7 @@ void Rasterizer::ScopeMarkerBegin(const std::string_view& str) { } void Rasterizer::ScopeMarkerEnd() { - if (Config::nullGpu() || !Config::isMarkersEnabled()) { + if (Config::nullGpu() || !Config::vkMarkersEnabled()) { return; } @@ -302,7 +404,7 @@ void Rasterizer::ScopeMarkerEnd() { } void Rasterizer::ScopedMarkerInsert(const std::string_view& str) { - if (Config::nullGpu() || !Config::isMarkersEnabled()) { + if (Config::nullGpu() || !Config::vkMarkersEnabled()) { return; } @@ -312,11 +414,4 @@ void Rasterizer::ScopedMarkerInsert(const std::string_view& str) { }); } -void Rasterizer::Breadcrumb(u64 id) { - if (Config::nullGpu() || !instance.HasNvCheckpoints()) { - return; - } - scheduler.CommandBuffer().setCheckpointNV(id); -} - } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.h b/src/video_core/renderer_vulkan/vk_rasterizer.h index 34f6ae726dc..bd05c8faf41 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.h +++ b/src/video_core/renderer_vulkan/vk_rasterizer.h @@ -32,6 +32,7 @@ class Rasterizer { } void Draw(bool is_indexed, u32 index_offset = 0); + void DrawIndirect(bool is_indexed, VAddr address, u32 offset, u32 size); void DispatchDirect(); void DispatchIndirect(VAddr address, u32 offset, u32 size); @@ -39,16 +40,19 @@ class Rasterizer { void ScopeMarkerBegin(const std::string_view& str); void ScopeMarkerEnd(); void ScopedMarkerInsert(const std::string_view& str); - void Breadcrumb(u64 id); + void InlineDataToGds(u32 gds_offset, u32 value); + u32 ReadDataFromGds(u32 gsd_offset); void InvalidateMemory(VAddr addr, u64 size); void MapMemory(VAddr addr, u64 size); void UnmapMemory(VAddr addr, u64 size); + void CpSync(); u64 Flush(); + void Finish(); private: - void BeginRendering(); + void BeginRendering(const GraphicsPipeline& pipeline); void UpdateDynamicState(const GraphicsPipeline& pipeline); void UpdateViewportScissorState(); diff --git a/src/video_core/renderer_vulkan/vk_resource_pool.cpp b/src/video_core/renderer_vulkan/vk_resource_pool.cpp index f9f2ae0a00e..a5ee22c2527 100644 --- a/src/video_core/renderer_vulkan/vk_resource_pool.cpp +++ b/src/video_core/renderer_vulkan/vk_resource_pool.cpp @@ -3,8 +3,8 @@ #include #include -#include #include "common/assert.h" +#include "common/scope_exit.h" #include "video_core/renderer_vulkan/vk_instance.h" #include "video_core/renderer_vulkan/vk_master_semaphore.h" #include "video_core/renderer_vulkan/vk_resource_pool.h" @@ -103,88 +103,86 @@ vk::CommandBuffer CommandPool::Commit() { return cmd_buffers[index]; } -constexpr u32 DESCRIPTOR_SET_BATCH = 32; - -DescriptorHeap::DescriptorHeap(const Instance& instance, MasterSemaphore* master_semaphore, - std::span bindings, +DescriptorHeap::DescriptorHeap(const Instance& instance, MasterSemaphore* master_semaphore_, + std::span pool_sizes_, u32 descriptor_heap_count_) - : ResourcePool{master_semaphore, DESCRIPTOR_SET_BATCH}, device{instance.GetDevice()}, - descriptor_heap_count{descriptor_heap_count_} { - // Create descriptor set layout. - const vk::DescriptorSetLayoutCreateInfo layout_ci = { - .bindingCount = static_cast(bindings.size()), - .pBindings = bindings.data(), - }; - descriptor_set_layout = device.createDescriptorSetLayoutUnique(layout_ci); - if (instance.HasDebuggingToolAttached()) { - SetObjectName(device, *descriptor_set_layout, "DescriptorSetLayout"); - } + : device{instance.GetDevice()}, master_semaphore{master_semaphore_}, + descriptor_heap_count{descriptor_heap_count_}, pool_sizes{pool_sizes_} { + CreateDescriptorPool(); +} - // Build descriptor set pool counts. - std::unordered_map descriptor_type_counts; - for (const auto& binding : bindings) { - descriptor_type_counts[binding.descriptorType] += binding.descriptorCount; - } - for (const auto& [type, count] : descriptor_type_counts) { - auto& pool_size = pool_sizes.emplace_back(); - pool_size.descriptorCount = count * descriptor_heap_count; - pool_size.type = type; +DescriptorHeap::~DescriptorHeap() { + device.destroyDescriptorPool(curr_pool); + for (const auto [pool, tick] : pending_pools) { + master_semaphore->Wait(tick); + device.destroyDescriptorPool(pool); } - - // Create descriptor pool - AppendDescriptorPool(); } -DescriptorHeap::~DescriptorHeap() = default; +vk::DescriptorSet DescriptorHeap::Commit(vk::DescriptorSetLayout set_layout) { + const u64 set_key = std::bit_cast(set_layout); + const auto [it, _] = descriptor_sets.try_emplace(set_key); -void DescriptorHeap::Allocate(std::size_t begin, std::size_t end) { - ASSERT(end - begin == DESCRIPTOR_SET_BATCH); - descriptor_sets.resize(end); - hashes.resize(end); + // Check if allocated sets exist and pick one. + if (!it->second.empty()) { + const auto desc_set = it->second.back(); + it.value().pop_back(); + return desc_set; + } - std::array layouts; - layouts.fill(*descriptor_set_layout); + DescSetBatch desc_sets(DescriptorSetBatch); + std::array layouts; + layouts.fill(set_layout); - u32 current_pool = 0; vk::DescriptorSetAllocateInfo alloc_info = { - .descriptorPool = *pools[current_pool], - .descriptorSetCount = DESCRIPTOR_SET_BATCH, + .descriptorPool = curr_pool, + .descriptorSetCount = DescriptorSetBatch, .pSetLayouts = layouts.data(), }; - // Attempt to allocate the descriptor set batch. If the pool has run out of space, use a new - // one. - while (true) { - const auto result = - device.allocateDescriptorSets(&alloc_info, descriptor_sets.data() + begin); - if (result == vk::Result::eSuccess) { - break; - } - if (result == vk::Result::eErrorOutOfPoolMemory) { - current_pool++; - if (current_pool == pools.size()) { - LOG_INFO(Render_Vulkan, "Run out of pools, creating new one!"); - AppendDescriptorPool(); - } - alloc_info.descriptorPool = *pools[current_pool]; - } + // Attempt to allocate the descriptor set batch. + auto result = device.allocateDescriptorSets(&alloc_info, desc_sets.data()); + if (result == vk::Result::eSuccess) { + const auto desc_set = desc_sets.back(); + desc_sets.pop_back(); + it.value() = std::move(desc_sets); + return desc_set; } -} -vk::DescriptorSet DescriptorHeap::Commit() { - const std::size_t index = CommitResource(); - return descriptor_sets[index]; + // The pool has run out. Record current tick and place it in pending list. + ASSERT_MSG(result == vk::Result::eErrorOutOfPoolMemory, + "Unexpected error during descriptor set allocation {}", vk::to_string(result)); + pending_pools.emplace_back(curr_pool, master_semaphore->CurrentTick()); + if (const auto [pool, tick] = pending_pools.front(); master_semaphore->IsFree(tick)) { + curr_pool = pool; + pending_pools.pop_front(); + device.resetDescriptorPool(curr_pool); + } else { + CreateDescriptorPool(); + } + + // Attempt to allocate again with fresh pool. + alloc_info.descriptorPool = curr_pool; + result = device.allocateDescriptorSets(&alloc_info, desc_sets.data()); + ASSERT_MSG(result == vk::Result::eSuccess, + "Unexpected error during descriptor set allocation {}", vk::to_string(result)); + + // We've changed pool so also reset descriptor batch cache. + descriptor_sets.clear(); + const auto desc_set = desc_sets.back(); + desc_sets.pop_back(); + descriptor_sets[set_key] = std::move(desc_sets); + return desc_set; } -void DescriptorHeap::AppendDescriptorPool() { +void DescriptorHeap::CreateDescriptorPool() { const vk::DescriptorPoolCreateInfo pool_info = { .flags = vk::DescriptorPoolCreateFlagBits::eUpdateAfterBind, .maxSets = descriptor_heap_count, .poolSizeCount = static_cast(pool_sizes.size()), .pPoolSizes = pool_sizes.data(), }; - auto& pool = pools.emplace_back(); - pool = device.createDescriptorPoolUnique(pool_info); + curr_pool = device.createDescriptorPool(pool_info); } } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_resource_pool.h b/src/video_core/renderer_vulkan/vk_resource_pool.h index b138b969379..98c2ddb8c49 100644 --- a/src/video_core/renderer_vulkan/vk_resource_pool.h +++ b/src/video_core/renderer_vulkan/vk_resource_pool.h @@ -3,7 +3,9 @@ #pragma once +#include #include +#include #include #include "common/types.h" @@ -62,32 +64,29 @@ class CommandPool final : public ResourcePool { std::vector cmd_buffers; }; -class DescriptorHeap final : public ResourcePool { +class DescriptorHeap final { + static constexpr u32 DescriptorSetBatch = 32; + public: explicit DescriptorHeap(const Instance& instance, MasterSemaphore* master_semaphore, - std::span bindings, + std::span pool_sizes, u32 descriptor_heap_count = 1024); - ~DescriptorHeap() override; - - const vk::DescriptorSetLayout& Layout() const { - return *descriptor_set_layout; - } - - void Allocate(std::size_t begin, std::size_t end) override; + ~DescriptorHeap(); - vk::DescriptorSet Commit(); + vk::DescriptorSet Commit(vk::DescriptorSetLayout set_layout); private: - void AppendDescriptorPool(); + void CreateDescriptorPool(); private: vk::Device device; - vk::UniqueDescriptorSetLayout descriptor_set_layout; + MasterSemaphore* master_semaphore; u32 descriptor_heap_count; - std::vector pool_sizes; - std::vector pools; - std::vector descriptor_sets; - std::vector hashes; + std::span pool_sizes; + vk::DescriptorPool curr_pool; + std::deque> pending_pools; + using DescSetBatch = boost::container::static_vector; + tsl::robin_map descriptor_sets; }; } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_scheduler.cpp b/src/video_core/renderer_vulkan/vk_scheduler.cpp index 2f1f13d726d..08b5014ec14 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.cpp +++ b/src/video_core/renderer_vulkan/vk_scheduler.cpp @@ -4,6 +4,7 @@ #include #include "common/assert.h" #include "common/debug.h" +#include "imgui/renderer/texture_manager.h" #include "video_core/renderer_vulkan/vk_instance.h" #include "video_core/renderer_vulkan/vk_scheduler.h" @@ -58,58 +59,6 @@ void Scheduler::EndRendering() { } is_rendering = false; current_cmdbuf.endRendering(); - - boost::container::static_vector barriers; - for (size_t i = 0; i < render_state.num_color_attachments; ++i) { - barriers.push_back(vk::ImageMemoryBarrier{ - .srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite, - .dstAccessMask = vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite, - .oldLayout = vk::ImageLayout::eColorAttachmentOptimal, - .newLayout = vk::ImageLayout::eColorAttachmentOptimal, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = render_state.color_images[i], - .subresourceRange = - { - .aspectMask = vk::ImageAspectFlagBits::eColor, - .baseMipLevel = 0, - .levelCount = VK_REMAINING_MIP_LEVELS, - .baseArrayLayer = 0, - .layerCount = VK_REMAINING_ARRAY_LAYERS, - }, - }); - } - if (render_state.has_depth || render_state.has_stencil) { - barriers.push_back(vk::ImageMemoryBarrier{ - .srcAccessMask = vk::AccessFlagBits::eDepthStencilAttachmentWrite, - .dstAccessMask = vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite, - .oldLayout = render_state.depth_attachment.imageLayout, - .newLayout = render_state.depth_attachment.imageLayout, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = render_state.depth_image, - .subresourceRange = - { - .aspectMask = vk::ImageAspectFlagBits::eDepth | - (render_state.has_stencil ? vk::ImageAspectFlagBits::eStencil - : vk::ImageAspectFlagBits::eNone), - .baseMipLevel = 0, - .levelCount = VK_REMAINING_MIP_LEVELS, - .baseArrayLayer = 0, - .layerCount = VK_REMAINING_ARRAY_LAYERS, - }, - }); - } - - if (!barriers.empty()) { - const auto src_stages = - vk::PipelineStageFlagBits::eColorAttachmentOutput | - (render_state.has_depth ? vk::PipelineStageFlagBits::eLateFragmentTests | - vk::PipelineStageFlagBits::eEarlyFragmentTests - : vk::PipelineStageFlagBits::eNone); - current_cmdbuf.pipelineBarrier(src_stages, vk::PipelineStageFlagBits::eFragmentShader, - vk::DependencyFlagBits::eByRegion, {}, {}, barriers); - } } void Scheduler::Flush(SubmitInfo& info) { @@ -190,15 +139,9 @@ void Scheduler::SubmitExecution(SubmitInfo& info) { }; try { + ImGui::Core::TextureManager::Submit(); instance.GetGraphicsQueue().submit(submit_info, info.fence); } catch (vk::DeviceLostError& err) { - if (instance.HasNvCheckpoints()) { - const auto checkpoint_data = instance.GetGraphicsQueue().getCheckpointData2NV(); - for (const auto& cp : checkpoint_data) { - LOG_CRITICAL(Render_Vulkan, "{}: {:#x}", vk::to_string(cp.stage), - reinterpret_cast(cp.pCheckpointMarker)); - } - } UNREACHABLE_MSG("Device lost during submit: {}", err.what()); } diff --git a/src/video_core/renderer_vulkan/vk_shader_cache.cpp b/src/video_core/renderer_vulkan/vk_shader_cache.cpp deleted file mode 100644 index 9250f84ced9..00000000000 --- a/src/video_core/renderer_vulkan/vk_shader_cache.cpp +++ /dev/null @@ -1,192 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "common/config.h" -#include "common/io_file.h" -#include "common/path_util.h" -#include "shader_recompiler/backend/spirv/emit_spirv.h" -#include "shader_recompiler/recompiler.h" -#include "video_core/renderer_vulkan/vk_instance.h" -#include "video_core/renderer_vulkan/vk_platform.h" -#include "video_core/renderer_vulkan/vk_shader_cache.h" -#include "video_core/renderer_vulkan/vk_shader_util.h" - -namespace Vulkan { - -using Shader::VsOutput; - -void BuildVsOutputs(Shader::Info& info, const AmdGpu::Liverpool::VsOutputControl& ctl) { - const auto add_output = [&](VsOutput x, VsOutput y, VsOutput z, VsOutput w) { - if (x != VsOutput::None || y != VsOutput::None || z != VsOutput::None || - w != VsOutput::None) { - info.vs_outputs.emplace_back(Shader::VsOutputMap{x, y, z, w}); - } - }; - // VS_OUT_MISC_VEC - add_output(ctl.use_vtx_point_size ? VsOutput::PointSprite : VsOutput::None, - ctl.use_vtx_edge_flag - ? VsOutput::EdgeFlag - : (ctl.use_vtx_gs_cut_flag ? VsOutput::GsCutFlag : VsOutput::None), - ctl.use_vtx_kill_flag - ? VsOutput::KillFlag - : (ctl.use_vtx_render_target_idx ? VsOutput::GsMrtIndex : VsOutput::None), - ctl.use_vtx_viewport_idx ? VsOutput::GsVpIndex : VsOutput::None); - // VS_OUT_CCDIST0 - add_output(ctl.IsClipDistEnabled(0) - ? VsOutput::ClipDist0 - : (ctl.IsCullDistEnabled(0) ? VsOutput::CullDist0 : VsOutput::None), - ctl.IsClipDistEnabled(1) - ? VsOutput::ClipDist1 - : (ctl.IsCullDistEnabled(1) ? VsOutput::CullDist1 : VsOutput::None), - ctl.IsClipDistEnabled(2) - ? VsOutput::ClipDist2 - : (ctl.IsCullDistEnabled(2) ? VsOutput::CullDist2 : VsOutput::None), - ctl.IsClipDistEnabled(3) - ? VsOutput::ClipDist3 - : (ctl.IsCullDistEnabled(3) ? VsOutput::CullDist3 : VsOutput::None)); - // VS_OUT_CCDIST1 - add_output(ctl.IsClipDistEnabled(4) - ? VsOutput::ClipDist4 - : (ctl.IsCullDistEnabled(4) ? VsOutput::CullDist4 : VsOutput::None), - ctl.IsClipDistEnabled(5) - ? VsOutput::ClipDist5 - : (ctl.IsCullDistEnabled(5) ? VsOutput::CullDist5 : VsOutput::None), - ctl.IsClipDistEnabled(6) - ? VsOutput::ClipDist6 - : (ctl.IsCullDistEnabled(6) ? VsOutput::CullDist6 : VsOutput::None), - ctl.IsClipDistEnabled(7) - ? VsOutput::ClipDist7 - : (ctl.IsCullDistEnabled(7) ? VsOutput::CullDist7 : VsOutput::None)); -} - -Shader::Info MakeShaderInfo(const GuestProgram& pgm, const AmdGpu::Liverpool::Regs& regs) { - Shader::Info info{}; - info.user_data = pgm.user_data; - info.pgm_base = VAddr(pgm.code.data()); - info.pgm_hash = pgm.hash; - info.stage = pgm.stage; - switch (pgm.stage) { - case Shader::Stage::Vertex: { - info.num_user_data = regs.vs_program.settings.num_user_regs; - info.num_input_vgprs = regs.vs_program.settings.vgpr_comp_cnt; - BuildVsOutputs(info, regs.vs_output_control); - break; - } - case Shader::Stage::Fragment: { - info.num_user_data = regs.ps_program.settings.num_user_regs; - for (u32 i = 0; i < regs.num_interp; i++) { - info.ps_inputs.push_back({ - .param_index = regs.ps_inputs[i].input_offset.Value(), - .is_default = bool(regs.ps_inputs[i].use_default), - .is_flat = bool(regs.ps_inputs[i].flat_shade), - .default_value = regs.ps_inputs[i].default_value, - }); - } - break; - } - case Shader::Stage::Compute: { - const auto& cs_pgm = regs.cs_program; - info.num_user_data = cs_pgm.settings.num_user_regs; - info.workgroup_size = {cs_pgm.num_thread_x.full, cs_pgm.num_thread_y.full, - cs_pgm.num_thread_z.full}; - info.tgid_enable = {cs_pgm.IsTgidEnabled(0), cs_pgm.IsTgidEnabled(1), - cs_pgm.IsTgidEnabled(2)}; - info.shared_memory_size = cs_pgm.SharedMemSize(); - break; - } - default: - break; - } - return info; -} - -[[nodiscard]] inline u64 HashCombine(const u64 seed, const u64 hash) { - return seed ^ (hash + 0x9e3779b9 + (seed << 6) + (seed >> 2)); -} - -ShaderCache::ShaderCache(const Instance& instance_, AmdGpu::Liverpool* liverpool_) - : instance{instance_}, liverpool{liverpool_}, inst_pool{8192}, block_pool{512} { - profile = Shader::Profile{ - .supported_spirv = instance.ApiVersion() >= VK_API_VERSION_1_3 ? 0x00010600U : 0x00010500U, - .subgroup_size = instance.SubgroupSize(), - .support_explicit_workgroup_layout = true, - }; -} - -vk::ShaderModule ShaderCache::CompileModule(Shader::Info& info, std::span code, - size_t perm_idx, u32& binding) { - LOG_INFO(Render_Vulkan, "Compiling {} shader {:#x} {}", info.stage, info.pgm_hash, - perm_idx != 0 ? "(permutation)" : ""); - - if (Config::dumpShaders()) { - DumpShader(code, info.pgm_hash, info.stage, perm_idx, "bin"); - } - - block_pool.ReleaseContents(); - inst_pool.ReleaseContents(); - const auto ir_program = Shader::TranslateProgram(inst_pool, block_pool, code, info, profile); - - // Compile IR to SPIR-V - const auto spv = Shader::Backend::SPIRV::EmitSPIRV(profile, ir_program, binding); - if (Config::dumpShaders()) { - DumpShader(spv, info.pgm_hash, info.stage, perm_idx, "spv"); - } - - // Create module and set name to hash in renderdoc - const auto module = CompileSPV(spv, instance.GetDevice()); - ASSERT(module != VK_NULL_HANDLE); - const auto name = fmt::format("{}_{:#x}_{}", info.stage, info.pgm_hash, perm_idx); - Vulkan::SetObjectName(instance.GetDevice(), module, name); - return module; -} - -Program* ShaderCache::CreateProgram(const GuestProgram& pgm, u32& binding) { - Program* program = program_pool.Create(MakeShaderInfo(pgm, liverpool->regs)); - u32 start_binding = binding; - const auto module = CompileModule(program->info, pgm.code, 0, binding); - program->modules.emplace_back(module, StageSpecialization{program->info, start_binding}); - return program; -} - -std::tuple ShaderCache::GetProgram( - const GuestProgram& pgm, u32& binding) { - auto [it_pgm, new_program] = program_cache.try_emplace(pgm.hash); - if (new_program) { - auto program = CreateProgram(pgm, binding); - const auto module = program->modules.back().module; - it_pgm.value() = program; - return std::make_tuple(&program->info, module, HashCombine(pgm.hash, 0)); - } - - Program* program = it_pgm->second; - const auto& info = program->info; - size_t perm_idx = program->modules.size(); - StageSpecialization spec{info, binding}; - vk::ShaderModule module{}; - - const auto it = std::ranges::find(program->modules, spec, &Program::Module::spec); - if (it == program->modules.end()) { - auto new_info = MakeShaderInfo(pgm, liverpool->regs); - module = CompileModule(new_info, pgm.code, perm_idx, binding); - program->modules.emplace_back(module, std::move(spec)); - } else { - binding += info.NumBindings(); - module = it->module; - perm_idx = std::distance(program->modules.begin(), it); - } - return std::make_tuple(&info, module, HashCombine(pgm.hash, perm_idx)); -} - -void ShaderCache::DumpShader(std::span code, u64 hash, Shader::Stage stage, - size_t perm_idx, std::string_view ext) { - using namespace Common::FS; - const auto dump_dir = GetUserPath(PathType::ShaderDir) / "dumps"; - if (!std::filesystem::exists(dump_dir)) { - std::filesystem::create_directories(dump_dir); - } - const auto filename = fmt::format("{}_{:#018x}_{}.{}", stage, hash, perm_idx, ext); - const auto file = IOFile{dump_dir / filename, FileAccessMode::Write}; - file.WriteSpan(code); -} - -} // namespace Vulkan diff --git a/src/video_core/texture_cache/host_compatibility.h b/src/video_core/texture_cache/host_compatibility.h new file mode 100644 index 00000000000..a73f7e6bea3 --- /dev/null +++ b/src/video_core/texture_cache/host_compatibility.h @@ -0,0 +1,392 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// Copyright © 2023 Skyline Team and Contributors (https://github.com/skyline-emu/) +// Copyright © 2015-2023 The Khronos Group Inc. +// Copyright © 2015-2023 Valve Corporation +// Copyright © 2015-2023 LunarG, Inc. + +#pragma once + +#include +#include "video_core/renderer_vulkan/vk_common.h" + +namespace VideoCore { +/** + * @brief All classes of format compatibility according to the Vulkan specification + * @url + * https://github.com/KhronosGroup/Vulkan-ValidationLayers/blob/d37c676f75f545a3e5a98d7dfb89864391a1db1e/layers/generated/vk_format_utils.h#L47-L131 + * @note This is copied directly from Vulkan Validation Layers and doesn't follow the Skyline naming + * conventions + */ +enum class FORMAT_COMPATIBILITY_CLASS { + NONE = 0, + _10BIT_2PLANE_420, + _10BIT_2PLANE_422, + _10BIT_2PLANE_444, + _10BIT_3PLANE_420, + _10BIT_3PLANE_422, + _10BIT_3PLANE_444, + _12BIT_2PLANE_420, + _12BIT_2PLANE_422, + _12BIT_2PLANE_444, + _12BIT_3PLANE_420, + _12BIT_3PLANE_422, + _12BIT_3PLANE_444, + _128BIT, + _16BIT, + _16BIT_2PLANE_420, + _16BIT_2PLANE_422, + _16BIT_2PLANE_444, + _16BIT_3PLANE_420, + _16BIT_3PLANE_422, + _16BIT_3PLANE_444, + _192BIT, + _24BIT, + _256BIT, + _32BIT, + _32BIT_B8G8R8G8, + _32BIT_G8B8G8R8, + _48BIT, + _64BIT, + _64BIT_B10G10R10G10, + _64BIT_B12G12R12G12, + _64BIT_B16G16R16G16, + _64BIT_G10B10G10R10, + _64BIT_G12B12G12R12, + _64BIT_G16B16G16R16, + _64BIT_R10G10B10A10, + _64BIT_R12G12B12A12, + _8BIT, + _8BIT_2PLANE_420, + _8BIT_2PLANE_422, + _8BIT_2PLANE_444, + _8BIT_3PLANE_420, + _8BIT_3PLANE_422, + _8BIT_3PLANE_444, + _96BIT, + ASTC_10X10, + ASTC_10X5, + ASTC_10X6, + ASTC_10X8, + ASTC_12X10, + ASTC_12X12, + ASTC_4X4, + ASTC_5X4, + ASTC_5X5, + ASTC_6X5, + ASTC_6X6, + ASTC_8X5, + ASTC_8X6, + ASTC_8X8, + BC1_RGB, + BC1_RGBA, + BC2, + BC3, + BC4, + BC5, + BC6H, + BC7, + D16, + D16S8, + D24, + D24S8, + D32, + D32S8, + EAC_R, + EAC_RG, + ETC2_EAC_RGBA, + ETC2_RGB, + ETC2_RGBA, + PVRTC1_2BPP, + PVRTC1_4BPP, + PVRTC2_2BPP, + PVRTC2_4BPP, + S8 +}; + +/** + * @brief The format compatibility class according to the Vulkan specification + * @url + * https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#formats-compatibility-classes + * @url + * https://github.com/KhronosGroup/Vulkan-ValidationLayers/blob/d37c676f75f545a3e5a98d7dfb89864391a1db1e/layers/generated/vk_format_utils.cpp#L70-L812 + * @note This is copied directly from Vulkan Validation Layers and doesn't follow the Skyline naming + * conventions + */ +static const std::unordered_map vkFormatClassTable{ + {VK_FORMAT_A1R5G5B5_UNORM_PACK16, FORMAT_COMPATIBILITY_CLASS::_16BIT}, + {VK_FORMAT_A2B10G10R10_SINT_PACK32, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_A2B10G10R10_SNORM_PACK32, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_A2B10G10R10_SSCALED_PACK32, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_A2B10G10R10_UINT_PACK32, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_A2B10G10R10_UNORM_PACK32, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_A2B10G10R10_USCALED_PACK32, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_A2R10G10B10_SINT_PACK32, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_A2R10G10B10_SNORM_PACK32, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_A2R10G10B10_SSCALED_PACK32, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_A2R10G10B10_UINT_PACK32, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_A2R10G10B10_UNORM_PACK32, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_A2R10G10B10_USCALED_PACK32, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_A4B4G4R4_UNORM_PACK16_EXT, FORMAT_COMPATIBILITY_CLASS::_16BIT}, + {VK_FORMAT_A4R4G4B4_UNORM_PACK16_EXT, FORMAT_COMPATIBILITY_CLASS::_16BIT}, + {VK_FORMAT_A8B8G8R8_SINT_PACK32, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_A8B8G8R8_SNORM_PACK32, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_A8B8G8R8_SRGB_PACK32, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_A8B8G8R8_SSCALED_PACK32, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_A8B8G8R8_UINT_PACK32, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_A8B8G8R8_UNORM_PACK32, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_A8B8G8R8_USCALED_PACK32, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_ASTC_10x10_SFLOAT_BLOCK_EXT, FORMAT_COMPATIBILITY_CLASS::ASTC_10X10}, + {VK_FORMAT_ASTC_10x10_SRGB_BLOCK, FORMAT_COMPATIBILITY_CLASS::ASTC_10X10}, + {VK_FORMAT_ASTC_10x10_UNORM_BLOCK, FORMAT_COMPATIBILITY_CLASS::ASTC_10X10}, + {VK_FORMAT_ASTC_10x5_SFLOAT_BLOCK_EXT, FORMAT_COMPATIBILITY_CLASS::ASTC_10X5}, + {VK_FORMAT_ASTC_10x5_SRGB_BLOCK, FORMAT_COMPATIBILITY_CLASS::ASTC_10X5}, + {VK_FORMAT_ASTC_10x5_UNORM_BLOCK, FORMAT_COMPATIBILITY_CLASS::ASTC_10X5}, + {VK_FORMAT_ASTC_10x6_SFLOAT_BLOCK_EXT, FORMAT_COMPATIBILITY_CLASS::ASTC_10X6}, + {VK_FORMAT_ASTC_10x6_SRGB_BLOCK, FORMAT_COMPATIBILITY_CLASS::ASTC_10X6}, + {VK_FORMAT_ASTC_10x6_UNORM_BLOCK, FORMAT_COMPATIBILITY_CLASS::ASTC_10X6}, + {VK_FORMAT_ASTC_10x8_SFLOAT_BLOCK_EXT, FORMAT_COMPATIBILITY_CLASS::ASTC_10X8}, + {VK_FORMAT_ASTC_10x8_SRGB_BLOCK, FORMAT_COMPATIBILITY_CLASS::ASTC_10X8}, + {VK_FORMAT_ASTC_10x8_UNORM_BLOCK, FORMAT_COMPATIBILITY_CLASS::ASTC_10X8}, + {VK_FORMAT_ASTC_12x10_SFLOAT_BLOCK_EXT, FORMAT_COMPATIBILITY_CLASS::ASTC_12X10}, + {VK_FORMAT_ASTC_12x10_SRGB_BLOCK, FORMAT_COMPATIBILITY_CLASS::ASTC_12X10}, + {VK_FORMAT_ASTC_12x10_UNORM_BLOCK, FORMAT_COMPATIBILITY_CLASS::ASTC_12X10}, + {VK_FORMAT_ASTC_12x12_SFLOAT_BLOCK_EXT, FORMAT_COMPATIBILITY_CLASS::ASTC_12X12}, + {VK_FORMAT_ASTC_12x12_SRGB_BLOCK, FORMAT_COMPATIBILITY_CLASS::ASTC_12X12}, + {VK_FORMAT_ASTC_12x12_UNORM_BLOCK, FORMAT_COMPATIBILITY_CLASS::ASTC_12X12}, + {VK_FORMAT_ASTC_4x4_SFLOAT_BLOCK_EXT, FORMAT_COMPATIBILITY_CLASS::ASTC_4X4}, + {VK_FORMAT_ASTC_4x4_SRGB_BLOCK, FORMAT_COMPATIBILITY_CLASS::ASTC_4X4}, + {VK_FORMAT_ASTC_4x4_UNORM_BLOCK, FORMAT_COMPATIBILITY_CLASS::ASTC_4X4}, + {VK_FORMAT_ASTC_5x4_SFLOAT_BLOCK_EXT, FORMAT_COMPATIBILITY_CLASS::ASTC_5X4}, + {VK_FORMAT_ASTC_5x4_SRGB_BLOCK, FORMAT_COMPATIBILITY_CLASS::ASTC_5X4}, + {VK_FORMAT_ASTC_5x4_UNORM_BLOCK, FORMAT_COMPATIBILITY_CLASS::ASTC_5X4}, + {VK_FORMAT_ASTC_5x5_SFLOAT_BLOCK_EXT, FORMAT_COMPATIBILITY_CLASS::ASTC_5X5}, + {VK_FORMAT_ASTC_5x5_SRGB_BLOCK, FORMAT_COMPATIBILITY_CLASS::ASTC_5X5}, + {VK_FORMAT_ASTC_5x5_UNORM_BLOCK, FORMAT_COMPATIBILITY_CLASS::ASTC_5X5}, + {VK_FORMAT_ASTC_6x5_SFLOAT_BLOCK_EXT, FORMAT_COMPATIBILITY_CLASS::ASTC_6X5}, + {VK_FORMAT_ASTC_6x5_SRGB_BLOCK, FORMAT_COMPATIBILITY_CLASS::ASTC_6X5}, + {VK_FORMAT_ASTC_6x5_UNORM_BLOCK, FORMAT_COMPATIBILITY_CLASS::ASTC_6X5}, + {VK_FORMAT_ASTC_6x6_SFLOAT_BLOCK_EXT, FORMAT_COMPATIBILITY_CLASS::ASTC_6X6}, + {VK_FORMAT_ASTC_6x6_SRGB_BLOCK, FORMAT_COMPATIBILITY_CLASS::ASTC_6X6}, + {VK_FORMAT_ASTC_6x6_UNORM_BLOCK, FORMAT_COMPATIBILITY_CLASS::ASTC_6X6}, + {VK_FORMAT_ASTC_8x5_SFLOAT_BLOCK_EXT, FORMAT_COMPATIBILITY_CLASS::ASTC_8X5}, + {VK_FORMAT_ASTC_8x5_SRGB_BLOCK, FORMAT_COMPATIBILITY_CLASS::ASTC_8X5}, + {VK_FORMAT_ASTC_8x5_UNORM_BLOCK, FORMAT_COMPATIBILITY_CLASS::ASTC_8X5}, + {VK_FORMAT_ASTC_8x6_SFLOAT_BLOCK_EXT, FORMAT_COMPATIBILITY_CLASS::ASTC_8X6}, + {VK_FORMAT_ASTC_8x6_SRGB_BLOCK, FORMAT_COMPATIBILITY_CLASS::ASTC_8X6}, + {VK_FORMAT_ASTC_8x6_UNORM_BLOCK, FORMAT_COMPATIBILITY_CLASS::ASTC_8X6}, + {VK_FORMAT_ASTC_8x8_SFLOAT_BLOCK_EXT, FORMAT_COMPATIBILITY_CLASS::ASTC_8X8}, + {VK_FORMAT_ASTC_8x8_SRGB_BLOCK, FORMAT_COMPATIBILITY_CLASS::ASTC_8X8}, + {VK_FORMAT_ASTC_8x8_UNORM_BLOCK, FORMAT_COMPATIBILITY_CLASS::ASTC_8X8}, + {VK_FORMAT_B10G11R11_UFLOAT_PACK32, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_B10X6G10X6R10X6G10X6_422_UNORM_4PACK16, + FORMAT_COMPATIBILITY_CLASS::_64BIT_B10G10R10G10}, + {VK_FORMAT_B12X4G12X4R12X4G12X4_422_UNORM_4PACK16, + FORMAT_COMPATIBILITY_CLASS::_64BIT_B12G12R12G12}, + {VK_FORMAT_B16G16R16G16_422_UNORM, FORMAT_COMPATIBILITY_CLASS::_64BIT_B16G16R16G16}, + {VK_FORMAT_B4G4R4A4_UNORM_PACK16, FORMAT_COMPATIBILITY_CLASS::_16BIT}, + {VK_FORMAT_B5G5R5A1_UNORM_PACK16, FORMAT_COMPATIBILITY_CLASS::_16BIT}, + {VK_FORMAT_B5G6R5_UNORM_PACK16, FORMAT_COMPATIBILITY_CLASS::_16BIT}, + {VK_FORMAT_B8G8R8A8_SINT, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_B8G8R8A8_SNORM, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_B8G8R8A8_SRGB, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_B8G8R8A8_SSCALED, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_B8G8R8A8_UINT, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_B8G8R8A8_UNORM, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_B8G8R8A8_USCALED, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_B8G8R8G8_422_UNORM, FORMAT_COMPATIBILITY_CLASS::_32BIT_B8G8R8G8}, + {VK_FORMAT_B8G8R8_SINT, FORMAT_COMPATIBILITY_CLASS::_24BIT}, + {VK_FORMAT_B8G8R8_SNORM, FORMAT_COMPATIBILITY_CLASS::_24BIT}, + {VK_FORMAT_B8G8R8_SRGB, FORMAT_COMPATIBILITY_CLASS::_24BIT}, + {VK_FORMAT_B8G8R8_SSCALED, FORMAT_COMPATIBILITY_CLASS::_24BIT}, + {VK_FORMAT_B8G8R8_UINT, FORMAT_COMPATIBILITY_CLASS::_24BIT}, + {VK_FORMAT_B8G8R8_UNORM, FORMAT_COMPATIBILITY_CLASS::_24BIT}, + {VK_FORMAT_B8G8R8_USCALED, FORMAT_COMPATIBILITY_CLASS::_24BIT}, + {VK_FORMAT_BC1_RGBA_SRGB_BLOCK, FORMAT_COMPATIBILITY_CLASS::BC1_RGBA}, + {VK_FORMAT_BC1_RGBA_UNORM_BLOCK, FORMAT_COMPATIBILITY_CLASS::BC1_RGBA}, + {VK_FORMAT_BC1_RGB_SRGB_BLOCK, FORMAT_COMPATIBILITY_CLASS::BC1_RGB}, + {VK_FORMAT_BC1_RGB_UNORM_BLOCK, FORMAT_COMPATIBILITY_CLASS::BC1_RGB}, + {VK_FORMAT_BC2_SRGB_BLOCK, FORMAT_COMPATIBILITY_CLASS::BC2}, + {VK_FORMAT_BC2_UNORM_BLOCK, FORMAT_COMPATIBILITY_CLASS::BC2}, + {VK_FORMAT_BC3_SRGB_BLOCK, FORMAT_COMPATIBILITY_CLASS::BC3}, + {VK_FORMAT_BC3_UNORM_BLOCK, FORMAT_COMPATIBILITY_CLASS::BC3}, + {VK_FORMAT_BC4_SNORM_BLOCK, FORMAT_COMPATIBILITY_CLASS::BC4}, + {VK_FORMAT_BC4_UNORM_BLOCK, FORMAT_COMPATIBILITY_CLASS::BC4}, + {VK_FORMAT_BC5_SNORM_BLOCK, FORMAT_COMPATIBILITY_CLASS::BC5}, + {VK_FORMAT_BC5_UNORM_BLOCK, FORMAT_COMPATIBILITY_CLASS::BC5}, + {VK_FORMAT_BC6H_SFLOAT_BLOCK, FORMAT_COMPATIBILITY_CLASS::BC6H}, + {VK_FORMAT_BC6H_UFLOAT_BLOCK, FORMAT_COMPATIBILITY_CLASS::BC6H}, + {VK_FORMAT_BC7_SRGB_BLOCK, FORMAT_COMPATIBILITY_CLASS::BC7}, + {VK_FORMAT_BC7_UNORM_BLOCK, FORMAT_COMPATIBILITY_CLASS::BC7}, + {VK_FORMAT_D16_UNORM, FORMAT_COMPATIBILITY_CLASS::D16}, + {VK_FORMAT_D16_UNORM_S8_UINT, FORMAT_COMPATIBILITY_CLASS::D16S8}, + {VK_FORMAT_D24_UNORM_S8_UINT, FORMAT_COMPATIBILITY_CLASS::D24S8}, + {VK_FORMAT_D32_SFLOAT, FORMAT_COMPATIBILITY_CLASS::D32}, + {VK_FORMAT_D32_SFLOAT_S8_UINT, FORMAT_COMPATIBILITY_CLASS::D32S8}, + {VK_FORMAT_E5B9G9R9_UFLOAT_PACK32, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_EAC_R11G11_SNORM_BLOCK, FORMAT_COMPATIBILITY_CLASS::EAC_RG}, + {VK_FORMAT_EAC_R11G11_UNORM_BLOCK, FORMAT_COMPATIBILITY_CLASS::EAC_RG}, + {VK_FORMAT_EAC_R11_SNORM_BLOCK, FORMAT_COMPATIBILITY_CLASS::EAC_R}, + {VK_FORMAT_EAC_R11_UNORM_BLOCK, FORMAT_COMPATIBILITY_CLASS::EAC_R}, + {VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK, FORMAT_COMPATIBILITY_CLASS::ETC2_RGBA}, + {VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK, FORMAT_COMPATIBILITY_CLASS::ETC2_RGBA}, + {VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK, FORMAT_COMPATIBILITY_CLASS::ETC2_EAC_RGBA}, + {VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK, FORMAT_COMPATIBILITY_CLASS::ETC2_EAC_RGBA}, + {VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK, FORMAT_COMPATIBILITY_CLASS::ETC2_RGB}, + {VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK, FORMAT_COMPATIBILITY_CLASS::ETC2_RGB}, + {VK_FORMAT_G10X6B10X6G10X6R10X6_422_UNORM_4PACK16, + FORMAT_COMPATIBILITY_CLASS::_64BIT_G10B10G10R10}, + {VK_FORMAT_G10X6_B10X6R10X6_2PLANE_420_UNORM_3PACK16, + FORMAT_COMPATIBILITY_CLASS::_10BIT_2PLANE_420}, + {VK_FORMAT_G10X6_B10X6R10X6_2PLANE_422_UNORM_3PACK16, + FORMAT_COMPATIBILITY_CLASS::_10BIT_2PLANE_422}, + {VK_FORMAT_G10X6_B10X6R10X6_2PLANE_444_UNORM_3PACK16_EXT, + FORMAT_COMPATIBILITY_CLASS::_10BIT_2PLANE_444}, + {VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_420_UNORM_3PACK16, + FORMAT_COMPATIBILITY_CLASS::_10BIT_3PLANE_420}, + {VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_422_UNORM_3PACK16, + FORMAT_COMPATIBILITY_CLASS::_10BIT_3PLANE_422}, + {VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_444_UNORM_3PACK16, + FORMAT_COMPATIBILITY_CLASS::_10BIT_3PLANE_444}, + {VK_FORMAT_G12X4B12X4G12X4R12X4_422_UNORM_4PACK16, + FORMAT_COMPATIBILITY_CLASS::_64BIT_G12B12G12R12}, + {VK_FORMAT_G12X4_B12X4R12X4_2PLANE_420_UNORM_3PACK16, + FORMAT_COMPATIBILITY_CLASS::_12BIT_2PLANE_420}, + {VK_FORMAT_G12X4_B12X4R12X4_2PLANE_422_UNORM_3PACK16, + FORMAT_COMPATIBILITY_CLASS::_12BIT_2PLANE_422}, + {VK_FORMAT_G12X4_B12X4R12X4_2PLANE_444_UNORM_3PACK16_EXT, + FORMAT_COMPATIBILITY_CLASS::_12BIT_2PLANE_444}, + {VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_420_UNORM_3PACK16, + FORMAT_COMPATIBILITY_CLASS::_12BIT_3PLANE_420}, + {VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_422_UNORM_3PACK16, + FORMAT_COMPATIBILITY_CLASS::_12BIT_3PLANE_422}, + {VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_444_UNORM_3PACK16, + FORMAT_COMPATIBILITY_CLASS::_12BIT_3PLANE_444}, + {VK_FORMAT_G16B16G16R16_422_UNORM, FORMAT_COMPATIBILITY_CLASS::_64BIT_G16B16G16R16}, + {VK_FORMAT_G16_B16R16_2PLANE_420_UNORM, FORMAT_COMPATIBILITY_CLASS::_16BIT_2PLANE_420}, + {VK_FORMAT_G16_B16R16_2PLANE_422_UNORM, FORMAT_COMPATIBILITY_CLASS::_16BIT_2PLANE_422}, + {VK_FORMAT_G16_B16R16_2PLANE_444_UNORM_EXT, FORMAT_COMPATIBILITY_CLASS::_16BIT_2PLANE_444}, + {VK_FORMAT_G16_B16_R16_3PLANE_420_UNORM, FORMAT_COMPATIBILITY_CLASS::_16BIT_3PLANE_420}, + {VK_FORMAT_G16_B16_R16_3PLANE_422_UNORM, FORMAT_COMPATIBILITY_CLASS::_16BIT_3PLANE_422}, + {VK_FORMAT_G16_B16_R16_3PLANE_444_UNORM, FORMAT_COMPATIBILITY_CLASS::_16BIT_3PLANE_444}, + {VK_FORMAT_G8B8G8R8_422_UNORM, FORMAT_COMPATIBILITY_CLASS::_32BIT_G8B8G8R8}, + {VK_FORMAT_G8_B8R8_2PLANE_420_UNORM, FORMAT_COMPATIBILITY_CLASS::_8BIT_2PLANE_420}, + {VK_FORMAT_G8_B8R8_2PLANE_422_UNORM, FORMAT_COMPATIBILITY_CLASS::_8BIT_2PLANE_422}, + {VK_FORMAT_G8_B8R8_2PLANE_444_UNORM_EXT, FORMAT_COMPATIBILITY_CLASS::_8BIT_2PLANE_444}, + {VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM, FORMAT_COMPATIBILITY_CLASS::_8BIT_3PLANE_420}, + {VK_FORMAT_G8_B8_R8_3PLANE_422_UNORM, FORMAT_COMPATIBILITY_CLASS::_8BIT_3PLANE_422}, + {VK_FORMAT_G8_B8_R8_3PLANE_444_UNORM, FORMAT_COMPATIBILITY_CLASS::_8BIT_3PLANE_444}, + {VK_FORMAT_PVRTC1_2BPP_SRGB_BLOCK_IMG, FORMAT_COMPATIBILITY_CLASS::PVRTC1_2BPP}, + {VK_FORMAT_PVRTC1_2BPP_UNORM_BLOCK_IMG, FORMAT_COMPATIBILITY_CLASS::PVRTC1_2BPP}, + {VK_FORMAT_PVRTC1_4BPP_SRGB_BLOCK_IMG, FORMAT_COMPATIBILITY_CLASS::PVRTC1_4BPP}, + {VK_FORMAT_PVRTC1_4BPP_UNORM_BLOCK_IMG, FORMAT_COMPATIBILITY_CLASS::PVRTC1_4BPP}, + {VK_FORMAT_PVRTC2_2BPP_SRGB_BLOCK_IMG, FORMAT_COMPATIBILITY_CLASS::PVRTC2_2BPP}, + {VK_FORMAT_PVRTC2_2BPP_UNORM_BLOCK_IMG, FORMAT_COMPATIBILITY_CLASS::PVRTC2_2BPP}, + {VK_FORMAT_PVRTC2_4BPP_SRGB_BLOCK_IMG, FORMAT_COMPATIBILITY_CLASS::PVRTC2_4BPP}, + {VK_FORMAT_PVRTC2_4BPP_UNORM_BLOCK_IMG, FORMAT_COMPATIBILITY_CLASS::PVRTC2_4BPP}, + {VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16, FORMAT_COMPATIBILITY_CLASS::_64BIT_R10G10B10A10}, + {VK_FORMAT_R10X6G10X6_UNORM_2PACK16, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_R10X6_UNORM_PACK16, FORMAT_COMPATIBILITY_CLASS::_16BIT}, + {VK_FORMAT_R12X4G12X4B12X4A12X4_UNORM_4PACK16, FORMAT_COMPATIBILITY_CLASS::_64BIT_R12G12B12A12}, + {VK_FORMAT_R12X4G12X4_UNORM_2PACK16, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_R12X4_UNORM_PACK16, FORMAT_COMPATIBILITY_CLASS::_16BIT}, + {VK_FORMAT_R16G16B16A16_SFLOAT, FORMAT_COMPATIBILITY_CLASS::_64BIT}, + {VK_FORMAT_R16G16B16A16_SINT, FORMAT_COMPATIBILITY_CLASS::_64BIT}, + {VK_FORMAT_R16G16B16A16_SNORM, FORMAT_COMPATIBILITY_CLASS::_64BIT}, + {VK_FORMAT_R16G16B16A16_SSCALED, FORMAT_COMPATIBILITY_CLASS::_64BIT}, + {VK_FORMAT_R16G16B16A16_UINT, FORMAT_COMPATIBILITY_CLASS::_64BIT}, + {VK_FORMAT_R16G16B16A16_UNORM, FORMAT_COMPATIBILITY_CLASS::_64BIT}, + {VK_FORMAT_R16G16B16A16_USCALED, FORMAT_COMPATIBILITY_CLASS::_64BIT}, + {VK_FORMAT_R16G16B16_SFLOAT, FORMAT_COMPATIBILITY_CLASS::_48BIT}, + {VK_FORMAT_R16G16B16_SINT, FORMAT_COMPATIBILITY_CLASS::_48BIT}, + {VK_FORMAT_R16G16B16_SNORM, FORMAT_COMPATIBILITY_CLASS::_48BIT}, + {VK_FORMAT_R16G16B16_SSCALED, FORMAT_COMPATIBILITY_CLASS::_48BIT}, + {VK_FORMAT_R16G16B16_UINT, FORMAT_COMPATIBILITY_CLASS::_48BIT}, + {VK_FORMAT_R16G16B16_UNORM, FORMAT_COMPATIBILITY_CLASS::_48BIT}, + {VK_FORMAT_R16G16B16_USCALED, FORMAT_COMPATIBILITY_CLASS::_48BIT}, + {VK_FORMAT_R16G16_SFLOAT, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_R16G16_SINT, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_R16G16_SNORM, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_R16G16_SSCALED, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_R16G16_UINT, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_R16G16_UNORM, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_R16G16_USCALED, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_R16_SFLOAT, FORMAT_COMPATIBILITY_CLASS::_16BIT}, + {VK_FORMAT_R16_SINT, FORMAT_COMPATIBILITY_CLASS::_16BIT}, + {VK_FORMAT_R16_SNORM, FORMAT_COMPATIBILITY_CLASS::_16BIT}, + {VK_FORMAT_R16_SSCALED, FORMAT_COMPATIBILITY_CLASS::_16BIT}, + {VK_FORMAT_R16_UINT, FORMAT_COMPATIBILITY_CLASS::_16BIT}, + {VK_FORMAT_R16_UNORM, FORMAT_COMPATIBILITY_CLASS::_16BIT}, + {VK_FORMAT_R16_USCALED, FORMAT_COMPATIBILITY_CLASS::_16BIT}, + {VK_FORMAT_R32G32B32A32_SFLOAT, FORMAT_COMPATIBILITY_CLASS::_128BIT}, + {VK_FORMAT_R32G32B32A32_SINT, FORMAT_COMPATIBILITY_CLASS::_128BIT}, + {VK_FORMAT_R32G32B32A32_UINT, FORMAT_COMPATIBILITY_CLASS::_128BIT}, + {VK_FORMAT_R32G32B32_SFLOAT, FORMAT_COMPATIBILITY_CLASS::_96BIT}, + {VK_FORMAT_R32G32B32_SINT, FORMAT_COMPATIBILITY_CLASS::_96BIT}, + {VK_FORMAT_R32G32B32_UINT, FORMAT_COMPATIBILITY_CLASS::_96BIT}, + {VK_FORMAT_R32G32_SFLOAT, FORMAT_COMPATIBILITY_CLASS::_64BIT}, + {VK_FORMAT_R32G32_SINT, FORMAT_COMPATIBILITY_CLASS::_64BIT}, + {VK_FORMAT_R32G32_UINT, FORMAT_COMPATIBILITY_CLASS::_64BIT}, + {VK_FORMAT_R32_SFLOAT, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_R32_SINT, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_R32_UINT, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_R4G4B4A4_UNORM_PACK16, FORMAT_COMPATIBILITY_CLASS::_16BIT}, + {VK_FORMAT_R4G4_UNORM_PACK8, FORMAT_COMPATIBILITY_CLASS::_8BIT}, + {VK_FORMAT_R5G5B5A1_UNORM_PACK16, FORMAT_COMPATIBILITY_CLASS::_16BIT}, + {VK_FORMAT_R5G6B5_UNORM_PACK16, FORMAT_COMPATIBILITY_CLASS::_16BIT}, + {VK_FORMAT_R64G64B64A64_SFLOAT, FORMAT_COMPATIBILITY_CLASS::_256BIT}, + {VK_FORMAT_R64G64B64A64_SINT, FORMAT_COMPATIBILITY_CLASS::_256BIT}, + {VK_FORMAT_R64G64B64A64_UINT, FORMAT_COMPATIBILITY_CLASS::_256BIT}, + {VK_FORMAT_R64G64B64_SFLOAT, FORMAT_COMPATIBILITY_CLASS::_192BIT}, + {VK_FORMAT_R64G64B64_SINT, FORMAT_COMPATIBILITY_CLASS::_192BIT}, + {VK_FORMAT_R64G64B64_UINT, FORMAT_COMPATIBILITY_CLASS::_192BIT}, + {VK_FORMAT_R64G64_SFLOAT, FORMAT_COMPATIBILITY_CLASS::_128BIT}, + {VK_FORMAT_R64G64_SINT, FORMAT_COMPATIBILITY_CLASS::_128BIT}, + {VK_FORMAT_R64G64_UINT, FORMAT_COMPATIBILITY_CLASS::_128BIT}, + {VK_FORMAT_R64_SFLOAT, FORMAT_COMPATIBILITY_CLASS::_64BIT}, + {VK_FORMAT_R64_SINT, FORMAT_COMPATIBILITY_CLASS::_64BIT}, + {VK_FORMAT_R64_UINT, FORMAT_COMPATIBILITY_CLASS::_64BIT}, + {VK_FORMAT_R8G8B8A8_SINT, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_R8G8B8A8_SNORM, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_R8G8B8A8_SRGB, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_R8G8B8A8_SSCALED, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_R8G8B8A8_UINT, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_R8G8B8A8_UNORM, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_R8G8B8A8_USCALED, FORMAT_COMPATIBILITY_CLASS::_32BIT}, + {VK_FORMAT_R8G8B8_SINT, FORMAT_COMPATIBILITY_CLASS::_24BIT}, + {VK_FORMAT_R8G8B8_SNORM, FORMAT_COMPATIBILITY_CLASS::_24BIT}, + {VK_FORMAT_R8G8B8_SRGB, FORMAT_COMPATIBILITY_CLASS::_24BIT}, + {VK_FORMAT_R8G8B8_SSCALED, FORMAT_COMPATIBILITY_CLASS::_24BIT}, + {VK_FORMAT_R8G8B8_UINT, FORMAT_COMPATIBILITY_CLASS::_24BIT}, + {VK_FORMAT_R8G8B8_UNORM, FORMAT_COMPATIBILITY_CLASS::_24BIT}, + {VK_FORMAT_R8G8B8_USCALED, FORMAT_COMPATIBILITY_CLASS::_24BIT}, + {VK_FORMAT_R8G8_SINT, FORMAT_COMPATIBILITY_CLASS::_16BIT}, + {VK_FORMAT_R8G8_SNORM, FORMAT_COMPATIBILITY_CLASS::_16BIT}, + {VK_FORMAT_R8G8_SRGB, FORMAT_COMPATIBILITY_CLASS::_16BIT}, + {VK_FORMAT_R8G8_SSCALED, FORMAT_COMPATIBILITY_CLASS::_16BIT}, + {VK_FORMAT_R8G8_UINT, FORMAT_COMPATIBILITY_CLASS::_16BIT}, + {VK_FORMAT_R8G8_UNORM, FORMAT_COMPATIBILITY_CLASS::_16BIT}, + {VK_FORMAT_R8G8_USCALED, FORMAT_COMPATIBILITY_CLASS::_16BIT}, + {VK_FORMAT_R8_SINT, FORMAT_COMPATIBILITY_CLASS::_8BIT}, + {VK_FORMAT_R8_SNORM, FORMAT_COMPATIBILITY_CLASS::_8BIT}, + {VK_FORMAT_R8_SRGB, FORMAT_COMPATIBILITY_CLASS::_8BIT}, + {VK_FORMAT_R8_SSCALED, FORMAT_COMPATIBILITY_CLASS::_8BIT}, + {VK_FORMAT_R8_UINT, FORMAT_COMPATIBILITY_CLASS::_8BIT}, + {VK_FORMAT_R8_UNORM, FORMAT_COMPATIBILITY_CLASS::_8BIT}, + {VK_FORMAT_R8_USCALED, FORMAT_COMPATIBILITY_CLASS::_8BIT}, + {VK_FORMAT_S8_UINT, FORMAT_COMPATIBILITY_CLASS::S8}, + {VK_FORMAT_X8_D24_UNORM_PACK32, FORMAT_COMPATIBILITY_CLASS::D24}, + {VK_FORMAT_UNDEFINED, FORMAT_COMPATIBILITY_CLASS::NONE}, +}; + +/** + * @return If the two formats are compatible according to Vulkan's format compatibility rules + * @url + * https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#formats-compatibility + */ +static bool IsVulkanFormatCompatible(vk::Format lhs, vk::Format rhs) { + if (lhs == rhs) { + return true; + } + return vkFormatClassTable.at(VkFormat(lhs)) == vkFormatClassTable.at(VkFormat(rhs)); +} +} // namespace VideoCore diff --git a/src/video_core/texture_cache/image.cpp b/src/video_core/texture_cache/image.cpp index 0b725655bd4..4ce6e1eea6b 100644 --- a/src/video_core/texture_cache/image.cpp +++ b/src/video_core/texture_cache/image.cpp @@ -1,20 +1,22 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#define VULKAN_HPP_NO_EXCEPTIONS +#include #include "common/assert.h" -#include "common/config.h" #include "video_core/renderer_vulkan/liverpool_to_vk.h" #include "video_core/renderer_vulkan/vk_instance.h" #include "video_core/renderer_vulkan/vk_scheduler.h" #include "video_core/texture_cache/image.h" -#include "video_core/texture_cache/tile_manager.h" +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnullability-completeness" #include +#pragma GCC diagnostic pop namespace VideoCore { using namespace Vulkan; -using Libraries::VideoOut::TilingMode; bool ImageInfo::IsBlockCoded() const { switch (pixel_format) { @@ -73,7 +75,6 @@ static vk::ImageUsageFlags ImageUsageFlags(const ImageInfo& info) { if (!info.IsBlockCoded() && !info.IsPacked()) { usage |= vk::ImageUsageFlagBits::eColorAttachment; } - // In cases where an image is created as a render/depth target and cleared with compute, // we cannot predict whether it will be used as a storage image. A proper solution would // involve re-creating the resource with a new configuration and copying previous content @@ -124,7 +125,7 @@ Image::Image(const Vulkan::Instance& instance_, Vulkan::Scheduler& scheduler_, // the texture cache should re-create the resource with the usage requested vk::ImageCreateFlags flags{vk::ImageCreateFlagBits::eMutableFormat | vk::ImageCreateFlagBits::eExtendedUsage}; - if (info.props.is_cube) { + if (info.props.is_cube || (info.type == vk::ImageType::e2D && info.resources.layers >= 6)) { flags |= vk::ImageCreateFlagBits::eCubeCompatible; } else if (info.props.is_volume) { flags |= vk::ImageCreateFlagBits::e2DArrayCompatible; @@ -147,10 +148,18 @@ Image::Image(const Vulkan::Instance& instance_, Vulkan::Scheduler& scheduler_, break; } + constexpr auto tiling = vk::ImageTiling::eOptimal; + const auto supported_format = instance->GetSupportedFormat(info.pixel_format); + const auto properties = instance->GetPhysicalDevice().getImageFormatProperties( + supported_format, info.type, tiling, usage, flags); + const auto supported_samples = properties.result == vk::Result::eSuccess + ? properties.value.sampleCounts + : vk::SampleCountFlagBits::e1; + const vk::ImageCreateInfo image_ci = { .flags = flags, .imageType = info.type, - .format = instance->GetSupportedFormat(info.pixel_format), + .format = supported_format, .extent{ .width = info.size.width, .height = info.size.height, @@ -158,64 +167,145 @@ Image::Image(const Vulkan::Instance& instance_, Vulkan::Scheduler& scheduler_, }, .mipLevels = static_cast(info.resources.levels), .arrayLayers = static_cast(info.resources.layers), - .samples = LiverpoolToVK::NumSamples(info.num_samples), - .tiling = vk::ImageTiling::eOptimal, + .samples = LiverpoolToVK::NumSamples(info.num_samples, supported_samples), + .tiling = tiling, .usage = usage, .initialLayout = vk::ImageLayout::eUndefined, }; image.Create(image_ci); - Vulkan::SetObjectName(instance->GetDevice(), (vk::Image)image, "Image {:#x}:{:#x}", - info.guest_address, info.guest_size_bytes); + Vulkan::SetObjectName(instance->GetDevice(), (vk::Image)image, "Image {}x{}x{} {:#x}:{:#x}", + info.size.width, info.size.height, info.size.depth, info.guest_address, + info.guest_size_bytes); } -void Image::Transit(vk::ImageLayout dst_layout, vk::Flags dst_mask, - vk::CommandBuffer cmdbuf) { - if (dst_layout == layout && dst_mask == access_mask) { - return; +boost::container::small_vector Image::GetBarriers( + vk::ImageLayout dst_layout, vk::Flags dst_mask, + vk::PipelineStageFlags2 dst_stage, std::optional subres_range) { + const bool needs_partial_transition = + subres_range && + (subres_range->base != SubresourceBase{} || subres_range->extent != info.resources); + const bool partially_transited = !subresource_states.empty(); + + boost::container::small_vector barriers{}; + if (needs_partial_transition || partially_transited) { + if (!partially_transited) { + subresource_states.resize(info.resources.levels * info.resources.layers); + std::fill(subresource_states.begin(), subresource_states.end(), last_state); + } + + // In case of partial transition, we need to change the specified subresources only. + // Otherwise all subresources need to be set to the same state so we can use a full + // resource transition for the next time. + const auto mips = + needs_partial_transition + ? std::ranges::views::iota(subres_range->base.level, + subres_range->base.level + subres_range->extent.levels) + : std::views::iota(0u, info.resources.levels); + const auto layers = + needs_partial_transition + ? std::ranges::views::iota(subres_range->base.layer, + subres_range->base.layer + subres_range->extent.layers) + : std::views::iota(0u, info.resources.layers); + + for (u32 mip : mips) { + for (u32 layer : layers) { + // NOTE: these loops may produce a lot of small barriers. + // If this becomes a problem, we can optimize it by merging adjacent barriers. + const auto subres_idx = mip * info.resources.layers + layer; + ASSERT(subres_idx < subresource_states.size()); + auto& state = subresource_states[subres_idx]; + + if (state.layout != dst_layout || state.access_mask != dst_mask) { + barriers.emplace_back(vk::ImageMemoryBarrier2{ + .srcStageMask = state.pl_stage, + .srcAccessMask = state.access_mask, + .dstStageMask = dst_stage, + .dstAccessMask = dst_mask, + .oldLayout = state.layout, + .newLayout = dst_layout, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = image, + .subresourceRange{ + .aspectMask = aspect_mask, + .baseMipLevel = mip, + .levelCount = 1, + .baseArrayLayer = layer, + .layerCount = 1, + }, + }); + state.layout = dst_layout; + state.access_mask = dst_mask; + state.pl_stage = dst_stage; + } + } + } + + if (!needs_partial_transition) { + subresource_states.clear(); + } + } else { // Full resource transition + if (last_state.layout == dst_layout && last_state.access_mask == dst_mask) { + return {}; + } + + barriers.emplace_back(vk::ImageMemoryBarrier2{ + .srcStageMask = last_state.pl_stage, + .srcAccessMask = last_state.access_mask, + .dstStageMask = dst_stage, + .dstAccessMask = dst_mask, + .oldLayout = last_state.layout, + .newLayout = dst_layout, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = image, + .subresourceRange{ + .aspectMask = aspect_mask, + .baseMipLevel = 0, + .levelCount = VK_REMAINING_MIP_LEVELS, + .baseArrayLayer = 0, + .layerCount = VK_REMAINING_ARRAY_LAYERS, + }, + }); } - const vk::ImageMemoryBarrier barrier = { - .srcAccessMask = access_mask, - .dstAccessMask = dst_mask, - .oldLayout = layout, - .newLayout = dst_layout, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = image, - .subresourceRange{ - .aspectMask = aspect_mask, - .baseMipLevel = 0, - .levelCount = VK_REMAINING_MIP_LEVELS, - .baseArrayLayer = 0, - .layerCount = VK_REMAINING_ARRAY_LAYERS, - }, - }; + last_state.layout = dst_layout; + last_state.access_mask = dst_mask; + last_state.pl_stage = dst_stage; + + return barriers; +} +void Image::Transit(vk::ImageLayout dst_layout, vk::Flags dst_mask, + std::optional range, vk::CommandBuffer cmdbuf /*= {}*/) { // Adjust pipieline stage - const vk::PipelineStageFlags dst_pl_stage = - (dst_mask == vk::AccessFlagBits::eTransferRead || - dst_mask == vk::AccessFlagBits::eTransferWrite) - ? vk::PipelineStageFlagBits::eTransfer - : vk::PipelineStageFlagBits::eAllGraphics | vk::PipelineStageFlagBits::eComputeShader; + const vk::PipelineStageFlags2 dst_pl_stage = + (dst_mask == vk::AccessFlagBits2::eTransferRead || + dst_mask == vk::AccessFlagBits2::eTransferWrite) + ? vk::PipelineStageFlagBits2::eTransfer + : vk::PipelineStageFlagBits2::eAllGraphics | vk::PipelineStageFlagBits2::eComputeShader; + + const auto barriers = GetBarriers(dst_layout, dst_mask, dst_pl_stage, range); + if (barriers.empty()) { + return; + } if (!cmdbuf) { // When using external cmdbuf you are responsible for ending rp. scheduler->EndRendering(); cmdbuf = scheduler->CommandBuffer(); } - cmdbuf.pipelineBarrier(pl_stage, dst_pl_stage, vk::DependencyFlagBits::eByRegion, {}, {}, - barrier); - - layout = dst_layout; - access_mask = dst_mask; - pl_stage = dst_pl_stage; + cmdbuf.pipelineBarrier2(vk::DependencyInfo{ + .imageMemoryBarrierCount = static_cast(barriers.size()), + .pImageMemoryBarriers = barriers.data(), + }); } void Image::Upload(vk::Buffer buffer, u64 offset) { scheduler->EndRendering(); - Transit(vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits::eTransferWrite); + Transit(vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits2::eTransferWrite, {}); // Copy to the image. const auto aspect = aspect_mask & vk::ImageAspectFlagBits::eStencil @@ -239,7 +329,77 @@ void Image::Upload(vk::Buffer buffer, u64 offset) { cmdbuf.copyBufferToImage(buffer, image, vk::ImageLayout::eTransferDstOptimal, image_copy); Transit(vk::ImageLayout::eGeneral, - vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eTransferRead); + vk::AccessFlagBits2::eShaderRead | vk::AccessFlagBits2::eTransferRead, {}); +} + +void Image::CopyImage(const Image& image) { + scheduler->EndRendering(); + Transit(vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits2::eTransferWrite, {}); + + auto cmdbuf = scheduler->CommandBuffer(); + + boost::container::small_vector image_copy{}; + for (u32 m = 0; m < image.info.resources.levels; ++m) { + const auto mip_w = std::max(info.size.width >> m, 1u); + const auto mip_h = std::max(info.size.height >> m, 1u); + const auto mip_d = std::max(info.size.depth >> m, 1u); + + image_copy.emplace_back(vk::ImageCopy{ + .srcSubresource{ + .aspectMask = image.aspect_mask, + .mipLevel = m, + .baseArrayLayer = 0, + .layerCount = image.info.resources.layers, + }, + .dstSubresource{ + .aspectMask = image.aspect_mask, + .mipLevel = m, + .baseArrayLayer = 0, + .layerCount = image.info.resources.layers, + }, + .extent = {mip_w, mip_h, mip_d}, + }); + } + cmdbuf.copyImage(image.image, image.last_state.layout, this->image, this->last_state.layout, + image_copy); + + Transit(vk::ImageLayout::eGeneral, + vk::AccessFlagBits2::eShaderRead | vk::AccessFlagBits2::eTransferRead, {}); +} + +void Image::CopyMip(const Image& image, u32 mip) { + scheduler->EndRendering(); + Transit(vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits2::eTransferWrite, {}); + + auto cmdbuf = scheduler->CommandBuffer(); + + const auto mip_w = std::max(info.size.width >> mip, 1u); + const auto mip_h = std::max(info.size.height >> mip, 1u); + const auto mip_d = std::max(info.size.depth >> mip, 1u); + + ASSERT(mip_w == image.info.size.width); + ASSERT(mip_h == image.info.size.height); + + const vk::ImageCopy image_copy{ + .srcSubresource{ + .aspectMask = image.aspect_mask, + .mipLevel = 0, + .baseArrayLayer = 0, + .layerCount = image.info.resources.layers, + }, + .dstSubresource{ + .aspectMask = image.aspect_mask, + .mipLevel = mip, + .baseArrayLayer = 0, + .layerCount = info.resources.layers, + }, + .extent = {mip_w, mip_h, mip_d}, + }; + cmdbuf.copyImage(image.image, image.last_state.layout, this->image, this->last_state.layout, + image_copy); + + Transit(vk::ImageLayout::eGeneral, + vk::AccessFlagBits2::eShaderRead | vk::AccessFlagBits2::eTransferRead, {}); } Image::~Image() = default; diff --git a/src/video_core/texture_cache/image.h b/src/video_core/texture_cache/image.h index 3df8ddb7052..01e6fe8f3b5 100644 --- a/src/video_core/texture_cache/image.h +++ b/src/video_core/texture_cache/image.h @@ -5,13 +5,9 @@ #include "common/enum.h" #include "common/types.h" -#include "core/libraries/videoout/buffer.h" -#include "video_core/amdgpu/liverpool.h" -#include "video_core/amdgpu/resource.h" #include "video_core/renderer_vulkan/vk_common.h" #include "video_core/texture_cache/image_info.h" #include "video_core/texture_cache/image_view.h" -#include "video_core/texture_cache/types.h" #include @@ -26,12 +22,16 @@ VK_DEFINE_HANDLE(VmaAllocator) namespace VideoCore { enum ImageFlagBits : u32 { - CpuModified = 1 << 2, ///< Contents have been modified from the CPU + CpuDirty = 1 << 1, ///< Contents have been modified from the CPU + GpuDirty = 1 << 2, ///< Contents have been modified from the GPU (valid data in buffer cache) + Dirty = CpuDirty | GpuDirty, GpuModified = 1 << 3, ///< Contents have been modified from the GPU Tracked = 1 << 4, ///< Writes and reads are being hooked from the CPU Registered = 1 << 6, ///< True when the image is registered Picked = 1 << 7, ///< Temporary flag to mark the image as picked MetaRegistered = 1 << 8, ///< True when metadata for this surface is known and registered + Bound = 1 << 9, ///< True when the image is bound to a descriptor set + NeedsRebind = 1 << 10, ///< True when the image needs to be rebound }; DECLARE_ENUM_FLAG_OPERATORS(ImageFlagBits) @@ -91,16 +91,22 @@ struct Image { return image_view_ids[std::distance(image_view_infos.begin(), it)]; } - void Transit(vk::ImageLayout dst_layout, vk::Flags dst_mask, - vk::CommandBuffer cmdbuf = {}); + boost::container::small_vector GetBarriers( + vk::ImageLayout dst_layout, vk::Flags dst_mask, + vk::PipelineStageFlags2 dst_stage, std::optional subres_range); + void Transit(vk::ImageLayout dst_layout, vk::Flags dst_mask, + std::optional range, vk::CommandBuffer cmdbuf = {}); void Upload(vk::Buffer buffer, u64 offset); + void CopyImage(const Image& image); + void CopyMip(const Image& image, u32 mip); + const Vulkan::Instance* instance; Vulkan::Scheduler* scheduler; ImageInfo info; UniqueImage image; vk::ImageAspectFlags aspect_mask = vk::ImageAspectFlagBits::eColor; - ImageFlagBits flags = ImageFlagBits::CpuModified; + ImageFlagBits flags = ImageFlagBits::Dirty; VAddr cpu_addr = 0; VAddr cpu_addr_end = 0; std::vector image_view_infos; @@ -108,10 +114,15 @@ struct Image { // Resource state tracking vk::ImageUsageFlags usage; - vk::Flags pl_stage = vk::PipelineStageFlagBits::eAllCommands; - vk::Flags access_mask = vk::AccessFlagBits::eNone; - vk::ImageLayout layout = vk::ImageLayout::eUndefined; - boost::container::small_vector mip_hashes; + struct State { + vk::Flags pl_stage = vk::PipelineStageFlagBits2::eAllCommands; + vk::Flags access_mask = vk::AccessFlagBits2::eNone; + vk::ImageLayout layout = vk::ImageLayout::eUndefined; + }; + State last_state{}; + std::vector subresource_states{}; + boost::container::small_vector mip_hashes{}; + u64 tick_accessed_last{0}; }; } // namespace VideoCore diff --git a/src/video_core/texture_cache/image_info.cpp b/src/video_core/texture_cache/image_info.cpp index 4ac4aee8fea..521e4118fa3 100644 --- a/src/video_core/texture_cache/image_info.cpp +++ b/src/video_core/texture_cache/image_info.cpp @@ -174,6 +174,7 @@ ImageInfo::ImageInfo(const AmdGpu::Liverpool::ColorBuffer& buffer, const auto color_slice_sz = buffer.GetColorSliceSize(); guest_size_bytes = color_slice_sz * buffer.NumSlices(); mips_layout.emplace_back(color_slice_sz, pitch, 0); + tiling_idx = static_cast(buffer.attrib.tile_mode_index.Value()); } ImageInfo::ImageInfo(const AmdGpu::Liverpool::DepthBuffer& buffer, u32 num_slices, @@ -186,7 +187,7 @@ ImageInfo::ImageInfo(const AmdGpu::Liverpool::DepthBuffer& buffer, u32 num_slice size.width = hint.Valid() ? hint.width : buffer.Pitch(); size.height = hint.Valid() ? hint.height : buffer.Height(); size.depth = 1; - pitch = size.width; + pitch = buffer.Pitch(); resources.layers = num_slices; meta_info.htile_addr = buffer.z_info.tile_surface_en ? htile_address : 0; usage.depth_target = true; @@ -199,9 +200,13 @@ ImageInfo::ImageInfo(const AmdGpu::Liverpool::DepthBuffer& buffer, u32 num_slice mips_layout.emplace_back(depth_slice_sz, pitch, 0); } -ImageInfo::ImageInfo(const AmdGpu::Image& image) noexcept { +ImageInfo::ImageInfo(const AmdGpu::Image& image, const Shader::ImageResource& desc) noexcept { tiling_mode = image.GetTilingMode(); pixel_format = LiverpoolToVK::SurfaceFormat(image.GetDataFmt(), image.GetNumberFmt()); + // Override format if image is forced to be a depth target + if (desc.is_depth) { + pixel_format = LiverpoolToVK::PromoteFormatToDepth(pixel_format); + } type = ConvertImageType(image.GetType()); props.is_tiled = image.IsTiled(); props.is_cube = image.GetType() == AmdGpu::ImageType::Cube; @@ -213,7 +218,7 @@ ImageInfo::ImageInfo(const AmdGpu::Image& image) noexcept { size.depth = props.is_volume ? image.depth + 1 : 1; pitch = image.Pitch(); resources.levels = image.NumLevels(); - resources.layers = image.NumLayers(); + resources.layers = image.NumLayers(desc.is_array); num_bits = NumBits(image.GetDataFmt()); usage.texture = true; @@ -249,7 +254,6 @@ void ImageInfo::UpdateSize() { switch (tiling_mode) { case AmdGpu::TilingMode::Display_Linear: { - ASSERT(!props.is_cube); std::tie(mip_info.pitch, mip_info.size) = ImageSizeLinearAligned(mip_w, mip_h, bpp, num_samples); mip_info.height = mip_h; @@ -287,4 +291,74 @@ void ImageInfo::UpdateSize() { guest_size_bytes *= resources.layers; } +bool ImageInfo::IsMipOf(const ImageInfo& info) const { + if (!IsCompatible(info)) { + return false; + } + + // Currently we expect only on level to be copied. + if (resources.levels != 1) { + return false; + } + + const int mip = info.resources.levels - resources.levels; + if (mip < 1) { + return false; + } + + const auto mip_w = std::max(info.size.width >> mip, 1u); + const auto mip_h = std::max(info.size.height >> mip, 1u); + if ((size.width != mip_w) || (size.height != mip_h)) { + return false; + } + + const auto mip_d = std::max(info.size.depth >> mip, 1u); + if (info.type == vk::ImageType::e3D && type == vk::ImageType::e2D) { + // In case of 2D array to 3D copy, make sure we have proper number of layers. + if (resources.layers != mip_d) { + return false; + } + } else { + if (type != info.type) { + return false; + } + } + + // Check if the mip has correct size. + if (info.mips_layout.size() <= mip || info.mips_layout[mip].size != guest_size_bytes) { + return false; + } + + return true; +} + +bool ImageInfo::IsSliceOf(const ImageInfo& info) const { + if (!IsCompatible(info)) { + return false; + } + + // Array slices should be of the same type. + if (type != info.type) { + return false; + } + + // 2D dimensions of both images should be the same. + if ((size.width != info.size.width) || (size.height != info.size.height)) { + return false; + } + + // Check for size alignment. + const bool slice_size = info.guest_size_bytes / info.resources.layers; + if (guest_size_bytes % slice_size != 0) { + return false; + } + + // Ensure that address is aligned too. + if (((info.guest_address - guest_address) % guest_size_bytes) != 0) { + return false; + } + + return true; +} + } // namespace VideoCore diff --git a/src/video_core/texture_cache/image_info.h b/src/video_core/texture_cache/image_info.h index ddad318d9d0..2ae2547f7ef 100644 --- a/src/video_core/texture_cache/image_info.h +++ b/src/video_core/texture_cache/image_info.h @@ -3,9 +3,9 @@ #pragma once -#include "common/enum.h" #include "common/types.h" #include "core/libraries/videoout/buffer.h" +#include "shader_recompiler/info.h" #include "video_core/amdgpu/liverpool.h" #include "video_core/texture_cache/types.h" @@ -20,7 +20,7 @@ struct ImageInfo { const AmdGpu::Liverpool::CbDbExtent& hint = {}) noexcept; ImageInfo(const AmdGpu::Liverpool::DepthBuffer& buffer, u32 num_slices, VAddr htile_address, const AmdGpu::Liverpool::CbDbExtent& hint = {}) noexcept; - ImageInfo(const AmdGpu::Image& image) noexcept; + ImageInfo(const AmdGpu::Image& image, const Shader::ImageResource& desc) noexcept; bool IsTiled() const { return tiling_mode != AmdGpu::TilingMode::Display_Linear; @@ -29,6 +29,15 @@ struct ImageInfo { bool IsPacked() const; bool IsDepthStencil() const; + bool IsMipOf(const ImageInfo& info) const; + bool IsSliceOf(const ImageInfo& info) const; + + /// Verifies if images are compatible for subresource merging. + bool IsCompatible(const ImageInfo& info) const { + return (pixel_format == info.pixel_format && tiling_idx == info.tiling_idx && + num_samples == info.num_samples && num_bits == info.num_bits); + } + void UpdateSize(); struct { diff --git a/src/video_core/texture_cache/image_view.cpp b/src/video_core/texture_cache/image_view.cpp index bcdc11ad974..2aad1afb61c 100644 --- a/src/video_core/texture_cache/image_view.cpp +++ b/src/video_core/texture_cache/image_view.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include "common/logging/log.h" +#include "shader_recompiler/info.h" #include "video_core/amdgpu/resource.h" #include "video_core/renderer_vulkan/liverpool_to_vk.h" #include "video_core/renderer_vulkan/vk_instance.h" @@ -66,14 +67,40 @@ vk::Format TrySwizzleFormat(vk::Format format, u32 dst_sel) { return format; } -ImageViewInfo::ImageViewInfo(const AmdGpu::Image& image, bool is_storage_) noexcept - : is_storage{is_storage_} { - type = ConvertImageViewType(image.GetType()); - format = Vulkan::LiverpoolToVK::SurfaceFormat(image.GetDataFmt(), image.GetNumberFmt()); +ImageViewInfo::ImageViewInfo(const AmdGpu::Image& image, const Shader::ImageResource& desc) noexcept + : is_storage{desc.is_storage} { + const auto dfmt = image.GetDataFmt(); + auto nfmt = image.GetNumberFmt(); + if (is_storage && nfmt == AmdGpu::NumberFormat::Srgb) { + nfmt = AmdGpu::NumberFormat::Unorm; + } + format = Vulkan::LiverpoolToVK::SurfaceFormat(dfmt, nfmt); + if (desc.is_depth) { + format = Vulkan::LiverpoolToVK::PromoteFormatToDepth(format); + } range.base.level = image.base_level; range.base.layer = image.base_array; - range.extent.levels = image.last_level + 1; - range.extent.layers = image.last_array + 1; + range.extent.levels = image.last_level - image.base_level + 1; + range.extent.layers = image.last_array - image.base_array + 1; + type = ConvertImageViewType(image.GetType()); + + // Adjust view type for partial cubemaps and arrays + if (image.IsPartialCubemap()) { + type = vk::ImageViewType::e2DArray; + } + if (type == vk::ImageViewType::eCube) { + if (desc.is_array) { + type = vk::ImageViewType::eCubeArray; + } else { + // Some games try to bind an array of cubemaps while shader reads only single one. + range.extent.layers = std::min(range.extent.layers, 6u); + } + } + if (type == vk::ImageViewType::e3D && range.extent.layers > 1) { + // Some games pass incorrect layer count for 3D textures so we need to fixup it. + range.extent.layers = 1; + } + if (!is_storage) { mapping.r = ConvertComponentSwizzle(image.dst_sel_x); mapping.g = ConvertComponentSwizzle(image.dst_sel_y); @@ -98,7 +125,7 @@ ImageViewInfo::ImageViewInfo(const AmdGpu::Liverpool::ColorBuffer& col_buffer, const auto base_format = Vulkan::LiverpoolToVK::SurfaceFormat(col_buffer.info.format, col_buffer.NumFormat()); range.base.layer = col_buffer.view.slice_start; - range.extent.layers = col_buffer.NumSlices(); + range.extent.layers = col_buffer.NumSlices() - range.base.layer; format = Vulkan::LiverpoolToVK::AdjustColorBufferFormat( base_format, col_buffer.info.comp_swap.Value(), is_vo_surface); } @@ -110,26 +137,31 @@ ImageViewInfo::ImageViewInfo(const AmdGpu::Liverpool::DepthBuffer& depth_buffer, depth_buffer.stencil_info.format); is_storage = ctl.depth_write_enable; range.base.layer = view.slice_start; - range.extent.layers = view.NumSlices(); + range.extent.layers = view.NumSlices() - range.base.layer; } ImageView::ImageView(const Vulkan::Instance& instance, const ImageViewInfo& info_, Image& image, - ImageId image_id_, std::optional usage_override /*= {}*/) + ImageId image_id_) : image_id{image_id_}, info{info_} { - vk::ImageViewUsageCreateInfo usage_ci{}; - if (usage_override) { - usage_ci.usage = usage_override.value(); + vk::ImageViewUsageCreateInfo usage_ci{.usage = image.usage}; + if (!info.is_storage) { + usage_ci.usage &= ~vk::ImageUsageFlagBits::eStorage; } // When sampling D32 texture from shader, the T# specifies R32 Float format so adjust it. vk::Format format = info.format; vk::ImageAspectFlags aspect = image.aspect_mask; - if (image.aspect_mask & vk::ImageAspectFlagBits::eDepth && format == vk::Format::eR32Sfloat) { + if (image.aspect_mask & vk::ImageAspectFlagBits::eDepth && + (format == vk::Format::eR32Sfloat || format == vk::Format::eD32Sfloat)) { format = image.info.pixel_format; aspect = vk::ImageAspectFlagBits::eDepth; } + if (image.aspect_mask & vk::ImageAspectFlagBits::eStencil && format == vk::Format::eR8Unorm) { + format = image.info.pixel_format; + aspect = vk::ImageAspectFlagBits::eStencil; + } const vk::ImageViewCreateInfo image_view_ci = { - .pNext = usage_override ? &usage_ci : nullptr, + .pNext = &usage_ci, .image = image.image, .viewType = info.type, .format = instance.GetSupportedFormat(format), @@ -137,9 +169,9 @@ ImageView::ImageView(const Vulkan::Instance& instance, const ImageViewInfo& info .subresourceRange{ .aspectMask = aspect, .baseMipLevel = info.range.base.level, - .levelCount = info.range.extent.levels - info.range.base.level, - .baseArrayLayer = info_.range.base.layer, - .layerCount = info.range.extent.layers - info.range.base.layer, + .levelCount = info.range.extent.levels, + .baseArrayLayer = info.range.base.layer, + .layerCount = info.range.extent.layers, }, }; image_view = instance.GetDevice().createImageViewUnique(image_view_ci); diff --git a/src/video_core/texture_cache/image_view.h b/src/video_core/texture_cache/image_view.h index fbc62db36ac..ba8d2c72b24 100644 --- a/src/video_core/texture_cache/image_view.h +++ b/src/video_core/texture_cache/image_view.h @@ -3,13 +3,12 @@ #pragma once +#include "shader_recompiler/info.h" #include "video_core/amdgpu/liverpool.h" #include "video_core/amdgpu/resource.h" #include "video_core/renderer_vulkan/vk_common.h" #include "video_core/texture_cache/types.h" -#include - namespace Vulkan { class Instance; class Scheduler; @@ -19,7 +18,7 @@ namespace VideoCore { struct ImageViewInfo { ImageViewInfo() = default; - ImageViewInfo(const AmdGpu::Image& image, bool is_storage) noexcept; + ImageViewInfo(const AmdGpu::Image& image, const Shader::ImageResource& desc) noexcept; ImageViewInfo(const AmdGpu::Liverpool::ColorBuffer& col_buffer, bool is_vo_surface) noexcept; ImageViewInfo(const AmdGpu::Liverpool::DepthBuffer& depth_buffer, AmdGpu::Liverpool::DepthView view, AmdGpu::Liverpool::DepthControl ctl); @@ -28,7 +27,7 @@ struct ImageViewInfo { vk::Format format = vk::Format::eR8G8B8A8Unorm; SubresourceRange range; vk::ComponentMapping mapping{}; - bool is_storage; + bool is_storage = false; auto operator<=>(const ImageViewInfo&) const = default; }; @@ -38,8 +37,8 @@ struct Image; constexpr Common::SlotId NULL_IMAGE_VIEW_ID{0}; struct ImageView { - explicit ImageView(const Vulkan::Instance& instance, const ImageViewInfo& info, Image& image, - ImageId image_id, std::optional usage_override = {}); + ImageView(const Vulkan::Instance& instance, const ImageViewInfo& info, Image& image, + ImageId image_id); ~ImageView(); ImageView(const ImageView&) = delete; diff --git a/src/video_core/texture_cache/texture_cache.cpp b/src/video_core/texture_cache/texture_cache.cpp index 3354a8ecbfa..4813a3c5757 100644 --- a/src/video_core/texture_cache/texture_cache.cpp +++ b/src/video_core/texture_cache/texture_cache.cpp @@ -1,18 +1,21 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include #include #include "common/assert.h" #include "video_core/buffer_cache/buffer_cache.h" #include "video_core/page_manager.h" #include "video_core/renderer_vulkan/vk_instance.h" #include "video_core/renderer_vulkan/vk_scheduler.h" +#include "video_core/texture_cache/host_compatibility.h" #include "video_core/texture_cache/texture_cache.h" #include "video_core/texture_cache/tile_manager.h" namespace VideoCore { static constexpr u64 PageShift = 12; +static constexpr u64 NumFramesBeforeRemoval = 32; TextureCache::TextureCache(const Vulkan::Instance& instance_, Vulkan::Scheduler& scheduler_, BufferCache& buffer_cache_, PageManager& tracker_) @@ -26,24 +29,40 @@ TextureCache::TextureCache(const Vulkan::Instance& instance_, Vulkan::Scheduler& info.UpdateSize(); const ImageId null_id = slot_images.insert(instance, scheduler, info); ASSERT(null_id.index == 0); - slot_images[null_id].flags = ImageFlagBits{}; + const vk::Image& null_image = slot_images[null_id].image; + Vulkan::SetObjectName(instance.GetDevice(), null_image, "Null Image"); + slot_images[null_id].flags = ImageFlagBits::Tracked; ImageViewInfo view_info; - void(slot_image_views.insert(instance, view_info, slot_images[null_id], null_id)); + const auto null_view_id = + slot_image_views.insert(instance, view_info, slot_images[null_id], null_id); + ASSERT(null_view_id.index == 0); + const vk::ImageView& null_image_view = slot_image_views[null_view_id].image_view.get(); + Vulkan::SetObjectName(instance.GetDevice(), null_image_view, "Null Image View"); } TextureCache::~TextureCache() = default; void TextureCache::InvalidateMemory(VAddr address, size_t size) { - std::unique_lock lock{mutex}; + std::scoped_lock lock{mutex}; ForEachImageInRegion(address, size, [&](ImageId image_id, Image& image) { - if (!image.Overlaps(address, size)) { + // Ensure image is reuploaded when accessed again. + image.flags |= ImageFlagBits::CpuDirty; + // Untrack image, so the range is unprotected and the guest can write freely. + UntrackImage(image_id); + }); +} + +void TextureCache::InvalidateMemoryFromGPU(VAddr address, size_t max_size) { + std::scoped_lock lock{mutex}; + ForEachImageInRegion(address, max_size, [&](ImageId image_id, Image& image) { + // Only consider images that match base address. + // TODO: Maybe also consider subresources + if (image.info.guest_address != address) { return; } // Ensure image is reuploaded when accessed again. - image.flags |= ImageFlagBits::CpuModified; - // Untrack image, so the range is unprotected and the guest can write freely. - UntrackImage(image, image_id); + image.flags |= ImageFlagBits::GpuDirty; }); } @@ -53,46 +72,180 @@ void TextureCache::UnmapMemory(VAddr cpu_addr, size_t size) { boost::container::small_vector deleted_images; ForEachImageInRegion(cpu_addr, size, [&](ImageId id, Image&) { deleted_images.push_back(id); }); for (const ImageId id : deleted_images) { - Image& image = slot_images[id]; - if (True(image.flags & ImageFlagBits::Tracked)) { - UntrackImage(image, id); - } // TODO: Download image data back to host. - UnregisterImage(id); - DeleteImage(id); + FreeImage(id); } } -ImageId TextureCache::FindImage(const ImageInfo& info) { - if (info.guest_address == 0) [[unlikely]] { - return NULL_IMAGE_VIEW_ID; +ImageId TextureCache::ResolveDepthOverlap(const ImageInfo& requested_info, ImageId cache_image_id) { + const auto& cache_info = slot_images[cache_image_id].info; + + const bool was_bound_as_texture = + !cache_info.usage.depth_target && (cache_info.usage.texture || cache_info.usage.storage); + if (requested_info.usage.depth_target && was_bound_as_texture) { + auto new_image_id = slot_images.insert(instance, scheduler, requested_info); + RegisterImage(new_image_id); + + // TODO: perform a depth copy here + + FreeImage(cache_image_id); + return new_image_id; } - std::unique_lock lock{mutex}; - boost::container::small_vector image_ids; - ForEachImageInRegion( - info.guest_address, info.guest_size_bytes, [&](ImageId image_id, Image& image) { - // Address and width must match. - if (image.cpu_addr != info.guest_address || image.info.size.width != info.size.width) { - return; - } - if (info.IsDepthStencil() != image.info.IsDepthStencil() && - info.pixel_format != vk::Format::eR32Sfloat) { - return; + const bool should_bind_as_texture = + !requested_info.usage.depth_target && + (requested_info.usage.texture || requested_info.usage.storage); + if (cache_info.usage.depth_target && should_bind_as_texture) { + if (cache_info.resources == requested_info.resources) { + return cache_image_id; + } else { + UNREACHABLE(); + } + } + + return {}; +} + +ImageId TextureCache::ResolveOverlap(const ImageInfo& image_info, ImageId cache_image_id, + ImageId merged_image_id) { + auto& tex_cache_image = slot_images[cache_image_id]; + + if (image_info.guest_address == tex_cache_image.info.guest_address) { // Equal address + if (image_info.size != tex_cache_image.info.size) { + // Very likely this kind of overlap is caused by allocation from a pool. We can assume + // it is safe to delete the image if it wasn't accessed in some amount of frames. + if (scheduler.CurrentTick() - tex_cache_image.tick_accessed_last > + NumFramesBeforeRemoval) { + + FreeImage(cache_image_id); } - image_ids.push_back(image_id); - }); + return merged_image_id; + } + + if (auto depth_image_id = ResolveDepthOverlap(image_info, cache_image_id)) { + return depth_image_id; + } + + if (image_info.pixel_format != tex_cache_image.info.pixel_format || + image_info.guest_size_bytes <= tex_cache_image.info.guest_size_bytes) { + auto result_id = merged_image_id ? merged_image_id : cache_image_id; + const auto& result_image = slot_images[result_id]; + return IsVulkanFormatCompatible(image_info.pixel_format, result_image.info.pixel_format) + ? result_id + : ImageId{}; + } + + ImageId new_image_id{}; + if (image_info.type == tex_cache_image.info.type) { + new_image_id = ExpandImage(image_info, cache_image_id); + } else { + UNREACHABLE(); + } + return new_image_id; + } + + // Right overlap, the image requested is a possible subresource of the image from cache. + if (image_info.guest_address > tex_cache_image.info.guest_address) { + // Should be handled by view. No additional actions needed. + } else { + // Left overlap, the image from cache is a possible subresource of the image requested + if (!merged_image_id) { + // We need to have a larger, already allocated image to copy this one into + return {}; + } + + if (tex_cache_image.info.IsMipOf(image_info)) { + tex_cache_image.Transit(vk::ImageLayout::eTransferSrcOptimal, + vk::AccessFlagBits2::eTransferRead, {}); + + const auto num_mips_to_copy = tex_cache_image.info.resources.levels; + ASSERT(num_mips_to_copy == 1); + + auto& merged_image = slot_images[merged_image_id]; + merged_image.CopyMip(tex_cache_image, image_info.resources.levels - 1); + + FreeImage(cache_image_id); + } + } + + return merged_image_id; +} - // ASSERT_MSG(image_ids.size() <= 1, "Overlapping images not allowed!"); +ImageId TextureCache::ExpandImage(const ImageInfo& info, ImageId image_id) { + const auto new_image_id = slot_images.insert(instance, scheduler, info); + RegisterImage(new_image_id); + + auto& src_image = slot_images[image_id]; + auto& new_image = slot_images[new_image_id]; + + src_image.Transit(vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits2::eTransferRead, {}); + new_image.CopyImage(src_image); + + if (True(src_image.flags & ImageFlagBits::Bound)) { + src_image.flags |= ImageFlagBits::NeedsRebind; + } + + FreeImage(image_id); + + TrackImage(new_image_id); + new_image.flags &= ~ImageFlagBits::Dirty; + return new_image_id; +} + +ImageId TextureCache::FindImage(const ImageInfo& info, FindFlags flags) { + if (info.guest_address == 0) [[unlikely]] { + return NULL_IMAGE_VIEW_ID; + } + + std::scoped_lock lock{mutex}; + boost::container::small_vector image_ids; + ForEachImageInRegion(info.guest_address, info.guest_size_bytes, + [&](ImageId image_id, Image& image) { image_ids.push_back(image_id); }); ImageId image_id{}; - if (image_ids.empty()) { + + // Check for a perfect match first + for (const auto& cache_id : image_ids) { + auto& cache_image = slot_images[cache_id]; + if (cache_image.info.guest_address != info.guest_address) { + continue; + } + if (False(flags & FindFlags::RelaxSize) && + cache_image.info.guest_size_bytes != info.guest_size_bytes) { + continue; + } + if (False(flags & FindFlags::RelaxDim) && cache_image.info.size != info.size) { + continue; + } + if (False(flags & FindFlags::RelaxFmt) && + !IsVulkanFormatCompatible(info.pixel_format, cache_image.info.pixel_format)) { + continue; + } + ASSERT(cache_image.info.type == info.type || True(flags & FindFlags::RelaxFmt)); + image_id = cache_id; + } + + if (True(flags & FindFlags::NoCreate) && !image_id) { + return {}; + } + + // Try to resolve overlaps (if any) + if (!image_id) { + for (const auto& cache_id : image_ids) { + const auto& merged_info = image_id ? slot_images[image_id].info : info; + image_id = ResolveOverlap(merged_info, cache_id, image_id); + } + } + + // Create and register a new image + if (!image_id) { image_id = slot_images.insert(instance, scheduler, info); RegisterImage(image_id); - } else { - image_id = image_ids[image_ids.size() > 1 ? 1 : 0]; } + Image& image = slot_images[image_id]; + image.tick_accessed_last = scheduler.CurrentTick(); + return image_id; } @@ -102,64 +255,31 @@ ImageView& TextureCache::RegisterImageView(ImageId image_id, const ImageViewInfo return slot_image_views[view_id]; } - // All tiled images are created with storage usage flag. This makes set of formats (e.g. sRGB) - // impossible to use. However, during view creation, if an image isn't used as storage we can - // temporary remove its storage bit. - std::optional usage_override; - if (!image.info.usage.storage) { - usage_override = image.usage & ~vk::ImageUsageFlagBits::eStorage; - } - - const ImageViewId view_id = - slot_image_views.insert(instance, view_info, image, image_id, usage_override); + const ImageViewId view_id = slot_image_views.insert(instance, view_info, image, image_id); image.image_view_infos.emplace_back(view_info); image.image_view_ids.emplace_back(view_id); return slot_image_views[view_id]; } -ImageView& TextureCache::FindTexture(const ImageInfo& info, const ImageViewInfo& view_info) { - const ImageId image_id = FindImage(info); - UpdateImage(image_id); +ImageView& TextureCache::FindTexture(ImageId image_id, const ImageViewInfo& view_info) { Image& image = slot_images[image_id]; + UpdateImage(image_id); auto& usage = image.info.usage; if (view_info.is_storage) { image.Transit(vk::ImageLayout::eGeneral, - vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite); + vk::AccessFlagBits2::eShaderRead | vk::AccessFlagBits2::eShaderWrite, + view_info.range); usage.storage = true; } else { const auto new_layout = image.info.IsDepthStencil() ? vk::ImageLayout::eDepthStencilReadOnlyOptimal : vk::ImageLayout::eShaderReadOnlyOptimal; - image.Transit(new_layout, vk::AccessFlagBits::eShaderRead); + image.Transit(new_layout, vk::AccessFlagBits2::eShaderRead, view_info.range); usage.texture = true; } - // These changes are temporary and should be removed once texture cache will handle subresources - // merging - auto view_info_tmp = view_info; - if (view_info_tmp.range.base.level > image.info.resources.levels - 1 || - view_info_tmp.range.base.layer > image.info.resources.layers - 1 || - view_info_tmp.range.extent.levels > image.info.resources.levels || - view_info_tmp.range.extent.layers > image.info.resources.layers) { - - LOG_DEBUG(Render_Vulkan, - "Subresource range ({}~{},{}~{}) exceeds base image extents ({},{})", - view_info_tmp.range.base.level, view_info_tmp.range.extent.levels, - view_info_tmp.range.base.layer, view_info_tmp.range.extent.layers, - image.info.resources.levels, image.info.resources.layers); - - view_info_tmp.range.base.level = - std::min(view_info_tmp.range.base.level, image.info.resources.levels - 1); - view_info_tmp.range.base.layer = - std::min(view_info_tmp.range.base.layer, image.info.resources.layers - 1); - view_info_tmp.range.extent.levels = - std::min(view_info_tmp.range.extent.levels, image.info.resources.levels); - view_info_tmp.range.extent.layers = - std::min(view_info_tmp.range.extent.layers, image.info.resources.layers); - } - - return RegisterImageView(image_id, view_info_tmp); + return RegisterImageView(image_id, view_info); } ImageView& TextureCache::FindRenderTarget(const ImageInfo& image_info, @@ -170,8 +290,9 @@ ImageView& TextureCache::FindRenderTarget(const ImageInfo& image_info, UpdateImage(image_id); image.Transit(vk::ImageLayout::eColorAttachmentOptimal, - vk::AccessFlagBits::eColorAttachmentWrite | - vk::AccessFlagBits::eColorAttachmentRead); + vk::AccessFlagBits2::eColorAttachmentWrite | + vk::AccessFlagBits2::eColorAttachmentRead, + view_info.range); // Register meta data for this color buffer if (!(image.flags & ImageFlagBits::MetaRegistered)) { @@ -203,13 +324,23 @@ ImageView& TextureCache::FindDepthTarget(const ImageInfo& image_info, const ImageId image_id = FindImage(image_info); Image& image = slot_images[image_id]; image.flags |= ImageFlagBits::GpuModified; - image.flags &= ~ImageFlagBits::CpuModified; - image.aspect_mask = vk::ImageAspectFlagBits::eDepth | vk::ImageAspectFlagBits::eStencil; + image.flags &= ~ImageFlagBits::Dirty; + image.aspect_mask = vk::ImageAspectFlagBits::eDepth; - const auto new_layout = view_info.is_storage ? vk::ImageLayout::eDepthStencilAttachmentOptimal - : vk::ImageLayout::eDepthStencilReadOnlyOptimal; - image.Transit(new_layout, vk::AccessFlagBits::eDepthStencilAttachmentWrite | - vk::AccessFlagBits::eDepthStencilAttachmentRead); + const bool has_stencil = image_info.usage.stencil; + if (has_stencil) { + image.aspect_mask |= vk::ImageAspectFlagBits::eStencil; + } + + const auto new_layout = view_info.is_storage + ? has_stencil ? vk::ImageLayout::eDepthStencilAttachmentOptimal + : vk::ImageLayout::eDepthAttachmentOptimal + : has_stencil ? vk::ImageLayout::eDepthStencilReadOnlyOptimal + : vk::ImageLayout::eDepthReadOnlyOptimal; + image.Transit(new_layout, + vk::AccessFlagBits2::eDepthStencilAttachmentWrite | + vk::AccessFlagBits2::eDepthStencilAttachmentRead, + view_info.range); // Register meta data for this depth buffer if (!(image.flags & ImageFlagBits::MetaRegistered)) { @@ -224,13 +355,15 @@ ImageView& TextureCache::FindDepthTarget(const ImageInfo& image_info, // Update tracked image usage image.info.usage.depth_target = true; + image.info.usage.stencil = has_stencil; return RegisterImageView(image_id, view_info); } void TextureCache::RefreshImage(Image& image, Vulkan::Scheduler* custom_scheduler /*= nullptr*/) { - // Mark image as validated. - image.flags &= ~ImageFlagBits::CpuModified; + if (False(image.flags & ImageFlagBits::Dirty)) { + return; + } const auto& num_layers = image.info.resources.layers; const auto& num_mips = image.info.resources.levels; @@ -244,9 +377,10 @@ void TextureCache::RefreshImage(Image& image, Vulkan::Scheduler* custom_schedule image.info.props.is_volume ? std::max(image.info.size.depth >> m, 1u) : 1u; const auto& [mip_size, mip_pitch, mip_height, mip_ofs] = image.info.mips_layout[m]; - // Protect GPU modified resources from accidental reuploads. - if (True(image.flags & ImageFlagBits::GpuModified) && - !buffer_cache.IsRegionGpuModified(image.info.guest_address + mip_ofs, mip_size)) { + // Protect GPU modified resources from accidental CPU reuploads. + const bool is_gpu_modified = True(image.flags & ImageFlagBits::GpuModified); + const bool is_gpu_dirty = True(image.flags & ImageFlagBits::GpuDirty); + if (is_gpu_modified && !is_gpu_dirty) { const u8* addr = std::bit_cast(image.info.guest_address); const u64 hash = XXH3_64bits(addr + mip_ofs, mip_size); if (image.mip_hashes[m] == hash) { @@ -260,7 +394,7 @@ void TextureCache::RefreshImage(Image& image, Vulkan::Scheduler* custom_schedule .bufferRowLength = static_cast(mip_pitch), .bufferImageHeight = static_cast(mip_height), .imageSubresource{ - .aspectMask = vk::ImageAspectFlagBits::eColor, + .aspectMask = image.aspect_mask & ~vk::ImageAspectFlagBits::eStencil, .mipLevel = m, .baseArrayLayer = 0, .layerCount = num_layers, @@ -278,25 +412,31 @@ void TextureCache::RefreshImage(Image& image, Vulkan::Scheduler* custom_schedule sched_ptr->EndRendering(); const auto cmdbuf = sched_ptr->CommandBuffer(); - image.Transit(vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits::eTransferWrite, cmdbuf); + image.Transit(vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits2::eTransferWrite, {}, + cmdbuf); const VAddr image_addr = image.info.guest_address; const size_t image_size = image.info.guest_size_bytes; - vk::Buffer buffer{}; - u32 offset{}; - if (auto upload_buffer = tile_manager.TryDetile(image); upload_buffer) { - buffer = *upload_buffer; - } else { - const auto [vk_buffer, buf_offset] = buffer_cache.ObtainTempBuffer(image_addr, image_size); - buffer = vk_buffer->Handle(); - offset = buf_offset; + const auto [vk_buffer, buf_offset] = buffer_cache.ObtainTempBuffer(image_addr, image_size); + // The obtained buffer may be written by a shader so we need to emit a barrier to prevent RAW + // hazard + if (auto barrier = vk_buffer->GetBarrier(vk::AccessFlagBits2::eTransferRead, + vk::PipelineStageFlagBits2::eTransfer)) { + const auto dependencies = vk::DependencyInfo{ + .dependencyFlags = vk::DependencyFlagBits::eByRegion, + .bufferMemoryBarrierCount = 1, + .pBufferMemoryBarriers = &barrier.value(), + }; + cmdbuf.pipelineBarrier2(dependencies); } + const auto [buffer, offset] = tile_manager.TryDetile(vk_buffer->Handle(), buf_offset, image); for (auto& copy : image_copy) { copy.bufferOffset += offset; } cmdbuf.copyBufferToImage(buffer, image.image, vk::ImageLayout::eTransferDstOptimal, image_copy); + image.flags &= ~ImageFlagBits::Dirty; } vk::Sampler TextureCache::GetSampler(const AmdGpu::Sampler& sampler) { @@ -335,7 +475,8 @@ void TextureCache::UnregisterImage(ImageId image_id) { }); } -void TextureCache::TrackImage(Image& image, ImageId image_id) { +void TextureCache::TrackImage(ImageId image_id) { + auto& image = slot_images[image_id]; if (True(image.flags & ImageFlagBits::Tracked)) { return; } @@ -343,7 +484,8 @@ void TextureCache::TrackImage(Image& image, ImageId image_id) { tracker.UpdatePagesCachedCount(image.cpu_addr, image.info.guest_size_bytes, 1); } -void TextureCache::UntrackImage(Image& image, ImageId image_id) { +void TextureCache::UntrackImage(ImageId image_id) { + auto& image = slot_images[image_id]; if (False(image.flags & ImageFlagBits::Tracked)) { return; } diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h index 31b1e39398e..3bbfd952c71 100644 --- a/src/video_core/texture_cache/texture_cache.h +++ b/src/video_core/texture_cache/texture_cache.h @@ -23,6 +23,16 @@ namespace VideoCore { class BufferCache; class PageManager; +enum class FindFlags { + NoCreate = 1 << 0, ///< Do not create an image if searching for one fails. + RelaxDim = 1 << 1, ///< Do not check the dimentions of image, only address. + RelaxSize = 1 << 2, ///< Do not check that the size matches exactly. + RelaxFmt = 1 << 3, ///< Do not check that format is compatible. +}; +DECLARE_ENUM_FLAG_OPERATORS(FindFlags) + +static constexpr u32 MaxInvalidateDist = 12_MB; + class TextureCache { struct Traits { using Entry = boost::container::small_vector; @@ -40,15 +50,17 @@ class TextureCache { /// Invalidates any image in the logical page range. void InvalidateMemory(VAddr address, size_t size); + /// Marks an image as dirty if it exists at the provided address. + void InvalidateMemoryFromGPU(VAddr address, size_t max_size); + /// Evicts any images that overlap the unmapped range. void UnmapMemory(VAddr cpu_addr, size_t size); /// Retrieves the image handle of the image with the provided attributes. - [[nodiscard]] ImageId FindImage(const ImageInfo& info); + [[nodiscard]] ImageId FindImage(const ImageInfo& info, FindFlags flags = {}); - /// Retrieves an image view with the properties of the specified image descriptor. - [[nodiscard]] ImageView& FindTexture(const ImageInfo& image_info, - const ImageViewInfo& view_info); + /// Retrieves an image view with the properties of the specified image id. + [[nodiscard]] ImageView& FindTexture(ImageId image_id, const ImageViewInfo& view_info); /// Retrieves the render target with specified properties [[nodiscard]] ImageView& FindRenderTarget(const ImageInfo& image_info, @@ -61,13 +73,19 @@ class TextureCache { /// Updates image contents if it was modified by CPU. void UpdateImage(ImageId image_id, Vulkan::Scheduler* custom_scheduler = nullptr) { Image& image = slot_images[image_id]; - if (False(image.flags & ImageFlagBits::CpuModified)) { - return; - } + TrackImage(image_id); RefreshImage(image, custom_scheduler); - TrackImage(image, image_id); } + [[nodiscard]] ImageId ResolveOverlap(const ImageInfo& info, ImageId cache_img_id, + ImageId merged_image_id); + + /// Resolves depth overlap and either re-creates the image or returns existing one + [[nodiscard]] ImageId ResolveDepthOverlap(const ImageInfo& requested_info, + ImageId cache_img_id); + + [[nodiscard]] ImageId ExpandImage(const ImageInfo& info, ImageId image_id); + /// Reuploads image contents. void RefreshImage(Image& image, Vulkan::Scheduler* custom_scheduler = nullptr); @@ -79,6 +97,11 @@ class TextureCache { return slot_images[id]; } + /// Retrieves the image view with the specified id. + [[nodiscard]] ImageView& GetImageView(ImageId id) { + return slot_image_views[id]; + } + bool IsMeta(VAddr address) const { return surface_metas.contains(address); } @@ -100,31 +123,12 @@ class TextureCache { return false; } -private: - ImageView& RegisterImageView(ImageId image_id, const ImageViewInfo& view_info); - - /// Iterate over all page indices in a range - template - static void ForEachPage(PAddr addr, size_t size, Func&& func) { - static constexpr bool RETURNS_BOOL = std::is_same_v, bool>; - const u64 page_end = (addr + size - 1) >> Traits::PageBits; - for (u64 page = addr >> Traits::PageBits; page <= page_end; ++page) { - if constexpr (RETURNS_BOOL) { - if (func(page)) { - break; - } - } else { - func(page); - } - } - } - template void ForEachImageInRegion(VAddr cpu_addr, size_t size, Func&& func) { using FuncReturn = typename std::invoke_result::type; static constexpr bool BOOL_BREAK = std::is_same_v; boost::container::small_vector images; - ForEachPage(cpu_addr, size, [this, &images, func](u64 page) { + ForEachPage(cpu_addr, size, [this, &images, cpu_addr, size, func](u64 page) { const auto it = page_table.find(page); if (it == nullptr) { if constexpr (BOOL_BREAK) { @@ -138,6 +142,9 @@ class TextureCache { if (image.flags & ImageFlagBits::Picked) { continue; } + if (!image.Overlaps(cpu_addr, size)) { + continue; + } image.flags |= ImageFlagBits::Picked; images.push_back(image_id); if constexpr (BOOL_BREAK) { @@ -157,6 +164,26 @@ class TextureCache { } } +private: + /// Iterate over all page indices in a range + template + static void ForEachPage(PAddr addr, size_t size, Func&& func) { + static constexpr bool RETURNS_BOOL = std::is_same_v, bool>; + const u64 page_end = (addr + size - 1) >> Traits::PageBits; + for (u64 page = addr >> Traits::PageBits; page <= page_end; ++page) { + if constexpr (RETURNS_BOOL) { + if (func(page)) { + break; + } + } else { + func(page); + } + } + } + + /// Registers an image view for provided image + ImageView& RegisterImageView(ImageId image_id, const ImageViewInfo& view_info); + /// Create an image from the given parameters [[nodiscard]] ImageId InsertImage(const ImageInfo& info, VAddr cpu_addr); @@ -167,14 +194,20 @@ class TextureCache { void UnregisterImage(ImageId image); /// Track CPU reads and writes for image - void TrackImage(Image& image, ImageId image_id); + void TrackImage(ImageId image_id); /// Stop tracking CPU reads and writes for image - void UntrackImage(Image& image, ImageId image_id); + void UntrackImage(ImageId image_id); /// Removes the image and any views/surface metas that reference it. void DeleteImage(ImageId image_id); + void FreeImage(ImageId image_id) { + UntrackImage(image_id); + UnregisterImage(image_id); + DeleteImage(image_id); + } + private: const Vulkan::Instance& instance; Vulkan::Scheduler& scheduler; diff --git a/src/video_core/texture_cache/tile_manager.cpp b/src/video_core/texture_cache/tile_manager.cpp index 8b022762471..7e06291e73d 100644 --- a/src/video_core/texture_cache/tile_manager.cpp +++ b/src/video_core/texture_cache/tile_manager.cpp @@ -15,7 +15,10 @@ #include #include +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnullability-completeness" #include +#pragma GCC diagnostic pop namespace VideoCore { @@ -251,11 +254,8 @@ struct DetilerParams { u32 sizes[14]; }; -static constexpr size_t StreamBufferSize = 1_GB; - TileManager::TileManager(const Vulkan::Instance& instance, Vulkan::Scheduler& scheduler) - : instance{instance}, scheduler{scheduler}, - stream_buffer{instance, scheduler, MemoryUsage::Upload, StreamBufferSize} { + : instance{instance}, scheduler{scheduler} { static const std::array detiler_shaders{ HostShaders::DETILE_M8X1_COMP, HostShaders::DETILE_M8X2_COMP, HostShaders::DETILE_M32X1_COMP, HostShaders::DETILE_M32X2_COMP, @@ -377,35 +377,23 @@ void TileManager::FreeBuffer(ScratchBuffer buffer) { vmaDestroyBuffer(instance.GetAllocator(), buffer.first, buffer.second); } -std::optional TileManager::TryDetile(Image& image) { +std::pair TileManager::TryDetile(vk::Buffer in_buffer, u32 in_offset, + Image& image) { if (!image.info.props.is_tiled) { - return std::nullopt; + return {in_buffer, in_offset}; } const auto* detiler = GetDetiler(image); if (!detiler) { - if (image.info.tiling_mode != AmdGpu::TilingMode::Texture_MacroTiled) { + if (image.info.tiling_mode != AmdGpu::TilingMode::Texture_MacroTiled && + image.info.tiling_mode != AmdGpu::TilingMode::Display_MacroTiled) { LOG_ERROR(Render_Vulkan, "Unsupported tiled image: {} ({})", vk::to_string(image.info.pixel_format), NameOf(image.info.tiling_mode)); } - return std::nullopt; + return {in_buffer, in_offset}; } - // Prepare input buffer const u32 image_size = image.info.guest_size_bytes; - const auto [in_buffer, in_offset] = [&] -> std::pair { - // Use stream buffer for smaller textures. - if (image_size <= stream_buffer.GetFreeSize()) { - u32 offset = stream_buffer.Copy(image.info.guest_address, image_size); - return {stream_buffer.Handle(), offset}; - } - // Request temporary host buffer for larger sizes. - auto in_buffer = AllocBuffer(image_size); - const auto addr = reinterpret_cast(image.info.guest_address); - Upload(in_buffer, addr, image_size); - scheduler.DeferOperation([=, this]() { FreeBuffer(in_buffer); }); - return {in_buffer.first, 0}; - }(); // Prepare output buffer auto out_buffer = AllocBuffer(image_size, true); @@ -476,7 +464,7 @@ std::optional TileManager::TryDetile(Image& image) { vk::PipelineStageFlagBits::eTransfer, vk::DependencyFlagBits::eByRegion, {}, post_barrier, {}); - return {out_buffer.first}; + return {out_buffer.first, 0}; } } // namespace VideoCore diff --git a/src/video_core/texture_cache/tile_manager.h b/src/video_core/texture_cache/tile_manager.h index 00765b1f84a..d0e5eb0f37f 100644 --- a/src/video_core/texture_cache/tile_manager.h +++ b/src/video_core/texture_cache/tile_manager.h @@ -39,7 +39,7 @@ class TileManager { TileManager(const Vulkan::Instance& instance, Vulkan::Scheduler& scheduler); ~TileManager(); - std::optional TryDetile(Image& image); + std::pair TryDetile(vk::Buffer in_buffer, u32 in_offset, Image& image); ScratchBuffer AllocBuffer(u32 size, bool is_storage = false); void Upload(ScratchBuffer buffer, const void* data, size_t size); @@ -51,7 +51,6 @@ class TileManager { private: const Vulkan::Instance& instance; Vulkan::Scheduler& scheduler; - StreamBuffer stream_buffer; std::array detilers; }; diff --git a/src/video_core/texture_cache/types.h b/src/video_core/texture_cache/types.h index 45ffe251176..bcef193556d 100644 --- a/src/video_core/texture_cache/types.h +++ b/src/video_core/texture_cache/types.h @@ -36,6 +36,8 @@ struct Extent3D { u32 width; u32 height; u32 depth; + + auto operator<=>(const Extent3D&) const = default; }; struct SubresourceLayers {