Compare commits

..

9 Commits

Author SHA1 Message Date
GreemDev
1e1bcb4a5b storing commit string in github output causes weird CI failures
so let's just not bother, it didn't show anything more than the UI already did anyways
2025-12-23 00:02:02 -06:00
Coxxs
ca76bacd22 gdb: add monitor get mapping (ryubing/ryujinx!215)
See merge request ryubing/ryujinx!215
2025-12-21 22:34:20 -06:00
GreemDev
66f339d265 CI 2.0 (ryubing/ryujinx!237)
See merge request ryubing/ryujinx!237
2025-12-18 22:56:50 -06:00
GreemDev
6cdbdfd329 [ci skip] Pin GitLabCli to 1.4.1 in CI scripts so I can test v2.0 2025-12-18 03:27:43 -06:00
GreemDev
9f817d60d5 oops 2025-12-18 03:05:42 -06:00
GreemDev
5cffc95be6 Make all OSes build on Linux (7zip has a linux version) 2025-12-18 03:01:22 -06:00
LotP
2c0977f6b3 fix pre-action crash (ryubing/ryujinx!236)
See merge request ryubing/ryujinx!236
2025-12-12 14:28:54 -06:00
LotP
3a593b6084 Fix kaddressarbiter crash (ryubing/ryujinx!235)
See merge request ryubing/ryujinx!235
2025-12-06 20:16:43 -06:00
LotP
c3155fcadb Memory Changes 3.2 (ryubing/ryujinx!234)
See merge request ryubing/ryujinx!234
2025-12-06 17:19:19 -06:00
46 changed files with 956 additions and 1020 deletions

View File

@ -1,4 +1,4 @@
name: Canary release job name: Canary CI
on: on:
workflow_dispatch: workflow_dispatch:
@ -19,7 +19,6 @@ concurrency: release
env: env:
POWERSHELL_TELEMETRY_OPTOUT: 1 POWERSHELL_TELEMETRY_OPTOUT: 1
DOTNET_CLI_TELEMETRY_OPTOUT: 1 DOTNET_CLI_TELEMETRY_OPTOUT: 1
RYUJINX_BASE_VERSION: "1.3"
RYUJINX_TARGET_RELEASE_CHANNEL_NAME: "canary" RYUJINX_TARGET_RELEASE_CHANNEL_NAME: "canary"
RELEASE: 1 RELEASE: 1
@ -30,8 +29,8 @@ jobs:
strategy: strategy:
matrix: matrix:
platform: platform:
- { name: win-x64, os: windows-latest, zip_os_name: win_x64 } - { name: win-x64, os: ubuntu-latest, zip_os_name: win_x64 }
#- { name: win-arm64, os: windows-latest, zip_os_name: win_arm64 } #- { name: win-arm64, os: ubuntu-latest, zip_os_name: win_arm64 }
- { name: linux-x64, os: ubuntu-latest, zip_os_name: linux_x64 } - { name: linux-x64, os: ubuntu-latest, zip_os_name: linux_x64 }
- { name: linux-arm64, os: ubuntu-latest, zip_os_name: linux_arm64 } - { name: linux-arm64, os: ubuntu-latest, zip_os_name: linux_arm64 }
steps: steps:
@ -44,11 +43,25 @@ jobs:
- name: Overwrite csc problem matcher - name: Overwrite csc problem matcher
run: echo "::add-matcher::.github/csc.json" 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 - name: Get version info
id: version_info id: version_info
run: | run: |
echo "build_version=${{ env.RYUJINX_BASE_VERSION }}.${{ github.run_number }}" >> $GITHUB_OUTPUT echo "build_version=$(gli get-next-version -c Canary -R)" >> $GITHUB_OUTPUT
echo "prev_build_version=${{ env.RYUJINX_BASE_VERSION }}.$((${{ github.run_number }} - 1))" >> $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 echo "git_short_hash=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
shell: bash 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 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 - name: Packing Windows builds
if: matrix.platform.os == 'windows-latest' if: contains(matrix.platform.name, 'win')
run: | run: |
pushd publish pushd publish
rm libarmeilleure-jitsupport.dylib rm libarmeilleure-jitsupport.dylib
7z a ../release_output/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip ../publish 7z a ../release_output/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip ../publish
popd popd
gh release download -R GreemDev/GLI -O gli.exe -p 'GitLabCli-win_x64.exe' gli upload-generic-package -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/canary -n Ryubing-Canary -v ${{ steps.version_info.outputs.build_version }} -p release_output/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip
./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"
shell: bash shell: bash
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 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 - name: Packing Linux builds
if: matrix.platform.os == 'ubuntu-latest' if: contains(matrix.platform.name, 'linux')
run: | run: |
pushd publish pushd publish
rm libarmeilleure-jitsupport.dylib 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 tar -czvf ../release_output/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz ../publish
popd 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 }} -p release_output/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz
shell: bash shell: bash
- name: Build AppImage (Linux) - name: Build AppImage (Linux)
if: matrix.platform.os == 'ubuntu-latest' if: contains(matrix.platform.name, 'linux')
run: | run: |
BUILD_VERSION="${{ steps.version_info.outputs.build_version }}" BUILD_VERSION="${{ steps.version_info.outputs.build_version }}"
PLATFORM_NAME="${{ matrix.platform.name }}" PLATFORM_NAME="${{ matrix.platform.name }}"
@ -140,7 +140,7 @@ jobs:
mv Ryujinx.AppImage ../release_output/ryujinx-canary-$BUILD_VERSION-$ARCH_NAME.AppImage mv Ryujinx.AppImage ../release_output/ryujinx-canary-$BUILD_VERSION-$ARCH_NAME.AppImage
popd 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 }} -p release_output/ryujinx-canary-$BUILD_VERSION-$ARCH_NAME.AppImage
shell: bash shell: bash
macos_release: macos_release:
@ -159,10 +159,10 @@ jobs:
chmod +x llvm.sh chmod +x llvm.sh
sudo ./llvm.sh 17 sudo ./llvm.sh 17
- name: Install GitLabCli - name: Install gli
run: | run: |
mkdir -p $HOME/.bin 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 chmod +x gli
mv gli $HOME/.bin/ mv gli $HOME/.bin/
echo "$HOME/.bin" >> $GITHUB_PATH echo "$HOME/.bin" >> $GITHUB_PATH
@ -183,9 +183,10 @@ jobs:
- name: Get version info - name: Get version info
id: version_info id: version_info
run: | run: |
echo "build_version=${{ env.RYUJINX_BASE_VERSION }}.${{ github.run_number }}" >> $GITHUB_OUTPUT echo "build_version=$(gli get-next-version -c Canary -R)" >> $GITHUB_OUTPUT
echo "prev_build_version=${{ env.RYUJINX_BASE_VERSION }}.$((${{ github.run_number }} - 1))" >> $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 echo "git_short_hash=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
shell: bash
- name: Configure for release - name: Configure for release
run: | run: |
@ -200,7 +201,7 @@ jobs:
- name: Publish macOS Ryujinx - name: Publish macOS Ryujinx
run: | 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 ./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 }} -p publish_ava/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-macos_universal.app.tar.gz
create_gitlab_release: create_gitlab_release:
name: Create GitLab Release name: Create GitLab Release
@ -211,36 +212,40 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Get version info - name: Install gli
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
run: | run: |
mkdir -p $HOME/.bin 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 chmod +x gli
mv gli $HOME/.bin/ mv gli $HOME/.bin/
echo "$HOME/.bin" >> $GITHUB_PATH echo "$HOME/.bin" >> $GITHUB_PATH
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 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 - name: Create tag
run: | 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 - name: Create release
run: | 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 - name: Send notification webhook
run: | 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 - name: Notify update server of new builds
run: | 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

View File

@ -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"

View File

@ -1,15 +1,18 @@
name: Release job name: Stable CI
on: on:
workflow_dispatch: 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 concurrency: release
env: env:
POWERSHELL_TELEMETRY_OPTOUT: 1 POWERSHELL_TELEMETRY_OPTOUT: 1
DOTNET_CLI_TELEMETRY_OPTOUT: 1 DOTNET_CLI_TELEMETRY_OPTOUT: 1
RYUJINX_BASE_VERSION: "1.3"
RYUJINX_TARGET_RELEASE_CHANNEL_NAME: "release" RYUJINX_TARGET_RELEASE_CHANNEL_NAME: "release"
RELEASE: 1 RELEASE: 1
@ -20,8 +23,8 @@ jobs:
strategy: strategy:
matrix: matrix:
platform: platform:
- { name: win-x64, os: windows-latest, zip_os_name: win_x64 } - { name: win-x64, os: ubuntu-latest, zip_os_name: win_x64 }
#- { name: win-arm64, os: windows-latest, zip_os_name: win_arm64 } #- { name: win-arm64, os: ubuntu-latest, zip_os_name: win_arm64 }
- { name: linux-x64, os: ubuntu-latest, zip_os_name: linux_x64 } - { name: linux-x64, os: ubuntu-latest, zip_os_name: linux_x64 }
- { name: linux-arm64, os: ubuntu-latest, zip_os_name: linux_arm64 } - { name: linux-arm64, os: ubuntu-latest, zip_os_name: linux_arm64 }
steps: steps:
@ -34,11 +37,29 @@ jobs:
- name: Overwrite csc problem matcher - name: Overwrite csc problem matcher
run: echo "::add-matcher::.github/csc.json" 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 - name: Get version info
id: version_info id: version_info
run: | run: |
echo "build_version=${{ env.RYUJINX_BASE_VERSION }}.${{ github.run_number }}" >> $GITHUB_OUTPUT if [ '${{ inputs.is_bugfix_release }}' == 'false' ]; then
echo "prev_build_version=${{ env.RYUJINX_BASE_VERSION }}.$((${{ github.run_number }} - 1))" >> $GITHUB_OUTPUT 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 "git_short_hash=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
shell: bash shell: bash
@ -58,33 +79,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 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 - name: Packing Windows builds
if: matrix.platform.os == 'windows-latest' if: contains(matrix.platform.name, 'win')
run: | run: |
pushd publish pushd publish
rm libarmeilleure-jitsupport.dylib rm libarmeilleure-jitsupport.dylib
7z a ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip ../publish 7z a ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip ../publish
popd popd
gh release download -R GreemDev/GLI -O gli.exe -p 'GitLabCli-win_x64.exe' gli upload-generic-package -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/ryujinx -n Ryubing -v ${{ steps.version_info.outputs.build_version }} -p release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip
./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 shell: bash
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 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 - name: Packing Linux builds
if: matrix.platform.os == 'ubuntu-latest' if: contains(matrix.platform.name, 'linux')
run: | run: |
pushd publish pushd publish
rm libarmeilleure-jitsupport.dylib rm libarmeilleure-jitsupport.dylib
@ -92,13 +100,13 @@ jobs:
tar -czvf ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz ../publish tar -czvf ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz ../publish
popd 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 }} -p release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz
shell: bash shell: bash
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Build AppImage (Linux) - name: Build AppImage (Linux)
if: matrix.platform.os == 'ubuntu-latest' if: contains(matrix.platform.name, 'linux')
run: | run: |
BUILD_VERSION="${{ steps.version_info.outputs.build_version }}" BUILD_VERSION="${{ steps.version_info.outputs.build_version }}"
PLATFORM_NAME="${{ matrix.platform.name }}" PLATFORM_NAME="${{ matrix.platform.name }}"
@ -131,7 +139,7 @@ jobs:
mv Ryujinx.AppImage ../release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage mv Ryujinx.AppImage ../release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage
popd 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 }} -p release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage
shell: bash shell: bash
macos_release: macos_release:
@ -150,10 +158,10 @@ jobs:
chmod +x llvm.sh chmod +x llvm.sh
sudo ./llvm.sh 17 sudo ./llvm.sh 17
- name: Install GitLabCli - name: Install gli
run: | run: |
mkdir -p $HOME/.bin 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 chmod +x gli
mv gli $HOME/.bin/ mv gli $HOME/.bin/
echo "$HOME/.bin" >> $GITHUB_PATH echo "$HOME/.bin" >> $GITHUB_PATH
@ -174,9 +182,14 @@ jobs:
- name: Get version info - name: Get version info
id: version_info id: version_info
run: | run: |
echo "build_version=${{ env.RYUJINX_BASE_VERSION }}.${{ github.run_number }}" >> $GITHUB_OUTPUT if [ '${{ inputs.is_bugfix_release }}' == 'false' ]; then
echo "prev_build_version=${{ env.RYUJINX_BASE_VERSION }}.$((${{ github.run_number }} - 1))" >> $GITHUB_OUTPUT 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 "git_short_hash=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
shell: bash
- name: Configure for release - name: Configure for release
run: | run: |
@ -189,7 +202,8 @@ jobs:
- name: Publish macOS Ryujinx - name: Publish macOS Ryujinx
run: | 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 ./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 }} -p publish/ryujinx-${{ steps.version_info.outputs.build_version }}-macos_universal.app.tar.gz
create_gitlab_release: create_gitlab_release:
name: Create GitLab Release name: Create GitLab Release
@ -200,32 +214,45 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Get version info - name: Install gli
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
run: | run: |
mkdir -p $HOME/.bin 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 chmod +x gli
mv gli $HOME/.bin/ mv gli $HOME/.bin/
echo "$HOME/.bin" >> $GITHUB_PATH echo "$HOME/.bin" >> $GITHUB_PATH
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 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 - name: Create release
run: | 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 - name: Send notification webhook
run: | 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 - name: Notify update server of new builds
run: | 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
View File

@ -100,6 +100,7 @@ DocProject/Help/html
# Click-Once directory # Click-Once directory
publish/ publish/
RyubingMaintainerTools/
# Publish Web Output # Publish Web Output
*.Publish.xml *.Publish.xml

View File

@ -7,6 +7,9 @@ namespace Ryujinx.Common.Memory
{ {
private static readonly RecyclableMemoryStreamManager _shared = new(); private static readonly RecyclableMemoryStreamManager _shared = new();
private static readonly ObjectPool<RecyclableMemoryStream> _streamPool =
new(() => new RecyclableMemoryStream(_shared, Guid.NewGuid(), null, 0));
/// <summary> /// <summary>
/// We don't expose the <c>RecyclableMemoryStreamManager</c> directly because version 2.x /// 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 /// 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> /// </summary>
/// <returns>A <c>RecyclableMemoryStream</c></returns> /// <returns>A <c>RecyclableMemoryStream</c></returns>
public static RecyclableMemoryStream GetStream() public static RecyclableMemoryStream GetStream()
=> new(_shared); {
RecyclableMemoryStream stream = _streamPool.Allocate();
stream.SetLength(0);
return stream;
}
/// <summary> /// <summary>
/// Retrieve a new <c>MemoryStream</c> object with the contents copied from the provided /// 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; RecyclableMemoryStream stream = null;
try try
{ {
stream = new RecyclableMemoryStream(_shared, id, tag, buffer.Length); stream = _streamPool.Allocate();
stream.SetLength(0);
stream.Write(buffer); stream.Write(buffer);
stream.Position = 0; stream.Position = 0;
return stream; return stream;
@ -83,7 +92,8 @@ namespace Ryujinx.Common.Memory
RecyclableMemoryStream stream = null; RecyclableMemoryStream stream = null;
try try
{ {
stream = new RecyclableMemoryStream(_shared, id, tag, count); stream = _streamPool.Allocate();
stream.SetLength(0);
stream.Write(buffer, offset, count); stream.Write(buffer, offset, count);
stream.Position = 0; stream.Position = 0;
return stream; return stream;
@ -94,6 +104,11 @@ namespace Ryujinx.Common.Memory
throw; throw;
} }
} }
public static void ReleaseStream(RecyclableMemoryStream stream)
{
_streamPool.Release(stream);
}
} }
} }
} }

View File

@ -82,7 +82,7 @@ namespace Ryujinx.Graphics.GAL
void SetRasterizerDiscard(bool discard); void SetRasterizerDiscard(bool discard);
void SetRenderTargetColorMasks(ReadOnlySpan<uint> componentMask); void SetRenderTargetColorMasks(ReadOnlySpan<uint> componentMask);
void SetRenderTargets(ITexture[] colors, ITexture depthStencil); void SetRenderTargets(Span<ITexture> colors, ITexture depthStencil);
void SetScissors(ReadOnlySpan<Rectangle<int>> regions); void SetScissors(ReadOnlySpan<Rectangle<int>> regions);

View File

@ -1,5 +1,6 @@
using Ryujinx.Graphics.GAL.Multithreading.Model; using Ryujinx.Graphics.GAL.Multithreading.Model;
using Ryujinx.Graphics.GAL.Multithreading.Resources; using Ryujinx.Graphics.GAL.Multithreading.Resources;
using System;
using System.Buffers; using System.Buffers;
namespace Ryujinx.Graphics.GAL.Multithreading.Commands 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 static readonly ArrayPool<ITexture> ArrayPool = ArrayPool<ITexture>.Create(512, 50);
public readonly CommandType CommandType => CommandType.SetRenderTargets; public readonly CommandType CommandType => CommandType.SetRenderTargets;
private int _colorsCount;
private TableRef<ITexture[]> _colors; private TableRef<ITexture[]> _colors;
private TableRef<ITexture> _depthStencil; 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; _colors = colors;
_depthStencil = depthStencil; _depthStencil = depthStencil;
} }
@ -20,16 +23,15 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands
public static void Run(ref SetRenderTargetsCommand command, ThreadedRenderer threaded, IRenderer renderer) public static void Run(ref SetRenderTargetsCommand command, ThreadedRenderer threaded, IRenderer renderer)
{ {
ITexture[] colors = command._colors.Get(threaded); 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); ArrayPool.Return(colors);
} }
} }

View File

@ -267,12 +267,12 @@ namespace Ryujinx.Graphics.GAL.Multithreading
_renderer.QueueCommand(); _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); 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(); _renderer.QueueCommand();
} }

View File

@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
/// </summary> /// </summary>
public class ThreadedRenderer : IRenderer 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 MaxRefsPerCommand = 2;
private const int QueueCount = 10000; private const int QueueCount = 10000;

View File

@ -404,9 +404,12 @@ namespace Ryujinx.Graphics.Gpu
if (force || _pendingSync || (syncPoint && SyncpointActions.Count > 0)) 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) foreach (ISyncActionHandler action in SyncpointActions)

View File

@ -411,7 +411,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// flushes often enough, which is determined by the flush balance. /// flushes often enough, which is determined by the flush balance.
/// </summary> /// </summary>
/// <inheritdoc/> /// <inheritdoc/>
public void SyncPreAction(bool syncpoint) public bool SyncPreAction(bool syncpoint)
{ {
if (syncpoint || NextSyncCopies()) if (syncpoint || NextSyncCopies())
{ {
@ -421,6 +421,8 @@ namespace Ryujinx.Graphics.Gpu.Image
_registeredBufferSync = _modifiedSync; _registeredBufferSync = _modifiedSync;
} }
} }
return true;
} }
/// <summary> /// <summary>

View File

@ -15,7 +15,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <summary> /// <summary>
/// Buffer, used to store vertex and index data, uniform and storage buffers, and others. /// Buffer, used to store vertex and index data, uniform and storage buffers, and others.
/// </summary> /// </summary>
class Buffer : INonOverlappingRange, ISyncActionHandler, IDisposable class Buffer : INonOverlappingRange<Buffer>, ISyncActionHandler, IDisposable
{ {
private const ulong GranularBufferThreshold = 4096; private const ulong GranularBufferThreshold = 4096;
@ -42,6 +42,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// </summary> /// </summary>
public ulong EndAddress => Address + Size; public ulong EndAddress => Address + Size;
public Buffer Next { get; set; }
public Buffer Previous { get; set; }
/// <summary> /// <summary>
/// Increments when the buffer is (partially) unmapped or disposed. /// Increments when the buffer is (partially) unmapped or disposed.
/// </summary> /// </summary>
@ -87,6 +90,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
private readonly bool _useGranular; private readonly bool _useGranular;
private bool _syncActionRegistered; private bool _syncActionRegistered;
private bool _bufferInherited;
private int _referenceCount = 1; private int _referenceCount = 1;
@ -113,7 +117,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
ulong size, ulong size,
BufferStage stage, BufferStage stage,
bool sparseCompatible, bool sparseCompatible,
RangeItem<Buffer>[] baseBuffers) Buffer[] baseBuffers)
{ {
_context = context; _context = context;
_physicalMemory = physicalMemory; _physicalMemory = physicalMemory;
@ -134,15 +138,15 @@ namespace Ryujinx.Graphics.Gpu.Memory
if (baseBuffers.Length != 0) if (baseBuffers.Length != 0)
{ {
baseHandles = new List<IRegionHandle>(); 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 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. /// Checks if a given range overlaps with the buffer.
/// </summary> /// </summary>
/// <param name="address">Start address of the range</param> /// <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> /// <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(); throw new NotImplementedException();
} }
@ -389,11 +393,16 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// This will copy any buffer ranges designated for pre-flushing. /// This will copy any buffer ranges designated for pre-flushing.
/// </summary> /// </summary>
/// <param name="syncpoint">True if the action is a guest syncpoint</param> /// <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) if (_referenceCount == 0)
{ {
return; return false;
} }
if (BackingState.ShouldChangeBacking()) if (BackingState.ShouldChangeBacking())
@ -410,6 +419,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
_modifiedRanges?.GetRangesAtSync(Address, Size, _context.SyncNumber, _syncPreRangeAction); _modifiedRanges?.GetRangesAtSync(Address, Size, _context.SyncNumber, _syncPreRangeAction);
} }
} }
return false;
} }
void SyncPreRangeAction(ulong address, ulong size) void SyncPreRangeAction(ulong address, ulong size)
@ -426,10 +437,13 @@ namespace Ryujinx.Graphics.Gpu.Memory
{ {
_syncActionRegistered = false; _syncActionRegistered = false;
if (_bufferInherited)
{
return true;
}
if (_useGranular) if (_useGranular)
{ {
_modifiedRanges?.GetRanges(Address, Size, _syncRangeAction); _modifiedRanges?.GetRanges(Address, Size, _syncRangeAction);
} }
else else
@ -453,6 +467,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="from">The buffer to inherit from</param> /// <param name="from">The buffer to inherit from</param>
public void InheritModifiedRanges(Buffer from) public void InheritModifiedRanges(Buffer from)
{ {
from._bufferInherited = true;
if (from._modifiedRanges is { HasRanges: true }) if (from._modifiedRanges is { HasRanges: true })
{ {
if (from._syncActionRegistered && !_syncActionRegistered) if (from._syncActionRegistered && !_syncActionRegistered)

View File

@ -56,7 +56,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="parent">Parent buffer</param> /// <param name="parent">Parent buffer</param>
/// <param name="stage">Initial buffer stage</param> /// <param name="stage">Initial buffer stage</param>
/// <param name="baseBuffers">Buffers to inherit state from</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; _size = (int)parent.Size;
_systemMemoryType = context.Capabilities.MemoryType; _systemMemoryType = context.Capabilities.MemoryType;
@ -102,9 +102,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
if (baseBuffers.Length != 0) if (baseBuffers.Length != 0)
{ {
foreach (RangeItem<Buffer> item in baseBuffers) foreach (Buffer item in baseBuffers)
{ {
CombineState(item.Value.BackingState); CombineState(item.BackingState);
} }
} }
} }

View File

@ -80,15 +80,12 @@ namespace Ryujinx.Graphics.Gpu.Memory
{ {
MemoryRange subRange = range.GetSubRange(index); MemoryRange subRange = range.GetSubRange(index);
_buffers.Lock.EnterReadLock(); ReadOnlySpan<Buffer> overlaps = _buffers.FindOverlapsAsSpan(subRange.Address, subRange.Size);
Span<RangeItem<Buffer>> overlaps = _buffers.FindOverlapsAsSpan(subRange.Address, subRange.Size);
for (int i = 0; i < overlaps.Length; i++) 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 alignedEndAddress = (endAddress + alignmentMask) & ~alignmentMask;
ulong alignedSize = alignedEndAddress - alignedAddress; ulong alignedSize = alignedEndAddress - alignedAddress;
Buffer buffer = _buffers.FindOverlap(alignedAddress, alignedSize).Value; Buffer buffer = _buffers.FindOverlap(alignedAddress, alignedSize);
BufferRange bufferRange = buffer.GetRange(alignedAddress, alignedSize, false); BufferRange bufferRange = buffer.GetRange(alignedAddress, alignedSize, false);
alignedSubRanges[i] = new MemoryRange(alignedAddress, alignedSize); alignedSubRanges[i] = new MemoryRange(alignedAddress, alignedSize);
@ -395,7 +392,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
if (subRange.Address != MemoryManager.PteUnmapped) 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); virtualBuffer.AddPhysicalDependency(buffer, subRange.Address, dstOffset, subRange.Size);
physicalBuffers.Add(buffer); physicalBuffers.Add(buffer);
@ -487,10 +484,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="stage">The type of usage that created the buffer</param> /// <param name="stage">The type of usage that created the buffer</param>
private void CreateBufferAligned(ulong address, ulong size, BufferStage stage) private void CreateBufferAligned(ulong address, ulong size, BufferStage stage)
{ {
Buffer newBuffer = null; ReadOnlySpan<Buffer> overlaps = _buffers.FindOverlapsAsSpan(address, size);
_buffers.Lock.EnterWriteLock();
Span<RangeItem<Buffer>> overlaps = _buffers.FindOverlapsAsSpan(address, size);
if (overlaps.Length != 0) 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. // 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. // 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; ulong growthSize = (existingSize + Math.Min(existingSize >> 1, MaxDynamicGrowthSize)) & ~BufferAlignmentMask;
size = Math.Max(size, growthSize); size = Math.Max(size, growthSize);
@ -535,39 +529,22 @@ namespace Ryujinx.Graphics.Gpu.Memory
for (int i = 0; i < overlaps.Length; i++) 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.RemoveRange(overlaps[0], overlaps[^1]);
_buffers.Lock.ExitWriteLock();
ulong newSize = endAddress - address; ulong newSize = endAddress - address;
newBuffer = CreateBufferAligned(address, newSize, stage, anySparseCompatible, overlapsArray); _buffers.Add(CreateBufferAligned(address, newSize, stage, anySparseCompatible, overlapsArray));
}
else
{
_buffers.Lock.ExitWriteLock();
} }
} }
else else
{ {
_buffers.Lock.ExitWriteLock();
// No overlap, just create a new buffer. // No overlap, just create a new buffer.
newBuffer = new(_context, _physicalMemory, address, size, stage, sparseCompatible: false, []); _buffers.Add(new(_context, _physicalMemory, address, size, stage, sparseCompatible: false, []));
}
if (newBuffer is not null)
{
_buffers.Lock.EnterWriteLock();
_buffers.Add(newBuffer);
_buffers.Lock.ExitWriteLock();
} }
} }
@ -583,10 +560,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
private void CreateBufferAligned(ulong address, ulong size, BufferStage stage, ulong alignment) private void CreateBufferAligned(ulong address, ulong size, BufferStage stage, ulong alignment)
{ {
bool sparseAligned = alignment >= SparseBufferAlignmentSize; bool sparseAligned = alignment >= SparseBufferAlignmentSize;
Buffer newBuffer = null;
_buffers.Lock.EnterWriteLock(); ReadOnlySpan<Buffer> overlaps = _buffers.FindOverlapsAsSpan(address, size);
Span<RangeItem<Buffer>> overlaps = _buffers.FindOverlapsAsSpan(address, size);
if (overlaps.Length != 0) if (overlaps.Length != 0)
{ {
@ -598,7 +573,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
if (overlaps[0].Address > address || if (overlaps[0].Address > address ||
overlaps[0].EndAddress < endAddress || overlaps[0].EndAddress < endAddress ||
(overlaps[0].Address & (alignment - 1)) != 0 || (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. // We need to make sure the new buffer is properly aligned.
// However, after the range is aligned, it is possible that it // However, after the range is aligned, it is possible that it
@ -622,34 +597,17 @@ namespace Ryujinx.Graphics.Gpu.Memory
ulong newSize = endAddress - address; ulong newSize = endAddress - address;
RangeItem<Buffer>[] overlapsArray = overlaps.ToArray(); Buffer[] overlapsArray = overlaps.ToArray();
_buffers.RemoveRange(overlaps[0], overlaps[^1]); _buffers.RemoveRange(overlaps[0], overlaps[^1]);
_buffers.Lock.ExitWriteLock(); _buffers.Add(CreateBufferAligned(address, newSize, stage, sparseAligned, overlapsArray));
newBuffer = CreateBufferAligned(address, newSize, stage, sparseAligned, overlapsArray);
}
else
{
_buffers.Lock.ExitWriteLock();
} }
} }
else else
{ {
_buffers.Lock.ExitWriteLock();
// No overlap, just create a new buffer. // No overlap, just create a new buffer.
newBuffer = new(_context, _physicalMemory, address, size, stage, sparseAligned, []); _buffers.Add(new(_context, _physicalMemory, address, size, stage, sparseAligned, []));
}
if (newBuffer is not null)
{
_buffers.Lock.EnterWriteLock();
_buffers.Add(newBuffer);
_buffers.Lock.ExitWriteLock();
} }
} }
@ -663,13 +621,13 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="stage">The type of usage that created the buffer</param> /// <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="sparseCompatible">Indicates if the buffer can be used in a sparse buffer mapping</param>
/// <param name="overlaps">Buffers overlapping the range</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); Buffer newBuffer = new(_context, _physicalMemory, address, size, stage, sparseCompatible, overlaps);
for (int index = 0; index < overlaps.Length; index++) for (int index = 0; index < overlaps.Length; index++)
{ {
Buffer buffer = overlaps[index].Value; Buffer buffer = overlaps[index];
int dstOffset = (int)(buffer.Address - newBuffer.Address); int dstOffset = (int)(buffer.Address - newBuffer.Address);
@ -897,7 +855,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
{ {
MemoryRange subRange = range.GetSubRange(i); 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); subBuffer.SynchronizeMemory(subRange.Address, subRange.Size);
@ -945,7 +903,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
if (size != 0) if (size != 0)
{ {
buffer = _buffers.FindOverlap(address, size).Value; buffer = _buffers.FindOverlap(address, size);
buffer.CopyFromDependantVirtualBuffers(); buffer.CopyFromDependantVirtualBuffers();
buffer.SynchronizeMemory(address, size); buffer.SynchronizeMemory(address, size);
@ -957,7 +915,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
} }
else else
{ {
buffer = _buffers.FindOverlapFast(address, 1).Value; buffer = _buffers.FindOverlapFast(address, 1);
} }
return buffer; return buffer;
@ -995,7 +953,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
{ {
if (size != 0) if (size != 0)
{ {
Buffer buffer = _buffers.FindOverlap(address, size).Value; Buffer buffer = _buffers.FindOverlap(address, size);
if (copyBackVirtual) if (copyBackVirtual)
{ {

View File

@ -8,7 +8,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <summary> /// <summary>
/// A range within a buffer that has been modified by the GPU. /// A range within a buffer that has been modified by the GPU.
/// </summary> /// </summary>
class BufferModifiedRange : INonOverlappingRange class BufferModifiedRange : INonOverlappingRange<BufferModifiedRange>
{ {
/// <summary> /// <summary>
/// Start address of the range in guest memory. /// Start address of the range in guest memory.
@ -25,6 +25,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// </summary> /// </summary>
public ulong EndAddress => Address + Size; public ulong EndAddress => Address + Size;
public BufferModifiedRange Next { get; set; }
public BufferModifiedRange Previous { get; set; }
/// <summary> /// <summary>
/// The GPU sync number at the time of the last modification. /// The GPU sync number at the time of the last modification.
/// </summary> /// </summary>
@ -54,14 +57,14 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// Checks if a given range overlaps with the modified range. /// Checks if a given range overlaps with the modified range.
/// </summary> /// </summary>
/// <param name="address">Start address of the range</param> /// <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> /// <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(); 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. // Slices a given region using the modified regions in the list. Calls the action for the new slices.
Lock.EnterReadLock(); Lock.EnterReadLock();
Span<RangeItem<BufferModifiedRange>> overlaps = FindOverlapsAsSpan(address, size); ReadOnlySpan<BufferModifiedRange> overlaps = FindOverlapsAsSpan(address, size);
for (int i = 0; i < overlaps.Length; i++) for (int i = 0; i < overlaps.Length; i++)
{ {
BufferModifiedRange overlap = overlaps[i].Value; BufferModifiedRange overlap = overlaps[i];
if (overlap.Address > address) if (overlap.Address > address)
{ {
@ -157,7 +160,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
ulong syncNumber = _context.SyncNumber; ulong syncNumber = _context.SyncNumber;
// We may overlap with some existing modified regions. They must be cut into by the new entry. // We may overlap with some existing modified regions. They must be cut into by the new entry.
Lock.EnterWriteLock(); Lock.EnterWriteLock();
(RangeItem<BufferModifiedRange> first, RangeItem<BufferModifiedRange> last) = FindOverlapsAsNodes(address, size); (BufferModifiedRange first, BufferModifiedRange last) = FindOverlapsAsNodes(address, size);
if (first is null) if (first is null)
{ {
@ -170,34 +173,39 @@ namespace Ryujinx.Graphics.Gpu.Memory
{ {
if (first.Address == address && first.EndAddress == endAddress) if (first.Address == address && first.EndAddress == endAddress)
{ {
first.Value.SyncNumber = syncNumber; first.SyncNumber = syncNumber;
first.Value.Parent = this; first.Parent = this;
Lock.ExitWriteLock(); Lock.ExitWriteLock();
return; return;
} }
if (first.Address < address) if (first.Address < address)
{ {
first.Value.Size = address - first.Address;
Update(first);
if (first.EndAddress > endAddress) if (first.EndAddress > endAddress)
{ {
Add(new BufferModifiedRange(endAddress, 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 else
{ {
if (first.EndAddress > endAddress) if (first.EndAddress > endAddress)
{ {
first.Value.Size = first.EndAddress - endAddress; first.Size = first.EndAddress - endAddress;
first.Value.Address = endAddress; first.Address = endAddress;
Update(first);
} }
else 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; return;
} }
BufferModifiedRange buffPre = null;
BufferModifiedRange buffPost = null;
bool extendsPost = false;
bool extendsPre = false;
if (first.Address < address) if (first.Address < address)
{ {
buffPre = new BufferModifiedRange(first.Address, address - first.Address, first.Size = address - first.Address;
first.Value.SyncNumber, first.Value.Parent); first = first.Next;
extendsPre = true;
} }
if (last.EndAddress > endAddress) if (last.EndAddress > endAddress)
{ {
buffPost = new BufferModifiedRange(endAddress, last.EndAddress - endAddress, last.Size = last.EndAddress - endAddress;
last.Value.SyncNumber, last.Value.Parent); last.Address = endAddress;
extendsPost = true; last = last.Previous;
} }
RemoveRange(first, last); if (first.Address < last.Address)
if (extendsPre)
{ {
Add(buffPre); RemoveRange(first.Next, last);
first.Address = address;
first.Size = size;
first.SyncNumber = syncNumber;
first.Parent = this;
} }
else if (first.Address == last.Address)
if (extendsPost)
{ {
Add(buffPost); first.Address = address;
first.Size = size;
first.SyncNumber = syncNumber;
first.Parent = this;
}
else
{
Add(new BufferModifiedRange(address, size, syncNumber, this));
} }
Add(new BufferModifiedRange(address, size, syncNumber, this));
Lock.ExitWriteLock(); Lock.ExitWriteLock();
} }
@ -252,11 +261,11 @@ namespace Ryujinx.Graphics.Gpu.Memory
public void GetRangesAtSync(ulong address, ulong size, ulong syncNumber, Action<ulong, ulong> rangeAction) public void GetRangesAtSync(ulong address, ulong size, ulong syncNumber, Action<ulong, ulong> rangeAction)
{ {
Lock.EnterReadLock(); Lock.EnterReadLock();
Span<RangeItem<BufferModifiedRange>> overlaps = FindOverlapsAsSpan(address, size); ReadOnlySpan<BufferModifiedRange> overlaps = FindOverlapsAsSpan(address, size);
for (int i = 0; i < overlaps.Length; i++) for (int i = 0; i < overlaps.Length; i++)
{ {
BufferModifiedRange overlap = overlaps[i].Value; BufferModifiedRange overlap = overlaps[i];
if (overlap.SyncNumber == syncNumber) 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. // We use the non-span method here because keeping the lock will cause a deadlock.
Lock.EnterReadLock(); Lock.EnterReadLock();
RangeItem<BufferModifiedRange>[] overlaps = FindOverlapsAsArray(address, size, out int length); BufferModifiedRange[] overlaps = FindOverlapsAsArray(address, size, out int length);
Lock.ExitReadLock(); Lock.ExitReadLock();
if (length != 0) if (length != 0)
{ {
for (int i = 0; i < length; i++) for (int i = 0; i < length; i++)
{ {
BufferModifiedRange overlap = overlaps[i].Value; BufferModifiedRange overlap = overlaps[i];
rangeAction(overlap.Address, overlap.Size); 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) public bool HasRange(ulong address, ulong size)
{ {
Lock.EnterReadLock(); Lock.EnterReadLock();
RangeItem<BufferModifiedRange> first = FindOverlapFast(address, size); BufferModifiedRange first = FindOverlapFast(address, size);
bool result = first is not null; bool result = first is not null;
Lock.ExitReadLock(); Lock.ExitReadLock();
return result; return result;
@ -336,7 +345,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="address">The start address of the flush range</param> /// <param name="address">The start address of the flush range</param>
/// <param name="endAddress">The end address of the flush range</param> /// <param name="endAddress">The end address of the flush range</param>
private void RemoveRangesAndFlush( private void RemoveRangesAndFlush(
RangeItem<BufferModifiedRange>[] overlaps, BufferModifiedRange[] overlaps,
int rangeCount, int rangeCount,
long highestDiff, long highestDiff,
ulong currentSync, ulong currentSync,
@ -349,7 +358,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
for (int i = 0; i < rangeCount; i++) for (int i = 0; i < rangeCount; i++)
{ {
BufferModifiedRange overlap = overlaps[i].Value; BufferModifiedRange overlap = overlaps[i];
long diff = (long)(overlap.SyncNumber - currentSync); long diff = (long)(overlap.SyncNumber - currentSync);
@ -358,7 +367,14 @@ namespace Ryujinx.Graphics.Gpu.Memory
ulong clampAddress = Math.Max(address, overlap.Address); ulong clampAddress = Math.Max(address, overlap.Address);
ulong clampEnd = Math.Min(endAddress, overlap.EndAddress); 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); RangeActionWithMigration(clampAddress, clampEnd - clampAddress, waitSync, _flushAction);
} }
@ -398,7 +414,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
Lock.EnterWriteLock(); Lock.EnterWriteLock();
// We use the non-span method here because the array is partially modified by the code, which would invalidate a span. // 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) if (rangeCount == 0)
{ {
@ -414,7 +430,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
for (int i = 0; i < rangeCount; i++) for (int i = 0; i < rangeCount; i++)
{ {
BufferModifiedRange overlap = overlaps![i].Value; BufferModifiedRange overlap = overlaps![i];
long diff = (long)(overlap.SyncNumber - currentSync); long diff = (long)(overlap.SyncNumber - currentSync);
@ -436,7 +452,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
RemoveRangesAndFlush(overlaps, rangeCount, highestDiff, currentSync, address, endAddress); RemoveRangesAndFlush(overlaps, rangeCount, highestDiff, currentSync, address, endAddress);
ArrayPool<RangeItem<BufferModifiedRange>>.Shared.Return(overlaps!); ArrayPool<BufferModifiedRange>.Shared.Return(overlaps!);
Lock.ExitWriteLock(); Lock.ExitWriteLock();
} }
@ -452,7 +468,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
public void InheritRanges(BufferModifiedRangeList ranges, Action<ulong, ulong> registerRangeAction) public void InheritRanges(BufferModifiedRangeList ranges, Action<ulong, ulong> registerRangeAction)
{ {
ranges.Lock.EnterReadLock(); 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(); ranges.Lock.ExitReadLock();
// Copy over the migration from the previous range list // Copy over the migration from the previous range list
@ -479,21 +497,25 @@ namespace Ryujinx.Graphics.Gpu.Memory
Lock.EnterWriteLock(); Lock.EnterWriteLock();
foreach (BufferModifiedRange range in inheritRanges) for (int i = 0; i < rangesCount; i++)
{ {
BufferModifiedRange range = inheritRanges[i];
Add(range); Add(range);
} }
Lock.ExitWriteLock(); Lock.ExitWriteLock();
ulong currentSync = _context.SyncNumber; ulong currentSync = _context.SyncNumber;
foreach (BufferModifiedRange range in inheritRanges) for (int i = 0; i < rangesCount; i++)
{ {
BufferModifiedRange range = inheritRanges[i];
if (range.SyncNumber != currentSync) if (range.SyncNumber != currentSync)
{ {
registerRangeAction(range.Address, range.Size); registerRangeAction(range.Address, range.Size);
} }
} }
ArrayPool<BufferModifiedRange>.Shared.Return(inheritRanges);
} }
/// <summary> /// <summary>
@ -534,18 +556,25 @@ namespace Ryujinx.Graphics.Gpu.Memory
private void ClearPart(BufferModifiedRange overlap, ulong address, ulong endAddress) 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 the overlap extends outside of the clear range, make sure those parts still exist.
if (overlap.Address < address) 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));
}
if (overlap.EndAddress > endAddress) overlap.Size = address - overlap.Address;
}
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; ulong endAddress = address + size;
Lock.EnterWriteLock(); Lock.EnterWriteLock();
(RangeItem<BufferModifiedRange> first, RangeItem<BufferModifiedRange> last) = FindOverlapsAsNodes(address, size); (BufferModifiedRange first, BufferModifiedRange last) = FindOverlapsAsNodes(address, size);
if (first is null) if (first is null)
{ {
@ -570,26 +599,24 @@ namespace Ryujinx.Graphics.Gpu.Memory
{ {
if (first.Address < address) if (first.Address < address)
{ {
first.Value.Size = address - first.Address; first.Size = address - first.Address;
Update(first);
if (first.EndAddress > endAddress) if (first.EndAddress > endAddress)
{ {
Add(new BufferModifiedRange(endAddress, first.EndAddress - endAddress, Add(new BufferModifiedRange(endAddress, first.EndAddress - endAddress,
first.Value.SyncNumber, first.Value.Parent)); first.SyncNumber, first.Parent));
} }
} }
else else
{ {
if (first.EndAddress > endAddress) if (first.EndAddress > endAddress)
{ {
first.Value.Size = first.EndAddress - endAddress; first.Size = first.EndAddress - endAddress;
first.Value.Address = endAddress; first.Address = endAddress;
Update(first);
} }
else else
{ {
Remove(first.Value); Remove(first);
} }
} }
@ -605,14 +632,14 @@ namespace Ryujinx.Graphics.Gpu.Memory
if (first.Address < address) if (first.Address < address)
{ {
buffPre = new BufferModifiedRange(first.Address, address - first.Address, buffPre = new BufferModifiedRange(first.Address, address - first.Address,
first.Value.SyncNumber, first.Value.Parent); first.SyncNumber, first.Parent);
extendsPre = true; extendsPre = true;
} }
if (last.EndAddress > endAddress) if (last.EndAddress > endAddress)
{ {
buffPost = new BufferModifiedRange(endAddress, last.EndAddress - endAddress, buffPost = new BufferModifiedRange(endAddress, last.EndAddress - endAddress,
last.Value.SyncNumber, last.Value.Parent); last.SyncNumber, last.Parent);
extendsPost = true; extendsPost = true;
} }

View File

@ -15,7 +15,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <summary> /// <summary>
/// Represents a GPU virtual memory range. /// Represents a GPU virtual memory range.
/// </summary> /// </summary>
private class VirtualRange : INonOverlappingRange private class VirtualRange : INonOverlappingRange<VirtualRange>
{ {
/// <summary> /// <summary>
/// GPU virtual address where the range starts. /// GPU virtual address where the range starts.
@ -32,6 +32,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// </summary> /// </summary>
public ulong EndAddress => Address + Size; public ulong EndAddress => Address + Size;
public VirtualRange Next { get; set; }
public VirtualRange Previous { get; set; }
/// <summary> /// <summary>
/// Physical regions where the GPU virtual region is mapped. /// Physical regions where the GPU virtual region is mapped.
/// </summary> /// </summary>
@ -54,14 +57,14 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// Checks if a given range overlaps with the buffer. /// Checks if a given range overlaps with the buffer.
/// </summary> /// </summary>
/// <param name="address">Start address of the range</param> /// <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> /// <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(); throw new NotImplementedException();
} }
@ -122,7 +125,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
ulong originalVa = gpuVa; ulong originalVa = gpuVa;
_virtualRanges.Lock.EnterWriteLock(); _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) if (first is not null)
{ {
@ -147,8 +150,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
} }
else else
{ {
found = first.Value.Range.Count == 1 || IsSparseAligned(first.Value.Range); found = first.Range.Count == 1 || IsSparseAligned(first.Range);
range = first.Value.Range.Slice(gpuVa - first.Address, size); range = first.Range.Slice(gpuVa - first.Address, size);
} }
} }
else else

View File

@ -17,6 +17,6 @@ namespace Ryujinx.Graphics.Gpu.Synchronization
/// Action to be performed immediately before sync is created. /// Action to be performed immediately before sync is created.
/// </summary> /// </summary>
/// <param name="syncpoint">True if the action is a guest syncpoint</param> /// <param name="syncpoint">True if the action is a guest syncpoint</param>
void SyncPreAction(bool syncpoint) { } bool SyncPreAction(bool syncpoint) { return true; }
} }
} }

View File

@ -1166,7 +1166,7 @@ namespace Ryujinx.Graphics.OpenGL
} }
} }
public void SetRenderTargets(ITexture[] colors, ITexture depthStencil) public void SetRenderTargets(Span<ITexture> colors, ITexture depthStencil)
{ {
EnsureFramebuffer(); EnsureFramebuffer();

View File

@ -71,17 +71,31 @@ namespace Ryujinx.Graphics.Vulkan
HasDepthStencil = isDepthStencil; HasDepthStencil = isDepthStencil;
} }
public FramebufferParams(Device device, ITexture[] colors, ITexture depthStencil) public FramebufferParams(Device device, ReadOnlySpan<ITexture> colors, ITexture depthStencil)
{ {
_device = device; _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); int count = colorsCount + (IsValidTextureView(depthStencil) ? 1 : 0);
_attachments = new Auto<DisposableImageView>[count]; _attachments = new Auto<DisposableImageView>[count];
_colors = new TextureView[colorsCount]; _colors = new TextureView[colorsCount];
_colorsCanonical = colors.Select(color => color is TextureView view && view.Valid ? view : null).ToArray();
AttachmentSamples = new uint[count]; AttachmentSamples = new uint[count];
AttachmentFormats = new VkFormat[count]; AttachmentFormats = new VkFormat[count];
@ -165,9 +179,17 @@ namespace Ryujinx.Graphics.Vulkan
_totalCount = colors.Length; _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); int count = colorsCount + (IsValidTextureView(depthStencil) ? 1 : 0);

View File

@ -1,7 +1,7 @@
using Ryujinx.Common;
using Ryujinx.Common.Memory; using Ryujinx.Common.Memory;
using Silk.NET.Vulkan; using Silk.NET.Vulkan;
using System; using System;
using System.Buffers;
namespace Ryujinx.Graphics.Vulkan namespace Ryujinx.Graphics.Vulkan
{ {
@ -10,6 +10,8 @@ namespace Ryujinx.Graphics.Vulkan
/// </summary> /// </summary>
class MultiFenceHolder class MultiFenceHolder
{ {
public static readonly ObjectPool<FenceHolder[]> FencePool = new(() => new FenceHolder[CommandBufferPool.MaxCommandBuffers]);
private const int BufferUsageTrackingGranularity = 4096; private const int BufferUsageTrackingGranularity = 4096;
public FenceHolder[] Fences { get; } public FenceHolder[] Fences { get; }
@ -20,7 +22,7 @@ namespace Ryujinx.Graphics.Vulkan
/// </summary> /// </summary>
public MultiFenceHolder() public MultiFenceHolder()
{ {
Fences = ArrayPool<FenceHolder>.Shared.Rent(CommandBufferPool.MaxCommandBuffers); Fences = FencePool.Allocate();
} }
/// <summary> /// <summary>
@ -29,7 +31,7 @@ namespace Ryujinx.Graphics.Vulkan
/// <param name="size">Size of the buffer</param> /// <param name="size">Size of the buffer</param>
public MultiFenceHolder(int size) public MultiFenceHolder(int size)
{ {
Fences = ArrayPool<FenceHolder>.Shared.Rent(CommandBufferPool.MaxCommandBuffers); Fences = FencePool.Allocate();
_bufferUsageBitmap = new BufferUsageBitmap(size, BufferUsageTrackingGranularity); _bufferUsageBitmap = new BufferUsageBitmap(size, BufferUsageTrackingGranularity);
} }

View File

@ -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); CreateFramebuffer(colors, depthStencil, filterWriteMasked);
CreateRenderPass(); CreateRenderPass();
@ -1043,7 +1043,7 @@ namespace Ryujinx.Graphics.Vulkan
SignalAttachmentChange(); SignalAttachmentChange();
} }
public void SetRenderTargets(ITexture[] colors, ITexture depthStencil) public void SetRenderTargets(Span<ITexture> colors, ITexture depthStencil)
{ {
_framebufferUsingColorWriteMask = false; _framebufferUsingColorWriteMask = false;
SetRenderTargetsInternal(colors, depthStencil, Gd.IsTBDR); SetRenderTargetsInternal(colors, depthStencil, Gd.IsTBDR);
@ -1389,7 +1389,7 @@ namespace Ryujinx.Graphics.Vulkan
_currentPipelineHandle = 0; _currentPipelineHandle = 0;
} }
private void CreateFramebuffer(ITexture[] colors, ITexture depthStencil, bool filterWriteMasked) private void CreateFramebuffer(Span<ITexture> colors, ITexture depthStencil, bool filterWriteMasked)
{ {
if (filterWriteMasked) if (filterWriteMasked)
{ {
@ -1399,7 +1399,7 @@ namespace Ryujinx.Graphics.Vulkan
// Just try to remove duplicate attachments. // Just try to remove duplicate attachments.
// Save a copy of the array to rebind when mask changes. // Save a copy of the array to rebind when mask changes.
void MaskOut() void MaskOut(ReadOnlySpan<ITexture> colors)
{ {
if (!_framebufferUsingColorWriteMask) if (!_framebufferUsingColorWriteMask)
{ {
@ -1436,12 +1436,12 @@ namespace Ryujinx.Graphics.Vulkan
if (vkBlend.ColorWriteMask == 0) if (vkBlend.ColorWriteMask == 0)
{ {
colors[i] = null; colors[i] = null;
MaskOut(); MaskOut(colors);
} }
else if (vkBlend2.ColorWriteMask == 0) else if (vkBlend2.ColorWriteMask == 0)
{ {
colors[j] = null; colors[j] = null;
MaskOut(); MaskOut(colors);
} }
} }
} }

View File

@ -1,6 +1,6 @@
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Silk.NET.Vulkan; using Silk.NET.Vulkan;
using System.Buffers; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
@ -193,7 +193,8 @@ namespace Ryujinx.Graphics.Vulkan
{ {
_firstHandle = first.ID + 1; _firstHandle = first.ID + 1;
_handles.RemoveAt(0); _handles.RemoveAt(0);
ArrayPool<FenceHolder>.Shared.Return(first.Waitable.Fences); Array.Clear(first.Waitable.Fences);
MultiFenceHolder.FencePool.Release(first.Waitable.Fences);
first.Waitable = null; first.Waitable = null;
} }
} }

View File

@ -1,43 +1,91 @@
using Gommon; using Gommon;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.HLE.HOS.Kernel.Memory;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
using System.Linq; using System.Linq;
using System.Text;
namespace Ryujinx.HLE.Debugger namespace Ryujinx.HLE.Debugger
{ {
public partial class 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() static Debugger()
{ {
_rcmdDelegates.Add(["help"], _rcmdDelegates.Add(new RcmdEntry(
_ => _rcmdDelegates.Keys ["help"],
.Where(x => !x[0].Equals("help")) (dbgr, _) => _rcmdDelegates
.Select(x => x.JoinToString('\n')) .Where(entry => entry.HelpLines.Length > 0)
.JoinToString('\n') + '\n' .SelectMany(entry => entry.HelpLines)
); .JoinToString('\n') + '\n',
_rcmdDelegates.Add(["get info"], dbgr => dbgr.GetProcessInfo()); Array.Empty<string>()));
_rcmdDelegates.Add(["backtrace", "bt"], dbgr => dbgr.GetStackTrace());
_rcmdDelegates.Add(["registers", "reg"], dbgr => dbgr.GetRegisters()); _rcmdDelegates.Add(new RcmdEntry(["get info"], (dbgr, _) => dbgr.GetProcessInfo(), ["get info"]));
_rcmdDelegates.Add(["minidump"], dbgr => dbgr.GetMinidump()); _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; if (trimmedCommand.Equals(name, StringComparison.OrdinalIgnoreCase))
break; {
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() public string GetStackTrace()
@ -86,5 +134,181 @@ namespace Ryujinx.HLE.Debugger
return $"Error getting process info: {e.Message}\n"; 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}";
}
} }
} }

View File

@ -404,9 +404,8 @@ namespace Ryujinx.HLE.Debugger.Gdb
string command = Helpers.FromHex(hexCommand); string command = Helpers.FromHex(hexCommand);
Logger.Debug?.Print(LogClass.GdbStub, $"Received Rcmd: {command}"); Logger.Debug?.Print(LogClass.GdbStub, $"Received Rcmd: {command}");
Func<Debugger, string> rcmd = Debugger.FindRcmdDelegate(command); string response = Debugger.CallRcmdDelegate(Debugger, command);
Processor.ReplyHex(response);
Processor.ReplyHex(rcmd(Debugger));
} }
catch (Exception e) catch (Exception e)
{ {

View File

@ -2,6 +2,7 @@ using Microsoft.IO;
using Ryujinx.Common; using Ryujinx.Common;
using Ryujinx.Common.Memory; using Ryujinx.Common.Memory;
using System; using System;
using System.Buffers;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
@ -37,7 +38,7 @@ namespace Ryujinx.HLE.HOS.Ipc
public IpcMessage(ReadOnlySpan<byte> data, long cmdPtr) public IpcMessage(ReadOnlySpan<byte> data, long cmdPtr)
{ {
using RecyclableMemoryStream ms = MemoryStreamManager.Shared.GetStream(data); RecyclableMemoryStream ms = MemoryStreamManager.Shared.GetStream(data);
BinaryReader reader = new(ms); BinaryReader reader = new(ms);
@ -123,6 +124,8 @@ namespace Ryujinx.HLE.HOS.Ipc
} }
ObjectIds = []; ObjectIds = [];
MemoryStreamManager.Shared.ReleaseStream(ms);
} }
public RecyclableMemoryStream GetStream(long cmdPtr, ulong recvListAddr) public RecyclableMemoryStream GetStream(long cmdPtr, ulong recvListAddr)

View File

@ -20,6 +20,15 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
_exchangeBufferDescriptors = new List<KBufferDescriptor>(MaxInternalBuffersCount); _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) public Result AddSendBuffer(ulong src, ulong dst, ulong size, MemoryState state)
{ {
return Add(_sendBufferDescriptors, src, dst, size, state); return Add(_sendBufferDescriptors, src, dst, size, state);

View File

@ -1,3 +1,4 @@
using Ryujinx.Common;
using Ryujinx.HLE.HOS.Kernel.Common; using Ryujinx.HLE.HOS.Kernel.Common;
using Ryujinx.HLE.HOS.Kernel.Process; using Ryujinx.HLE.HOS.Kernel.Process;
using Ryujinx.HLE.HOS.Kernel.Threading; using Ryujinx.HLE.HOS.Kernel.Threading;
@ -32,7 +33,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
{ {
KThread currentThread = KernelStatic.GetCurrentThread(); KThread currentThread = KernelStatic.GetCurrentThread();
KSessionRequest request = new(currentThread, customCmdBuffAddr, customCmdBuffSize); KSessionRequest request = _parent.ServerSession.RequestPool.Allocate().Set(currentThread, customCmdBuffAddr, customCmdBuffSize);
KernelContext.CriticalSection.Enter(); KernelContext.CriticalSection.Enter();
@ -55,7 +56,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
{ {
KThread currentThread = KernelStatic.GetCurrentThread(); 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(); KernelContext.CriticalSection.Enter();

View File

@ -10,6 +10,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
{ {
class KServerSession : KSynchronizationObject class KServerSession : KSynchronizationObject
{ {
public readonly ObjectPool<KSessionRequest> RequestPool = new(() => new KSessionRequest());
private static readonly MemoryState[] _ipcMemoryStates = private static readonly MemoryState[] _ipcMemoryStates =
[ [
MemoryState.IpcBuffer3, MemoryState.IpcBuffer3,
@ -274,6 +276,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
KernelContext.CriticalSection.Leave(); KernelContext.CriticalSection.Leave();
WakeClientThread(request, clientResult); WakeClientThread(request, clientResult);
RequestPool.Release(request);
} }
if (clientHeader.ReceiveListType < 2 && if (clientHeader.ReceiveListType < 2 &&
@ -627,6 +631,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
CloseAllHandles(clientMsg, serverHeader, clientProcess); CloseAllHandles(clientMsg, serverHeader, clientProcess);
FinishRequest(request, clientResult); FinishRequest(request, clientResult);
RequestPool.Release(request);
} }
if (clientHeader.ReceiveListType < 2 && if (clientHeader.ReceiveListType < 2 &&
@ -866,6 +872,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
// Unmap buffers from server. // Unmap buffers from server.
FinishRequest(request, clientResult); FinishRequest(request, clientResult);
RequestPool.Release(request);
return serverResult; return serverResult;
} }
@ -1098,6 +1106,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
foreach (KSessionRequest request in IterateWithRemovalOfAllRequests()) foreach (KSessionRequest request in IterateWithRemovalOfAllRequests())
{ {
FinishRequest(request, KernelResult.PortRemoteClosed); FinishRequest(request, KernelResult.PortRemoteClosed);
RequestPool.Release(request);
} }
} }
@ -1117,6 +1127,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
{ {
SendResultToAsyncRequestClient(request, KernelResult.PortRemoteClosed); SendResultToAsyncRequestClient(request, KernelResult.PortRemoteClosed);
} }
RequestPool.Release(request);
} }
WakeServerThreads(KernelResult.PortRemoteClosed); WakeServerThreads(KernelResult.PortRemoteClosed);

View File

@ -5,18 +5,18 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
{ {
class KSessionRequest 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 KProcess ServerProcess { get; set; }
public KWritableEvent AsyncEvent { get; } public KWritableEvent AsyncEvent { get; private set; }
public ulong CustomCmdBuffAddr { get; } public ulong CustomCmdBuffAddr { get; private set; }
public ulong CustomCmdBuffSize { get; } public ulong CustomCmdBuffSize { get; private set; }
public KSessionRequest( public KSessionRequest Set(
KThread clientThread, KThread clientThread,
ulong customCmdBuffAddr, ulong customCmdBuffAddr,
ulong customCmdBuffSize, ulong customCmdBuffSize,
@ -27,7 +27,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
CustomCmdBuffSize = customCmdBuffSize; CustomCmdBuffSize = customCmdBuffSize;
AsyncEvent = asyncEvent; AsyncEvent = asyncEvent;
BufferDescriptorTable = new KBufferDescriptorTable(); BufferDescriptorTable = BufferDescriptorTable?.Clear() ?? new KBufferDescriptorTable();
return this;
} }
} }
} }

View File

@ -1,10 +1,8 @@
using Ryujinx.Common;
using Ryujinx.HLE.HOS.Kernel.Common; using Ryujinx.HLE.HOS.Kernel.Common;
using Ryujinx.HLE.HOS.Kernel.Process; using Ryujinx.HLE.HOS.Kernel.Process;
using Ryujinx.Horizon.Common; using Ryujinx.Horizon.Common;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Threading; using System.Threading;
namespace Ryujinx.HLE.HOS.Kernel.Threading namespace Ryujinx.HLE.HOS.Kernel.Threading
@ -12,12 +10,12 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
class KAddressArbiter class KAddressArbiter
{ {
private const int HasListenersMask = 0x40000000; private const int HasListenersMask = 0x40000000;
private static readonly ObjectPool<KThread[]> _threadArrayPool = new(() => []);
private readonly KernelContext _context; private readonly KernelContext _context;
private readonly List<KThread> _condVarThreads; private readonly Dictionary<ulong, List<KThread>> _condVarThreads;
private readonly List<KThread> _arbiterThreads; private readonly Dictionary<ulong, List<KThread>> _arbiterThreads;
private readonly ByDynamicPriority _byDynamicPriority;
public KAddressArbiter(KernelContext context) public KAddressArbiter(KernelContext context)
{ {
@ -25,6 +23,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
_condVarThreads = []; _condVarThreads = [];
_arbiterThreads = []; _arbiterThreads = [];
_byDynamicPriority = new ByDynamicPriority();
} }
public Result ArbitrateLock(int ownerHandle, ulong mutexAddress, int requesterHandle) public Result ArbitrateLock(int ownerHandle, ulong mutexAddress, int requesterHandle)
@ -140,9 +139,23 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
currentThread.MutexAddress = mutexAddress; currentThread.MutexAddress = mutexAddress;
currentThread.ThreadHandleForUserMutex = threadHandle; 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) if (timeout != 0)
{ {
@ -165,7 +178,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
currentThread.MutexOwner?.RemoveMutexWaiter(currentThread); currentThread.MutexOwner?.RemoveMutexWaiter(currentThread);
_condVarThreads.Remove(currentThread); _condVarThreads[condVarAddress].Remove(currentThread);
_context.CriticalSection.Leave(); _context.CriticalSection.Leave();
@ -200,12 +213,13 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
{ {
_context.CriticalSection.Enter(); _context.CriticalSection.Enter();
static bool SignalProcessWideKeyPredicate(KThread thread, ulong address) int validThreads = 0;
{ _condVarThreads.TryGetValue(address, out List<KThread> threads);
return thread.CondVarAddress == address;
}
int validThreads = WakeThreads(_condVarThreads, count, TryAcquireMutex, SignalProcessWideKeyPredicate, address); if (threads is not null && threads.Count > 0)
{
validThreads = WakeThreads(threads, count, TryAcquireMutex);
}
if (validThreads == 0) if (validThreads == 0)
{ {
@ -316,7 +330,22 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
currentThread.MutexAddress = address; currentThread.MutexAddress = address;
currentThread.WaitingInArbitration = true; currentThread.WaitingInArbitration = true;
_arbiterThreads.Add(currentThread); if (_arbiterThreads.TryGetValue(address, 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
{
_arbiterThreads.Add(address, [currentThread]);
}
currentThread.Reschedule(ThreadSchedState.Paused); currentThread.Reschedule(ThreadSchedState.Paused);
@ -336,7 +365,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
if (currentThread.WaitingInArbitration) if (currentThread.WaitingInArbitration)
{ {
_arbiterThreads.Remove(currentThread); _arbiterThreads[address].Remove(currentThread);
currentThread.WaitingInArbitration = false; currentThread.WaitingInArbitration = false;
} }
@ -393,7 +422,22 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
currentThread.MutexAddress = address; currentThread.MutexAddress = address;
currentThread.WaitingInArbitration = true; currentThread.WaitingInArbitration = true;
_arbiterThreads.Add(currentThread); if (_arbiterThreads.TryGetValue(address, 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
{
_arbiterThreads.Add(address, [currentThread]);
}
currentThread.Reschedule(ThreadSchedState.Paused); currentThread.Reschedule(ThreadSchedState.Paused);
@ -413,7 +457,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
if (currentThread.WaitingInArbitration) if (currentThread.WaitingInArbitration)
{ {
_arbiterThreads.Remove(currentThread); _arbiterThreads[address].Remove(currentThread);
currentThread.WaitingInArbitration = false; currentThread.WaitingInArbitration = false;
} }
@ -487,15 +531,12 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
// or negative. It is incremented if there are no threads waiting. // or negative. It is incremented if there are no threads waiting.
int waitingCount = 0; int waitingCount = 0;
foreach (KThread thread in _arbiterThreads) if (_arbiterThreads.TryGetValue(address, out List<KThread> threads))
{ {
if (thread.MutexAddress == address && waitingCount = threads.Count;
++waitingCount >= count)
{
break;
}
} }
if (waitingCount > 0) if (waitingCount > 0)
{ {
if (count <= 0) if (count <= 0)
@ -561,55 +602,38 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
thread.WaitingInArbitration = false; thread.WaitingInArbitration = false;
} }
static bool ArbiterThreadPredecate(KThread thread, ulong address) _arbiterThreads.TryGetValue(address, out List<KThread> threads);
{
return thread.MutexAddress == address;
}
WakeThreads(_arbiterThreads, count, RemoveArbiterThread, ArbiterThreadPredecate, address); if (threads is not null && threads.Count > 0)
{
WakeThreads(threads, count, RemoveArbiterThread);
}
} }
private static int WakeThreads( private static int WakeThreads(
List<KThread> threads, List<KThread> threads,
int count, int count,
Action<KThread> removeCallback, Action<KThread> removeCallback)
Func<KThread, ulong, bool> predicate,
ulong address = 0)
{ {
KThread[] candidates = _threadArrayPool.Allocate(); int validCount = count > 0 ? Math.Min(count, threads.Count) : threads.Count;
if (candidates.Length < threads.Count)
{ for (int i = 0; i < validCount; i++)
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)));
if (count > 0)
{
candidatesSpan = candidatesSpan[..Math.Min(count, candidatesSpan.Length)];
}
foreach (KThread thread in candidatesSpan)
{ {
KThread thread = threads[i];
removeCallback(thread); removeCallback(thread);
threads.Remove(thread);
} }
_threadArrayPool.Release(candidates); threads.RemoveRange(0, validCount);
return validCount; return validCount;
} }
private class ByDynamicPriority : IComparer<KThread>
{
public int Compare(KThread x, KThread y)
{
return x!.DynamicPriority.CompareTo(y!.DynamicPriority);
}
}
} }
} }

View File

@ -61,8 +61,6 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
public KSynchronizationObject SignaledObj { get; set; } public KSynchronizationObject SignaledObj { get; set; }
public ulong CondVarAddress { get; set; }
private ulong _entrypoint; private ulong _entrypoint;
private ThreadStart _customThreadStart; private ThreadStart _customThreadStart;
private bool _forcedUnschedulable; private bool _forcedUnschedulable;

View File

@ -23,6 +23,9 @@ namespace Ryujinx.HLE.HOS.Services
private int _selfId; private int _selfId;
private bool _isDomain; 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) public IpcService(ServerBase server = null, bool registerTipc = false)
{ {
Stopwatch sw = Stopwatch.StartNew(); Stopwatch sw = Stopwatch.StartNew();
@ -146,7 +149,9 @@ namespace Ryujinx.HLE.HOS.Services
{ {
Logger.Trace?.Print(LogClass.KernelIpc, $"{service.GetType().Name}: {processRequest.Name}"); 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 else
{ {
@ -196,7 +201,9 @@ namespace Ryujinx.HLE.HOS.Services
{ {
Logger.Debug?.Print(LogClass.KernelIpc, $"{GetType().Name}: {processRequest.Name}"); 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 else
{ {

View File

@ -60,6 +60,10 @@ namespace Ryujinx.HLE.HOS.Services.Nv
// TODO: This should call set:sys::GetDebugModeFlag // TODO: This should call set:sys::GetDebugModeFlag
private readonly bool _debugModeEnabled = false; private readonly bool _debugModeEnabled = false;
private byte[] _ioctl2Buffer = [];
private byte[] _ioctlArgumentBuffer = [];
private byte[] _ioctl3Buffer = [];
public INvDrvServices(ServiceCtx context) : base(context.Device.System.NvDrvServer) public INvDrvServices(ServiceCtx context) : base(context.Device.System.NvDrvServer)
{ {
_owner = 0; _owner = 0;
@ -128,27 +132,38 @@ namespace Ryujinx.HLE.HOS.Services.Nv
if (!context.Memory.TryReadUnsafe(inputDataPosition, (int)inputDataSize, out arguments)) 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); context.Memory.Read(inputDataPosition, arguments);
} }
else
{
arguments = arguments.ToArray();
}
} }
else if (isWrite) else if (isWrite)
{ {
byte[] outputData = new byte[outputDataSize]; if (_ioctlArgumentBuffer.Length < (int)outputDataSize)
{
Array.Resize(ref _ioctlArgumentBuffer, (int)outputDataSize);
}
arguments = new Span<byte>(outputData); arguments = _ioctlArgumentBuffer.AsSpan(0, (int)outputDataSize);
} }
else 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);
}
context.Memory.Read(inputDataPosition, temp); arguments = _ioctlArgumentBuffer.AsSpan(0, (int)inputDataSize);
arguments = new Span<byte>(temp); context.Memory.Read(inputDataPosition, arguments);
}
} }
return NvResult.Success; return NvResult.Success;
@ -270,7 +285,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv
if ((ioctlCommand.DirectionValue & NvIoctl.Direction.Write) != 0) 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); errorCode = GetIoctlArgument(context, ioctlCommand, out Span<byte> arguments);
byte[] inlineInBuffer = null;
if (!context.Memory.TryReadUnsafe(inlineInBufferPosition, (int)inlineInBufferSize, out Span<byte> inlineInBufferSpan)) if (!context.Memory.TryReadUnsafe(inlineInBufferPosition, (int)inlineInBufferSize, out Span<byte> inlineInBufferSpan))
{ {
inlineInBuffer = _byteArrayPool.Rent((int)inlineInBufferSize); if (_ioctl2Buffer.Length < (int)inlineInBufferSize)
inlineInBufferSpan = inlineInBuffer; {
context.Memory.Read(inlineInBufferPosition, inlineInBufferSpan[..(int)inlineInBufferSize]); Array.Resize(ref _ioctl2Buffer, (int)inlineInBufferSize);
}
inlineInBufferSpan = _ioctl2Buffer.AsSpan(0, (int)inlineInBufferSize);
context.Memory.Read(inlineInBufferPosition, inlineInBufferSpan);
} }
if (errorCode == NvResult.Success) if (errorCode == NvResult.Success)
@ -489,7 +506,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv
if (errorCode == NvResult.Success) if (errorCode == NvResult.Success)
{ {
NvInternalResult internalResult = deviceFile.Ioctl2(ioctlCommand, arguments, inlineInBufferSpan[..(int)inlineInBufferSize]); NvInternalResult internalResult = deviceFile.Ioctl2(ioctlCommand, arguments, inlineInBufferSpan);
if (internalResult == NvInternalResult.NotImplemented) if (internalResult == NvInternalResult.NotImplemented)
{ {
@ -500,15 +517,10 @@ namespace Ryujinx.HLE.HOS.Services.Nv
if ((ioctlCommand.DirectionValue & NvIoctl.Direction.Write) != 0) 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); context.ResponseData.Write((uint)errorCode);
@ -531,13 +543,15 @@ namespace Ryujinx.HLE.HOS.Services.Nv
errorCode = GetIoctlArgument(context, ioctlCommand, out Span<byte> arguments); errorCode = GetIoctlArgument(context, ioctlCommand, out Span<byte> arguments);
byte[] inlineOutBuffer = null;
if (!context.Memory.TryReadUnsafe(inlineOutBufferPosition, (int)inlineOutBufferSize, out Span<byte> inlineOutBufferSpan)) if (!context.Memory.TryReadUnsafe(inlineOutBufferPosition, (int)inlineOutBufferSize, out Span<byte> inlineOutBufferSpan))
{ {
inlineOutBuffer = _byteArrayPool.Rent((int)inlineOutBufferSize); if (_ioctl3Buffer.Length < (int)inlineOutBufferSize)
inlineOutBufferSpan = inlineOutBuffer; {
context.Memory.Read(inlineOutBufferPosition, inlineOutBufferSpan[..(int)inlineOutBufferSize]); Array.Resize(ref _ioctl3Buffer, (int)inlineOutBufferSize);
}
inlineOutBufferSpan = _ioctl3Buffer.AsSpan(0, (int)inlineOutBufferSize);
context.Memory.Read(inlineOutBufferPosition, inlineOutBufferSpan);
} }
if (errorCode == NvResult.Success) if (errorCode == NvResult.Success)
@ -546,7 +560,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv
if (errorCode == NvResult.Success) if (errorCode == NvResult.Success)
{ {
NvInternalResult internalResult = deviceFile.Ioctl3(ioctlCommand, arguments, inlineOutBufferSpan[..(int)inlineOutBufferSize]); NvInternalResult internalResult = deviceFile.Ioctl3(ioctlCommand, arguments, inlineOutBufferSpan);
if (internalResult == NvInternalResult.NotImplemented) if (internalResult == NvInternalResult.NotImplemented)
{ {
@ -557,16 +571,11 @@ namespace Ryujinx.HLE.HOS.Services.Nv
if ((ioctlCommand.DirectionValue & NvIoctl.Direction.Write) != 0) 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);
context.Memory.Write(inlineOutBufferPosition, inlineOutBufferSpan[..(int)inlineOutBufferSize].ToArray()); context.Memory.Write(inlineOutBufferPosition, inlineOutBufferSpan);
} }
} }
} }
if (inlineOutBuffer is not null)
{
_byteArrayPool.Return(inlineOutBuffer);
}
} }
context.ResponseData.Write((uint)errorCode); context.ResponseData.Write((uint)errorCode);

View File

@ -454,8 +454,9 @@ namespace Ryujinx.HLE.HOS.Services
response.RawData = _responseDataStream.ToArray(); response.RawData = _responseDataStream.ToArray();
using RecyclableMemoryStream responseStream = response.GetStreamTipc(); RecyclableMemoryStream responseStream = response.GetStreamTipc();
_selfProcess.CpuMemory.Write(_selfThread.TlsAddress, responseStream.GetReadOnlySequence()); _selfProcess.CpuMemory.Write(_selfThread.TlsAddress, responseStream.GetReadOnlySequence());
MemoryStreamManager.Shared.ReleaseStream(responseStream);
} }
else else
{ {
@ -464,8 +465,9 @@ namespace Ryujinx.HLE.HOS.Services
if (!isTipcCommunication) 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()); _selfProcess.CpuMemory.Write(_selfThread.TlsAddress, responseStream.GetReadOnlySequence());
MemoryStreamManager.Shared.ReleaseStream(responseStream);
} }
return shouldReply; return shouldReply;

View File

@ -20,6 +20,9 @@ namespace Ryujinx.Horizon.Sdk.OsTypes.Impl
private MultiWaitHolderBase _signaledHolder; private MultiWaitHolderBase _signaledHolder;
ObjectPool<int[]> _objectHandlePool = new(() => new int[64]);
ObjectPool<MultiWaitHolderBase[]> _objectPool = new(() => new MultiWaitHolderBase[64]);
public long CurrentTime { get; private set; } public long CurrentTime { get; private set; }
public IEnumerable<MultiWaitHolderBase> MultiWaits => _multiWaits; public IEnumerable<MultiWaitHolderBase> MultiWaits => _multiWaits;
@ -76,11 +79,15 @@ namespace Ryujinx.Horizon.Sdk.OsTypes.Impl
private MultiWaitHolderBase WaitAnyHandleImpl(bool infinite, long timeout) 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; long endTime = infinite ? long.MaxValue : PerformanceCounter.ElapsedMilliseconds * 1000000;
@ -98,7 +105,7 @@ namespace Ryujinx.Horizon.Sdk.OsTypes.Impl
} }
else else
{ {
index = WaitSynchronization(objectHandles[..count], minTimeout); index = WaitSynchronization(objectHandlesSpan[..count], minTimeout);
DebugUtil.Assert(index != WaitInvalid); DebugUtil.Assert(index != WaitInvalid);
} }
@ -116,12 +123,18 @@ namespace Ryujinx.Horizon.Sdk.OsTypes.Impl
{ {
_signaledHolder = minTimeoutObject; _signaledHolder = minTimeoutObject;
_objectHandlePool.Release(objectHandles);
_objectPool.Release(objects);
return _signaledHolder; return _signaledHolder;
} }
} }
} }
else else
{ {
_objectHandlePool.Release(objectHandles);
_objectPool.Release(objects);
return null; return null;
} }
@ -131,6 +144,9 @@ namespace Ryujinx.Horizon.Sdk.OsTypes.Impl
{ {
if (_signaledHolder != null) if (_signaledHolder != null)
{ {
_objectHandlePool.Release(objectHandles);
_objectPool.Release(objects);
return _signaledHolder; return _signaledHolder;
} }
} }
@ -139,7 +155,10 @@ namespace Ryujinx.Horizon.Sdk.OsTypes.Impl
default: default:
lock (_lock) lock (_lock)
{ {
_signaledHolder = objects[index]; _signaledHolder = objectsSpan[index];
_objectHandlePool.Release(objectHandles);
_objectPool.Release(objects);
return _signaledHolder; return _signaledHolder;
} }

View File

@ -533,8 +533,6 @@ namespace Ryujinx.Input.HLE
hidKeyboard.Modifier |= value << entry.Target; hidKeyboard.Modifier |= value << entry.Target;
} }
ArrayPool<bool>.Shared.Return(keyboardState.KeysState);
return hidKeyboard; return hidKeyboard;
} }

View File

@ -20,7 +20,6 @@ namespace Ryujinx.Input.HLE
{ {
public class NpadManager : IDisposable public class NpadManager : IDisposable
{ {
private static readonly ObjectPool<List<SixAxisInput>> _hleMotionStatesPool = new (() => new List<SixAxisInput>(NpadDevices.MaxControllers));
private readonly CemuHookClient _cemuHookClient; private readonly CemuHookClient _cemuHookClient;
private readonly Lock _lock = new(); private readonly Lock _lock = new();
@ -41,6 +40,9 @@ namespace Ryujinx.Input.HLE
private bool _enableMouse; private bool _enableMouse;
private Switch _device; private Switch _device;
private readonly List<GamepadInput> _hleInputStates = [];
private readonly List<SixAxisInput> _hleMotionStates = new(NpadDevices.MaxControllers);
public NpadManager(IGamepadDriver keyboardDriver, IGamepadDriver gamepadDriver, IGamepadDriver mouseDriver) public NpadManager(IGamepadDriver keyboardDriver, IGamepadDriver gamepadDriver, IGamepadDriver mouseDriver)
{ {
_controllers = new NpadController[MaxControllers]; _controllers = new NpadController[MaxControllers];
@ -217,8 +219,8 @@ namespace Ryujinx.Input.HLE
{ {
lock (_lock) lock (_lock)
{ {
List<GamepadInput> hleInputStates = []; _hleInputStates.Clear();
List<SixAxisInput> hleMotionStates = _hleMotionStatesPool.Allocate(); _hleMotionStates.Clear();
KeyboardInput? hleKeyboardInput = null; KeyboardInput? hleKeyboardInput = null;
@ -260,14 +262,14 @@ namespace Ryujinx.Input.HLE
inputState.PlayerId = playerIndex; inputState.PlayerId = playerIndex;
motionState.Item1.PlayerId = playerIndex; motionState.Item1.PlayerId = playerIndex;
hleInputStates.Add(inputState); _hleInputStates.Add(inputState);
hleMotionStates.Add(motionState.Item1); _hleMotionStates.Add(motionState.Item1);
if (isJoyconPair && !motionState.Item2.Equals(default)) if (isJoyconPair && !motionState.Item2.Equals(default))
{ {
motionState.Item2.PlayerId = playerIndex; motionState.Item2.PlayerId = playerIndex;
hleMotionStates.Add(motionState.Item2); _hleMotionStates.Add(motionState.Item2);
} }
} }
@ -276,8 +278,8 @@ namespace Ryujinx.Input.HLE
hleKeyboardInput = NpadController.GetHLEKeyboardInput(_keyboardDriver); hleKeyboardInput = NpadController.GetHLEKeyboardInput(_keyboardDriver);
} }
_device.Hid.Npads.Update(hleInputStates); _device.Hid.Npads.Update(_hleInputStates);
_device.Hid.Npads.UpdateSixAxis(hleMotionStates); _device.Hid.Npads.UpdateSixAxis(_hleMotionStates);
if (hleKeyboardInput.HasValue) if (hleKeyboardInput.HasValue)
{ {
@ -328,10 +330,7 @@ namespace Ryujinx.Input.HLE
_device.Hid.Mouse.Update(0, 0); _device.Hid.Mouse.Update(0, 0);
} }
_device.TamperMachine.UpdateInput(hleInputStates); _device.TamperMachine.UpdateInput(_hleInputStates);
hleMotionStates.Clear();
_hleMotionStatesPool.Release(hleMotionStates);
} }
} }

View File

@ -8,6 +8,8 @@ namespace Ryujinx.Input
/// </summary> /// </summary>
public interface IKeyboard : IGamepad public interface IKeyboard : IGamepad
{ {
private static bool[] _keyState;
/// <summary> /// <summary>
/// Check if a given key is pressed on the keyboard. /// Check if a given key is pressed on the keyboard.
/// </summary> /// </summary>
@ -29,15 +31,17 @@ namespace Ryujinx.Input
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
static KeyboardStateSnapshot GetStateSnapshot(IKeyboard keyboard) static KeyboardStateSnapshot GetStateSnapshot(IKeyboard keyboard)
{ {
if (_keyState is null)
bool[] keysState = ArrayPool<bool>.Shared.Rent((int)Key.Count); {
_keyState = new bool[(int)Key.Count];
}
for (Key key = 0; key < Key.Count; key++) 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);
} }
} }
} }

View File

@ -3,7 +3,7 @@ namespace Ryujinx.Memory.Range
/// <summary> /// <summary>
/// Range of memory that can be split in two. /// Range of memory that can be split in two.
/// </summary> /// </summary>
public interface INonOverlappingRange : IRange public interface INonOverlappingRange<T> : IRangeListRange<T> where T : class, IRangeListRange<T>
{ {
/// <summary> /// <summary>
/// Split this region into two, around the specified address. /// Split this region into two, around the specified address.
@ -11,6 +11,6 @@ namespace Ryujinx.Memory.Range
/// </summary> /// </summary>
/// <param name="splitAddress">Address to split the region around</param> /// <param name="splitAddress">Address to split the region around</param>
/// <returns>The second part of the split region, with start address at the given split.</returns> /// <returns>The second part of the split region, with start address at the given split.</returns>
public INonOverlappingRange Split(ulong splitAddress); public INonOverlappingRange<T> Split(ulong splitAddress);
} }
} }

View File

@ -24,8 +24,8 @@ namespace Ryujinx.Memory.Range
/// Check if this range overlaps with another. /// Check if this range overlaps with another.
/// </summary> /// </summary>
/// <param name="address">Base address</param> /// <param name="address">Base address</param>
/// <param name="size">Size of the range</param> /// <param name="endAddress">EndAddress of the range</param>
/// <returns>True if overlapping, false otherwise</returns> /// <returns>True if overlapping, false otherwise</returns>
bool OverlapsWith(ulong address, ulong size); bool OverlapsWith(ulong address, ulong endAddress);
} }
} }

View File

@ -11,7 +11,7 @@ namespace Ryujinx.Memory.Range
/// A range list that assumes ranges are non-overlapping, with list items that can be split in two to avoid overlaps. /// A range list that assumes ranges are non-overlapping, with list items that can be split in two to avoid overlaps.
/// </summary> /// </summary>
/// <typeparam name="T">Type of the range.</typeparam> /// <typeparam name="T">Type of the range.</typeparam>
public unsafe class NonOverlappingRangeList<T> : RangeListBase<T> where T : class, INonOverlappingRange public class NonOverlappingRangeList<T> : RangeListBase<T> where T : class, INonOverlappingRange<T>
{ {
public readonly ReaderWriterLockSlim Lock = new(); public readonly ReaderWriterLockSlim Lock = new();
@ -32,6 +32,8 @@ namespace Ryujinx.Memory.Range
/// <param name="item">The item to be added</param> /// <param name="item">The item to be added</param>
public override void Add(T item) public override void Add(T item)
{ {
Debug.Assert(item.Address != item.EndAddress);
int index = BinarySearch(item.Address); int index = BinarySearch(item.Address);
if (index < 0) if (index < 0)
@ -39,76 +41,9 @@ namespace Ryujinx.Memory.Range
index = ~index; index = ~index;
} }
RangeItem<T> rangeItem = _rangeItemPool.Allocate().Set(item);
Insert(index, rangeItem);
}
/// <summary>
/// Updates an item's end address on the list. Address must be the same.
/// </summary>
/// <param name="item">The item to be updated</param>
/// <returns>True if the item was located and updated, false otherwise</returns>
protected override bool Update(T item)
{
int index = BinarySearch(item.Address);
if (index >= 0 && Items[index].Value.Equals(item))
{
RangeItem<T> rangeItem = new(item) { Previous = Items[index].Previous, Next = Items[index].Next };
if (index > 0)
{
Items[index - 1].Next = rangeItem;
}
if (index < Count - 1)
{
Items[index + 1].Previous = rangeItem;
}
Items[index] = rangeItem;
return true;
}
return false;
}
/// <summary>
/// Updates an item's end address on the list. Address must be the same.
/// </summary>
/// <param name="item">The RangeItem to be updated</param>
/// <returns>True if the item was located and updated, false otherwise</returns>
protected override bool Update(RangeItem<T> item)
{
int index = BinarySearch(item.Address);
RangeItem<T> rangeItem = new(item.Value) { Previous = item.Previous, Next = item.Next };
if (index > 0)
{
Items[index - 1].Next = rangeItem;
}
if (index < Count - 1)
{
Items[index + 1].Previous = rangeItem;
}
Items[index] = rangeItem;
return true;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void Insert(int index, RangeItem<T> item)
{
Debug.Assert(item.Address != item.EndAddress);
if (Count + 1 > Items.Length) if (Count + 1 > Items.Length)
{ {
Array.Resize(ref Items, Items.Length + BackingGrowthSize); Array.Resize(ref Items, (int)(Items.Length * 1.5));
} }
if (index >= Count) if (index >= Count)
@ -145,8 +80,6 @@ namespace Ryujinx.Memory.Range
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private void RemoveAt(int index) private void RemoveAt(int index)
{ {
_rangeItemPool.Release(Items[index]);
if (index < Count - 1) if (index < Count - 1)
{ {
Items[index + 1].Previous = index > 0 ? Items[index - 1] : null; Items[index + 1].Previous = index > 0 ? Items[index - 1] : null;
@ -173,7 +106,7 @@ namespace Ryujinx.Memory.Range
{ {
int index = BinarySearch(item.Address); int index = BinarySearch(item.Address);
if (index >= 0 && Items[index].Value.Equals(item)) if (index >= 0 && Items[index] == item)
{ {
RemoveAt(index); RemoveAt(index);
@ -188,7 +121,7 @@ namespace Ryujinx.Memory.Range
/// </summary> /// </summary>
/// <param name="startItem">The first item in the range of items to be removed</param> /// <param name="startItem">The first item in the range of items to be removed</param>
/// <param name="endItem">The last item in the range of items to be removed</param> /// <param name="endItem">The last item in the range of items to be removed</param>
public override void RemoveRange(RangeItem<T> startItem, RangeItem<T> endItem) public override void RemoveRange(T startItem, T endItem)
{ {
if (startItem is null) if (startItem is null)
{ {
@ -197,7 +130,7 @@ namespace Ryujinx.Memory.Range
if (startItem == endItem) if (startItem == endItem)
{ {
Remove(startItem.Value); Remove(startItem);
return; return;
} }
@ -229,42 +162,45 @@ namespace Ryujinx.Memory.Range
/// <param name="size">Size of the range</param> /// <param name="size">Size of the range</param>
public void RemoveRange(ulong address, ulong size) public void RemoveRange(ulong address, ulong size)
{ {
int startIndex = BinarySearchLeftEdge(address, address + size); (int startIndex, int endIndex) = BinarySearchEdges(address, address + size);
if (startIndex < 0) if (startIndex < 0)
{ {
return; return;
} }
int endIndex = startIndex; if (startIndex == endIndex - 1)
while (Items[endIndex] is not null && Items[endIndex].Address < address + size)
{ {
if (endIndex == Count - 1) RemoveAt(startIndex);
{ return;
break;
}
endIndex++;
} }
if (endIndex < Count - 1) RemoveRangeInternal(startIndex, endIndex);
}
/// <summary>
/// Removes a range of items from the item list
/// </summary>
/// <param name="index">Start index of the range</param>
/// <param name="endIndex">End index of the range (exclusive)</param>
private void RemoveRangeInternal(int index, int endIndex)
{
if (endIndex < Count)
{ {
Items[endIndex + 1].Previous = startIndex > 0 ? Items[startIndex - 1] : null; Items[endIndex].Previous = index > 0 ? Items[index - 1] : null;
} }
if (startIndex > 0) if (index > 0)
{ {
Items[startIndex - 1].Next = endIndex < Count - 1 ? Items[endIndex + 1] : null; Items[index - 1].Next = endIndex < Count ? Items[endIndex] : null;
} }
if (endIndex < Count)
if (endIndex < Count - 1)
{ {
Array.Copy(Items, endIndex + 1, Items, startIndex, Count - endIndex - 1); Array.Copy(Items, endIndex, Items, index, Count - endIndex);
} }
Count -= endIndex - startIndex + 1; Count -= endIndex - index;
} }
/// <summary> /// <summary>
@ -296,8 +232,8 @@ namespace Ryujinx.Memory.Range
// So we need to return both the split 0-1 and 1-2 ranges. // So we need to return both the split 0-1 and 1-2 ranges.
Lock.EnterWriteLock(); Lock.EnterWriteLock();
(RangeItem<T> first, RangeItem<T> last) = FindOverlapsAsNodes(address, size); (T first, T last) = FindOverlapsAsNodes(address, size);
list = new List<T>(); list = [];
if (first is null) if (first is null)
{ {
@ -311,42 +247,41 @@ namespace Ryujinx.Memory.Range
ulong lastAddress = address; ulong lastAddress = address;
ulong endAddress = address + size; ulong endAddress = address + size;
RangeItem<T> current = first; T current = first;
while (last is not null && current is not null && current.Address < endAddress) while (last is not null && current is not null && current.Address < endAddress)
{ {
T region = current.Value; if (first == last && current.Address == address && current.Size == size)
if (first == last && region.Address == address && region.Size == size)
{ {
// Exact match, no splitting required. // Exact match, no splitting required.
list.Add(region); list.Add(current);
Lock.ExitWriteLock(); Lock.ExitWriteLock();
return; return;
} }
if (lastAddress < region.Address) if (lastAddress < current.Address)
{ {
// There is a gap between this region and the last. We need to fill it. // There is a gap between this region and the last. We need to fill it.
T fillRegion = factory(lastAddress, region.Address - lastAddress); T fillRegion = factory(lastAddress, current.Address - lastAddress);
list.Add(fillRegion); list.Add(fillRegion);
Add(fillRegion); Add(fillRegion);
} }
if (region.Address < address) if (current.Address < address)
{ {
// Split the region around our base address and take the high half. // Split the region around our base address and take the high half.
region = Split(region, address); current = Split(current, address);
} }
if (region.EndAddress > address + size) if (current.EndAddress > address + size)
{ {
// Split the region around our end address and take the low half. // Split the region around our end address and take the low half.
Split(region, address + size); Split(current, address + size);
} }
list.Add(region); list.Add(current);
lastAddress = region.EndAddress; lastAddress = current.EndAddress;
current = current.Next; current = current.Next;
} }
@ -374,7 +309,6 @@ namespace Ryujinx.Memory.Range
private T Split(T region, ulong splitAddress) private T Split(T region, ulong splitAddress)
{ {
T newRegion = (T)region.Split(splitAddress); T newRegion = (T)region.Split(splitAddress);
Update(region);
Add(newRegion); Add(newRegion);
return newRegion; return newRegion;
} }
@ -386,16 +320,11 @@ namespace Ryujinx.Memory.Range
/// <param name="size">Size in bytes of the range</param> /// <param name="size">Size in bytes of the range</param>
/// <returns>The leftmost overlapping item, or null if none is found</returns> /// <returns>The leftmost overlapping item, or null if none is found</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public override RangeItem<T> FindOverlap(ulong address, ulong size) public override T FindOverlap(ulong address, ulong size)
{ {
int index = BinarySearchLeftEdge(address, address + size); int index = BinarySearchLeftEdge(address, address + size);
if (index < 0) return index < 0 ? null : Items[index];
{
return null;
}
return Items[index];
} }
/// <summary> /// <summary>
@ -405,16 +334,11 @@ namespace Ryujinx.Memory.Range
/// <param name="size">Size in bytes of the range</param> /// <param name="size">Size in bytes of the range</param>
/// <returns>The overlapping item, or null if none is found</returns> /// <returns>The overlapping item, or null if none is found</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public override RangeItem<T> FindOverlapFast(ulong address, ulong size) public override T FindOverlapFast(ulong address, ulong size)
{ {
int index = BinarySearch(address, address + size); int index = BinarySearch(address, address + size);
if (index < 0) return index < 0 ? null : Items[index];
{
return null;
}
return Items[index];
} }
/// <summary> /// <summary>
@ -424,23 +348,18 @@ namespace Ryujinx.Memory.Range
/// <param name="size">Size in bytes of the range</param> /// <param name="size">Size in bytes of the range</param>
/// <returns>The first and last overlapping items, or null if none are found</returns> /// <returns>The first and last overlapping items, or null if none are found</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public (RangeItem<T>, RangeItem<T>) FindOverlapsAsNodes(ulong address, ulong size) public (T, T) FindOverlapsAsNodes(ulong address, ulong size)
{ {
(int index, int endIndex) = BinarySearchEdges(address, address + size); (int index, int endIndex) = BinarySearchEdges(address, address + size);
if (index < 0) return index < 0 ? (null, null) : (Items[index], Items[endIndex - 1]);
{
return (null, null);
}
return (Items[index], Items[endIndex - 1]);
} }
public RangeItem<T>[] FindOverlapsAsArray(ulong address, ulong size, out int length) public T[] FindOverlapsAsArray(ulong address, ulong size, out int length)
{ {
(int index, int endIndex) = BinarySearchEdges(address, address + size); (int index, int endIndex) = BinarySearchEdges(address, address + size);
RangeItem<T>[] result; T[] result;
if (index < 0) if (index < 0)
{ {
@ -449,29 +368,20 @@ namespace Ryujinx.Memory.Range
} }
else else
{ {
result = ArrayPool<RangeItem<T>>.Shared.Rent(endIndex - index); result = ArrayPool<T>.Shared.Rent(endIndex - index);
length = endIndex - index; length = endIndex - index;
Array.Copy(Items, index, result, 0, endIndex - index); Items.AsSpan(index, endIndex - index).CopyTo(result);
} }
return result; return result;
} }
public Span<RangeItem<T>> FindOverlapsAsSpan(ulong address, ulong size) public ReadOnlySpan<T> FindOverlapsAsSpan(ulong address, ulong size)
{ {
(int index, int endIndex) = BinarySearchEdges(address, address + size); (int index, int endIndex) = BinarySearchEdges(address, address + size);
Span<RangeItem<T>> result; ReadOnlySpan<T> result = index < 0 ? [] : Items.AsSpan(index, endIndex - index);
if (index < 0)
{
result = [];
}
else
{
result = Items.AsSpan().Slice(index, endIndex - index);
}
return result; return result;
} }
@ -480,7 +390,7 @@ namespace Ryujinx.Memory.Range
{ {
for (int i = 0; i < Count; i++) for (int i = 0; i < Count; i++)
{ {
yield return Items[i].Value; yield return Items[i];
} }
} }
} }

View File

@ -14,14 +14,14 @@ namespace Ryujinx.Memory.Range
/// startIndex is inclusive. /// startIndex is inclusive.
/// endIndex is exclusive. /// endIndex is exclusive.
/// </remarks> /// </remarks>
public readonly struct OverlapResult<T> where T : IRange public readonly struct OverlapResult<T> where T : class, IRangeListRange<T>
{ {
public readonly int StartIndex = -1; public readonly int StartIndex = -1;
public readonly int EndIndex = -1; public readonly int EndIndex = -1;
public readonly RangeItem<T> QuickResult; public readonly T QuickResult;
public int Count => EndIndex - StartIndex; public int Count => EndIndex - StartIndex;
public OverlapResult(int startIndex, int endIndex, RangeItem<T> quickResult = null) public OverlapResult(int startIndex, int endIndex, T quickResult = null)
{ {
this.StartIndex = startIndex; this.StartIndex = startIndex;
this.EndIndex = endIndex; this.EndIndex = endIndex;
@ -33,7 +33,7 @@ namespace Ryujinx.Memory.Range
/// Sorted list of ranges that supports binary search. /// Sorted list of ranges that supports binary search.
/// </summary> /// </summary>
/// <typeparam name="T">Type of the range.</typeparam> /// <typeparam name="T">Type of the range.</typeparam>
public class RangeList<T> : RangeListBase<T> where T : IRange public class RangeList<T> : RangeListBase<T> where T : class, IRangeListRange<T>
{ {
public readonly ReaderWriterLockSlim Lock = new(); public readonly ReaderWriterLockSlim Lock = new();
@ -61,104 +61,6 @@ namespace Ryujinx.Memory.Range
index = ~index; index = ~index;
} }
Insert(index, new RangeItem<T>(item));
}
/// <summary>
/// Updates an item's end address on the list. Address must be the same.
/// </summary>
/// <param name="item">The item to be updated</param>
/// <returns>True if the item was located and updated, false otherwise</returns>
protected override bool Update(T item)
{
int index = BinarySearch(item.Address);
if (index >= 0)
{
while (index < Count)
{
if (Items[index].Value.Equals(item))
{
RangeItem<T> rangeItem = new(item) { Previous = Items[index].Previous, Next = Items[index].Next };
if (index > 0)
{
Items[index - 1].Next = rangeItem;
}
if (index < Count - 1)
{
Items[index + 1].Previous = rangeItem;
}
Items[index] = rangeItem;
return true;
}
if (Items[index].Address > item.Address)
{
break;
}
index++;
}
}
return false;
}
/// <summary>
/// Updates an item's end address on the list. Address must be the same.
/// </summary>
/// <param name="item">The RangeItem to be updated</param>
/// <returns>True if the item was located and updated, false otherwise</returns>
protected override bool Update(RangeItem<T> item)
{
int index = BinarySearch(item.Address);
if (index >= 0)
{
while (index < Count)
{
if (Items[index].Equals(item))
{
RangeItem<T> rangeItem = new(item.Value) { Previous = item.Previous, Next = item.Next };
if (index > 0)
{
Items[index - 1].Next = rangeItem;
}
if (index < Count - 1)
{
Items[index + 1].Previous = rangeItem;
}
Items[index] = rangeItem;
return true;
}
if (Items[index].Address > item.Address)
{
break;
}
index++;
}
}
return false;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void Insert(int index, RangeItem<T> item)
{
Debug.Assert(item.Address != item.EndAddress);
Debug.Assert(item.Address % 32 == 0);
if (Count + 1 > Items.Length) if (Count + 1 > Items.Length)
{ {
Array.Resize(ref Items, Items.Length + BackingGrowthSize); Array.Resize(ref Items, Items.Length + BackingGrowthSize);
@ -220,7 +122,7 @@ namespace Ryujinx.Memory.Range
/// <param name="startItem">The first item in the range of items to be removed</param> /// <param name="startItem">The first item in the range of items to be removed</param>
/// <param name="endItem">The last item in the range of items to be removed</param> /// <param name="endItem">The last item in the range of items to be removed</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public override void RemoveRange(RangeItem<T> startItem, RangeItem<T> endItem) public override void RemoveRange(T startItem, T endItem)
{ {
if (startItem is null) if (startItem is null)
{ {
@ -229,30 +131,29 @@ namespace Ryujinx.Memory.Range
if (startItem == endItem) if (startItem == endItem)
{ {
Remove(startItem.Value); Remove(startItem);
return; return;
} }
int startIndex = BinarySearch(startItem.Address); (int index, int endIndex) = BinarySearchEdges(startItem.Address, endItem.EndAddress);
int endIndex = BinarySearch(endItem.Address);
if (endIndex < Count - 1) if (endIndex < Count)
{ {
Items[endIndex + 1].Previous = startIndex > 0 ? Items[startIndex - 1] : null; Items[endIndex].Previous = index > 0 ? Items[index - 1] : null;
} }
if (startIndex > 0) if (index > 0)
{ {
Items[startIndex - 1].Next = endIndex < Count - 1 ? Items[endIndex + 1] : null; Items[index - 1].Next = endIndex < Count ? Items[endIndex] : null;
} }
if (endIndex < Count - 1) if (endIndex < Count)
{ {
Array.Copy(Items, endIndex + 1, Items, startIndex, Count - endIndex - 1); Array.Copy(Items, endIndex, Items, index, Count - endIndex);
} }
Count -= endIndex - startIndex + 1; Count -= endIndex - index;
} }
/// <summary> /// <summary>
@ -268,7 +169,7 @@ namespace Ryujinx.Memory.Range
{ {
while (index < Count) while (index < Count)
{ {
if (Items[index].Value.Equals(item)) if (Items[index] == item)
{ {
RemoveAt(index); RemoveAt(index);
@ -298,7 +199,7 @@ namespace Ryujinx.Memory.Range
/// <param name="size">Size in bytes of the range</param> /// <param name="size">Size in bytes of the range</param>
/// <returns>The overlapping item, or the default value for the type if none found</returns> /// <returns>The overlapping item, or the default value for the type if none found</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public override RangeItem<T> FindOverlap(ulong address, ulong size) public override T FindOverlap(ulong address, ulong size)
{ {
int index = BinarySearchLeftEdge(address, address + size); int index = BinarySearchLeftEdge(address, address + size);
@ -321,7 +222,7 @@ namespace Ryujinx.Memory.Range
/// <param name="size">Size in bytes of the range</param> /// <param name="size">Size in bytes of the range</param>
/// <returns>The overlapping item, or the default value for the type if none found</returns> /// <returns>The overlapping item, or the default value for the type if none found</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public override RangeItem<T> FindOverlapFast(ulong address, ulong size) public override T FindOverlapFast(ulong address, ulong size)
{ {
int index = BinarySearch(address, address + size); int index = BinarySearch(address, address + size);
@ -340,7 +241,7 @@ namespace Ryujinx.Memory.Range
/// <param name="size">Size in bytes of the range</param> /// <param name="size">Size in bytes of the range</param>
/// <param name="output">Output array where matches will be written. It is automatically resized to fit the results</param> /// <param name="output">Output array where matches will be written. It is automatically resized to fit the results</param>
/// <returns>Range information of overlapping items found</returns> /// <returns>Range information of overlapping items found</returns>
private OverlapResult<T> FindOverlaps(ulong address, ulong size, ref RangeItem<T>[] output) private OverlapResult<T> FindOverlaps(ulong address, ulong size, ref T[] output)
{ {
int outputCount = 0; int outputCount = 0;
@ -353,7 +254,7 @@ namespace Ryujinx.Memory.Range
for (int i = startIndex; i < Count; i++) for (int i = startIndex; i < Count; i++)
{ {
ref RangeItem<T> item = ref Items[i]; T item = Items[i];
if (item.Address >= endAddress) if (item.Address >= endAddress)
{ {
@ -398,7 +299,7 @@ namespace Ryujinx.Memory.Range
{ {
for (int i = 0; i < Count; i++) for (int i = 0; i < Count; i++)
{ {
yield return Items[i].Value; yield return Items[i];
} }
} }
} }

View File

@ -1,56 +1,22 @@
using Ryujinx.Common; using Ryujinx.Common;
using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
namespace Ryujinx.Memory.Range namespace Ryujinx.Memory.Range
{ {
public class RangeItem<TValue> where TValue : IRange public interface IRangeListRange<TValue> : IRange where TValue : class, IRangeListRange<TValue>
{ {
public RangeItem<TValue> Next; public TValue Next { get; set; }
public RangeItem<TValue> Previous; public TValue Previous { get; set; }
public ulong Address;
public ulong EndAddress;
public TValue Value;
public RangeItem()
{
}
public RangeItem(TValue value)
{
Address = value.Address;
EndAddress = value.Address + value.Size;
Value = value;
}
public RangeItem<TValue> Set(TValue value)
{
Next = null;
Previous = null;
Address = value.Address;
EndAddress = value.Address + value.Size;
Value = value;
return this;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool OverlapsWith(ulong address, ulong endAddress)
{
return Address < endAddress && address < EndAddress;
}
} }
public unsafe abstract class RangeListBase<T> : IEnumerable<T> where T : IRange public unsafe abstract class RangeListBase<T> : IEnumerable<T> where T : class, IRangeListRange<T>
{ {
protected static readonly ObjectPool<RangeItem<T>> _rangeItemPool = new(() => new RangeItem<T>());
private const int BackingInitialSize = 1024; private const int BackingInitialSize = 1024;
protected RangeItem<T>[] Items; protected T[] Items;
protected readonly int BackingGrowthSize; protected readonly int BackingGrowthSize;
public int Count { get; protected set; } public int Count { get; protected set; }
@ -62,32 +28,18 @@ namespace Ryujinx.Memory.Range
protected RangeListBase(int backingInitialSize = BackingInitialSize) protected RangeListBase(int backingInitialSize = BackingInitialSize)
{ {
BackingGrowthSize = backingInitialSize; BackingGrowthSize = backingInitialSize;
Items = new RangeItem<T>[backingInitialSize]; Items = new T[backingInitialSize];
} }
public abstract void Add(T item); public abstract void Add(T item);
/// <summary>
/// Updates an item's end address on the list. Address must be the same.
/// </summary>
/// <param name="item">The item to be updated</param>
/// <returns>True if the item was located and updated, false otherwise</returns>
protected abstract bool Update(T item);
/// <summary>
/// Updates an item's end address on the list. Address must be the same.
/// </summary>
/// <param name="item">The RangeItem to be updated</param>
/// <returns>True if the item was located and updated, false otherwise</returns>
protected abstract bool Update(RangeItem<T> item);
public abstract bool Remove(T item); public abstract bool Remove(T item);
public abstract void RemoveRange(RangeItem<T> startItem, RangeItem<T> endItem); public abstract void RemoveRange(T startItem, T endItem);
public abstract RangeItem<T> FindOverlap(ulong address, ulong size); public abstract T FindOverlap(ulong address, ulong size);
public abstract RangeItem<T> FindOverlapFast(ulong address, ulong size); public abstract T FindOverlapFast(ulong address, ulong size);
/// <summary> /// <summary>
/// Performs binary search on the internal list of items. /// Performs binary search on the internal list of items.
@ -106,7 +58,7 @@ namespace Ryujinx.Memory.Range
int middle = left + (range >> 1); int middle = left + (range >> 1);
ref RangeItem<T> item = ref Items[middle]; T item = Items[middle];
if (item.Address == address) if (item.Address == address)
{ {
@ -144,7 +96,7 @@ namespace Ryujinx.Memory.Range
int middle = left + (range >> 1); int middle = left + (range >> 1);
ref RangeItem<T> item = ref Items[middle]; T item = Items[middle];
if (item.OverlapsWith(address, endAddress)) if (item.OverlapsWith(address, endAddress))
{ {
@ -185,7 +137,7 @@ namespace Ryujinx.Memory.Range
int middle = left + (range >> 1); int middle = left + (range >> 1);
ref RangeItem<T> item = ref Items[middle]; T item = Items[middle];
bool match = item.OverlapsWith(address, endAddress); bool match = item.OverlapsWith(address, endAddress);
@ -237,7 +189,7 @@ namespace Ryujinx.Memory.Range
int middle = right - (range >> 1); int middle = right - (range >> 1);
ref RangeItem<T> item = ref Items[middle]; T item = Items[middle];
bool match = item.OverlapsWith(address, endAddress); bool match = item.OverlapsWith(address, endAddress);
@ -282,7 +234,7 @@ namespace Ryujinx.Memory.Range
if (Count == 1) if (Count == 1)
{ {
ref RangeItem<T> item = ref Items[0]; T item = Items[0];
if (item.OverlapsWith(address, endAddress)) if (item.OverlapsWith(address, endAddress))
{ {
@ -312,7 +264,7 @@ namespace Ryujinx.Memory.Range
int middle = left + (range >> 1); int middle = left + (range >> 1);
ref RangeItem<T> item = ref Items[middle]; T item = Items[middle];
bool match = item.OverlapsWith(address, endAddress); bool match = item.OverlapsWith(address, endAddress);
@ -369,7 +321,7 @@ namespace Ryujinx.Memory.Range
int middle = right - (range >> 1); int middle = right - (range >> 1);
ref RangeItem<T> item = ref Items[middle]; T item = Items[middle];
bool match = item.OverlapsWith(address, endAddress); bool match = item.OverlapsWith(address, endAddress);

View File

@ -5,7 +5,7 @@ namespace Ryujinx.Memory.Tracking
/// <summary> /// <summary>
/// A region of memory. /// A region of memory.
/// </summary> /// </summary>
abstract class AbstractRegion : INonOverlappingRange abstract class AbstractRegion<T> : INonOverlappingRange<T> where T : class, INonOverlappingRange<T>
{ {
/// <summary> /// <summary>
/// Base address. /// Base address.
@ -22,6 +22,9 @@ namespace Ryujinx.Memory.Tracking
/// </summary> /// </summary>
public ulong EndAddress => Address + Size; public ulong EndAddress => Address + Size;
public T Next { get; set; }
public T Previous { get; set; }
/// <summary> /// <summary>
/// Create a new region. /// Create a new region.
/// </summary> /// </summary>
@ -37,11 +40,11 @@ namespace Ryujinx.Memory.Tracking
/// Check if this range overlaps with another. /// Check if this range overlaps with another.
/// </summary> /// </summary>
/// <param name="address">Base address</param> /// <param name="address">Base address</param>
/// <param name="size">Size of the range</param> /// <param name="endAddress">End address</param>
/// <returns>True if overlapping, false otherwise</returns> /// <returns>True if overlapping, 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;
} }
/// <summary> /// <summary>
@ -68,6 +71,6 @@ namespace Ryujinx.Memory.Tracking
/// </summary> /// </summary>
/// <param name="splitAddress">Address to split the region around</param> /// <param name="splitAddress">Address to split the region around</param>
/// <returns>The second part of the split region, with start address at the given split.</returns> /// <returns>The second part of the split region, with start address at the given split.</returns>
public abstract INonOverlappingRange Split(ulong splitAddress); public abstract INonOverlappingRange<T> Split(ulong splitAddress);
} }
} }

View File

@ -81,10 +81,10 @@ namespace Ryujinx.Memory.Tracking
{ {
NonOverlappingRangeList<VirtualRegion> regions = type == 0 ? _virtualRegions : _guestVirtualRegions; NonOverlappingRangeList<VirtualRegion> regions = type == 0 ? _virtualRegions : _guestVirtualRegions;
regions.Lock.EnterReadLock(); regions.Lock.EnterReadLock();
Span<RangeItem<VirtualRegion>> overlaps = regions.FindOverlapsAsSpan(va, size); ReadOnlySpan<VirtualRegion> overlaps = regions.FindOverlapsAsSpan(va, size);
for (int i = 0; i < overlaps.Length; i++) for (int i = 0; i < overlaps.Length; i++)
{ {
VirtualRegion region = overlaps[i].Value; VirtualRegion region = overlaps[i];
// If the region has been fully remapped, signal that it has been mapped again. // If the region has been fully remapped, signal that it has been mapped again.
bool remapped = _memoryManager.IsRangeMapped(region.Address, region.Size); bool remapped = _memoryManager.IsRangeMapped(region.Address, region.Size);
@ -117,11 +117,11 @@ namespace Ryujinx.Memory.Tracking
{ {
NonOverlappingRangeList<VirtualRegion> regions = type == 0 ? _virtualRegions : _guestVirtualRegions; NonOverlappingRangeList<VirtualRegion> regions = type == 0 ? _virtualRegions : _guestVirtualRegions;
regions.Lock.EnterReadLock(); regions.Lock.EnterReadLock();
Span<RangeItem<VirtualRegion>> overlaps = regions.FindOverlapsAsSpan(va, size); ReadOnlySpan<VirtualRegion> overlaps = regions.FindOverlapsAsSpan(va, size);
for (int i = 0; i < overlaps.Length; i++) for (int i = 0; i < overlaps.Length; i++)
{ {
overlaps[i].Value.SignalMappingChanged(false); overlaps[i].SignalMappingChanged(false);
} }
regions.Lock.ExitReadLock(); regions.Lock.ExitReadLock();
} }
@ -301,7 +301,7 @@ namespace Ryujinx.Memory.Tracking
// We use the non-span method here because keeping the lock will cause a deadlock. // We use the non-span method here because keeping the lock will cause a deadlock.
regions.Lock.EnterReadLock(); regions.Lock.EnterReadLock();
RangeItem<VirtualRegion>[] overlaps = regions.FindOverlapsAsArray(address, size, out int length); VirtualRegion[] overlaps = regions.FindOverlapsAsArray(address, size, out int length);
regions.Lock.ExitReadLock(); regions.Lock.ExitReadLock();
if (length == 0 && !precise) if (length == 0 && !precise)
@ -327,7 +327,7 @@ namespace Ryujinx.Memory.Tracking
for (int i = 0; i < length; i++) for (int i = 0; i < length; i++)
{ {
VirtualRegion region = overlaps[i].Value; VirtualRegion region = overlaps[i];
if (precise) if (precise)
{ {
@ -341,7 +341,7 @@ namespace Ryujinx.Memory.Tracking
if (length != 0) if (length != 0)
{ {
ArrayPool<RangeItem<VirtualRegion>>.Shared.Return(overlaps); ArrayPool<VirtualRegion>.Shared.Return(overlaps);
} }
} }
} }

View File

@ -6,7 +6,7 @@ namespace Ryujinx.Memory.Tracking
/// <summary> /// <summary>
/// A region of virtual memory. /// A region of virtual memory.
/// </summary> /// </summary>
class VirtualRegion : AbstractRegion class VirtualRegion : AbstractRegion<VirtualRegion>
{ {
public List<RegionHandle> Handles = []; public List<RegionHandle> Handles = [];
@ -137,7 +137,7 @@ namespace Ryujinx.Memory.Tracking
} }
} }
public override INonOverlappingRange Split(ulong splitAddress) public override INonOverlappingRange<VirtualRegion> Split(ulong splitAddress)
{ {
VirtualRegion newRegion = new(_tracking, splitAddress, EndAddress - splitAddress, Guest, _lastPermission); VirtualRegion newRegion = new(_tracking, splitAddress, EndAddress - splitAddress, Guest, _lastPermission);
Size = splitAddress - Address; Size = splitAddress - Address;