mirror of
https://git.ryujinx.app/ryubing/ryujinx.git
synced 2026-01-11 20:10:38 +00:00
Compare commits
29 commits
Canary-1.3
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
99feaafbe6 | ||
|
|
fa55608587 | ||
|
|
4c64300576 | ||
|
|
0a3db19b28 | ||
|
|
453b246faa | ||
|
|
45193dcc8d | ||
|
|
9ebf444644 | ||
|
|
f585b36263 | ||
|
|
a96f20dca5 | ||
|
|
1e1bcb4a5b | ||
|
|
ca76bacd22 | ||
|
|
66f339d265 | ||
|
|
6cdbdfd329 | ||
|
|
9f817d60d5 | ||
|
|
5cffc95be6 | ||
|
|
2c0977f6b3 | ||
|
|
3a593b6084 | ||
|
|
c3155fcadb | ||
|
|
fd7554425a | ||
|
|
52700f71dc | ||
|
|
b018a44ff0 | ||
|
|
d522bfef62 | ||
|
|
39f55b2af3 | ||
|
|
6126e3dc1e | ||
|
|
e1c829f91d | ||
|
|
6c7dc7646b | ||
|
|
862a686c5e | ||
|
|
e8751e1c40 | ||
|
|
09748b140a |
142 changed files with 3306 additions and 1778 deletions
99
.github/workflows/canary.yml
vendored
99
.github/workflows/canary.yml
vendored
|
|
@ -1,4 +1,4 @@
|
|||
name: Canary release job
|
||||
name: Canary CI
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
|
@ -19,7 +19,6 @@ concurrency: release
|
|||
env:
|
||||
POWERSHELL_TELEMETRY_OPTOUT: 1
|
||||
DOTNET_CLI_TELEMETRY_OPTOUT: 1
|
||||
RYUJINX_BASE_VERSION: "1.3"
|
||||
RYUJINX_TARGET_RELEASE_CHANNEL_NAME: "canary"
|
||||
RELEASE: 1
|
||||
|
||||
|
|
@ -30,8 +29,8 @@ jobs:
|
|||
strategy:
|
||||
matrix:
|
||||
platform:
|
||||
- { name: win-x64, os: windows-latest, zip_os_name: win_x64 }
|
||||
#- { name: win-arm64, os: windows-latest, zip_os_name: win_arm64 }
|
||||
- { name: win-x64, os: ubuntu-latest, zip_os_name: win_x64 }
|
||||
#- { name: win-arm64, os: ubuntu-latest, zip_os_name: win_arm64 }
|
||||
- { name: linux-x64, os: ubuntu-latest, zip_os_name: linux_x64 }
|
||||
- { name: linux-arm64, os: ubuntu-latest, zip_os_name: linux_arm64 }
|
||||
steps:
|
||||
|
|
@ -44,11 +43,25 @@ jobs:
|
|||
- name: Overwrite csc problem matcher
|
||||
run: echo "::add-matcher::.github/csc.json"
|
||||
|
||||
- name: Install 7zip
|
||||
run: |
|
||||
sudo apt install -y 7zip
|
||||
|
||||
- name: Install gli
|
||||
run: |
|
||||
mkdir -p $HOME/.bin
|
||||
gh release download -R GreemDev/GLI -O gli -p 'gli-linux-x64'
|
||||
chmod +x gli
|
||||
mv gli $HOME/.bin/
|
||||
echo "$HOME/.bin" >> $GITHUB_PATH
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Get version info
|
||||
id: version_info
|
||||
run: |
|
||||
echo "build_version=${{ env.RYUJINX_BASE_VERSION }}.${{ github.run_number }}" >> $GITHUB_OUTPUT
|
||||
echo "prev_build_version=${{ env.RYUJINX_BASE_VERSION }}.$((${{ github.run_number }} - 1))" >> $GITHUB_OUTPUT
|
||||
echo "build_version=$(gli get-next-version -c Canary -R)" >> $GITHUB_OUTPUT
|
||||
echo "prev_build_version=$(gli get-current-version -c Canary -R)" >> $GITHUB_OUTPUT
|
||||
echo "git_short_hash=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
|
||||
shell: bash
|
||||
|
||||
|
|
@ -69,33 +82,20 @@ jobs:
|
|||
dotnet publish -c Release -r "${{ matrix.platform.name }}" -o ./publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx --self-contained
|
||||
|
||||
- name: Packing Windows builds
|
||||
if: matrix.platform.os == 'windows-latest'
|
||||
if: contains(matrix.platform.name, 'win')
|
||||
run: |
|
||||
pushd publish
|
||||
rm libarmeilleure-jitsupport.dylib
|
||||
7z a ../release_output/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip ../publish
|
||||
popd
|
||||
|
||||
gh release download -R GreemDev/GLI -O gli.exe -p 'GitLabCli-win_x64.exe'
|
||||
|
||||
./gli.exe --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/canary --command=UploadGenericPackage "Ryubing-Canary|${{ steps.version_info.outputs.build_version }}|release_output/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip"
|
||||
|
||||
gli upload-generic-package -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/canary -n Ryubing-Canary -v ${{ steps.version_info.outputs.build_version }} -r 5 -p release_output/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip
|
||||
shell: bash
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Install GitLabCli
|
||||
if: matrix.platform.os == 'ubuntu-latest'
|
||||
run: |
|
||||
mkdir -p $HOME/.bin
|
||||
gh release download -R GreemDev/GLI -O gli -p 'GitLabCli-linux_x64'
|
||||
chmod +x gli
|
||||
mv gli $HOME/.bin/
|
||||
echo "$HOME/.bin" >> $GITHUB_PATH
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Packing Linux builds
|
||||
if: matrix.platform.os == 'ubuntu-latest'
|
||||
if: contains(matrix.platform.name, 'linux')
|
||||
run: |
|
||||
pushd publish
|
||||
rm libarmeilleure-jitsupport.dylib
|
||||
|
|
@ -103,11 +103,11 @@ jobs:
|
|||
tar -czvf ../release_output/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz ../publish
|
||||
popd
|
||||
|
||||
gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/canary --command=UploadGenericPackage "Ryubing-Canary|${{ steps.version_info.outputs.build_version }}|release_output/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz"
|
||||
gli upload-generic-package -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/canary -n Ryubing-Canary -v ${{ steps.version_info.outputs.build_version }} -r 5 -p release_output/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz
|
||||
shell: bash
|
||||
|
||||
- name: Build AppImage (Linux)
|
||||
if: matrix.platform.os == 'ubuntu-latest'
|
||||
if: contains(matrix.platform.name, 'linux')
|
||||
run: |
|
||||
BUILD_VERSION="${{ steps.version_info.outputs.build_version }}"
|
||||
PLATFORM_NAME="${{ matrix.platform.name }}"
|
||||
|
|
@ -139,8 +139,8 @@ jobs:
|
|||
pushd publish_appimage
|
||||
mv Ryujinx.AppImage ../release_output/ryujinx-canary-$BUILD_VERSION-$ARCH_NAME.AppImage
|
||||
popd
|
||||
|
||||
gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/canary --command=UploadGenericPackage "Ryubing-Canary|${{ steps.version_info.outputs.build_version }}|release_output/ryujinx-canary-$BUILD_VERSION-$ARCH_NAME.AppImage"
|
||||
|
||||
gli upload-generic-package -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/canary -n Ryubing-Canary -v ${{ steps.version_info.outputs.build_version }} -r 5 -p release_output/ryujinx-canary-$BUILD_VERSION-$ARCH_NAME.AppImage
|
||||
shell: bash
|
||||
|
||||
macos_release:
|
||||
|
|
@ -159,10 +159,10 @@ jobs:
|
|||
chmod +x llvm.sh
|
||||
sudo ./llvm.sh 17
|
||||
|
||||
- name: Install GitLabCli
|
||||
- name: Install gli
|
||||
run: |
|
||||
mkdir -p $HOME/.bin
|
||||
gh release download -R GreemDev/GLI -O gli -p 'GitLabCli-linux_x64'
|
||||
gh release download -R GreemDev/GLI -O gli -p 'gli-linux-x64'
|
||||
chmod +x gli
|
||||
mv gli $HOME/.bin/
|
||||
echo "$HOME/.bin" >> $GITHUB_PATH
|
||||
|
|
@ -183,9 +183,10 @@ jobs:
|
|||
- name: Get version info
|
||||
id: version_info
|
||||
run: |
|
||||
echo "build_version=${{ env.RYUJINX_BASE_VERSION }}.${{ github.run_number }}" >> $GITHUB_OUTPUT
|
||||
echo "prev_build_version=${{ env.RYUJINX_BASE_VERSION }}.$((${{ github.run_number }} - 1))" >> $GITHUB_OUTPUT
|
||||
echo "build_version=$(gli get-next-version -c Canary -R)" >> $GITHUB_OUTPUT
|
||||
echo "prev_build_version=$(gli get-current-version -c Canary -R)" >> $GITHUB_OUTPUT
|
||||
echo "git_short_hash=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
|
||||
shell: bash
|
||||
|
||||
- name: Configure for release
|
||||
run: |
|
||||
|
|
@ -200,7 +201,7 @@ jobs:
|
|||
- name: Publish macOS Ryujinx
|
||||
run: |
|
||||
./distribution/macos/create_macos_build_ava.sh . publish_tmp_ava publish_ava ./distribution/macos/entitlements.xml "${{ steps.version_info.outputs.build_version }}" "${{ steps.version_info.outputs.git_short_hash }}" Release 1
|
||||
gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/canary --command=UploadGenericPackage "Ryubing-Canary|${{ steps.version_info.outputs.build_version }}|publish_ava/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-macos_universal.app.tar.gz"
|
||||
gli upload-generic-package -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/canary -n Ryubing-Canary -v ${{ steps.version_info.outputs.build_version }} -r 5 -p publish_ava/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-macos_universal.app.tar.gz
|
||||
|
||||
create_gitlab_release:
|
||||
name: Create GitLab Release
|
||||
|
|
@ -210,37 +211,41 @@ jobs:
|
|||
- release
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Get version info
|
||||
id: version_info
|
||||
run: |
|
||||
echo "build_version=${{ env.RYUJINX_BASE_VERSION }}.${{ github.run_number }}" >> $GITHUB_OUTPUT
|
||||
echo "prev_build_version=${{ env.RYUJINX_BASE_VERSION }}.$((${{ github.run_number }} - 1))" >> $GITHUB_OUTPUT
|
||||
echo "git_short_hash=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
|
||||
shell: bash
|
||||
|
||||
- name: Install GitLabCli
|
||||
|
||||
- name: Install gli
|
||||
run: |
|
||||
mkdir -p $HOME/.bin
|
||||
gh release download -R GreemDev/GLI -O gli -p 'GitLabCli-linux_x64'
|
||||
gh release download -R GreemDev/GLI -O gli -p 'gli-linux-x64'
|
||||
chmod +x gli
|
||||
mv gli $HOME/.bin/
|
||||
echo "$HOME/.bin" >> $GITHUB_PATH
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Get version info
|
||||
id: version_info
|
||||
run: |
|
||||
echo "build_version=$(gli get-next-version -c Canary -R)" >> $GITHUB_OUTPUT
|
||||
echo "prev_build_version=$(gli get-current-version -c Canary -R)" >> $GITHUB_OUTPUT
|
||||
echo "git_short_hash=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
|
||||
shell: bash
|
||||
|
||||
- name: Create tag
|
||||
run: |
|
||||
gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=CreateTag "Canary-${{ steps.version_info.outputs.build_version }}|${{ steps.version_info.outputs.git_short_hash }}"
|
||||
gli create-tag -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/ryujinx -n Canary-${{ steps.version_info.outputs.build_version }} -r ${{ steps.version_info.outputs.git_short_hash }}
|
||||
|
||||
- name: Create release
|
||||
run: |
|
||||
gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/canary --command=CreateReleaseFromGenericPackageFiles "Ryubing-Canary|${{ steps.version_info.outputs.build_version }}|main|Canary ${{ steps.version_info.outputs.build_version }}|**Full Changelog:** [${{ steps.version_info.outputs.prev_build_version }}...${{ steps.version_info.outputs.build_version }}](https://git.ryujinx.app/ryubing/ryujinx/-/compare/Canary-${{ steps.version_info.outputs.prev_build_version }}...Canary-${{ steps.version_info.outputs.build_version }})"
|
||||
gli create-release-from-generic-package-files -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/canary -n Ryubing-Canary -v ${{ steps.version_info.outputs.build_version }} -r main -t "Canary ${{ steps.version_info.outputs.build_version }}" -b "**Full Changelog:** [${{ steps.version_info.outputs.prev_build_version }}...${{ steps.version_info.outputs.build_version }}](https://git.ryujinx.app/ryubing/ryujinx/-/compare/Canary-${{ steps.version_info.outputs.prev_build_version }}...Canary-${{ steps.version_info.outputs.build_version }})"
|
||||
|
||||
- name: Send notification webhook
|
||||
run: |
|
||||
gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/canary --command=SendUpdateMessage "${{ steps.version_info.outputs.build_version }}|FF4500|${{ secrets.CANARY_DISCORD_WEBHOOK }}|https://avatars.githubusercontent.com/u/192939710?s=200&v=4|false"
|
||||
gli send-update-message -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/canary -t ${{ steps.version_info.outputs.build_version }} -c FF4500 -w ${{ secrets.CANARY_DISCORD_WEBHOOK }} -i https://avatars.githubusercontent.com/u/192939710?s=200&v=4
|
||||
|
||||
- name: Notify update server of new builds
|
||||
run: |
|
||||
curl 'https://update.ryujinx.app/api/v1/admin/refresh_cache?rc=canary' -X PATCH -H 'accept: */*' -H 'Authorization: ${{ secrets.UPDATE_SERVER_ADMIN_TOKEN }}'
|
||||
gli refresh-version-cache -T ${{ secrets.UPDATE_SERVER_ADMIN_TOKEN }} -c Canary
|
||||
|
||||
- name: Advance to the next version
|
||||
run: |
|
||||
gli increment-version -T ${{ secrets.UPDATE_SERVER_ADMIN_TOKEN }} -c Canary
|
||||
|
|
|
|||
224
.github/workflows/debug_release.yml
vendored
224
.github/workflows/debug_release.yml
vendored
|
|
@ -1,224 +0,0 @@
|
|||
name: Release job (Debug)
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs: {}
|
||||
|
||||
concurrency: release
|
||||
|
||||
env:
|
||||
POWERSHELL_TELEMETRY_OPTOUT: 1
|
||||
DOTNET_CLI_TELEMETRY_OPTOUT: 1
|
||||
RYUJINX_BASE_VERSION: "1.3"
|
||||
RYUJINX_TARGET_RELEASE_CHANNEL_NAME: "release"
|
||||
RELEASE: 1
|
||||
|
||||
jobs:
|
||||
release:
|
||||
name: Release for ${{ matrix.platform.name }}
|
||||
runs-on: ${{ matrix.platform.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
platform:
|
||||
- { name: win-x64, os: windows-latest, zip_os_name: win_x64 }
|
||||
#- { name: win-arm64, os: windows-latest, zip_os_name: win_arm64 }
|
||||
- { name: linux-x64, os: ubuntu-latest, zip_os_name: linux_x64 }
|
||||
- { name: linux-arm64, os: ubuntu-latest, zip_os_name: linux_arm64 }
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
global-json-file: global.json
|
||||
|
||||
- name: Overwrite csc problem matcher
|
||||
run: echo "::add-matcher::.github/csc.json"
|
||||
|
||||
- name: Get version info
|
||||
id: version_info
|
||||
run: |
|
||||
echo "build_version=${{ env.RYUJINX_BASE_VERSION }}.$((${{ github.run_number }} + 10))" >> $GITHUB_OUTPUT
|
||||
echo "prev_build_version=${{ env.RYUJINX_BASE_VERSION }}.$((${{ github.run_number }} - 1))" >> $GITHUB_OUTPUT
|
||||
echo "git_short_hash=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
|
||||
shell: bash
|
||||
|
||||
- name: Configure for release
|
||||
run: |
|
||||
sed -r --in-place 's/\%\%RYUJINX_BUILD_VERSION\%\%/${{ steps.version_info.outputs.build_version }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
sed -r --in-place 's/\%\%RYUJINX_BUILD_GIT_HASH\%\%/${{ steps.version_info.outputs.git_short_hash }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_NAME\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_NAME }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
sed -r --in-place 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/Config\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
shell: bash
|
||||
|
||||
- name: Create output dir
|
||||
run: "mkdir release_output"
|
||||
|
||||
- name: Publish
|
||||
run: |
|
||||
dotnet publish -c Release -r "${{ matrix.platform.name }}" -o ./publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx --self-contained
|
||||
|
||||
- name: Packing Windows builds
|
||||
if: matrix.platform.os == 'windows-latest'
|
||||
run: |
|
||||
pushd publish
|
||||
rm libarmeilleure-jitsupport.dylib
|
||||
7z a ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip ../publish
|
||||
popd
|
||||
|
||||
gh release download -R GreemDev/GLI -O gli.exe -p 'GitLabCli-win_x64.exe'
|
||||
|
||||
./gli.exe --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=UploadGenericPackage "Ryubing|${{ steps.version_info.outputs.build_version }}|release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip"
|
||||
shell: bash
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Install GitLabCli
|
||||
if: matrix.platform.os == 'ubuntu-latest'
|
||||
run: |
|
||||
mkdir -p $HOME/.bin
|
||||
gh release download -R GreemDev/GLI -O gli -p 'GitLabCli-linux_x64'
|
||||
chmod +x gli
|
||||
mv gli $HOME/.bin/
|
||||
echo "$HOME/.bin" >> $GITHUB_PATH
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Packing Linux builds
|
||||
if: matrix.platform.os == 'ubuntu-latest'
|
||||
run: |
|
||||
pushd publish
|
||||
rm libarmeilleure-jitsupport.dylib
|
||||
chmod +x Ryujinx.sh Ryujinx
|
||||
tar -czvf ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz ../publish
|
||||
popd
|
||||
|
||||
gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=UploadGenericPackage "Ryubing|${{ steps.version_info.outputs.build_version }}|release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz"
|
||||
shell: bash
|
||||
|
||||
- name: Build AppImage (Linux)
|
||||
if: matrix.platform.os == 'ubuntu-latest'
|
||||
run: |
|
||||
BUILD_VERSION="${{ steps.version_info.outputs.build_version }}"
|
||||
PLATFORM_NAME="${{ matrix.platform.name }}"
|
||||
|
||||
sudo apt install -y zsync desktop-file-utils appstream
|
||||
|
||||
mkdir -p tools
|
||||
export PATH="$PATH:$(readlink -f tools)"
|
||||
|
||||
# Setup appimagetool
|
||||
wget -q -O tools/appimagetool "https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage"
|
||||
chmod +x tools/appimagetool
|
||||
chmod +x distribution/linux/appimage/build-appimage.sh
|
||||
|
||||
# Explicitly set $ARCH for appimagetool ($ARCH_NAME is for the file name)
|
||||
if [ "$PLATFORM_NAME" = "linux-x64" ]; then
|
||||
ARCH_NAME=x64
|
||||
export ARCH=x86_64
|
||||
elif [ "$PLATFORM_NAME" = "linux-arm64" ]; then
|
||||
ARCH_NAME=arm64
|
||||
export ARCH=aarch64
|
||||
else
|
||||
echo "Unexpected PLATFORM_NAME "$PLATFORM_NAME""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
export UFLAG="gh-releases-zsync|${{ github.repository_owner }}|${{ github.event.repository.name }}|latest|*-$ARCH_NAME.AppImage.zsync"
|
||||
BUILDDIR=publish OUTDIR=publish_appimage distribution/linux/appimage/build-appimage.sh
|
||||
|
||||
pushd publish_appimage
|
||||
mv Ryujinx.AppImage ../release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage
|
||||
mv Ryujinx.AppImage.zsync ../release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage.zsync
|
||||
popd
|
||||
|
||||
gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=UploadGenericPackage "Ryubing|${{ steps.version_info.outputs.build_version }}|release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage"
|
||||
gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=UploadGenericPackage "Ryubing|${{ steps.version_info.outputs.build_version }}|release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage.zsync"
|
||||
shell: bash
|
||||
|
||||
macos_release:
|
||||
name: Release MacOS universal
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
global-json-file: global.json
|
||||
|
||||
- name: Setup LLVM 17
|
||||
run: |
|
||||
wget https://apt.llvm.org/llvm.sh
|
||||
chmod +x llvm.sh
|
||||
sudo ./llvm.sh 17
|
||||
|
||||
- name: Install GitLabCli
|
||||
run: |
|
||||
mkdir -p $HOME/.bin
|
||||
gh release download -R GreemDev/GLI -O gli -p 'GitLabCli-linux_x64'
|
||||
chmod +x gli
|
||||
mv gli $HOME/.bin/
|
||||
echo "$HOME/.bin" >> $GITHUB_PATH
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Install rcodesign
|
||||
run: |
|
||||
mkdir -p $HOME/.bin
|
||||
gh release download -R indygreg/apple-platform-rs -O apple-codesign.tar.gz -p 'apple-codesign-*-x86_64-unknown-linux-musl.tar.gz'
|
||||
tar -xzvf apple-codesign.tar.gz --wildcards '*/rcodesign' --strip-components=1
|
||||
rm apple-codesign.tar.gz
|
||||
mv rcodesign $HOME/.bin/
|
||||
echo "$HOME/.bin" >> $GITHUB_PATH
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Get version info
|
||||
id: version_info
|
||||
run: |
|
||||
echo "build_version=${{ env.RYUJINX_BASE_VERSION }}.$((${{ github.run_number }} + 10))" >> $GITHUB_OUTPUT
|
||||
echo "prev_build_version=${{ env.RYUJINX_BASE_VERSION }}.$((${{ github.run_number }} - 1))" >> $GITHUB_OUTPUT
|
||||
echo "git_short_hash=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Configure for release
|
||||
run: |
|
||||
sed -r --in-place 's/\%\%RYUJINX_BUILD_VERSION\%\%/${{ steps.version_info.outputs.build_version }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
sed -r --in-place 's/\%\%RYUJINX_BUILD_GIT_HASH\%\%/${{ steps.version_info.outputs.git_short_hash }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_NAME\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_NAME }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
sed -r --in-place 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/Config\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
shell: bash
|
||||
|
||||
- name: Publish macOS Ryujinx
|
||||
run: |
|
||||
./distribution/macos/create_macos_build_ava.sh . publish_tmp_ava publish ./distribution/macos/entitlements.xml "${{ steps.version_info.outputs.build_version }}" "${{ steps.version_info.outputs.git_short_hash }}" Release 0
|
||||
gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=UploadGenericPackage "Ryubing|${{ steps.version_info.outputs.build_version }}|publish/ryujinx-${{ steps.version_info.outputs.build_version }}-macos_universal.app.tar.gz"
|
||||
|
||||
create_gitlab_release:
|
||||
name: Create GitLab Release
|
||||
runs-on: ubuntu-24.04
|
||||
needs:
|
||||
- macos_release
|
||||
- release
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Get version info
|
||||
id: version_info
|
||||
run: |
|
||||
echo "build_version=${{ env.RYUJINX_BASE_VERSION }}.$((${{ github.run_number }} + 10))" >> $GITHUB_OUTPUT
|
||||
echo "prev_build_version=${{ env.RYUJINX_BASE_VERSION }}.$((${{ github.run_number }} - 1))" >> $GITHUB_OUTPUT
|
||||
echo "git_short_hash=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
|
||||
shell: bash
|
||||
|
||||
- name: Install GitLabCli
|
||||
run: |
|
||||
mkdir -p $HOME/.bin
|
||||
gh release download -R GreemDev/GLI -O gli -p 'GitLabCli-linux_x64'
|
||||
chmod +x gli
|
||||
mv gli $HOME/.bin/
|
||||
echo "$HOME/.bin" >> $GITHUB_PATH
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Create release
|
||||
run: |
|
||||
gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=CreateReleaseFromGenericPackageFiles "Ryubing|${{ steps.version_info.outputs.build_version }}|${{ steps.version_info.outputs.git_short_hash }}|test|THIS IS NOT INTENDED FOR END USER USAGE"
|
||||
123
.github/workflows/release.yml
vendored
123
.github/workflows/release.yml
vendored
|
|
@ -1,15 +1,18 @@
|
|||
name: Release job
|
||||
name: Stable CI
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs: {}
|
||||
inputs:
|
||||
is_bugfix_release:
|
||||
description: "Bug fix release: If checked, this will increment the third number for only Stable, and leave the Major version alone for both Stable and Canary."
|
||||
required: true
|
||||
type: boolean
|
||||
|
||||
concurrency: release
|
||||
|
||||
env:
|
||||
POWERSHELL_TELEMETRY_OPTOUT: 1
|
||||
DOTNET_CLI_TELEMETRY_OPTOUT: 1
|
||||
RYUJINX_BASE_VERSION: "1.3"
|
||||
RYUJINX_TARGET_RELEASE_CHANNEL_NAME: "release"
|
||||
RELEASE: 1
|
||||
|
||||
|
|
@ -20,8 +23,8 @@ jobs:
|
|||
strategy:
|
||||
matrix:
|
||||
platform:
|
||||
- { name: win-x64, os: windows-latest, zip_os_name: win_x64 }
|
||||
#- { name: win-arm64, os: windows-latest, zip_os_name: win_arm64 }
|
||||
- { name: win-x64, os: ubuntu-latest, zip_os_name: win_x64 }
|
||||
#- { name: win-arm64, os: ubuntu-latest, zip_os_name: win_arm64 }
|
||||
- { name: linux-x64, os: ubuntu-latest, zip_os_name: linux_x64 }
|
||||
- { name: linux-arm64, os: ubuntu-latest, zip_os_name: linux_arm64 }
|
||||
steps:
|
||||
|
|
@ -33,12 +36,30 @@ jobs:
|
|||
|
||||
- name: Overwrite csc problem matcher
|
||||
run: echo "::add-matcher::.github/csc.json"
|
||||
|
||||
- name: Install 7zip
|
||||
run: |
|
||||
sudo apt install -y 7zip
|
||||
|
||||
- name: Install gli
|
||||
run: |
|
||||
mkdir -p $HOME/.bin
|
||||
gh release download -R GreemDev/GLI -O gli -p 'gli-linux-x64'
|
||||
chmod +x gli
|
||||
mv gli $HOME/.bin/
|
||||
echo "$HOME/.bin" >> $GITHUB_PATH
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Get version info
|
||||
id: version_info
|
||||
run: |
|
||||
echo "build_version=${{ env.RYUJINX_BASE_VERSION }}.${{ github.run_number }}" >> $GITHUB_OUTPUT
|
||||
echo "prev_build_version=${{ env.RYUJINX_BASE_VERSION }}.$((${{ github.run_number }} - 1))" >> $GITHUB_OUTPUT
|
||||
if [ '${{ inputs.is_bugfix_release }}' == 'false' ]; then
|
||||
echo "build_version=$(gli get-next-version -m -c Stable -R)" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "build_version=$(gli get-next-version -c Stable -R)" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
echo "prev_build_version=$(gli get-current-version -c Stable -R)" >> $GITHUB_OUTPUT
|
||||
echo "git_short_hash=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
|
||||
shell: bash
|
||||
|
||||
|
|
@ -58,47 +79,34 @@ jobs:
|
|||
dotnet publish -c Release -r "${{ matrix.platform.name }}" -o ./publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx --self-contained
|
||||
|
||||
- name: Packing Windows builds
|
||||
if: matrix.platform.os == 'windows-latest'
|
||||
if: contains(matrix.platform.name, 'win')
|
||||
run: |
|
||||
pushd publish
|
||||
rm libarmeilleure-jitsupport.dylib
|
||||
7z a ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip ../publish
|
||||
popd
|
||||
|
||||
gh release download -R GreemDev/GLI -O gli.exe -p 'GitLabCli-win_x64.exe'
|
||||
|
||||
./gli.exe --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=UploadGenericPackage "Ryubing|${{ steps.version_info.outputs.build_version }}|release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip"
|
||||
|
||||
gli upload-generic-package -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/ryujinx -n Ryubing -v ${{ steps.version_info.outputs.build_version }} -r 5 -p release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip
|
||||
shell: bash
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Install GitLabCli
|
||||
if: matrix.platform.os == 'ubuntu-latest'
|
||||
run: |
|
||||
mkdir -p $HOME/.bin
|
||||
gh release download -R GreemDev/GLI -O gli -p 'GitLabCli-linux_x64'
|
||||
chmod +x gli
|
||||
mv gli $HOME/.bin/
|
||||
echo "$HOME/.bin" >> $GITHUB_PATH
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Packing Linux builds
|
||||
if: matrix.platform.os == 'ubuntu-latest'
|
||||
if: contains(matrix.platform.name, 'linux')
|
||||
run: |
|
||||
pushd publish
|
||||
rm libarmeilleure-jitsupport.dylib
|
||||
chmod +x Ryujinx.sh Ryujinx
|
||||
tar -czvf ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz ../publish
|
||||
popd
|
||||
|
||||
gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=UploadGenericPackage "Ryubing|${{ steps.version_info.outputs.build_version }}|release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz"
|
||||
|
||||
gli upload-generic-package -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/ryujinx -n Ryubing -v ${{ steps.version_info.outputs.build_version }} -r 5 -p release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz
|
||||
shell: bash
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build AppImage (Linux)
|
||||
if: matrix.platform.os == 'ubuntu-latest'
|
||||
if: contains(matrix.platform.name, 'linux')
|
||||
run: |
|
||||
BUILD_VERSION="${{ steps.version_info.outputs.build_version }}"
|
||||
PLATFORM_NAME="${{ matrix.platform.name }}"
|
||||
|
|
@ -131,7 +139,7 @@ jobs:
|
|||
mv Ryujinx.AppImage ../release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage
|
||||
popd
|
||||
|
||||
gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=UploadGenericPackage "Ryubing|${{ steps.version_info.outputs.build_version }}|release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage"
|
||||
gli upload-generic-package -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/ryujinx -n Ryubing -v ${{ steps.version_info.outputs.build_version }} -r 5 -p release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage
|
||||
shell: bash
|
||||
|
||||
macos_release:
|
||||
|
|
@ -150,10 +158,10 @@ jobs:
|
|||
chmod +x llvm.sh
|
||||
sudo ./llvm.sh 17
|
||||
|
||||
- name: Install GitLabCli
|
||||
- name: Install gli
|
||||
run: |
|
||||
mkdir -p $HOME/.bin
|
||||
gh release download -R GreemDev/GLI -O gli -p 'GitLabCli-linux_x64'
|
||||
gh release download -R GreemDev/GLI -O gli -p 'gli-linux-x64'
|
||||
chmod +x gli
|
||||
mv gli $HOME/.bin/
|
||||
echo "$HOME/.bin" >> $GITHUB_PATH
|
||||
|
|
@ -174,9 +182,14 @@ jobs:
|
|||
- name: Get version info
|
||||
id: version_info
|
||||
run: |
|
||||
echo "build_version=${{ env.RYUJINX_BASE_VERSION }}.${{ github.run_number }}" >> $GITHUB_OUTPUT
|
||||
echo "prev_build_version=${{ env.RYUJINX_BASE_VERSION }}.$((${{ github.run_number }} - 1))" >> $GITHUB_OUTPUT
|
||||
if [ '${{ inputs.is_bugfix_release }}' == 'false' ]; then
|
||||
echo "build_version=$(gli get-next-version -m -c Stable -R)" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "build_version=$(gli get-next-version -c Stable -R)" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
echo "prev_build_version=$(gli get-current-version -c Stable -R)" >> $GITHUB_OUTPUT
|
||||
echo "git_short_hash=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
|
||||
shell: bash
|
||||
|
||||
- name: Configure for release
|
||||
run: |
|
||||
|
|
@ -189,7 +202,8 @@ jobs:
|
|||
- name: Publish macOS Ryujinx
|
||||
run: |
|
||||
./distribution/macos/create_macos_build_ava.sh . publish_tmp_ava publish ./distribution/macos/entitlements.xml "${{ steps.version_info.outputs.build_version }}" "${{ steps.version_info.outputs.git_short_hash }}" Release 0
|
||||
gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=UploadGenericPackage "Ryubing|${{ steps.version_info.outputs.build_version }}|publish/ryujinx-${{ steps.version_info.outputs.build_version }}-macos_universal.app.tar.gz"
|
||||
|
||||
gli upload-generic-package -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/ryujinx -n Ryubing -v ${{ steps.version_info.outputs.build_version }} -r 5 -p publish/ryujinx-${{ steps.version_info.outputs.build_version }}-macos_universal.app.tar.gz
|
||||
|
||||
create_gitlab_release:
|
||||
name: Create GitLab Release
|
||||
|
|
@ -200,32 +214,45 @@ jobs:
|
|||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Get version info
|
||||
id: version_info
|
||||
run: |
|
||||
echo "build_version=${{ env.RYUJINX_BASE_VERSION }}.${{ github.run_number }}" >> $GITHUB_OUTPUT
|
||||
echo "prev_build_version=${{ env.RYUJINX_BASE_VERSION }}.$((${{ github.run_number }} - 1))" >> $GITHUB_OUTPUT
|
||||
echo "git_short_hash=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
|
||||
shell: bash
|
||||
|
||||
- name: Install GitLabCli
|
||||
- name: Install gli
|
||||
run: |
|
||||
mkdir -p $HOME/.bin
|
||||
gh release download -R GreemDev/GLI -O gli -p 'GitLabCli-linux_x64'
|
||||
gh release download -R GreemDev/GLI -O gli -p 'gli-linux-x64'
|
||||
chmod +x gli
|
||||
mv gli $HOME/.bin/
|
||||
echo "$HOME/.bin" >> $GITHUB_PATH
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Get version info
|
||||
id: version_info
|
||||
run: |
|
||||
if [ '${{ inputs.is_bugfix_release }}' == 'false' ]; then
|
||||
echo "build_version=$(gli get-next-version -m -c Stable -R)" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "build_version=$(gli get-next-version -c Stable -R)" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
echo "prev_build_version=$(gli get-current-version -c Stable -R)" >> $GITHUB_OUTPUT
|
||||
echo "git_short_hash=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
|
||||
echo "commit_message=$(git log -1 --pretty=%B)" >> $GITHUB_OUTPUT
|
||||
shell: bash
|
||||
|
||||
- name: Create release
|
||||
run: |
|
||||
gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=CreateReleaseFromGenericPackageFiles "Ryubing|${{ steps.version_info.outputs.build_version }}|${{ steps.version_info.outputs.git_short_hash }}|${{ steps.version_info.outputs.build_version }}|msd:${{ steps.version_info.outputs.build_version }}"
|
||||
|
||||
gli create-release-from-generic-package-files -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/ryujinx -n Ryubing -v ${{ steps.version_info.outputs.build_version }} -r ${{ steps.version_info.outputs.git_short_hash }} -t "${{ steps.version_info.outputs.build_version }}" -b "msd:${{ steps.version_info.outputs.build_version }}"
|
||||
|
||||
- name: Send notification webhook
|
||||
run: |
|
||||
gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=SendUpdateMessage "${{ steps.version_info.outputs.build_version }}|32cd32|${{ secrets.STABLE_DISCORD_WEBHOOK }}|https://avatars.githubusercontent.com/u/192939710?s=200&v=4|false"
|
||||
|
||||
gli send-update-message -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/ryujinx -t ${{ steps.version_info.outputs.build_version }} -c 32cd32 -w ${{ secrets.STABLE_DISCORD_WEBHOOK }} -i https://avatars.githubusercontent.com/u/192939710?s=200&v=4
|
||||
|
||||
- name: Notify update server of new builds
|
||||
run: |
|
||||
curl 'https://update.ryujinx.app/api/v1/admin/refresh_cache?rc=stable' -X PATCH -H 'accept: */*' -H 'Authorization: ${{ secrets.UPDATE_SERVER_ADMIN_TOKEN }}'
|
||||
gli refresh-version-cache -T ${{ secrets.UPDATE_SERVER_ADMIN_TOKEN }} -c Stable
|
||||
|
||||
- name: Advance to the next version
|
||||
run: |
|
||||
if [ '${{ inputs.is_bugfix_release }}' == 'false' ]; then
|
||||
gli advance-version -T ${{ secrets.UPDATE_SERVER_ADMIN_TOKEN }}
|
||||
else
|
||||
gli increment-version -T ${{ secrets.UPDATE_SERVER_ADMIN_TOKEN }} -c Stable
|
||||
fi
|
||||
|
|
|
|||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -100,6 +100,7 @@ DocProject/Help/html
|
|||
|
||||
# Click-Once directory
|
||||
publish/
|
||||
RyubingMaintainerTools/
|
||||
|
||||
# Publish Web Output
|
||||
*.Publish.xml
|
||||
|
|
|
|||
14
Ryujinx.sln
14
Ryujinx.sln
|
|
@ -21,6 +21,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.GAL", "src
|
|||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.OpenGL", "src\Ryujinx.Graphics.OpenGL\Ryujinx.Graphics.OpenGL.csproj", "{9558FB96-075D-4219-8FFF-401979DC0B69}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ryujinx.Graphics.RenderDoc", "src\Ryujinx.Graphics.RenderDocApi\Ryujinx.Graphics.RenderDocApi.csproj", "{D58FA894-27D5-4EAA-9042-AD422AD82931}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Texture", "src\Ryujinx.Graphics.Texture\Ryujinx.Graphics.Texture.csproj", "{E1B1AD28-289D-47B7-A106-326972240207}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Shader", "src\Ryujinx.Graphics.Shader\Ryujinx.Graphics.Shader.csproj", "{03B955CD-AD84-4B93-AAA7-BF17923BBAA5}"
|
||||
|
|
@ -555,6 +557,18 @@ Global
|
|||
{988E6191-82E1-4E13-9DDB-CB9FA2FDAF29}.Release|x64.Build.0 = Release|Any CPU
|
||||
{988E6191-82E1-4E13-9DDB-CB9FA2FDAF29}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{988E6191-82E1-4E13-9DDB-CB9FA2FDAF29}.Release|x86.Build.0 = Release|Any CPU
|
||||
{D58FA894-27D5-4EAA-9042-AD422AD82931}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{D58FA894-27D5-4EAA-9042-AD422AD82931}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{D58FA894-27D5-4EAA-9042-AD422AD82931}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{D58FA894-27D5-4EAA-9042-AD422AD82931}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{D58FA894-27D5-4EAA-9042-AD422AD82931}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{D58FA894-27D5-4EAA-9042-AD422AD82931}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{D58FA894-27D5-4EAA-9042-AD422AD82931}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{D58FA894-27D5-4EAA-9042-AD422AD82931}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{D58FA894-27D5-4EAA-9042-AD422AD82931}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{D58FA894-27D5-4EAA-9042-AD422AD82931}.Release|x64.Build.0 = Release|Any CPU
|
||||
{D58FA894-27D5-4EAA-9042-AD422AD82931}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{D58FA894-27D5-4EAA-9042-AD422AD82931}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
|
|||
24
assets/Languages.json
Normal file
24
assets/Languages.json
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"Languages": {
|
||||
"ar_SA": "اَلْعَرَبِيَّةُ",
|
||||
"de_DE": "Deutsch",
|
||||
"el_GR": "Ελληνικά",
|
||||
"en_US": "English (US)",
|
||||
"es_ES": "Español (ES)",
|
||||
"fr_FR": "Français (FR)",
|
||||
"he_IL": "עִברִית",
|
||||
"it_IT": "Italiano",
|
||||
"ja_JP": "日本語",
|
||||
"ko_KR": "한국어",
|
||||
"no_NO": "Norsk",
|
||||
"pl_PL": "Polski",
|
||||
"pt_BR": "Português (BR)",
|
||||
"ru_RU": "Русский",
|
||||
"sv_SE": "Svenska",
|
||||
"th_TH": "ภาษาไทย",
|
||||
"tr_TR": "Türkçe",
|
||||
"uk_UA": "Українська",
|
||||
"zh_CN": "简体中文",
|
||||
"zh_TW": "繁體中文 (台灣)"
|
||||
}
|
||||
}
|
||||
60
assets/Locales.md
Normal file
60
assets/Locales.md
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
|
||||
# Ryubing Locales
|
||||
|
||||
Ryubing Locales uses a custom format, which uses a file for defining the supported languages and a folder of json files for the locales themselves.
|
||||
Each json file holds the locales for a specific part of the emulator, e.g. the Setup Wizard locales are in `SetupWizard.json`, and each locale entry in the file includes all the supported languages in the same place.
|
||||
|
||||
## Languages
|
||||
in the `/assets/` folder you will find the `Languages.json` file, which defines all the languages supported by the emulator.
|
||||
The file includes a table of the langauge codes and their langauge names.
|
||||
|
||||
#Example of the format for Languages.json
|
||||
{
|
||||
"Languages": {
|
||||
"ar_SA": "اَلْعَرَبِيَّةُ",
|
||||
"en_US": "English (US)",
|
||||
...
|
||||
"zh_TW": "繁體中文 (台灣)"
|
||||
}
|
||||
}
|
||||
|
||||
## Locales
|
||||
in the `/assets/Locales/` folder you will find the json files, which define all the locales supported by the emulator.
|
||||
Each json file holds locales for a specific part of the emulator in a large array of locale objects.
|
||||
Each locale is made up an ID used for lookup and a list of the languages and their matching translations.
|
||||
Any empty string or null value will automatically use the English translation instead in the emulator.
|
||||
|
||||
### Format
|
||||
When adding a new locale, you just need to add the ID and the en_US language translation, then the validation system will add default values for the rest of languages automatically, when rebuilding the project.
|
||||
If you want to signal that a translation is supposed to match the English translation, you just have to replace the empty string with `null`.
|
||||
When you want to check what translations are missing for a language just search for `"<lang_code>": ""`, e.g: `"en_US": ""` (but with any other language, as English will never be missing translations).
|
||||
|
||||
### Legacy file (Root.json)
|
||||
Currently all older locales are stored in `Root.json`, but they are slowly being moved into newer, more descriptive json files, to make the locale system more accessible.
|
||||
Do **not** add new locales to `Root.json`.
|
||||
If no json file exists for the specific part of the emulator you're working on, you should instead add a new json file for that part.
|
||||
|
||||
#Example of the format for Root.json
|
||||
{
|
||||
"Locales": [
|
||||
{
|
||||
"ID": "MenuBarActionsOpenMiiEditor",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"en_US": "Mii Editor",
|
||||
...
|
||||
"zh_TW": "Mii 編輯器"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "KeyNumber9",
|
||||
"Translations": {
|
||||
"ar_SA": "٩",
|
||||
"en_US": "9",
|
||||
...
|
||||
"zh_TW": null
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
104
assets/Locales/RenderDoc.json
Normal file
104
assets/Locales/RenderDoc.json
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
{
|
||||
"Locales": [
|
||||
{
|
||||
"ID": "MenuBarActions_StartCapture",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"el_GR": "",
|
||||
"en_US": "Start RenderDoc Frame Capture",
|
||||
"es_ES": "Iniciar una captura de fotograma de RenderDoc",
|
||||
"fr_FR": "Démarrer une capture de trame RenderDoc",
|
||||
"he_IL": "",
|
||||
"it_IT": "",
|
||||
"ja_JP": "",
|
||||
"ko_KR": "",
|
||||
"no_NO": "",
|
||||
"pl_PL": "",
|
||||
"pt_BR": "",
|
||||
"ru_RU": "",
|
||||
"sv_SE": "",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "",
|
||||
"zh_TW": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "MenuBarActions_EndCapture",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"el_GR": "",
|
||||
"en_US": "End RenderDoc Frame Capture",
|
||||
"es_ES": "Detener la captura de fotograma de RenderDoc",
|
||||
"fr_FR": "Arrêter la capture de trame RenderDoc",
|
||||
"he_IL": "",
|
||||
"it_IT": "",
|
||||
"ja_JP": "",
|
||||
"ko_KR": "",
|
||||
"no_NO": "",
|
||||
"pl_PL": "",
|
||||
"pt_BR": "",
|
||||
"ru_RU": "",
|
||||
"sv_SE": "",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "",
|
||||
"zh_TW": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "MenuBarActions_DiscardCapture",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"el_GR": "",
|
||||
"en_US": "Discard RenderDoc Frame Capture",
|
||||
"es_ES": "Descartar la captura de fotograma de RenderDoc",
|
||||
"fr_FR": "Supprimer la capture de trame RenderDoc",
|
||||
"he_IL": "",
|
||||
"it_IT": "",
|
||||
"ja_JP": "",
|
||||
"ko_KR": "",
|
||||
"no_NO": "",
|
||||
"pl_PL": "",
|
||||
"pt_BR": "",
|
||||
"ru_RU": "",
|
||||
"sv_SE": "",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "",
|
||||
"zh_TW": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "MenuBarActions_DiscardCapture_ToolTip",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"el_GR": "",
|
||||
"en_US": "Ends the currently active RenderDoc Frame Capture, immediately discarding its result.",
|
||||
"es_ES": "Finaliza la captura de fotograma de RenderDoc actualmente activa y descarta inmediatamente su resultado.",
|
||||
"fr_FR": "Met fin à la capture de trame RenderDoc en cours, en supprimant immédiatement son résultat.",
|
||||
"he_IL": "",
|
||||
"it_IT": "",
|
||||
"ja_JP": "",
|
||||
"ko_KR": "",
|
||||
"no_NO": "",
|
||||
"pl_PL": "",
|
||||
"pt_BR": "",
|
||||
"ru_RU": "",
|
||||
"sv_SE": "",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "",
|
||||
"zh_TW": ""
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -1,72 +1,5 @@
|
|||
{
|
||||
"Info": {
|
||||
"Format1": "The Locale file uses a custom Unified format.",
|
||||
"Format2": "The file starts with a list of all the supported languages.",
|
||||
"Format3": "Each locale is made up an ID used for lookup and a list",
|
||||
"Format4": "of the languages and their matching translations.",
|
||||
"Format5": "When adding a new locale you just need to add the ID and",
|
||||
"Format6": "the en_US language translation, then the validation system",
|
||||
"Format7": "will add the rest of the languages automatically on rebuild.",
|
||||
"Format8": "By default the languages will be added with an empty string.",
|
||||
"Format9": "Any empty string or null value will automatically match the",
|
||||
"Format10": "English translation.",
|
||||
"Format11": "If you want to signal that a translation is supposed to",
|
||||
"Format12": "match the English translation, you just have to replace the",
|
||||
"Format13": "empty string with null.",
|
||||
"Format14": "Translators who want to check what translations are missing",
|
||||
"Format15": "for their language just need to search for:",
|
||||
"Format16": "{'lang_code': ''} with double quotes instead of single",
|
||||
"Format17": "e.g: {'en_US': ''} (but with any other language as English",
|
||||
"Format18": "will never be missing translations)."
|
||||
},
|
||||
"Languages": [
|
||||
"ar_SA",
|
||||
"de_DE",
|
||||
"el_GR",
|
||||
"en_US",
|
||||
"es_ES",
|
||||
"fr_FR",
|
||||
"he_IL",
|
||||
"it_IT",
|
||||
"ja_JP",
|
||||
"ko_KR",
|
||||
"no_NO",
|
||||
"pl_PL",
|
||||
"pt_BR",
|
||||
"ru_RU",
|
||||
"sv_SE",
|
||||
"th_TH",
|
||||
"tr_TR",
|
||||
"uk_UA",
|
||||
"zh_CN",
|
||||
"zh_TW"
|
||||
],
|
||||
"Locales": [
|
||||
{
|
||||
"ID": "Language",
|
||||
"Translations": {
|
||||
"ar_SA": "اَلْعَرَبِيَّةُ",
|
||||
"de_DE": "Deutsch",
|
||||
"el_GR": "Ελληνικά",
|
||||
"en_US": "English (US)",
|
||||
"es_ES": "Español (ES)",
|
||||
"fr_FR": "Français (FR)",
|
||||
"he_IL": "עִברִית",
|
||||
"it_IT": "Italiano",
|
||||
"ja_JP": "日本語",
|
||||
"ko_KR": "한국어",
|
||||
"no_NO": "Norsk",
|
||||
"pl_PL": "Polski",
|
||||
"pt_BR": "Português (BR)",
|
||||
"ru_RU": "Русский",
|
||||
"sv_SE": "Svenska",
|
||||
"th_TH": "ภาษาไทย",
|
||||
"tr_TR": "Türkçe",
|
||||
"uk_UA": "Українська",
|
||||
"zh_CN": "简体中文",
|
||||
"zh_TW": "繁體中文 (台灣)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "MenuBarActionsOpenMiiEditor",
|
||||
"Translations": {
|
||||
|
|
@ -225,7 +158,7 @@
|
|||
"el_GR": "Χωρίς Ελέγχους (γρηγορότερο, μη ασφαλές)",
|
||||
"en_US": "Host Unchecked (Fastest, Unsafe)",
|
||||
"es_ES": "Host Sin Verificación (Más rápido, Inseguro)",
|
||||
"fr_FR": "Hôte Non Vérifié (plus rapide, non sécurisé)",
|
||||
"fr_FR": "Hôte Non Vérifié (Plus Rapide, Non Sécurisé)",
|
||||
"he_IL": "מארח לא מבוקר (המהיר ביותר, לא בטוח)",
|
||||
"it_IT": "Host senza verifica (più veloce, non sicura)",
|
||||
"ja_JP": "ホスト, チェックなし (最高速, 安全でない)",
|
||||
|
|
@ -3350,7 +3283,7 @@
|
|||
"el_GR": "Ερώτημα",
|
||||
"en_US": "Prompt",
|
||||
"es_ES": "Al Inicio",
|
||||
"fr_FR": "Demande",
|
||||
"fr_FR": "Demander",
|
||||
"he_IL": "הודעה",
|
||||
"it_IT": "Domanda",
|
||||
"ja_JP": "プロンプト",
|
||||
|
|
@ -4800,7 +4733,7 @@
|
|||
"el_GR": "Backend Ήχου:",
|
||||
"en_US": "Audio Backend:",
|
||||
"es_ES": "Motor de Audio:",
|
||||
"fr_FR": "Bibliothèque Audio :",
|
||||
"fr_FR": "Moteur Audio :",
|
||||
"he_IL": "אחראי שמע:",
|
||||
"it_IT": "Backend audio:",
|
||||
"ja_JP": "音声バックエンド:",
|
||||
|
|
@ -5700,7 +5633,7 @@
|
|||
"el_GR": "Έκταση σε όλο το παράθυρο",
|
||||
"en_US": "Stretch to Fit Window",
|
||||
"es_ES": "Estirar a la Ventana",
|
||||
"fr_FR": "Ajuster à la Taille de la Fenêtre",
|
||||
"fr_FR": "Adapter à la Taille de la Fenêtre",
|
||||
"he_IL": "מתח לגודל חלון",
|
||||
"it_IT": "Adatta alla finestra",
|
||||
"ja_JP": "ウインドウサイズに合わせる",
|
||||
|
|
@ -5750,7 +5683,7 @@
|
|||
"el_GR": "Τοποθεσία Shaders Γραφικών:",
|
||||
"en_US": "Graphics Shader Dump Path:",
|
||||
"es_ES": "Directorio de Volcado de Sombreadores:",
|
||||
"fr_FR": "Chemin du Dossier de Copie des Shaders :",
|
||||
"fr_FR": "Chemin du dump des shaders graphiques :",
|
||||
"he_IL": "",
|
||||
"it_IT": "Percorso di dump degli shader:",
|
||||
"ja_JP": "グラフィックス シェーダー ダンプパス:",
|
||||
|
|
@ -15000,7 +14933,7 @@
|
|||
"el_GR": "Πολυνηματική Επεξεργασία Γραφικών:",
|
||||
"en_US": "Graphics Backend Multithreading:",
|
||||
"es_ES": "Multihilado del Motor Gráfico:",
|
||||
"fr_FR": "Interface graphique multithread :",
|
||||
"fr_FR": "Interface Graphique Multithread :",
|
||||
"he_IL": "אחראי גרפיקה רב-תהליכי:",
|
||||
"it_IT": "Multithreading del backend grafico:",
|
||||
"ja_JP": "グラフィックスバックエンドのマルチスレッド実行:",
|
||||
|
|
@ -16598,9 +16531,9 @@
|
|||
"ar_SA": "",
|
||||
"de_DE": "Diese Option überspringt den Dialog 'Benutzerprofile verwalten' während des Spiels und verwendet ein voreingestelltes Profil.\n\nDie Profilumschaltung finden Sie unter 'Einstellungen' - 'Benutzerprofile verwalten'. Wählen Sie das gewünschte Profil aus, bevor Sie das Spiel laden.",
|
||||
"el_GR": "Αυτή η επιλογή παρακάμπτει το παράθυρο διαλόγου 'Διαχειριστής Προφίλ Χρήστη' κατά τη διάρκεια του παιχνιδιού, χρησιμοποιώντας ένα προεπιλεγμένο προφίλ.\n\nΗ εναλλαγή προφίλ βρίσκεται στις 'Ρυθμίσεις' - 'Διαχειριστής Προφίλ Χρήστη'. Επιλέξτε το επιθυμητό προφίλ πριν φορτώσετε το παιχνίδι.",
|
||||
"en_US": "This option skips the 'Manage User Profiles' dialog during gameplay, using a pre-selected profile.\n\nProfile switching is found in 'Settings' - 'Manager User Profiles'. Select the desired profile before loading the game.",
|
||||
"es_ES": "Esta opción omite el diálogo de 'Gestionar perfiles de usuario' durante el juego, utilizando un perfil preseleccionado.\n\nEl cambio de perfil se encuentra en 'Configuración' - 'Gestionar perfiles de usuario'. Seleccione el perfil deseado antes de cargar el juego.",
|
||||
"fr_FR": "Cette option permet d'éviter le dialogue du 'Gérer les profils d'utilisateurs' pendant le jeu, en utilisant un profil pré-sélectionné.\n\nLa sélection du profil se trouve dans 'Paramètres' - 'Gérer les profils d'utilisateurs'. Sélectionnez le profil souhaité avant de lancer le jeu.",
|
||||
"en_US": "This option skips the 'Manage User Profiles' dialog during gameplay, using a pre-selected profile.\n\nProfile switching is found in 'Options' - 'User Profiles'. Select the desired profile before loading the game.",
|
||||
"es_ES": "Esta opción omite el diálogo de 'Gestionar perfiles de usuario' durante el juego, utilizando un perfil preseleccionado.\n\nEl cambio de perfil se encuentra en 'Opciones' - 'Perfiles de Usuario'. Seleccione el perfil deseado antes de cargar el juego.",
|
||||
"fr_FR": "Cette option permet de passer le dialogue 'Gérer les profils d'utilisateurs' pendant le jeu, en utilisant un profil pré-sélectionné.\n\nLa sélection du profil se trouve dans 'Options' - 'Profils d'Utilisateurs'. Sélectionnez le profil souhaité avant de lancer le jeu.",
|
||||
"he_IL": "",
|
||||
"it_IT": "Questa opzione salta la finestra di dialogo 'Gestisci i profili utente' durante il gioco, utilizzando un profilo pre-selezionato.\n\nIl cambio del profilo si trova in 'Impostazioni' - 'Gestisci i profili utente'. Seleziona il profilo desiderato prima di caricare il gioco.",
|
||||
"ja_JP": "このオプションは、ゲームプレイ中に「ユーザプロファイルを管理」ダイアログをスキップし、事前に選択されたプロファイルを使用します。\n\nプロファイルの切り替えは、「設定」-「ユーザプロファイルを管理」で見つけることができます。ゲームのロード前に目的のプロファイルをを選択してください。",
|
||||
|
|
@ -22520,26 +22453,26 @@
|
|||
{
|
||||
"ID": "MultiplayerModeTooltip",
|
||||
"Translations": {
|
||||
"ar_SA": "تغيير وضع LDN متعدد اللاعبين.\n\nسوف يقوم LdnMitm بتعديل وظيفة اللعب المحلية/اللاسلكية المحلية في الألعاب لتعمل كما لو كانت شبكة LAN، مما يسمح باتصالات الشبكة المحلية نفسها مع محاكيات ريوجينكس الأخرى وأجهزة نينتندو سويتش المخترقة التي تم تثبيت وحدة ldn_mitm عليها.\n\nيتطلب وضع اللاعبين المتعددين أن يكون جميع اللاعبين على نفس إصدار اللعبة (على سبيل المثال، يتعذر على الإصدار 13.0.1 من سوبر سماش برذرز ألتميت الاتصال بالإصدار 13.0.0).\n\nاتركه معطلا إذا لم تكن متأكدا.",
|
||||
"de_DE": "Ändert den LDN-Mehrspielermodus.\n\nLdnMitm ändert die lokale drahtlose/lokale Spielfunktionalität in Spielen so, dass sie wie ein LAN funktioniert und lokale, netzwerkgleiche Verbindungen mit anderen Ryujinx-Instanzen und gehackten Nintendo Switch-Konsolen ermöglicht, auf denen das ldn_mitm-Modul installiert ist.\n\nMultiplayer erfordert, dass alle Spieler die gleiche Spielversion verwenden (d.h. Super Smash Bros. Ultimate v13.0.1 kann sich nicht mit v13.0.0 verbinden).\n\nIm Zweifelsfall auf DISABLED lassen.",
|
||||
"ar_SA": "تغيير وضع LDN متعدد اللاعبين.\n\nسوف يقوم ldn_mitm بتعديل وظيفة اللعب المحلية/اللاسلكية المحلية في الألعاب لتعمل كما لو كانت شبكة LAN، مما يسمح باتصالات الشبكة المحلية نفسها مع محاكيات ريوجينكس الأخرى وأجهزة نينتندو سويتش المخترقة التي تم تثبيت وحدة ldn_mitm عليها.\n\nيتطلب وضع اللاعبين المتعددين أن يكون جميع اللاعبين على نفس إصدار اللعبة (على سبيل المثال، يتعذر على الإصدار 13.0.1 من سوبر سماش برذرز ألتميت الاتصال بالإصدار 13.0.0).\n\nاتركه معطلا إذا لم تكن متأكدا.",
|
||||
"de_DE": "Ändert den LDN-Mehrspielermodus.\n\nldn_mitm ändert die lokale drahtlose/lokale Spielfunktionalität in Spielen so, dass sie wie ein LAN funktioniert und lokale, netzwerkgleiche Verbindungen mit anderen Ryujinx-Instanzen und gehackten Nintendo Switch-Konsolen ermöglicht, auf denen das ldn_mitm-Modul installiert ist.\n\nMultiplayer erfordert, dass alle Spieler die gleiche Spielversion verwenden (d.h. Super Smash Bros. Ultimate v13.0.1 kann sich nicht mit v13.0.0 verbinden).\n\nIm Zweifelsfall auf DISABLED lassen.",
|
||||
"el_GR": "",
|
||||
"en_US": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure.",
|
||||
"es_ES": "Cambiar modo LDN multijugador.\n\nLdnMitm modificará la funcionalidad local de juego inalámbrico para funcionar como si fuera LAN, permitiendo locales conexiones de la misma red con otras instancias de Ryujinx y consolas hackeadas de Nintendo Switch que tienen instalado el módulo ldn_mitm.\n\nMultijugador requiere que todos los jugadores estén en la misma versión del juego (por ejemplo, Super Smash Bros. Ultimate v13.0.1 no se puede conectar a v13.0.0).\n\nDejar DESACTIVADO si no está seguro.",
|
||||
"fr_FR": "Change le mode multijoueur LDN.\n\nLdnMitm modifiera la fonctionnalité de jeu sans fil local/jeu local dans les jeux pour fonctionner comme s'il s'agissait d'un LAN, permettant des connexions locales sur le même réseau avec d'autres instances de Ryujinx et des consoles Nintendo Switch piratées ayant le module ldn_mitm installé.\n\nLe multijoueur nécessite que tous les joueurs soient sur la même version du jeu (par exemple, Super Smash Bros. Ultimate v13.0.1 ne peut pas se connecter à v13.0.0).\n\nLaissez DÉSACTIVÉ si vous n'êtes pas sûr.",
|
||||
"en_US": "Change LDN multiplayer mode.\n\nldn_mitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure.",
|
||||
"es_ES": "Cambiar modo LDN multijugador.\n\nldn_mitm modificará la funcionalidad local de juego inalámbrico para funcionar como si fuera LAN, permitiendo locales conexiones de la misma red con otras instancias de Ryujinx y consolas hackeadas de Nintendo Switch que tienen instalado el módulo ldn_mitm.\n\nMultijugador requiere que todos los jugadores estén en la misma versión del juego (por ejemplo, Super Smash Bros. Ultimate v13.0.1 no se puede conectar a v13.0.0).\n\nDejar DESACTIVADO si no está seguro.",
|
||||
"fr_FR": "Change le mode multijoueur LDN.\n\nldn_mitm modifiera la fonctionnalité de jeu sans fil local/jeu local dans les jeux pour fonctionner comme s'il s'agissait d'un LAN, permettant des connexions locales sur le même réseau avec d'autres instances de Ryujinx et des consoles Nintendo Switch piratées ayant le module ldn_mitm installé.\n\nLe multijoueur nécessite que tous les joueurs soient sur la même version du jeu (par exemple, Super Smash Bros. Ultimate v13.0.1 ne peut pas se connecter à v13.0.0).\n\nLaissez DÉSACTIVÉ si vous n'êtes pas sûr.",
|
||||
"he_IL": "",
|
||||
"it_IT": "Cambia la modalità multigiocatore LDN.\n\nLdnMitm modificherà la funzionalità locale wireless/local play nei giochi per funzionare come se fosse in modalità LAN, consentendo connessioni locali sulla stessa rete con altre istanze di Ryujinx e console Nintendo Switch modificate che hanno il modulo ldn_mitm installato.\n\nLa modalità multigiocatore richiede che tutti i giocatori usino la stessa versione del gioco (es. Super Smash Bros. Ultimate v13.0.1 non può connettersi con la v13.0.0).\n\nNel dubbio, lascia l'opzione su Disabilitato.",
|
||||
"it_IT": "Cambia la modalità multigiocatore LDN.\n\nldn_mitm modificherà la funzionalità locale wireless/local play nei giochi per funzionare come se fosse in modalità LAN, consentendo connessioni locali sulla stessa rete con altre istanze di Ryujinx e console Nintendo Switch modificate che hanno il modulo ldn_mitm installato.\n\nLa modalità multigiocatore richiede che tutti i giocatori usino la stessa versione del gioco (es. Super Smash Bros. Ultimate v13.0.1 non può connettersi con la v13.0.0).\n\nNel dubbio, lascia l'opzione su Disabilitato.",
|
||||
"ja_JP": "LDNマルチプレイヤーモードを変更します.\n\nldn_mitmモジュールがインストールされた, 他のRyujinxインスタンスや,ハックされたNintendo Switchコンソールとのローカル/同一ネットワーク接続を可能にします.\n\nマルチプレイでは, すべてのプレイヤーが同じゲームバージョンである必要があります(例:Super Smash Bros. Ultimate v13.0.1はv13.0.0に接続できません).\n\n不明な場合は「無効」のままにしてください.",
|
||||
"ko_KR": "LDN 멀티플레이어 모드를 변경합니다.\n\nLdnMitm은 게임의 로컬 무선/로컬 플레이 기능을 LAN처럼 작동하도록 수정하여 다른 Ryujinx 인스턴스나 ldn_mitm 모듈이 설치된 해킹된 Nintendo Switch 콘솔과 로컬, 동일 네트워크 연결이 가능합니다.\n\n멀티플레이어는 모든 플레이어가 동일한 게임 버전을 사용해야 합니다(예 : 슈퍼 스매시브라더스 얼티밋 v13.0.1은 v13.0.0에 연결할 수 없음).\n\n모르면 비활성화 상태로 두세요.",
|
||||
"no_NO": "Endre LDN flerspillermodus.\n\nLdnMitm vil endre lokal trådløst/lokal spillfunksjonalitet i spill som skal fungere som om den var LAN, noe som tillater lokal, samme nettverk forbindelser med andre Ryujinx instanser og hacket Nintendo Switch konsoller som har installert ldn_mitm-modulen.\n\nFlerspiller krever at alle spillerne er på samme versjon (dvs. Super Smash Bros. Ultimat v13.0.1 kan ikke koble til v13.0.0).\n\nForlat DEAKTIVERT hvis usikker.",
|
||||
"ko_KR": "LDN 멀티플레이어 모드를 변경합니다.\n\nldn_mitm은 게임의 로컬 무선/로컬 플레이 기능을 LAN처럼 작동하도록 수정하여 다른 Ryujinx 인스턴스나 ldn_mitm 모듈이 설치된 해킹된 Nintendo Switch 콘솔과 로컬, 동일 네트워크 연결이 가능합니다.\n\n멀티플레이어는 모든 플레이어가 동일한 게임 버전을 사용해야 합니다(예 : 슈퍼 스매시브라더스 얼티밋 v13.0.1은 v13.0.0에 연결할 수 없음).\n\n모르면 비활성화 상태로 두세요.",
|
||||
"no_NO": "Endre LDN flerspillermodus.\n\nldn_mitm vil endre lokal trådløst/lokal spillfunksjonalitet i spill som skal fungere som om den var LAN, noe som tillater lokal, samme nettverk forbindelser med andre Ryujinx instanser og hacket Nintendo Switch konsoller som har installert ldn_mitm-modulen.\n\nFlerspiller krever at alle spillerne er på samme versjon (dvs. Super Smash Bros. Ultimat v13.0.1 kan ikke koble til v13.0.0).\n\nForlat DEAKTIVERT hvis usikker.",
|
||||
"pl_PL": "",
|
||||
"pt_BR": "Alterar o modo multiplayer LDN.\n\nLdnMitm modificará a funcionalidade de jogo sem fio/local nos jogos para funcionar como se fosse LAN, permitindo conexões locais, na mesma rede, com outras instâncias do Ryujinx e consoles Nintendo Switch hackeados que possuem o módulo ldn_mitm instalado.\n\nO multiplayer exige que todos os jogadores estejam na mesma versão do jogo (ex.: Super Smash Bros. Ultimate v13.0.1 não consegue se conectar à v13.0.0).\n\nDeixe DESATIVADO se estiver em dúvida.",
|
||||
"ru_RU": "Меняет многопользовательский режим LDN.\n\nLdnMitm модифицирует функциональность локальной беспроводной игры на одном устройстве в играх, позволяя играть с другими пользователями Ryujinx или взломанными консолями Nintendo Switch с установленным модулем ldn_mitm, находящимися в одной локальной сети друг с другом.\n\nМногопользовательская игра требует наличия у всех игроков одной и той же версии игры (т.е. Super Smash Bros. Ultimate v13.0.1 не может подключиться к v13.0.0).\n\nРекомендуется оставить выключенным.",
|
||||
"sv_SE": "Ändra LDN-flerspelarläge\n\nLdnMitm kommer att ändra lokal funktionalitet för trådlös/lokalt spel att fungera som om det vore ett LAN, vilket ger stöd för anslutningar med local och same-network med andra Ryujinx-instanser och hackade Nintendo Switch-konsoller som har modulen ldn_mitm installerad.\n\nFlerspelare kräver att alla spelare har samma spelversion (t.ex. Super Smash Bros. Ultimate v13.0.1 kan inte ansluta till v13.0.0).\n\nLämna INAKTIVERAD om du är osäker.",
|
||||
"th_TH": "เปลี่ยนโหมดผู้เล่นหลายคนของ LDN\n\nLdnMitm จะปรับเปลี่ยนฟังก์ชันการเล่นแบบไร้สาย/ภายใน จะให้เกมทำงานเหมือนกับว่าเป็น LAN ช่วยให้สามารถเชื่อมต่อภายในเครือข่ายเดียวกันกับอินสแตนซ์ Ryujinx อื่น ๆ และคอนโซล Nintendo Switch ที่ถูกแฮ็กซึ่งมีโมดูล ldn_mitm ติดตั้งอยู่\n\nผู้เล่นหลายคนต้องการให้ผู้เล่นทุกคนอยู่ในเกมเวอร์ชันเดียวกัน (เช่น Super Smash Bros. Ultimate v13.0.1 ไม่สามารถเชื่อมต่อกับ v13.0.0)\n\nปล่อยให้ปิดการใช้งานหากไม่แน่ใจ",
|
||||
"pt_BR": "Alterar o modo multiplayer LDN.\n\nldn_mitm modificará a funcionalidade de jogo sem fio/local nos jogos para funcionar como se fosse LAN, permitindo conexões locais, na mesma rede, com outras instâncias do Ryujinx e consoles Nintendo Switch hackeados que possuem o módulo ldn_mitm instalado.\n\nO multiplayer exige que todos os jogadores estejam na mesma versão do jogo (ex.: Super Smash Bros. Ultimate v13.0.1 não consegue se conectar à v13.0.0).\n\nDeixe DESATIVADO se estiver em dúvida.",
|
||||
"ru_RU": "Меняет многопользовательский режим LDN.\n\nldn_mitm модифицирует функциональность локальной беспроводной игры на одном устройстве в играх, позволяя играть с другими пользователями Ryujinx или взломанными консолями Nintendo Switch с установленным модулем ldn_mitm, находящимися в одной локальной сети друг с другом.\n\nМногопользовательская игра требует наличия у всех игроков одной и той же версии игры (т.е. Super Smash Bros. Ultimate v13.0.1 не может подключиться к v13.0.0).\n\nРекомендуется оставить выключенным.",
|
||||
"sv_SE": "Ändra LDN-flerspelarläge\n\nldn_mitm kommer att ändra lokal funktionalitet för trådlös/lokalt spel att fungera som om det vore ett LAN, vilket ger stöd för anslutningar med local och same-network med andra Ryujinx-instanser och hackade Nintendo Switch-konsoller som har modulen ldn_mitm installerad.\n\nFlerspelare kräver att alla spelare har samma spelversion (t.ex. Super Smash Bros. Ultimate v13.0.1 kan inte ansluta till v13.0.0).\n\nLämna INAKTIVERAD om du är osäker.",
|
||||
"th_TH": "เปลี่ยนโหมดผู้เล่นหลายคนของ LDN\n\nldn_mitm จะปรับเปลี่ยนฟังก์ชันการเล่นแบบไร้สาย/ภายใน จะให้เกมทำงานเหมือนกับว่าเป็น LAN ช่วยให้สามารถเชื่อมต่อภายในเครือข่ายเดียวกันกับอินสแตนซ์ Ryujinx อื่น ๆ และคอนโซล Nintendo Switch ที่ถูกแฮ็กซึ่งมีโมดูล ldn_mitm ติดตั้งอยู่\n\nผู้เล่นหลายคนต้องการให้ผู้เล่นทุกคนอยู่ในเกมเวอร์ชันเดียวกัน (เช่น Super Smash Bros. Ultimate v13.0.1 ไม่สามารถเชื่อมต่อกับ v13.0.0)\n\nปล่อยให้ปิดการใช้งานหากไม่แน่ใจ",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Змінити LDN мультиплеєру.\n\nLdnMitm змінить функціонал бездротової/локальної гри в іграх, щоб вони працювали так, ніби це LAN, що дозволяє локальні підключення в тій самій мережі з іншими екземплярами Ryujinx та хакнутими консолями Nintendo Switch, які мають встановлений модуль ldn_mitm.\n\nМультиплеєр вимагає, щоб усі гравці були на одній і тій же версії гри (наприклад Super Smash Bros. Ultimate v13.0.1 не зможе під'єднатися до v13.0.0).\n\nЗалиште на \"Вимкнено\", якщо не впевнені.",
|
||||
"uk_UA": "Змінити LDN мультиплеєру.\n\nldn_mitm змінить функціонал бездротової/локальної гри в іграх, щоб вони працювали так, ніби це LAN, що дозволяє локальні підключення в тій самій мережі з іншими екземплярами Ryujinx та хакнутими консолями Nintendo Switch, які мають встановлений модуль ldn_mitm.\n\nМультиплеєр вимагає, щоб усі гравці були на одній і тій же версії гри (наприклад Super Smash Bros. Ultimate v13.0.1 не зможе під'єднатися до v13.0.0).\n\nЗалиште на \"Вимкнено\", якщо не впевнені.",
|
||||
"zh_CN": "修改 LDN 多人联机游玩模式。\n\nldn_mitm 联机插件将修改游戏中的本地无线和本地游玩功能,使其表现得像局域网一样,允许和其他安装了 ldn_mitm 插件的 Ryujinx 模拟器和破解的任天堂 Switch 主机在同一网络下进行本地连接,实现多人联机游玩。\n\n多人联机游玩要求所有玩家必须运行相同的游戏版本(例如,游戏版本 v13.0.1 无法与 v13.0.0 联机)。\n\n如果不确定,请保持为“禁用”。",
|
||||
"zh_TW": "變更 LDN 多人遊戲模式。\n\nLdnMitm 將修改遊戲中的本機無線/本機遊戲功能,使其如同區域網路一樣執行,允許與其他安裝了 ldn_mitm 模組的 Ryujinx 實例和已破解的 Nintendo Switch 遊戲機進行本機同網路連線。\n\n多人遊戲要求所有玩家使用相同的遊戲版本 (例如,Super Smash Bros. Ultimate v13.0.1 無法連接 v13.0.0)。\n\n如果不確定,請保持 Disabled (停用) 狀態。"
|
||||
"zh_TW": "變更 LDN 多人遊戲模式。\n\nldn_mitm 將修改遊戲中的本機無線/本機遊戲功能,使其如同區域網路一樣執行,允許與其他安裝了 ldn_mitm 模組的 Ryujinx 實例和已破解的 Nintendo Switch 遊戲機進行本機同網路連線。\n\n多人遊戲要求所有玩家使用相同的遊戲版本 (例如,Super Smash Bros. Ultimate v13.0.1 無法連接 v13.0.0)。\n\n如果不確定,請保持 Disabled (停用) 狀態。"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -22800,7 +22733,7 @@
|
|||
"el_GR": "",
|
||||
"en_US": "Generates a new passphrase, which can be shared with other players.",
|
||||
"es_ES": "Genera una nueva frase de contraseña, que puede ser compartida con otros jugadores.",
|
||||
"fr_FR": "Génére un nouveau mot de passe, qui peut être partagé avec les autres.",
|
||||
"fr_FR": "Génère un nouveau mot de passe, qui peut être partagé avec les autres.",
|
||||
"he_IL": "",
|
||||
"it_IT": "Genera una nuova passphrase, che può essere condivisa con altri giocatori.",
|
||||
"ja_JP": "",
|
||||
|
|
@ -1,124 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
if [ "$#" -lt 8 ]; then
|
||||
echo "usage <BASE_DIR> <TEMP_DIRECTORY> <OUTPUT_DIRECTORY> <ENTITLEMENTS_FILE_PATH> <VERSION> <SOURCE_REVISION_ID> <CONFIGURATION> <CANARY>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$1"
|
||||
mkdir -p "$2"
|
||||
mkdir -p "$3"
|
||||
|
||||
BASE_DIR=$(readlink -f "$1")
|
||||
TEMP_DIRECTORY=$(readlink -f "$2")
|
||||
OUTPUT_DIRECTORY=$(readlink -f "$3")
|
||||
ENTITLEMENTS_FILE_PATH=$(readlink -f "$4")
|
||||
VERSION=$5
|
||||
SOURCE_REVISION_ID=$6
|
||||
CONFIGURATION=$7
|
||||
CANARY=$8
|
||||
|
||||
if [[ "$(uname)" == "Darwin" ]]; then
|
||||
echo "Clearing xattr on all dot undercsore files"
|
||||
find "$BASE_DIR" -type f -name "._*" -exec sh -c '
|
||||
for f; do
|
||||
dir=$(dirname "$f")
|
||||
base=$(basename "$f")
|
||||
orig="$dir/${base#._}"
|
||||
[ -f "$orig" ] && xattr -c "$orig" || true
|
||||
done
|
||||
' sh {} +
|
||||
fi
|
||||
|
||||
if [ "$CANARY" == "1" ]; then
|
||||
RELEASE_TAR_FILE_NAME=nogui-ryujinx-canary-$VERSION-macos_universal.tar
|
||||
elif [ "$VERSION" == "1.1.0" ]; then
|
||||
RELEASE_TAR_FILE_NAME=nogui-ryujinx-$CONFIGURATION-$VERSION+$SOURCE_REVISION_ID-macos_universal.tar
|
||||
else
|
||||
RELEASE_TAR_FILE_NAME=nogui-ryujinx-$VERSION-macos_universal.tar
|
||||
fi
|
||||
|
||||
ARM64_OUTPUT="$TEMP_DIRECTORY/publish_arm64"
|
||||
X64_OUTPUT="$TEMP_DIRECTORY/publish_x64"
|
||||
UNIVERSAL_OUTPUT="$OUTPUT_DIRECTORY/publish"
|
||||
EXECUTABLE_SUB_PATH=Ryujinx.Headless.SDL3
|
||||
|
||||
rm -rf "$TEMP_DIRECTORY"
|
||||
mkdir -p "$TEMP_DIRECTORY"
|
||||
|
||||
DOTNET_COMMON_ARGS=(-p:DebugType=embedded -p:Version="$VERSION" -p:SourceRevisionId="$SOURCE_REVISION_ID" --self-contained true $EXTRA_ARGS)
|
||||
|
||||
dotnet restore
|
||||
dotnet build -c "$CONFIGURATION" src/Ryujinx.Headless.SDL3
|
||||
dotnet publish -c "$CONFIGURATION" -r osx-arm64 -o "$TEMP_DIRECTORY/publish_arm64" "${DOTNET_COMMON_ARGS[@]}" src/Ryujinx.Headless.SDL3
|
||||
dotnet publish -c "$CONFIGURATION" -r osx-x64 -o "$TEMP_DIRECTORY/publish_x64" "${DOTNET_COMMON_ARGS[@]}" src/Ryujinx.Headless.SDL3
|
||||
|
||||
# Get rid of the support library for ARMeilleure for x64 (that's only for arm64)
|
||||
rm -rf "$TEMP_DIRECTORY/publish_x64/libarmeilleure-jitsupport.dylib"
|
||||
|
||||
# Get rid of libsoundio from arm64 builds as we don't have a arm64 variant
|
||||
# TODO: remove this once done
|
||||
rm -rf "$TEMP_DIRECTORY/publish_arm64/libsoundio.dylib"
|
||||
|
||||
rm -rf "$OUTPUT_DIRECTORY"
|
||||
mkdir -p "$OUTPUT_DIRECTORY"
|
||||
|
||||
# Let's copy one of the two different outputs and remove the executable
|
||||
cp -R "$ARM64_OUTPUT/" "$UNIVERSAL_OUTPUT"
|
||||
rm "$UNIVERSAL_OUTPUT/$EXECUTABLE_SUB_PATH"
|
||||
|
||||
# Make its libraries universal
|
||||
python3 "$BASE_DIR/distribution/macos/construct_universal_dylib.py" "$ARM64_OUTPUT" "$X64_OUTPUT" "$UNIVERSAL_OUTPUT" "**/*.dylib"
|
||||
|
||||
if ! [ -x "$(command -v lipo)" ];
|
||||
then
|
||||
if ! [ -x "$(command -v llvm-lipo-17)" ];
|
||||
then
|
||||
LIPO=llvm-lipo
|
||||
else
|
||||
LIPO=llvm-lipo-17
|
||||
fi
|
||||
else
|
||||
LIPO=lipo
|
||||
fi
|
||||
|
||||
# Make the executable universal
|
||||
$LIPO "$ARM64_OUTPUT/$EXECUTABLE_SUB_PATH" "$X64_OUTPUT/$EXECUTABLE_SUB_PATH" -output "$UNIVERSAL_OUTPUT/$EXECUTABLE_SUB_PATH" -create
|
||||
|
||||
# Now sign it
|
||||
if ! [ -x "$(command -v codesign)" ];
|
||||
then
|
||||
if ! [ -x "$(command -v rcodesign)" ];
|
||||
then
|
||||
echo "Cannot find rcodesign on your system, please install rcodesign."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# NOTE: Currently require https://github.com/indygreg/apple-platform-rs/pull/44 to work on other OSes.
|
||||
# cargo install --git "https://github.com/marysaka/apple-platform-rs" --branch "fix/adhoc-app-bundle" apple-codesign --bin "rcodesign"
|
||||
echo "Using rcodesign for ad-hoc signing"
|
||||
for FILE in "$UNIVERSAL_OUTPUT"/*; do
|
||||
if [[ $(file "$FILE") == *"Mach-O"* ]]; then
|
||||
rcodesign sign --entitlements-xml-path "$ENTITLEMENTS_FILE_PATH" "$FILE"
|
||||
fi
|
||||
done
|
||||
else
|
||||
echo "Using codesign for ad-hoc signing"
|
||||
for FILE in "$UNIVERSAL_OUTPUT"/*; do
|
||||
if [[ $(file "$FILE") == *"Mach-O"* ]]; then
|
||||
codesign --entitlements "$ENTITLEMENTS_FILE_PATH" -f -s - "$FILE"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
echo "Creating archive"
|
||||
pushd "$OUTPUT_DIRECTORY"
|
||||
tar --exclude "publish/Ryujinx.Headless.SDL3" -cvf "$RELEASE_TAR_FILE_NAME" publish 1> /dev/null
|
||||
python3 "$BASE_DIR/distribution/misc/add_tar_exec.py" "$RELEASE_TAR_FILE_NAME" "publish/Ryujinx.Headless.SDL3" "publish/Ryujinx.Headless.SDL3"
|
||||
gzip -9 < "$RELEASE_TAR_FILE_NAME" > "$RELEASE_TAR_FILE_NAME.gz"
|
||||
rm "$RELEASE_TAR_FILE_NAME"
|
||||
popd
|
||||
|
||||
echo "Done"
|
||||
|
|
@ -19,7 +19,7 @@ namespace ARMeilleure.Instructions
|
|||
|
||||
context.LoadFromContext();
|
||||
|
||||
context.Return(Const(op.Address));
|
||||
InstEmitFlowHelper.EmitReturn(context, Const(op.Address));
|
||||
}
|
||||
|
||||
public static void Svc(ArmEmitterContext context)
|
||||
|
|
@ -49,7 +49,7 @@ namespace ARMeilleure.Instructions
|
|||
|
||||
context.LoadFromContext();
|
||||
|
||||
context.Return(Const(op.Address));
|
||||
InstEmitFlowHelper.EmitReturn(context, Const(op.Address));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ namespace ARMeilleure.Instructions
|
|||
|
||||
context.LoadFromContext();
|
||||
|
||||
context.Return(Const(context.CurrOp.Address));
|
||||
InstEmitFlowHelper.EmitReturn(context, Const(context.CurrOp.Address));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ namespace ARMeilleure.Instructions
|
|||
{
|
||||
OpCodeBReg op = (OpCodeBReg)context.CurrOp;
|
||||
|
||||
context.Return(GetIntOrZR(context, op.Rn));
|
||||
EmitReturn(context, GetIntOrZR(context, op.Rn));
|
||||
}
|
||||
|
||||
public static void Tbnz(ArmEmitterContext context) => EmitTb(context, onNotZero: true);
|
||||
|
|
|
|||
|
|
@ -13,6 +13,10 @@ namespace ARMeilleure.Instructions
|
|||
{
|
||||
static class InstEmitFlowHelper
|
||||
{
|
||||
// How many calls we can have in our call stack before we give up and return to the dispatcher.
|
||||
// This prevents stack overflows caused by deep recursive calls.
|
||||
private const int MaxCallDepth = 200;
|
||||
|
||||
public static void EmitCondBranch(ArmEmitterContext context, Operand target, Condition cond)
|
||||
{
|
||||
if (cond != Condition.Al)
|
||||
|
|
@ -182,12 +186,7 @@ namespace ARMeilleure.Instructions
|
|||
{
|
||||
if (isReturn || context.IsSingleStep)
|
||||
{
|
||||
if (target.Type == OperandType.I32)
|
||||
{
|
||||
target = context.ZeroExtend32(OperandType.I64, target);
|
||||
}
|
||||
|
||||
context.Return(target);
|
||||
EmitReturn(context, target);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -195,6 +194,19 @@ namespace ARMeilleure.Instructions
|
|||
}
|
||||
}
|
||||
|
||||
public static void EmitReturn(ArmEmitterContext context, Operand target)
|
||||
{
|
||||
Operand nativeContext = context.LoadArgument(OperandType.I64, 0);
|
||||
DecreaseCallDepth(context, nativeContext);
|
||||
|
||||
if (target.Type == OperandType.I32)
|
||||
{
|
||||
target = context.ZeroExtend32(OperandType.I64, target);
|
||||
}
|
||||
|
||||
context.Return(target);
|
||||
}
|
||||
|
||||
private static void EmitTableBranch(ArmEmitterContext context, Operand guestAddress, bool isJump)
|
||||
{
|
||||
context.StoreToContext();
|
||||
|
|
@ -257,6 +269,8 @@ namespace ARMeilleure.Instructions
|
|||
|
||||
if (isJump)
|
||||
{
|
||||
DecreaseCallDepth(context, nativeContext);
|
||||
|
||||
context.Tailcall(hostAddress, nativeContext);
|
||||
}
|
||||
else
|
||||
|
|
@ -278,8 +292,42 @@ namespace ARMeilleure.Instructions
|
|||
Operand lblContinue = context.GetLabel(nextAddr.Value);
|
||||
context.BranchIf(lblContinue, returnAddress, nextAddr, Comparison.Equal, BasicBlockFrequency.Cold);
|
||||
|
||||
DecreaseCallDepth(context, nativeContext);
|
||||
|
||||
context.Return(returnAddress);
|
||||
}
|
||||
}
|
||||
|
||||
public static void EmitCallDepthCheckAndIncrement(EmitterContext context, Operand guestAddress)
|
||||
{
|
||||
if (!Optimizations.EnableDeepCallRecursionProtection)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Operand nativeContext = context.LoadArgument(OperandType.I64, 0);
|
||||
Operand callDepthAddr = context.Add(nativeContext, Const((ulong)NativeContext.GetCallDepthOffset()));
|
||||
Operand currentCallDepth = context.Load(OperandType.I32, callDepthAddr);
|
||||
Operand lblDoCall = Label();
|
||||
|
||||
context.BranchIf(lblDoCall, currentCallDepth, Const(MaxCallDepth), Comparison.LessUI);
|
||||
context.Store(callDepthAddr, context.Subtract(currentCallDepth, Const(1)));
|
||||
context.Return(guestAddress);
|
||||
|
||||
context.MarkLabel(lblDoCall);
|
||||
context.Store(callDepthAddr, context.Add(currentCallDepth, Const(1)));
|
||||
}
|
||||
|
||||
private static void DecreaseCallDepth(EmitterContext context, Operand nativeContext)
|
||||
{
|
||||
if (!Optimizations.EnableDeepCallRecursionProtection)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Operand callDepthAddr = context.Add(nativeContext, Const((ulong)NativeContext.GetCallDepthOffset()));
|
||||
Operand currentCallDepth = context.Load(OperandType.I32, callDepthAddr);
|
||||
context.Store(callDepthAddr, context.Subtract(currentCallDepth, Const(1)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ namespace ARMeilleure
|
|||
public static bool AllowLcqInFunctionTable { get; set; } = true;
|
||||
public static bool UseUnmanagedDispatchLoop { get; set; } = true;
|
||||
public static bool EnableDebugging { get; set; } = false;
|
||||
public static bool EnableDeepCallRecursionProtection { get; set; } = true;
|
||||
|
||||
public static bool UseAdvSimdIfAvailable { get; set; } = true;
|
||||
public static bool UseArm64AesIfAvailable { get; set; } = true;
|
||||
|
|
|
|||
|
|
@ -134,6 +134,11 @@ namespace ARMeilleure.State
|
|||
public bool GetFPstateFlag(FPState flag) => _nativeContext.GetFPStateFlag(flag);
|
||||
public void SetFPstateFlag(FPState flag, bool value) => _nativeContext.SetFPStateFlag(flag, value);
|
||||
|
||||
internal void ResetCallDepth()
|
||||
{
|
||||
_nativeContext.ResetCallDepth();
|
||||
}
|
||||
|
||||
internal void CheckInterrupt()
|
||||
{
|
||||
if (Interrupted)
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ namespace ARMeilleure.State
|
|||
public ulong ExclusiveValueHigh;
|
||||
public int Running;
|
||||
public long Tpidr2El0;
|
||||
public int CallDepth;
|
||||
|
||||
/// <summary>
|
||||
/// Precise PC value used for debugging.
|
||||
|
|
@ -199,6 +200,8 @@ namespace ARMeilleure.State
|
|||
public bool GetRunning() => GetStorage().Running != 0;
|
||||
public void SetRunning(bool value) => GetStorage().Running = value ? 1 : 0;
|
||||
|
||||
public void ResetCallDepth() => GetStorage().CallDepth = 0;
|
||||
|
||||
public unsafe static int GetRegisterOffset(Register reg)
|
||||
{
|
||||
if (reg.Type == RegisterType.Integer)
|
||||
|
|
@ -284,6 +287,11 @@ namespace ARMeilleure.State
|
|||
return StorageOffset(ref _dummyStorage, ref _dummyStorage.DebugPrecisePc);
|
||||
}
|
||||
|
||||
public static int GetCallDepthOffset()
|
||||
{
|
||||
return StorageOffset(ref _dummyStorage, ref _dummyStorage.CallDepth);
|
||||
}
|
||||
|
||||
private static int StorageOffset<T>(ref NativeCtxStorage storage, ref T target)
|
||||
{
|
||||
return (int)Unsafe.ByteOffset(ref Unsafe.As<NativeCtxStorage, T>(ref storage), ref target);
|
||||
|
|
|
|||
|
|
@ -361,10 +361,7 @@ namespace ARMeilleure.Translation
|
|||
|
||||
IntervalTreeNode<TK, TV> tmp = LeftOf(replacementNode) ?? RightOf(replacementNode);
|
||||
|
||||
if (tmp != null)
|
||||
{
|
||||
tmp.Parent = ParentOf(replacementNode);
|
||||
}
|
||||
tmp?.Parent = ParentOf(replacementNode);
|
||||
|
||||
if (ParentOf(replacementNode) == null)
|
||||
{
|
||||
|
|
@ -582,10 +579,7 @@ namespace ARMeilleure.Translation
|
|||
{
|
||||
IntervalTreeNode<TK, TV> right = RightOf(node);
|
||||
node.Right = LeftOf(right);
|
||||
if (node.Right != null)
|
||||
{
|
||||
node.Right.Parent = node;
|
||||
}
|
||||
node.Right?.Parent = node;
|
||||
|
||||
IntervalTreeNode<TK, TV> nodeParent = ParentOf(node);
|
||||
right.Parent = nodeParent;
|
||||
|
|
@ -615,10 +609,7 @@ namespace ARMeilleure.Translation
|
|||
{
|
||||
IntervalTreeNode<TK, TV> left = LeftOf(node);
|
||||
node.Left = RightOf(left);
|
||||
if (node.Left != null)
|
||||
{
|
||||
node.Left.Parent = node;
|
||||
}
|
||||
node.Left?.Parent = node;
|
||||
|
||||
IntervalTreeNode<TK, TV> nodeParent = ParentOf(node);
|
||||
left.Parent = nodeParent;
|
||||
|
|
@ -667,10 +658,7 @@ namespace ARMeilleure.Translation
|
|||
/// <param name="color">Color (Boolean)</param>
|
||||
private static void SetColor(IntervalTreeNode<TK, TV> node, bool color)
|
||||
{
|
||||
if (node != null)
|
||||
{
|
||||
node.Color = color;
|
||||
}
|
||||
node?.Color = color;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ namespace ARMeilleure.Translation.PTC
|
|||
private const string OuterHeaderMagicString = "PTCohd\0\0";
|
||||
private const string InnerHeaderMagicString = "PTCihd\0\0";
|
||||
|
||||
private const uint InternalVersion = 7009; //! To be incremented manually for each change to the ARMeilleure project.
|
||||
private const uint InternalVersion = 7010; //! To be incremented manually for each change to the ARMeilleure project.
|
||||
|
||||
private const string ActualDir = "0";
|
||||
private const string BackupDir = "1";
|
||||
|
|
|
|||
|
|
@ -186,6 +186,7 @@ namespace ARMeilleure.Translation
|
|||
|
||||
Statistics.StartTimer();
|
||||
|
||||
context.ResetCallDepth();
|
||||
ulong nextAddr = func.Execute(Stubs.ContextWrapper, context);
|
||||
|
||||
Statistics.StopTimer(address);
|
||||
|
|
@ -260,6 +261,7 @@ namespace ARMeilleure.Translation
|
|||
|
||||
Logger.StartPass(PassName.Translation);
|
||||
|
||||
InstEmitFlowHelper.EmitCallDepthCheckAndIncrement(context, Const(address));
|
||||
EmitSynchronization(context);
|
||||
|
||||
if (blocks[0].Address != address)
|
||||
|
|
|
|||
|
|
@ -262,10 +262,18 @@ namespace ARMeilleure.Translation
|
|||
|
||||
Operand runningAddress = context.Add(nativeContext, Const((ulong)NativeContext.GetRunningOffset()));
|
||||
Operand dispatchAddress = context.Add(nativeContext, Const((ulong)NativeContext.GetDispatchAddressOffset()));
|
||||
Operand callDepthAddress = context.Add(nativeContext, Const((ulong)NativeContext.GetCallDepthOffset()));
|
||||
|
||||
EmitSyncFpContext(context, nativeContext, true);
|
||||
|
||||
context.MarkLabel(beginLbl);
|
||||
|
||||
if (Optimizations.EnableDeepCallRecursionProtection)
|
||||
{
|
||||
// Reset the call depth counter, since this is our first guest function call.
|
||||
context.Store(callDepthAddress, Const(0));
|
||||
}
|
||||
|
||||
context.Store(dispatchAddress, guestAddress);
|
||||
context.Copy(guestAddress, context.Call(Const((ulong)DispatchStub), OperandType.I64, nativeContext));
|
||||
context.BranchIfFalse(endLbl, guestAddress);
|
||||
|
|
|
|||
|
|
@ -19,6 +19,11 @@ namespace Ryujinx.Audio.Renderer.Parameter.Effect
|
|||
/// The output channel indices that will be used by the <see cref="Dsp.AudioProcessor"/>.
|
||||
/// </summary>
|
||||
public Array6<byte> Output;
|
||||
|
||||
/// <summary>
|
||||
/// Reserved/unused.
|
||||
/// </summary>
|
||||
private readonly uint _padding;
|
||||
|
||||
/// <summary>
|
||||
/// Biquad filter numerator (b0, b1, b2).
|
||||
|
|
|
|||
|
|
@ -11,9 +11,7 @@ namespace Ryujinx.BuildValidationTasks
|
|||
{
|
||||
static readonly JsonSerializerOptions _jsonOptions = new()
|
||||
{
|
||||
WriteIndented = true,
|
||||
NewLine = "\n",
|
||||
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
|
||||
WriteIndented = true, NewLine = "\n", Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
|
||||
};
|
||||
|
||||
public LocalesValidationTask() { }
|
||||
|
|
@ -22,77 +20,116 @@ namespace Ryujinx.BuildValidationTasks
|
|||
{
|
||||
Console.WriteLine("Running Locale Validation Task...");
|
||||
|
||||
string path = projectPath + "assets/locales.json";
|
||||
bool encounteredIssue = false;
|
||||
string langPath = projectPath + "assets/Languages.json";
|
||||
string data;
|
||||
|
||||
using (StreamReader sr = new(path))
|
||||
using (StreamReader sr = new(langPath))
|
||||
{
|
||||
data = sr.ReadToEnd();
|
||||
}
|
||||
|
||||
LocalesJson json;
|
||||
|
||||
if (isGitRunner && data.Contains("\r\n"))
|
||||
throw new FormatException("locales.json is using CRLF line endings! It should be using LF line endings, rebuild locally to fix...");
|
||||
throw new FormatException("Languages.json is using CRLF line endings! It should be using LF line endings, rebuild locally to fix...");
|
||||
|
||||
LanguagesJson langJson;
|
||||
|
||||
try
|
||||
{
|
||||
json = JsonSerializer.Deserialize<LocalesJson>(data);
|
||||
|
||||
langJson = JsonSerializer.Deserialize<LanguagesJson>(data);
|
||||
}
|
||||
catch (JsonException e)
|
||||
{
|
||||
throw new JsonException(e.Message); //shorter and easier stacktrace
|
||||
}
|
||||
|
||||
bool encounteredIssue = false;
|
||||
|
||||
for (int i = 0; i < json.Locales.Count; i++)
|
||||
foreach ((string code, string lang) in langJson.Languages)
|
||||
{
|
||||
LocalesEntry locale = json.Locales[i];
|
||||
|
||||
foreach (string langCode in json.Languages.Where(lang => !locale.Translations.ContainsKey(lang)))
|
||||
if (string.IsNullOrEmpty(lang))
|
||||
{
|
||||
encounteredIssue = true;
|
||||
|
||||
if (!isGitRunner)
|
||||
{
|
||||
locale.Translations.Add(langCode, string.Empty);
|
||||
Console.WriteLine($"Added '{langCode}' to Locale '{locale.ID}'");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"Missing '{langCode}' in Locale '{locale.ID}'!");
|
||||
}
|
||||
throw new JsonException($"{code} language name missing!");
|
||||
}
|
||||
|
||||
foreach (string langCode in json.Languages.Where(lang => locale.Translations.ContainsKey(lang) && lang != "en_US" && locale.Translations[lang] == locale.Translations["en_US"]))
|
||||
{
|
||||
encounteredIssue = true;
|
||||
|
||||
if (!isGitRunner)
|
||||
{
|
||||
locale.Translations[langCode] = string.Empty;
|
||||
Console.WriteLine($"Lanugage '{langCode}' is a duplicate of en_US in Locale '{locale.ID}'! Resetting it...");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"Lanugage '{langCode}' is a duplicate of en_US in Locale '{locale.ID}'!");
|
||||
}
|
||||
}
|
||||
|
||||
locale.Translations = locale.Translations.OrderBy(pair => pair.Key).ToDictionary(pair => pair.Key, pair => pair.Value);
|
||||
json.Locales[i] = locale;
|
||||
}
|
||||
|
||||
if (isGitRunner && encounteredIssue)
|
||||
throw new JsonException("1 or more locales are invalid! Rebuild locally to fix...");
|
||||
string folderPath = projectPath + "assets/Locales/";
|
||||
|
||||
string jsonString = JsonSerializer.Serialize(json, _jsonOptions);
|
||||
string[] paths = Directory.GetFiles(folderPath, "*.json", SearchOption.AllDirectories);
|
||||
|
||||
using (StreamWriter sw = new(path))
|
||||
foreach (string path in paths)
|
||||
{
|
||||
sw.Write(jsonString);
|
||||
using (StreamReader sr = new(path))
|
||||
{
|
||||
data = sr.ReadToEnd();
|
||||
}
|
||||
|
||||
if (isGitRunner && data.Contains("\r\n"))
|
||||
throw new FormatException($"{Path.GetFileName(path)} is using CRLF line endings! It should be using LF line endings, rebuild locally to fix...");
|
||||
|
||||
LocalesJson json;
|
||||
|
||||
try
|
||||
{
|
||||
json = JsonSerializer.Deserialize<LocalesJson>(data);
|
||||
}
|
||||
catch (JsonException e)
|
||||
{
|
||||
throw new JsonException(e.Message); //shorter and easier stacktrace
|
||||
}
|
||||
|
||||
|
||||
for (int i = 0; i < json.Locales.Count; i++)
|
||||
{
|
||||
LocalesEntry locale = json.Locales[i];
|
||||
|
||||
foreach (string langCode in
|
||||
langJson.Languages.Keys.Where(lang => !locale.Translations.ContainsKey(lang)))
|
||||
{
|
||||
encounteredIssue = true;
|
||||
|
||||
if (!isGitRunner)
|
||||
{
|
||||
locale.Translations.Add(langCode, string.Empty);
|
||||
Console.WriteLine($"Added '{langCode}' to Locale '{locale.ID}'");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"Missing '{langCode}' in Locale '{locale.ID}'!");
|
||||
}
|
||||
}
|
||||
|
||||
foreach (string langCode in langJson.Languages.Keys.Where(lang =>
|
||||
locale.Translations.ContainsKey(lang) && lang != "en_US" &&
|
||||
locale.Translations[lang] == locale.Translations["en_US"]))
|
||||
{
|
||||
encounteredIssue = true;
|
||||
|
||||
if (!isGitRunner)
|
||||
{
|
||||
locale.Translations[langCode] = string.Empty;
|
||||
Console.WriteLine(
|
||||
$"Lanugage '{langCode}' is a duplicate of en_US in Locale '{locale.ID}'! Resetting it...");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine(
|
||||
$"Lanugage '{langCode}' is a duplicate of en_US in Locale '{locale.ID}'!");
|
||||
}
|
||||
}
|
||||
|
||||
locale.Translations = locale.Translations.OrderBy(pair => pair.Key)
|
||||
.ToDictionary(pair => pair.Key, pair => pair.Value);
|
||||
json.Locales[i] = locale;
|
||||
}
|
||||
|
||||
if (isGitRunner && encounteredIssue)
|
||||
throw new JsonException("1 or more locales are invalid! Rebuild locally to fix...");
|
||||
|
||||
string jsonString = JsonSerializer.Serialize(json, _jsonOptions);
|
||||
|
||||
using (StreamWriter sw = new(path))
|
||||
{
|
||||
sw.Write(jsonString);
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine("Finished Locale Validation Task!");
|
||||
|
|
@ -100,10 +137,13 @@ namespace Ryujinx.BuildValidationTasks
|
|||
return true;
|
||||
}
|
||||
|
||||
struct LanguagesJson
|
||||
{
|
||||
public Dictionary<string, string> Languages { get; set; }
|
||||
}
|
||||
|
||||
struct LocalesJson
|
||||
{
|
||||
public Dictionary<string, string> Info { get; set; }
|
||||
public List<string> Languages { get; set; }
|
||||
public List<LocalesEntry> Locales { get; set; }
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -386,10 +386,7 @@ namespace Ryujinx.Common.Collections
|
|||
|
||||
IntervalTreeNode<TKey, TValue> tmp = LeftOf(replacementNode) ?? RightOf(replacementNode);
|
||||
|
||||
if (tmp != null)
|
||||
{
|
||||
tmp.Parent = ParentOf(replacementNode);
|
||||
}
|
||||
tmp?.Parent = ParentOf(replacementNode);
|
||||
|
||||
if (ParentOf(replacementNode) == null)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -235,10 +235,7 @@ namespace Ryujinx.Common.Collections
|
|||
parent = ParentOf(element);
|
||||
color = ColorOf(element);
|
||||
|
||||
if (child != null)
|
||||
{
|
||||
child.Parent = parent;
|
||||
}
|
||||
child?.Parent = parent;
|
||||
|
||||
if (parent == null)
|
||||
{
|
||||
|
|
@ -258,8 +255,7 @@ namespace Ryujinx.Common.Collections
|
|||
element.Right = old.Right;
|
||||
element.Parent = old.Parent;
|
||||
element.Predecessor = old.Predecessor;
|
||||
if (element.Predecessor != null)
|
||||
element.Predecessor.Successor = element;
|
||||
element.Predecessor?.Successor = element;
|
||||
|
||||
if (ParentOf(old) == null)
|
||||
{
|
||||
|
|
@ -292,10 +288,7 @@ namespace Ryujinx.Common.Collections
|
|||
parent = ParentOf(nodeToDelete);
|
||||
color = ColorOf(nodeToDelete);
|
||||
|
||||
if (child != null)
|
||||
{
|
||||
child.Parent = parent;
|
||||
}
|
||||
child?.Parent = parent;
|
||||
|
||||
if (parent == null)
|
||||
{
|
||||
|
|
@ -314,11 +307,9 @@ namespace Ryujinx.Common.Collections
|
|||
{
|
||||
RestoreBalanceAfterRemoval(child);
|
||||
}
|
||||
|
||||
if (old.Successor != null)
|
||||
old.Successor.Predecessor = old.Predecessor;
|
||||
if (old.Predecessor != null)
|
||||
old.Predecessor.Successor = old.Successor;
|
||||
|
||||
old.Successor?.Predecessor = old.Predecessor;
|
||||
old.Predecessor?.Successor = old.Successor;
|
||||
|
||||
return old;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -250,10 +250,7 @@ namespace Ryujinx.Common.Collections
|
|||
{
|
||||
T right = RightOf(node);
|
||||
node.Right = LeftOf(right);
|
||||
if (node.Right != null)
|
||||
{
|
||||
node.Right.Parent = node;
|
||||
}
|
||||
node.Right?.Parent = node;
|
||||
|
||||
T nodeParent = ParentOf(node);
|
||||
right.Parent = nodeParent;
|
||||
|
|
@ -281,10 +278,7 @@ namespace Ryujinx.Common.Collections
|
|||
{
|
||||
T left = LeftOf(node);
|
||||
node.Left = RightOf(left);
|
||||
if (node.Left != null)
|
||||
{
|
||||
node.Left.Parent = node;
|
||||
}
|
||||
node.Left?.Parent = node;
|
||||
|
||||
T nodeParent = ParentOf(node);
|
||||
left.Parent = nodeParent;
|
||||
|
|
@ -329,10 +323,7 @@ namespace Ryujinx.Common.Collections
|
|||
/// <param name="color">Color (Boolean)</param>
|
||||
protected static void SetColor(T node, bool color)
|
||||
{
|
||||
if (node != null)
|
||||
{
|
||||
node.Color = color;
|
||||
}
|
||||
node?.Color = color;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -328,10 +328,7 @@ namespace Ryujinx.Common.Collections
|
|||
|
||||
Node<TKey, TValue> tmp = LeftOf(replacementNode) ?? RightOf(replacementNode);
|
||||
|
||||
if (tmp != null)
|
||||
{
|
||||
tmp.Parent = ParentOf(replacementNode);
|
||||
}
|
||||
tmp?.Parent = ParentOf(replacementNode);
|
||||
|
||||
if (ParentOf(replacementNode) == null)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
using Ryujinx.Common.Utilities;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Configuration
|
||||
{
|
||||
[JsonConverter(typeof(TypedStringEnumConverter<AntiAliasing>))]
|
||||
[JsonConverter(typeof(JsonStringEnumConverter<AntiAliasing>))]
|
||||
public enum AntiAliasing
|
||||
{
|
||||
None,
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
using Ryujinx.Common.Utilities;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Configuration
|
||||
{
|
||||
[JsonConverter(typeof(TypedStringEnumConverter<AspectRatio>))]
|
||||
[JsonConverter(typeof(JsonStringEnumConverter<AspectRatio>))]
|
||||
public enum AspectRatio
|
||||
{
|
||||
Fixed4x3,
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
using Ryujinx.Common.Utilities;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Configuration
|
||||
{
|
||||
[JsonConverter(typeof(TypedStringEnumConverter<BackendThreading>))]
|
||||
[JsonConverter(typeof(JsonStringEnumConverter<BackendThreading>))]
|
||||
public enum BackendThreading
|
||||
{
|
||||
Auto,
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
using Ryujinx.Common.Utilities;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Configuration
|
||||
{
|
||||
[JsonConverter(typeof(TypedStringEnumConverter<GraphicsBackend>))]
|
||||
[JsonConverter(typeof(JsonStringEnumConverter<GraphicsBackend>))]
|
||||
public enum GraphicsBackend
|
||||
{
|
||||
Vulkan,
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
using Ryujinx.Common.Utilities;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Configuration
|
||||
{
|
||||
[JsonConverter(typeof(TypedStringEnumConverter<GraphicsDebugLevel>))]
|
||||
[JsonConverter(typeof(JsonStringEnumConverter<GraphicsDebugLevel>))]
|
||||
public enum GraphicsDebugLevel
|
||||
{
|
||||
None,
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
using Ryujinx.Common.Utilities;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Configuration.Hid.Controller
|
||||
{
|
||||
[JsonConverter(typeof(TypedStringEnumConverter<GamepadInputId>))]
|
||||
[JsonConverter(typeof(JsonStringEnumConverter<GamepadInputId>))]
|
||||
public enum GamepadInputId : byte
|
||||
{
|
||||
Unbound,
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
using Ryujinx.Common.Utilities;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Configuration.Hid.Controller.Motion
|
||||
{
|
||||
[JsonConverter(typeof(TypedStringEnumConverter<MotionInputBackendType>))]
|
||||
[JsonConverter(typeof(JsonStringEnumConverter<MotionInputBackendType>))]
|
||||
public enum MotionInputBackendType : byte
|
||||
{
|
||||
Invalid,
|
||||
|
|
|
|||
|
|
@ -1,15 +1,14 @@
|
|||
using Ryujinx.Common.Utilities;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Configuration.Hid.Controller
|
||||
{
|
||||
[JsonConverter(typeof(TypedStringEnumConverter<StickInputId>))]
|
||||
[JsonConverter(typeof(JsonStringEnumConverter<StickInputId>))]
|
||||
public enum StickInputId : byte
|
||||
{
|
||||
Unbound,
|
||||
Left,
|
||||
Right,
|
||||
|
||||
|
||||
Count,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
using Ryujinx.Common.Utilities;
|
||||
using System;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
|
|
@ -6,7 +5,7 @@ namespace Ryujinx.Common.Configuration.Hid
|
|||
{
|
||||
// This enum was duplicated from Ryujinx.HLE.HOS.Services.Hid.PlayerIndex and should be kept identical
|
||||
[Flags]
|
||||
[JsonConverter(typeof(TypedStringEnumConverter<ControllerType>))]
|
||||
[JsonConverter(typeof(JsonStringEnumConverter<ControllerType>))]
|
||||
public enum ControllerType
|
||||
{
|
||||
None,
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
using Ryujinx.Common.Utilities;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Configuration.Hid
|
||||
{
|
||||
[JsonConverter(typeof(TypedStringEnumConverter<InputBackendType>))]
|
||||
[JsonConverter(typeof(JsonStringEnumConverter<InputBackendType>))]
|
||||
public enum InputBackendType
|
||||
{
|
||||
Invalid,
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
using Ryujinx.Common.Utilities;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Configuration.Hid
|
||||
{
|
||||
[JsonConverter(typeof(TypedStringEnumConverter<Key>))]
|
||||
[JsonConverter(typeof(JsonStringEnumConverter<Key>))]
|
||||
public enum Key
|
||||
{
|
||||
Unknown,
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
using Ryujinx.Common.Utilities;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Configuration.Hid
|
||||
{
|
||||
// This enum was duplicated from Ryujinx.HLE.HOS.Services.Hid.PlayerIndex and should be kept identical
|
||||
[JsonConverter(typeof(TypedStringEnumConverter<PlayerIndex>))]
|
||||
[JsonConverter(typeof(JsonStringEnumConverter<PlayerIndex>))]
|
||||
public enum PlayerIndex
|
||||
{
|
||||
Player1 = 0,
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
using Ryujinx.Common.Utilities;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Configuration
|
||||
{
|
||||
[JsonConverter(typeof(TypedStringEnumConverter<MemoryManagerMode>))]
|
||||
[JsonConverter(typeof(JsonStringEnumConverter<MemoryManagerMode>))]
|
||||
public enum MemoryManagerMode : byte
|
||||
{
|
||||
SoftwarePageTable,
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
using Ryujinx.Common.Utilities;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Configuration
|
||||
{
|
||||
[JsonConverter(typeof(TypedStringEnumConverter<ScalingFilter>))]
|
||||
[JsonConverter(typeof(JsonStringEnumConverter<ScalingFilter>))]
|
||||
public enum ScalingFilter
|
||||
{
|
||||
Bilinear,
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
using Ryujinx.Common.Utilities;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Logging
|
||||
{
|
||||
[JsonConverter(typeof(TypedStringEnumConverter<LogClass>))]
|
||||
[JsonConverter(typeof(JsonStringEnumConverter<LogClass>))]
|
||||
public enum LogClass
|
||||
{
|
||||
Application,
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
using Ryujinx.Common.Utilities;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Logging
|
||||
{
|
||||
[JsonConverter(typeof(TypedStringEnumConverter<LogLevel>))]
|
||||
[JsonConverter(typeof(JsonStringEnumConverter<LogLevel>))]
|
||||
public enum LogLevel
|
||||
{
|
||||
Debug,
|
||||
|
|
|
|||
|
|
@ -7,6 +7,9 @@ namespace Ryujinx.Common.Memory
|
|||
{
|
||||
private static readonly RecyclableMemoryStreamManager _shared = new();
|
||||
|
||||
private static readonly ObjectPool<RecyclableMemoryStream> _streamPool =
|
||||
new(() => new RecyclableMemoryStream(_shared, Guid.NewGuid(), null, 0));
|
||||
|
||||
/// <summary>
|
||||
/// We don't expose the <c>RecyclableMemoryStreamManager</c> directly because version 2.x
|
||||
/// returns them as <c>MemoryStream</c>. This Shared class is here to a) offer only the GetStream() versions we use
|
||||
|
|
@ -19,7 +22,12 @@ namespace Ryujinx.Common.Memory
|
|||
/// </summary>
|
||||
/// <returns>A <c>RecyclableMemoryStream</c></returns>
|
||||
public static RecyclableMemoryStream GetStream()
|
||||
=> new(_shared);
|
||||
{
|
||||
RecyclableMemoryStream stream = _streamPool.Allocate();
|
||||
stream.SetLength(0);
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve a new <c>MemoryStream</c> object with the contents copied from the provided
|
||||
|
|
@ -55,7 +63,8 @@ namespace Ryujinx.Common.Memory
|
|||
RecyclableMemoryStream stream = null;
|
||||
try
|
||||
{
|
||||
stream = new RecyclableMemoryStream(_shared, id, tag, buffer.Length);
|
||||
stream = _streamPool.Allocate();
|
||||
stream.SetLength(0);
|
||||
stream.Write(buffer);
|
||||
stream.Position = 0;
|
||||
return stream;
|
||||
|
|
@ -83,7 +92,8 @@ namespace Ryujinx.Common.Memory
|
|||
RecyclableMemoryStream stream = null;
|
||||
try
|
||||
{
|
||||
stream = new RecyclableMemoryStream(_shared, id, tag, count);
|
||||
stream = _streamPool.Allocate();
|
||||
stream.SetLength(0);
|
||||
stream.Write(buffer, offset, count);
|
||||
stream.Position = 0;
|
||||
return stream;
|
||||
|
|
@ -94,6 +104,11 @@ namespace Ryujinx.Common.Memory
|
|||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public static void ReleaseStream(RecyclableMemoryStream stream)
|
||||
{
|
||||
_streamPool.Release(stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -127,7 +127,7 @@ namespace Ryujinx.Common
|
|||
public static string[] GetAllAvailableResources(string path, string ext = "")
|
||||
{
|
||||
return ResolveManifestPath(path).Item1.GetManifestResourceNames()
|
||||
.Where(r => r.EndsWith(ext))
|
||||
.Where(r => r.StartsWith(path.Replace('/', '.')) && r.EndsWith(ext))
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,5 +20,21 @@ namespace Ryujinx.Common.Utilities
|
|||
Debug.Assert(res != -1);
|
||||
}
|
||||
}
|
||||
|
||||
// "dumpable" attribute of the calling process
|
||||
private const int PR_SET_DUMPABLE = 4;
|
||||
|
||||
[DllImport("libc", SetLastError = true)]
|
||||
private static extern int prctl(int option, int arg2);
|
||||
|
||||
public static void SetCoreDumpable(bool dumpable)
|
||||
{
|
||||
if (OperatingSystem.IsLinux())
|
||||
{
|
||||
int dumpableInt = dumpable ? 1 : 0;
|
||||
int result = prctl(PR_SET_DUMPABLE, dumpableInt);
|
||||
Debug.Assert(result == 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,37 +0,0 @@
|
|||
#nullable enable
|
||||
using Ryujinx.Common.Logging;
|
||||
using System;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Utilities
|
||||
{
|
||||
/// <summary>
|
||||
/// Specifies that value of <see cref="TEnum"/> will be serialized as string in JSONs
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Trimming friendly alternative to <see cref="JsonStringEnumConverter"/>.
|
||||
/// Get rid of this converter if dotnet supports similar functionality out of the box.
|
||||
/// </remarks>
|
||||
/// <typeparam name="TEnum">Type of enum to serialize</typeparam>
|
||||
public sealed class TypedStringEnumConverter<TEnum> : JsonConverter<TEnum> where TEnum : struct, Enum
|
||||
{
|
||||
public override TEnum Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
string? enumValue = reader.GetString();
|
||||
|
||||
if (Enum.TryParse(enumValue, out TEnum value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
Logger.Warning?.Print(LogClass.Configuration, $"Failed to parse enum value \"{enumValue}\" for {typeof(TEnum)}, using default \"{default(TEnum)}\"");
|
||||
return default;
|
||||
}
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, TEnum value, JsonSerializerOptions options)
|
||||
{
|
||||
writer.WriteStringValue(value.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -82,7 +82,7 @@ namespace Ryujinx.Graphics.GAL
|
|||
void SetRasterizerDiscard(bool discard);
|
||||
|
||||
void SetRenderTargetColorMasks(ReadOnlySpan<uint> componentMask);
|
||||
void SetRenderTargets(ITexture[] colors, ITexture depthStencil);
|
||||
void SetRenderTargets(Span<ITexture> colors, ITexture depthStencil);
|
||||
|
||||
void SetScissors(ReadOnlySpan<Rectangle<int>> regions);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using Ryujinx.Graphics.GAL.Multithreading.Model;
|
||||
using Ryujinx.Graphics.GAL.Multithreading.Resources;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
|
||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
||||
|
|
@ -8,11 +9,13 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
|||
{
|
||||
public static readonly ArrayPool<ITexture> ArrayPool = ArrayPool<ITexture>.Create(512, 50);
|
||||
public readonly CommandType CommandType => CommandType.SetRenderTargets;
|
||||
private int _colorsCount;
|
||||
private TableRef<ITexture[]> _colors;
|
||||
private TableRef<ITexture> _depthStencil;
|
||||
|
||||
public void Set(TableRef<ITexture[]> colors, TableRef<ITexture> depthStencil)
|
||||
public void Set(int colorsCount, TableRef<ITexture[]> colors, TableRef<ITexture> depthStencil)
|
||||
{
|
||||
_colorsCount = colorsCount;
|
||||
_colors = colors;
|
||||
_depthStencil = depthStencil;
|
||||
}
|
||||
|
|
@ -20,16 +23,15 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
|||
public static void Run(ref SetRenderTargetsCommand command, ThreadedRenderer threaded, IRenderer renderer)
|
||||
{
|
||||
ITexture[] colors = command._colors.Get(threaded);
|
||||
ITexture[] colorsCopy = ArrayPool.Rent(colors.Length);
|
||||
Span<ITexture> colorsSpan = colors.AsSpan(0, command._colorsCount);
|
||||
|
||||
for (int i = 0; i < colors.Length; i++)
|
||||
for (int i = 0; i < colorsSpan.Length; i++)
|
||||
{
|
||||
colorsCopy[i] = ((ThreadedTexture)colors[i])?.Base;
|
||||
colorsSpan[i] = ((ThreadedTexture)colorsSpan[i])?.Base;
|
||||
}
|
||||
|
||||
renderer.Pipeline.SetRenderTargets(colorsCopy, command._depthStencil.GetAs<ThreadedTexture>(threaded)?.Base);
|
||||
renderer.Pipeline.SetRenderTargets(colorsSpan, command._depthStencil.GetAs<ThreadedTexture>(threaded)?.Base);
|
||||
|
||||
ArrayPool.Return(colorsCopy);
|
||||
ArrayPool.Return(colors);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -267,12 +267,12 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
|||
_renderer.QueueCommand();
|
||||
}
|
||||
|
||||
public unsafe void SetRenderTargets(ITexture[] colors, ITexture depthStencil)
|
||||
public unsafe void SetRenderTargets(Span<ITexture> colors, ITexture depthStencil)
|
||||
{
|
||||
ITexture[] colorsCopy = SetRenderTargetsCommand.ArrayPool.Rent(colors.Length);
|
||||
colors.CopyTo(colorsCopy, 0);
|
||||
colors.CopyTo(colorsCopy.AsSpan());
|
||||
|
||||
_renderer.New<SetRenderTargetsCommand>()->Set(Ref(colorsCopy), Ref(depthStencil));
|
||||
_renderer.New<SetRenderTargetsCommand>()->Set(colors.Length, Ref(colorsCopy), Ref(depthStencil));
|
||||
_renderer.QueueCommand();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
|||
/// </summary>
|
||||
public class ThreadedRenderer : IRenderer
|
||||
{
|
||||
private const int SpanPoolBytes = 4 * 1024 * 1024;
|
||||
private const int SpanPoolBytes = 8 * 1024 * 1024;
|
||||
private const int MaxRefsPerCommand = 2;
|
||||
private const int QueueCount = 10000;
|
||||
|
||||
|
|
|
|||
|
|
@ -404,9 +404,12 @@ namespace Ryujinx.Graphics.Gpu
|
|||
|
||||
if (force || _pendingSync || (syncPoint && SyncpointActions.Count > 0))
|
||||
{
|
||||
foreach (ISyncActionHandler action in SyncActions)
|
||||
for (int i = 0; i < SyncActions.Count; i++)
|
||||
{
|
||||
action.SyncPreAction(syncPoint);
|
||||
if (SyncActions[i].SyncPreAction(syncPoint))
|
||||
{
|
||||
SyncActions.RemoveAt(i--);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (ISyncActionHandler action in SyncpointActions)
|
||||
|
|
|
|||
|
|
@ -411,7 +411,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
/// flushes often enough, which is determined by the flush balance.
|
||||
/// </summary>
|
||||
/// <inheritdoc/>
|
||||
public void SyncPreAction(bool syncpoint)
|
||||
public bool SyncPreAction(bool syncpoint)
|
||||
{
|
||||
if (syncpoint || NextSyncCopies())
|
||||
{
|
||||
|
|
@ -421,6 +421,8 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
_registeredBufferSync = _modifiedSync;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||
/// <summary>
|
||||
/// Buffer, used to store vertex and index data, uniform and storage buffers, and others.
|
||||
/// </summary>
|
||||
class Buffer : INonOverlappingRange, ISyncActionHandler, IDisposable
|
||||
class Buffer : INonOverlappingRange<Buffer>, ISyncActionHandler, IDisposable
|
||||
{
|
||||
private const ulong GranularBufferThreshold = 4096;
|
||||
|
||||
|
|
@ -41,6 +41,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||
/// End address of the buffer in guest memory.
|
||||
/// </summary>
|
||||
public ulong EndAddress => Address + Size;
|
||||
|
||||
public Buffer Next { get; set; }
|
||||
public Buffer Previous { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Increments when the buffer is (partially) unmapped or disposed.
|
||||
|
|
@ -87,6 +90,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||
|
||||
private readonly bool _useGranular;
|
||||
private bool _syncActionRegistered;
|
||||
private bool _bufferInherited;
|
||||
|
||||
private int _referenceCount = 1;
|
||||
|
||||
|
|
@ -113,7 +117,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||
ulong size,
|
||||
BufferStage stage,
|
||||
bool sparseCompatible,
|
||||
RangeItem<Buffer>[] baseBuffers)
|
||||
Buffer[] baseBuffers)
|
||||
{
|
||||
_context = context;
|
||||
_physicalMemory = physicalMemory;
|
||||
|
|
@ -134,15 +138,15 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||
if (baseBuffers.Length != 0)
|
||||
{
|
||||
baseHandles = new List<IRegionHandle>();
|
||||
foreach (RangeItem<Buffer> item in baseBuffers)
|
||||
foreach (Buffer item in baseBuffers)
|
||||
{
|
||||
if (item.Value._useGranular)
|
||||
if (item._useGranular)
|
||||
{
|
||||
baseHandles.AddRange(item.Value._memoryTrackingGranular.Handles);
|
||||
baseHandles.AddRange(item._memoryTrackingGranular.Handles);
|
||||
}
|
||||
else
|
||||
{
|
||||
baseHandles.Add(item.Value._memoryTracking);
|
||||
baseHandles.Add(item._memoryTracking);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -247,14 +251,14 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||
/// Checks if a given range overlaps with the buffer.
|
||||
/// </summary>
|
||||
/// <param name="address">Start address of the range</param>
|
||||
/// <param name="size">Size in bytes of the range</param>
|
||||
/// <param name="endAddress">End address of the range</param>
|
||||
/// <returns>True if the range overlaps, false otherwise</returns>
|
||||
public bool OverlapsWith(ulong address, ulong size)
|
||||
public bool OverlapsWith(ulong address, ulong endAddress)
|
||||
{
|
||||
return Address < address + size && address < EndAddress;
|
||||
return Address < endAddress && address < EndAddress;
|
||||
}
|
||||
|
||||
public INonOverlappingRange Split(ulong splitAddress)
|
||||
public INonOverlappingRange<Buffer> Split(ulong splitAddress)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
|
@ -389,11 +393,16 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||
/// This will copy any buffer ranges designated for pre-flushing.
|
||||
/// </summary>
|
||||
/// <param name="syncpoint">True if the action is a guest syncpoint</param>
|
||||
public void SyncPreAction(bool syncpoint)
|
||||
public bool SyncPreAction(bool syncpoint)
|
||||
{
|
||||
if (_bufferInherited)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_referenceCount == 0)
|
||||
{
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (BackingState.ShouldChangeBacking())
|
||||
|
|
@ -410,6 +419,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||
_modifiedRanges?.GetRangesAtSync(Address, Size, _context.SyncNumber, _syncPreRangeAction);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void SyncPreRangeAction(ulong address, ulong size)
|
||||
|
|
@ -426,10 +437,13 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||
{
|
||||
_syncActionRegistered = false;
|
||||
|
||||
if (_bufferInherited)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_useGranular)
|
||||
{
|
||||
|
||||
|
||||
_modifiedRanges?.GetRanges(Address, Size, _syncRangeAction);
|
||||
}
|
||||
else
|
||||
|
|
@ -453,6 +467,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||
/// <param name="from">The buffer to inherit from</param>
|
||||
public void InheritModifiedRanges(Buffer from)
|
||||
{
|
||||
from._bufferInherited = true;
|
||||
|
||||
if (from._modifiedRanges is { HasRanges: true })
|
||||
{
|
||||
if (from._syncActionRegistered && !_syncActionRegistered)
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||
/// <param name="parent">Parent buffer</param>
|
||||
/// <param name="stage">Initial buffer stage</param>
|
||||
/// <param name="baseBuffers">Buffers to inherit state from</param>
|
||||
public BufferBackingState(GpuContext context, Buffer parent, BufferStage stage, RangeItem<Buffer>[] baseBuffers)
|
||||
public BufferBackingState(GpuContext context, Buffer parent, BufferStage stage, Buffer[] baseBuffers)
|
||||
{
|
||||
_size = (int)parent.Size;
|
||||
_systemMemoryType = context.Capabilities.MemoryType;
|
||||
|
|
@ -102,9 +102,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||
|
||||
if (baseBuffers.Length != 0)
|
||||
{
|
||||
foreach (RangeItem<Buffer> item in baseBuffers)
|
||||
foreach (Buffer item in baseBuffers)
|
||||
{
|
||||
CombineState(item.Value.BackingState);
|
||||
CombineState(item.BackingState);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -79,16 +79,13 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||
for (int index = 0; index < range.Count; index++)
|
||||
{
|
||||
MemoryRange subRange = range.GetSubRange(index);
|
||||
|
||||
_buffers.Lock.EnterReadLock();
|
||||
Span<RangeItem<Buffer>> overlaps = _buffers.FindOverlapsAsSpan(subRange.Address, subRange.Size);
|
||||
|
||||
ReadOnlySpan<Buffer> overlaps = _buffers.FindOverlapsAsSpan(subRange.Address, subRange.Size);
|
||||
|
||||
for (int i = 0; i < overlaps.Length; i++)
|
||||
{
|
||||
overlaps[i].Value.Unmapped(subRange.Address, subRange.Size);
|
||||
overlaps[i].Unmapped(subRange.Address, subRange.Size);
|
||||
}
|
||||
|
||||
_buffers.Lock.ExitReadLock();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -328,7 +325,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||
ulong alignedEndAddress = (endAddress + alignmentMask) & ~alignmentMask;
|
||||
ulong alignedSize = alignedEndAddress - alignedAddress;
|
||||
|
||||
Buffer buffer = _buffers.FindOverlap(alignedAddress, alignedSize).Value;
|
||||
Buffer buffer = _buffers.FindOverlap(alignedAddress, alignedSize);
|
||||
BufferRange bufferRange = buffer.GetRange(alignedAddress, alignedSize, false);
|
||||
|
||||
alignedSubRanges[i] = new MemoryRange(alignedAddress, alignedSize);
|
||||
|
|
@ -395,7 +392,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||
|
||||
if (subRange.Address != MemoryManager.PteUnmapped)
|
||||
{
|
||||
Buffer buffer = _buffers.FindOverlap(subRange.Address, subRange.Size).Value;
|
||||
Buffer buffer = _buffers.FindOverlap(subRange.Address, subRange.Size);
|
||||
|
||||
virtualBuffer.AddPhysicalDependency(buffer, subRange.Address, dstOffset, subRange.Size);
|
||||
physicalBuffers.Add(buffer);
|
||||
|
|
@ -487,10 +484,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||
/// <param name="stage">The type of usage that created the buffer</param>
|
||||
private void CreateBufferAligned(ulong address, ulong size, BufferStage stage)
|
||||
{
|
||||
Buffer newBuffer = null;
|
||||
|
||||
_buffers.Lock.EnterWriteLock();
|
||||
Span<RangeItem<Buffer>> overlaps = _buffers.FindOverlapsAsSpan(address, size);
|
||||
ReadOnlySpan<Buffer> overlaps = _buffers.FindOverlapsAsSpan(address, size);
|
||||
|
||||
if (overlaps.Length != 0)
|
||||
{
|
||||
|
|
@ -521,7 +515,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||
{
|
||||
// Try to grow the buffer by 1.5x of its current size.
|
||||
// This improves performance in the cases where the buffer is resized often by small amounts.
|
||||
ulong existingSize = overlaps[0].Value.Size;
|
||||
ulong existingSize = overlaps[0].Size;
|
||||
ulong growthSize = (existingSize + Math.Min(existingSize >> 1, MaxDynamicGrowthSize)) & ~BufferAlignmentMask;
|
||||
|
||||
size = Math.Max(size, growthSize);
|
||||
|
|
@ -535,39 +529,22 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||
|
||||
for (int i = 0; i < overlaps.Length; i++)
|
||||
{
|
||||
anySparseCompatible |= overlaps[i].Value.SparseCompatible;
|
||||
anySparseCompatible |= overlaps[i].SparseCompatible;
|
||||
}
|
||||
|
||||
RangeItem<Buffer>[] overlapsArray = overlaps.ToArray();
|
||||
Buffer[] overlapsArray = overlaps.ToArray();
|
||||
|
||||
_buffers.RemoveRange(overlaps[0], overlaps[^1]);
|
||||
|
||||
_buffers.Lock.ExitWriteLock();
|
||||
|
||||
ulong newSize = endAddress - address;
|
||||
|
||||
newBuffer = CreateBufferAligned(address, newSize, stage, anySparseCompatible, overlapsArray);
|
||||
}
|
||||
else
|
||||
{
|
||||
_buffers.Lock.ExitWriteLock();
|
||||
_buffers.Add(CreateBufferAligned(address, newSize, stage, anySparseCompatible, overlapsArray));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_buffers.Lock.ExitWriteLock();
|
||||
|
||||
// No overlap, just create a new buffer.
|
||||
newBuffer = new(_context, _physicalMemory, address, size, stage, sparseCompatible: false, []);
|
||||
}
|
||||
|
||||
if (newBuffer is not null)
|
||||
{
|
||||
_buffers.Lock.EnterWriteLock();
|
||||
|
||||
_buffers.Add(newBuffer);
|
||||
|
||||
_buffers.Lock.ExitWriteLock();
|
||||
_buffers.Add(new(_context, _physicalMemory, address, size, stage, sparseCompatible: false, []));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -583,10 +560,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||
private void CreateBufferAligned(ulong address, ulong size, BufferStage stage, ulong alignment)
|
||||
{
|
||||
bool sparseAligned = alignment >= SparseBufferAlignmentSize;
|
||||
Buffer newBuffer = null;
|
||||
|
||||
_buffers.Lock.EnterWriteLock();
|
||||
Span<RangeItem<Buffer>> overlaps = _buffers.FindOverlapsAsSpan(address, size);
|
||||
ReadOnlySpan<Buffer> overlaps = _buffers.FindOverlapsAsSpan(address, size);
|
||||
|
||||
if (overlaps.Length != 0)
|
||||
{
|
||||
|
|
@ -598,7 +573,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||
if (overlaps[0].Address > address ||
|
||||
overlaps[0].EndAddress < endAddress ||
|
||||
(overlaps[0].Address & (alignment - 1)) != 0 ||
|
||||
(!overlaps[0].Value.SparseCompatible && sparseAligned))
|
||||
(!overlaps[0].SparseCompatible && sparseAligned))
|
||||
{
|
||||
// We need to make sure the new buffer is properly aligned.
|
||||
// However, after the range is aligned, it is possible that it
|
||||
|
|
@ -622,35 +597,18 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||
|
||||
ulong newSize = endAddress - address;
|
||||
|
||||
RangeItem<Buffer>[] overlapsArray = overlaps.ToArray();
|
||||
Buffer[] overlapsArray = overlaps.ToArray();
|
||||
|
||||
_buffers.RemoveRange(overlaps[0], overlaps[^1]);
|
||||
|
||||
_buffers.Lock.ExitWriteLock();
|
||||
|
||||
newBuffer = CreateBufferAligned(address, newSize, stage, sparseAligned, overlapsArray);
|
||||
}
|
||||
else
|
||||
{
|
||||
_buffers.Lock.ExitWriteLock();
|
||||
_buffers.Add(CreateBufferAligned(address, newSize, stage, sparseAligned, overlapsArray));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_buffers.Lock.ExitWriteLock();
|
||||
|
||||
// No overlap, just create a new buffer.
|
||||
newBuffer = new(_context, _physicalMemory, address, size, stage, sparseAligned, []);
|
||||
}
|
||||
|
||||
if (newBuffer is not null)
|
||||
{
|
||||
_buffers.Lock.EnterWriteLock();
|
||||
|
||||
_buffers.Add(newBuffer);
|
||||
|
||||
_buffers.Lock.ExitWriteLock();
|
||||
}
|
||||
_buffers.Add(new(_context, _physicalMemory, address, size, stage, sparseAligned, []));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -663,13 +621,13 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||
/// <param name="stage">The type of usage that created the buffer</param>
|
||||
/// <param name="sparseCompatible">Indicates if the buffer can be used in a sparse buffer mapping</param>
|
||||
/// <param name="overlaps">Buffers overlapping the range</param>
|
||||
private Buffer CreateBufferAligned(ulong address, ulong size, BufferStage stage, bool sparseCompatible, RangeItem<Buffer>[] overlaps)
|
||||
private Buffer CreateBufferAligned(ulong address, ulong size, BufferStage stage, bool sparseCompatible, Buffer[] overlaps)
|
||||
{
|
||||
Buffer newBuffer = new(_context, _physicalMemory, address, size, stage, sparseCompatible, overlaps);
|
||||
|
||||
for (int index = 0; index < overlaps.Length; index++)
|
||||
{
|
||||
Buffer buffer = overlaps[index].Value;
|
||||
Buffer buffer = overlaps[index];
|
||||
|
||||
int dstOffset = (int)(buffer.Address - newBuffer.Address);
|
||||
|
||||
|
|
@ -897,7 +855,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||
{
|
||||
MemoryRange subRange = range.GetSubRange(i);
|
||||
|
||||
Buffer subBuffer = _buffers.FindOverlap(subRange.Address, subRange.Size).Value;
|
||||
Buffer subBuffer = _buffers.FindOverlap(subRange.Address, subRange.Size);
|
||||
|
||||
subBuffer.SynchronizeMemory(subRange.Address, subRange.Size);
|
||||
|
||||
|
|
@ -945,7 +903,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||
|
||||
if (size != 0)
|
||||
{
|
||||
buffer = _buffers.FindOverlap(address, size).Value;
|
||||
buffer = _buffers.FindOverlap(address, size);
|
||||
|
||||
buffer.CopyFromDependantVirtualBuffers();
|
||||
buffer.SynchronizeMemory(address, size);
|
||||
|
|
@ -957,7 +915,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||
}
|
||||
else
|
||||
{
|
||||
buffer = _buffers.FindOverlapFast(address, 1).Value;
|
||||
buffer = _buffers.FindOverlapFast(address, 1);
|
||||
}
|
||||
|
||||
return buffer;
|
||||
|
|
@ -995,7 +953,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||
{
|
||||
if (size != 0)
|
||||
{
|
||||
Buffer buffer = _buffers.FindOverlap(address, size).Value;
|
||||
Buffer buffer = _buffers.FindOverlap(address, size);
|
||||
|
||||
if (copyBackVirtual)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||
/// <summary>
|
||||
/// A range within a buffer that has been modified by the GPU.
|
||||
/// </summary>
|
||||
class BufferModifiedRange : INonOverlappingRange
|
||||
class BufferModifiedRange : INonOverlappingRange<BufferModifiedRange>
|
||||
{
|
||||
/// <summary>
|
||||
/// Start address of the range in guest memory.
|
||||
|
|
@ -24,6 +24,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||
/// End address of the range in guest memory.
|
||||
/// </summary>
|
||||
public ulong EndAddress => Address + Size;
|
||||
|
||||
public BufferModifiedRange Next { get; set; }
|
||||
public BufferModifiedRange Previous { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The GPU sync number at the time of the last modification.
|
||||
|
|
@ -54,14 +57,14 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||
/// Checks if a given range overlaps with the modified range.
|
||||
/// </summary>
|
||||
/// <param name="address">Start address of the range</param>
|
||||
/// <param name="size">Size in bytes of the range</param>
|
||||
/// <param name="endAddress">End address of the range</param>
|
||||
/// <returns>True if the range overlaps, false otherwise</returns>
|
||||
public bool OverlapsWith(ulong address, ulong size)
|
||||
public bool OverlapsWith(ulong address, ulong endAddress)
|
||||
{
|
||||
return Address < address + size && address < EndAddress;
|
||||
return Address < endAddress && address < EndAddress;
|
||||
}
|
||||
|
||||
public INonOverlappingRange Split(ulong splitAddress)
|
||||
public INonOverlappingRange<BufferModifiedRange> Split(ulong splitAddress)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
|
@ -119,11 +122,11 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||
// Slices a given region using the modified regions in the list. Calls the action for the new slices.
|
||||
Lock.EnterReadLock();
|
||||
|
||||
Span<RangeItem<BufferModifiedRange>> overlaps = FindOverlapsAsSpan(address, size);
|
||||
ReadOnlySpan<BufferModifiedRange> overlaps = FindOverlapsAsSpan(address, size);
|
||||
|
||||
for (int i = 0; i < overlaps.Length; i++)
|
||||
{
|
||||
BufferModifiedRange overlap = overlaps[i].Value;
|
||||
BufferModifiedRange overlap = overlaps[i];
|
||||
|
||||
if (overlap.Address > address)
|
||||
{
|
||||
|
|
@ -157,7 +160,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||
ulong syncNumber = _context.SyncNumber;
|
||||
// We may overlap with some existing modified regions. They must be cut into by the new entry.
|
||||
Lock.EnterWriteLock();
|
||||
(RangeItem<BufferModifiedRange> first, RangeItem<BufferModifiedRange> last) = FindOverlapsAsNodes(address, size);
|
||||
(BufferModifiedRange first, BufferModifiedRange last) = FindOverlapsAsNodes(address, size);
|
||||
|
||||
if (first is null)
|
||||
{
|
||||
|
|
@ -170,34 +173,39 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||
{
|
||||
if (first.Address == address && first.EndAddress == endAddress)
|
||||
{
|
||||
first.Value.SyncNumber = syncNumber;
|
||||
first.Value.Parent = this;
|
||||
first.SyncNumber = syncNumber;
|
||||
first.Parent = this;
|
||||
Lock.ExitWriteLock();
|
||||
return;
|
||||
}
|
||||
|
||||
if (first.Address < address)
|
||||
{
|
||||
first.Value.Size = address - first.Address;
|
||||
Update(first);
|
||||
|
||||
if (first.EndAddress > endAddress)
|
||||
{
|
||||
Add(new BufferModifiedRange(endAddress, first.EndAddress - endAddress,
|
||||
first.Value.SyncNumber, first.Value.Parent));
|
||||
first.SyncNumber, first.Parent));
|
||||
}
|
||||
|
||||
first.Size = address - first.Address;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (first.EndAddress > endAddress)
|
||||
{
|
||||
first.Value.Size = first.EndAddress - endAddress;
|
||||
first.Value.Address = endAddress;
|
||||
Update(first);
|
||||
first.Size = first.EndAddress - endAddress;
|
||||
first.Address = endAddress;
|
||||
}
|
||||
else
|
||||
{
|
||||
Remove(first.Value);
|
||||
first.Address = address;
|
||||
first.Size = size;
|
||||
first.SyncNumber = syncNumber;
|
||||
first.Parent = this;
|
||||
|
||||
Lock.ExitWriteLock();
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -207,38 +215,39 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||
return;
|
||||
}
|
||||
|
||||
BufferModifiedRange buffPre = null;
|
||||
BufferModifiedRange buffPost = null;
|
||||
bool extendsPost = false;
|
||||
bool extendsPre = false;
|
||||
|
||||
if (first.Address < address)
|
||||
{
|
||||
buffPre = new BufferModifiedRange(first.Address, address - first.Address,
|
||||
first.Value.SyncNumber, first.Value.Parent);
|
||||
extendsPre = true;
|
||||
first.Size = address - first.Address;
|
||||
first = first.Next;
|
||||
}
|
||||
|
||||
if (last.EndAddress > endAddress)
|
||||
{
|
||||
buffPost = new BufferModifiedRange(endAddress, last.EndAddress - endAddress,
|
||||
last.Value.SyncNumber, last.Value.Parent);
|
||||
extendsPost = true;
|
||||
last.Size = last.EndAddress - endAddress;
|
||||
last.Address = endAddress;
|
||||
last = last.Previous;
|
||||
}
|
||||
|
||||
RemoveRange(first, last);
|
||||
|
||||
if (extendsPre)
|
||||
if (first.Address < last.Address)
|
||||
{
|
||||
Add(buffPre);
|
||||
RemoveRange(first.Next, last);
|
||||
first.Address = address;
|
||||
first.Size = size;
|
||||
first.SyncNumber = syncNumber;
|
||||
first.Parent = this;
|
||||
}
|
||||
|
||||
if (extendsPost)
|
||||
else if (first.Address == last.Address)
|
||||
{
|
||||
Add(buffPost);
|
||||
first.Address = address;
|
||||
first.Size = size;
|
||||
first.SyncNumber = syncNumber;
|
||||
first.Parent = this;
|
||||
}
|
||||
|
||||
Add(new BufferModifiedRange(address, size, syncNumber, this));
|
||||
else
|
||||
{
|
||||
Add(new BufferModifiedRange(address, size, syncNumber, this));
|
||||
}
|
||||
|
||||
Lock.ExitWriteLock();
|
||||
}
|
||||
|
||||
|
|
@ -252,11 +261,11 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||
public void GetRangesAtSync(ulong address, ulong size, ulong syncNumber, Action<ulong, ulong> rangeAction)
|
||||
{
|
||||
Lock.EnterReadLock();
|
||||
Span<RangeItem<BufferModifiedRange>> overlaps = FindOverlapsAsSpan(address, size);
|
||||
ReadOnlySpan<BufferModifiedRange> overlaps = FindOverlapsAsSpan(address, size);
|
||||
|
||||
for (int i = 0; i < overlaps.Length; i++)
|
||||
{
|
||||
BufferModifiedRange overlap = overlaps[i].Value;
|
||||
BufferModifiedRange overlap = overlaps[i];
|
||||
|
||||
if (overlap.SyncNumber == syncNumber)
|
||||
{
|
||||
|
|
@ -277,18 +286,18 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||
{
|
||||
// We use the non-span method here because keeping the lock will cause a deadlock.
|
||||
Lock.EnterReadLock();
|
||||
RangeItem<BufferModifiedRange>[] overlaps = FindOverlapsAsArray(address, size, out int length);
|
||||
BufferModifiedRange[] overlaps = FindOverlapsAsArray(address, size, out int length);
|
||||
Lock.ExitReadLock();
|
||||
|
||||
if (length != 0)
|
||||
{
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
BufferModifiedRange overlap = overlaps[i].Value;
|
||||
BufferModifiedRange overlap = overlaps[i];
|
||||
rangeAction(overlap.Address, overlap.Size);
|
||||
}
|
||||
|
||||
ArrayPool<RangeItem<BufferModifiedRange>>.Shared.Return(overlaps);
|
||||
ArrayPool<BufferModifiedRange>.Shared.Return(overlaps);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -301,7 +310,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||
public bool HasRange(ulong address, ulong size)
|
||||
{
|
||||
Lock.EnterReadLock();
|
||||
RangeItem<BufferModifiedRange> first = FindOverlapFast(address, size);
|
||||
BufferModifiedRange first = FindOverlapFast(address, size);
|
||||
bool result = first is not null;
|
||||
Lock.ExitReadLock();
|
||||
return result;
|
||||
|
|
@ -336,7 +345,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||
/// <param name="address">The start address of the flush range</param>
|
||||
/// <param name="endAddress">The end address of the flush range</param>
|
||||
private void RemoveRangesAndFlush(
|
||||
RangeItem<BufferModifiedRange>[] overlaps,
|
||||
BufferModifiedRange[] overlaps,
|
||||
int rangeCount,
|
||||
long highestDiff,
|
||||
ulong currentSync,
|
||||
|
|
@ -349,7 +358,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||
|
||||
for (int i = 0; i < rangeCount; i++)
|
||||
{
|
||||
BufferModifiedRange overlap = overlaps[i].Value;
|
||||
BufferModifiedRange overlap = overlaps[i];
|
||||
|
||||
long diff = (long)(overlap.SyncNumber - currentSync);
|
||||
|
||||
|
|
@ -358,7 +367,14 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||
ulong clampAddress = Math.Max(address, overlap.Address);
|
||||
ulong clampEnd = Math.Min(endAddress, overlap.EndAddress);
|
||||
|
||||
ClearPart(overlap, clampAddress, clampEnd);
|
||||
if (i == 0 || i == rangeCount - 1)
|
||||
{
|
||||
ClearPart(overlap, clampAddress, clampEnd);
|
||||
}
|
||||
else
|
||||
{
|
||||
Remove(overlap);
|
||||
}
|
||||
|
||||
RangeActionWithMigration(clampAddress, clampEnd - clampAddress, waitSync, _flushAction);
|
||||
}
|
||||
|
|
@ -398,7 +414,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||
|
||||
Lock.EnterWriteLock();
|
||||
// We use the non-span method here because the array is partially modified by the code, which would invalidate a span.
|
||||
RangeItem<BufferModifiedRange>[] overlaps = FindOverlapsAsArray(address, size, out int rangeCount);
|
||||
BufferModifiedRange[] overlaps = FindOverlapsAsArray(address, size, out int rangeCount);
|
||||
|
||||
if (rangeCount == 0)
|
||||
{
|
||||
|
|
@ -414,7 +430,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||
|
||||
for (int i = 0; i < rangeCount; i++)
|
||||
{
|
||||
BufferModifiedRange overlap = overlaps![i].Value;
|
||||
BufferModifiedRange overlap = overlaps![i];
|
||||
|
||||
long diff = (long)(overlap.SyncNumber - currentSync);
|
||||
|
||||
|
|
@ -436,7 +452,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||
|
||||
RemoveRangesAndFlush(overlaps, rangeCount, highestDiff, currentSync, address, endAddress);
|
||||
|
||||
ArrayPool<RangeItem<BufferModifiedRange>>.Shared.Return(overlaps!);
|
||||
ArrayPool<BufferModifiedRange>.Shared.Return(overlaps!);
|
||||
|
||||
Lock.ExitWriteLock();
|
||||
}
|
||||
|
|
@ -452,7 +468,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||
public void InheritRanges(BufferModifiedRangeList ranges, Action<ulong, ulong> registerRangeAction)
|
||||
{
|
||||
ranges.Lock.EnterReadLock();
|
||||
BufferModifiedRange[] inheritRanges = ranges.ToArray();
|
||||
int rangesCount = ranges.Count;
|
||||
BufferModifiedRange[] inheritRanges = ArrayPool<BufferModifiedRange>.Shared.Rent(ranges.Count);
|
||||
ranges.Items.AsSpan(0, ranges.Count).CopyTo(inheritRanges);
|
||||
ranges.Lock.ExitReadLock();
|
||||
|
||||
// Copy over the migration from the previous range list
|
||||
|
|
@ -478,22 +496,26 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||
ranges._migrationTarget = this;
|
||||
|
||||
Lock.EnterWriteLock();
|
||||
|
||||
foreach (BufferModifiedRange range in inheritRanges)
|
||||
|
||||
for (int i = 0; i < rangesCount; i++)
|
||||
{
|
||||
BufferModifiedRange range = inheritRanges[i];
|
||||
Add(range);
|
||||
}
|
||||
|
||||
Lock.ExitWriteLock();
|
||||
|
||||
ulong currentSync = _context.SyncNumber;
|
||||
foreach (BufferModifiedRange range in inheritRanges)
|
||||
for (int i = 0; i < rangesCount; i++)
|
||||
{
|
||||
BufferModifiedRange range = inheritRanges[i];
|
||||
if (range.SyncNumber != currentSync)
|
||||
{
|
||||
registerRangeAction(range.Address, range.Size);
|
||||
}
|
||||
}
|
||||
|
||||
ArrayPool<BufferModifiedRange>.Shared.Return(inheritRanges);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -534,18 +556,25 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||
|
||||
private void ClearPart(BufferModifiedRange overlap, ulong address, ulong endAddress)
|
||||
{
|
||||
Remove(overlap);
|
||||
|
||||
// If the overlap extends outside of the clear range, make sure those parts still exist.
|
||||
|
||||
if (overlap.Address < address)
|
||||
{
|
||||
Add(new BufferModifiedRange(overlap.Address, address - overlap.Address, overlap.SyncNumber, overlap.Parent));
|
||||
if (overlap.EndAddress > endAddress)
|
||||
{
|
||||
Add(new BufferModifiedRange(endAddress, overlap.EndAddress - endAddress, overlap.SyncNumber, overlap.Parent));
|
||||
}
|
||||
|
||||
overlap.Size = address - overlap.Address;
|
||||
}
|
||||
|
||||
if (overlap.EndAddress > endAddress)
|
||||
else if (overlap.EndAddress > endAddress)
|
||||
{
|
||||
Add(new BufferModifiedRange(endAddress, overlap.EndAddress - endAddress, overlap.SyncNumber, overlap.Parent));
|
||||
overlap.Size = overlap.EndAddress - endAddress;
|
||||
overlap.Address = endAddress;
|
||||
}
|
||||
else
|
||||
{
|
||||
Remove(overlap);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -558,7 +587,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||
{
|
||||
ulong endAddress = address + size;
|
||||
Lock.EnterWriteLock();
|
||||
(RangeItem<BufferModifiedRange> first, RangeItem<BufferModifiedRange> last) = FindOverlapsAsNodes(address, size);
|
||||
(BufferModifiedRange first, BufferModifiedRange last) = FindOverlapsAsNodes(address, size);
|
||||
|
||||
if (first is null)
|
||||
{
|
||||
|
|
@ -570,26 +599,24 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||
{
|
||||
if (first.Address < address)
|
||||
{
|
||||
first.Value.Size = address - first.Address;
|
||||
Update(first);
|
||||
first.Size = address - first.Address;
|
||||
|
||||
if (first.EndAddress > endAddress)
|
||||
{
|
||||
Add(new BufferModifiedRange(endAddress, first.EndAddress - endAddress,
|
||||
first.Value.SyncNumber, first.Value.Parent));
|
||||
first.SyncNumber, first.Parent));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (first.EndAddress > endAddress)
|
||||
{
|
||||
first.Value.Size = first.EndAddress - endAddress;
|
||||
first.Value.Address = endAddress;
|
||||
Update(first);
|
||||
first.Size = first.EndAddress - endAddress;
|
||||
first.Address = endAddress;
|
||||
}
|
||||
else
|
||||
{
|
||||
Remove(first.Value);
|
||||
Remove(first);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -605,14 +632,14 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||
if (first.Address < address)
|
||||
{
|
||||
buffPre = new BufferModifiedRange(first.Address, address - first.Address,
|
||||
first.Value.SyncNumber, first.Value.Parent);
|
||||
first.SyncNumber, first.Parent);
|
||||
extendsPre = true;
|
||||
}
|
||||
|
||||
if (last.EndAddress > endAddress)
|
||||
{
|
||||
buffPost = new BufferModifiedRange(endAddress, last.EndAddress - endAddress,
|
||||
last.Value.SyncNumber, last.Value.Parent);
|
||||
last.SyncNumber, last.Parent);
|
||||
extendsPost = true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -84,10 +84,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
ICounterEvent evt = _items[index + i].Event;
|
||||
if (evt != null)
|
||||
{
|
||||
evt.Invalid = true;
|
||||
}
|
||||
evt?.Invalid = true;
|
||||
}
|
||||
|
||||
_items.RemoveRange(index, count);
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||
/// <summary>
|
||||
/// Represents a GPU virtual memory range.
|
||||
/// </summary>
|
||||
private class VirtualRange : INonOverlappingRange
|
||||
private class VirtualRange : INonOverlappingRange<VirtualRange>
|
||||
{
|
||||
/// <summary>
|
||||
/// GPU virtual address where the range starts.
|
||||
|
|
@ -32,6 +32,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||
/// </summary>
|
||||
public ulong EndAddress => Address + Size;
|
||||
|
||||
public VirtualRange Next { get; set; }
|
||||
public VirtualRange Previous { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Physical regions where the GPU virtual region is mapped.
|
||||
/// </summary>
|
||||
|
|
@ -54,14 +57,14 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||
/// Checks if a given range overlaps with the buffer.
|
||||
/// </summary>
|
||||
/// <param name="address">Start address of the range</param>
|
||||
/// <param name="size">Size in bytes of the range</param>
|
||||
/// <param name="endAddress">End address of the range</param>
|
||||
/// <returns>True if the range overlaps, false otherwise</returns>
|
||||
public bool OverlapsWith(ulong address, ulong size)
|
||||
public bool OverlapsWith(ulong address, ulong endAddress)
|
||||
{
|
||||
return Address < address + size && address < EndAddress;
|
||||
return Address < endAddress && address < EndAddress;
|
||||
}
|
||||
|
||||
public INonOverlappingRange Split(ulong splitAddress)
|
||||
public INonOverlappingRange<VirtualRange> Split(ulong splitAddress)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
|
@ -122,7 +125,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||
ulong originalVa = gpuVa;
|
||||
|
||||
_virtualRanges.Lock.EnterWriteLock();
|
||||
(RangeItem<VirtualRange> first, RangeItem<VirtualRange> last) = _virtualRanges.FindOverlapsAsNodes(gpuVa, size);
|
||||
(VirtualRange first, VirtualRange last) = _virtualRanges.FindOverlapsAsNodes(gpuVa, size);
|
||||
|
||||
if (first is not null)
|
||||
{
|
||||
|
|
@ -147,8 +150,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||
}
|
||||
else
|
||||
{
|
||||
found = first.Value.Range.Count == 1 || IsSparseAligned(first.Value.Range);
|
||||
range = first.Value.Range.Slice(gpuVa - first.Address, size);
|
||||
found = first.Range.Count == 1 || IsSparseAligned(first.Range);
|
||||
range = first.Range.Slice(gpuVa - first.Address, size);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
|
|
|||
|
|
@ -17,6 +17,6 @@ namespace Ryujinx.Graphics.Gpu.Synchronization
|
|||
/// Action to be performed immediately before sync is created.
|
||||
/// </summary>
|
||||
/// <param name="syncpoint">True if the action is a guest syncpoint</param>
|
||||
void SyncPreAction(bool syncpoint) { }
|
||||
bool SyncPreAction(bool syncpoint) { return true; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1166,7 +1166,7 @@ namespace Ryujinx.Graphics.OpenGL
|
|||
}
|
||||
}
|
||||
|
||||
public void SetRenderTargets(ITexture[] colors, ITexture depthStencil)
|
||||
public void SetRenderTargets(Span<ITexture> colors, ITexture depthStencil)
|
||||
{
|
||||
EnsureFramebuffer();
|
||||
|
||||
|
|
|
|||
12
src/Ryujinx.Graphics.RenderDocApi/Capture.cs
Normal file
12
src/Ryujinx.Graphics.RenderDocApi/Capture.cs
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.RenderDocApi
|
||||
{
|
||||
public readonly record struct Capture(int Index, string FileName, DateTime Timestamp)
|
||||
{
|
||||
public void SetComments(string comments)
|
||||
{
|
||||
RenderDoc.SetCaptureFileComments(FileName, comments);
|
||||
}
|
||||
}
|
||||
}
|
||||
100
src/Ryujinx.Graphics.RenderDocApi/CaptureOption.cs
Normal file
100
src/Ryujinx.Graphics.RenderDocApi/CaptureOption.cs
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
// ReSharper disable UnusedMember.Global
|
||||
|
||||
namespace Ryujinx.Graphics.RenderDocApi
|
||||
{
|
||||
public enum CaptureOption
|
||||
{
|
||||
/// <summary>
|
||||
/// specifies whether the application is allowed to enable vsync. Default is on.
|
||||
/// </summary>
|
||||
AllowVsync = 0,
|
||||
/// <summary>
|
||||
/// specifies whether the application is allowed to enter exclusive fullscreen. Default is on.
|
||||
/// </summary>
|
||||
AllowFullscreen = 1,
|
||||
/// <summary>
|
||||
/// specifies whether (where possible) API-specific debugging is enabled. Default is off.
|
||||
/// </summary>
|
||||
ApiValidation = 2,
|
||||
/// <summary>
|
||||
/// specifies whether each API call should save a callstack. Default is off.
|
||||
/// </summary>
|
||||
CaptureCallstacks = 3,
|
||||
/// <summary>
|
||||
/// specifies whether, if <see cref="CaptureCallstacks"/> is enabled, callstacks are only saved on actions. Default is off.
|
||||
/// </summary>
|
||||
CaptureCallstacksOnlyDraws = 4,
|
||||
/// <summary>
|
||||
/// specifies a delay in seconds after launching a process to pause, to allow debuggers to attach. <br/>
|
||||
/// This will only apply to child processes since the delay happens at process startup. Default is 0.
|
||||
/// </summary>
|
||||
DelayForDebugger = 5,
|
||||
/// <summary>
|
||||
/// specifies whether any mapped memory updates should be bounds-checked for overruns,
|
||||
/// and uninitialised buffers are initialized to <code>0xDDDDDDDD</code> to catch use of uninitialised data.
|
||||
/// Only supported on D3D11 and OpenGL. Default is off.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This option is only valid for OpenGL and D3D11. Explicit APIs such as D3D12 and Vulkan do
|
||||
/// not do the same kind of interception & checking, and undefined contents are really undefined.
|
||||
/// </remarks>
|
||||
VerifyBufferAccess = 6,
|
||||
/// <summary>
|
||||
/// Hooks any system API calls that create child processes, and injects
|
||||
/// RenderDoc into them recursively with the same options.
|
||||
/// </summary>
|
||||
HookIntoChildren = 7,
|
||||
/// <summary>
|
||||
/// specifies whether all live resources at the time of capture should be included in the capture,
|
||||
/// even if they are not referenced by the frame. Default is off.
|
||||
/// </summary>
|
||||
RefAllSources = 8,
|
||||
/// <summary>
|
||||
/// By default, RenderDoc skips saving initial states for resources where the
|
||||
/// previous contents don't appear to be used, assuming that writes before
|
||||
/// reads indicate previous contents aren't used.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// **NOTE**: As of RenderDoc v1.1 this option has been deprecated. Setting or
|
||||
/// getting it will be ignored, to allow compatibility with older versions.
|
||||
/// In v1.1 the option acts as if it's always enabled.
|
||||
/// </remarks>
|
||||
SaveAllInitials = 9,
|
||||
/// <summary>
|
||||
/// In APIs that allow for the recording of command lists to be replayed later,
|
||||
/// RenderDoc may choose to not capture command lists before a frame capture is
|
||||
/// triggered, to reduce overheads. This means any command lists recorded once
|
||||
/// and replayed many times will not be available and may cause a failure to
|
||||
/// capture.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// NOTE: This is only true for APIs where multithreading is difficult or
|
||||
/// discouraged. Newer APIs like Vulkan and D3D12 will ignore this option
|
||||
/// and always capture all command lists since the API is heavily oriented
|
||||
/// around it and the overheads have been reduced by API design.
|
||||
/// </remarks>
|
||||
CaptureAllCmdLists = 10,
|
||||
/// <summary>
|
||||
/// Mute API debugging output when the <see cref="ApiValidation"/> option is enabled.
|
||||
/// </summary>
|
||||
DebugOutputMute = 11,
|
||||
/// <summary>
|
||||
/// Allow vendor extensions to be used even when they may be
|
||||
/// incompatible with RenderDoc and cause corrupted replays or crashes.
|
||||
/// </summary>
|
||||
AllowUnsupportedVendorExtensions = 12,
|
||||
/// <summary>
|
||||
/// Define a soft memory limit which some APIs may aim to keep overhead under where
|
||||
/// possible. Anything above this limit will where possible be saved directly to disk during
|
||||
/// capture.<br/>
|
||||
/// This will cause increased disk space use (which may cause a capture to fail if disk space is
|
||||
/// exhausted) as well as slower capture times.
|
||||
/// <br/><br/>
|
||||
/// Not all memory allocations may be deferred like this so it is not a guarantee of a memory
|
||||
/// limit.
|
||||
/// <br/><br/>
|
||||
/// Units are in MBs, suggested values would range from 200MB to 1000MB.
|
||||
/// </summary>
|
||||
SoftMemoryLimit = 13,
|
||||
}
|
||||
}
|
||||
83
src/Ryujinx.Graphics.RenderDocApi/InputButton.cs
Normal file
83
src/Ryujinx.Graphics.RenderDocApi/InputButton.cs
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
|
||||
// ReSharper disable UnusedMember.Global
|
||||
namespace Ryujinx.Graphics.RenderDocApi
|
||||
{
|
||||
public enum InputButton
|
||||
{
|
||||
// '0' - '9' matches ASCII values
|
||||
Key0 = 0x30,
|
||||
Key1 = 0x31,
|
||||
Key2 = 0x32,
|
||||
Key3 = 0x33,
|
||||
Key4 = 0x34,
|
||||
Key5 = 0x35,
|
||||
Key6 = 0x36,
|
||||
Key7 = 0x37,
|
||||
Key8 = 0x38,
|
||||
Key9 = 0x39,
|
||||
|
||||
// 'A' - 'Z' matches ASCII values
|
||||
A = 0x41,
|
||||
B = 0x42,
|
||||
C = 0x43,
|
||||
D = 0x44,
|
||||
E = 0x45,
|
||||
F = 0x46,
|
||||
G = 0x47,
|
||||
H = 0x48,
|
||||
I = 0x49,
|
||||
J = 0x4A,
|
||||
K = 0x4B,
|
||||
L = 0x4C,
|
||||
M = 0x4D,
|
||||
N = 0x4E,
|
||||
O = 0x4F,
|
||||
P = 0x50,
|
||||
Q = 0x51,
|
||||
R = 0x52,
|
||||
S = 0x53,
|
||||
T = 0x54,
|
||||
U = 0x55,
|
||||
V = 0x56,
|
||||
W = 0x57,
|
||||
X = 0x58,
|
||||
Y = 0x59,
|
||||
Z = 0x5A,
|
||||
|
||||
// leave the rest of the ASCII range free
|
||||
// in case we want to use it later
|
||||
NonPrintable = 0x100,
|
||||
|
||||
Divide,
|
||||
Multiply,
|
||||
Subtract,
|
||||
Plus,
|
||||
|
||||
F1,
|
||||
F2,
|
||||
F3,
|
||||
F4,
|
||||
F5,
|
||||
F6,
|
||||
F7,
|
||||
F8,
|
||||
F9,
|
||||
F10,
|
||||
F11,
|
||||
F12,
|
||||
|
||||
Home,
|
||||
End,
|
||||
Insert,
|
||||
Delete,
|
||||
PageUp,
|
||||
PageDn,
|
||||
|
||||
Backspace,
|
||||
Tab,
|
||||
PrtScrn,
|
||||
Pause,
|
||||
|
||||
Max,
|
||||
}
|
||||
}
|
||||
39
src/Ryujinx.Graphics.RenderDocApi/OverlayBits.cs
Normal file
39
src/Ryujinx.Graphics.RenderDocApi/OverlayBits.cs
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
// ReSharper disable UnusedMember.Global
|
||||
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.RenderDocApi
|
||||
{
|
||||
[Flags]
|
||||
public enum OverlayBits
|
||||
{
|
||||
/// <summary>
|
||||
/// This single bit controls whether the overlay is enabled or disabled globally
|
||||
/// </summary>
|
||||
Enabled = 1 << 0,
|
||||
/// <summary>
|
||||
/// Show the average framerate over several seconds as well as min/max
|
||||
/// </summary>
|
||||
FrameRate = 1 << 1,
|
||||
/// <summary>
|
||||
/// Show the current frame number
|
||||
/// </summary>
|
||||
FrameNumber = 1 << 2,
|
||||
/// <summary>
|
||||
/// Show a list of recent captures, and how many captures have been made
|
||||
/// </summary>
|
||||
CaptureList = 1 << 3,
|
||||
/// <summary>
|
||||
/// Default values for the overlay mask
|
||||
/// </summary>
|
||||
Default = Enabled | FrameRate | FrameNumber | CaptureList,
|
||||
/// <summary>
|
||||
/// Enable all bits
|
||||
/// </summary>
|
||||
All = ~0,
|
||||
/// <summary>
|
||||
/// Disable all bits
|
||||
/// </summary>
|
||||
None = 0
|
||||
}
|
||||
}
|
||||
5
src/Ryujinx.Graphics.RenderDocApi/README.md
Normal file
5
src/Ryujinx.Graphics.RenderDocApi/README.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
# Ryujinx.Graphics.RenderDocApi
|
||||
|
||||
This is a C# binding for RenderDoc's application API.
|
||||
This is a source-inclusion of https://github.com/utkumaden/RenderdocSharp.
|
||||
I didn't use the NuGet package as I had a few minor changes I wanted to make, and I want to learn from it as well via hands-on experience.
|
||||
639
src/Ryujinx.Graphics.RenderDocApi/RenderDoc.cs
Normal file
639
src/Ryujinx.Graphics.RenderDocApi/RenderDoc.cs
Normal file
|
|
@ -0,0 +1,639 @@
|
|||
using System;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Ryujinx.Graphics.RenderDocApi
|
||||
{
|
||||
public static unsafe partial class RenderDoc
|
||||
{
|
||||
/// <summary>
|
||||
/// True if the API is available.
|
||||
/// </summary>
|
||||
public static bool IsAvailable => Api != null;
|
||||
|
||||
/// <summary>
|
||||
/// Set the minimum version of the API you require.
|
||||
/// </summary>
|
||||
/// <remarks>Set this before you do anything else with the RenderDoc API, including <see cref="IsAvailable"/>.</remarks>
|
||||
public static RenderDocVersion MinimumRequired { get; set; } = RenderDocVersion.Version_1_0_0;
|
||||
|
||||
/// <summary>
|
||||
/// Set to true to assert versions.
|
||||
/// </summary>
|
||||
public static bool AssertVersionEnabled { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Version of the API available.
|
||||
/// </summary>
|
||||
[MemberNotNullWhen(true, nameof(IsAvailable))]
|
||||
public static Version? Version
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!IsAvailable)
|
||||
return null;
|
||||
|
||||
int major, minor, build;
|
||||
Api->GetApiVersion(&major, &minor, &build);
|
||||
return new Version(major, minor, build);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The current mask which determines what sections of the overlay render on each window.
|
||||
/// </summary>
|
||||
[RenderDocApiVersion(1, 0)]
|
||||
public static OverlayBits OverlayBits
|
||||
{
|
||||
get => Api->GetOverlayBits();
|
||||
set
|
||||
{
|
||||
Api->MaskOverlayBits(~value, value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The template for new captures.<br/>
|
||||
/// The template can either be a relative or absolute path, which determines where captures will be saved and how they will be named.
|
||||
/// If the path template is 'my_captures/example', then captures saved will be e.g.
|
||||
/// 'my_captures/example_frame123.rdc' and 'my_captures/example_frame456.rdc'.<br/>
|
||||
/// Relative paths will be saved relative to the process’s current working directory.<br/>
|
||||
/// </summary>
|
||||
/// <remarks>The default template is in a folder controlled by the UI - initially the system temporary folder, and the filename is the executable’s filename.</remarks>
|
||||
[RenderDocApiVersion(1, 0)]
|
||||
public static string CaptureFilePathTemplate
|
||||
{
|
||||
get
|
||||
{
|
||||
byte* ptr = Api->GetCaptureFilePathTemplate();
|
||||
return Marshal.PtrToStringUTF8((nint)ptr)!;
|
||||
}
|
||||
set
|
||||
{
|
||||
fixed (byte* ptr = value.ToNullTerminatedByteArray())
|
||||
{
|
||||
Api->SetCaptureFilePathTemplate(ptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The amount of frame captures that have been made.
|
||||
/// </summary>
|
||||
[RenderDocApiVersion(1, 0)]
|
||||
public static int CaptureCount => Api->GetNumCaptures();
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the RenderDoc UI is currently connected to this process.
|
||||
/// </summary>
|
||||
[RenderDocApiVersion(1, 0)]
|
||||
public static bool IsTargetControlConnected => Api is not null && Api->IsTargetControlConnected() != 0;
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the current frame is capturing.
|
||||
/// </summary>
|
||||
[RenderDocApiVersion(1, 0)]
|
||||
public static bool IsFrameCapturing => Api is not null && Api->IsFrameCapturing() != 0;
|
||||
|
||||
/// <summary>
|
||||
/// Set one of the options for tweaking some behaviors of capturing.
|
||||
/// </summary>
|
||||
/// <param name="option">specifies which capture option should be set.</param>
|
||||
/// <param name="integer">the unsigned integer value to set for the option.</param>
|
||||
/// <remarks>Note that each option only takes effect from after it is set - so it is advised to set these options as early as possible, ideally before any graphics API has been initialized.</remarks>
|
||||
/// <returns>
|
||||
/// true, if the <paramref name="option"/> is valid, and the value set on the option is within valid ranges.<br/>
|
||||
/// false, if the option is not a <see cref="CaptureOption"/>, or the value is not valid for the option.
|
||||
/// </returns>
|
||||
[RenderDocApiVersion(1, 0)]
|
||||
public static bool SetCaptureOption(CaptureOption option, uint integer)
|
||||
{
|
||||
return Api is not null && Api->SetCaptureOptionU32(option, integer) != 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set one of the options for tweaking some behaviors of capturing.
|
||||
/// </summary>
|
||||
/// <param name="option">specifies which capture option should be set.</param>
|
||||
/// <param name="boolean">the value to set for the option, converted to a 0 or 1 before setting.</param>
|
||||
/// <remarks>Note that each option only takes effect from after it is set - so it is advised to set these options as early as possible, ideally before any graphics API has been initialized.</remarks>
|
||||
/// <returns>
|
||||
/// true, if the <paramref name="option"/> is valid, and the value set on the option is within valid ranges.<br/>
|
||||
/// false, if the option is not a <see cref="CaptureOption"/>, or the value is not valid for the option.
|
||||
/// </returns>
|
||||
[RenderDocApiVersion(1, 0)]
|
||||
public static bool SetCaptureOption(CaptureOption option, bool boolean)
|
||||
=> SetCaptureOption(option, boolean ? 1 : 0);
|
||||
|
||||
/// <summary>
|
||||
/// Set one of the options for tweaking some behaviors of capturing.
|
||||
/// </summary>
|
||||
/// <param name="option">specifies which capture option should be set.</param>
|
||||
/// <param name="single">the floating point value to set for the option.</param>
|
||||
/// <remarks>Note that each option only takes effect from after it is set - so it is advised to set these options as early as possible, ideally before any graphics API has been initialized.</remarks>
|
||||
/// <returns>
|
||||
/// true, if the <paramref name="option"/> is valid, and the value set on the option is within valid ranges.<br/>
|
||||
/// false, if the option is not a <see cref="CaptureOption"/>, or the value is not valid for the option.
|
||||
/// </returns>
|
||||
[RenderDocApiVersion(1, 0)]
|
||||
public static bool SetCaptureOption(CaptureOption option, float single)
|
||||
{
|
||||
return Api is not null && Api->SetCaptureOptionF32(option, single) != 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current value of one of the different options in <see cref="CaptureOption"/>, writing it to an out parameter.
|
||||
/// </summary>
|
||||
/// <param name="option">specifies which capture option should be retrieved.</param>
|
||||
/// <param name="integer">the value of the capture option, if the option is a valid <see cref="CaptureOption"/> enum. Otherwise, <see cref="int.MaxValue"/>.</param>
|
||||
[RenderDocApiVersion(1, 0)]
|
||||
public static void GetCaptureOption(CaptureOption option, out uint integer)
|
||||
{
|
||||
integer = Api->GetCaptureOptionU32(option);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current value of one of the different options in <see cref="CaptureOption"/>, writing it to an out parameter.
|
||||
/// </summary>
|
||||
/// <param name="option">specifies which capture option should be retrieved.</param>
|
||||
/// <param name="single">the value of the capture option, if the option is a valid <see cref="CaptureOption"/> enum. Otherwise, -<see cref="float.MaxValue"/>.</param>
|
||||
[RenderDocApiVersion(1, 0)]
|
||||
public static void GetCaptureOption(CaptureOption option, out float single)
|
||||
{
|
||||
single = Api->GetCaptureOptionF32(option);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current value of one of the different options in <see cref="CaptureOption"/>,
|
||||
/// converted to a boolean.
|
||||
/// </summary>
|
||||
/// <param name="option">specifies which capture option should be retrieved.</param>
|
||||
/// <returns>
|
||||
/// the value of the capture option, converted to bool, if the option is a valid <see cref="CaptureOption"/> enum.
|
||||
/// Otherwise, returns null.
|
||||
/// </returns>
|
||||
[RenderDocApiVersion(1, 0)]
|
||||
public static bool? GetCaptureOptionBool(CaptureOption option)
|
||||
{
|
||||
if (Api is null) return false;
|
||||
|
||||
uint returnVal = GetCaptureOptionU32(option);
|
||||
if (returnVal == uint.MaxValue)
|
||||
return null;
|
||||
|
||||
return returnVal is not 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current value of one of the different options in <see cref="CaptureOption"/>.
|
||||
/// </summary>
|
||||
/// <param name="option">specifies which capture option should be retrieved.</param>
|
||||
/// <returns>
|
||||
/// the value of the capture option, if the option is a valid <see cref="CaptureOption"/> enum.
|
||||
/// Otherwise, returns <see cref="int.MaxValue"/>.
|
||||
/// </returns>
|
||||
[RenderDocApiVersion(1, 0)]
|
||||
public static uint GetCaptureOptionU32(CaptureOption option) => Api->GetCaptureOptionU32(option);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current value of one of the different options in <see cref="CaptureOption"/>.
|
||||
/// </summary>
|
||||
/// <param name="option">specifies which capture option should be retrieved.</param>
|
||||
/// <returns>
|
||||
/// the value of the capture option, if the option is a valid <see cref="CaptureOption"/> enum.
|
||||
/// Otherwise, returns -<see cref="float.MaxValue"/>.
|
||||
/// </returns>
|
||||
[RenderDocApiVersion(1, 0)]
|
||||
public static float GetCaptureOptionF32(CaptureOption option) => Api->GetCaptureOptionF32(option);
|
||||
|
||||
/// <summary>
|
||||
/// Changes the key bindings in-application for changing the focussed window.
|
||||
/// </summary>
|
||||
/// <param name="buttons">lists the keys to bind.</param>
|
||||
[RenderDocApiVersion(1, 0)]
|
||||
public static void SetFocusToggleKeys(ReadOnlySpan<InputButton> buttons)
|
||||
{
|
||||
if (Api is null) return;
|
||||
|
||||
fixed (InputButton* ptr = buttons)
|
||||
{
|
||||
Api->SetFocusToggleKeys(ptr, buttons.Length);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes the key bindings in-application for triggering a capture on the current window.
|
||||
/// </summary>
|
||||
/// <param name="buttons">lists the keys to bind.</param>
|
||||
[RenderDocApiVersion(1, 0)]
|
||||
public static void SetCaptureKeys(ReadOnlySpan<InputButton> buttons)
|
||||
{
|
||||
if (Api is null) return;
|
||||
|
||||
fixed (InputButton* ptr = buttons)
|
||||
{
|
||||
Api->SetCaptureKeys(ptr, buttons.Length);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to remove RenderDoc and its hooks from the target process.<br/>
|
||||
/// It must be called as early as possible in the process, and will have undefined results
|
||||
/// if any graphics API functions have been called.
|
||||
/// </summary>
|
||||
[RenderDocApiVersion(1, 0)]
|
||||
public static void RemoveHooks()
|
||||
{
|
||||
if (Api is null) return;
|
||||
|
||||
Api->RemoveHooks();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove RenderDoc’s crash handler from the target process.<br/>
|
||||
/// If you have your own crash handler that you want to handle any exceptions,
|
||||
/// RenderDoc’s handler could interfere; so it can be disabled.
|
||||
/// </summary>
|
||||
[RenderDocApiVersion(1, 0)]
|
||||
public static void UnloadCrashHandler()
|
||||
{
|
||||
if (Api is null) return;
|
||||
|
||||
Api->UnloadCrashHandler();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Trigger a capture as if the user had pressed one of the capture hotkeys.<br/>
|
||||
/// The capture will be taken from the next frame presented to whichever window is considered current.
|
||||
/// </summary>
|
||||
[RenderDocApiVersion(1, 0)]
|
||||
public static void TriggerCapture()
|
||||
{
|
||||
if (Api is null) return;
|
||||
|
||||
Api->TriggerCapture();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets the details of all frame capture in the current session.
|
||||
/// This simply calls <see cref="GetCapture"/> for each index available as specified by <see cref="CaptureCount"/>.
|
||||
/// </summary>
|
||||
/// <returns>An immutable array of structs representing RenderDoc Captures.</returns>
|
||||
public static ImmutableArray<Capture> GetCaptures()
|
||||
{
|
||||
if (Api is null) return [];
|
||||
int captureCount = CaptureCount;
|
||||
if (captureCount is 0) return [];
|
||||
|
||||
ImmutableArray<Capture>.Builder captures = ImmutableArray.CreateBuilder<Capture>(captureCount);
|
||||
|
||||
for (int captureIndex = 0; captureIndex < captureCount; captureIndex++)
|
||||
{
|
||||
if (GetCapture(captureIndex) is { } capture)
|
||||
captures.Add(capture);
|
||||
}
|
||||
|
||||
return captures.DrainToImmutable();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the details of a particular frame capture, as specified by an index from 0 to <see cref="CaptureCount"/> - 1.
|
||||
/// </summary>
|
||||
/// <param name="index">specifies which capture to return the details of. Must be less than the value returned by <see cref="CaptureCount"/>.</param>
|
||||
/// <returns>A struct representing a RenderDoc Capture.</returns>
|
||||
[RenderDocApiVersion(1, 0)]
|
||||
public static Capture? GetCapture(int index)
|
||||
{
|
||||
if (Api is null) return null;
|
||||
|
||||
int length = 0;
|
||||
if (Api->GetCapture(index, null, &length, null) == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
Span<byte> bytes = stackalloc byte[length + 1];
|
||||
long timestamp;
|
||||
|
||||
fixed (byte* ptr = bytes)
|
||||
Api->GetCapture(index, ptr, &length, ×tamp);
|
||||
|
||||
string fileName = Encoding.UTF8.GetString(bytes[length..]);
|
||||
return new Capture(index, fileName, DateTimeOffset.FromUnixTimeSeconds(timestamp).DateTime);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determine the closest matching replay UI executable for the current RenderDoc module, and launch it.
|
||||
/// </summary>
|
||||
/// <param name="connectTargetControl">if the UI should immediately connect to the application.</param>
|
||||
/// <param name="commandLine">string to be appended to the command line, e.g. a capture filename. If this parameter is null, the command line will be unmodified.</param>
|
||||
/// <returns>true if the UI was successfully launched; false otherwise.</returns>
|
||||
[RenderDocApiVersion(1, 0)]
|
||||
public static bool LaunchReplayUI(bool connectTargetControl, string? commandLine = null)
|
||||
{
|
||||
if (Api is null) return false;
|
||||
|
||||
if (commandLine == null)
|
||||
{
|
||||
return Api->LaunchReplayUI(connectTargetControl ? 1u : 0u, null) != 0;
|
||||
}
|
||||
|
||||
fixed (byte* ptr = commandLine.ToNullTerminatedByteArray())
|
||||
{
|
||||
return Api->LaunchReplayUI(connectTargetControl ? 1u : 0u, ptr) != 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Explicitly sets which window is considered active.<br/>
|
||||
/// The active window is the one that will be captured when the keybind to trigger a capture is pressed.
|
||||
/// </summary>
|
||||
/// <param name="hDevice">a handle to the API ‘device’ object that will be set active. Must be valid.</param>
|
||||
/// <param name="hWindow">a handle to the platform window handle that will be set active. Must be valid.</param>
|
||||
[RenderDocApiVersion(1, 0)]
|
||||
public static void SetActiveWindow(nint hDevice, nint hWindow)
|
||||
{
|
||||
if (Api is null) return;
|
||||
|
||||
Api->SetActiveWindow((void*)hDevice, (void*)hWindow);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Immediately begin a capture for the specified device/window combination.
|
||||
/// </summary>
|
||||
/// <param name="hDevice">a handle to the API ‘device’ object that will be set active. May be <see cref="nint.Zero"/> to wildcard match.</param>
|
||||
/// <param name="hWindow">a handle to the platform window handle that will be set active. May be <see cref="nint.Zero"/> to wildcard match.</param>
|
||||
[RenderDocApiVersion(1, 0)]
|
||||
public static void StartFrameCapture(nint hDevice, nint hWindow)
|
||||
{
|
||||
if (Api is null) return;
|
||||
|
||||
Api->StartFrameCapture((void*)hDevice, (void*)hWindow);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Immediately end an active capture for the specified device/window combination.
|
||||
/// </summary>
|
||||
/// <param name="hDevice">a handle to the API ‘device’ object that will be set active. May be <see cref="nint.Zero"/> to wildcard match.</param>
|
||||
/// <param name="hWindow">a handle to the platform window handle that will be set active. May be <see cref="nint.Zero"/> to wildcard match.</param>
|
||||
/// <returns>true if the capture succeeded; false otherwise.</returns>
|
||||
[RenderDocApiVersion(1, 0)]
|
||||
public static bool EndFrameCapture(nint hDevice, nint hWindow)
|
||||
{
|
||||
if (Api is null) return false;
|
||||
|
||||
return Api->EndFrameCapture((void*)hDevice, (void*)hWindow) != 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Trigger multiple sequential frame captures as if the user had pressed one of the capture hotkeys before each frame.<br/>
|
||||
/// The captures will be taken from the next frames presented to whichever window is considered current.<br/>
|
||||
/// Each capture will be taken independently and saved to a separate file, with no reference to the other frames.
|
||||
/// </summary>
|
||||
/// <param name="numFrames">the number of frames to capture.</param>
|
||||
/// <remarks>Requires RenderDoc API version 1.1</remarks>
|
||||
[RenderDocApiVersion(1, 1)]
|
||||
public static void TriggerMultiFrameCapture(uint numFrames)
|
||||
{
|
||||
if (Api is null) return;
|
||||
|
||||
AssertAtLeast(1, 1);
|
||||
Api->TriggerMultiFrameCapture(numFrames);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds an arbitrary comments field to the most recent capture,
|
||||
/// which will then be displayed in the UI to anyone opening the capture.
|
||||
/// <br/><br/>
|
||||
/// This is equivalent to calling <see cref="SetCaptureFileComments"/> with a null first (fileName) parameter.
|
||||
/// </summary>
|
||||
/// <param name="comments">the comments to set in the capture file.</param>
|
||||
/// <remarks>Requires RenderDoc API version 1.2</remarks>
|
||||
public static void SetMostRecentCaptureFileComments(string comments)
|
||||
{
|
||||
if (Api is null) return;
|
||||
|
||||
AssertAtLeast(1, 2);
|
||||
|
||||
byte[] commentBytes = comments.ToNullTerminatedByteArray();
|
||||
|
||||
fixed (byte* pcomment = commentBytes)
|
||||
{
|
||||
Api->SetCaptureFileComments((byte*)nint.Zero, pcomment);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds an arbitrary comments field to an existing capture on disk,
|
||||
/// which will then be displayed in the UI to anyone opening the capture.
|
||||
/// </summary>
|
||||
/// <param name="fileName">the path to the capture file to set comments in. If this path is null or an empty string, the most recent capture file that has been created will be used.</param>
|
||||
/// <param name="comments">the comments to set in the capture file.</param>
|
||||
/// <remarks>Requires RenderDoc API version 1.2</remarks>
|
||||
[RenderDocApiVersion(1, 2)]
|
||||
public static void SetCaptureFileComments(string? fileName, string comments)
|
||||
{
|
||||
if (Api is null) return;
|
||||
|
||||
AssertAtLeast(1, 2);
|
||||
|
||||
byte[] commentBytes = comments.ToNullTerminatedByteArray();
|
||||
|
||||
fixed (byte* pcomment = commentBytes)
|
||||
{
|
||||
if (fileName is null)
|
||||
{
|
||||
Api->SetCaptureFileComments((byte*)nint.Zero, pcomment);
|
||||
}
|
||||
else
|
||||
{
|
||||
byte[] fileBytes = fileName.ToNullTerminatedByteArray();
|
||||
|
||||
fixed (byte* pfile = fileBytes)
|
||||
{
|
||||
Api->SetCaptureFileComments(pfile, pcomment);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Similar to <see cref="EndFrameCapture"/>, but the capture contents will be discarded immediately, and not processed and written to disk.<br/>
|
||||
/// This will be more efficient than <see cref="EndFrameCapture"/> if the frame capture is not needed.
|
||||
/// </summary>
|
||||
/// <param name="hDevice">a handle to the API ‘device’ object that will be set active. May be <see cref="nint.Zero"/> to wildcard match.</param>
|
||||
/// <param name="hWindow">a handle to the platform window handle that will be set active. May be <see cref="nint.Zero"/> to wildcard match.</param>
|
||||
/// <returns>true if the capture was discarded; false if there was an error or no capture was in progress.</returns>
|
||||
/// <remarks>Requires RenderDoc API version 1.4</remarks>
|
||||
[RenderDocApiVersion(1, 4)]
|
||||
public static bool DiscardFrameCapture(nint hDevice, nint hWindow)
|
||||
{
|
||||
if (Api is null) return false;
|
||||
|
||||
AssertAtLeast(1, 4);
|
||||
return Api->DiscardFrameCapture((void*)hDevice, (void*)hWindow) != 0;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Requests that the currently connected replay UI raise its window to the top.<br/>
|
||||
/// This is only possible if an instance of the replay UI is currently connected, otherwise this method does nothing.<br/>
|
||||
/// This can be used in conjunction with <see cref="IsTargetControlConnected"/> and <see cref="LaunchReplayUI"/>,<br/> to intelligently handle showing the UI after making a capture.<br/><br/>
|
||||
/// Given OS differences, it is not guaranteed that the UI will be successfully raised even if the request is passed on.<br/>
|
||||
/// On some systems it may only be highlighted or otherwise indicated to the user.
|
||||
/// </summary>
|
||||
/// <returns>true if the request was passed onto the UI successfully; false if there is no UI connected or some other error occurred.</returns>
|
||||
/// <remarks>Requires RenderDoc API version 1.5</remarks>
|
||||
[RenderDocApiVersion(1, 5)]
|
||||
public static bool ShowReplayUI()
|
||||
{
|
||||
if (Api is null) return false;
|
||||
|
||||
AssertAtLeast(1, 5);
|
||||
return Api->ShowReplayUI() != 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a given title for the currently in-progress capture, which will be displayed in the UI.<br/>
|
||||
/// This can be used either with a user-defined capture using a manual start and end,
|
||||
/// or an automatic capture triggered by <see cref="TriggerCapture"/> or a keypress.<br/>
|
||||
/// If multiple captures are ongoing at once, the title will be applied to the first capture to end only.<br/>
|
||||
/// Any subsequent captures will not get any title unless the function is called again.
|
||||
/// This function can only be called while a capture is in-progress,
|
||||
/// after <see cref="StartFrameCapture"/> and before <see cref="EndFrameCapture"/>.<br/>
|
||||
/// If it is called elsewhere it will have no effect.
|
||||
/// If it is called multiple times within a capture, only the last title will have any effect.
|
||||
/// </summary>
|
||||
/// <param name="title">The title to set for the in-progress capture.</param>
|
||||
/// <remarks>Requires RenderDoc API version 1.6</remarks>
|
||||
[RenderDocApiVersion(1, 6)]
|
||||
public static void SetCaptureTitle(string title)
|
||||
{
|
||||
if (Api is null) return;
|
||||
|
||||
AssertAtLeast(1, 6);
|
||||
fixed (byte* ptr = title.ToNullTerminatedByteArray())
|
||||
Api->SetCaptureTitle(ptr);
|
||||
}
|
||||
|
||||
#region Dynamic Library loading
|
||||
|
||||
/// <summary>
|
||||
/// Reload the internal RenderDoc API structure. Useful for manually refreshing <see cref="Api"/> while using process injection.
|
||||
/// </summary>
|
||||
/// <param name="ignoreAlreadyLoaded">Ignores the existing API function structure and overwrites it with a re-request.</param>
|
||||
/// <param name="requiredVersion">The version of the RenderDoc API required by your application.</param>
|
||||
public static void ReloadApi(bool ignoreAlreadyLoaded = false, RenderDocVersion? requiredVersion = null)
|
||||
{
|
||||
if (_loaded && !ignoreAlreadyLoaded)
|
||||
return;
|
||||
|
||||
lock (typeof(RenderDoc))
|
||||
{
|
||||
// Prevent double loads.
|
||||
if (_loaded && !ignoreAlreadyLoaded)
|
||||
return;
|
||||
|
||||
if (requiredVersion.HasValue)
|
||||
MinimumRequired = requiredVersion.Value;
|
||||
|
||||
_loaded = true;
|
||||
_api = GetApi(MinimumRequired);
|
||||
|
||||
if (_api != null)
|
||||
AssertAtLeast(MinimumRequired);
|
||||
}
|
||||
}
|
||||
|
||||
private static RenderDocApi* _api = null;
|
||||
private static bool _loaded;
|
||||
|
||||
private static RenderDocApi* Api
|
||||
{
|
||||
get
|
||||
{
|
||||
ReloadApi();
|
||||
return _api;
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly Regex _dynamicLibraryPattern = RenderDocApiDynamicLibraryRegex();
|
||||
|
||||
private static RenderDocApi* GetApi(RenderDocVersion minimumRequired = RenderDocVersion.Version_1_0_0)
|
||||
{
|
||||
foreach (ProcessModule module in Process.GetCurrentProcess().Modules)
|
||||
{
|
||||
string moduleName = module.FileName ?? string.Empty;
|
||||
|
||||
if (!_dynamicLibraryPattern.IsMatch(moduleName))
|
||||
continue;
|
||||
|
||||
if (!NativeLibrary.TryLoad(moduleName, out nint moduleHandle))
|
||||
return null;
|
||||
|
||||
if (!NativeLibrary.TryGetExport(moduleHandle, "RENDERDOC_GetAPI", out nint procAddress))
|
||||
return null;
|
||||
|
||||
var RENDERDOC_GetApi = (delegate* unmanaged[Cdecl]<RenderDocVersion, RenderDocApi**, int>)procAddress;
|
||||
|
||||
RenderDocApi* api;
|
||||
return RENDERDOC_GetApi(minimumRequired, &api) != 0 ? api : null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void AssertAtLeast(RenderDocVersion rdv, [CallerMemberName] string callee = "")
|
||||
{
|
||||
Version ver = rdv.SystemVersion;
|
||||
AssertAtLeast(ver.Major, ver.Minor, ver.Build, callee);
|
||||
}
|
||||
|
||||
private static void AssertAtLeast(int major, int minor, int patch = 0, [CallerMemberName] string callee = "")
|
||||
{
|
||||
if (!AssertVersionEnabled)
|
||||
return;
|
||||
|
||||
if (Version!.Major < major)
|
||||
goto fail;
|
||||
|
||||
if (Version.Major > major)
|
||||
goto success;
|
||||
if (Version.Minor < minor)
|
||||
goto fail;
|
||||
if (Version.Minor > minor)
|
||||
goto success;
|
||||
if (Version.Build < patch)
|
||||
goto fail;
|
||||
|
||||
success:
|
||||
return;
|
||||
|
||||
fail:
|
||||
Version minVersion =
|
||||
typeof(RenderDoc).GetMethod(callee)!.GetCustomAttribute<RenderDocApiVersionAttribute>()!.MinVersion;
|
||||
throw new NotSupportedException(
|
||||
$"This API was introduced in RenderDoc API {minVersion}. Current API version is {Version}.");
|
||||
}
|
||||
|
||||
private static byte[] ToNullTerminatedByteArray(this string str, Encoding? encoding = null)
|
||||
{
|
||||
encoding ??= Encoding.UTF8;
|
||||
|
||||
return encoding.GetBytes(str + '\0');
|
||||
}
|
||||
|
||||
[GeneratedRegex(@"(lib)?renderdoc(\.dll|\.so|\.dylib)(\.\d+)?",
|
||||
RegexOptions.IgnoreCase | RegexOptions.CultureInvariant)]
|
||||
private static partial Regex RenderDocApiDynamicLibraryRegex();
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
51
src/Ryujinx.Graphics.RenderDocApi/RenderDocApi.cs
Normal file
51
src/Ryujinx.Graphics.RenderDocApi/RenderDocApi.cs
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
namespace Ryujinx.Graphics.RenderDocApi
|
||||
{
|
||||
#pragma warning disable CS0649
|
||||
internal unsafe struct RenderDocApi
|
||||
{
|
||||
public delegate* unmanaged[Cdecl]<int*, int*, int*, void> GetApiVersion;
|
||||
|
||||
public delegate* unmanaged[Cdecl]<CaptureOption, uint, int> SetCaptureOptionU32;
|
||||
public delegate* unmanaged[Cdecl]<CaptureOption, float, int> SetCaptureOptionF32;
|
||||
public delegate* unmanaged[Cdecl]<CaptureOption, uint> GetCaptureOptionU32;
|
||||
public delegate* unmanaged[Cdecl]<CaptureOption, float> GetCaptureOptionF32;
|
||||
|
||||
public delegate* unmanaged[Cdecl]<InputButton*, int, void> SetFocusToggleKeys;
|
||||
public delegate* unmanaged[Cdecl]<InputButton*, int, void> SetCaptureKeys;
|
||||
|
||||
public delegate* unmanaged[Cdecl]<OverlayBits> GetOverlayBits;
|
||||
public delegate* unmanaged[Cdecl]<OverlayBits, OverlayBits, void> MaskOverlayBits;
|
||||
|
||||
public delegate* unmanaged[Cdecl]<void> RemoveHooks;
|
||||
public delegate* unmanaged[Cdecl]<void> UnloadCrashHandler;
|
||||
public delegate* unmanaged[Cdecl]<byte*, void> SetCaptureFilePathTemplate;
|
||||
public delegate* unmanaged[Cdecl]<byte*> GetCaptureFilePathTemplate;
|
||||
|
||||
public delegate* unmanaged[Cdecl]<int> GetNumCaptures;
|
||||
public delegate* unmanaged[Cdecl]<int, byte*, int*, long*, uint> GetCapture;
|
||||
public delegate* unmanaged[Cdecl]<void> TriggerCapture;
|
||||
public delegate* unmanaged[Cdecl]<uint> IsTargetControlConnected;
|
||||
public delegate* unmanaged[Cdecl]<uint, byte*, uint> LaunchReplayUI;
|
||||
|
||||
public delegate* unmanaged[Cdecl]<void*, void*, void> SetActiveWindow;
|
||||
public delegate* unmanaged[Cdecl]<void*, void*, void> StartFrameCapture;
|
||||
public delegate* unmanaged[Cdecl]<uint> IsFrameCapturing;
|
||||
public delegate* unmanaged[Cdecl]<void*, void*, uint> EndFrameCapture;
|
||||
|
||||
// 1.1
|
||||
public delegate* unmanaged[Cdecl]<uint, void> TriggerMultiFrameCapture;
|
||||
|
||||
// 1.2
|
||||
public delegate* unmanaged[Cdecl]<byte*, byte*, void> SetCaptureFileComments;
|
||||
|
||||
// 1.3
|
||||
public delegate* unmanaged[Cdecl]<void*, void*, uint> DiscardFrameCapture;
|
||||
|
||||
// 1.5
|
||||
public delegate* unmanaged[Cdecl]<uint> ShowReplayUI;
|
||||
|
||||
// 1.6
|
||||
public delegate* unmanaged[Cdecl]<byte*, void> SetCaptureTitle;
|
||||
}
|
||||
#pragma warning restore CS0649
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.RenderDocApi
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property)]
|
||||
public sealed class RenderDocApiVersionAttribute : Attribute
|
||||
{
|
||||
public Version MinVersion { get; }
|
||||
|
||||
public RenderDocApiVersionAttribute(int major, int minor, int patch = 0)
|
||||
{
|
||||
MinVersion = new Version(major, minor, patch);
|
||||
}
|
||||
}
|
||||
}
|
||||
47
src/Ryujinx.Graphics.RenderDocApi/RenderDocVersion.cs
Normal file
47
src/Ryujinx.Graphics.RenderDocApi/RenderDocVersion.cs
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.RenderDocApi
|
||||
{
|
||||
public enum RenderDocVersion
|
||||
{
|
||||
Version_1_0_0 = 10000,
|
||||
Version_1_0_1 = 10001,
|
||||
Version_1_0_2 = 10002,
|
||||
Version_1_1_0 = 10100,
|
||||
Version_1_1_1 = 10101,
|
||||
Version_1_1_2 = 10102,
|
||||
Version_1_2_0 = 10200,
|
||||
Version_1_3_0 = 10300,
|
||||
Version_1_4_0 = 10400,
|
||||
Version_1_4_1 = 10401,
|
||||
Version_1_4_2 = 10402,
|
||||
Version_1_5_0 = 10500,
|
||||
Version_1_6_0 = 10600,
|
||||
}
|
||||
|
||||
public static partial class Helpers
|
||||
{
|
||||
extension(RenderDocVersion rdv)
|
||||
{
|
||||
public Version SystemVersion
|
||||
{
|
||||
get
|
||||
{
|
||||
int i = (int)rdv;
|
||||
return new (i / 10000, (i % 10000) / 100, i % 100);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension(Version sv)
|
||||
{
|
||||
public RenderDocVersion RenderDocVersion
|
||||
{
|
||||
get
|
||||
{
|
||||
return (RenderDocVersion)(sv.Major * 10000 + sv.Minor * 100 + sv.Build);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>disable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
|
@ -26,12 +26,8 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
|
|||
// - Both branches are jumping to the same location.
|
||||
// In this case, the branch on the current block can be removed,
|
||||
// as the next block is going to jump to the same place anyway.
|
||||
if (nextBlock == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (nextBlock.Operations.First?.Value is not Operation next)
|
||||
if (nextBlock?.Operations.First?.Value is not Operation next)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,17 +71,31 @@ namespace Ryujinx.Graphics.Vulkan
|
|||
HasDepthStencil = isDepthStencil;
|
||||
}
|
||||
|
||||
public FramebufferParams(Device device, ITexture[] colors, ITexture depthStencil)
|
||||
public FramebufferParams(Device device, ReadOnlySpan<ITexture> colors, ITexture depthStencil)
|
||||
{
|
||||
_device = device;
|
||||
|
||||
int colorsCount = colors.Count(IsValidTextureView);
|
||||
int colorsCount = 0;
|
||||
_colorsCanonical = new TextureView[colors.Length];
|
||||
|
||||
for (int i = 0; i < colors.Length; i++)
|
||||
{
|
||||
ITexture color = colors[i];
|
||||
if (color is TextureView { Valid: true } view)
|
||||
{
|
||||
colorsCount++;
|
||||
_colorsCanonical[i] = view;
|
||||
}
|
||||
else
|
||||
{
|
||||
_colorsCanonical[i] = null;
|
||||
}
|
||||
}
|
||||
|
||||
int count = colorsCount + (IsValidTextureView(depthStencil) ? 1 : 0);
|
||||
|
||||
_attachments = new Auto<DisposableImageView>[count];
|
||||
_colors = new TextureView[colorsCount];
|
||||
_colorsCanonical = colors.Select(color => color is TextureView view && view.Valid ? view : null).ToArray();
|
||||
|
||||
AttachmentSamples = new uint[count];
|
||||
AttachmentFormats = new VkFormat[count];
|
||||
|
|
@ -165,9 +179,17 @@ namespace Ryujinx.Graphics.Vulkan
|
|||
_totalCount = colors.Length;
|
||||
}
|
||||
|
||||
public FramebufferParams Update(ITexture[] colors, ITexture depthStencil)
|
||||
public FramebufferParams Update(ReadOnlySpan<ITexture> colors, ITexture depthStencil)
|
||||
{
|
||||
int colorsCount = colors.Count(IsValidTextureView);
|
||||
int colorsCount = 0;
|
||||
|
||||
foreach (ITexture color in colors)
|
||||
{
|
||||
if (IsValidTextureView(color))
|
||||
{
|
||||
colorsCount++;
|
||||
}
|
||||
}
|
||||
|
||||
int count = colorsCount + (IsValidTextureView(depthStencil) ? 1 : 0);
|
||||
|
||||
|
|
|
|||
32
src/Ryujinx.Graphics.Vulkan/Helpers.cs
Normal file
32
src/Ryujinx.Graphics.Vulkan/Helpers.cs
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
using Silk.NET.Vulkan;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
public static class Helpers
|
||||
{
|
||||
extension(Vk api)
|
||||
{
|
||||
/// <summary>
|
||||
/// C# implementation of the RENDERDOC_DEVICEPOINTER_FROM_VKINSTANCE macro from the RenderDoc API header, since we cannot use macros from C#.
|
||||
/// </summary>
|
||||
/// <returns>The dispatch table pointer, which sits as the first pointer-sized object in the memory pointed to by the <see cref="Vk"/>'s <see cref="Instance"/> pointer.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public unsafe void* GetRenderDocDevicePointer() =>
|
||||
api.CurrentInstance is not null
|
||||
? api.CurrentInstance.Value.GetRenderDocDevicePointer()
|
||||
: null;
|
||||
}
|
||||
|
||||
extension(Instance instance)
|
||||
{
|
||||
/// <summary>
|
||||
/// C# implementation of the RENDERDOC_DEVICEPOINTER_FROM_VKINSTANCE macro from the RenderDoc API header, since we cannot use macros from C#.
|
||||
/// </summary>
|
||||
/// <returns>The dispatch table pointer, which sits as the first pointer-sized object in the memory pointed to by the <see cref="Instance"/>'s pointer.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public unsafe void* GetRenderDocDevicePointer()
|
||||
=> (*((void**)(instance.Handle)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Memory;
|
||||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
|
|
@ -10,6 +10,8 @@ namespace Ryujinx.Graphics.Vulkan
|
|||
/// </summary>
|
||||
class MultiFenceHolder
|
||||
{
|
||||
public static readonly ObjectPool<FenceHolder[]> FencePool = new(() => new FenceHolder[CommandBufferPool.MaxCommandBuffers]);
|
||||
|
||||
private const int BufferUsageTrackingGranularity = 4096;
|
||||
|
||||
public FenceHolder[] Fences { get; }
|
||||
|
|
@ -20,7 +22,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||
/// </summary>
|
||||
public MultiFenceHolder()
|
||||
{
|
||||
Fences = ArrayPool<FenceHolder>.Shared.Rent(CommandBufferPool.MaxCommandBuffers);
|
||||
Fences = FencePool.Allocate();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -29,7 +31,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||
/// <param name="size">Size of the buffer</param>
|
||||
public MultiFenceHolder(int size)
|
||||
{
|
||||
Fences = ArrayPool<FenceHolder>.Shared.Rent(CommandBufferPool.MaxCommandBuffers);
|
||||
Fences = FencePool.Allocate();
|
||||
_bufferUsageBitmap = new BufferUsageBitmap(size, BufferUsageTrackingGranularity);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1035,7 +1035,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||
}
|
||||
}
|
||||
|
||||
private void SetRenderTargetsInternal(ITexture[] colors, ITexture depthStencil, bool filterWriteMasked)
|
||||
private void SetRenderTargetsInternal(Span<ITexture> colors, ITexture depthStencil, bool filterWriteMasked)
|
||||
{
|
||||
CreateFramebuffer(colors, depthStencil, filterWriteMasked);
|
||||
CreateRenderPass();
|
||||
|
|
@ -1043,7 +1043,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||
SignalAttachmentChange();
|
||||
}
|
||||
|
||||
public void SetRenderTargets(ITexture[] colors, ITexture depthStencil)
|
||||
public void SetRenderTargets(Span<ITexture> colors, ITexture depthStencil)
|
||||
{
|
||||
_framebufferUsingColorWriteMask = false;
|
||||
SetRenderTargetsInternal(colors, depthStencil, Gd.IsTBDR);
|
||||
|
|
@ -1389,7 +1389,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||
_currentPipelineHandle = 0;
|
||||
}
|
||||
|
||||
private void CreateFramebuffer(ITexture[] colors, ITexture depthStencil, bool filterWriteMasked)
|
||||
private void CreateFramebuffer(Span<ITexture> colors, ITexture depthStencil, bool filterWriteMasked)
|
||||
{
|
||||
if (filterWriteMasked)
|
||||
{
|
||||
|
|
@ -1399,7 +1399,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||
// Just try to remove duplicate attachments.
|
||||
// Save a copy of the array to rebind when mask changes.
|
||||
|
||||
void MaskOut()
|
||||
void MaskOut(ReadOnlySpan<ITexture> colors)
|
||||
{
|
||||
if (!_framebufferUsingColorWriteMask)
|
||||
{
|
||||
|
|
@ -1436,12 +1436,12 @@ namespace Ryujinx.Graphics.Vulkan
|
|||
if (vkBlend.ColorWriteMask == 0)
|
||||
{
|
||||
colors[i] = null;
|
||||
MaskOut();
|
||||
MaskOut(colors);
|
||||
}
|
||||
else if (vkBlend2.ColorWriteMask == 0)
|
||||
{
|
||||
colors[j] = null;
|
||||
MaskOut();
|
||||
MaskOut(colors);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
using Ryujinx.Common.Logging;
|
||||
using Silk.NET.Vulkan;
|
||||
using System.Buffers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
|
|
@ -193,7 +193,8 @@ namespace Ryujinx.Graphics.Vulkan
|
|||
{
|
||||
_firstHandle = first.ID + 1;
|
||||
_handles.RemoveAt(0);
|
||||
ArrayPool<FenceHolder>.Shared.Return(first.Waitable.Fences);
|
||||
Array.Clear(first.Waitable.Fences);
|
||||
MultiFenceHolder.FencePool.Release(first.Waitable.Fences);
|
||||
first.Waitable = null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,43 +1,91 @@
|
|||
using Gommon;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.HLE.HOS.Kernel.Memory;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Ryujinx.HLE.Debugger
|
||||
{
|
||||
public partial class Debugger
|
||||
{
|
||||
private sealed record RcmdEntry(string[] Names, Func<Debugger, string, string> Handler, string[] HelpLines);
|
||||
|
||||
// Atmosphere/libraries/libmesosphere/source/kern_k_memory_block_manager.cpp
|
||||
private static readonly string[] _memoryStateNames =
|
||||
{
|
||||
"----- Free -----",
|
||||
"Io ",
|
||||
"Static ",
|
||||
"Code ",
|
||||
"CodeData ",
|
||||
"Normal ",
|
||||
"Shared ",
|
||||
"Alias ",
|
||||
"AliasCode ",
|
||||
"AliasCodeData ",
|
||||
"Ipc ",
|
||||
"Stack ",
|
||||
"ThreadLocal ",
|
||||
"Transfered ",
|
||||
"SharedTransfered",
|
||||
"SharedCode ",
|
||||
"Inaccessible ",
|
||||
"NonSecureIpc ",
|
||||
"NonDeviceIpc ",
|
||||
"Kernel ",
|
||||
"GeneratedCode ",
|
||||
"CodeOut ",
|
||||
"Coverage ",
|
||||
};
|
||||
|
||||
static Debugger()
|
||||
{
|
||||
_rcmdDelegates.Add(["help"],
|
||||
_ => _rcmdDelegates.Keys
|
||||
.Where(x => !x[0].Equals("help"))
|
||||
.Select(x => x.JoinToString('\n'))
|
||||
.JoinToString('\n') + '\n'
|
||||
);
|
||||
_rcmdDelegates.Add(["get info"], dbgr => dbgr.GetProcessInfo());
|
||||
_rcmdDelegates.Add(["backtrace", "bt"], dbgr => dbgr.GetStackTrace());
|
||||
_rcmdDelegates.Add(["registers", "reg"], dbgr => dbgr.GetRegisters());
|
||||
_rcmdDelegates.Add(["minidump"], dbgr => dbgr.GetMinidump());
|
||||
_rcmdDelegates.Add(new RcmdEntry(
|
||||
["help"],
|
||||
(dbgr, _) => _rcmdDelegates
|
||||
.Where(entry => entry.HelpLines.Length > 0)
|
||||
.SelectMany(entry => entry.HelpLines)
|
||||
.JoinToString('\n') + '\n',
|
||||
Array.Empty<string>()));
|
||||
|
||||
_rcmdDelegates.Add(new RcmdEntry(["get info"], (dbgr, _) => dbgr.GetProcessInfo(), ["get info"]));
|
||||
_rcmdDelegates.Add(new RcmdEntry(["backtrace", "bt"], (dbgr, _) => dbgr.GetStackTrace(), ["backtrace", "bt"]));
|
||||
_rcmdDelegates.Add(new RcmdEntry(["registers", "reg"], (dbgr, _) => dbgr.GetRegisters(), ["registers", "reg"]));
|
||||
_rcmdDelegates.Add(new RcmdEntry(["minidump"], (dbgr, _) => dbgr.GetMinidump(), ["minidump"]));
|
||||
_rcmdDelegates.Add(new RcmdEntry(["get mappings"], (dbgr, args) => dbgr.GetMemoryMappings(args), ["get mappings", "get mappings {address}"]));
|
||||
_rcmdDelegates.Add(new RcmdEntry(["get mapping"], (dbgr, args) => dbgr.GetMemoryMapping(args), ["get mapping {address}"]));
|
||||
}
|
||||
|
||||
private static readonly Dictionary<string[], Func<Debugger, string>> _rcmdDelegates = new();
|
||||
private static readonly List<RcmdEntry> _rcmdDelegates = [];
|
||||
|
||||
public static Func<Debugger, string> FindRcmdDelegate(string command)
|
||||
public static string CallRcmdDelegate(Debugger debugger, string command)
|
||||
{
|
||||
Func<Debugger, string> searchResult = _ => $"Unknown command: {command}\n";
|
||||
string originalCommand = command ?? string.Empty;
|
||||
string trimmedCommand = originalCommand.Trim();
|
||||
|
||||
foreach ((string[] names, Func<Debugger, string> dlg) in _rcmdDelegates)
|
||||
foreach (RcmdEntry entry in _rcmdDelegates)
|
||||
{
|
||||
if (names.ContainsIgnoreCase(command.Trim()))
|
||||
foreach (string name in entry.Names)
|
||||
{
|
||||
searchResult = dlg;
|
||||
break;
|
||||
if (trimmedCommand.Equals(name, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return entry.Handler(debugger, string.Empty);
|
||||
}
|
||||
|
||||
if (trimmedCommand.Length > name.Length &&
|
||||
trimmedCommand.StartsWith(name, StringComparison.OrdinalIgnoreCase) &&
|
||||
char.IsWhiteSpace(trimmedCommand[name.Length]))
|
||||
{
|
||||
string arguments = trimmedCommand[name.Length..].TrimStart();
|
||||
return entry.Handler(debugger, arguments);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return searchResult;
|
||||
return $"Unknown command: {originalCommand}\n";
|
||||
}
|
||||
|
||||
public string GetStackTrace()
|
||||
|
|
@ -86,5 +134,181 @@ namespace Ryujinx.HLE.Debugger
|
|||
return $"Error getting process info: {e.Message}\n";
|
||||
}
|
||||
}
|
||||
|
||||
public string GetMemoryMappings(string arguments)
|
||||
{
|
||||
if (Process?.MemoryManager is not { } memoryManager)
|
||||
{
|
||||
return "No application process found\n";
|
||||
}
|
||||
|
||||
string trimmedArgs = arguments?.Trim() ?? string.Empty;
|
||||
|
||||
ulong startAddress = 0;
|
||||
if (!string.IsNullOrEmpty(trimmedArgs))
|
||||
{
|
||||
if (!TryParseAddressArgument(trimmedArgs, out startAddress))
|
||||
{
|
||||
return $"Invalid address: {trimmedArgs}\n";
|
||||
}
|
||||
}
|
||||
|
||||
ulong requestedAddress = startAddress;
|
||||
ulong currentAddress = Math.Max(requestedAddress, memoryManager.AddrSpaceStart);
|
||||
StringBuilder sb = new();
|
||||
sb.AppendLine($"Mappings (starting from 0x{requestedAddress:x10}):");
|
||||
|
||||
if (currentAddress >= memoryManager.AddrSpaceEnd)
|
||||
{
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
while (currentAddress < memoryManager.AddrSpaceEnd)
|
||||
{
|
||||
KMemoryInfo info = memoryManager.QueryMemory(currentAddress);
|
||||
|
||||
try
|
||||
{
|
||||
if (info.Size == 0 || info.Address >= memoryManager.AddrSpaceEnd)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
sb.AppendLine(FormatMapping(info, indent: true));
|
||||
|
||||
if (info.Address > ulong.MaxValue - info.Size)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
ulong nextAddress = info.Address + info.Size;
|
||||
if (nextAddress <= currentAddress)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
currentAddress = nextAddress;
|
||||
}
|
||||
finally
|
||||
{
|
||||
KMemoryInfo.Pool.Release(info);
|
||||
}
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public string GetMemoryMapping(string arguments)
|
||||
{
|
||||
if (Process?.MemoryManager is not { } memoryManager)
|
||||
{
|
||||
return "No application process found\n";
|
||||
}
|
||||
|
||||
string trimmedArgs = arguments?.Trim() ?? string.Empty;
|
||||
|
||||
if (string.IsNullOrEmpty(trimmedArgs))
|
||||
{
|
||||
return "Missing address argument for `get mapping`\n";
|
||||
}
|
||||
|
||||
if (!TryParseAddressArgument(trimmedArgs, out ulong address))
|
||||
{
|
||||
return $"Invalid address: {trimmedArgs}\n";
|
||||
}
|
||||
|
||||
KMemoryInfo info = memoryManager.QueryMemory(address);
|
||||
|
||||
try
|
||||
{
|
||||
return FormatMapping(info, indent: false) + '\n';
|
||||
}
|
||||
finally
|
||||
{
|
||||
KMemoryInfo.Pool.Release(info);
|
||||
}
|
||||
}
|
||||
|
||||
private static string FormatMapping(KMemoryInfo info, bool indent)
|
||||
{
|
||||
ulong endAddress;
|
||||
|
||||
if (info.Size == 0)
|
||||
{
|
||||
endAddress = info.Address;
|
||||
}
|
||||
else if (info.Address > ulong.MaxValue - (info.Size - 1))
|
||||
{
|
||||
endAddress = ulong.MaxValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
endAddress = info.Address + info.Size - 1;
|
||||
}
|
||||
|
||||
string prefix = indent ? " " : string.Empty;
|
||||
return $"{prefix}0x{info.Address:x10} - 0x{endAddress:x10} {GetPermissionString(info)} {GetMemoryStateName(info.State)} {GetAttributeFlags(info)} [{info.IpcRefCount}, {info.DeviceRefCount}]";
|
||||
}
|
||||
|
||||
private static string GetPermissionString(KMemoryInfo info)
|
||||
{
|
||||
if ((info.State & MemoryState.UserMask) == MemoryState.Unmapped)
|
||||
{
|
||||
return " ";
|
||||
}
|
||||
|
||||
return info.Permission switch
|
||||
{
|
||||
KMemoryPermission.ReadAndExecute => "r-x",
|
||||
KMemoryPermission.Read => "r--",
|
||||
KMemoryPermission.ReadAndWrite => "rw-",
|
||||
_ => "---"
|
||||
};
|
||||
}
|
||||
|
||||
private static string GetMemoryStateName(MemoryState state)
|
||||
{
|
||||
int stateIndex = (int)(state & MemoryState.UserMask);
|
||||
if ((uint)stateIndex < _memoryStateNames.Length)
|
||||
{
|
||||
return _memoryStateNames[stateIndex];
|
||||
}
|
||||
|
||||
return "Unknown ";
|
||||
}
|
||||
|
||||
private static bool TryParseAddressArgument(string text, out ulong value)
|
||||
{
|
||||
value = 0;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
string trimmed = text.Trim();
|
||||
|
||||
if (trimmed.StartsWith("0x", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
trimmed = trimmed[2..];
|
||||
}
|
||||
|
||||
if (trimmed.Length == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return ulong.TryParse(trimmed, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out value);
|
||||
}
|
||||
|
||||
private static string GetAttributeFlags(KMemoryInfo info)
|
||||
{
|
||||
char locked = info.Attribute.HasFlag(MemoryAttribute.Borrowed) ? 'L' : '-';
|
||||
char ipc = info.Attribute.HasFlag(MemoryAttribute.IpcMapped) ? 'I' : '-';
|
||||
char device = info.Attribute.HasFlag(MemoryAttribute.DeviceMapped) ? 'D' : '-';
|
||||
char uncached = info.Attribute.HasFlag(MemoryAttribute.Uncached) ? 'U' : '-';
|
||||
|
||||
return $"{locked}{ipc}{device}{uncached}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -404,9 +404,8 @@ namespace Ryujinx.HLE.Debugger.Gdb
|
|||
string command = Helpers.FromHex(hexCommand);
|
||||
Logger.Debug?.Print(LogClass.GdbStub, $"Received Rcmd: {command}");
|
||||
|
||||
Func<Debugger, string> rcmd = Debugger.FindRcmdDelegate(command);
|
||||
|
||||
Processor.ReplyHex(rcmd(Debugger));
|
||||
string response = Debugger.CallRcmdDelegate(Debugger, command);
|
||||
Processor.ReplyHex(response);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -483,10 +483,29 @@ namespace Ryujinx.HLE.FileSystem
|
|||
{
|
||||
if (Directory.Exists(keysSource))
|
||||
{
|
||||
foreach (string filePath in Directory.EnumerateFiles(keysSource, "*.keys"))
|
||||
string[] keyPaths = Directory.EnumerateFiles(keysSource, "*.keys").ToArray();
|
||||
|
||||
if (keyPaths.Length is 0)
|
||||
throw new FileNotFoundException($"Directory '{keysSource}' contained no '.keys' files.");
|
||||
|
||||
foreach (string filePath in keyPaths)
|
||||
{
|
||||
VerifyKeysFile(filePath);
|
||||
File.Copy(filePath, Path.Combine(installDirectory, Path.GetFileName(filePath)), true);
|
||||
try
|
||||
{
|
||||
VerifyKeysFile(filePath);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application, e.Message);
|
||||
continue;
|
||||
}
|
||||
|
||||
string destPath = Path.Combine(installDirectory, Path.GetFileName(filePath));
|
||||
|
||||
if (File.Exists(destPath))
|
||||
File.Delete(destPath);
|
||||
|
||||
File.Copy(filePath, destPath, true);
|
||||
}
|
||||
|
||||
return;
|
||||
|
|
@ -501,13 +520,25 @@ namespace Ryujinx.HLE.FileSystem
|
|||
|
||||
using FileStream file = File.OpenRead(keysSource);
|
||||
|
||||
if (info.Extension is ".keys")
|
||||
if (info.Extension is not ".keys")
|
||||
throw new InvalidFirmwarePackageException("Input file extension is not .keys");
|
||||
|
||||
try
|
||||
{
|
||||
VerifyKeysFile(keysSource);
|
||||
File.Copy(keysSource, Path.Combine(installDirectory, info.Name), true);
|
||||
}
|
||||
else
|
||||
}
|
||||
catch
|
||||
{
|
||||
throw new InvalidFirmwarePackageException("Input file is not a valid key package");
|
||||
}
|
||||
|
||||
string dest = Path.Combine(installDirectory, info.Name);
|
||||
|
||||
if (File.Exists(dest))
|
||||
File.Delete(dest);
|
||||
|
||||
// overwrite: true seems to not work on its own? https://github.com/Ryubing/Issues/issues/189
|
||||
File.Copy(keysSource, dest, true);
|
||||
}
|
||||
|
||||
private void FinishInstallation(string temporaryDirectory, string registeredDirectory)
|
||||
|
|
@ -985,8 +1016,8 @@ namespace Ryujinx.HLE.FileSystem
|
|||
public static void VerifyKeysFile(string filePath)
|
||||
{
|
||||
// Verify the keys file format refers to https://github.com/Thealexbarney/LibHac/blob/master/KEYS.md
|
||||
string genericPattern = @"^[a-z0-9_]+ = [a-z0-9]+$";
|
||||
string titlePattern = @"^[a-z0-9]{32} = [a-z0-9]{32}$";
|
||||
string genericPattern = "^[a-z0-9_]+ = [a-z0-9]+$";
|
||||
string titlePattern = "^[a-z0-9]{32} = [a-z0-9]{32}$";
|
||||
|
||||
if (File.Exists(filePath))
|
||||
{
|
||||
|
|
@ -994,24 +1025,13 @@ namespace Ryujinx.HLE.FileSystem
|
|||
string fileName = Path.GetFileName(filePath);
|
||||
string[] lines = File.ReadAllLines(filePath);
|
||||
|
||||
bool verified;
|
||||
switch (fileName)
|
||||
bool verified = fileName switch
|
||||
{
|
||||
case "prod.keys":
|
||||
verified = VerifyKeys(lines, genericPattern);
|
||||
break;
|
||||
case "title.keys":
|
||||
verified = VerifyKeys(lines, titlePattern);
|
||||
break;
|
||||
case "console.keys":
|
||||
verified = VerifyKeys(lines, genericPattern);
|
||||
break;
|
||||
case "dev.keys":
|
||||
verified = VerifyKeys(lines, genericPattern);
|
||||
break;
|
||||
default:
|
||||
throw new FormatException($"Keys file name \"{fileName}\" not supported. Only \"prod.keys\", \"title.keys\", \"console.keys\", \"dev.keys\" are supported.");
|
||||
}
|
||||
"prod.keys" or "console.keys" or "dev.keys" => VerifyKeys(lines, genericPattern),
|
||||
"title.keys" => VerifyKeys(lines, titlePattern),
|
||||
_ => throw new FormatException(
|
||||
$"Keys file name \"{fileName}\" not supported. Only \"prod.keys\", \"title.keys\", \"console.keys\", \"dev.keys\" are supported.")
|
||||
};
|
||||
|
||||
if (!verified)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -891,10 +891,7 @@ namespace Ryujinx.HLE.HOS.Diagnostics.Demangler
|
|||
result = new NestedName(name, prev);
|
||||
}
|
||||
|
||||
if (context != null)
|
||||
{
|
||||
context.FinishWithTemplateArguments = false;
|
||||
}
|
||||
context?.FinishWithTemplateArguments = false;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
|
@ -1074,10 +1071,7 @@ namespace Ryujinx.HLE.HOS.Diagnostics.Demangler
|
|||
return null;
|
||||
}
|
||||
|
||||
if (context != null)
|
||||
{
|
||||
context.CtorDtorConversion = true;
|
||||
}
|
||||
context?.CtorDtorConversion = true;
|
||||
|
||||
return new ConversionOperatorType(type);
|
||||
default:
|
||||
|
|
@ -1349,10 +1343,7 @@ namespace Ryujinx.HLE.HOS.Diagnostics.Demangler
|
|||
|
||||
_position++;
|
||||
|
||||
if (context != null)
|
||||
{
|
||||
context.CtorDtorConversion = true;
|
||||
}
|
||||
context?.CtorDtorConversion = true;
|
||||
|
||||
if (isInherited && ParseName(context) == null)
|
||||
{
|
||||
|
|
@ -1372,10 +1363,7 @@ namespace Ryujinx.HLE.HOS.Diagnostics.Demangler
|
|||
|
||||
_position++;
|
||||
|
||||
if (context != null)
|
||||
{
|
||||
context.CtorDtorConversion = true;
|
||||
}
|
||||
context?.CtorDtorConversion = true;
|
||||
|
||||
return new CtorDtorNameType(prev, true);
|
||||
}
|
||||
|
|
@ -3005,16 +2993,10 @@ namespace Ryujinx.HLE.HOS.Diagnostics.Demangler
|
|||
|
||||
BaseNode result = null;
|
||||
CvType cv = new(ParseCvQualifiers(), null);
|
||||
if (context != null)
|
||||
{
|
||||
context.Cv = cv;
|
||||
}
|
||||
context?.Cv = cv;
|
||||
|
||||
SimpleReferenceType Ref = ParseRefQualifiers();
|
||||
if (context != null)
|
||||
{
|
||||
context.Ref = Ref;
|
||||
}
|
||||
context?.Ref = Ref;
|
||||
|
||||
if (ConsumeIf("St"))
|
||||
{
|
||||
|
|
@ -3060,10 +3042,7 @@ namespace Ryujinx.HLE.HOS.Diagnostics.Demangler
|
|||
}
|
||||
|
||||
result = new NameTypeWithTemplateArguments(result, templateArgument);
|
||||
if (context != null)
|
||||
{
|
||||
context.FinishWithTemplateArguments = true;
|
||||
}
|
||||
context?.FinishWithTemplateArguments = true;
|
||||
|
||||
_substitutionList.Add(result);
|
||||
continue;
|
||||
|
|
@ -3256,10 +3235,7 @@ namespace Ryujinx.HLE.HOS.Diagnostics.Demangler
|
|||
return null;
|
||||
}
|
||||
|
||||
if (context != null)
|
||||
{
|
||||
context.FinishWithTemplateArguments = true;
|
||||
}
|
||||
context?.FinishWithTemplateArguments = true;
|
||||
|
||||
return new NameTypeWithTemplateArguments(substitution, templateArguments);
|
||||
}
|
||||
|
|
@ -3279,10 +3255,7 @@ namespace Ryujinx.HLE.HOS.Diagnostics.Demangler
|
|||
return null;
|
||||
}
|
||||
|
||||
if (context != null)
|
||||
{
|
||||
context.FinishWithTemplateArguments = true;
|
||||
}
|
||||
context?.FinishWithTemplateArguments = true;
|
||||
|
||||
return new NameTypeWithTemplateArguments(result, templateArguments);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ using Microsoft.IO;
|
|||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Memory;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
|
|
@ -37,7 +38,7 @@ namespace Ryujinx.HLE.HOS.Ipc
|
|||
|
||||
public IpcMessage(ReadOnlySpan<byte> data, long cmdPtr)
|
||||
{
|
||||
using RecyclableMemoryStream ms = MemoryStreamManager.Shared.GetStream(data);
|
||||
RecyclableMemoryStream ms = MemoryStreamManager.Shared.GetStream(data);
|
||||
|
||||
BinaryReader reader = new(ms);
|
||||
|
||||
|
|
@ -123,6 +124,8 @@ namespace Ryujinx.HLE.HOS.Ipc
|
|||
}
|
||||
|
||||
ObjectIds = [];
|
||||
|
||||
MemoryStreamManager.Shared.ReleaseStream(ms);
|
||||
}
|
||||
|
||||
public RecyclableMemoryStream GetStream(long cmdPtr, ulong recvListAddr)
|
||||
|
|
|
|||
|
|
@ -20,6 +20,15 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
|
|||
_exchangeBufferDescriptors = new List<KBufferDescriptor>(MaxInternalBuffersCount);
|
||||
}
|
||||
|
||||
public KBufferDescriptorTable Clear()
|
||||
{
|
||||
_sendBufferDescriptors.Clear();
|
||||
_receiveBufferDescriptors.Clear();
|
||||
_exchangeBufferDescriptors.Clear();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Result AddSendBuffer(ulong src, ulong dst, ulong size, MemoryState state)
|
||||
{
|
||||
return Add(_sendBufferDescriptors, src, dst, size, state);
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using Ryujinx.Common;
|
||||
using Ryujinx.HLE.HOS.Kernel.Common;
|
||||
using Ryujinx.HLE.HOS.Kernel.Process;
|
||||
using Ryujinx.HLE.HOS.Kernel.Threading;
|
||||
|
|
@ -32,7 +33,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
|
|||
{
|
||||
KThread currentThread = KernelStatic.GetCurrentThread();
|
||||
|
||||
KSessionRequest request = new(currentThread, customCmdBuffAddr, customCmdBuffSize);
|
||||
KSessionRequest request = _parent.ServerSession.RequestPool.Allocate().Set(currentThread, customCmdBuffAddr, customCmdBuffSize);
|
||||
|
||||
KernelContext.CriticalSection.Enter();
|
||||
|
||||
|
|
@ -55,7 +56,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
|
|||
{
|
||||
KThread currentThread = KernelStatic.GetCurrentThread();
|
||||
|
||||
KSessionRequest request = new(currentThread, customCmdBuffAddr, customCmdBuffSize, asyncEvent);
|
||||
KSessionRequest request = _parent.ServerSession.RequestPool.Allocate().Set(currentThread, customCmdBuffAddr, customCmdBuffSize, asyncEvent);
|
||||
|
||||
KernelContext.CriticalSection.Enter();
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
|
|||
{
|
||||
class KServerSession : KSynchronizationObject
|
||||
{
|
||||
public readonly ObjectPool<KSessionRequest> RequestPool = new(() => new KSessionRequest());
|
||||
|
||||
private static readonly MemoryState[] _ipcMemoryStates =
|
||||
[
|
||||
MemoryState.IpcBuffer3,
|
||||
|
|
@ -274,6 +276,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
|
|||
KernelContext.CriticalSection.Leave();
|
||||
|
||||
WakeClientThread(request, clientResult);
|
||||
|
||||
RequestPool.Release(request);
|
||||
}
|
||||
|
||||
if (clientHeader.ReceiveListType < 2 &&
|
||||
|
|
@ -627,6 +631,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
|
|||
CloseAllHandles(clientMsg, serverHeader, clientProcess);
|
||||
|
||||
FinishRequest(request, clientResult);
|
||||
|
||||
RequestPool.Release(request);
|
||||
}
|
||||
|
||||
if (clientHeader.ReceiveListType < 2 &&
|
||||
|
|
@ -865,6 +871,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
|
|||
|
||||
// Unmap buffers from server.
|
||||
FinishRequest(request, clientResult);
|
||||
|
||||
RequestPool.Release(request);
|
||||
|
||||
return serverResult;
|
||||
}
|
||||
|
|
@ -1098,6 +1106,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
|
|||
foreach (KSessionRequest request in IterateWithRemovalOfAllRequests())
|
||||
{
|
||||
FinishRequest(request, KernelResult.PortRemoteClosed);
|
||||
|
||||
RequestPool.Release(request);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1117,6 +1127,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
|
|||
{
|
||||
SendResultToAsyncRequestClient(request, KernelResult.PortRemoteClosed);
|
||||
}
|
||||
|
||||
RequestPool.Release(request);
|
||||
}
|
||||
|
||||
WakeServerThreads(KernelResult.PortRemoteClosed);
|
||||
|
|
|
|||
|
|
@ -5,18 +5,18 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
|
|||
{
|
||||
class KSessionRequest
|
||||
{
|
||||
public KBufferDescriptorTable BufferDescriptorTable { get; }
|
||||
public KBufferDescriptorTable BufferDescriptorTable { get; private set; }
|
||||
|
||||
public KThread ClientThread { get; }
|
||||
public KThread ClientThread { get; private set; }
|
||||
|
||||
public KProcess ServerProcess { get; set; }
|
||||
|
||||
public KWritableEvent AsyncEvent { get; }
|
||||
public KWritableEvent AsyncEvent { get; private set; }
|
||||
|
||||
public ulong CustomCmdBuffAddr { get; }
|
||||
public ulong CustomCmdBuffSize { get; }
|
||||
public ulong CustomCmdBuffAddr { get; private set; }
|
||||
public ulong CustomCmdBuffSize { get; private set; }
|
||||
|
||||
public KSessionRequest(
|
||||
public KSessionRequest Set(
|
||||
KThread clientThread,
|
||||
ulong customCmdBuffAddr,
|
||||
ulong customCmdBuffSize,
|
||||
|
|
@ -27,7 +27,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
|
|||
CustomCmdBuffSize = customCmdBuffSize;
|
||||
AsyncEvent = asyncEvent;
|
||||
|
||||
BufferDescriptorTable = new KBufferDescriptorTable();
|
||||
BufferDescriptorTable = BufferDescriptorTable?.Clear() ?? new KBufferDescriptorTable();
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
using Ryujinx.Common;
|
||||
using Ryujinx.HLE.HOS.Kernel.Common;
|
||||
using Ryujinx.HLE.HOS.Kernel.Process;
|
||||
using Ryujinx.Horizon.Common;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||
|
|
@ -12,12 +10,12 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
|||
class KAddressArbiter
|
||||
{
|
||||
private const int HasListenersMask = 0x40000000;
|
||||
private static readonly ObjectPool<KThread[]> _threadArrayPool = new(() => []);
|
||||
|
||||
private readonly KernelContext _context;
|
||||
|
||||
private readonly List<KThread> _condVarThreads;
|
||||
private readonly List<KThread> _arbiterThreads;
|
||||
private readonly Dictionary<ulong, List<KThread>> _condVarThreads;
|
||||
private readonly Dictionary<ulong, List<KThread>> _arbiterThreads;
|
||||
private readonly ByDynamicPriority _byDynamicPriority;
|
||||
|
||||
public KAddressArbiter(KernelContext context)
|
||||
{
|
||||
|
|
@ -25,6 +23,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
|||
|
||||
_condVarThreads = [];
|
||||
_arbiterThreads = [];
|
||||
_byDynamicPriority = new ByDynamicPriority();
|
||||
}
|
||||
|
||||
public Result ArbitrateLock(int ownerHandle, ulong mutexAddress, int requesterHandle)
|
||||
|
|
@ -140,9 +139,23 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
|||
|
||||
currentThread.MutexAddress = mutexAddress;
|
||||
currentThread.ThreadHandleForUserMutex = threadHandle;
|
||||
currentThread.CondVarAddress = condVarAddress;
|
||||
|
||||
_condVarThreads.Add(currentThread);
|
||||
if (_condVarThreads.TryGetValue(condVarAddress, out List<KThread> threads))
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
if (threads.Count > 0)
|
||||
{
|
||||
i = threads.BinarySearch(currentThread, _byDynamicPriority);
|
||||
if (i < 0) i = ~i;
|
||||
}
|
||||
|
||||
threads.Insert(i, currentThread);
|
||||
}
|
||||
else
|
||||
{
|
||||
_condVarThreads.Add(condVarAddress, [currentThread]);
|
||||
}
|
||||
|
||||
if (timeout != 0)
|
||||
{
|
||||
|
|
@ -165,7 +178,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
|||
|
||||
currentThread.MutexOwner?.RemoveMutexWaiter(currentThread);
|
||||
|
||||
_condVarThreads.Remove(currentThread);
|
||||
_condVarThreads[condVarAddress].Remove(currentThread);
|
||||
|
||||
_context.CriticalSection.Leave();
|
||||
|
||||
|
|
@ -200,13 +213,14 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
|||
{
|
||||
_context.CriticalSection.Enter();
|
||||
|
||||
static bool SignalProcessWideKeyPredicate(KThread thread, ulong address)
|
||||
int validThreads = 0;
|
||||
_condVarThreads.TryGetValue(address, out List<KThread> threads);
|
||||
|
||||
if (threads is not null && threads.Count > 0)
|
||||
{
|
||||
return thread.CondVarAddress == address;
|
||||
validThreads = WakeThreads(threads, count, TryAcquireMutex);
|
||||
}
|
||||
|
||||
int validThreads = WakeThreads(_condVarThreads, count, TryAcquireMutex, SignalProcessWideKeyPredicate, address);
|
||||
|
||||
|
||||
if (validThreads == 0)
|
||||
{
|
||||
KernelTransfer.KernelToUser(address, 0);
|
||||
|
|
@ -315,9 +329,24 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
|||
|
||||
currentThread.MutexAddress = address;
|
||||
currentThread.WaitingInArbitration = true;
|
||||
|
||||
if (_arbiterThreads.TryGetValue(address, out List<KThread> threads))
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
_arbiterThreads.Add(currentThread);
|
||||
|
||||
if (threads.Count > 0)
|
||||
{
|
||||
i = threads.BinarySearch(currentThread, _byDynamicPriority);
|
||||
if (i < 0) i = ~i;
|
||||
}
|
||||
|
||||
threads.Insert(i, currentThread);
|
||||
}
|
||||
else
|
||||
{
|
||||
_arbiterThreads.Add(address, [currentThread]);
|
||||
}
|
||||
|
||||
currentThread.Reschedule(ThreadSchedState.Paused);
|
||||
|
||||
if (timeout > 0)
|
||||
|
|
@ -336,7 +365,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
|||
|
||||
if (currentThread.WaitingInArbitration)
|
||||
{
|
||||
_arbiterThreads.Remove(currentThread);
|
||||
_arbiterThreads[address].Remove(currentThread);
|
||||
|
||||
currentThread.WaitingInArbitration = false;
|
||||
}
|
||||
|
|
@ -392,9 +421,24 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
|||
|
||||
currentThread.MutexAddress = address;
|
||||
currentThread.WaitingInArbitration = true;
|
||||
|
||||
if (_arbiterThreads.TryGetValue(address, out List<KThread> threads))
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
_arbiterThreads.Add(currentThread);
|
||||
|
||||
if (threads.Count > 0)
|
||||
{
|
||||
i = threads.BinarySearch(currentThread, _byDynamicPriority);
|
||||
if (i < 0) i = ~i;
|
||||
}
|
||||
|
||||
threads.Insert(i, currentThread);
|
||||
}
|
||||
else
|
||||
{
|
||||
_arbiterThreads.Add(address, [currentThread]);
|
||||
}
|
||||
|
||||
currentThread.Reschedule(ThreadSchedState.Paused);
|
||||
|
||||
if (timeout > 0)
|
||||
|
|
@ -413,7 +457,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
|||
|
||||
if (currentThread.WaitingInArbitration)
|
||||
{
|
||||
_arbiterThreads.Remove(currentThread);
|
||||
_arbiterThreads[address].Remove(currentThread);
|
||||
|
||||
currentThread.WaitingInArbitration = false;
|
||||
}
|
||||
|
|
@ -486,15 +530,12 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
|||
// or equal to the Count of threads to be signaled, or Count is zero
|
||||
// or negative. It is incremented if there are no threads waiting.
|
||||
int waitingCount = 0;
|
||||
|
||||
foreach (KThread thread in _arbiterThreads)
|
||||
|
||||
if (_arbiterThreads.TryGetValue(address, out List<KThread> threads))
|
||||
{
|
||||
if (thread.MutexAddress == address &&
|
||||
++waitingCount >= count)
|
||||
{
|
||||
break;
|
||||
}
|
||||
waitingCount = threads.Count;
|
||||
}
|
||||
|
||||
|
||||
if (waitingCount > 0)
|
||||
{
|
||||
|
|
@ -561,55 +602,38 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
|||
thread.WaitingInArbitration = false;
|
||||
}
|
||||
|
||||
static bool ArbiterThreadPredecate(KThread thread, ulong address)
|
||||
{
|
||||
return thread.MutexAddress == address;
|
||||
}
|
||||
_arbiterThreads.TryGetValue(address, out List<KThread> threads);
|
||||
|
||||
WakeThreads(_arbiterThreads, count, RemoveArbiterThread, ArbiterThreadPredecate, address);
|
||||
if (threads is not null && threads.Count > 0)
|
||||
{
|
||||
WakeThreads(threads, count, RemoveArbiterThread);
|
||||
}
|
||||
}
|
||||
|
||||
private static int WakeThreads(
|
||||
List<KThread> threads,
|
||||
int count,
|
||||
Action<KThread> removeCallback,
|
||||
Func<KThread, ulong, bool> predicate,
|
||||
ulong address = 0)
|
||||
Action<KThread> removeCallback)
|
||||
{
|
||||
KThread[] candidates = _threadArrayPool.Allocate();
|
||||
if (candidates.Length < threads.Count)
|
||||
{
|
||||
Array.Resize(ref candidates, threads.Count);
|
||||
}
|
||||
|
||||
int validCount = 0;
|
||||
|
||||
for (int i = 0; i < threads.Count; i++)
|
||||
{
|
||||
if (predicate(threads[i], address))
|
||||
{
|
||||
candidates[validCount++] = threads[i];
|
||||
}
|
||||
}
|
||||
|
||||
Span<KThread> candidatesSpan = candidates.AsSpan(..validCount);
|
||||
|
||||
candidatesSpan.Sort((x, y) => (x.DynamicPriority.CompareTo(y.DynamicPriority)));
|
||||
int validCount = count > 0 ? Math.Min(count, threads.Count) : threads.Count;
|
||||
|
||||
if (count > 0)
|
||||
{
|
||||
candidatesSpan = candidatesSpan[..Math.Min(count, candidatesSpan.Length)];
|
||||
}
|
||||
|
||||
foreach (KThread thread in candidatesSpan)
|
||||
for (int i = 0; i < validCount; i++)
|
||||
{
|
||||
KThread thread = threads[i];
|
||||
removeCallback(thread);
|
||||
threads.Remove(thread);
|
||||
}
|
||||
|
||||
_threadArrayPool.Release(candidates);
|
||||
|
||||
threads.RemoveRange(0, validCount);
|
||||
|
||||
return validCount;
|
||||
}
|
||||
|
||||
private class ByDynamicPriority : IComparer<KThread>
|
||||
{
|
||||
public int Compare(KThread x, KThread y)
|
||||
{
|
||||
return x!.DynamicPriority.CompareTo(y!.DynamicPriority);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -174,10 +174,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
|||
|
||||
if (previousThread != nextThread)
|
||||
{
|
||||
if (previousThread != null)
|
||||
{
|
||||
previousThread.LastScheduledTime = PerformanceCounter.ElapsedTicks;
|
||||
}
|
||||
previousThread?.LastScheduledTime = PerformanceCounter.ElapsedTicks;
|
||||
|
||||
_state.SelectedThread = nextThread;
|
||||
_state.NeedsScheduling = true;
|
||||
|
|
|
|||
|
|
@ -61,8 +61,6 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
|||
|
||||
public KSynchronizationObject SignaledObj { get; set; }
|
||||
|
||||
public ulong CondVarAddress { get; set; }
|
||||
|
||||
private ulong _entrypoint;
|
||||
private ThreadStart _customThreadStart;
|
||||
private bool _forcedUnschedulable;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
using Ryujinx.Common.Utilities;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Account.Acc
|
||||
{
|
||||
[JsonConverter(typeof(TypedStringEnumConverter<AccountState>))]
|
||||
[JsonConverter(typeof(JsonStringEnumConverter<AccountState>))]
|
||||
public enum AccountState
|
||||
{
|
||||
Closed,
|
||||
|
|
|
|||
|
|
@ -416,7 +416,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
|
|||
return ResultCode.InvalidParameters;
|
||||
}
|
||||
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceAm, new { albumReportOption });
|
||||
context.Device.UIHandler.TakeScreenshot();
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,9 @@ namespace Ryujinx.HLE.HOS.Services
|
|||
private int _selfId;
|
||||
private bool _isDomain;
|
||||
|
||||
// cache array so we don't recreate it all the time
|
||||
private object[] _parameters = [null];
|
||||
|
||||
public IpcService(ServerBase server = null, bool registerTipc = false)
|
||||
{
|
||||
Stopwatch sw = Stopwatch.StartNew();
|
||||
|
|
@ -146,7 +149,9 @@ namespace Ryujinx.HLE.HOS.Services
|
|||
{
|
||||
Logger.Trace?.Print(LogClass.KernelIpc, $"{service.GetType().Name}: {processRequest.Name}");
|
||||
|
||||
result = (ResultCode)processRequest.Invoke(service, [context]);
|
||||
_parameters[0] = context;
|
||||
|
||||
result = (ResultCode)processRequest.Invoke(service, _parameters);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -196,7 +201,9 @@ namespace Ryujinx.HLE.HOS.Services
|
|||
{
|
||||
Logger.Debug?.Print(LogClass.KernelIpc, $"{GetType().Name}: {processRequest.Name}");
|
||||
|
||||
result = (ResultCode)processRequest.Invoke(this, [context]);
|
||||
_parameters[0] = context;
|
||||
|
||||
result = (ResultCode)processRequest.Invoke(this, _parameters);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
|||
|
|
@ -59,6 +59,10 @@ namespace Ryujinx.HLE.HOS.Services.Nv
|
|||
|
||||
// TODO: This should call set:sys::GetDebugModeFlag
|
||||
private readonly bool _debugModeEnabled = false;
|
||||
|
||||
private byte[] _ioctl2Buffer = [];
|
||||
private byte[] _ioctlArgumentBuffer = [];
|
||||
private byte[] _ioctl3Buffer = [];
|
||||
|
||||
public INvDrvServices(ServiceCtx context) : base(context.Device.System.NvDrvServer)
|
||||
{
|
||||
|
|
@ -128,27 +132,38 @@ namespace Ryujinx.HLE.HOS.Services.Nv
|
|||
|
||||
if (!context.Memory.TryReadUnsafe(inputDataPosition, (int)inputDataSize, out arguments))
|
||||
{
|
||||
arguments = new byte[inputDataSize];
|
||||
if (_ioctlArgumentBuffer.Length < (int)inputDataSize)
|
||||
{
|
||||
Array.Resize(ref _ioctlArgumentBuffer, (int)inputDataSize);
|
||||
}
|
||||
|
||||
arguments = _ioctlArgumentBuffer.AsSpan(0, (int)inputDataSize);
|
||||
|
||||
context.Memory.Read(inputDataPosition, arguments);
|
||||
}
|
||||
else
|
||||
{
|
||||
arguments = arguments.ToArray();
|
||||
}
|
||||
}
|
||||
else if (isWrite)
|
||||
{
|
||||
byte[] outputData = new byte[outputDataSize];
|
||||
|
||||
arguments = new Span<byte>(outputData);
|
||||
if (_ioctlArgumentBuffer.Length < (int)outputDataSize)
|
||||
{
|
||||
Array.Resize(ref _ioctlArgumentBuffer, (int)outputDataSize);
|
||||
}
|
||||
|
||||
arguments = _ioctlArgumentBuffer.AsSpan(0, (int)outputDataSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
byte[] temp = new byte[inputDataSize];
|
||||
if (!context.Memory.TryReadUnsafe(inputDataPosition, (int)inputDataSize, out arguments))
|
||||
{
|
||||
if (_ioctlArgumentBuffer.Length < (int)inputDataSize)
|
||||
{
|
||||
Array.Resize(ref _ioctlArgumentBuffer, (int)inputDataSize);
|
||||
}
|
||||
|
||||
arguments = _ioctlArgumentBuffer.AsSpan(0, (int)inputDataSize);
|
||||
|
||||
context.Memory.Read(inputDataPosition, temp);
|
||||
|
||||
arguments = new Span<byte>(temp);
|
||||
context.Memory.Read(inputDataPosition, arguments);
|
||||
}
|
||||
}
|
||||
|
||||
return NvResult.Success;
|
||||
|
|
@ -270,7 +285,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv
|
|||
|
||||
if ((ioctlCommand.DirectionValue & NvIoctl.Direction.Write) != 0)
|
||||
{
|
||||
context.Memory.Write(context.Request.GetBufferType0x22(0).Position, arguments.ToArray());
|
||||
context.Memory.Write(context.Request.GetBufferType0x22(0).Position, arguments);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -474,13 +489,15 @@ namespace Ryujinx.HLE.HOS.Services.Nv
|
|||
|
||||
errorCode = GetIoctlArgument(context, ioctlCommand, out Span<byte> arguments);
|
||||
|
||||
byte[] inlineInBuffer = null;
|
||||
|
||||
if (!context.Memory.TryReadUnsafe(inlineInBufferPosition, (int)inlineInBufferSize, out Span<byte> inlineInBufferSpan))
|
||||
{
|
||||
inlineInBuffer = _byteArrayPool.Rent((int)inlineInBufferSize);
|
||||
inlineInBufferSpan = inlineInBuffer;
|
||||
context.Memory.Read(inlineInBufferPosition, inlineInBufferSpan[..(int)inlineInBufferSize]);
|
||||
if (_ioctl2Buffer.Length < (int)inlineInBufferSize)
|
||||
{
|
||||
Array.Resize(ref _ioctl2Buffer, (int)inlineInBufferSize);
|
||||
}
|
||||
|
||||
inlineInBufferSpan = _ioctl2Buffer.AsSpan(0, (int)inlineInBufferSize);
|
||||
context.Memory.Read(inlineInBufferPosition, inlineInBufferSpan);
|
||||
}
|
||||
|
||||
if (errorCode == NvResult.Success)
|
||||
|
|
@ -489,7 +506,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv
|
|||
|
||||
if (errorCode == NvResult.Success)
|
||||
{
|
||||
NvInternalResult internalResult = deviceFile.Ioctl2(ioctlCommand, arguments, inlineInBufferSpan[..(int)inlineInBufferSize]);
|
||||
NvInternalResult internalResult = deviceFile.Ioctl2(ioctlCommand, arguments, inlineInBufferSpan);
|
||||
|
||||
if (internalResult == NvInternalResult.NotImplemented)
|
||||
{
|
||||
|
|
@ -500,15 +517,10 @@ namespace Ryujinx.HLE.HOS.Services.Nv
|
|||
|
||||
if ((ioctlCommand.DirectionValue & NvIoctl.Direction.Write) != 0)
|
||||
{
|
||||
context.Memory.Write(context.Request.GetBufferType0x22(0).Position, arguments.ToArray());
|
||||
context.Memory.Write(context.Request.GetBufferType0x22(0).Position, arguments);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (inlineInBuffer is not null)
|
||||
{
|
||||
_byteArrayPool.Return(inlineInBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
context.ResponseData.Write((uint)errorCode);
|
||||
|
|
@ -531,13 +543,15 @@ namespace Ryujinx.HLE.HOS.Services.Nv
|
|||
|
||||
errorCode = GetIoctlArgument(context, ioctlCommand, out Span<byte> arguments);
|
||||
|
||||
byte[] inlineOutBuffer = null;
|
||||
|
||||
if (!context.Memory.TryReadUnsafe(inlineOutBufferPosition, (int)inlineOutBufferSize, out Span<byte> inlineOutBufferSpan))
|
||||
{
|
||||
inlineOutBuffer = _byteArrayPool.Rent((int)inlineOutBufferSize);
|
||||
inlineOutBufferSpan = inlineOutBuffer;
|
||||
context.Memory.Read(inlineOutBufferPosition, inlineOutBufferSpan[..(int)inlineOutBufferSize]);
|
||||
if (_ioctl3Buffer.Length < (int)inlineOutBufferSize)
|
||||
{
|
||||
Array.Resize(ref _ioctl3Buffer, (int)inlineOutBufferSize);
|
||||
}
|
||||
|
||||
inlineOutBufferSpan = _ioctl3Buffer.AsSpan(0, (int)inlineOutBufferSize);
|
||||
context.Memory.Read(inlineOutBufferPosition, inlineOutBufferSpan);
|
||||
}
|
||||
|
||||
if (errorCode == NvResult.Success)
|
||||
|
|
@ -546,7 +560,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv
|
|||
|
||||
if (errorCode == NvResult.Success)
|
||||
{
|
||||
NvInternalResult internalResult = deviceFile.Ioctl3(ioctlCommand, arguments, inlineOutBufferSpan[..(int)inlineOutBufferSize]);
|
||||
NvInternalResult internalResult = deviceFile.Ioctl3(ioctlCommand, arguments, inlineOutBufferSpan);
|
||||
|
||||
if (internalResult == NvInternalResult.NotImplemented)
|
||||
{
|
||||
|
|
@ -557,16 +571,11 @@ namespace Ryujinx.HLE.HOS.Services.Nv
|
|||
|
||||
if ((ioctlCommand.DirectionValue & NvIoctl.Direction.Write) != 0)
|
||||
{
|
||||
context.Memory.Write(context.Request.GetBufferType0x22(0).Position, arguments.ToArray());
|
||||
context.Memory.Write(inlineOutBufferPosition, inlineOutBufferSpan[..(int)inlineOutBufferSize].ToArray());
|
||||
context.Memory.Write(context.Request.GetBufferType0x22(0).Position, arguments);
|
||||
context.Memory.Write(inlineOutBufferPosition, inlineOutBufferSpan);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (inlineOutBuffer is not null)
|
||||
{
|
||||
_byteArrayPool.Return(inlineOutBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
context.ResponseData.Write((uint)errorCode);
|
||||
|
|
|
|||
|
|
@ -454,8 +454,9 @@ namespace Ryujinx.HLE.HOS.Services
|
|||
|
||||
response.RawData = _responseDataStream.ToArray();
|
||||
|
||||
using RecyclableMemoryStream responseStream = response.GetStreamTipc();
|
||||
RecyclableMemoryStream responseStream = response.GetStreamTipc();
|
||||
_selfProcess.CpuMemory.Write(_selfThread.TlsAddress, responseStream.GetReadOnlySequence());
|
||||
MemoryStreamManager.Shared.ReleaseStream(responseStream);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -464,8 +465,9 @@ namespace Ryujinx.HLE.HOS.Services
|
|||
|
||||
if (!isTipcCommunication)
|
||||
{
|
||||
using RecyclableMemoryStream responseStream = response.GetStream((long)_selfThread.TlsAddress, recvListAddr | ((ulong)PointerBufferSize << 48));
|
||||
RecyclableMemoryStream responseStream = response.GetStream((long)_selfThread.TlsAddress, recvListAddr | ((ulong)PointerBufferSize << 48));
|
||||
_selfProcess.CpuMemory.Write(_selfThread.TlsAddress, responseStream.GetReadOnlySequence());
|
||||
MemoryStreamManager.Shared.ReleaseStream(responseStream);
|
||||
}
|
||||
|
||||
return shouldReply;
|
||||
|
|
|
|||
|
|
@ -1169,9 +1169,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
|
|||
|
||||
public override void DestroyAtExit()
|
||||
{
|
||||
if (_context != null) {
|
||||
_context.Dispose();
|
||||
}
|
||||
_context?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,5 +68,10 @@ namespace Ryujinx.HLE.UI
|
|||
/// Displays the player select dialog and returns the selected profile.
|
||||
/// </summary>
|
||||
UserProfile ShowPlayerSelectDialog();
|
||||
|
||||
/// <summary>
|
||||
/// Takes a screenshot from the current renderer and saves it in the screenshots folder.
|
||||
/// </summary>
|
||||
void TakeScreenshot();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,9 @@ namespace Ryujinx.Horizon.Sdk.OsTypes.Impl
|
|||
private int _waitingThreadHandle;
|
||||
|
||||
private MultiWaitHolderBase _signaledHolder;
|
||||
|
||||
ObjectPool<int[]> _objectHandlePool = new(() => new int[64]);
|
||||
ObjectPool<MultiWaitHolderBase[]> _objectPool = new(() => new MultiWaitHolderBase[64]);
|
||||
|
||||
public long CurrentTime { get; private set; }
|
||||
|
||||
|
|
@ -76,11 +79,15 @@ namespace Ryujinx.Horizon.Sdk.OsTypes.Impl
|
|||
|
||||
private MultiWaitHolderBase WaitAnyHandleImpl(bool infinite, long timeout)
|
||||
{
|
||||
Span<int> objectHandles = new int[64];
|
||||
int[] objectHandles = _objectHandlePool.Allocate();
|
||||
Span<int> objectHandlesSpan = objectHandles;
|
||||
objectHandlesSpan.Clear();
|
||||
|
||||
Span<MultiWaitHolderBase> objects = new MultiWaitHolderBase[64];
|
||||
MultiWaitHolderBase[] objects = _objectPool.Allocate();
|
||||
Span<MultiWaitHolderBase> objectsSpan = objects;
|
||||
objectsSpan.Clear();
|
||||
|
||||
int count = FillObjectsArray(objectHandles, objects);
|
||||
int count = FillObjectsArray(objectHandlesSpan, objectsSpan);
|
||||
|
||||
long endTime = infinite ? long.MaxValue : PerformanceCounter.ElapsedMilliseconds * 1000000;
|
||||
|
||||
|
|
@ -98,7 +105,7 @@ namespace Ryujinx.Horizon.Sdk.OsTypes.Impl
|
|||
}
|
||||
else
|
||||
{
|
||||
index = WaitSynchronization(objectHandles[..count], minTimeout);
|
||||
index = WaitSynchronization(objectHandlesSpan[..count], minTimeout);
|
||||
|
||||
DebugUtil.Assert(index != WaitInvalid);
|
||||
}
|
||||
|
|
@ -116,12 +123,18 @@ namespace Ryujinx.Horizon.Sdk.OsTypes.Impl
|
|||
{
|
||||
_signaledHolder = minTimeoutObject;
|
||||
|
||||
_objectHandlePool.Release(objectHandles);
|
||||
_objectPool.Release(objects);
|
||||
|
||||
return _signaledHolder;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_objectHandlePool.Release(objectHandles);
|
||||
_objectPool.Release(objects);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
@ -131,6 +144,9 @@ namespace Ryujinx.Horizon.Sdk.OsTypes.Impl
|
|||
{
|
||||
if (_signaledHolder != null)
|
||||
{
|
||||
_objectHandlePool.Release(objectHandles);
|
||||
_objectPool.Release(objects);
|
||||
|
||||
return _signaledHolder;
|
||||
}
|
||||
}
|
||||
|
|
@ -139,8 +155,11 @@ namespace Ryujinx.Horizon.Sdk.OsTypes.Impl
|
|||
default:
|
||||
lock (_lock)
|
||||
{
|
||||
_signaledHolder = objects[index];
|
||||
_signaledHolder = objectsSpan[index];
|
||||
|
||||
_objectHandlePool.Release(objectHandles);
|
||||
_objectPool.Release(objects);
|
||||
|
||||
return _signaledHolder;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -532,8 +532,6 @@ namespace Ryujinx.Input.HLE
|
|||
|
||||
hidKeyboard.Modifier |= value << entry.Target;
|
||||
}
|
||||
|
||||
ArrayPool<bool>.Shared.Return(keyboardState.KeysState);
|
||||
|
||||
return hidKeyboard;
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ namespace Ryujinx.Input.HLE
|
|||
{
|
||||
public class NpadManager : IDisposable
|
||||
{
|
||||
private static readonly ObjectPool<List<SixAxisInput>> _hleMotionStatesPool = new (() => new List<SixAxisInput>(NpadDevices.MaxControllers));
|
||||
private readonly CemuHookClient _cemuHookClient;
|
||||
|
||||
private readonly Lock _lock = new();
|
||||
|
|
@ -40,6 +39,9 @@ namespace Ryujinx.Input.HLE
|
|||
private bool _enableKeyboard;
|
||||
private bool _enableMouse;
|
||||
private Switch _device;
|
||||
|
||||
private readonly List<GamepadInput> _hleInputStates = [];
|
||||
private readonly List<SixAxisInput> _hleMotionStates = new(NpadDevices.MaxControllers);
|
||||
|
||||
public NpadManager(IGamepadDriver keyboardDriver, IGamepadDriver gamepadDriver, IGamepadDriver mouseDriver)
|
||||
{
|
||||
|
|
@ -217,8 +219,8 @@ namespace Ryujinx.Input.HLE
|
|||
{
|
||||
lock (_lock)
|
||||
{
|
||||
List<GamepadInput> hleInputStates = [];
|
||||
List<SixAxisInput> hleMotionStates = _hleMotionStatesPool.Allocate();
|
||||
_hleInputStates.Clear();
|
||||
_hleMotionStates.Clear();
|
||||
|
||||
KeyboardInput? hleKeyboardInput = null;
|
||||
|
||||
|
|
@ -260,14 +262,14 @@ namespace Ryujinx.Input.HLE
|
|||
inputState.PlayerId = playerIndex;
|
||||
motionState.Item1.PlayerId = playerIndex;
|
||||
|
||||
hleInputStates.Add(inputState);
|
||||
hleMotionStates.Add(motionState.Item1);
|
||||
_hleInputStates.Add(inputState);
|
||||
_hleMotionStates.Add(motionState.Item1);
|
||||
|
||||
if (isJoyconPair && !motionState.Item2.Equals(default))
|
||||
{
|
||||
motionState.Item2.PlayerId = playerIndex;
|
||||
|
||||
hleMotionStates.Add(motionState.Item2);
|
||||
_hleMotionStates.Add(motionState.Item2);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -276,8 +278,8 @@ namespace Ryujinx.Input.HLE
|
|||
hleKeyboardInput = NpadController.GetHLEKeyboardInput(_keyboardDriver);
|
||||
}
|
||||
|
||||
_device.Hid.Npads.Update(hleInputStates);
|
||||
_device.Hid.Npads.UpdateSixAxis(hleMotionStates);
|
||||
_device.Hid.Npads.Update(_hleInputStates);
|
||||
_device.Hid.Npads.UpdateSixAxis(_hleMotionStates);
|
||||
|
||||
if (hleKeyboardInput.HasValue)
|
||||
{
|
||||
|
|
@ -328,10 +330,7 @@ namespace Ryujinx.Input.HLE
|
|||
_device.Hid.Mouse.Update(0, 0);
|
||||
}
|
||||
|
||||
_device.TamperMachine.UpdateInput(hleInputStates);
|
||||
|
||||
hleMotionStates.Clear();
|
||||
_hleMotionStatesPool.Release(hleMotionStates);
|
||||
_device.TamperMachine.UpdateInput(_hleInputStates);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ namespace Ryujinx.Input
|
|||
/// </summary>
|
||||
public interface IKeyboard : IGamepad
|
||||
{
|
||||
private static bool[] _keyState;
|
||||
|
||||
/// <summary>
|
||||
/// Check if a given key is pressed on the keyboard.
|
||||
/// </summary>
|
||||
|
|
@ -29,15 +31,17 @@ namespace Ryujinx.Input
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
static KeyboardStateSnapshot GetStateSnapshot(IKeyboard keyboard)
|
||||
{
|
||||
if (_keyState is null)
|
||||
{
|
||||
_keyState = new bool[(int)Key.Count];
|
||||
}
|
||||
|
||||
bool[] keysState = ArrayPool<bool>.Shared.Rent((int)Key.Count);
|
||||
|
||||
for (Key key = 0; key < Key.Count; key++)
|
||||
{
|
||||
keysState[(int)key] = keyboard.IsPressed(key);
|
||||
_keyState[(int)key] = keyboard.IsPressed(key);
|
||||
}
|
||||
|
||||
return new KeyboardStateSnapshot(keysState);
|
||||
return new KeyboardStateSnapshot(_keyState);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue