Compare commits
95 Commits
Author | SHA1 | Date | |
---|---|---|---|
bc4d99a078 | |||
ec6cb0abb4 | |||
53b5985da6 | |||
87f238be60 | |||
4d0dbbfae2 | |||
08384ee5a8 | |||
d4d0a48bfe | |||
57d8afd0c9 | |||
c43fb92bbf | |||
167f50bbcd | |||
ba91f5d401 | |||
79f6c18a9b | |||
4f63782bac | |||
6f5fcb7970 | |||
6ef8946169 | |||
42340fc743 | |||
103e7cb021 | |||
31ed061bea | |||
4218311e6a | |||
e37735ed26 | |||
74a18b7c18 | |||
74fe814329 | |||
d1a093e5ca | |||
dfb14a5607 | |||
904a5ffcb4 | |||
946633276b | |||
baf94e0e3e | |||
cf6201a4a6 | |||
18909195d1 | |||
f06d22d6f0 | |||
84d6e8d121 | |||
95c4912d58 | |||
356a75af0b | |||
4ae9921063 | |||
6a8ac389e5 | |||
8dd1eb333c | |||
7dc3a62c14 | |||
e59dba42ef | |||
bd6937ae5c | |||
b82e789d4f | |||
4a6724622e | |||
0c73eba3db | |||
a082e14ede | |||
d29da11d5f | |||
ea07328aea | |||
a0b3d82ee0 | |||
609de33b0b | |||
dfc0819e72 | |||
d4803356bb | |||
459efd0db7 | |||
8bb7a3fc97 | |||
628d092fc6 | |||
6c90d50c8e | |||
d56bab1e24 | |||
a37e2d6e44 | |||
25123232bd | |||
8927e0669f | |||
bbed3b9926 | |||
24c8b0edc0 | |||
e5066449a5 | |||
d704bcd93b | |||
c94f0fbb83 | |||
d1b30fbe08 | |||
4505a7f162 | |||
ccbbaddbcb | |||
8bf102d2cd | |||
2adf031830 | |||
bb4a28b525 | |||
a8fbcdae9f | |||
4e81ab4229 | |||
4117c13377 | |||
20a392ad55 | |||
70fcba39de | |||
7795b662a9 | |||
30bdc4544e | |||
f6475cca17 | |||
0335c52254 | |||
b8d992e5a7 | |||
a620cbcc90 | |||
cea204d48e | |||
35fb409e85 | |||
d7ec4308b4 | |||
fbdd390f90 | |||
f33fea3287 | |||
5d3eea40be | |||
cd37c75b82 | |||
43705c2320 | |||
371e6fa24c | |||
1d9b63cc6a | |||
795539bc82 | |||
dd2e851e95 | |||
2ca70eb9a0 | |||
6575952432 | |||
9a28ba72b1 | |||
34a9922b57 |
@ -17,8 +17,8 @@ tab_width = 4
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
|
||||
# JSON files
|
||||
[*.json]
|
||||
# Markdown, JSON, YAML, props and csproj files
|
||||
[*.{md,json,yml,props,csproj}]
|
||||
|
||||
# Indentation and spacing
|
||||
indent_size = 2
|
||||
@ -259,12 +259,12 @@ dotnet_diagnostic.CA1861.severity = none
|
||||
# Disable "Prefer using 'string.Equals(string, StringComparison)' to perform a case-insensitive comparison, but keep in mind that this might cause subtle changes in behavior, so make sure to conduct thorough testing after applying the suggestion, or if culturally sensitive comparison is not required, consider using 'StringComparison.OrdinalIgnoreCase'"
|
||||
dotnet_diagnostic.CA1862.severity = none
|
||||
|
||||
[src/Ryujinx.HLE/HOS/Services/**.cs]
|
||||
# Disable "mark members as static" rule for services
|
||||
[src/Ryujinx/UI/ViewModels/**.cs]
|
||||
# Disable "mark members as static" rule for ViewModels
|
||||
dotnet_diagnostic.CA1822.severity = none
|
||||
|
||||
[src/Ryujinx.Ava/UI/ViewModels/**.cs]
|
||||
# Disable "mark members as static" rule for ViewModels
|
||||
[src/Ryujinx.HLE/HOS/Services/**.cs]
|
||||
# Disable "mark members as static" rule for services
|
||||
dotnet_diagnostic.CA1822.severity = none
|
||||
|
||||
[src/Ryujinx.Tests/Cpu/*.cs]
|
||||
|
2
.github/labeler.yml
vendored
2
.github/labeler.yml
vendored
@ -20,7 +20,7 @@ gpu:
|
||||
|
||||
gui:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: ['src/Ryujinx/**', 'src/Ryujinx.Ui.Common/**', 'src/Ryujinx.Ui.LocaleGenerator/**', 'src/Ryujinx.Ava/**']
|
||||
- any-glob-to-any-file: ['src/Ryujinx/**', 'src/Ryujinx.UI.Common/**', 'src/Ryujinx.UI.LocaleGenerator/**', 'src/Ryujinx.Gtk3/**']
|
||||
|
||||
horizon:
|
||||
- changed-files:
|
||||
|
87
.github/workflows/build.yml
vendored
87
.github/workflows/build.yml
vendored
@ -10,28 +10,17 @@ env:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: ${{ matrix.OS_NAME }} (${{ matrix.configuration }})
|
||||
runs-on: ${{ matrix.os }}
|
||||
name: ${{ matrix.platform.name }} (${{ matrix.configuration }})
|
||||
runs-on: ${{ matrix.platform.os }}
|
||||
timeout-minutes: 45
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macOS-latest, windows-latest]
|
||||
configuration: [Debug, Release]
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
OS_NAME: Linux x64
|
||||
DOTNET_RUNTIME_IDENTIFIER: linux-x64
|
||||
RELEASE_ZIP_OS_NAME: linux_x64
|
||||
|
||||
- os: macOS-latest
|
||||
OS_NAME: macOS x64
|
||||
DOTNET_RUNTIME_IDENTIFIER: osx-x64
|
||||
RELEASE_ZIP_OS_NAME: osx_x64
|
||||
|
||||
- os: windows-latest
|
||||
OS_NAME: Windows x64
|
||||
DOTNET_RUNTIME_IDENTIFIER: win-x64
|
||||
RELEASE_ZIP_OS_NAME: win_x64
|
||||
platform:
|
||||
- { name: win-x64, os: windows-latest, zip_os_name: win_x64 }
|
||||
- { name: linux-x64, os: ubuntu-latest, zip_os_name: linux_x64 }
|
||||
- { name: linux-arm64, os: ubuntu-latest, zip_os_name: linux_arm64 }
|
||||
- { name: osx-x64, os: macOS-latest, zip_os_name: osx_x64 }
|
||||
|
||||
fail-fast: false
|
||||
steps:
|
||||
@ -40,7 +29,7 @@ jobs:
|
||||
- uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
global-json-file: global.json
|
||||
|
||||
|
||||
- name: Overwrite csc problem matcher
|
||||
run: echo "::add-matcher::.github/csc.json"
|
||||
|
||||
@ -49,6 +38,16 @@ jobs:
|
||||
run: echo "result=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
|
||||
shell: bash
|
||||
|
||||
- name: Change config filename
|
||||
run: sed -r --in-place 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/PRConfig\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
shell: bash
|
||||
if: github.event_name == 'pull_request' && matrix.platform.os != 'macOS-latest'
|
||||
|
||||
- name: Change config filename for macOS
|
||||
run: sed -r -i '' 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/PRConfig\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
shell: bash
|
||||
if: github.event_name == 'pull_request' && matrix.platform.os == 'macOS-latest'
|
||||
|
||||
- name: Build
|
||||
run: dotnet build -c "${{ matrix.configuration }}" -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER
|
||||
|
||||
@ -58,46 +57,47 @@ jobs:
|
||||
commands: dotnet test --no-build -c "${{ matrix.configuration }}"
|
||||
timeout-minutes: 10
|
||||
retry-codes: 139
|
||||
if: matrix.platform.name != 'linux-arm64'
|
||||
|
||||
- name: Publish Ryujinx
|
||||
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx --self-contained true
|
||||
if: github.event_name == 'pull_request' && matrix.os != 'macOS-latest'
|
||||
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.platform.name }}" -o ./publish -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx --self-contained true
|
||||
if: github.event_name == 'pull_request' && matrix.platform.os != 'macOS-latest'
|
||||
|
||||
- name: Publish Ryujinx.Headless.SDL2
|
||||
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish_sdl2_headless -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx.Headless.SDL2 --self-contained true
|
||||
if: github.event_name == 'pull_request' && matrix.os != 'macOS-latest'
|
||||
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.platform.name }}" -o ./publish_sdl2_headless -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx.Headless.SDL2 --self-contained true
|
||||
if: github.event_name == 'pull_request' && matrix.platform.os != 'macOS-latest'
|
||||
|
||||
- name: Publish Ryujinx.Ava
|
||||
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish_ava -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx.Ava --self-contained true
|
||||
if: github.event_name == 'pull_request' && matrix.os != 'macOS-latest'
|
||||
- name: Publish Ryujinx.Gtk3
|
||||
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.platform.name }}" -o ./publish_gtk -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx.Gtk3 --self-contained true
|
||||
if: github.event_name == 'pull_request' && matrix.platform.os != 'macOS-latest'
|
||||
|
||||
- name: Set executable bit
|
||||
run: |
|
||||
chmod +x ./publish/Ryujinx ./publish/Ryujinx.sh
|
||||
chmod +x ./publish_sdl2_headless/Ryujinx.Headless.SDL2 ./publish_sdl2_headless/Ryujinx.sh
|
||||
chmod +x ./publish_ava/Ryujinx.Ava ./publish_ava/Ryujinx.sh
|
||||
if: github.event_name == 'pull_request' && matrix.os == 'ubuntu-latest'
|
||||
chmod +x ./publish_gtk/Ryujinx.Gtk3 ./publish_gtk/Ryujinx.sh
|
||||
if: github.event_name == 'pull_request' && matrix.platform.os == 'ubuntu-latest'
|
||||
|
||||
- name: Upload Ryujinx artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.RELEASE_ZIP_OS_NAME }}
|
||||
name: ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.platform.zip_os_name }}
|
||||
path: publish
|
||||
if: github.event_name == 'pull_request' && matrix.os != 'macOS-latest'
|
||||
if: github.event_name == 'pull_request' && matrix.platform.os != 'macOS-latest'
|
||||
|
||||
- name: Upload Ryujinx.Headless.SDL2 artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: sdl2-ryujinx-headless-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.RELEASE_ZIP_OS_NAME }}
|
||||
name: sdl2-ryujinx-headless-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.platform.zip_os_name }}
|
||||
path: publish_sdl2_headless
|
||||
if: github.event_name == 'pull_request' && matrix.os != 'macOS-latest'
|
||||
if: github.event_name == 'pull_request' && matrix.platform.os != 'macOS-latest'
|
||||
|
||||
- name: Upload Ryujinx.Ava artifact
|
||||
- name: Upload Ryujinx.Gtk3 artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ava-ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.RELEASE_ZIP_OS_NAME }}
|
||||
path: publish_ava
|
||||
if: github.event_name == 'pull_request' && matrix.os != 'macOS-latest'
|
||||
name: gtk-ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.platform.zip_os_name }}
|
||||
path: publish_gtk
|
||||
if: github.event_name == 'pull_request' && matrix.platform.os != 'macOS-latest'
|
||||
|
||||
build_macos:
|
||||
name: macOS Universal (${{ matrix.configuration }})
|
||||
@ -135,19 +135,24 @@ jobs:
|
||||
id: git_short_hash
|
||||
run: echo "result=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Publish macOS Ryujinx.Ava
|
||||
- name: Change config filename
|
||||
run: sed -r --in-place 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/PRConfig\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
shell: bash
|
||||
if: github.event_name == 'pull_request'
|
||||
|
||||
- name: Publish macOS Ryujinx
|
||||
run: |
|
||||
./distribution/macos/create_macos_build_ava.sh . publish_tmp_ava publish_ava ./distribution/macos/entitlements.xml "${{ env.RYUJINX_BASE_VERSION }}" "${{ steps.git_short_hash.outputs.result }}" "${{ matrix.configuration }}" "-p:ExtraDefineConstants=DISABLE_UPDATER"
|
||||
./distribution/macos/create_macos_build_ava.sh . publish_tmp publish ./distribution/macos/entitlements.xml "${{ env.RYUJINX_BASE_VERSION }}" "${{ steps.git_short_hash.outputs.result }}" "${{ matrix.configuration }}" "-p:ExtraDefineConstants=DISABLE_UPDATER"
|
||||
|
||||
- name: Publish macOS Ryujinx.Headless.SDL2
|
||||
run: |
|
||||
./distribution/macos/create_macos_build_headless.sh . publish_tmp_headless publish_headless ./distribution/macos/entitlements.xml "${{ env.RYUJINX_BASE_VERSION }}" "${{ steps.git_short_hash.outputs.result }}" "${{ matrix.configuration }}" "-p:ExtraDefineConstants=DISABLE_UPDATER"
|
||||
|
||||
- name: Upload Ryujinx.Ava artifact
|
||||
- name: Upload Ryujinx artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ava-ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-macos_universal
|
||||
path: "publish_ava/*.tar.gz"
|
||||
name: ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-macos_universal
|
||||
path: "publish/*.tar.gz"
|
||||
if: github.event_name == 'pull_request'
|
||||
|
||||
- name: Upload Ryujinx.Headless.SDL2 artifact
|
||||
|
2
.github/workflows/checks.yml
vendored
2
.github/workflows/checks.yml
vendored
@ -8,7 +8,7 @@ on:
|
||||
- '!.github/**'
|
||||
- '!*.yml'
|
||||
- '!*.config'
|
||||
- '!README.md'
|
||||
- '!*.md'
|
||||
- '.github/workflows/*.yml'
|
||||
|
||||
permissions:
|
||||
|
72
.github/workflows/flatpak.yml
vendored
72
.github/workflows/flatpak.yml
vendored
@ -51,38 +51,76 @@ jobs:
|
||||
- name: Restore Nuget packages
|
||||
# With .NET 8.0.100, Microsoft.NET.ILLink.Tasks isn't restored by default and only seems to appears when publishing.
|
||||
# So we just publish to grab the dependencies
|
||||
run: dotnet publish -c Release -r linux-x64 Ryujinx/${{ env.RYUJINX_PROJECT_FILE }} --self-contained
|
||||
run: |
|
||||
dotnet publish -c Release -r linux-x64 Ryujinx/${{ env.RYUJINX_PROJECT_FILE }} --self-contained
|
||||
dotnet publish -c Release -r linux-arm64 Ryujinx/${{ env.RYUJINX_PROJECT_FILE }} --self-contained
|
||||
|
||||
- name: Generate nuget_sources.json
|
||||
shell: python
|
||||
run: |
|
||||
import hashlib
|
||||
from pathlib import Path
|
||||
import base64
|
||||
import binascii
|
||||
import json
|
||||
import os
|
||||
import urllib.request
|
||||
|
||||
sources = []
|
||||
|
||||
for path in Path(os.environ['NUGET_PACKAGES']).glob('**/*.nupkg.sha512'):
|
||||
name = path.parent.parent.name
|
||||
version = path.parent.name
|
||||
filename = '{}.{}.nupkg'.format(name, version)
|
||||
url = 'https://api.nuget.org/v3-flatcontainer/{}/{}/{}'.format(name, version, filename)
|
||||
|
||||
with path.open() as fp:
|
||||
sha512 = binascii.hexlify(base64.b64decode(fp.read())).decode('ascii')
|
||||
def create_source_from_external(name, version):
|
||||
full_dir_path = Path(os.environ["NUGET_PACKAGES"]).joinpath(name).joinpath(version)
|
||||
os.makedirs(full_dir_path, exist_ok=True)
|
||||
|
||||
sources.append({
|
||||
'type': 'file',
|
||||
'url': url,
|
||||
'sha512': sha512,
|
||||
'dest': os.environ['NUGET_SOURCES_DESTDIR'],
|
||||
'dest-filename': filename,
|
||||
})
|
||||
filename = "{}.{}.nupkg".format(name, version)
|
||||
url = "https://api.nuget.org/v3-flatcontainer/{}/{}/{}".format(
|
||||
name, version, filename
|
||||
)
|
||||
|
||||
with open('flathub/nuget_sources.json', 'w') as fp:
|
||||
json.dump(sources, fp, indent=4)
|
||||
print(f"Processing {url}...")
|
||||
response = urllib.request.urlopen(url)
|
||||
sha512 = hashlib.sha512(response.read()).hexdigest()
|
||||
|
||||
return {
|
||||
"type": "file",
|
||||
"url": url,
|
||||
"sha512": sha512,
|
||||
"dest": os.environ["NUGET_SOURCES_DESTDIR"],
|
||||
"dest-filename": filename,
|
||||
}
|
||||
|
||||
|
||||
has_added_x64_apphost = False
|
||||
|
||||
for path in Path(os.environ["NUGET_PACKAGES"]).glob("**/*.nupkg.sha512"):
|
||||
name = path.parent.parent.name
|
||||
version = path.parent.name
|
||||
filename = "{}.{}.nupkg".format(name, version)
|
||||
url = "https://api.nuget.org/v3-flatcontainer/{}/{}/{}".format(
|
||||
name, version, filename
|
||||
)
|
||||
|
||||
with path.open() as fp:
|
||||
sha512 = binascii.hexlify(base64.b64decode(fp.read())).decode("ascii")
|
||||
|
||||
sources.append(
|
||||
{
|
||||
"type": "file",
|
||||
"url": url,
|
||||
"sha512": sha512,
|
||||
"dest": os.environ["NUGET_SOURCES_DESTDIR"],
|
||||
"dest-filename": filename,
|
||||
}
|
||||
)
|
||||
|
||||
# .NET will not add current installed application host to the list, force inject it here.
|
||||
if not has_added_x64_apphost and name.startswith('microsoft.netcore.app.host'):
|
||||
sources.append(create_source_from_external("microsoft.netcore.app.host.linux-x64", version))
|
||||
has_added_x64_apphost = True
|
||||
|
||||
with open("flathub/nuget_sources.json", "w") as fp:
|
||||
json.dump(sources, fp, indent=4)
|
||||
|
||||
- name: Update flatpak metadata
|
||||
id: metadata
|
||||
|
41
.github/workflows/mako.yml
vendored
Normal file
41
.github/workflows/mako.yml
vendored
Normal file
@ -0,0 +1,41 @@
|
||||
name: Mako
|
||||
on:
|
||||
discussion:
|
||||
types: [created, edited, answered, unanswered, category_changed]
|
||||
discussion_comment:
|
||||
types: [created, edited]
|
||||
gollum:
|
||||
issue_comment:
|
||||
types: [created, edited]
|
||||
issues:
|
||||
types: [opened, edited, reopened, pinned, milestoned, demilestoned, assigned, unassigned, labeled, unlabeled]
|
||||
pull_request_target:
|
||||
types: [opened, edited, reopened, synchronize, ready_for_review, assigned, unassigned]
|
||||
|
||||
jobs:
|
||||
tasks:
|
||||
name: Run Ryujinx tasks
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
discussions: write
|
||||
issues: write
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
if: github.event_name == 'pull_request_target'
|
||||
with:
|
||||
# Ensure we pin the source origin as pull_request_target run under forks.
|
||||
fetch-depth: 0
|
||||
repository: Ryujinx/Ryujinx
|
||||
ref: master
|
||||
|
||||
- name: Run Mako command
|
||||
uses: Ryujinx/Ryujinx-Mako@master
|
||||
with:
|
||||
command: exec-ryujinx-tasks
|
||||
args: --event-name "${{ github.event_name }}" --event-path "${{ github.event_path }}" -w "${{ github.workspace }}" "${{ github.repository }}" "${{ github.run_id }}"
|
||||
app_id: ${{ secrets.MAKO_APP_ID }}
|
||||
private_key: ${{ secrets.MAKO_PRIVATE_KEY }}
|
||||
installation_id: ${{ secrets.MAKO_INSTALLATION_ID }}
|
10
.github/workflows/nightly_pr_comment.yml
vendored
10
.github/workflows/nightly_pr_comment.yml
vendored
@ -39,24 +39,24 @@ jobs:
|
||||
return core.error(`No artifacts found`);
|
||||
}
|
||||
let body = `Download the artifacts for this pull request:\n`;
|
||||
let hidden_avalonia_artifacts = `\n\n <details><summary>Experimental GUI (Avalonia)</summary>\n`;
|
||||
let hidden_gtk_artifacts = `\n\n <details><summary>Old GUI (GTK3)</summary>\n`;
|
||||
let hidden_headless_artifacts = `\n\n <details><summary>GUI-less (SDL2)</summary>\n`;
|
||||
let hidden_debug_artifacts = `\n\n <details><summary>Only for Developers</summary>\n`;
|
||||
for (const art of artifacts) {
|
||||
if(art.name.includes('Debug')) {
|
||||
hidden_debug_artifacts += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
|
||||
} else if(art.name.includes('ava-ryujinx')) {
|
||||
hidden_avalonia_artifacts += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
|
||||
} else if(art.name.includes('gtk-ryujinx')) {
|
||||
hidden_gtk_artifacts += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
|
||||
} else if(art.name.includes('sdl2-ryujinx-headless')) {
|
||||
hidden_headless_artifacts += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
|
||||
} else {
|
||||
body += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
|
||||
}
|
||||
}
|
||||
hidden_avalonia_artifacts += `\n</details>`;
|
||||
hidden_gtk_artifacts += `\n</details>`;
|
||||
hidden_headless_artifacts += `\n</details>`;
|
||||
hidden_debug_artifacts += `\n</details>`;
|
||||
body += hidden_avalonia_artifacts;
|
||||
body += hidden_gtk_artifacts;
|
||||
body += hidden_headless_artifacts;
|
||||
body += hidden_debug_artifacts;
|
||||
|
||||
|
19
.github/workflows/pr_triage.yml
vendored
19
.github/workflows/pr_triage.yml
vendored
@ -21,27 +21,8 @@ jobs:
|
||||
repository: Ryujinx/Ryujinx
|
||||
ref: master
|
||||
|
||||
- name: Checkout Ryujinx-Mako
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: Ryujinx/Ryujinx-Mako
|
||||
ref: master
|
||||
path: '.ryujinx-mako'
|
||||
|
||||
- name: Setup Ryujinx-Mako
|
||||
uses: ./.ryujinx-mako/.github/actions/setup-mako
|
||||
|
||||
- name: Update labels based on changes
|
||||
uses: actions/labeler@v5
|
||||
with:
|
||||
sync-labels: true
|
||||
dot: true
|
||||
|
||||
- name: Assign reviewers
|
||||
run: |
|
||||
poetry -n -C .ryujinx-mako run ryujinx-mako update-reviewers ${{ github.repository }} ${{ github.event.pull_request.number }} .github/reviewers.yml
|
||||
shell: bash
|
||||
env:
|
||||
MAKO_APP_ID: ${{ secrets.MAKO_APP_ID }}
|
||||
MAKO_PRIVATE_KEY: ${{ secrets.MAKO_PRIVATE_KEY }}
|
||||
MAKO_INSTALLATION_ID: ${{ secrets.MAKO_INSTALLATION_ID }}
|
||||
|
65
.github/workflows/release.yml
vendored
65
.github/workflows/release.yml
vendored
@ -10,7 +10,7 @@ on:
|
||||
- '*.yml'
|
||||
- '*.json'
|
||||
- '*.config'
|
||||
- 'README.md'
|
||||
- '*.md'
|
||||
|
||||
concurrency: release
|
||||
|
||||
@ -44,23 +44,27 @@ jobs:
|
||||
sha: context.sha
|
||||
})
|
||||
|
||||
- name: Create release
|
||||
uses: ncipollo/release-action@v1
|
||||
with:
|
||||
name: ${{ steps.version_info.outputs.build_version }}
|
||||
tag: ${{ steps.version_info.outputs.build_version }}
|
||||
body: "For more information about this release please check out the official [Changelog](https://github.com/Ryujinx/Ryujinx/wiki/Changelog)."
|
||||
omitBodyDuringUpdate: true
|
||||
owner: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}
|
||||
repo: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}
|
||||
token: ${{ secrets.RELEASE_TOKEN }}
|
||||
|
||||
release:
|
||||
name: Release ${{ matrix.OS_NAME }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
name: Release for ${{ matrix.platform.name }}
|
||||
runs-on: ${{ matrix.platform.os }}
|
||||
timeout-minutes: ${{ fromJSON(vars.JOB_TIMEOUT) }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ ubuntu-latest, windows-latest ]
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
OS_NAME: Linux x64
|
||||
DOTNET_RUNTIME_IDENTIFIER: linux-x64
|
||||
RELEASE_ZIP_OS_NAME: linux_x64
|
||||
|
||||
- os: windows-latest
|
||||
OS_NAME: Windows x64
|
||||
DOTNET_RUNTIME_IDENTIFIER: win-x64
|
||||
RELEASE_ZIP_OS_NAME: win_x64
|
||||
platform:
|
||||
- { name: win-x64, os: windows-latest, zip_os_name: win_x64 }
|
||||
- { 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
|
||||
|
||||
@ -85,6 +89,7 @@ jobs:
|
||||
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_TARGET_RELEASE_CHANNEL_OWNER\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_REPO\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/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
|
||||
@ -92,42 +97,43 @@ jobs:
|
||||
|
||||
- name: Publish
|
||||
run: |
|
||||
dotnet publish -c Release -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish_gtk/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 true
|
||||
dotnet publish -c Release -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish_sdl2_headless/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx.Headless.SDL2 --self-contained true
|
||||
dotnet publish -c Release -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish_ava/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx.Ava --self-contained true
|
||||
dotnet publish -c Release -r "${{ matrix.platform.name }}" -o ./publish_ava/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 true
|
||||
dotnet publish -c Release -r "${{ matrix.platform.name }}" -o ./publish_sdl2_headless/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx.Headless.SDL2 --self-contained true
|
||||
|
||||
- name: Packing Windows builds
|
||||
if: matrix.os == 'windows-latest'
|
||||
if: matrix.platform.os == 'windows-latest'
|
||||
run: |
|
||||
pushd publish_gtk
|
||||
7z a ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-win_x64.zip publish
|
||||
pushd publish_ava
|
||||
7z a ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip publish
|
||||
popd
|
||||
|
||||
pushd publish_sdl2_headless
|
||||
7z a ../release_output/sdl2-ryujinx-headless-${{ steps.version_info.outputs.build_version }}-win_x64.zip publish
|
||||
7z a ../release_output/sdl2-ryujinx-headless-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip publish
|
||||
popd
|
||||
|
||||
pushd publish_ava
|
||||
7z a ../release_output/test-ava-ryujinx-${{ steps.version_info.outputs.build_version }}-win_x64.zip publish
|
||||
mv publish/Ryujinx.exe publish/Ryujinx.Ava.exe
|
||||
7z a ../release_output/test-ava-ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip publish
|
||||
popd
|
||||
shell: bash
|
||||
|
||||
- name: Packing Linux builds
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
if: matrix.platform.os == 'ubuntu-latest'
|
||||
run: |
|
||||
pushd publish_gtk
|
||||
pushd publish_ava
|
||||
chmod +x publish/Ryujinx.sh publish/Ryujinx
|
||||
tar -czvf ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-linux_x64.tar.gz publish
|
||||
tar -czvf ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz publish
|
||||
popd
|
||||
|
||||
pushd publish_sdl2_headless
|
||||
chmod +x publish/Ryujinx.sh publish/Ryujinx.Headless.SDL2
|
||||
tar -czvf ../release_output/sdl2-ryujinx-headless-${{ steps.version_info.outputs.build_version }}-linux_x64.tar.gz publish
|
||||
tar -czvf ../release_output/sdl2-ryujinx-headless-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz publish
|
||||
popd
|
||||
|
||||
pushd publish_ava
|
||||
mv publish/Ryujinx publish/Ryujinx.Ava
|
||||
chmod +x publish/Ryujinx.sh publish/Ryujinx.Ava
|
||||
tar -czvf ../release_output/test-ava-ryujinx-${{ steps.version_info.outputs.build_version }}-linux_x64.tar.gz publish
|
||||
tar -czvf ../release_output/test-ava-ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz publish
|
||||
popd
|
||||
shell: bash
|
||||
|
||||
@ -186,12 +192,13 @@ jobs:
|
||||
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_TARGET_RELEASE_CHANNEL_OWNER\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_REPO\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/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.Ava
|
||||
- name: Publish macOS Ryujinx
|
||||
run: |
|
||||
./distribution/macos/create_macos_build_ava.sh . publish_tmp_ava publish_ava ./distribution/macos/entitlements.xml "${{ steps.version_info.outputs.build_version }}" "${{ steps.version_info.outputs.git_short_hash }}" Release
|
||||
|
||||
|
||||
- name: Publish macOS Ryujinx.Headless.SDL2
|
||||
run: |
|
||||
./distribution/macos/create_macos_build_headless.sh . publish_tmp_headless publish_headless ./distribution/macos/entitlements.xml "${{ steps.version_info.outputs.build_version }}" "${{ steps.version_info.outputs.git_short_hash }}" Release
|
||||
|
@ -8,35 +8,34 @@
|
||||
<PackageVersion Include="Avalonia.Desktop" Version="11.0.7" />
|
||||
<PackageVersion Include="Avalonia.Diagnostics" Version="11.0.7" />
|
||||
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="11.0.7" />
|
||||
<PackageVersion Include="Avalonia.Svg" Version="11.0.0.10" />
|
||||
<PackageVersion Include="Avalonia.Svg.Skia" Version="11.0.0.10" />
|
||||
<PackageVersion Include="Avalonia.Svg" Version="11.0.0.13" />
|
||||
<PackageVersion Include="Avalonia.Svg.Skia" Version="11.0.0.13" />
|
||||
<PackageVersion Include="CommandLineParser" Version="2.9.1" />
|
||||
<PackageVersion Include="Concentus" Version="1.1.7" />
|
||||
<PackageVersion Include="DiscordRichPresence" Version="1.2.1.24" />
|
||||
<PackageVersion Include="DynamicData" Version="7.14.2" />
|
||||
<PackageVersion Include="DynamicData" Version="8.3.27" />
|
||||
<PackageVersion Include="FluentAvaloniaUI" Version="2.0.5" />
|
||||
<PackageVersion Include="GtkSharp.Dependencies" Version="1.1.1" />
|
||||
<PackageVersion Include="GtkSharp.Dependencies.osx" Version="0.0.5" />
|
||||
<PackageVersion Include="jp2masa.Avalonia.Flexbox" Version="0.3.0-beta.4" />
|
||||
<PackageVersion Include="LibHac" Version="0.19.0" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.7.0" />
|
||||
<PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="7.2.0" />
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
|
||||
<PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="2.3.2" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0" />
|
||||
<PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="7.3.0" />
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
|
||||
<PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.0" />
|
||||
<PackageVersion Include="MsgPack.Cli" Version="1.0.1" />
|
||||
<PackageVersion Include="NetCoreServer" Version="7.0.0" />
|
||||
<PackageVersion Include="NetCoreServer" Version="8.0.7" />
|
||||
<PackageVersion Include="NUnit" Version="3.13.3" />
|
||||
<PackageVersion Include="NUnit3TestAdapter" Version="4.1.0" />
|
||||
<PackageVersion Include="OpenTK.Core" Version="4.8.1" />
|
||||
<PackageVersion Include="OpenTK.Graphics" Version="4.8.1" />
|
||||
<PackageVersion Include="OpenTK.Audio.OpenAL" Version="4.8.1" />
|
||||
<PackageVersion Include="OpenTK.Windowing.GraphicsLibraryFramework" Version="4.8.1" />
|
||||
<PackageVersion Include="OpenTK.Core" Version="4.8.2" />
|
||||
<PackageVersion Include="OpenTK.Graphics" Version="4.8.2" />
|
||||
<PackageVersion Include="OpenTK.Audio.OpenAL" Version="4.8.2" />
|
||||
<PackageVersion Include="OpenTK.Windowing.GraphicsLibraryFramework" Version="4.8.2" />
|
||||
<PackageVersion Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" />
|
||||
<PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.1-build13" />
|
||||
<PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.3-build14" />
|
||||
<PackageVersion Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" />
|
||||
<PackageVersion Include="Ryujinx.GtkSharp" Version="3.24.24.59-ryujinx" />
|
||||
<PackageVersion Include="Ryujinx.SDL2-CS" Version="2.28.1-build28" />
|
||||
<PackageVersion Include="Ryujinx.SDL2-CS" Version="2.30.0-build32" />
|
||||
<PackageVersion Include="securifybv.ShellLink" Version="0.1.0" />
|
||||
<PackageVersion Include="shaderc.net" Version="0.1.0" />
|
||||
<PackageVersion Include="SharpZipLib" Version="1.4.2" />
|
||||
@ -45,10 +44,10 @@
|
||||
<PackageVersion Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.16.0" />
|
||||
<PackageVersion Include="SixLabors.ImageSharp" Version="1.0.4" />
|
||||
<PackageVersion Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta11" />
|
||||
<PackageVersion Include="SPB" Version="0.0.4-build28" />
|
||||
<PackageVersion Include="System.Drawing.Common" Version="8.0.0" />
|
||||
<PackageVersion Include="SPB" Version="0.0.4-build32" />
|
||||
<PackageVersion Include="System.Drawing.Common" Version="8.0.2" />
|
||||
<PackageVersion Include="System.IO.Hashing" Version="8.0.0" />
|
||||
<PackageVersion Include="System.Management" Version="8.0.0" />
|
||||
<PackageVersion Include="UnicornEngine.Unicorn" Version="2.0.2-rc1-fb78016" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
|
93
README.md
93
README.md
@ -1,21 +1,21 @@
|
||||
|
||||
<h1 align="center">
|
||||
<br>
|
||||
<a href="https://ryujinx.org/"><img src="https://i.imgur.com/WcCj6Rt.png" alt="Ryujinx" width="150"></a>
|
||||
<a href="https://ryujinx.org/"><img src="https://raw.githubusercontent.com/Ryujinx/Ryujinx/master/distribution/misc/Logo.svg" alt="Ryujinx" width="150"></a>
|
||||
<br>
|
||||
<b>Ryujinx</b>
|
||||
<br>
|
||||
<sub><sup><b>(REE-YOU-JINX)</b></sup></sub>
|
||||
<br>
|
||||
|
||||
</h1>
|
||||
|
||||
<p align="center">
|
||||
Ryujinx is an open-source Nintendo Switch emulator, created by gdkchan, written in C#.
|
||||
This emulator aims at providing excellent accuracy and performance, a user-friendly interface and consistent builds.
|
||||
It was written from scratch and development on the project began in September 2017. Ryujinx is available on Github under the <a href="https://github.com/Ryujinx/Ryujinx/blob/master/LICENSE.txt" target="_blank">MIT license</a>. <br />
|
||||
|
||||
Ryujinx is an open-source Nintendo Switch emulator, created by gdkchan, written in C#.
|
||||
This emulator aims at providing excellent accuracy and performance, a user-friendly interface and consistent builds.
|
||||
It was written from scratch and development on the project began in September 2017.
|
||||
Ryujinx is available on Github under the <a href="https://github.com/Ryujinx/Ryujinx/blob/master/LICENSE.txt" target="_blank">MIT license</a>.
|
||||
<br />
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/Ryujinx/Ryujinx/actions/workflows/release.yml">
|
||||
<img src="https://github.com/Ryujinx/Ryujinx/actions/workflows/release.yml/badge.svg"
|
||||
@ -34,87 +34,111 @@
|
||||
<img src="https://raw.githubusercontent.com/Ryujinx/Ryujinx-Website/master/public/assets/images/shell.png">
|
||||
</p>
|
||||
|
||||
<h5 align="center">
|
||||
|
||||
</h5>
|
||||
|
||||
## Compatibility
|
||||
|
||||
As of April 2023, Ryujinx has been tested on approximately 4,050 titles; over 4,000 boot past menus and into gameplay, with roughly 3,400 of those being considered playable.
|
||||
You can check out the compatibility list [here](https://github.com/Ryujinx/Ryujinx-Games-List/issues). Anyone is free to submit a new game test or update an existing game test entry; simply follow the new issue template and testing guidelines, or post as a reply to the applicable game issue. Use the search function to see if a game has been tested already!
|
||||
As of October 2023, Ryujinx has been tested on approximately 4,200 titles;
|
||||
over 4,150 boot past menus and into gameplay, with roughly 3,500 of those being considered playable.
|
||||
|
||||
You can check out the compatibility list [here](https://github.com/Ryujinx/Ryujinx-Games-List/issues).
|
||||
|
||||
Anyone is free to submit a new game test or update an existing game test entry;
|
||||
simply follow the new issue template and testing guidelines, or post as a reply to the applicable game issue.
|
||||
Use the search function to see if a game has been tested already!
|
||||
|
||||
## Usage
|
||||
|
||||
To run this emulator, your PC must be equipped with at least 8GiB of RAM; failing to meet this requirement may result in a poor gameplay experience or unexpected crashes.
|
||||
To run this emulator, your PC must be equipped with at least 8GiB of RAM;
|
||||
failing to meet this requirement may result in a poor gameplay experience or unexpected crashes.
|
||||
|
||||
See our [Setup & Configuration Guide](https://github.com/Ryujinx/Ryujinx/wiki/Ryujinx-Setup-&-Configuration-Guide) on how to set up the emulator.
|
||||
|
||||
For our Local Wireless and LAN builds, see our [Multiplayer: Local Play/Local Wireless Guide
|
||||
For our Local Wireless (LDN) builds, see our [Multiplayer: Local Play/Local Wireless Guide
|
||||
](https://github.com/Ryujinx/Ryujinx/wiki/Multiplayer-(LDN-Local-Wireless)-Guide).
|
||||
|
||||
Avalonia UI comes with translations for various languages. See [Crowdin](https://crwd.in/ryujinx) for more information.
|
||||
|
||||
## Latest build
|
||||
|
||||
These builds are compiled automatically for each commit on the master branch. While we strive to ensure optimal stability and performance prior to pushing an update, our automated builds **may be unstable or completely broken.**
|
||||
These builds are compiled automatically for each commit on the master branch.
|
||||
While we strive to ensure optimal stability and performance prior to pushing an update, our automated builds **may be unstable or completely broken**.
|
||||
|
||||
If you want to see details on updates to the emulator, you can visit our [Changelog](https://github.com/Ryujinx/Ryujinx/wiki/Changelog).
|
||||
|
||||
The latest automatic build for Windows, macOS, and Linux can be found on the [Official Website](https://ryujinx.org/download).
|
||||
|
||||
## Documentation
|
||||
|
||||
If you are planning to contribute or just want to learn more about this project please read through our [documentation](docs/README.md).
|
||||
|
||||
## Building
|
||||
|
||||
If you wish to build the emulator yourself, follow these steps:
|
||||
|
||||
### Step 1
|
||||
Install the X64 version of [.NET 8.0 (or higher) SDK](https://dotnet.microsoft.com/download/dotnet/8.0).
|
||||
|
||||
Install the [.NET 8.0 (or higher) SDK](https://dotnet.microsoft.com/download/dotnet/8.0).
|
||||
Make sure your SDK version is higher or equal to the required version specified in [global.json](global.json).
|
||||
|
||||
### Step 2
|
||||
|
||||
Either use `git clone https://github.com/Ryujinx/Ryujinx` on the command line to clone the repository or use Code --> Download zip button to get the files.
|
||||
|
||||
### Step 3
|
||||
|
||||
To build Ryujinx, open a command prompt inside the project directory. You can quickly access it on Windows by holding shift in File Explorer, then right clicking and selecting `Open command window here`. Then type the following command:
|
||||
`dotnet build -c Release -o build`
|
||||
To build Ryujinx, open a command prompt inside the project directory.
|
||||
You can quickly access it on Windows by holding shift in File Explorer, then right clicking and selecting `Open command window here`.
|
||||
Then type the following command: `dotnet build -c Release -o build`
|
||||
the built files will be found in the newly created build directory.
|
||||
|
||||
Ryujinx system files are stored in the `Ryujinx` folder. This folder is located in the user folder, which can be accessed by clicking `Open Ryujinx Folder` under the File menu in the GUI.
|
||||
|
||||
Ryujinx system files are stored in the `Ryujinx` folder.
|
||||
This folder is located in the user folder, which can be accessed by clicking `Open Ryujinx Folder` under the File menu in the GUI.
|
||||
|
||||
## Features
|
||||
|
||||
- **Audio**
|
||||
- **Audio**
|
||||
|
||||
Audio output is entirely supported, audio input (microphone) isn't supported. We use C# wrappers for [OpenAL](https://openal-soft.org/), and [SDL2](https://www.libsdl.org/) & [libsoundio](http://libsound.io/) as fallbacks.
|
||||
Audio output is entirely supported, audio input (microphone) isn't supported.
|
||||
We use C# wrappers for [OpenAL](https://openal-soft.org/), and [SDL2](https://www.libsdl.org/) & [libsoundio](http://libsound.io/) as fallbacks.
|
||||
|
||||
- **CPU**
|
||||
|
||||
The CPU emulator, ARMeilleure, emulates an ARMv8 CPU and currently has support for most 64-bit ARMv8 and some of the ARMv7 (and older) instructions, including partial 32-bit support. It translates the ARM code to a custom IR, performs a few optimizations, and turns that into x86 code.
|
||||
There are three memory manager options available depending on the user's preference, leveraging both software-based (slower) and host-mapped modes (much faster). The fastest option (host, unchecked) is set by default.
|
||||
Ryujinx also features an optional Profiled Persistent Translation Cache, which essentially caches translated functions so that they do not need to be translated every time the game loads. The net result is a significant reduction in load times (the amount of time between launching a game and arriving at the title screen) for nearly every game. NOTE: this feature is enabled by default in the Options menu > System tab. You must launch the game at least twice to the title screen or beyond before performance improvements are unlocked on the third launch! These improvements are permanent and do not require any extra launches going forward.
|
||||
The CPU emulator, ARMeilleure, emulates an ARMv8 CPU and currently has support for most 64-bit ARMv8 and some of the ARMv7 (and older) instructions, including partial 32-bit support.
|
||||
It translates the ARM code to a custom IR, performs a few optimizations, and turns that into x86 code.
|
||||
There are three memory manager options available depending on the user's preference, leveraging both software-based (slower) and host-mapped modes (much faster).
|
||||
The fastest option (host, unchecked) is set by default.
|
||||
Ryujinx also features an optional Profiled Persistent Translation Cache, which essentially caches translated functions so that they do not need to be translated every time the game loads.
|
||||
The net result is a significant reduction in load times (the amount of time between launching a game and arriving at the title screen) for nearly every game.
|
||||
NOTE: This feature is enabled by default in the Options menu > System tab.
|
||||
You must launch the game at least twice to the title screen or beyond before performance improvements are unlocked on the third launch!
|
||||
These improvements are permanent and do not require any extra launches going forward.
|
||||
|
||||
- **GPU**
|
||||
|
||||
The GPU emulator emulates the Switch's Maxwell GPU using either the OpenGL (version 4.5 minimum), Vulkan, or Metal (via MoltenVK) APIs through a custom build of OpenTK or Silk.NET respectively. There are currently six graphics enhancements available to the end user in Ryujinx: Disk Shader Caching, Resolution Scaling, Anti-Aliasing, Scaling Filters (including FSR), Anisotropic Filtering and Aspect Ratio Adjustment. These enhancements can be adjusted or toggled as desired in the GUI.
|
||||
The GPU emulator emulates the Switch's Maxwell GPU using either the OpenGL (version 4.5 minimum), Vulkan, or Metal (via MoltenVK) APIs through a custom build of OpenTK or Silk.NET respectively.
|
||||
There are currently six graphics enhancements available to the end user in Ryujinx: Disk Shader Caching, Resolution Scaling, Anti-Aliasing, Scaling Filters (including FSR), Anisotropic Filtering and Aspect Ratio Adjustment.
|
||||
These enhancements can be adjusted or toggled as desired in the GUI.
|
||||
|
||||
- **Input**
|
||||
|
||||
We currently have support for keyboard, mouse, touch input, JoyCon input support, and nearly all controllers. Motion controls are natively supported in most cases; for dual-JoyCon motion support, DS4Windows or BetterJoy are currently required.
|
||||
In all scenarios, you can set up everything inside the input configuration menu.
|
||||
We currently have support for keyboard, mouse, touch input, JoyCon input support, and nearly all controllers.
|
||||
Motion controls are natively supported in most cases; for dual-JoyCon motion support, DS4Windows or BetterJoy are currently required.
|
||||
In all scenarios, you can set up everything inside the input configuration menu.
|
||||
|
||||
- **DLC & Modifications**
|
||||
|
||||
Ryujinx is able to manage add-on content/downloadable content through the GUI. Mods (romfs, exefs, and runtime mods such as cheats) are also supported; the GUI contains a shortcut to open the respective mods folder for a particular game.
|
||||
Ryujinx is able to manage add-on content/downloadable content through the GUI.
|
||||
Mods (romfs, exefs, and runtime mods such as cheats) are also supported;
|
||||
the GUI contains a shortcut to open the respective mods folder for a particular game.
|
||||
|
||||
- **Configuration**
|
||||
|
||||
The emulator has settings for enabling or disabling some logging, remapping controllers, and more. You can configure all of them through the graphical interface or manually through the config file, `Config.json`, found in the user folder which can be accessed by clicking `Open Ryujinx Folder` under the File menu in the GUI.
|
||||
|
||||
The emulator has settings for enabling or disabling some logging, remapping controllers, and more.
|
||||
You can configure all of them through the graphical interface or manually through the config file, `Config.json`, found in the user folder which can be accessed by clicking `Open Ryujinx Folder` under the File menu in the GUI.
|
||||
|
||||
## Contact
|
||||
|
||||
If you have contributions, suggestions, need emulator support or just want to get in touch with the team, join our [Discord server](https://discord.com/invite/Ryujinx). You may also review our [FAQ](https://github.com/Ryujinx/Ryujinx/wiki/Frequently-Asked-Questions).
|
||||
If you have contributions, suggestions, need emulator support or just want to get in touch with the team, join our [Discord server](https://discord.com/invite/Ryujinx).
|
||||
You may also review our [FAQ](https://github.com/Ryujinx/Ryujinx/wiki/Frequently-Asked-Questions).
|
||||
|
||||
## Donations
|
||||
|
||||
@ -134,9 +158,10 @@ All funds received through Patreon are considered a donation to support the proj
|
||||
|
||||
## License
|
||||
|
||||
This software is licensed under the terms of the <a href="https://github.com/Ryujinx/Ryujinx/blob/master/LICENSE.txt" target="_blank">MIT license.</a></i><br />
|
||||
This software is licensed under the terms of the [MIT license](LICENSE.txt).
|
||||
This project makes use of code authored by the libvpx project, licensed under BSD and the ffmpeg project, licensed under LGPLv3.
|
||||
See [LICENSE.txt](LICENSE.txt) and [THIRDPARTY.md](distribution/legal/THIRDPARTY.md) for more details.
|
||||
|
||||
## Credits
|
||||
|
||||
- [LibHac](https://github.com/Thealexbarney/LibHac) is used for our file-system.
|
||||
|
@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.1.32228.430
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx", "src\Ryujinx\Ryujinx.csproj", "{074045D4-3ED2-4711-9169-E385F2BFB5A0}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Gtk3", "src\Ryujinx.Gtk3\Ryujinx.Gtk3.csproj", "{074045D4-3ED2-4711-9169-E385F2BFB5A0}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Tests", "src\Ryujinx.Tests\Ryujinx.Tests.csproj", "{EBB55AEA-C7D7-4DEB-BF96-FA1789E225E9}"
|
||||
EndProject
|
||||
@ -69,9 +69,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Headless.SDL2", "sr
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Nvdec.FFmpeg", "src\Ryujinx.Graphics.Nvdec.FFmpeg\Ryujinx.Graphics.Nvdec.FFmpeg.csproj", "{BEE1C184-C9A4-410B-8DFC-FB74D5C93AEB}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Ava", "src\Ryujinx.Ava\Ryujinx.Ava.csproj", "{7C1B2721-13DA-4B62-B046-C626605ECCE6}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx", "src\Ryujinx\Ryujinx.csproj", "{7C1B2721-13DA-4B62-B046-C626605ECCE6}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Ui.Common", "src\Ryujinx.Ui.Common\Ryujinx.Ui.Common.csproj", "{BA161CA0-CD65-4E6E-B644-51C8D1E542DC}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.UI.Common", "src\Ryujinx.UI.Common\Ryujinx.UI.Common.csproj", "{BA161CA0-CD65-4E6E-B644-51C8D1E542DC}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Horizon.Generators", "src\Ryujinx.Horizon.Generators\Ryujinx.Horizon.Generators.csproj", "{6AE2A5E8-4C5A-48B9-997B-E1455C0355C6}"
|
||||
EndProject
|
||||
@ -79,7 +79,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Vulkan", "
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spv.Generator", "src\Spv.Generator\Spv.Generator.csproj", "{2BCB3D7A-38C0-4FE7-8FDA-374C6AD56D0E}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Ui.LocaleGenerator", "src\Ryujinx.Ui.LocaleGenerator\Ryujinx.Ui.LocaleGenerator.csproj", "{77D01AD9-2C98-478E-AE1D-8F7100738FB4}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.UI.LocaleGenerator", "src\Ryujinx.UI.LocaleGenerator\Ryujinx.UI.LocaleGenerator.csproj", "{77D01AD9-2C98-478E-AE1D-8F7100738FB4}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Horizon.Common", "src\Ryujinx.Horizon.Common\Ryujinx.Horizon.Common.csproj", "{77F96ECE-4952-42DB-A528-DED25572A573}"
|
||||
EndProject
|
||||
|
@ -4,7 +4,7 @@ Name=Ryujinx
|
||||
Type=Application
|
||||
Icon=Ryujinx
|
||||
Exec=Ryujinx.sh %f
|
||||
Comment=Plays Nintendo Switch applications
|
||||
Comment=A Nintendo Switch Emulator
|
||||
GenericName=Nintendo Switch Emulator
|
||||
Terminal=false
|
||||
Categories=Game;Emulator;
|
||||
|
15
distribution/linux/Ryujinx.sh
Normal file → Executable file
15
distribution/linux/Ryujinx.sh
Normal file → Executable file
@ -1,20 +1,23 @@
|
||||
#!/bin/sh
|
||||
|
||||
SCRIPT_DIR=$(dirname "$(realpath "$0")")
|
||||
RYUJINX_BIN="Ryujinx"
|
||||
|
||||
if [ -f "$SCRIPT_DIR/Ryujinx.Ava" ]; then
|
||||
RYUJINX_BIN="Ryujinx.Ava"
|
||||
fi
|
||||
|
||||
if [ -f "$SCRIPT_DIR/Ryujinx.Headless.SDL2" ]; then
|
||||
RYUJINX_BIN="Ryujinx.Headless.SDL2"
|
||||
fi
|
||||
|
||||
if [ -f "$SCRIPT_DIR/Ryujinx" ]; then
|
||||
RYUJINX_BIN="Ryujinx"
|
||||
fi
|
||||
|
||||
if [ -z "$RYUJINX_BIN" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
COMMAND="env DOTNET_EnableAlternateStackCheck=1"
|
||||
|
||||
if command -v gamemoderun > /dev/null 2>&1; then
|
||||
COMMAND="$COMMAND gamemoderun"
|
||||
fi
|
||||
|
||||
$COMMAND "$SCRIPT_DIR/$RYUJINX_BIN" "$@"
|
||||
exec $COMMAND "$SCRIPT_DIR/$RYUJINX_BIN" "$@"
|
||||
|
@ -14,8 +14,8 @@ mkdir "$APP_BUNDLE_DIRECTORY/Contents/Frameworks"
|
||||
mkdir "$APP_BUNDLE_DIRECTORY/Contents/MacOS"
|
||||
mkdir "$APP_BUNDLE_DIRECTORY/Contents/Resources"
|
||||
|
||||
# Copy executables first
|
||||
cp "$PUBLISH_DIRECTORY/Ryujinx.Ava" "$APP_BUNDLE_DIRECTORY/Contents/MacOS/Ryujinx"
|
||||
# Copy executable and nsure executable can be executed
|
||||
cp "$PUBLISH_DIRECTORY/Ryujinx" "$APP_BUNDLE_DIRECTORY/Contents/MacOS/Ryujinx"
|
||||
chmod u+x "$APP_BUNDLE_DIRECTORY/Contents/MacOS/Ryujinx"
|
||||
|
||||
# Then all libraries
|
||||
|
@ -22,9 +22,9 @@ EXTRA_ARGS=$8
|
||||
|
||||
if [ "$VERSION" == "1.1.0" ];
|
||||
then
|
||||
RELEASE_TAR_FILE_NAME=test-ava-ryujinx-$CONFIGURATION-$VERSION+$SOURCE_REVISION_ID-macos_universal.app.tar
|
||||
RELEASE_TAR_FILE_NAME=ryujinx-$CONFIGURATION-$VERSION+$SOURCE_REVISION_ID-macos_universal.app.tar
|
||||
else
|
||||
RELEASE_TAR_FILE_NAME=test-ava-ryujinx-$VERSION-macos_universal.app.tar
|
||||
RELEASE_TAR_FILE_NAME=ryujinx-$VERSION-macos_universal.app.tar
|
||||
fi
|
||||
|
||||
ARM64_APP_BUNDLE="$TEMP_DIRECTORY/output_arm64/Ryujinx.app"
|
||||
@ -38,9 +38,9 @@ mkdir -p "$TEMP_DIRECTORY"
|
||||
DOTNET_COMMON_ARGS=(-p:DebugType=embedded -p:Version="$VERSION" -p:SourceRevisionId="$SOURCE_REVISION_ID" --self-contained true $EXTRA_ARGS)
|
||||
|
||||
dotnet restore
|
||||
dotnet build -c "$CONFIGURATION" src/Ryujinx.Ava
|
||||
dotnet publish -c "$CONFIGURATION" -r osx-arm64 -o "$TEMP_DIRECTORY/publish_arm64" "${DOTNET_COMMON_ARGS[@]}" src/Ryujinx.Ava
|
||||
dotnet publish -c "$CONFIGURATION" -r osx-x64 -o "$TEMP_DIRECTORY/publish_x64" "${DOTNET_COMMON_ARGS[@]}" src/Ryujinx.Ava
|
||||
dotnet build -c "$CONFIGURATION" src/Ryujinx
|
||||
dotnet publish -c "$CONFIGURATION" -r osx-arm64 -o "$TEMP_DIRECTORY/publish_arm64" "${DOTNET_COMMON_ARGS[@]}" src/Ryujinx
|
||||
dotnet publish -c "$CONFIGURATION" -r osx-x64 -o "$TEMP_DIRECTORY/publish_x64" "${DOTNET_COMMON_ARGS[@]}" src/Ryujinx
|
||||
|
||||
# Get rid of the support library for ARMeilleure for x64 (that's only for arm64)
|
||||
rm -rf "$TEMP_DIRECTORY/publish_x64/libarmeilleure-jitsupport.dylib"
|
||||
@ -108,6 +108,13 @@ tar --exclude "Ryujinx.app/Contents/MacOS/Ryujinx" -cvf "$RELEASE_TAR_FILE_NAME"
|
||||
python3 "$BASE_DIR/distribution/misc/add_tar_exec.py" "$RELEASE_TAR_FILE_NAME" "Ryujinx.app/Contents/MacOS/Ryujinx" "Ryujinx.app/Contents/MacOS/Ryujinx"
|
||||
gzip -9 < "$RELEASE_TAR_FILE_NAME" > "$RELEASE_TAR_FILE_NAME.gz"
|
||||
rm "$RELEASE_TAR_FILE_NAME"
|
||||
|
||||
# Create legacy update package for Avalonia to not left behind old testers.
|
||||
if [ "$VERSION" != "1.1.0" ];
|
||||
then
|
||||
cp $RELEASE_TAR_FILE_NAME.gz test-ava-ryujinx-$VERSION-macos_universal.app.tar.gz
|
||||
fi
|
||||
|
||||
popd
|
||||
|
||||
echo "Done"
|
8
distribution/macos/shortcut-launch-script.sh
Normal file
8
distribution/macos/shortcut-launch-script.sh
Normal file
@ -0,0 +1,8 @@
|
||||
#!/bin/sh
|
||||
launch_arch="$(uname -m)"
|
||||
if [ "$(sysctl -in sysctl.proc_translated)" = "1" ]
|
||||
then
|
||||
launch_arch="arm64"
|
||||
fi
|
||||
|
||||
arch -$launch_arch {0} {1}
|
@ -517,7 +517,10 @@ namespace ARMeilleure.Decoders
|
||||
SetA64("0x00111100>>>xxx100111xxxxxxxxxx", InstName.Sqrshrn_V, InstEmit.Sqrshrn_V, OpCodeSimdShImm.Create);
|
||||
SetA64("0111111100>>>xxx100011xxxxxxxxxx", InstName.Sqrshrun_S, InstEmit.Sqrshrun_S, OpCodeSimdShImm.Create);
|
||||
SetA64("0x10111100>>>xxx100011xxxxxxxxxx", InstName.Sqrshrun_V, InstEmit.Sqrshrun_V, OpCodeSimdShImm.Create);
|
||||
SetA64("010111110>>>>xxx011101xxxxxxxxxx", InstName.Sqshl_Si, InstEmit.Sqshl_Si, OpCodeSimdShImm.Create);
|
||||
SetA64("0>001110<<1xxxxx010011xxxxxxxxxx", InstName.Sqshl_V, InstEmit.Sqshl_V, OpCodeSimdReg.Create);
|
||||
SetA64("0000111100>>>xxx011101xxxxxxxxxx", InstName.Sqshl_Vi, InstEmit.Sqshl_Vi, OpCodeSimdShImm.Create);
|
||||
SetA64("010011110>>>>xxx011101xxxxxxxxxx", InstName.Sqshl_Vi, InstEmit.Sqshl_Vi, OpCodeSimdShImm.Create);
|
||||
SetA64("0101111100>>>xxx100101xxxxxxxxxx", InstName.Sqshrn_S, InstEmit.Sqshrn_S, OpCodeSimdShImm.Create);
|
||||
SetA64("0x00111100>>>xxx100101xxxxxxxxxx", InstName.Sqshrn_V, InstEmit.Sqshrn_V, OpCodeSimdShImm.Create);
|
||||
SetA64("0111111100>>>xxx100001xxxxxxxxxx", InstName.Sqshrun_S, InstEmit.Sqshrun_S, OpCodeSimdShImm.Create);
|
||||
@ -872,6 +875,7 @@ namespace ARMeilleure.Decoders
|
||||
SetVfp("<<<<11100x10xxxxxxxx101xx1x0xxxx", InstName.Vnmul, InstEmit32.Vnmul_S, OpCode32SimdRegS.Create, OpCode32SimdRegS.CreateT32);
|
||||
SetVfp("111111101x1110xxxxxx101x01x0xxxx", InstName.Vrint, InstEmit32.Vrint_RM, OpCode32SimdS.Create, OpCode32SimdS.CreateT32);
|
||||
SetVfp("<<<<11101x110110xxxx101x11x0xxxx", InstName.Vrint, InstEmit32.Vrint_Z, OpCode32SimdS.Create, OpCode32SimdS.CreateT32);
|
||||
SetVfp("<<<<11101x110110xxxx101x01x0xxxx", InstName.Vrintr, InstEmit32.Vrintr_S, OpCode32SimdS.Create, OpCode32SimdS.CreateT32);
|
||||
SetVfp("<<<<11101x110111xxxx101x01x0xxxx", InstName.Vrintx, InstEmit32.Vrintx_S, OpCode32SimdS.Create, OpCode32SimdS.CreateT32);
|
||||
SetVfp("<<<<11101x110001xxxx101x11x0xxxx", InstName.Vsqrt, InstEmit32.Vsqrt_S, OpCode32SimdS.Create, OpCode32SimdS.CreateT32);
|
||||
SetVfp("111111100xxxxxxxxxxx101xx0x0xxxx", InstName.Vsel, InstEmit32.Vsel, OpCode32SimdSel.Create, OpCode32SimdSel.CreateT32);
|
||||
@ -992,6 +996,7 @@ namespace ARMeilleure.Decoders
|
||||
SetAsimd("1111001x1x000xxxxxxx<<x10x01xxxx", InstName.Vorr, InstEmit32.Vorr_II, OpCode32SimdImm.Create, OpCode32SimdImm.CreateT32);
|
||||
SetAsimd("111100100x<<xxxxxxxx1011x0x1xxxx", InstName.Vpadd, InstEmit32.Vpadd_I, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);
|
||||
SetAsimd("111100110x00xxxxxxxx1101x0x0xxxx", InstName.Vpadd, InstEmit32.Vpadd_V, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);
|
||||
SetAsimd("111100111x11<<00xxxx0110xxx0xxxx", InstName.Vpadal, InstEmit32.Vpadal, OpCode32SimdCmpZ.Create, OpCode32SimdCmpZ.CreateT32);
|
||||
SetAsimd("111100111x11<<00xxxx0010xxx0xxxx", InstName.Vpaddl, InstEmit32.Vpaddl, OpCode32SimdCmpZ.Create, OpCode32SimdCmpZ.CreateT32);
|
||||
SetAsimd("1111001x0x<<xxxxxxxx1010x0x0xxxx", InstName.Vpmax, InstEmit32.Vpmax_I, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);
|
||||
SetAsimd("111100110x00xxxxxxxx1111x0x0xxxx", InstName.Vpmax, InstEmit32.Vpmax_V, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);
|
||||
|
@ -1115,6 +1115,13 @@ namespace ARMeilleure.Instructions
|
||||
}
|
||||
}
|
||||
|
||||
public static void Vpadal(ArmEmitterContext context)
|
||||
{
|
||||
OpCode32Simd op = (OpCode32Simd)context.CurrOp;
|
||||
|
||||
EmitVectorPairwiseTernaryLongOpI32(context, (op1, op2, op3) => context.Add(context.Add(op1, op2), op3), op.Opc != 1);
|
||||
}
|
||||
|
||||
public static void Vpaddl(ArmEmitterContext context)
|
||||
{
|
||||
OpCode32Simd op = (OpCode32Simd)context.CurrOp;
|
||||
|
@ -578,6 +578,22 @@ namespace ARMeilleure.Instructions
|
||||
}
|
||||
}
|
||||
|
||||
// VRINTR (floating-point).
|
||||
public static void Vrintr_S(ArmEmitterContext context)
|
||||
{
|
||||
if (Optimizations.UseAdvSimd)
|
||||
{
|
||||
InstEmitSimdHelper32Arm64.EmitScalarUnaryOpF32(context, Intrinsic.Arm64FrintiS);
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitScalarUnaryOpF32(context, (op1) =>
|
||||
{
|
||||
return EmitRoundByRMode(context, op1);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// VRINTZ (floating-point).
|
||||
public static void Vrint_Z(ArmEmitterContext context)
|
||||
{
|
||||
|
@ -673,6 +673,35 @@ namespace ARMeilleure.Instructions
|
||||
context.Copy(GetVecA32(op.Qd), res);
|
||||
}
|
||||
|
||||
public static void EmitVectorPairwiseTernaryLongOpI32(ArmEmitterContext context, Func3I emit, bool signed)
|
||||
{
|
||||
OpCode32Simd op = (OpCode32Simd)context.CurrOp;
|
||||
|
||||
int elems = op.GetBytesCount() >> op.Size;
|
||||
int pairs = elems >> 1;
|
||||
|
||||
Operand res = GetVecA32(op.Qd);
|
||||
|
||||
for (int index = 0; index < pairs; index++)
|
||||
{
|
||||
int pairIndex = index * 2;
|
||||
Operand m1 = EmitVectorExtract32(context, op.Qm, op.Im + pairIndex, op.Size, signed);
|
||||
Operand m2 = EmitVectorExtract32(context, op.Qm, op.Im + pairIndex + 1, op.Size, signed);
|
||||
|
||||
if (op.Size == 2)
|
||||
{
|
||||
m1 = signed ? context.SignExtend32(OperandType.I64, m1) : context.ZeroExtend32(OperandType.I64, m1);
|
||||
m2 = signed ? context.SignExtend32(OperandType.I64, m2) : context.ZeroExtend32(OperandType.I64, m2);
|
||||
}
|
||||
|
||||
Operand d1 = EmitVectorExtract32(context, op.Qd, op.Id + index, op.Size + 1, signed);
|
||||
|
||||
res = EmitVectorInsert(context, res, emit(m1, m2, d1), op.Id + index, op.Size + 1);
|
||||
}
|
||||
|
||||
context.Copy(GetVecA32(op.Qd), res);
|
||||
}
|
||||
|
||||
// Narrow
|
||||
|
||||
public static void EmitVectorUnaryNarrowOp32(ArmEmitterContext context, Func1I emit, bool signed = false)
|
||||
|
@ -116,7 +116,7 @@ namespace ARMeilleure.Instructions
|
||||
}
|
||||
else if (shift >= eSize)
|
||||
{
|
||||
if ((op.RegisterSize == RegisterSize.Simd64))
|
||||
if (op.RegisterSize == RegisterSize.Simd64)
|
||||
{
|
||||
Operand res = context.VectorZeroUpper64(GetVec(op.Rd));
|
||||
|
||||
@ -359,6 +359,16 @@ namespace ARMeilleure.Instructions
|
||||
}
|
||||
}
|
||||
|
||||
public static void Sqshl_Si(ArmEmitterContext context)
|
||||
{
|
||||
EmitShlImmOp(context, signedDst: true, ShlRegFlags.Signed | ShlRegFlags.Scalar | ShlRegFlags.Saturating);
|
||||
}
|
||||
|
||||
public static void Sqshl_Vi(ArmEmitterContext context)
|
||||
{
|
||||
EmitShlImmOp(context, signedDst: true, ShlRegFlags.Signed | ShlRegFlags.Saturating);
|
||||
}
|
||||
|
||||
public static void Sqshrn_S(ArmEmitterContext context)
|
||||
{
|
||||
if (Optimizations.UseAdvSimd)
|
||||
@ -1593,6 +1603,99 @@ namespace ARMeilleure.Instructions
|
||||
Saturating = 1 << 3,
|
||||
}
|
||||
|
||||
private static void EmitShlImmOp(ArmEmitterContext context, bool signedDst, ShlRegFlags flags = ShlRegFlags.None)
|
||||
{
|
||||
bool scalar = flags.HasFlag(ShlRegFlags.Scalar);
|
||||
bool signed = flags.HasFlag(ShlRegFlags.Signed);
|
||||
bool saturating = flags.HasFlag(ShlRegFlags.Saturating);
|
||||
|
||||
OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp;
|
||||
|
||||
Operand res = context.VectorZero();
|
||||
|
||||
int elems = !scalar ? op.GetBytesCount() >> op.Size : 1;
|
||||
|
||||
for (int index = 0; index < elems; index++)
|
||||
{
|
||||
Operand ne = EmitVectorExtract(context, op.Rn, index, op.Size, signed);
|
||||
|
||||
Operand e = !saturating
|
||||
? EmitShlImm(context, ne, GetImmShl(op), op.Size)
|
||||
: EmitShlImmSatQ(context, ne, GetImmShl(op), op.Size, signed, signedDst);
|
||||
|
||||
res = EmitVectorInsert(context, res, e, index, op.Size);
|
||||
}
|
||||
|
||||
context.Copy(GetVec(op.Rd), res);
|
||||
}
|
||||
|
||||
private static Operand EmitShlImm(ArmEmitterContext context, Operand op, int shiftLsB, int size)
|
||||
{
|
||||
int eSize = 8 << size;
|
||||
|
||||
Debug.Assert(op.Type == OperandType.I64);
|
||||
Debug.Assert(eSize == 8 || eSize == 16 || eSize == 32 || eSize == 64);
|
||||
|
||||
Operand res = context.AllocateLocal(OperandType.I64);
|
||||
|
||||
if (shiftLsB >= eSize)
|
||||
{
|
||||
Operand shl = context.ShiftLeft(op, Const(shiftLsB));
|
||||
context.Copy(res, shl);
|
||||
}
|
||||
else
|
||||
{
|
||||
Operand zeroL = Const(0L);
|
||||
context.Copy(res, zeroL);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
private static Operand EmitShlImmSatQ(ArmEmitterContext context, Operand op, int shiftLsB, int size, bool signedSrc, bool signedDst)
|
||||
{
|
||||
int eSize = 8 << size;
|
||||
|
||||
Debug.Assert(op.Type == OperandType.I64);
|
||||
Debug.Assert(eSize == 8 || eSize == 16 || eSize == 32 || eSize == 64);
|
||||
|
||||
Operand lblEnd = Label();
|
||||
|
||||
Operand res = context.Copy(context.AllocateLocal(OperandType.I64), op);
|
||||
|
||||
if (shiftLsB >= eSize)
|
||||
{
|
||||
context.Copy(res, signedSrc
|
||||
? EmitSignedSignSatQ(context, op, size)
|
||||
: EmitUnsignedSignSatQ(context, op, size));
|
||||
}
|
||||
else
|
||||
{
|
||||
Operand shl = context.ShiftLeft(op, Const(shiftLsB));
|
||||
if (eSize == 64)
|
||||
{
|
||||
Operand sarOrShr = signedSrc
|
||||
? context.ShiftRightSI(shl, Const(shiftLsB))
|
||||
: context.ShiftRightUI(shl, Const(shiftLsB));
|
||||
context.Copy(res, shl);
|
||||
context.BranchIf(lblEnd, sarOrShr, op, Comparison.Equal);
|
||||
context.Copy(res, signedSrc
|
||||
? EmitSignedSignSatQ(context, op, size)
|
||||
: EmitUnsignedSignSatQ(context, op, size));
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Copy(res, signedSrc
|
||||
? EmitSignedSrcSatQ(context, shl, size, signedDst)
|
||||
: EmitUnsignedSrcSatQ(context, shl, size, signedDst));
|
||||
}
|
||||
}
|
||||
|
||||
context.MarkLabel(lblEnd);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
private static void EmitShlRegOp(ArmEmitterContext context, ShlRegFlags flags = ShlRegFlags.None)
|
||||
{
|
||||
bool scalar = flags.HasFlag(ShlRegFlags.Scalar);
|
||||
|
@ -384,7 +384,9 @@ namespace ARMeilleure.Instructions
|
||||
Sqrshrn_V,
|
||||
Sqrshrun_S,
|
||||
Sqrshrun_V,
|
||||
Sqshl_Si,
|
||||
Sqshl_V,
|
||||
Sqshl_Vi,
|
||||
Sqshrn_S,
|
||||
Sqshrn_V,
|
||||
Sqshrun_S,
|
||||
@ -635,6 +637,7 @@ namespace ARMeilleure.Instructions
|
||||
Vorn,
|
||||
Vorr,
|
||||
Vpadd,
|
||||
Vpadal,
|
||||
Vpaddl,
|
||||
Vpmax,
|
||||
Vpmin,
|
||||
@ -654,6 +657,7 @@ namespace ARMeilleure.Instructions
|
||||
Vrintm,
|
||||
Vrintn,
|
||||
Vrintp,
|
||||
Vrintr,
|
||||
Vrintx,
|
||||
Vrshr,
|
||||
Vrshrn,
|
||||
|
@ -20,6 +20,25 @@ namespace Ryujinx.Audio.Backends.OpenAL
|
||||
private bool _stillRunning;
|
||||
private readonly Thread _updaterThread;
|
||||
|
||||
private float _volume;
|
||||
|
||||
public float Volume
|
||||
{
|
||||
get
|
||||
{
|
||||
return _volume;
|
||||
}
|
||||
set
|
||||
{
|
||||
_volume = value;
|
||||
|
||||
foreach (OpenALHardwareDeviceSession session in _sessions.Keys)
|
||||
{
|
||||
session.UpdateMasterVolume(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public OpenALHardwareDeviceDriver()
|
||||
{
|
||||
_device = ALC.OpenDevice("");
|
||||
@ -34,6 +53,8 @@ namespace Ryujinx.Audio.Backends.OpenAL
|
||||
Name = "HardwareDeviceDriver.OpenAL",
|
||||
};
|
||||
|
||||
_volume = 1f;
|
||||
|
||||
_updaterThread.Start();
|
||||
}
|
||||
|
||||
@ -52,7 +73,7 @@ namespace Ryujinx.Audio.Backends.OpenAL
|
||||
}
|
||||
}
|
||||
|
||||
public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount, float volume)
|
||||
public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount)
|
||||
{
|
||||
if (channelCount == 0)
|
||||
{
|
||||
@ -73,7 +94,7 @@ namespace Ryujinx.Audio.Backends.OpenAL
|
||||
throw new ArgumentException($"{channelCount}");
|
||||
}
|
||||
|
||||
OpenALHardwareDeviceSession session = new(this, memoryManager, sampleFormat, sampleRate, channelCount, volume);
|
||||
OpenALHardwareDeviceSession session = new(this, memoryManager, sampleFormat, sampleRate, channelCount);
|
||||
|
||||
_sessions.TryAdd(session, 0);
|
||||
|
||||
|
@ -16,10 +16,11 @@ namespace Ryujinx.Audio.Backends.OpenAL
|
||||
private bool _isActive;
|
||||
private readonly Queue<OpenALAudioBuffer> _queuedBuffers;
|
||||
private ulong _playedSampleCount;
|
||||
private float _volume;
|
||||
|
||||
private readonly object _lock = new();
|
||||
|
||||
public OpenALHardwareDeviceSession(OpenALHardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount, float requestedVolume) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount)
|
||||
public OpenALHardwareDeviceSession(OpenALHardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount)
|
||||
{
|
||||
_driver = driver;
|
||||
_queuedBuffers = new Queue<OpenALAudioBuffer>();
|
||||
@ -27,7 +28,7 @@ namespace Ryujinx.Audio.Backends.OpenAL
|
||||
_targetFormat = GetALFormat();
|
||||
_isActive = false;
|
||||
_playedSampleCount = 0;
|
||||
SetVolume(requestedVolume);
|
||||
SetVolume(1f);
|
||||
}
|
||||
|
||||
private ALFormat GetALFormat()
|
||||
@ -85,17 +86,22 @@ namespace Ryujinx.Audio.Backends.OpenAL
|
||||
|
||||
public override void SetVolume(float volume)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
AL.Source(_sourceId, ALSourcef.Gain, volume);
|
||||
}
|
||||
_volume = volume;
|
||||
|
||||
UpdateMasterVolume(_driver.Volume);
|
||||
}
|
||||
|
||||
public override float GetVolume()
|
||||
{
|
||||
AL.GetSource(_sourceId, ALSourcef.Gain, out float volume);
|
||||
return _volume;
|
||||
}
|
||||
|
||||
return volume;
|
||||
public void UpdateMasterVolume(float newVolume)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
AL.Source(_sourceId, ALSourcef.Gain, newVolume * _volume);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Start()
|
||||
|
@ -20,6 +20,8 @@ namespace Ryujinx.Audio.Backends.SDL2
|
||||
|
||||
private readonly bool _supportSurroundConfiguration;
|
||||
|
||||
public float Volume { get; set; }
|
||||
|
||||
// TODO: Add this to SDL2-CS
|
||||
// NOTE: We use a DllImport here because of marshaling issue for spec.
|
||||
#pragma warning disable SYSLIB1054
|
||||
@ -48,6 +50,8 @@ namespace Ryujinx.Audio.Backends.SDL2
|
||||
{
|
||||
_supportSurroundConfiguration = spec.channels >= 6;
|
||||
}
|
||||
|
||||
Volume = 1f;
|
||||
}
|
||||
|
||||
public static bool IsSupported => IsSupportedInternal();
|
||||
@ -74,7 +78,7 @@ namespace Ryujinx.Audio.Backends.SDL2
|
||||
return _pauseEvent;
|
||||
}
|
||||
|
||||
public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount, float volume)
|
||||
public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount)
|
||||
{
|
||||
if (channelCount == 0)
|
||||
{
|
||||
@ -91,7 +95,7 @@ namespace Ryujinx.Audio.Backends.SDL2
|
||||
throw new NotImplementedException("Input direction is currently not implemented on SDL2 backend!");
|
||||
}
|
||||
|
||||
SDL2HardwareDeviceSession session = new(this, memoryManager, sampleFormat, sampleRate, channelCount, volume);
|
||||
SDL2HardwareDeviceSession session = new(this, memoryManager, sampleFormat, sampleRate, channelCount);
|
||||
|
||||
_sessions.TryAdd(session, 0);
|
||||
|
||||
|
@ -26,7 +26,7 @@ namespace Ryujinx.Audio.Backends.SDL2
|
||||
private float _volume;
|
||||
private readonly ushort _nativeSampleFormat;
|
||||
|
||||
public SDL2HardwareDeviceSession(SDL2HardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount, float requestedVolume) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount)
|
||||
public SDL2HardwareDeviceSession(SDL2HardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount)
|
||||
{
|
||||
_driver = driver;
|
||||
_updateRequiredEvent = _driver.GetUpdateRequiredEvent();
|
||||
@ -37,7 +37,7 @@ namespace Ryujinx.Audio.Backends.SDL2
|
||||
_nativeSampleFormat = SDL2HardwareDeviceDriver.GetSDL2Format(RequestedSampleFormat);
|
||||
_sampleCount = uint.MaxValue;
|
||||
_started = false;
|
||||
_volume = requestedVolume;
|
||||
_volume = 1f;
|
||||
}
|
||||
|
||||
private void EnsureAudioStreamSetup(AudioBuffer buffer)
|
||||
@ -99,7 +99,7 @@ namespace Ryujinx.Audio.Backends.SDL2
|
||||
streamSpan.Clear();
|
||||
|
||||
// Apply volume to written data
|
||||
SDL_MixAudioFormat(stream, pStreamSrc, _nativeSampleFormat, (uint)samples.Length, (int)(_volume * SDL_MIX_MAXVOLUME));
|
||||
SDL_MixAudioFormat(stream, pStreamSrc, _nativeSampleFormat, (uint)samples.Length, (int)(_driver.Volume * _volume * SDL_MIX_MAXVOLUME));
|
||||
}
|
||||
|
||||
ulong sampleCount = GetSampleCount(samples.Length);
|
||||
|
@ -11,15 +11,15 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ContentWithTargetPath Include="Native\libsoundio\libs\libsoundio.dll" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'">
|
||||
<ContentWithTargetPath Include="Native\libsoundio\libs\libsoundio.dll" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'linux-arm64' AND '$(RuntimeIdentifier)' != 'osx-x64'">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<TargetPath>libsoundio.dll</TargetPath>
|
||||
</ContentWithTargetPath>
|
||||
<ContentWithTargetPath Include="Native\libsoundio\libs\libsoundio.dylib" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'win-x64'">
|
||||
<ContentWithTargetPath Include="Native\libsoundio\libs\libsoundio.dylib" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'linux-arm64' AND '$(RuntimeIdentifier)' != 'win-x64'">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<TargetPath>libsoundio.dylib</TargetPath>
|
||||
</ContentWithTargetPath>
|
||||
<ContentWithTargetPath Include="Native\libsoundio\libs\libsoundio.so" Condition="'$(RuntimeIdentifier)' != 'win-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'">
|
||||
<ContentWithTargetPath Include="Native\libsoundio\libs\libsoundio.so" Condition="'$(RuntimeIdentifier)' != 'win-x64' AND '$(RuntimeIdentifier)' != 'osx-x64' AND '$(RuntimeIdentifier)' != 'linux-arm64'">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<TargetPath>libsoundio.so</TargetPath>
|
||||
</ContentWithTargetPath>
|
||||
|
@ -19,6 +19,25 @@ namespace Ryujinx.Audio.Backends.SoundIo
|
||||
private readonly ConcurrentDictionary<SoundIoHardwareDeviceSession, byte> _sessions;
|
||||
private int _disposeState;
|
||||
|
||||
private float _volume = 1f;
|
||||
|
||||
public float Volume
|
||||
{
|
||||
get
|
||||
{
|
||||
return _volume;
|
||||
}
|
||||
set
|
||||
{
|
||||
_volume = value;
|
||||
|
||||
foreach (SoundIoHardwareDeviceSession session in _sessions.Keys)
|
||||
{
|
||||
session.UpdateMasterVolume(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public SoundIoHardwareDeviceDriver()
|
||||
{
|
||||
_audioContext = SoundIoContext.Create();
|
||||
@ -122,7 +141,7 @@ namespace Ryujinx.Audio.Backends.SoundIo
|
||||
return _pauseEvent;
|
||||
}
|
||||
|
||||
public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount, float volume)
|
||||
public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount)
|
||||
{
|
||||
if (channelCount == 0)
|
||||
{
|
||||
@ -134,14 +153,12 @@ namespace Ryujinx.Audio.Backends.SoundIo
|
||||
sampleRate = Constants.TargetSampleRate;
|
||||
}
|
||||
|
||||
volume = Math.Clamp(volume, 0, 1);
|
||||
|
||||
if (direction != Direction.Output)
|
||||
{
|
||||
throw new NotImplementedException("Input direction is currently not implemented on SoundIO backend!");
|
||||
}
|
||||
|
||||
SoundIoHardwareDeviceSession session = new(this, memoryManager, sampleFormat, sampleRate, channelCount, volume);
|
||||
SoundIoHardwareDeviceSession session = new(this, memoryManager, sampleFormat, sampleRate, channelCount);
|
||||
|
||||
_sessions.TryAdd(session, 0);
|
||||
|
||||
|
@ -18,16 +18,18 @@ namespace Ryujinx.Audio.Backends.SoundIo
|
||||
private readonly DynamicRingBuffer _ringBuffer;
|
||||
private ulong _playedSampleCount;
|
||||
private readonly ManualResetEvent _updateRequiredEvent;
|
||||
private float _volume;
|
||||
private int _disposeState;
|
||||
|
||||
public SoundIoHardwareDeviceSession(SoundIoHardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount, float requestedVolume) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount)
|
||||
public SoundIoHardwareDeviceSession(SoundIoHardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount)
|
||||
{
|
||||
_driver = driver;
|
||||
_updateRequiredEvent = _driver.GetUpdateRequiredEvent();
|
||||
_queuedBuffers = new ConcurrentQueue<SoundIoAudioBuffer>();
|
||||
_ringBuffer = new DynamicRingBuffer();
|
||||
_volume = 1f;
|
||||
|
||||
SetupOutputStream(requestedVolume);
|
||||
SetupOutputStream(driver.Volume);
|
||||
}
|
||||
|
||||
private void SetupOutputStream(float requestedVolume)
|
||||
@ -47,7 +49,7 @@ namespace Ryujinx.Audio.Backends.SoundIo
|
||||
|
||||
public override float GetVolume()
|
||||
{
|
||||
return _outputStream.Volume;
|
||||
return _volume;
|
||||
}
|
||||
|
||||
public override void PrepareToClose() { }
|
||||
@ -63,7 +65,14 @@ namespace Ryujinx.Audio.Backends.SoundIo
|
||||
|
||||
public override void SetVolume(float volume)
|
||||
{
|
||||
_outputStream.SetVolume(volume);
|
||||
_volume = volume;
|
||||
|
||||
_outputStream.SetVolume(_driver.Volume * volume);
|
||||
}
|
||||
|
||||
public void UpdateMasterVolume(float newVolume)
|
||||
{
|
||||
_outputStream.SetVolume(newVolume * _volume);
|
||||
}
|
||||
|
||||
public override void Start()
|
||||
|
@ -16,6 +16,12 @@ namespace Ryujinx.Audio.Backends.CompatLayer
|
||||
|
||||
public static bool IsSupported => true;
|
||||
|
||||
public float Volume
|
||||
{
|
||||
get => _realDriver.Volume;
|
||||
set => _realDriver.Volume = value;
|
||||
}
|
||||
|
||||
public CompatLayerHardwareDeviceDriver(IHardwareDeviceDriver realDevice)
|
||||
{
|
||||
_realDriver = realDevice;
|
||||
@ -90,7 +96,7 @@ namespace Ryujinx.Audio.Backends.CompatLayer
|
||||
throw new ArgumentException("No valid sample format configuration found!");
|
||||
}
|
||||
|
||||
public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount, float volume)
|
||||
public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount)
|
||||
{
|
||||
if (channelCount == 0)
|
||||
{
|
||||
@ -102,8 +108,6 @@ namespace Ryujinx.Audio.Backends.CompatLayer
|
||||
sampleRate = Constants.TargetSampleRate;
|
||||
}
|
||||
|
||||
volume = Math.Clamp(volume, 0, 1);
|
||||
|
||||
if (!_realDriver.SupportsDirection(direction))
|
||||
{
|
||||
if (direction == Direction.Input)
|
||||
@ -119,7 +123,7 @@ namespace Ryujinx.Audio.Backends.CompatLayer
|
||||
SampleFormat hardwareSampleFormat = SelectHardwareSampleFormat(sampleFormat);
|
||||
uint hardwareChannelCount = SelectHardwareChannelCount(channelCount);
|
||||
|
||||
IHardwareDeviceSession realSession = _realDriver.OpenDeviceSession(direction, memoryManager, hardwareSampleFormat, sampleRate, hardwareChannelCount, volume);
|
||||
IHardwareDeviceSession realSession = _realDriver.OpenDeviceSession(direction, memoryManager, hardwareSampleFormat, sampleRate, hardwareChannelCount);
|
||||
|
||||
if (hardwareChannelCount == channelCount && hardwareSampleFormat == sampleFormat)
|
||||
{
|
||||
|
@ -14,13 +14,17 @@ namespace Ryujinx.Audio.Backends.Dummy
|
||||
|
||||
public static bool IsSupported => true;
|
||||
|
||||
public float Volume { get; set; }
|
||||
|
||||
public DummyHardwareDeviceDriver()
|
||||
{
|
||||
_updateRequiredEvent = new ManualResetEvent(false);
|
||||
_pauseEvent = new ManualResetEvent(true);
|
||||
|
||||
Volume = 1f;
|
||||
}
|
||||
|
||||
public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount, float volume)
|
||||
public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount)
|
||||
{
|
||||
if (sampleRate == 0)
|
||||
{
|
||||
@ -34,7 +38,7 @@ namespace Ryujinx.Audio.Backends.Dummy
|
||||
|
||||
if (direction == Direction.Output)
|
||||
{
|
||||
return new DummyHardwareDeviceSessionOutput(this, memoryManager, sampleFormat, sampleRate, channelCount, volume);
|
||||
return new DummyHardwareDeviceSessionOutput(this, memoryManager, sampleFormat, sampleRate, channelCount);
|
||||
}
|
||||
|
||||
return new DummyHardwareDeviceSessionInput(this, memoryManager);
|
||||
|
@ -13,9 +13,9 @@ namespace Ryujinx.Audio.Backends.Dummy
|
||||
|
||||
private ulong _playedSampleCount;
|
||||
|
||||
public DummyHardwareDeviceSessionOutput(IHardwareDeviceDriver manager, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount, float requestedVolume) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount)
|
||||
public DummyHardwareDeviceSessionOutput(IHardwareDeviceDriver manager, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount)
|
||||
{
|
||||
_volume = requestedVolume;
|
||||
_volume = 1f;
|
||||
_manager = manager;
|
||||
}
|
||||
|
||||
|
@ -166,7 +166,6 @@ namespace Ryujinx.Audio.Input
|
||||
/// </summary>
|
||||
/// <param name="filtered">If true, filter disconnected devices</param>
|
||||
/// <returns>The list of all audio inputs name</returns>
|
||||
#pragma warning disable CA1822 // Mark member as static
|
||||
public string[] ListAudioIns(bool filtered)
|
||||
{
|
||||
if (filtered)
|
||||
@ -176,7 +175,6 @@ namespace Ryujinx.Audio.Input
|
||||
|
||||
return new[] { Constants.DefaultDeviceInputName };
|
||||
}
|
||||
#pragma warning restore CA1822
|
||||
|
||||
/// <summary>
|
||||
/// Open a new <see cref="AudioInputSystem"/>.
|
||||
@ -188,8 +186,6 @@ namespace Ryujinx.Audio.Input
|
||||
/// <param name="inputDeviceName">The input device name wanted by the user</param>
|
||||
/// <param name="sampleFormat">The sample format to use</param>
|
||||
/// <param name="parameter">The user configuration</param>
|
||||
/// <param name="appletResourceUserId">The applet resource user id of the application</param>
|
||||
/// <param name="processHandle">The process handle of the application</param>
|
||||
/// <returns>A <see cref="ResultCode"/> reporting an error or a success</returns>
|
||||
public ResultCode OpenAudioIn(out string outputDeviceName,
|
||||
out AudioOutputConfiguration outputConfiguration,
|
||||
@ -197,9 +193,7 @@ namespace Ryujinx.Audio.Input
|
||||
IVirtualMemoryManager memoryManager,
|
||||
string inputDeviceName,
|
||||
SampleFormat sampleFormat,
|
||||
ref AudioInputConfiguration parameter,
|
||||
ulong appletResourceUserId,
|
||||
uint processHandle)
|
||||
ref AudioInputConfiguration parameter)
|
||||
{
|
||||
int sessionId = AcquireSessionId();
|
||||
|
||||
|
@ -13,9 +13,9 @@ namespace Ryujinx.Audio.Integration
|
||||
|
||||
private readonly byte[] _buffer;
|
||||
|
||||
public HardwareDeviceImpl(IHardwareDeviceDriver deviceDriver, uint channelCount, uint sampleRate, float volume)
|
||||
public HardwareDeviceImpl(IHardwareDeviceDriver deviceDriver, uint channelCount, uint sampleRate)
|
||||
{
|
||||
_session = deviceDriver.OpenDeviceSession(IHardwareDeviceDriver.Direction.Output, null, SampleFormat.PcmInt16, sampleRate, channelCount, volume);
|
||||
_session = deviceDriver.OpenDeviceSession(IHardwareDeviceDriver.Direction.Output, null, SampleFormat.PcmInt16, sampleRate, channelCount);
|
||||
_channelCount = channelCount;
|
||||
_sampleRate = sampleRate;
|
||||
_currentBufferTag = 0;
|
||||
|
@ -16,7 +16,9 @@ namespace Ryujinx.Audio.Integration
|
||||
Output,
|
||||
}
|
||||
|
||||
IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount, float volume = 1f);
|
||||
float Volume { get; set; }
|
||||
|
||||
IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount);
|
||||
|
||||
ManualResetEvent GetUpdateRequiredEvent();
|
||||
ManualResetEvent GetPauseEvent();
|
||||
|
@ -165,12 +165,10 @@ namespace Ryujinx.Audio.Output
|
||||
/// Get the list of all audio outputs name.
|
||||
/// </summary>
|
||||
/// <returns>The list of all audio outputs name</returns>
|
||||
#pragma warning disable CA1822 // Mark member as static
|
||||
public string[] ListAudioOuts()
|
||||
{
|
||||
return new[] { Constants.DefaultDeviceOutputName };
|
||||
}
|
||||
#pragma warning restore CA1822
|
||||
|
||||
/// <summary>
|
||||
/// Open a new <see cref="AudioOutputSystem"/>.
|
||||
@ -182,9 +180,6 @@ namespace Ryujinx.Audio.Output
|
||||
/// <param name="inputDeviceName">The input device name wanted by the user</param>
|
||||
/// <param name="sampleFormat">The sample format to use</param>
|
||||
/// <param name="parameter">The user configuration</param>
|
||||
/// <param name="appletResourceUserId">The applet resource user id of the application</param>
|
||||
/// <param name="processHandle">The process handle of the application</param>
|
||||
/// <param name="volume">The volume level to request</param>
|
||||
/// <returns>A <see cref="ResultCode"/> reporting an error or a success</returns>
|
||||
public ResultCode OpenAudioOut(out string outputDeviceName,
|
||||
out AudioOutputConfiguration outputConfiguration,
|
||||
@ -192,16 +187,13 @@ namespace Ryujinx.Audio.Output
|
||||
IVirtualMemoryManager memoryManager,
|
||||
string inputDeviceName,
|
||||
SampleFormat sampleFormat,
|
||||
ref AudioInputConfiguration parameter,
|
||||
ulong appletResourceUserId,
|
||||
uint processHandle,
|
||||
float volume)
|
||||
ref AudioInputConfiguration parameter)
|
||||
{
|
||||
int sessionId = AcquireSessionId();
|
||||
|
||||
_sessionsBufferEvents[sessionId].Clear();
|
||||
|
||||
IHardwareDeviceSession deviceSession = _deviceDriver.OpenDeviceSession(IHardwareDeviceDriver.Direction.Output, memoryManager, sampleFormat, parameter.SampleRate, parameter.ChannelCount, volume);
|
||||
IHardwareDeviceSession deviceSession = _deviceDriver.OpenDeviceSession(IHardwareDeviceDriver.Direction.Output, memoryManager, sampleFormat, parameter.SampleRate, parameter.ChannelCount);
|
||||
|
||||
AudioOutputSystem audioOut = new(this, _lock, deviceSession, _sessionsBufferEvents[sessionId]);
|
||||
|
||||
@ -234,41 +226,6 @@ namespace Ryujinx.Audio.Output
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the volume for all output devices.
|
||||
/// </summary>
|
||||
/// <param name="volume">The volume to set.</param>
|
||||
public void SetVolume(float volume)
|
||||
{
|
||||
if (_sessions != null)
|
||||
{
|
||||
foreach (AudioOutputSystem session in _sessions)
|
||||
{
|
||||
session?.SetVolume(volume);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the volume for all output devices.
|
||||
/// </summary>
|
||||
/// <returns>A float indicating the volume level.</returns>
|
||||
public float GetVolume()
|
||||
{
|
||||
if (_sessions != null)
|
||||
{
|
||||
foreach (AudioOutputSystem session in _sessions)
|
||||
{
|
||||
if (session != null)
|
||||
{
|
||||
return session.GetVolume();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
GC.SuppressFinalize(this);
|
||||
|
@ -45,7 +45,6 @@ namespace Ryujinx.Audio.Renderer.Dsp
|
||||
_event = new ManualResetEvent(false);
|
||||
}
|
||||
|
||||
#pragma warning disable IDE0051 // Remove unused private member
|
||||
private static uint GetHardwareChannelCount(IHardwareDeviceDriver deviceDriver)
|
||||
{
|
||||
// Get the real device driver (In case the compat layer is on top of it).
|
||||
@ -59,9 +58,8 @@ namespace Ryujinx.Audio.Renderer.Dsp
|
||||
// NOTE: We default to stereo as this will get downmixed to mono by the compat layer if it's not compatible.
|
||||
return 2;
|
||||
}
|
||||
#pragma warning restore IDE0051
|
||||
|
||||
public void Start(IHardwareDeviceDriver deviceDriver, float volume)
|
||||
public void Start(IHardwareDeviceDriver deviceDriver)
|
||||
{
|
||||
OutputDevices = new IHardwareDevice[Constants.AudioRendererSessionCountMax];
|
||||
|
||||
@ -70,7 +68,7 @@ namespace Ryujinx.Audio.Renderer.Dsp
|
||||
for (int i = 0; i < OutputDevices.Length; i++)
|
||||
{
|
||||
// TODO: Don't hardcode sample rate.
|
||||
OutputDevices[i] = new HardwareDeviceImpl(deviceDriver, channelCount, Constants.TargetSampleRate, volume);
|
||||
OutputDevices[i] = new HardwareDeviceImpl(deviceDriver, channelCount, Constants.TargetSampleRate);
|
||||
}
|
||||
|
||||
_mailbox = new Mailbox<MailboxMessage>();
|
||||
@ -231,33 +229,6 @@ namespace Ryujinx.Audio.Renderer.Dsp
|
||||
_mailbox.SendResponse(MailboxMessage.Stop);
|
||||
}
|
||||
|
||||
public float GetVolume()
|
||||
{
|
||||
if (OutputDevices != null)
|
||||
{
|
||||
foreach (IHardwareDevice outputDevice in OutputDevices)
|
||||
{
|
||||
if (outputDevice != null)
|
||||
{
|
||||
return outputDevice.GetVolume();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0f;
|
||||
}
|
||||
|
||||
public void SetVolume(float volume)
|
||||
{
|
||||
if (OutputDevices != null)
|
||||
{
|
||||
foreach (IHardwareDevice outputDevice in OutputDevices)
|
||||
{
|
||||
outputDevice?.SetVolume(volume);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
GC.SuppressFinalize(this);
|
||||
@ -269,6 +240,7 @@ namespace Ryujinx.Audio.Renderer.Dsp
|
||||
if (disposing)
|
||||
{
|
||||
_event.Dispose();
|
||||
_mailbox?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -177,12 +177,12 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||
/// <summary>
|
||||
/// Start the <see cref="AudioProcessor"/> and worker thread.
|
||||
/// </summary>
|
||||
private void StartLocked(float volume)
|
||||
private void StartLocked()
|
||||
{
|
||||
_isRunning = true;
|
||||
|
||||
// TODO: virtual device mapping (IAudioDevice)
|
||||
Processor.Start(_deviceDriver, volume);
|
||||
Processor.Start(_deviceDriver);
|
||||
|
||||
_workerThread = new Thread(SendCommands)
|
||||
{
|
||||
@ -254,7 +254,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||
/// Register a new <see cref="AudioRenderSystem"/>.
|
||||
/// </summary>
|
||||
/// <param name="renderer">The <see cref="AudioRenderSystem"/> to register.</param>
|
||||
private void Register(AudioRenderSystem renderer, float volume)
|
||||
private void Register(AudioRenderSystem renderer)
|
||||
{
|
||||
lock (_sessionLock)
|
||||
{
|
||||
@ -265,7 +265,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||
{
|
||||
if (!_isRunning)
|
||||
{
|
||||
StartLocked(volume);
|
||||
StartLocked();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -312,8 +312,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||
ulong appletResourceUserId,
|
||||
ulong workBufferAddress,
|
||||
ulong workBufferSize,
|
||||
uint processHandle,
|
||||
float volume)
|
||||
uint processHandle)
|
||||
{
|
||||
int sessionId = AcquireSessionId();
|
||||
|
||||
@ -338,7 +337,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||
{
|
||||
renderer = audioRenderer;
|
||||
|
||||
Register(renderer, volume);
|
||||
Register(renderer);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -350,21 +349,6 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||
return result;
|
||||
}
|
||||
|
||||
public float GetVolume()
|
||||
{
|
||||
if (Processor != null)
|
||||
{
|
||||
return Processor.GetVolume();
|
||||
}
|
||||
|
||||
return 0f;
|
||||
}
|
||||
|
||||
public void SetVolume(float volume)
|
||||
{
|
||||
Processor?.SetVolume(volume);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
GC.SuppressFinalize(this);
|
||||
|
@ -1,777 +0,0 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Threading;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using ICSharpCode.SharpZipLib.GZip;
|
||||
using ICSharpCode.SharpZipLib.Tar;
|
||||
using ICSharpCode.SharpZipLib.Zip;
|
||||
using Ryujinx.Ava;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.UI.Helpers;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common.Utilities;
|
||||
using Ryujinx.Ui.Common.Helper;
|
||||
using Ryujinx.Ui.Common.Models.Github;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ryujinx.Modules
|
||||
{
|
||||
internal static class Updater
|
||||
{
|
||||
private const string GitHubApiUrl = "https://api.github.com";
|
||||
private static readonly GithubReleasesJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
||||
|
||||
private static readonly string _homeDir = AppDomain.CurrentDomain.BaseDirectory;
|
||||
private static readonly string _updateDir = Path.Combine(Path.GetTempPath(), "Ryujinx", "update");
|
||||
private static readonly string _updatePublishDir = Path.Combine(_updateDir, "publish");
|
||||
private const int ConnectionCount = 4;
|
||||
|
||||
private static string _buildVer;
|
||||
private static string _platformExt;
|
||||
private static string _buildUrl;
|
||||
private static long _buildSize;
|
||||
private static bool _updateSuccessful;
|
||||
private static bool _running;
|
||||
|
||||
private static readonly string[] _windowsDependencyDirs = Array.Empty<string>();
|
||||
|
||||
public static async Task BeginParse(Window mainWindow, bool showVersionUpToDate)
|
||||
{
|
||||
if (_running)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_running = true;
|
||||
|
||||
// Detect current platform
|
||||
if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
_platformExt = "macos_universal.app.tar.gz";
|
||||
}
|
||||
else if (OperatingSystem.IsWindows())
|
||||
{
|
||||
_platformExt = "win_x64.zip";
|
||||
}
|
||||
else if (OperatingSystem.IsLinux())
|
||||
{
|
||||
_platformExt = "linux_x64.tar.gz";
|
||||
}
|
||||
|
||||
Version newVersion;
|
||||
Version currentVersion;
|
||||
|
||||
try
|
||||
{
|
||||
currentVersion = Version.Parse(Program.Version);
|
||||
}
|
||||
catch
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application, "Failed to convert the current Ryujinx version!");
|
||||
|
||||
await ContentDialogHelper.CreateWarningDialog(
|
||||
LocaleManager.Instance[LocaleKeys.DialogUpdaterConvertFailedMessage],
|
||||
LocaleManager.Instance[LocaleKeys.DialogUpdaterCancelUpdateMessage]);
|
||||
|
||||
_running = false;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Get latest version number from GitHub API
|
||||
try
|
||||
{
|
||||
using HttpClient jsonClient = ConstructHttpClient();
|
||||
|
||||
string buildInfoUrl = $"{GitHubApiUrl}/repos/{ReleaseInformation.ReleaseChannelOwner}/{ReleaseInformation.ReleaseChannelRepo}/releases/latest";
|
||||
string fetchedJson = await jsonClient.GetStringAsync(buildInfoUrl);
|
||||
var fetched = JsonHelper.Deserialize(fetchedJson, _serializerContext.GithubReleasesJsonResponse);
|
||||
_buildVer = fetched.Name;
|
||||
|
||||
foreach (var asset in fetched.Assets)
|
||||
{
|
||||
if (asset.Name.StartsWith("test-ava-ryujinx") && asset.Name.EndsWith(_platformExt))
|
||||
{
|
||||
_buildUrl = asset.BrowserDownloadUrl;
|
||||
|
||||
if (asset.State != "uploaded")
|
||||
{
|
||||
if (showVersionUpToDate)
|
||||
{
|
||||
await ContentDialogHelper.CreateUpdaterInfoDialog(
|
||||
LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage],
|
||||
"");
|
||||
}
|
||||
|
||||
_running = false;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If build not done, assume no new update are available.
|
||||
if (_buildUrl is null)
|
||||
{
|
||||
if (showVersionUpToDate)
|
||||
{
|
||||
await ContentDialogHelper.CreateUpdaterInfoDialog(
|
||||
LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage],
|
||||
"");
|
||||
}
|
||||
|
||||
_running = false;
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application, exception.Message);
|
||||
|
||||
await ContentDialogHelper.CreateErrorDialog(
|
||||
LocaleManager.Instance[LocaleKeys.DialogUpdaterFailedToGetVersionMessage]);
|
||||
|
||||
_running = false;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
newVersion = Version.Parse(_buildVer);
|
||||
}
|
||||
catch
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application, "Failed to convert the received Ryujinx version from Github!");
|
||||
|
||||
await ContentDialogHelper.CreateWarningDialog(
|
||||
LocaleManager.Instance[LocaleKeys.DialogUpdaterConvertFailedGithubMessage],
|
||||
LocaleManager.Instance[LocaleKeys.DialogUpdaterCancelUpdateMessage]);
|
||||
|
||||
_running = false;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (newVersion <= currentVersion)
|
||||
{
|
||||
if (showVersionUpToDate)
|
||||
{
|
||||
await ContentDialogHelper.CreateUpdaterInfoDialog(
|
||||
LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage],
|
||||
"");
|
||||
}
|
||||
|
||||
_running = false;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Fetch build size information to learn chunk sizes.
|
||||
using HttpClient buildSizeClient = ConstructHttpClient();
|
||||
try
|
||||
{
|
||||
buildSizeClient.DefaultRequestHeaders.Add("Range", "bytes=0-0");
|
||||
|
||||
HttpResponseMessage message = await buildSizeClient.GetAsync(new Uri(_buildUrl), HttpCompletionOption.ResponseHeadersRead);
|
||||
|
||||
_buildSize = message.Content.Headers.ContentRange.Length.Value;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application, ex.Message);
|
||||
Logger.Warning?.Print(LogClass.Application, "Couldn't determine build size for update, using single-threaded updater");
|
||||
|
||||
_buildSize = -1;
|
||||
}
|
||||
|
||||
await Dispatcher.UIThread.InvokeAsync(async () =>
|
||||
{
|
||||
// Show a message asking the user if they want to update
|
||||
var shouldUpdate = await ContentDialogHelper.CreateChoiceDialog(
|
||||
LocaleManager.Instance[LocaleKeys.RyujinxUpdater],
|
||||
LocaleManager.Instance[LocaleKeys.RyujinxUpdaterMessage],
|
||||
$"{Program.Version} -> {newVersion}");
|
||||
|
||||
if (shouldUpdate)
|
||||
{
|
||||
await UpdateRyujinx(mainWindow, _buildUrl);
|
||||
}
|
||||
else
|
||||
{
|
||||
_running = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static HttpClient ConstructHttpClient()
|
||||
{
|
||||
HttpClient result = new();
|
||||
|
||||
// Required by GitHub to interact with APIs.
|
||||
result.DefaultRequestHeaders.Add("User-Agent", "Ryujinx-Updater/1.0.0");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static async Task UpdateRyujinx(Window parent, string downloadUrl)
|
||||
{
|
||||
_updateSuccessful = false;
|
||||
|
||||
// Empty update dir, although it shouldn't ever have anything inside it
|
||||
if (Directory.Exists(_updateDir))
|
||||
{
|
||||
Directory.Delete(_updateDir, true);
|
||||
}
|
||||
|
||||
Directory.CreateDirectory(_updateDir);
|
||||
|
||||
string updateFile = Path.Combine(_updateDir, "update.bin");
|
||||
|
||||
TaskDialog taskDialog = new()
|
||||
{
|
||||
Header = LocaleManager.Instance[LocaleKeys.RyujinxUpdater],
|
||||
SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterDownloading],
|
||||
IconSource = new SymbolIconSource { Symbol = Symbol.Download },
|
||||
ShowProgressBar = true,
|
||||
XamlRoot = parent,
|
||||
};
|
||||
|
||||
taskDialog.Opened += (s, e) =>
|
||||
{
|
||||
if (_buildSize >= 0)
|
||||
{
|
||||
DoUpdateWithMultipleThreads(taskDialog, downloadUrl, updateFile);
|
||||
}
|
||||
else
|
||||
{
|
||||
DoUpdateWithSingleThread(taskDialog, downloadUrl, updateFile);
|
||||
}
|
||||
};
|
||||
|
||||
await taskDialog.ShowAsync(true);
|
||||
|
||||
if (_updateSuccessful)
|
||||
{
|
||||
bool shouldRestart = true;
|
||||
|
||||
if (!OperatingSystem.IsMacOS())
|
||||
{
|
||||
shouldRestart = await ContentDialogHelper.CreateChoiceDialog(LocaleManager.Instance[LocaleKeys.RyujinxUpdater],
|
||||
LocaleManager.Instance[LocaleKeys.DialogUpdaterCompleteMessage],
|
||||
LocaleManager.Instance[LocaleKeys.DialogUpdaterRestartMessage]);
|
||||
}
|
||||
|
||||
if (shouldRestart)
|
||||
{
|
||||
List<string> arguments = CommandLineState.Arguments.ToList();
|
||||
string executableDirectory = AppDomain.CurrentDomain.BaseDirectory;
|
||||
|
||||
// On macOS we perform the update at relaunch.
|
||||
if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
string baseBundlePath = Path.GetFullPath(Path.Combine(executableDirectory, "..", ".."));
|
||||
string newBundlePath = Path.Combine(_updateDir, "Ryujinx.app");
|
||||
string updaterScriptPath = Path.Combine(newBundlePath, "Contents", "Resources", "updater.sh");
|
||||
string currentPid = Environment.ProcessId.ToString();
|
||||
|
||||
arguments.InsertRange(0, new List<string> { updaterScriptPath, baseBundlePath, newBundlePath, currentPid });
|
||||
Process.Start("/bin/bash", arguments);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Find the process name.
|
||||
string ryuName = Path.GetFileName(Environment.ProcessPath);
|
||||
|
||||
// Some operating systems can see the renamed executable, so strip off the .ryuold if found.
|
||||
if (ryuName.EndsWith(".ryuold"))
|
||||
{
|
||||
ryuName = ryuName[..^7];
|
||||
}
|
||||
|
||||
// Fallback if the executable could not be found.
|
||||
if (!Path.Exists(Path.Combine(executableDirectory, ryuName)))
|
||||
{
|
||||
ryuName = OperatingSystem.IsWindows() ? "Ryujinx.Ava.exe" : "Ryujinx.Ava";
|
||||
}
|
||||
|
||||
ProcessStartInfo processStart = new(ryuName)
|
||||
{
|
||||
UseShellExecute = true,
|
||||
WorkingDirectory = executableDirectory,
|
||||
};
|
||||
|
||||
foreach (string argument in CommandLineState.Arguments)
|
||||
{
|
||||
processStart.ArgumentList.Add(argument);
|
||||
}
|
||||
|
||||
Process.Start(processStart);
|
||||
}
|
||||
|
||||
Environment.Exit(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void DoUpdateWithMultipleThreads(TaskDialog taskDialog, string downloadUrl, string updateFile)
|
||||
{
|
||||
// Multi-Threaded Updater
|
||||
long chunkSize = _buildSize / ConnectionCount;
|
||||
long remainderChunk = _buildSize % ConnectionCount;
|
||||
|
||||
int completedRequests = 0;
|
||||
int totalProgressPercentage = 0;
|
||||
int[] progressPercentage = new int[ConnectionCount];
|
||||
|
||||
List<byte[]> list = new(ConnectionCount);
|
||||
List<WebClient> webClients = new(ConnectionCount);
|
||||
|
||||
for (int i = 0; i < ConnectionCount; i++)
|
||||
{
|
||||
list.Add(Array.Empty<byte>());
|
||||
}
|
||||
|
||||
for (int i = 0; i < ConnectionCount; i++)
|
||||
{
|
||||
#pragma warning disable SYSLIB0014
|
||||
// TODO: WebClient is obsolete and need to be replaced with a more complex logic using HttpClient.
|
||||
using WebClient client = new();
|
||||
#pragma warning restore SYSLIB0014
|
||||
|
||||
webClients.Add(client);
|
||||
|
||||
if (i == ConnectionCount - 1)
|
||||
{
|
||||
client.Headers.Add("Range", $"bytes={chunkSize * i}-{(chunkSize * (i + 1) - 1) + remainderChunk}");
|
||||
}
|
||||
else
|
||||
{
|
||||
client.Headers.Add("Range", $"bytes={chunkSize * i}-{chunkSize * (i + 1) - 1}");
|
||||
}
|
||||
|
||||
client.DownloadProgressChanged += (_, args) =>
|
||||
{
|
||||
int index = (int)args.UserState;
|
||||
|
||||
Interlocked.Add(ref totalProgressPercentage, -1 * progressPercentage[index]);
|
||||
Interlocked.Exchange(ref progressPercentage[index], args.ProgressPercentage);
|
||||
Interlocked.Add(ref totalProgressPercentage, args.ProgressPercentage);
|
||||
|
||||
taskDialog.SetProgressBarState(totalProgressPercentage / ConnectionCount, TaskDialogProgressState.Normal);
|
||||
};
|
||||
|
||||
client.DownloadDataCompleted += (_, args) =>
|
||||
{
|
||||
int index = (int)args.UserState;
|
||||
|
||||
if (args.Cancelled)
|
||||
{
|
||||
webClients[index].Dispose();
|
||||
|
||||
taskDialog.Hide();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
list[index] = args.Result;
|
||||
Interlocked.Increment(ref completedRequests);
|
||||
|
||||
if (Equals(completedRequests, ConnectionCount))
|
||||
{
|
||||
byte[] mergedFileBytes = new byte[_buildSize];
|
||||
for (int connectionIndex = 0, destinationOffset = 0; connectionIndex < ConnectionCount; connectionIndex++)
|
||||
{
|
||||
Array.Copy(list[connectionIndex], 0, mergedFileBytes, destinationOffset, list[connectionIndex].Length);
|
||||
destinationOffset += list[connectionIndex].Length;
|
||||
}
|
||||
|
||||
File.WriteAllBytes(updateFile, mergedFileBytes);
|
||||
|
||||
// On macOS, ensure that we remove the quarantine bit to prevent Gatekeeper from blocking execution.
|
||||
if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
using Process xattrProcess = Process.Start("xattr", new List<string> { "-d", "com.apple.quarantine", updateFile });
|
||||
|
||||
xattrProcess.WaitForExit();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
InstallUpdate(taskDialog, updateFile);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application, e.Message);
|
||||
Logger.Warning?.Print(LogClass.Application, "Multi-Threaded update failed, falling back to single-threaded updater.");
|
||||
|
||||
DoUpdateWithSingleThread(taskDialog, downloadUrl, updateFile);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
client.DownloadDataAsync(new Uri(downloadUrl), i);
|
||||
}
|
||||
catch (WebException ex)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application, ex.Message);
|
||||
Logger.Warning?.Print(LogClass.Application, "Multi-Threaded update failed, falling back to single-threaded updater.");
|
||||
|
||||
foreach (WebClient webClient in webClients)
|
||||
{
|
||||
webClient.CancelAsync();
|
||||
}
|
||||
|
||||
DoUpdateWithSingleThread(taskDialog, downloadUrl, updateFile);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void DoUpdateWithSingleThreadWorker(TaskDialog taskDialog, string downloadUrl, string updateFile)
|
||||
{
|
||||
using HttpClient client = new();
|
||||
// We do not want to timeout while downloading
|
||||
client.Timeout = TimeSpan.FromDays(1);
|
||||
|
||||
using HttpResponseMessage response = client.GetAsync(downloadUrl, HttpCompletionOption.ResponseHeadersRead).Result;
|
||||
using Stream remoteFileStream = response.Content.ReadAsStreamAsync().Result;
|
||||
using Stream updateFileStream = File.Open(updateFile, FileMode.Create);
|
||||
|
||||
long totalBytes = response.Content.Headers.ContentLength.Value;
|
||||
long byteWritten = 0;
|
||||
|
||||
byte[] buffer = new byte[32 * 1024];
|
||||
|
||||
while (true)
|
||||
{
|
||||
int readSize = remoteFileStream.Read(buffer);
|
||||
|
||||
if (readSize == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
byteWritten += readSize;
|
||||
|
||||
taskDialog.SetProgressBarState(GetPercentage(byteWritten, totalBytes), TaskDialogProgressState.Normal);
|
||||
|
||||
updateFileStream.Write(buffer, 0, readSize);
|
||||
}
|
||||
|
||||
InstallUpdate(taskDialog, updateFile);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static double GetPercentage(double value, double max)
|
||||
{
|
||||
return max == 0 ? 0 : value / max * 100;
|
||||
}
|
||||
|
||||
private static void DoUpdateWithSingleThread(TaskDialog taskDialog, string downloadUrl, string updateFile)
|
||||
{
|
||||
Thread worker = new(() => DoUpdateWithSingleThreadWorker(taskDialog, downloadUrl, updateFile))
|
||||
{
|
||||
Name = "Updater.SingleThreadWorker",
|
||||
};
|
||||
|
||||
worker.Start();
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("linux")]
|
||||
[SupportedOSPlatform("macos")]
|
||||
private static void ExtractTarGzipFile(TaskDialog taskDialog, string archivePath, string outputDirectoryPath)
|
||||
{
|
||||
using Stream inStream = File.OpenRead(archivePath);
|
||||
using GZipInputStream gzipStream = new(inStream);
|
||||
using TarInputStream tarStream = new(gzipStream, Encoding.ASCII);
|
||||
|
||||
TarEntry tarEntry;
|
||||
|
||||
while ((tarEntry = tarStream.GetNextEntry()) is not null)
|
||||
{
|
||||
if (tarEntry.IsDirectory)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string outPath = Path.Combine(outputDirectoryPath, tarEntry.Name);
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(outPath));
|
||||
|
||||
using FileStream outStream = File.OpenWrite(outPath);
|
||||
tarStream.CopyEntryContents(outStream);
|
||||
|
||||
File.SetUnixFileMode(outPath, (UnixFileMode)tarEntry.TarHeader.Mode);
|
||||
File.SetLastWriteTime(outPath, DateTime.SpecifyKind(tarEntry.ModTime, DateTimeKind.Utc));
|
||||
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
if (tarEntry is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
taskDialog.SetProgressBarState(GetPercentage(tarEntry.Size, inStream.Length), TaskDialogProgressState.Normal);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static void ExtractZipFile(TaskDialog taskDialog, string archivePath, string outputDirectoryPath)
|
||||
{
|
||||
using Stream inStream = File.OpenRead(archivePath);
|
||||
using ZipFile zipFile = new(inStream);
|
||||
|
||||
double count = 0;
|
||||
foreach (ZipEntry zipEntry in zipFile)
|
||||
{
|
||||
count++;
|
||||
if (zipEntry.IsDirectory)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string outPath = Path.Combine(outputDirectoryPath, zipEntry.Name);
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(outPath));
|
||||
|
||||
using Stream zipStream = zipFile.GetInputStream(zipEntry);
|
||||
using FileStream outStream = File.OpenWrite(outPath);
|
||||
|
||||
zipStream.CopyTo(outStream);
|
||||
|
||||
File.SetLastWriteTime(outPath, DateTime.SpecifyKind(zipEntry.DateTime, DateTimeKind.Utc));
|
||||
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
taskDialog.SetProgressBarState(GetPercentage(count, zipFile.Count), TaskDialogProgressState.Normal);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static void InstallUpdate(TaskDialog taskDialog, string updateFile)
|
||||
{
|
||||
// Extract Update
|
||||
taskDialog.SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterExtracting];
|
||||
taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal);
|
||||
|
||||
if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
|
||||
{
|
||||
ExtractTarGzipFile(taskDialog, updateFile, _updateDir);
|
||||
}
|
||||
else if (OperatingSystem.IsWindows())
|
||||
{
|
||||
ExtractZipFile(taskDialog, updateFile, _updateDir);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
// Delete downloaded zip
|
||||
File.Delete(updateFile);
|
||||
|
||||
List<string> allFiles = EnumerateFilesToDelete().ToList();
|
||||
|
||||
taskDialog.SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterRenaming];
|
||||
taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal);
|
||||
|
||||
// NOTE: On macOS, replacement is delayed to the restart phase.
|
||||
if (!OperatingSystem.IsMacOS())
|
||||
{
|
||||
// Replace old files
|
||||
double count = 0;
|
||||
foreach (string file in allFiles)
|
||||
{
|
||||
count++;
|
||||
try
|
||||
{
|
||||
File.Move(file, file + ".ryuold");
|
||||
|
||||
Dispatcher.UIThread.InvokeAsync(() =>
|
||||
{
|
||||
taskDialog.SetProgressBarState(GetPercentage(count, allFiles.Count), TaskDialogProgressState.Normal);
|
||||
});
|
||||
}
|
||||
catch
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application, LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.UpdaterRenameFailed, file));
|
||||
}
|
||||
}
|
||||
|
||||
Dispatcher.UIThread.InvokeAsync(() =>
|
||||
{
|
||||
taskDialog.SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterAddingFiles];
|
||||
taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal);
|
||||
});
|
||||
|
||||
MoveAllFilesOver(_updatePublishDir, _homeDir, taskDialog);
|
||||
|
||||
Directory.Delete(_updateDir, true);
|
||||
}
|
||||
|
||||
_updateSuccessful = true;
|
||||
|
||||
taskDialog.Hide();
|
||||
}
|
||||
|
||||
public static bool CanUpdate(bool showWarnings)
|
||||
{
|
||||
#if !DISABLE_UPDATER
|
||||
if (RuntimeInformation.OSArchitecture != Architecture.X64 && !OperatingSystem.IsMacOS())
|
||||
{
|
||||
if (showWarnings)
|
||||
{
|
||||
Dispatcher.UIThread.InvokeAsync(() =>
|
||||
ContentDialogHelper.CreateWarningDialog(
|
||||
LocaleManager.Instance[LocaleKeys.DialogUpdaterArchNotSupportedMessage],
|
||||
LocaleManager.Instance[LocaleKeys.DialogUpdaterArchNotSupportedSubMessage])
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!NetworkInterface.GetIsNetworkAvailable())
|
||||
{
|
||||
if (showWarnings)
|
||||
{
|
||||
Dispatcher.UIThread.InvokeAsync(() =>
|
||||
ContentDialogHelper.CreateWarningDialog(
|
||||
LocaleManager.Instance[LocaleKeys.DialogUpdaterNoInternetMessage],
|
||||
LocaleManager.Instance[LocaleKeys.DialogUpdaterNoInternetSubMessage])
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Program.Version.Contains("dirty") || !ReleaseInformation.IsValid())
|
||||
{
|
||||
if (showWarnings)
|
||||
{
|
||||
Dispatcher.UIThread.InvokeAsync(() =>
|
||||
ContentDialogHelper.CreateWarningDialog(
|
||||
LocaleManager.Instance[LocaleKeys.DialogUpdaterDirtyBuildMessage],
|
||||
LocaleManager.Instance[LocaleKeys.DialogUpdaterDirtyBuildSubMessage])
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
#else
|
||||
if (showWarnings)
|
||||
{
|
||||
if (ReleaseInformation.IsFlatHubBuild())
|
||||
{
|
||||
Dispatcher.UIThread.InvokeAsync(() =>
|
||||
ContentDialogHelper.CreateWarningDialog(
|
||||
LocaleManager.Instance[LocaleKeys.UpdaterDisabledWarningTitle],
|
||||
LocaleManager.Instance[LocaleKeys.DialogUpdaterFlatpakNotSupportedMessage])
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
Dispatcher.UIThread.InvokeAsync(() =>
|
||||
ContentDialogHelper.CreateWarningDialog(
|
||||
LocaleManager.Instance[LocaleKeys.UpdaterDisabledWarningTitle],
|
||||
LocaleManager.Instance[LocaleKeys.DialogUpdaterDirtyBuildSubMessage])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
// NOTE: This method should always reflect the latest build layout.
|
||||
private static IEnumerable<string> EnumerateFilesToDelete()
|
||||
{
|
||||
var files = Directory.EnumerateFiles(_homeDir); // All files directly in base dir.
|
||||
|
||||
// Determine and exclude user files only when the updater is running, not when cleaning old files
|
||||
if (_running && !OperatingSystem.IsMacOS())
|
||||
{
|
||||
// Compare the loose files in base directory against the loose files from the incoming update, and store foreign ones in a user list.
|
||||
var oldFiles = Directory.EnumerateFiles(_homeDir, "*", SearchOption.TopDirectoryOnly).Select(Path.GetFileName);
|
||||
var newFiles = Directory.EnumerateFiles(_updatePublishDir, "*", SearchOption.TopDirectoryOnly).Select(Path.GetFileName);
|
||||
var userFiles = oldFiles.Except(newFiles).Select(filename => Path.Combine(_homeDir, filename));
|
||||
|
||||
// Remove user files from the paths in files.
|
||||
files = files.Except(userFiles);
|
||||
}
|
||||
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
foreach (string dir in _windowsDependencyDirs)
|
||||
{
|
||||
string dirPath = Path.Combine(_homeDir, dir);
|
||||
if (Directory.Exists(dirPath))
|
||||
{
|
||||
files = files.Concat(Directory.EnumerateFiles(dirPath, "*", SearchOption.AllDirectories));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return files.Where(f => !new FileInfo(f).Attributes.HasFlag(FileAttributes.Hidden | FileAttributes.System));
|
||||
}
|
||||
|
||||
private static void MoveAllFilesOver(string root, string dest, TaskDialog taskDialog)
|
||||
{
|
||||
int total = Directory.GetFiles(root, "*", SearchOption.AllDirectories).Length;
|
||||
foreach (string directory in Directory.GetDirectories(root))
|
||||
{
|
||||
string dirName = Path.GetFileName(directory);
|
||||
|
||||
if (!Directory.Exists(Path.Combine(dest, dirName)))
|
||||
{
|
||||
Directory.CreateDirectory(Path.Combine(dest, dirName));
|
||||
}
|
||||
|
||||
MoveAllFilesOver(directory, Path.Combine(dest, dirName), taskDialog);
|
||||
}
|
||||
|
||||
double count = 0;
|
||||
foreach (string file in Directory.GetFiles(root))
|
||||
{
|
||||
count++;
|
||||
|
||||
File.Move(file, Path.Combine(dest, Path.GetFileName(file)), true);
|
||||
|
||||
Dispatcher.UIThread.InvokeAsync(() =>
|
||||
{
|
||||
taskDialog.SetProgressBarState(GetPercentage(count, total), TaskDialogProgressState.Normal);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static void CleanupUpdate()
|
||||
{
|
||||
foreach (string file in Directory.GetFiles(_homeDir, "*.ryuold", SearchOption.AllDirectories))
|
||||
{
|
||||
File.Delete(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,236 +0,0 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Threading;
|
||||
using Ryujinx.Ava.UI.Helpers;
|
||||
using Ryujinx.Ava.UI.Windows;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Common.GraphicsDriver;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common.SystemInterop;
|
||||
using Ryujinx.Modules;
|
||||
using Ryujinx.SDL2.Common;
|
||||
using Ryujinx.Ui.Common;
|
||||
using Ryujinx.Ui.Common.Configuration;
|
||||
using Ryujinx.Ui.Common.Helper;
|
||||
using Ryujinx.Ui.Common.SystemInfo;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ryujinx.Ava
|
||||
{
|
||||
internal partial class Program
|
||||
{
|
||||
public static double WindowScaleFactor { get; set; }
|
||||
public static double DesktopScaleFactor { get; set; } = 1.0;
|
||||
public static string Version { get; private set; }
|
||||
public static string ConfigurationPath { get; private set; }
|
||||
public static bool PreviewerDetached { get; private set; }
|
||||
|
||||
[LibraryImport("user32.dll", SetLastError = true)]
|
||||
public static partial int MessageBoxA(IntPtr hWnd, [MarshalAs(UnmanagedType.LPStr)] string text, [MarshalAs(UnmanagedType.LPStr)] string caption, uint type);
|
||||
|
||||
private const uint MbIconwarning = 0x30;
|
||||
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
Version = ReleaseInformation.GetVersion();
|
||||
|
||||
if (OperatingSystem.IsWindows() && !OperatingSystem.IsWindowsVersionAtLeast(10, 0, 17134))
|
||||
{
|
||||
_ = MessageBoxA(IntPtr.Zero, "You are running an outdated version of Windows.\n\nStarting on June 1st 2022, Ryujinx will only support Windows 10 1803 and newer.\n", $"Ryujinx {Version}", MbIconwarning);
|
||||
}
|
||||
|
||||
PreviewerDetached = true;
|
||||
|
||||
Initialize(args);
|
||||
|
||||
LoggerAdapter.Register();
|
||||
|
||||
BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
|
||||
}
|
||||
|
||||
public static AppBuilder BuildAvaloniaApp()
|
||||
{
|
||||
return AppBuilder.Configure<App>()
|
||||
.UsePlatformDetect()
|
||||
.With(new X11PlatformOptions
|
||||
{
|
||||
EnableMultiTouch = true,
|
||||
EnableIme = true,
|
||||
RenderingMode = new[] { X11RenderingMode.Glx, X11RenderingMode.Software },
|
||||
})
|
||||
.With(new Win32PlatformOptions
|
||||
{
|
||||
WinUICompositionBackdropCornerRadius = 8.0f,
|
||||
RenderingMode = new[] { Win32RenderingMode.AngleEgl, Win32RenderingMode.Software },
|
||||
})
|
||||
.UseSkia();
|
||||
}
|
||||
|
||||
private static void Initialize(string[] args)
|
||||
{
|
||||
// Parse arguments
|
||||
CommandLineState.ParseArguments(args);
|
||||
|
||||
// Delete backup files after updating.
|
||||
Task.Run(Updater.CleanupUpdate);
|
||||
|
||||
Console.Title = $"Ryujinx Console {Version}";
|
||||
|
||||
// Hook unhandled exception and process exit events.
|
||||
AppDomain.CurrentDomain.UnhandledException += (sender, e) => ProcessUnhandledException(e.ExceptionObject as Exception, e.IsTerminating);
|
||||
AppDomain.CurrentDomain.ProcessExit += (sender, e) => Exit();
|
||||
|
||||
// Setup base data directory.
|
||||
AppDataManager.Initialize(CommandLineState.BaseDirPathArg);
|
||||
|
||||
// Initialize the configuration.
|
||||
ConfigurationState.Initialize();
|
||||
|
||||
// Initialize the logger system.
|
||||
LoggerModule.Initialize();
|
||||
|
||||
// Initialize Discord integration.
|
||||
DiscordIntegrationModule.Initialize();
|
||||
|
||||
// Initialize SDL2 driver
|
||||
SDL2Driver.MainThreadDispatcher = action => Dispatcher.UIThread.InvokeAsync(action, DispatcherPriority.Input);
|
||||
|
||||
ReloadConfig();
|
||||
|
||||
WindowScaleFactor = ForceDpiAware.GetWindowScaleFactor();
|
||||
|
||||
// Logging system information.
|
||||
PrintSystemInfo();
|
||||
|
||||
// Enable OGL multithreading on the driver, when available.
|
||||
DriverUtilities.ToggleOGLThreading(ConfigurationState.Instance.Graphics.BackendThreading == BackendThreading.Off);
|
||||
|
||||
// Check if keys exists.
|
||||
if (!File.Exists(Path.Combine(AppDataManager.KeysDirPath, "prod.keys")))
|
||||
{
|
||||
if (!(AppDataManager.Mode == AppDataManager.LaunchMode.UserProfile && File.Exists(Path.Combine(AppDataManager.KeysDirPathUser, "prod.keys"))))
|
||||
{
|
||||
MainWindow.ShowKeyErrorOnLoad = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (CommandLineState.LaunchPathArg != null)
|
||||
{
|
||||
MainWindow.DeferLoadApplication(CommandLineState.LaunchPathArg, CommandLineState.StartFullscreenArg);
|
||||
}
|
||||
}
|
||||
|
||||
public static void ReloadConfig()
|
||||
{
|
||||
string localConfigurationPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json");
|
||||
string appDataConfigurationPath = Path.Combine(AppDataManager.BaseDirPath, "Config.json");
|
||||
|
||||
// Now load the configuration as the other subsystems are now registered
|
||||
if (File.Exists(localConfigurationPath))
|
||||
{
|
||||
ConfigurationPath = localConfigurationPath;
|
||||
}
|
||||
else if (File.Exists(appDataConfigurationPath))
|
||||
{
|
||||
ConfigurationPath = appDataConfigurationPath;
|
||||
}
|
||||
|
||||
if (ConfigurationPath == null)
|
||||
{
|
||||
// No configuration, we load the default values and save it to disk
|
||||
ConfigurationPath = appDataConfigurationPath;
|
||||
|
||||
ConfigurationState.Instance.LoadDefault();
|
||||
ConfigurationState.Instance.ToFileFormat().SaveConfig(ConfigurationPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ConfigurationFileFormat.TryLoad(ConfigurationPath, out ConfigurationFileFormat configurationFileFormat))
|
||||
{
|
||||
ConfigurationState.Instance.Load(configurationFileFormat, ConfigurationPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
ConfigurationState.Instance.LoadDefault();
|
||||
|
||||
Logger.Warning?.PrintMsg(LogClass.Application, $"Failed to load config! Loading the default config instead.\nFailed config location {ConfigurationPath}");
|
||||
}
|
||||
}
|
||||
|
||||
// Check if graphics backend was overridden
|
||||
if (CommandLineState.OverrideGraphicsBackend != null)
|
||||
{
|
||||
if (CommandLineState.OverrideGraphicsBackend.ToLower() == "opengl")
|
||||
{
|
||||
ConfigurationState.Instance.Graphics.GraphicsBackend.Value = GraphicsBackend.OpenGl;
|
||||
}
|
||||
else if (CommandLineState.OverrideGraphicsBackend.ToLower() == "vulkan")
|
||||
{
|
||||
ConfigurationState.Instance.Graphics.GraphicsBackend.Value = GraphicsBackend.Vulkan;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if docked mode was overriden.
|
||||
if (CommandLineState.OverrideDockedMode.HasValue)
|
||||
{
|
||||
ConfigurationState.Instance.System.EnableDockedMode.Value = CommandLineState.OverrideDockedMode.Value;
|
||||
}
|
||||
|
||||
// Check if HideCursor was overridden.
|
||||
if (CommandLineState.OverrideHideCursor is not null)
|
||||
{
|
||||
ConfigurationState.Instance.HideCursor.Value = CommandLineState.OverrideHideCursor!.ToLower() switch
|
||||
{
|
||||
"never" => HideCursorMode.Never,
|
||||
"onidle" => HideCursorMode.OnIdle,
|
||||
"always" => HideCursorMode.Always,
|
||||
_ => ConfigurationState.Instance.HideCursor.Value,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private static void PrintSystemInfo()
|
||||
{
|
||||
Logger.Notice.Print(LogClass.Application, $"Ryujinx Version: {Version}");
|
||||
SystemInfo.Gather().Print();
|
||||
|
||||
Logger.Notice.Print(LogClass.Application, $"Logs Enabled: {(Logger.GetEnabledLevels().Count == 0 ? "<None>" : string.Join(", ", Logger.GetEnabledLevels()))}");
|
||||
|
||||
if (AppDataManager.Mode == AppDataManager.LaunchMode.Custom)
|
||||
{
|
||||
Logger.Notice.Print(LogClass.Application, $"Launch Mode: Custom Path {AppDataManager.BaseDirPath}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Notice.Print(LogClass.Application, $"Launch Mode: {AppDataManager.Mode}");
|
||||
}
|
||||
}
|
||||
|
||||
private static void ProcessUnhandledException(Exception ex, bool isTerminating)
|
||||
{
|
||||
string message = $"Unhandled exception caught: {ex}";
|
||||
|
||||
Logger.Error?.PrintMsg(LogClass.Application, message);
|
||||
|
||||
if (Logger.Error == null)
|
||||
{
|
||||
Logger.Notice.PrintMsg(LogClass.Application, message);
|
||||
}
|
||||
|
||||
if (isTerminating)
|
||||
{
|
||||
Exit();
|
||||
}
|
||||
}
|
||||
|
||||
public static void Exit()
|
||||
{
|
||||
DiscordIntegrationModule.Exit();
|
||||
|
||||
Logger.Shutdown();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,168 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<RuntimeIdentifiers>win-x64;osx-x64;linux-x64</RuntimeIdentifiers>
|
||||
<OutputType>Exe</OutputType>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<Version>1.0.0-dirty</Version>
|
||||
<DefineConstants Condition=" '$(ExtraDefineConstants)' != '' ">$(DefineConstants);$(ExtraDefineConstants)</DefineConstants>
|
||||
<SigningCertificate Condition=" '$(SigningCertificate)' == '' ">-</SigningCertificate>
|
||||
<RootNamespace>Ryujinx.Ava</RootNamespace>
|
||||
<ApplicationIcon>Ryujinx.ico</ApplicationIcon>
|
||||
<TieredPGO>true</TieredPGO>
|
||||
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
</PropertyGroup>
|
||||
|
||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent" Condition="$([MSBuild]::IsOSPlatform('OSX'))">
|
||||
<Exec Command="codesign --entitlements '$(ProjectDir)..\..\distribution\macos\entitlements.xml' -f --deep -s $(SigningCertificate) '$(TargetDir)$(TargetName)'" />
|
||||
</Target>
|
||||
|
||||
<PropertyGroup Condition="'$(RuntimeIdentifier)' != ''">
|
||||
<PublishSingleFile>true</PublishSingleFile>
|
||||
<TrimmerSingleWarn>false</TrimmerSingleWarn>
|
||||
<PublishTrimmed>true</PublishTrimmed>
|
||||
<TrimMode>partial</TrimMode>
|
||||
</PropertyGroup>
|
||||
|
||||
<!--
|
||||
FluentAvalonia, used in the Avalonia UI, requires a workaround for the json serializer used internally when using .NET 8+ System.Text.Json.
|
||||
See:
|
||||
https://github.com/amwx/FluentAvalonia/issues/481
|
||||
https://devblogs.microsoft.com/dotnet/system-text-json-in-dotnet-8/
|
||||
-->
|
||||
<PropertyGroup>
|
||||
<JsonSerializerIsReflectionEnabledByDefault>true</JsonSerializerIsReflectionEnabledByDefault>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Avalonia" />
|
||||
<PackageReference Include="Avalonia.Desktop" />
|
||||
<PackageReference Include="Avalonia.Diagnostics" Condition="'$(Configuration)'=='Debug'" />
|
||||
<PackageReference Include="Avalonia.Controls.DataGrid" />
|
||||
<PackageReference Include="Avalonia.Markup.Xaml.Loader" />
|
||||
<PackageReference Include="Avalonia.Svg" />
|
||||
<PackageReference Include="Avalonia.Svg.Skia" />
|
||||
<PackageReference Include="jp2masa.Avalonia.Flexbox" />
|
||||
<PackageReference Include="DynamicData" />
|
||||
<PackageReference Include="FluentAvaloniaUI" />
|
||||
|
||||
<PackageReference Include="OpenTK.Core" />
|
||||
<PackageReference Include="Ryujinx.Audio.OpenAL.Dependencies" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64' AND '$(RuntimeIdentifier)' != 'osx-arm64'" />
|
||||
<PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" />
|
||||
<PackageReference Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'win-x64'" />
|
||||
<PackageReference Include="Silk.NET.Vulkan" />
|
||||
<PackageReference Include="Silk.NET.Vulkan.Extensions.EXT" />
|
||||
<PackageReference Include="Silk.NET.Vulkan.Extensions.KHR" />
|
||||
<PackageReference Include="SPB" />
|
||||
<PackageReference Include="SharpZipLib" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" />
|
||||
|
||||
<!--NOTE: DO NOT REMOVE, THIS IS REQUIRED AS A RESULT OF A TRIMMING ISSUE IN AVALONIA -->
|
||||
<PackageReference Include="System.Drawing.Common" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Ryujinx.Audio.Backends.SDL2\Ryujinx.Audio.Backends.SDL2.csproj" />
|
||||
<ProjectReference Include="..\Ryujinx.Graphics.Vulkan\Ryujinx.Graphics.Vulkan.csproj" />
|
||||
<ProjectReference Include="..\Ryujinx.Input\Ryujinx.Input.csproj" />
|
||||
<ProjectReference Include="..\Ryujinx.Input.SDL2\Ryujinx.Input.SDL2.csproj" />
|
||||
<ProjectReference Include="..\Ryujinx.Audio.Backends.OpenAL\Ryujinx.Audio.Backends.OpenAL.csproj" />
|
||||
<ProjectReference Include="..\Ryujinx.Audio.Backends.SoundIo\Ryujinx.Audio.Backends.SoundIo.csproj" />
|
||||
<ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
|
||||
<ProjectReference Include="..\Ryujinx.HLE\Ryujinx.HLE.csproj" />
|
||||
<ProjectReference Include="..\ARMeilleure\ARMeilleure.csproj" />
|
||||
<ProjectReference Include="..\Ryujinx.Graphics.OpenGL\Ryujinx.Graphics.OpenGL.csproj" />
|
||||
<ProjectReference Include="..\Ryujinx.Graphics.Gpu\Ryujinx.Graphics.Gpu.csproj" />
|
||||
<ProjectReference Include="..\Ryujinx.Ui.Common\Ryujinx.Ui.Common.csproj" />
|
||||
<ProjectReference Include="..\Ryujinx.Ui.LocaleGenerator\Ryujinx.Ui.LocaleGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="..\..\distribution\windows\alsoft.ini" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
<TargetPath>alsoft.ini</TargetPath>
|
||||
</Content>
|
||||
<Content Include="..\..\distribution\legal\THIRDPARTY.md">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
<TargetPath>THIRDPARTY.md</TargetPath>
|
||||
</Content>
|
||||
<Content Include="..\..\LICENSE.txt">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
<TargetPath>LICENSE.txt</TargetPath>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(RuntimeIdentifier)' == 'linux-x64'">
|
||||
<Content Include="..\..\distribution\linux\Ryujinx.sh">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="..\..\distribution\linux\mime\Ryujinx.xml">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
<TargetPath>mime\Ryujinx.xml</TargetPath>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<AvaloniaResource Include="Ui\**\*.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
</AvaloniaResource>
|
||||
<AvaloniaResource Include="Assets\Fonts\SegoeFluentIcons.ttf" />
|
||||
<AvaloniaResource Include="Assets\Styles\Themes.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</AvaloniaResource>
|
||||
<AvaloniaResource Include="Assets\Styles\Styles.xaml" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="Assets\Locales\el_GR.json" />
|
||||
<None Remove="Assets\Locales\en_US.json" />
|
||||
<None Remove="Assets\Locales\es_ES.json" />
|
||||
<None Remove="Assets\Locales\fr_FR.json" />
|
||||
<None Remove="Assets\Locales\he_IL.json" />
|
||||
<None Remove="Assets\Locales\de_DE.json" />
|
||||
<None Remove="Assets\Locales\it_IT.json" />
|
||||
<None Remove="Assets\Locales\ja_JP.json" />
|
||||
<None Remove="Assets\Locales\ko_KR.json" />
|
||||
<None Remove="Assets\Locales\pl_PL.json" />
|
||||
<None Remove="Assets\Locales\pt_BR.json" />
|
||||
<None Remove="Assets\Locales\ru_RU.json" />
|
||||
<None Remove="Assets\Locales\tr_TR.json" />
|
||||
<None Remove="Assets\Locales\uk_UA.json" />
|
||||
<None Remove="Assets\Locales\zh_CN.json" />
|
||||
<None Remove="Assets\Locales\zh_TW.json" />
|
||||
<None Remove="Assets\Styles\Styles.xaml" />
|
||||
<None Remove="Assets\Styles\Themes.xaml" />
|
||||
<None Remove="Assets\Icons\Controller_JoyConLeft.svg" />
|
||||
<None Remove="Assets\Icons\Controller_JoyConPair.svg" />
|
||||
<None Remove="Assets\Icons\Controller_JoyConRight.svg" />
|
||||
<None Remove="Assets\Icons\Controller_ProCon.svg" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Assets\Locales\el_GR.json" />
|
||||
<EmbeddedResource Include="Assets\Locales\en_US.json" />
|
||||
<EmbeddedResource Include="Assets\Locales\es_ES.json" />
|
||||
<EmbeddedResource Include="Assets\Locales\fr_FR.json" />
|
||||
<EmbeddedResource Include="Assets\Locales\he_IL.json" />
|
||||
<EmbeddedResource Include="Assets\Locales\de_DE.json" />
|
||||
<EmbeddedResource Include="Assets\Locales\it_IT.json" />
|
||||
<EmbeddedResource Include="Assets\Locales\ja_JP.json" />
|
||||
<EmbeddedResource Include="Assets\Locales\ko_KR.json" />
|
||||
<EmbeddedResource Include="Assets\Locales\pl_PL.json" />
|
||||
<EmbeddedResource Include="Assets\Locales\pt_BR.json" />
|
||||
<EmbeddedResource Include="Assets\Locales\ru_RU.json" />
|
||||
<EmbeddedResource Include="Assets\Locales\tr_TR.json" />
|
||||
<EmbeddedResource Include="Assets\Locales\uk_UA.json" />
|
||||
<EmbeddedResource Include="Assets\Locales\zh_CN.json" />
|
||||
<EmbeddedResource Include="Assets\Locales\zh_TW.json" />
|
||||
<EmbeddedResource Include="Assets\Styles\Styles.xaml" />
|
||||
<EmbeddedResource Include="Assets\Icons\Controller_JoyConLeft.svg" />
|
||||
<EmbeddedResource Include="Assets\Icons\Controller_JoyConPair.svg" />
|
||||
<EmbeddedResource Include="Assets\Icons\Controller_JoyConRight.svg" />
|
||||
<EmbeddedResource Include="Assets\Icons\Controller_ProCon.svg" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AdditionalFiles Include="Assets\Locales\en_US.json" />
|
||||
</ItemGroup>
|
||||
</Project>
|
@ -1,13 +1,15 @@
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common.Utilities;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.Versioning;
|
||||
|
||||
namespace Ryujinx.Common.Configuration
|
||||
{
|
||||
public static class AppDataManager
|
||||
{
|
||||
public const string DefaultBaseDir = "Ryujinx";
|
||||
public const string DefaultPortableDir = "portable";
|
||||
private const string DefaultBaseDir = "Ryujinx";
|
||||
private const string DefaultPortableDir = "portable";
|
||||
|
||||
// The following 3 are always part of Base Directory
|
||||
private const string GamesDir = "games";
|
||||
@ -29,6 +31,8 @@ namespace Ryujinx.Common.Configuration
|
||||
public static string KeysDirPath { get; private set; }
|
||||
public static string KeysDirPathUser { get; }
|
||||
|
||||
public static string LogsDirPath { get; private set; }
|
||||
|
||||
public const string DefaultNandDir = "bis";
|
||||
public const string DefaultSdcardDir = "sdcard";
|
||||
private const string DefaultModsDir = "mods";
|
||||
@ -45,15 +49,7 @@ namespace Ryujinx.Common.Configuration
|
||||
|
||||
public static void Initialize(string baseDirPath)
|
||||
{
|
||||
string appDataPath;
|
||||
if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
appDataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Library", "Application Support");
|
||||
}
|
||||
else
|
||||
{
|
||||
appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
|
||||
}
|
||||
string appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
|
||||
|
||||
if (appDataPath.Length == 0)
|
||||
{
|
||||
@ -100,65 +96,227 @@ namespace Ryujinx.Common.Configuration
|
||||
|
||||
BaseDirPath = Path.GetFullPath(BaseDirPath); // convert relative paths
|
||||
|
||||
// NOTE: Moves the Ryujinx folder in `~/.config` to `~/Library/Application Support` if one is found
|
||||
// and a Ryujinx folder does not already exist in Application Support.
|
||||
// Also creates a symlink from `~/.config/Ryujinx` to `~/Library/Application Support/Ryujinx` to preserve backwards compatibility.
|
||||
// This should be removed in the future.
|
||||
if (OperatingSystem.IsMacOS() && Mode == LaunchMode.UserProfile)
|
||||
if (IsPathSymlink(BaseDirPath))
|
||||
{
|
||||
string oldConfigPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), DefaultBaseDir);
|
||||
if (Path.Exists(oldConfigPath) && !IsPathSymlink(oldConfigPath) && !Path.Exists(BaseDirPath))
|
||||
{
|
||||
CopyDirectory(oldConfigPath, BaseDirPath);
|
||||
Directory.Delete(oldConfigPath, true);
|
||||
Directory.CreateSymbolicLink(oldConfigPath, BaseDirPath);
|
||||
}
|
||||
Logger.Warning?.Print(LogClass.Application, $"Application data directory is a symlink. This may be unintended.");
|
||||
}
|
||||
|
||||
SetupBasePaths();
|
||||
}
|
||||
|
||||
public static string GetOrCreateLogsDir()
|
||||
{
|
||||
if (Directory.Exists(LogsDirPath))
|
||||
{
|
||||
return LogsDirPath;
|
||||
}
|
||||
|
||||
Logger.Notice.Print(LogClass.Application, "Logging directory not found; attempting to create new logging directory.");
|
||||
LogsDirPath = SetUpLogsDir();
|
||||
|
||||
return LogsDirPath;
|
||||
}
|
||||
|
||||
private static string SetUpLogsDir()
|
||||
{
|
||||
string logDir = "";
|
||||
|
||||
if (Mode == LaunchMode.Portable)
|
||||
{
|
||||
logDir = Path.Combine(BaseDirPath, "Logs");
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(logDir);
|
||||
}
|
||||
catch
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application, $"Logging directory could not be created '{logDir}'");
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
// NOTE: Should evaluate to "~/Library/Logs/Ryujinx/".
|
||||
logDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Library", "Logs", DefaultBaseDir);
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(logDir);
|
||||
}
|
||||
catch
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application, $"Logging directory could not be created '{logDir}'");
|
||||
logDir = "";
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(logDir))
|
||||
{
|
||||
// NOTE: Should evaluate to "~/Library/Application Support/Ryujinx/Logs".
|
||||
logDir = Path.Combine(BaseDirPath, "Logs");
|
||||
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(logDir);
|
||||
}
|
||||
catch
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application, $"Logging directory could not be created '{logDir}'");
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (OperatingSystem.IsWindows())
|
||||
{
|
||||
// NOTE: Should evaluate to a "Logs" directory in whatever directory Ryujinx was launched from.
|
||||
logDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Logs");
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(logDir);
|
||||
}
|
||||
catch
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application, $"Logging directory could not be created '{logDir}'");
|
||||
logDir = "";
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(logDir))
|
||||
{
|
||||
// NOTE: Should evaluate to "C:\Users\user\AppData\Roaming\Ryujinx\Logs".
|
||||
logDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), DefaultBaseDir, "Logs");
|
||||
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(logDir);
|
||||
}
|
||||
catch
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application, $"Logging directory could not be created '{logDir}'");
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (OperatingSystem.IsLinux())
|
||||
{
|
||||
// NOTE: Should evaluate to "~/.config/Ryujinx/Logs".
|
||||
logDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), DefaultBaseDir, "Logs");
|
||||
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(logDir);
|
||||
}
|
||||
catch
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application, $"Logging directory could not be created '{logDir}'");
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return logDir;
|
||||
}
|
||||
|
||||
private static void SetupBasePaths()
|
||||
{
|
||||
Directory.CreateDirectory(BaseDirPath);
|
||||
LogsDirPath = SetUpLogsDir();
|
||||
Directory.CreateDirectory(GamesDirPath = Path.Combine(BaseDirPath, GamesDir));
|
||||
Directory.CreateDirectory(ProfilesDirPath = Path.Combine(BaseDirPath, ProfilesDir));
|
||||
Directory.CreateDirectory(KeysDirPath = Path.Combine(BaseDirPath, KeysDir));
|
||||
}
|
||||
|
||||
// Check if existing old baseDirPath is a symlink, to prevent possible errors.
|
||||
// Should be removed, when the existance of the old directory isn't checked anymore.
|
||||
// Should be removed, when the existence of the old directory isn't checked anymore.
|
||||
private static bool IsPathSymlink(string path)
|
||||
{
|
||||
FileAttributes attributes = File.GetAttributes(path);
|
||||
return (attributes & FileAttributes.ReparsePoint) == FileAttributes.ReparsePoint;
|
||||
try
|
||||
{
|
||||
FileAttributes attributes = File.GetAttributes(path);
|
||||
return (attributes & FileAttributes.ReparsePoint) == FileAttributes.ReparsePoint;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static void CopyDirectory(string sourceDir, string destinationDir)
|
||||
[SupportedOSPlatform("macos")]
|
||||
public static void FixMacOSConfigurationFolders()
|
||||
{
|
||||
var dir = new DirectoryInfo(sourceDir);
|
||||
|
||||
if (!dir.Exists)
|
||||
string oldConfigPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
|
||||
".config", DefaultBaseDir);
|
||||
if (Path.Exists(oldConfigPath) && !IsPathSymlink(oldConfigPath) && !Path.Exists(BaseDirPath))
|
||||
{
|
||||
throw new DirectoryNotFoundException($"Source directory not found: {dir.FullName}");
|
||||
FileSystemUtils.MoveDirectory(oldConfigPath, BaseDirPath);
|
||||
Directory.CreateSymbolicLink(oldConfigPath, BaseDirPath);
|
||||
}
|
||||
|
||||
DirectoryInfo[] subDirs = dir.GetDirectories();
|
||||
Directory.CreateDirectory(destinationDir);
|
||||
|
||||
foreach (FileInfo file in dir.GetFiles())
|
||||
string correctApplicationDataDirectoryPath =
|
||||
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), DefaultBaseDir);
|
||||
if (IsPathSymlink(correctApplicationDataDirectoryPath))
|
||||
{
|
||||
if (file.Name == ".DS_Store")
|
||||
//copy the files somewhere temporarily
|
||||
string tempPath = Path.Combine(Path.GetTempPath(), DefaultBaseDir);
|
||||
try
|
||||
{
|
||||
continue;
|
||||
FileSystemUtils.CopyDirectory(correctApplicationDataDirectoryPath, tempPath, true);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application,
|
||||
$"Critical error copying Ryujinx application data into the temp folder. {exception}");
|
||||
try
|
||||
{
|
||||
FileSystemInfo resolvedDirectoryInfo =
|
||||
Directory.ResolveLinkTarget(correctApplicationDataDirectoryPath, true);
|
||||
string resolvedPath = resolvedDirectoryInfo.FullName;
|
||||
Logger.Error?.Print(LogClass.Application, $"Please manually move your Ryujinx data from {resolvedPath} to {correctApplicationDataDirectoryPath}, and remove the symlink.");
|
||||
}
|
||||
catch (Exception symlinkException)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application, $"Unable to resolve the symlink for Ryujinx application data: {symlinkException}. Follow the symlink at {correctApplicationDataDirectoryPath} and move your data back to the Application Support folder.");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
file.CopyTo(Path.Combine(destinationDir, file.Name));
|
||||
}
|
||||
//delete the symlink
|
||||
try
|
||||
{
|
||||
//This will fail if this is an actual directory, so there is no way we can actually delete user data here.
|
||||
File.Delete(correctApplicationDataDirectoryPath);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application,
|
||||
$"Critical error deleting the Ryujinx application data folder symlink at {correctApplicationDataDirectoryPath}. {exception}");
|
||||
try
|
||||
{
|
||||
FileSystemInfo resolvedDirectoryInfo =
|
||||
Directory.ResolveLinkTarget(correctApplicationDataDirectoryPath, true);
|
||||
string resolvedPath = resolvedDirectoryInfo.FullName;
|
||||
Logger.Error?.Print(LogClass.Application, $"Please manually move your Ryujinx data from {resolvedPath} to {correctApplicationDataDirectoryPath}, and remove the symlink.");
|
||||
}
|
||||
catch (Exception symlinkException)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application, $"Unable to resolve the symlink for Ryujinx application data: {symlinkException}. Follow the symlink at {correctApplicationDataDirectoryPath} and move your data back to the Application Support folder.");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (DirectoryInfo subDir in subDirs)
|
||||
{
|
||||
CopyDirectory(subDir.FullName, Path.Combine(destinationDir, subDir.Name));
|
||||
//put the files back
|
||||
try
|
||||
{
|
||||
FileSystemUtils.CopyDirectory(tempPath, correctApplicationDataDirectoryPath, true);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application,
|
||||
$"Critical error copying Ryujinx application data into the correct location. {exception}. Please manually move your application data from {tempPath} to {correctApplicationDataDirectoryPath}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,7 @@ namespace Ryujinx.Common.Configuration.Hid
|
||||
{
|
||||
public Key ToggleVsync { get; set; }
|
||||
public Key Screenshot { get; set; }
|
||||
public Key ShowUi { get; set; }
|
||||
public Key ShowUI { get; set; }
|
||||
public Key Pause { get; set; }
|
||||
public Key ToggleMute { get; set; }
|
||||
public Key ResScaleUp { get; set; }
|
||||
|
9
src/Ryujinx.Common/Configuration/Mod.cs
Normal file
9
src/Ryujinx.Common/Configuration/Mod.cs
Normal file
@ -0,0 +1,9 @@
|
||||
namespace Ryujinx.Common.Configuration
|
||||
{
|
||||
public class Mod
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Path { get; set; }
|
||||
public bool Enabled { get; set; }
|
||||
}
|
||||
}
|
14
src/Ryujinx.Common/Configuration/ModMetadata.cs
Normal file
14
src/Ryujinx.Common/Configuration/ModMetadata.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.Common.Configuration
|
||||
{
|
||||
public struct ModMetadata
|
||||
{
|
||||
public List<Mod> Mods { get; set; }
|
||||
|
||||
public ModMetadata()
|
||||
{
|
||||
Mods = new List<Mod>();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Configuration
|
||||
{
|
||||
[JsonSourceGenerationOptions(WriteIndented = true)]
|
||||
[JsonSerializable(typeof(ModMetadata))]
|
||||
public partial class ModMetadataJsonSerializerContext : JsonSerializerContext
|
||||
{
|
||||
}
|
||||
}
|
@ -70,7 +70,7 @@ namespace Ryujinx.Common.Logging
|
||||
ServiceVi,
|
||||
SurfaceFlinger,
|
||||
TamperMachine,
|
||||
Ui,
|
||||
UI,
|
||||
Vic,
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ using Ryujinx.Common.SystemInterop;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
|
||||
@ -22,6 +23,9 @@ namespace Ryujinx.Common.Logging
|
||||
|
||||
public readonly struct Log
|
||||
{
|
||||
private static readonly string _homeDir = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
|
||||
private static readonly string _homeDirRedacted = Path.Combine(Directory.GetParent(_homeDir).FullName, "[redacted]");
|
||||
|
||||
internal readonly LogLevel Level;
|
||||
|
||||
internal Log(LogLevel level)
|
||||
@ -100,7 +104,12 @@ namespace Ryujinx.Common.Logging
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static string FormatMessage(LogClass logClass, string caller, string message) => $"{logClass} {caller}: {message}";
|
||||
private static string FormatMessage(LogClass logClass, string caller, string message)
|
||||
{
|
||||
message = message.Replace(_homeDir, _homeDirRedacted);
|
||||
|
||||
return $"{logClass} {caller}: {message}";
|
||||
}
|
||||
}
|
||||
|
||||
public static Log? Debug { get; private set; }
|
||||
|
@ -13,31 +13,82 @@ namespace Ryujinx.Common.Logging.Targets
|
||||
|
||||
string ILogTarget.Name { get => _name; }
|
||||
|
||||
public FileLogTarget(string path, string name)
|
||||
: this(path, name, FileShare.Read, FileMode.Append)
|
||||
{ }
|
||||
public FileLogTarget(string name, FileStream fileStream)
|
||||
{
|
||||
_name = name;
|
||||
_logWriter = new StreamWriter(fileStream);
|
||||
_formatter = new DefaultLogFormatter();
|
||||
}
|
||||
|
||||
public FileLogTarget(string path, string name, FileShare fileShare, FileMode fileMode)
|
||||
public static FileStream PrepareLogFile(string path)
|
||||
{
|
||||
// Ensure directory is present
|
||||
DirectoryInfo logDir = new(Path.Combine(path, "Logs"));
|
||||
logDir.Create();
|
||||
DirectoryInfo logDir;
|
||||
try
|
||||
{
|
||||
logDir = new DirectoryInfo(path);
|
||||
}
|
||||
catch (ArgumentException exception)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application, $"Logging directory path ('{path}') was invalid: {exception}");
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
logDir.Create();
|
||||
}
|
||||
catch (IOException exception)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application, $"Logging directory could not be created '{logDir}': {exception}");
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Clean up old logs, should only keep 3
|
||||
FileInfo[] files = logDir.GetFiles("*.log").OrderBy((info => info.CreationTime)).ToArray();
|
||||
for (int i = 0; i < files.Length - 2; i++)
|
||||
{
|
||||
files[i].Delete();
|
||||
try
|
||||
{
|
||||
files[i].Delete();
|
||||
}
|
||||
catch (UnauthorizedAccessException exception)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application, $"Old log file could not be deleted '{files[i].FullName}': {exception}");
|
||||
|
||||
return null;
|
||||
}
|
||||
catch (IOException exception)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application, $"Old log file could not be deleted '{files[i].FullName}': {exception}");
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
string version = ReleaseInformation.GetVersion();
|
||||
string version = ReleaseInformation.Version;
|
||||
|
||||
// Get path for the current time
|
||||
path = Path.Combine(logDir.FullName, $"Ryujinx_{version}_{DateTime.Now:yyyy-MM-dd_HH-mm-ss}.log");
|
||||
|
||||
_name = name;
|
||||
_logWriter = new StreamWriter(File.Open(path, fileMode, FileAccess.Write, fileShare));
|
||||
_formatter = new DefaultLogFormatter();
|
||||
try
|
||||
{
|
||||
return File.Open(path, FileMode.Append, FileAccess.Write, FileShare.Read);
|
||||
}
|
||||
catch (UnauthorizedAccessException exception)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application, $"Log file could not be created '{path}': {exception}");
|
||||
|
||||
return null;
|
||||
}
|
||||
catch (IOException exception)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application, $"Log file could not be created '{path}': {exception}");
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void Log(object sender, LogEventArgs args)
|
||||
|
@ -744,6 +744,17 @@ namespace Ryujinx.Common.Memory
|
||||
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length);
|
||||
}
|
||||
|
||||
public struct Array65<T> : IArray<T> where T : unmanaged
|
||||
{
|
||||
T _e0;
|
||||
Array64<T> _other;
|
||||
public readonly int Length => 65;
|
||||
public ref T this[int index] => ref AsSpan()[index];
|
||||
|
||||
[Pure]
|
||||
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length);
|
||||
}
|
||||
|
||||
public struct Array73<T> : IArray<T> where T : unmanaged
|
||||
{
|
||||
T _e0;
|
||||
|
@ -63,6 +63,18 @@ namespace Ryujinx.Common.Memory
|
||||
public Span<byte> AsSpan() => MemoryMarshal.CreateSpan(ref _element, Size);
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Size = Size, Pack = 1)]
|
||||
public struct ByteArray3000 : IArray<byte>
|
||||
{
|
||||
private const int Size = 3000;
|
||||
|
||||
byte _element;
|
||||
|
||||
public readonly int Length => Size;
|
||||
public ref byte this[int index] => ref AsSpan()[index];
|
||||
public Span<byte> AsSpan() => MemoryMarshal.CreateSpan(ref _element, Size);
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Size = Size, Pack = 1)]
|
||||
public struct ByteArray4096 : IArray<byte>
|
||||
{
|
||||
|
@ -1,5 +1,3 @@
|
||||
using Ryujinx.Common.Configuration;
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Ryujinx.Common
|
||||
@ -9,50 +7,25 @@ namespace Ryujinx.Common
|
||||
{
|
||||
private const string FlatHubChannelOwner = "flathub";
|
||||
|
||||
public const string BuildVersion = "%%RYUJINX_BUILD_VERSION%%";
|
||||
public const string BuildGitHash = "%%RYUJINX_BUILD_GIT_HASH%%";
|
||||
public const string ReleaseChannelName = "%%RYUJINX_TARGET_RELEASE_CHANNEL_NAME%%";
|
||||
private const string BuildVersion = "%%RYUJINX_BUILD_VERSION%%";
|
||||
private const string BuildGitHash = "%%RYUJINX_BUILD_GIT_HASH%%";
|
||||
private const string ReleaseChannelName = "%%RYUJINX_TARGET_RELEASE_CHANNEL_NAME%%";
|
||||
private const string ConfigFileName = "%%RYUJINX_CONFIG_FILE_NAME%%";
|
||||
|
||||
public const string ReleaseChannelOwner = "%%RYUJINX_TARGET_RELEASE_CHANNEL_OWNER%%";
|
||||
public const string ReleaseChannelRepo = "%%RYUJINX_TARGET_RELEASE_CHANNEL_REPO%%";
|
||||
|
||||
public static bool IsValid()
|
||||
{
|
||||
return !BuildGitHash.StartsWith("%%") &&
|
||||
!ReleaseChannelName.StartsWith("%%") &&
|
||||
!ReleaseChannelOwner.StartsWith("%%") &&
|
||||
!ReleaseChannelRepo.StartsWith("%%");
|
||||
}
|
||||
public static string ConfigName => !ConfigFileName.StartsWith("%%") ? ConfigFileName : "Config.json";
|
||||
|
||||
public static bool IsFlatHubBuild()
|
||||
{
|
||||
return IsValid() && ReleaseChannelOwner.Equals(FlatHubChannelOwner);
|
||||
}
|
||||
public static bool IsValid =>
|
||||
!BuildGitHash.StartsWith("%%") &&
|
||||
!ReleaseChannelName.StartsWith("%%") &&
|
||||
!ReleaseChannelOwner.StartsWith("%%") &&
|
||||
!ReleaseChannelRepo.StartsWith("%%") &&
|
||||
!ConfigFileName.StartsWith("%%");
|
||||
|
||||
public static string GetVersion()
|
||||
{
|
||||
if (IsValid())
|
||||
{
|
||||
return BuildVersion;
|
||||
}
|
||||
public static bool IsFlatHubBuild => IsValid && ReleaseChannelOwner.Equals(FlatHubChannelOwner);
|
||||
|
||||
return Assembly.GetEntryAssembly().GetCustomAttribute<AssemblyInformationalVersionAttribute>().InformationalVersion;
|
||||
}
|
||||
|
||||
#if FORCE_EXTERNAL_BASE_DIR
|
||||
public static string GetBaseApplicationDirectory()
|
||||
{
|
||||
return AppDataManager.BaseDirPath;
|
||||
}
|
||||
#else
|
||||
public static string GetBaseApplicationDirectory()
|
||||
{
|
||||
if (IsFlatHubBuild() || OperatingSystem.IsMacOS())
|
||||
{
|
||||
return AppDataManager.BaseDirPath;
|
||||
}
|
||||
|
||||
return AppDomain.CurrentDomain.BaseDirectory;
|
||||
}
|
||||
#endif
|
||||
public static string Version => IsValid ? BuildVersion : Assembly.GetEntryAssembly()!.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion;
|
||||
}
|
||||
}
|
||||
|
48
src/Ryujinx.Common/Utilities/FileSystemUtils.cs
Normal file
48
src/Ryujinx.Common/Utilities/FileSystemUtils.cs
Normal file
@ -0,0 +1,48 @@
|
||||
using System.IO;
|
||||
|
||||
namespace Ryujinx.Common.Utilities
|
||||
{
|
||||
public static class FileSystemUtils
|
||||
{
|
||||
public static void CopyDirectory(string sourceDir, string destinationDir, bool recursive)
|
||||
{
|
||||
// Get information about the source directory
|
||||
var dir = new DirectoryInfo(sourceDir);
|
||||
|
||||
// Check if the source directory exists
|
||||
if (!dir.Exists)
|
||||
{
|
||||
throw new DirectoryNotFoundException($"Source directory not found: {dir.FullName}");
|
||||
}
|
||||
|
||||
// Cache directories before we start copying
|
||||
DirectoryInfo[] dirs = dir.GetDirectories();
|
||||
|
||||
// Create the destination directory
|
||||
Directory.CreateDirectory(destinationDir);
|
||||
|
||||
// Get the files in the source directory and copy to the destination directory
|
||||
foreach (FileInfo file in dir.GetFiles())
|
||||
{
|
||||
string targetFilePath = Path.Combine(destinationDir, file.Name);
|
||||
file.CopyTo(targetFilePath);
|
||||
}
|
||||
|
||||
// If recursive and copying subdirectories, recursively call this method
|
||||
if (recursive)
|
||||
{
|
||||
foreach (DirectoryInfo subDir in dirs)
|
||||
{
|
||||
string newDestinationDir = Path.Combine(destinationDir, subDir.Name);
|
||||
CopyDirectory(subDir.FullName, newDestinationDir, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void MoveDirectory(string sourceDir, string destinationDir)
|
||||
{
|
||||
CopyDirectory(sourceDir, destinationDir, true);
|
||||
Directory.Delete(sourceDir, true);
|
||||
}
|
||||
}
|
||||
}
|
@ -10,6 +10,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm32
|
||||
public readonly List<InstInfo> Instructions;
|
||||
public readonly bool EndsWithBranch;
|
||||
public readonly bool HasHostCall;
|
||||
public readonly bool HasHostCallSkipContext;
|
||||
public readonly bool IsTruncated;
|
||||
public readonly bool IsLoopEnd;
|
||||
public readonly bool IsThumb;
|
||||
@ -20,6 +21,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm32
|
||||
List<InstInfo> instructions,
|
||||
bool endsWithBranch,
|
||||
bool hasHostCall,
|
||||
bool hasHostCallSkipContext,
|
||||
bool isTruncated,
|
||||
bool isLoopEnd,
|
||||
bool isThumb)
|
||||
@ -31,6 +33,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm32
|
||||
Instructions = instructions;
|
||||
EndsWithBranch = endsWithBranch;
|
||||
HasHostCall = hasHostCall;
|
||||
HasHostCallSkipContext = hasHostCallSkipContext;
|
||||
IsTruncated = isTruncated;
|
||||
IsLoopEnd = isLoopEnd;
|
||||
IsThumb = isThumb;
|
||||
@ -57,6 +60,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm32
|
||||
Instructions.GetRange(0, splitIndex),
|
||||
false,
|
||||
HasHostCall,
|
||||
HasHostCallSkipContext,
|
||||
false,
|
||||
false,
|
||||
IsThumb);
|
||||
@ -67,6 +71,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm32
|
||||
Instructions.GetRange(splitIndex, splitCount),
|
||||
EndsWithBranch,
|
||||
HasHostCall,
|
||||
HasHostCallSkipContext,
|
||||
IsTruncated,
|
||||
IsLoopEnd,
|
||||
IsThumb);
|
||||
|
@ -208,6 +208,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm32
|
||||
InstMeta meta;
|
||||
InstFlags extraFlags = InstFlags.None;
|
||||
bool hasHostCall = false;
|
||||
bool hasHostCallSkipContext = false;
|
||||
bool isTruncated = false;
|
||||
|
||||
do
|
||||
@ -246,9 +247,17 @@ namespace Ryujinx.Cpu.LightningJit.Arm32
|
||||
meta = InstTableA32<T>.GetMeta(encoding, cpuPreset.Version, cpuPreset.Features);
|
||||
}
|
||||
|
||||
if (meta.Name.IsSystemOrCall() && !hasHostCall)
|
||||
if (meta.Name.IsSystemOrCall())
|
||||
{
|
||||
hasHostCall = meta.Name.IsCall() || InstEmitSystem.NeedsCall(meta.Name);
|
||||
if (!hasHostCall)
|
||||
{
|
||||
hasHostCall = InstEmitSystem.NeedsCall(meta.Name);
|
||||
}
|
||||
|
||||
if (!hasHostCallSkipContext)
|
||||
{
|
||||
hasHostCallSkipContext = meta.Name.IsCall() || InstEmitSystem.NeedsCallSkipContext(meta.Name);
|
||||
}
|
||||
}
|
||||
|
||||
insts.Add(new(encoding, meta.Name, meta.EmitFunc, meta.Flags | extraFlags));
|
||||
@ -259,8 +268,8 @@ namespace Ryujinx.Cpu.LightningJit.Arm32
|
||||
|
||||
if (!isTruncated && IsBackwardsBranch(meta.Name, encoding))
|
||||
{
|
||||
hasHostCall = true;
|
||||
isLoopEnd = true;
|
||||
hasHostCallSkipContext = true;
|
||||
}
|
||||
|
||||
return new(
|
||||
@ -269,6 +278,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm32
|
||||
insts,
|
||||
!isTruncated,
|
||||
hasHostCall,
|
||||
hasHostCallSkipContext,
|
||||
isTruncated,
|
||||
isLoopEnd,
|
||||
isThumb);
|
||||
|
@ -6,6 +6,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm32
|
||||
{
|
||||
public readonly List<Block> Blocks;
|
||||
public readonly bool HasHostCall;
|
||||
public readonly bool HasHostCallSkipContext;
|
||||
public readonly bool IsTruncated;
|
||||
|
||||
public MultiBlock(List<Block> blocks)
|
||||
@ -15,12 +16,14 @@ namespace Ryujinx.Cpu.LightningJit.Arm32
|
||||
Block block = blocks[0];
|
||||
|
||||
HasHostCall = block.HasHostCall;
|
||||
HasHostCallSkipContext = block.HasHostCallSkipContext;
|
||||
|
||||
for (int index = 1; index < blocks.Count; index++)
|
||||
{
|
||||
block = blocks[index];
|
||||
|
||||
HasHostCall |= block.HasHostCall;
|
||||
HasHostCallSkipContext |= block.HasHostCallSkipContext;
|
||||
}
|
||||
|
||||
block = blocks[^1];
|
||||
|
@ -106,6 +106,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm32
|
||||
if ((regMask & AbiConstants.ReservedRegsMask) == 0)
|
||||
{
|
||||
_gprMask |= regMask;
|
||||
UsedGprsMask |= regMask;
|
||||
|
||||
return firstCalleeSaved;
|
||||
}
|
||||
|
@ -305,12 +305,23 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
|
||||
ForceConditionalEnd(cgContext, ref lastCondition, lastConditionIp);
|
||||
}
|
||||
|
||||
int reservedStackSize = 0;
|
||||
|
||||
if (multiBlock.HasHostCall)
|
||||
{
|
||||
reservedStackSize = CalculateStackSizeForCallSpill(regAlloc.UsedGprsMask, regAlloc.UsedFpSimdMask, UsablePStateMask);
|
||||
}
|
||||
else if (multiBlock.HasHostCallSkipContext)
|
||||
{
|
||||
reservedStackSize = 2 * sizeof(ulong); // Context and page table pointers.
|
||||
}
|
||||
|
||||
RegisterSaveRestore rsr = new(
|
||||
regAlloc.UsedGprsMask & AbiConstants.GprCalleeSavedRegsMask,
|
||||
regAlloc.UsedFpSimdMask & AbiConstants.FpSimdCalleeSavedRegsMask,
|
||||
OperandType.FP64,
|
||||
multiBlock.HasHostCall,
|
||||
multiBlock.HasHostCall ? CalculateStackSizeForCallSpill(regAlloc.UsedGprsMask, regAlloc.UsedFpSimdMask, UsablePStateMask) : 0);
|
||||
multiBlock.HasHostCall || multiBlock.HasHostCallSkipContext,
|
||||
reservedStackSize);
|
||||
|
||||
TailMerger tailMerger = new();
|
||||
|
||||
@ -596,7 +607,8 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
|
||||
name == InstName.Ldm ||
|
||||
name == InstName.Ldmda ||
|
||||
name == InstName.Ldmdb ||
|
||||
name == InstName.Ldmib)
|
||||
name == InstName.Ldmib ||
|
||||
name == InstName.Pop)
|
||||
{
|
||||
// Arm32 does not have a return instruction, instead returns are implemented
|
||||
// either using BX LR (for leaf functions), or POP { ... PC }.
|
||||
@ -711,7 +723,14 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
|
||||
switch (type)
|
||||
{
|
||||
case BranchType.SyncPoint:
|
||||
InstEmitSystem.WriteSyncPoint(context.Writer, context.RegisterAllocator, context.TailMerger, context.GetReservedStackOffset());
|
||||
InstEmitSystem.WriteSyncPoint(
|
||||
context.Writer,
|
||||
ref asm,
|
||||
context.RegisterAllocator,
|
||||
context.TailMerger,
|
||||
context.GetReservedStackOffset(),
|
||||
context.StoreToContext,
|
||||
context.LoadFromContext);
|
||||
break;
|
||||
case BranchType.SoftwareInterrupt:
|
||||
context.StoreToContext();
|
||||
|
@ -199,12 +199,12 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
|
||||
}
|
||||
}
|
||||
|
||||
private static void WriteSpillSkipContext(ref Assembler asm, RegisterAllocator regAlloc, int spillOffset)
|
||||
public static void WriteSpillSkipContext(ref Assembler asm, RegisterAllocator regAlloc, int spillOffset)
|
||||
{
|
||||
WriteSpillOrFillSkipContext(ref asm, regAlloc, spillOffset, spill: true);
|
||||
}
|
||||
|
||||
private static void WriteFillSkipContext(ref Assembler asm, RegisterAllocator regAlloc, int spillOffset)
|
||||
public static void WriteFillSkipContext(ref Assembler asm, RegisterAllocator regAlloc, int spillOffset)
|
||||
{
|
||||
WriteSpillOrFillSkipContext(ref asm, regAlloc, spillOffset, spill: false);
|
||||
}
|
||||
|
@ -354,11 +354,18 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
|
||||
// All instructions that might do a host call should be included here.
|
||||
// That is required to reserve space on the stack for caller saved registers.
|
||||
|
||||
return name == InstName.Mrrc;
|
||||
}
|
||||
|
||||
public static bool NeedsCallSkipContext(InstName name)
|
||||
{
|
||||
// All instructions that might do a host call should be included here.
|
||||
// That is required to reserve space on the stack for caller saved registers.
|
||||
|
||||
switch (name)
|
||||
{
|
||||
case InstName.Mcr:
|
||||
case InstName.Mrc:
|
||||
case InstName.Mrrc:
|
||||
case InstName.Svc:
|
||||
case InstName.Udf:
|
||||
return true;
|
||||
@ -372,7 +379,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
|
||||
Assembler asm = new(writer);
|
||||
|
||||
WriteCall(ref asm, regAlloc, GetBkptHandlerPtr(), skipContext: true, spillBaseOffset, null, pc, imm);
|
||||
WriteSyncPoint(writer, ref asm, regAlloc, tailMerger, skipContext: true, spillBaseOffset);
|
||||
WriteSyncPoint(writer, ref asm, regAlloc, tailMerger, spillBaseOffset);
|
||||
}
|
||||
|
||||
public static void WriteSvc(CodeWriter writer, RegisterAllocator regAlloc, TailMerger tailMerger, int spillBaseOffset, uint pc, uint svcId)
|
||||
@ -380,7 +387,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
|
||||
Assembler asm = new(writer);
|
||||
|
||||
WriteCall(ref asm, regAlloc, GetSvcHandlerPtr(), skipContext: true, spillBaseOffset, null, pc, svcId);
|
||||
WriteSyncPoint(writer, ref asm, regAlloc, tailMerger, skipContext: true, spillBaseOffset);
|
||||
WriteSyncPoint(writer, ref asm, regAlloc, tailMerger, spillBaseOffset);
|
||||
}
|
||||
|
||||
public static void WriteUdf(CodeWriter writer, RegisterAllocator regAlloc, TailMerger tailMerger, int spillBaseOffset, uint pc, uint imm)
|
||||
@ -388,7 +395,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
|
||||
Assembler asm = new(writer);
|
||||
|
||||
WriteCall(ref asm, regAlloc, GetUdfHandlerPtr(), skipContext: true, spillBaseOffset, null, pc, imm);
|
||||
WriteSyncPoint(writer, ref asm, regAlloc, tailMerger, skipContext: true, spillBaseOffset);
|
||||
WriteSyncPoint(writer, ref asm, regAlloc, tailMerger, spillBaseOffset);
|
||||
}
|
||||
|
||||
public static void WriteReadCntpct(CodeWriter writer, RegisterAllocator regAlloc, int spillBaseOffset, int rt, int rt2)
|
||||
@ -422,14 +429,14 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
|
||||
WriteFill(ref asm, regAlloc, resultMask, skipContext: false, spillBaseOffset, tempRegister);
|
||||
}
|
||||
|
||||
public static void WriteSyncPoint(CodeWriter writer, RegisterAllocator regAlloc, TailMerger tailMerger, int spillBaseOffset)
|
||||
{
|
||||
Assembler asm = new(writer);
|
||||
|
||||
WriteSyncPoint(writer, ref asm, regAlloc, tailMerger, skipContext: false, spillBaseOffset);
|
||||
}
|
||||
|
||||
private static void WriteSyncPoint(CodeWriter writer, ref Assembler asm, RegisterAllocator regAlloc, TailMerger tailMerger, bool skipContext, int spillBaseOffset)
|
||||
public static void WriteSyncPoint(
|
||||
CodeWriter writer,
|
||||
ref Assembler asm,
|
||||
RegisterAllocator regAlloc,
|
||||
TailMerger tailMerger,
|
||||
int spillBaseOffset,
|
||||
Action storeToContext = null,
|
||||
Action loadFromContext = null)
|
||||
{
|
||||
int tempRegister = regAlloc.AllocateTempGprRegister();
|
||||
|
||||
@ -440,7 +447,8 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
|
||||
int branchIndex = writer.InstructionPointer;
|
||||
asm.Cbnz(rt, 0);
|
||||
|
||||
WriteSpill(ref asm, regAlloc, 1u << tempRegister, skipContext, spillBaseOffset, tempRegister);
|
||||
storeToContext?.Invoke();
|
||||
WriteSpill(ref asm, regAlloc, 1u << tempRegister, skipContext: true, spillBaseOffset, tempRegister);
|
||||
|
||||
Operand rn = Register(tempRegister == 0 ? 1 : 0);
|
||||
|
||||
@ -449,7 +457,8 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
|
||||
|
||||
tailMerger.AddConditionalZeroReturn(writer, asm, Register(0, OperandType.I32));
|
||||
|
||||
WriteFill(ref asm, regAlloc, 1u << tempRegister, skipContext, spillBaseOffset, tempRegister);
|
||||
WriteFill(ref asm, regAlloc, 1u << tempRegister, skipContext: true, spillBaseOffset, tempRegister);
|
||||
loadFromContext?.Invoke();
|
||||
|
||||
asm.LdrRiUn(rt, Register(regAlloc.FixedContextRegister), NativeContextOffsets.CounterOffset);
|
||||
|
||||
@ -514,18 +523,31 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
|
||||
|
||||
private static void WriteSpill(ref Assembler asm, RegisterAllocator regAlloc, uint exceptMask, bool skipContext, int spillOffset, int tempRegister)
|
||||
{
|
||||
WriteSpillOrFill(ref asm, regAlloc, skipContext, exceptMask, spillOffset, tempRegister, spill: true);
|
||||
if (skipContext)
|
||||
{
|
||||
InstEmitFlow.WriteSpillSkipContext(ref asm, regAlloc, spillOffset);
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteSpillOrFill(ref asm, regAlloc, exceptMask, spillOffset, tempRegister, spill: true);
|
||||
}
|
||||
}
|
||||
|
||||
private static void WriteFill(ref Assembler asm, RegisterAllocator regAlloc, uint exceptMask, bool skipContext, int spillOffset, int tempRegister)
|
||||
{
|
||||
WriteSpillOrFill(ref asm, regAlloc, skipContext, exceptMask, spillOffset, tempRegister, spill: false);
|
||||
if (skipContext)
|
||||
{
|
||||
InstEmitFlow.WriteFillSkipContext(ref asm, regAlloc, spillOffset);
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteSpillOrFill(ref asm, regAlloc, exceptMask, spillOffset, tempRegister, spill: false);
|
||||
}
|
||||
}
|
||||
|
||||
private static void WriteSpillOrFill(
|
||||
ref Assembler asm,
|
||||
RegisterAllocator regAlloc,
|
||||
bool skipContext,
|
||||
uint exceptMask,
|
||||
int spillOffset,
|
||||
int tempRegister,
|
||||
@ -533,11 +555,6 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
|
||||
{
|
||||
uint gprMask = regAlloc.UsedGprsMask & ~(AbiConstants.GprCalleeSavedRegsMask | exceptMask);
|
||||
|
||||
if (skipContext)
|
||||
{
|
||||
gprMask &= ~Compiler.UsableGprsMask;
|
||||
}
|
||||
|
||||
if (!spill)
|
||||
{
|
||||
// We must reload the status register before reloading the GPRs,
|
||||
@ -600,11 +617,6 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
|
||||
|
||||
uint fpSimdMask = regAlloc.UsedFpSimdMask;
|
||||
|
||||
if (skipContext)
|
||||
{
|
||||
fpSimdMask &= ~Compiler.UsableFpSimdMask;
|
||||
}
|
||||
|
||||
while (fpSimdMask != 0)
|
||||
{
|
||||
int reg = BitOperations.TrailingZeroCount(fpSimdMask);
|
||||
|
@ -8,7 +8,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64
|
||||
{
|
||||
static class Decoder
|
||||
{
|
||||
private const int MaxInstructionsPerBlock = 1000;
|
||||
private const int MaxInstructionsPerFunction = 10000;
|
||||
|
||||
private const uint NzcvFlags = 0xfu << 28;
|
||||
private const uint CFlag = 0x1u << 29;
|
||||
@ -22,10 +22,11 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64
|
||||
|
||||
bool hasHostCall = false;
|
||||
bool hasMemoryInstruction = false;
|
||||
int totalInsts = 0;
|
||||
|
||||
while (true)
|
||||
{
|
||||
Block block = Decode(cpuPreset, memoryManager, address, ref useMask, ref hasHostCall, ref hasMemoryInstruction);
|
||||
Block block = Decode(cpuPreset, memoryManager, address, ref totalInsts, ref useMask, ref hasHostCall, ref hasMemoryInstruction);
|
||||
|
||||
if (!block.IsTruncated && TryGetBranchTarget(block, out ulong targetAddress))
|
||||
{
|
||||
@ -230,6 +231,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64
|
||||
CpuPreset cpuPreset,
|
||||
IMemoryManager memoryManager,
|
||||
ulong address,
|
||||
ref int totalInsts,
|
||||
ref RegisterMask useMask,
|
||||
ref bool hasHostCall,
|
||||
ref bool hasMemoryInstruction)
|
||||
@ -272,7 +274,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64
|
||||
|
||||
uint tempGprUseMask = gprUseMask | instGprReadMask | instGprWriteMask;
|
||||
|
||||
if (CalculateAvailableTemps(tempGprUseMask) < CalculateRequiredGprTemps(tempGprUseMask) || insts.Count >= MaxInstructionsPerBlock)
|
||||
if (CalculateAvailableTemps(tempGprUseMask) < CalculateRequiredGprTemps(tempGprUseMask) || totalInsts++ >= MaxInstructionsPerFunction)
|
||||
{
|
||||
isTruncated = true;
|
||||
address -= 4UL;
|
||||
|
@ -147,6 +147,8 @@ namespace Ryujinx.Graphics.GAL
|
||||
A1B5G5R5Unorm,
|
||||
B8G8R8A8Unorm,
|
||||
B8G8R8A8Srgb,
|
||||
B10G10R10A2Unorm,
|
||||
X8UintD24Unorm,
|
||||
}
|
||||
|
||||
public static class FormatExtensions
|
||||
@ -260,6 +262,7 @@ namespace Ryujinx.Graphics.GAL
|
||||
case Format.R10G10B10A2Sint:
|
||||
case Format.R10G10B10A2Uscaled:
|
||||
case Format.R10G10B10A2Sscaled:
|
||||
case Format.B10G10R10A2Unorm:
|
||||
return 4;
|
||||
|
||||
case Format.S8Uint:
|
||||
@ -267,6 +270,7 @@ namespace Ryujinx.Graphics.GAL
|
||||
case Format.D16Unorm:
|
||||
return 2;
|
||||
case Format.S8UintD24Unorm:
|
||||
case Format.X8UintD24Unorm:
|
||||
case Format.D32Float:
|
||||
case Format.D24UnormS8Uint:
|
||||
return 4;
|
||||
@ -347,6 +351,7 @@ namespace Ryujinx.Graphics.GAL
|
||||
case Format.D16Unorm:
|
||||
case Format.D24UnormS8Uint:
|
||||
case Format.S8UintD24Unorm:
|
||||
case Format.X8UintD24Unorm:
|
||||
case Format.D32Float:
|
||||
case Format.D32FloatS8Uint:
|
||||
return true;
|
||||
@ -451,6 +456,7 @@ namespace Ryujinx.Graphics.GAL
|
||||
case Format.R32G32Uint:
|
||||
case Format.B8G8R8A8Unorm:
|
||||
case Format.B8G8R8A8Srgb:
|
||||
case Format.B10G10R10A2Unorm:
|
||||
case Format.R10G10B10A2Unorm:
|
||||
case Format.R10G10B10A2Uint:
|
||||
case Format.R8G8B8A8Unorm:
|
||||
@ -611,6 +617,7 @@ namespace Ryujinx.Graphics.GAL
|
||||
case Format.B5G5R5A1Unorm:
|
||||
case Format.B8G8R8A8Unorm:
|
||||
case Format.B8G8R8A8Srgb:
|
||||
case Format.B10G10R10A2Unorm:
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -629,6 +636,7 @@ namespace Ryujinx.Graphics.GAL
|
||||
case Format.D16Unorm:
|
||||
case Format.D24UnormS8Uint:
|
||||
case Format.S8UintD24Unorm:
|
||||
case Format.X8UintD24Unorm:
|
||||
case Format.D32Float:
|
||||
case Format.D32FloatS8Uint:
|
||||
case Format.S8Uint:
|
||||
|
@ -4,11 +4,13 @@ namespace Ryujinx.Graphics.GAL
|
||||
{
|
||||
public string GpuVendor { get; }
|
||||
public string GpuModel { get; }
|
||||
public string GpuDriver { get; }
|
||||
|
||||
public HardwareInfo(string gpuVendor, string gpuModel)
|
||||
public HardwareInfo(string gpuVendor, string gpuModel, string gpuDriver)
|
||||
{
|
||||
GpuVendor = gpuVendor;
|
||||
GpuModel = gpuModel;
|
||||
GpuDriver = gpuDriver;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -58,7 +58,7 @@ namespace Ryujinx.Graphics.GAL
|
||||
|
||||
void SetIndexBuffer(BufferRange buffer, IndexType type);
|
||||
|
||||
void SetImage(int binding, ITexture texture, Format imageFormat);
|
||||
void SetImage(ShaderStage stage, int binding, ITexture texture, Format imageFormat);
|
||||
|
||||
void SetLineParameters(float width, bool smooth);
|
||||
|
||||
|
@ -1,17 +1,20 @@
|
||||
using Ryujinx.Graphics.GAL.Multithreading.Model;
|
||||
using Ryujinx.Graphics.GAL.Multithreading.Resources;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
|
||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
||||
{
|
||||
struct SetImageCommand : IGALCommand, IGALCommand<SetImageCommand>
|
||||
{
|
||||
public readonly CommandType CommandType => CommandType.SetImage;
|
||||
private ShaderStage _stage;
|
||||
private int _binding;
|
||||
private TableRef<ITexture> _texture;
|
||||
private Format _imageFormat;
|
||||
|
||||
public void Set(int binding, TableRef<ITexture> texture, Format imageFormat)
|
||||
public void Set(ShaderStage stage, int binding, TableRef<ITexture> texture, Format imageFormat)
|
||||
{
|
||||
_stage = stage;
|
||||
_binding = binding;
|
||||
_texture = texture;
|
||||
_imageFormat = imageFormat;
|
||||
@ -19,7 +22,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
||||
|
||||
public static void Run(ref SetImageCommand command, ThreadedRenderer threaded, IRenderer renderer)
|
||||
{
|
||||
renderer.Pipeline.SetImage(command._binding, command._texture.GetAs<ThreadedTexture>(threaded)?.Base, command._imageFormat);
|
||||
renderer.Pipeline.SetImage(command._stage, command._binding, command._texture.GetAs<ThreadedTexture>(threaded)?.Base, command._imageFormat);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -177,9 +177,9 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
||||
_renderer.QueueCommand();
|
||||
}
|
||||
|
||||
public void SetImage(int binding, ITexture texture, Format imageFormat)
|
||||
public void SetImage(ShaderStage stage, int binding, ITexture texture, Format imageFormat)
|
||||
{
|
||||
_renderer.New<SetImageCommand>().Set(binding, Ref(texture), imageFormat);
|
||||
_renderer.New<SetImageCommand>().Set(stage, binding, Ref(texture), imageFormat);
|
||||
_renderer.QueueCommand();
|
||||
}
|
||||
|
||||
|
@ -277,6 +277,14 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma
|
||||
|
||||
ReadOnlySpan<byte> srcSpan = memoryManager.GetSpan(srcGpuVa + (ulong)srcBaseOffset, srcSize, true);
|
||||
|
||||
// If remapping is disabled, we always copy the components directly, in order.
|
||||
// If it's enabled, but the mapping is just XYZW, we also copy them in order.
|
||||
bool isIdentityRemap = !remap ||
|
||||
(_state.State.SetRemapComponentsDstX == SetRemapComponentsDst.SrcX &&
|
||||
(dstComponents < 2 || _state.State.SetRemapComponentsDstY == SetRemapComponentsDst.SrcY) &&
|
||||
(dstComponents < 3 || _state.State.SetRemapComponentsDstZ == SetRemapComponentsDst.SrcZ) &&
|
||||
(dstComponents < 4 || _state.State.SetRemapComponentsDstW == SetRemapComponentsDst.SrcW));
|
||||
|
||||
bool completeSource = IsTextureCopyComplete(src, srcLinear, srcBpp, srcStride, xCount, yCount);
|
||||
bool completeDest = IsTextureCopyComplete(dst, dstLinear, dstBpp, dstStride, xCount, yCount);
|
||||
|
||||
@ -284,7 +292,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma
|
||||
// but only if we are doing a complete copy,
|
||||
// and not for block linear to linear copies, since those are typically accessed from the CPU.
|
||||
|
||||
if (completeSource && completeDest && !(dstLinear && !srcLinear))
|
||||
if (completeSource && completeDest && !(dstLinear && !srcLinear) && isIdentityRemap)
|
||||
{
|
||||
var target = memoryManager.Physical.TextureCache.FindTexture(
|
||||
memoryManager,
|
||||
@ -353,14 +361,6 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma
|
||||
TextureParams srcParams = new(srcRegionX, srcRegionY, srcBaseOffset, srcBpp, srcLinear, srcCalculator);
|
||||
TextureParams dstParams = new(dstRegionX, dstRegionY, dstBaseOffset, dstBpp, dstLinear, dstCalculator);
|
||||
|
||||
// If remapping is enabled, we always copy the components directly, in order.
|
||||
// If it's enabled, but the mapping is just XYZW, we also copy them in order.
|
||||
bool isIdentityRemap = !remap ||
|
||||
(_state.State.SetRemapComponentsDstX == SetRemapComponentsDst.SrcX &&
|
||||
(dstComponents < 2 || _state.State.SetRemapComponentsDstY == SetRemapComponentsDst.SrcY) &&
|
||||
(dstComponents < 3 || _state.State.SetRemapComponentsDstZ == SetRemapComponentsDst.SrcZ) &&
|
||||
(dstComponents < 4 || _state.State.SetRemapComponentsDstW == SetRemapComponentsDst.SrcW));
|
||||
|
||||
if (isIdentityRemap)
|
||||
{
|
||||
// The order of the components doesn't change, so we can just copy directly
|
||||
|
@ -26,6 +26,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
public const int PrimitiveRestartStateIndex = 12;
|
||||
public const int RenderTargetStateIndex = 27;
|
||||
|
||||
// Vertex buffers larger than this size will be clamped to the mapped size.
|
||||
private const ulong VertexBufferSizeToMappedSizeThreshold = 256 * 1024 * 1024; // 256 MB
|
||||
|
||||
private readonly GpuContext _context;
|
||||
private readonly GpuChannel _channel;
|
||||
private readonly DeviceStateWithShadow<ThreedClassState> _state;
|
||||
@ -1144,6 +1147,14 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
|
||||
size = Math.Min(size, maxVertexBufferSize);
|
||||
}
|
||||
else if (size > VertexBufferSizeToMappedSizeThreshold)
|
||||
{
|
||||
// Make sure we have a sane vertex buffer size, since in some cases applications
|
||||
// might set the "end address" of the vertex buffer to the end of the GPU address space,
|
||||
// which would result in a several GBs large buffer.
|
||||
|
||||
size = _channel.MemoryManager.GetMappedSize(address, size);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -37,6 +37,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Types
|
||||
R16G16Sint = 0xdc,
|
||||
R16G16Uint = 0xdd,
|
||||
R16G16Float = 0xde,
|
||||
B10G10R10A2Unorm = 0xdf,
|
||||
R11G11B10Float = 0xe0,
|
||||
R32Sint = 0xe3,
|
||||
R32Uint = 0xe4,
|
||||
@ -104,6 +105,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Types
|
||||
ColorFormat.R16G16Sint => new FormatInfo(Format.R16G16Sint, 1, 1, 4, 2),
|
||||
ColorFormat.R16G16Uint => new FormatInfo(Format.R16G16Uint, 1, 1, 4, 2),
|
||||
ColorFormat.R16G16Float => new FormatInfo(Format.R16G16Float, 1, 1, 4, 2),
|
||||
ColorFormat.B10G10R10A2Unorm => new FormatInfo(Format.B10G10R10A2Unorm, 1, 1, 4, 4),
|
||||
ColorFormat.R11G11B10Float => new FormatInfo(Format.R11G11B10Float, 1, 1, 4, 3),
|
||||
ColorFormat.R32Sint => new FormatInfo(Format.R32Sint, 1, 1, 4, 1),
|
||||
ColorFormat.R32Uint => new FormatInfo(Format.R32Uint, 1, 1, 4, 1),
|
||||
|
@ -8,13 +8,13 @@ namespace Ryujinx.Graphics.Gpu.Engine.Types
|
||||
/// </summary>
|
||||
enum ZetaFormat
|
||||
{
|
||||
D32Float = 0xa,
|
||||
D16Unorm = 0x13,
|
||||
D24UnormS8Uint = 0x14,
|
||||
D24Unorm = 0x15,
|
||||
S8UintD24Unorm = 0x16,
|
||||
Zf32 = 0xa,
|
||||
Z16 = 0x13,
|
||||
Z24S8 = 0x14,
|
||||
X8Z24 = 0x15,
|
||||
S8Z24 = 0x16,
|
||||
S8Uint = 0x17,
|
||||
D32FloatS8Uint = 0x19,
|
||||
Zf32X24S8 = 0x19,
|
||||
}
|
||||
|
||||
static class ZetaFormatConverter
|
||||
@ -29,14 +29,14 @@ namespace Ryujinx.Graphics.Gpu.Engine.Types
|
||||
return format switch
|
||||
{
|
||||
#pragma warning disable IDE0055 // Disable formatting
|
||||
ZetaFormat.D32Float => new FormatInfo(Format.D32Float, 1, 1, 4, 1),
|
||||
ZetaFormat.D16Unorm => new FormatInfo(Format.D16Unorm, 1, 1, 2, 1),
|
||||
ZetaFormat.D24UnormS8Uint => new FormatInfo(Format.D24UnormS8Uint, 1, 1, 4, 2),
|
||||
ZetaFormat.D24Unorm => new FormatInfo(Format.D24UnormS8Uint, 1, 1, 4, 1),
|
||||
ZetaFormat.S8UintD24Unorm => new FormatInfo(Format.S8UintD24Unorm, 1, 1, 4, 2),
|
||||
ZetaFormat.S8Uint => new FormatInfo(Format.S8Uint, 1, 1, 1, 1),
|
||||
ZetaFormat.D32FloatS8Uint => new FormatInfo(Format.D32FloatS8Uint, 1, 1, 8, 2),
|
||||
_ => FormatInfo.Default,
|
||||
ZetaFormat.Zf32 => new FormatInfo(Format.D32Float, 1, 1, 4, 1),
|
||||
ZetaFormat.Z16 => new FormatInfo(Format.D16Unorm, 1, 1, 2, 1),
|
||||
ZetaFormat.Z24S8 => new FormatInfo(Format.D24UnormS8Uint, 1, 1, 4, 2),
|
||||
ZetaFormat.X8Z24 => new FormatInfo(Format.X8UintD24Unorm, 1, 1, 4, 1),
|
||||
ZetaFormat.S8Z24 => new FormatInfo(Format.S8UintD24Unorm, 1, 1, 4, 2),
|
||||
ZetaFormat.S8Uint => new FormatInfo(Format.S8Uint, 1, 1, 1, 1),
|
||||
ZetaFormat.Zf32X24S8 => new FormatInfo(Format.D32FloatS8Uint, 1, 1, 8, 2),
|
||||
_ => FormatInfo.Default,
|
||||
#pragma warning restore IDE0055
|
||||
};
|
||||
}
|
||||
|
@ -185,6 +185,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
G24R8RUintGUnormBUnormAUnorm = G24R8 | RUint | GUnorm | BUnorm | AUnorm, // 0x24a0e
|
||||
Z24S8RUintGUnormBUnormAUnorm = Z24S8 | RUint | GUnorm | BUnorm | AUnorm, // 0x24a29
|
||||
Z24S8RUintGUnormBUintAUint = Z24S8 | RUint | GUnorm | BUint | AUint, // 0x48a29
|
||||
X8Z24RUnormGUintBUintAUint = X8Z24 | RUnorm | GUint | BUint | AUint, // 0x4912a
|
||||
S8Z24RUnormGUintBUintAUint = S8Z24 | RUnorm | GUint | BUint | AUint, // 0x4912b
|
||||
R32B24G8RFloatGUintBUnormAUnorm = R32B24G8 | RFloat | GUint | BUnorm | AUnorm, // 0x25385
|
||||
Zf32X24S8RFloatGUintBUnormAUnorm = Zf32X24S8 | RFloat | GUint | BUnorm | AUnorm, // 0x253b0
|
||||
@ -410,6 +411,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
{ TextureFormat.G24R8RUintGUnormBUnormAUnorm, new FormatInfo(Format.D24UnormS8Uint, 1, 1, 4, 2) },
|
||||
{ TextureFormat.Z24S8RUintGUnormBUnormAUnorm, new FormatInfo(Format.D24UnormS8Uint, 1, 1, 4, 2) },
|
||||
{ TextureFormat.Z24S8RUintGUnormBUintAUint, new FormatInfo(Format.D24UnormS8Uint, 1, 1, 4, 2) },
|
||||
{ TextureFormat.X8Z24RUnormGUintBUintAUint, new FormatInfo(Format.X8UintD24Unorm, 1, 1, 4, 2) },
|
||||
{ TextureFormat.S8Z24RUnormGUintBUintAUint, new FormatInfo(Format.S8UintD24Unorm, 1, 1, 4, 2) },
|
||||
{ TextureFormat.R32B24G8RFloatGUintBUnormAUnorm, new FormatInfo(Format.D32FloatS8Uint, 1, 1, 8, 2) },
|
||||
{ TextureFormat.Zf32X24S8RFloatGUintBUnormAUnorm, new FormatInfo(Format.D32FloatS8Uint, 1, 1, 8, 2) },
|
||||
@ -672,9 +674,9 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
1 => new FormatInfo(Format.R8Unorm, 1, 1, 1, 1),
|
||||
2 => new FormatInfo(Format.R16Unorm, 1, 1, 2, 1),
|
||||
4 => new FormatInfo(Format.R32Float, 1, 1, 4, 1),
|
||||
8 => new FormatInfo(Format.R32G32Float, 1, 1, 8, 2),
|
||||
16 => new FormatInfo(Format.R32G32B32A32Float, 1, 1, 16, 4),
|
||||
4 => new FormatInfo(Format.R32Uint, 1, 1, 4, 1),
|
||||
8 => new FormatInfo(Format.R32G32Uint, 1, 1, 8, 2),
|
||||
16 => new FormatInfo(Format.R32G32B32A32Uint, 1, 1, 16, 4),
|
||||
_ => format,
|
||||
};
|
||||
}
|
||||
|
@ -634,7 +634,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
state.Texture = hostTextureRebind;
|
||||
state.ImageFormat = format;
|
||||
|
||||
_context.Renderer.Pipeline.SetImage(bindingInfo.Binding, hostTextureRebind, format);
|
||||
_context.Renderer.Pipeline.SetImage(stage, bindingInfo.Binding, hostTextureRebind, format);
|
||||
}
|
||||
|
||||
continue;
|
||||
@ -692,7 +692,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
|
||||
state.ImageFormat = format;
|
||||
|
||||
_context.Renderer.Pipeline.SetImage(bindingInfo.Binding, hostTexture, format);
|
||||
_context.Renderer.Pipeline.SetImage(stage, bindingInfo.Binding, hostTexture, format);
|
||||
}
|
||||
|
||||
state.CachedTexture = texture;
|
||||
|
@ -242,7 +242,8 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
return TextureMatchQuality.FormatAlias;
|
||||
}
|
||||
else if ((lhs.FormatInfo.Format == Format.D24UnormS8Uint ||
|
||||
lhs.FormatInfo.Format == Format.S8UintD24Unorm) && rhs.FormatInfo.Format == Format.B8G8R8A8Unorm)
|
||||
lhs.FormatInfo.Format == Format.S8UintD24Unorm ||
|
||||
lhs.FormatInfo.Format == Format.X8UintD24Unorm) && rhs.FormatInfo.Format == Format.B8G8R8A8Unorm)
|
||||
{
|
||||
return TextureMatchQuality.FormatAlias;
|
||||
}
|
||||
|
@ -1630,12 +1630,6 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
return;
|
||||
}
|
||||
|
||||
// If size is zero, we have nothing to flush.
|
||||
if (size == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// There is a small gap here where the action is removed but _actionRegistered is still 1.
|
||||
// In this case it will skip registering the action, but here we are already handling it,
|
||||
// so there shouldn't be any issue as it's the same handler for all actions.
|
||||
|
@ -5,6 +5,8 @@ using Ryujinx.Memory.Tracking;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Memory
|
||||
{
|
||||
@ -65,6 +67,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
private readonly Action<ulong, ulong> _loadDelegate;
|
||||
private readonly Action<ulong, ulong> _modifiedDelegate;
|
||||
|
||||
private HashSet<MultiRangeBuffer> _virtualDependencies;
|
||||
private readonly ReaderWriterLockSlim _virtualDependenciesLock;
|
||||
|
||||
private int _sequenceNumber;
|
||||
|
||||
private readonly bool _useGranular;
|
||||
@ -152,6 +157,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
_externalFlushDelegate = new RegionSignal(ExternalFlush);
|
||||
_loadDelegate = new Action<ulong, ulong>(LoadRegion);
|
||||
_modifiedDelegate = new Action<ulong, ulong>(RegionModified);
|
||||
|
||||
_virtualDependenciesLock = new ReaderWriterLockSlim();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -220,6 +227,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// </remarks>
|
||||
/// <param name="address">Start address of the range to synchronize</param>
|
||||
/// <param name="size">Size in bytes of the range to synchronize</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SynchronizeMemory(ulong address, ulong size)
|
||||
{
|
||||
if (_useGranular)
|
||||
@ -239,6 +247,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
else
|
||||
{
|
||||
_context.Renderer.SetBufferData(Handle, 0, _physicalMemory.GetSpan(Address, (int)Size));
|
||||
CopyToDependantVirtualBuffers();
|
||||
}
|
||||
|
||||
_sequenceNumber = _context.SequenceNumber;
|
||||
@ -460,6 +469,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
int offset = (int)(mAddress - Address);
|
||||
|
||||
_context.Renderer.SetBufferData(Handle, offset, _physicalMemory.GetSpan(mAddress, (int)mSize));
|
||||
|
||||
CopyToDependantVirtualBuffers(mAddress, mSize);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -520,6 +531,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// <param name="dstOffset">The offset of the destination buffer to copy into</param>
|
||||
public void CopyTo(Buffer destination, int dstOffset)
|
||||
{
|
||||
CopyFromDependantVirtualBuffers();
|
||||
_context.Renderer.Pipeline.CopyBuffer(Handle, destination.Handle, 0, dstOffset, (int)Size);
|
||||
}
|
||||
|
||||
@ -536,7 +548,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
using PinnedSpan<byte> data = _context.Renderer.GetBufferData(Handle, offset, (int)size);
|
||||
|
||||
// TODO: When write tracking shaders, they will need to be aware of changes in overlapping buffers.
|
||||
_physicalMemory.WriteUntracked(address, data.Get());
|
||||
_physicalMemory.WriteUntracked(address, CopyFromDependantVirtualBuffers(data.Get(), address, size));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -617,6 +629,207 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
UnmappedSequence++;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a virtual buffer dependency, indicating that a virtual buffer depends on data from this buffer.
|
||||
/// </summary>
|
||||
/// <param name="virtualBuffer">Dependant virtual buffer</param>
|
||||
public void AddVirtualDependency(MultiRangeBuffer virtualBuffer)
|
||||
{
|
||||
_virtualDependenciesLock.EnterWriteLock();
|
||||
|
||||
try
|
||||
{
|
||||
(_virtualDependencies ??= new()).Add(virtualBuffer);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_virtualDependenciesLock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a virtual buffer dependency, indicating that a virtual buffer no longer depends on data from this buffer.
|
||||
/// </summary>
|
||||
/// <param name="virtualBuffer">Dependant virtual buffer</param>
|
||||
public void RemoveVirtualDependency(MultiRangeBuffer virtualBuffer)
|
||||
{
|
||||
_virtualDependenciesLock.EnterWriteLock();
|
||||
|
||||
try
|
||||
{
|
||||
if (_virtualDependencies != null)
|
||||
{
|
||||
_virtualDependencies.Remove(virtualBuffer);
|
||||
|
||||
if (_virtualDependencies.Count == 0)
|
||||
{
|
||||
_virtualDependencies = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_virtualDependenciesLock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the buffer data to all virtual buffers that depends on it.
|
||||
/// </summary>
|
||||
public void CopyToDependantVirtualBuffers()
|
||||
{
|
||||
CopyToDependantVirtualBuffers(Address, Size);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the buffer data inside the specifide range to all virtual buffers that depends on it.
|
||||
/// </summary>
|
||||
/// <param name="address">Address of the range</param>
|
||||
/// <param name="size">Size of the range in bytes</param>
|
||||
public void CopyToDependantVirtualBuffers(ulong address, ulong size)
|
||||
{
|
||||
if (_virtualDependencies != null)
|
||||
{
|
||||
foreach (var virtualBuffer in _virtualDependencies)
|
||||
{
|
||||
CopyToDependantVirtualBuffer(virtualBuffer, address, size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies all modified ranges from all virtual buffers back into this buffer.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void CopyFromDependantVirtualBuffers()
|
||||
{
|
||||
if (_virtualDependencies != null)
|
||||
{
|
||||
CopyFromDependantVirtualBuffersImpl();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies all modified ranges from all virtual buffers back into this buffer.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private void CopyFromDependantVirtualBuffersImpl()
|
||||
{
|
||||
foreach (var virtualBuffer in _virtualDependencies.OrderBy(x => x.ModificationSequenceNumber))
|
||||
{
|
||||
virtualBuffer.ConsumeModifiedRegion(this, (mAddress, mSize) =>
|
||||
{
|
||||
// Get offset inside both this and the virtual buffer.
|
||||
// Note that sometimes there is no right answer for the virtual offset,
|
||||
// as the same physical range might be mapped multiple times inside a virtual buffer.
|
||||
// We just assume it does not happen in practice as it can only be implemented correctly
|
||||
// when the host has support for proper sparse mapping.
|
||||
|
||||
ulong mEndAddress = mAddress + mSize;
|
||||
mAddress = Math.Max(mAddress, Address);
|
||||
mSize = Math.Min(mEndAddress, EndAddress) - mAddress;
|
||||
|
||||
int physicalOffset = (int)(mAddress - Address);
|
||||
int virtualOffset = virtualBuffer.Range.FindOffset(new(mAddress, mSize));
|
||||
|
||||
_context.Renderer.Pipeline.CopyBuffer(virtualBuffer.Handle, Handle, virtualOffset, physicalOffset, (int)mSize);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies all overlapping modified ranges from all virtual buffers back into this buffer, and returns an updated span with the data.
|
||||
/// </summary>
|
||||
/// <param name="dataSpan">Span where the unmodified data will be taken from for the output</param>
|
||||
/// <param name="address">Address of the region to copy</param>
|
||||
/// <param name="size">Size of the region to copy in bytes</param>
|
||||
/// <returns>A span with <paramref name="dataSpan"/>, and the data for all modified ranges if any</returns>
|
||||
private ReadOnlySpan<byte> CopyFromDependantVirtualBuffers(ReadOnlySpan<byte> dataSpan, ulong address, ulong size)
|
||||
{
|
||||
_virtualDependenciesLock.EnterReadLock();
|
||||
|
||||
try
|
||||
{
|
||||
if (_virtualDependencies != null)
|
||||
{
|
||||
byte[] storage = dataSpan.ToArray();
|
||||
|
||||
foreach (var virtualBuffer in _virtualDependencies.OrderBy(x => x.ModificationSequenceNumber))
|
||||
{
|
||||
virtualBuffer.ConsumeModifiedRegion(address, size, (mAddress, mSize) =>
|
||||
{
|
||||
// Get offset inside both this and the virtual buffer.
|
||||
// Note that sometimes there is no right answer for the virtual offset,
|
||||
// as the same physical range might be mapped multiple times inside a virtual buffer.
|
||||
// We just assume it does not happen in practice as it can only be implemented correctly
|
||||
// when the host has support for proper sparse mapping.
|
||||
|
||||
ulong mEndAddress = mAddress + mSize;
|
||||
mAddress = Math.Max(mAddress, address);
|
||||
mSize = Math.Min(mEndAddress, address + size) - mAddress;
|
||||
|
||||
int physicalOffset = (int)(mAddress - Address);
|
||||
int virtualOffset = virtualBuffer.Range.FindOffset(new(mAddress, mSize));
|
||||
|
||||
_context.Renderer.Pipeline.CopyBuffer(virtualBuffer.Handle, Handle, virtualOffset, physicalOffset, (int)size);
|
||||
virtualBuffer.GetData(storage.AsSpan().Slice((int)(mAddress - address), (int)mSize), virtualOffset, (int)mSize);
|
||||
});
|
||||
}
|
||||
|
||||
dataSpan = storage;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_virtualDependenciesLock.ExitReadLock();
|
||||
}
|
||||
|
||||
return dataSpan;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the buffer data to the specified virtual buffer.
|
||||
/// </summary>
|
||||
/// <param name="virtualBuffer">Virtual buffer to copy the data into</param>
|
||||
public void CopyToDependantVirtualBuffer(MultiRangeBuffer virtualBuffer)
|
||||
{
|
||||
CopyToDependantVirtualBuffer(virtualBuffer, Address, Size);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the buffer data inside the given range to the specified virtual buffer.
|
||||
/// </summary>
|
||||
/// <param name="virtualBuffer">Virtual buffer to copy the data into</param>
|
||||
/// <param name="address">Address of the range</param>
|
||||
/// <param name="size">Size of the range in bytes</param>
|
||||
public void CopyToDependantVirtualBuffer(MultiRangeBuffer virtualBuffer, ulong address, ulong size)
|
||||
{
|
||||
// Broadcast data to all ranges of the virtual buffer that are contained inside this buffer.
|
||||
|
||||
ulong lastOffset = 0;
|
||||
|
||||
while (virtualBuffer.TryGetPhysicalOffset(this, lastOffset, out ulong srcOffset, out ulong dstOffset, out ulong copySize))
|
||||
{
|
||||
ulong innerOffset = address - Address;
|
||||
ulong innerEndOffset = (address + size) - Address;
|
||||
|
||||
lastOffset = dstOffset + copySize;
|
||||
|
||||
// Clamp range to the specified range.
|
||||
ulong copySrcOffset = Math.Max(srcOffset, innerOffset);
|
||||
ulong copySrcEndOffset = Math.Min(innerEndOffset, srcOffset + copySize);
|
||||
|
||||
if (copySrcEndOffset > copySrcOffset)
|
||||
{
|
||||
copySize = copySrcEndOffset - copySrcOffset;
|
||||
dstOffset += copySrcOffset - srcOffset;
|
||||
srcOffset = copySrcOffset;
|
||||
|
||||
_context.Renderer.Pipeline.CopyBuffer(Handle, virtualBuffer.Handle, (int)srcOffset, (int)dstOffset, (int)copySize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Increments the buffer reference count.
|
||||
/// </summary>
|
||||
|
@ -3,6 +3,7 @@ using Ryujinx.Memory.Range;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Memory
|
||||
{
|
||||
@ -46,6 +47,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
private readonly Dictionary<ulong, BufferCacheEntry> _dirtyCache;
|
||||
private readonly Dictionary<ulong, BufferCacheEntry> _modifiedCache;
|
||||
private bool _pruneCaches;
|
||||
private int _virtualModifiedSequenceNumber;
|
||||
|
||||
public event Action NotifyBuffersModified;
|
||||
|
||||
@ -125,7 +127,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
|
||||
/// <summary>
|
||||
/// Performs address translation of the GPU virtual address, and creates
|
||||
/// new buffers, if needed, for the specified range.
|
||||
/// new physical and virtual buffers, if needed, for the specified range.
|
||||
/// </summary>
|
||||
/// <param name="memoryManager">GPU memory manager where the buffer is mapped</param>
|
||||
/// <param name="gpuVa">Start GPU virtual address of the buffer</param>
|
||||
@ -138,12 +140,10 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
return new MultiRange(MemoryManager.PteUnmapped, size);
|
||||
}
|
||||
|
||||
bool supportsSparse = _context.Capabilities.SupportsSparseBuffer;
|
||||
|
||||
// Fast path not taken for non-contiguous ranges,
|
||||
// since multi-range buffers are not coalesced, so a buffer that covers
|
||||
// the entire cached range might not actually exist.
|
||||
if (memoryManager.VirtualBufferCache.TryGetOrAddRange(gpuVa, size, supportsSparse, out MultiRange range) &&
|
||||
if (memoryManager.VirtualRangeCache.TryGetOrAddRange(gpuVa, size, out MultiRange range) &&
|
||||
range.Count == 1)
|
||||
{
|
||||
return range;
|
||||
@ -154,6 +154,50 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
return range;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs address translation of the GPU virtual address, and creates
|
||||
/// new physical buffers, if needed, for the specified range.
|
||||
/// </summary>
|
||||
/// <param name="memoryManager">GPU memory manager where the buffer is mapped</param>
|
||||
/// <param name="gpuVa">Start GPU virtual address of the buffer</param>
|
||||
/// <param name="size">Size in bytes of the buffer</param>
|
||||
/// <returns>Physical ranges of the buffer, after address translation</returns>
|
||||
public MultiRange TranslateAndCreateMultiBuffersPhysicalOnly(MemoryManager memoryManager, ulong gpuVa, ulong size)
|
||||
{
|
||||
if (gpuVa == 0)
|
||||
{
|
||||
return new MultiRange(MemoryManager.PteUnmapped, size);
|
||||
}
|
||||
|
||||
// Fast path not taken for non-contiguous ranges,
|
||||
// since multi-range buffers are not coalesced, so a buffer that covers
|
||||
// the entire cached range might not actually exist.
|
||||
if (memoryManager.VirtualRangeCache.TryGetOrAddRange(gpuVa, size, out MultiRange range) &&
|
||||
range.Count == 1)
|
||||
{
|
||||
return range;
|
||||
}
|
||||
|
||||
for (int i = 0; i < range.Count; i++)
|
||||
{
|
||||
MemoryRange subRange = range.GetSubRange(i);
|
||||
|
||||
if (subRange.Address != MemoryManager.PteUnmapped)
|
||||
{
|
||||
if (range.Count > 1)
|
||||
{
|
||||
CreateBuffer(subRange.Address, subRange.Size, SparseBufferAlignmentSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
CreateBuffer(subRange.Address, subRange.Size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return range;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new buffer for the specified range, if it does not yet exist.
|
||||
/// This can be used to ensure the existance of a buffer.
|
||||
@ -263,41 +307,108 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
}
|
||||
}
|
||||
|
||||
BufferRange[] storages = new BufferRange[range.Count];
|
||||
MultiRangeBuffer multiRangeBuffer;
|
||||
|
||||
MemoryRange[] alignedSubRanges = new MemoryRange[range.Count];
|
||||
|
||||
ulong alignmentMask = SparseBufferAlignmentSize - 1;
|
||||
|
||||
for (int i = 0; i < range.Count; i++)
|
||||
if (_context.Capabilities.SupportsSparseBuffer)
|
||||
{
|
||||
MemoryRange subRange = range.GetSubRange(i);
|
||||
BufferRange[] storages = new BufferRange[range.Count];
|
||||
|
||||
for (int i = 0; i < range.Count; i++)
|
||||
{
|
||||
MemoryRange subRange = range.GetSubRange(i);
|
||||
|
||||
if (subRange.Address != MemoryManager.PteUnmapped)
|
||||
{
|
||||
ulong endAddress = subRange.Address + subRange.Size;
|
||||
|
||||
ulong alignedAddress = subRange.Address & ~alignmentMask;
|
||||
ulong alignedEndAddress = (endAddress + alignmentMask) & ~alignmentMask;
|
||||
ulong alignedSize = alignedEndAddress - alignedAddress;
|
||||
|
||||
Buffer buffer = _buffers.FindFirstOverlap(alignedAddress, alignedSize);
|
||||
BufferRange bufferRange = buffer.GetRange(alignedAddress, alignedSize, false);
|
||||
|
||||
alignedSubRanges[i] = new MemoryRange(alignedAddress, alignedSize);
|
||||
storages[i] = bufferRange;
|
||||
}
|
||||
else
|
||||
{
|
||||
ulong alignedSize = (subRange.Size + alignmentMask) & ~alignmentMask;
|
||||
|
||||
alignedSubRanges[i] = new MemoryRange(MemoryManager.PteUnmapped, alignedSize);
|
||||
storages[i] = new BufferRange(BufferHandle.Null, 0, (int)alignedSize);
|
||||
}
|
||||
}
|
||||
|
||||
multiRangeBuffer = new(_context, new MultiRange(alignedSubRanges), storages);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < range.Count; i++)
|
||||
{
|
||||
MemoryRange subRange = range.GetSubRange(i);
|
||||
|
||||
if (subRange.Address != MemoryManager.PteUnmapped)
|
||||
{
|
||||
ulong endAddress = subRange.Address + subRange.Size;
|
||||
|
||||
ulong alignedAddress = subRange.Address & ~alignmentMask;
|
||||
ulong alignedEndAddress = (endAddress + alignmentMask) & ~alignmentMask;
|
||||
ulong alignedSize = alignedEndAddress - alignedAddress;
|
||||
|
||||
alignedSubRanges[i] = new MemoryRange(alignedAddress, alignedSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
ulong alignedSize = (subRange.Size + alignmentMask) & ~alignmentMask;
|
||||
|
||||
alignedSubRanges[i] = new MemoryRange(MemoryManager.PteUnmapped, alignedSize);
|
||||
}
|
||||
}
|
||||
|
||||
multiRangeBuffer = new(_context, new MultiRange(alignedSubRanges));
|
||||
|
||||
UpdateVirtualBufferDependencies(multiRangeBuffer);
|
||||
}
|
||||
|
||||
_multiRangeBuffers.Add(multiRangeBuffer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds two-way dependencies to all physical buffers contained within a given virtual buffer.
|
||||
/// </summary>
|
||||
/// <param name="virtualBuffer">Virtual buffer to have dependencies added</param>
|
||||
private void UpdateVirtualBufferDependencies(MultiRangeBuffer virtualBuffer)
|
||||
{
|
||||
virtualBuffer.ClearPhysicalDependencies();
|
||||
|
||||
ulong dstOffset = 0;
|
||||
|
||||
HashSet<Buffer> physicalBuffers = new();
|
||||
|
||||
for (int i = 0; i < virtualBuffer.Range.Count; i++)
|
||||
{
|
||||
MemoryRange subRange = virtualBuffer.Range.GetSubRange(i);
|
||||
|
||||
if (subRange.Address != MemoryManager.PteUnmapped)
|
||||
{
|
||||
ulong endAddress = subRange.Address + subRange.Size;
|
||||
Buffer buffer = _buffers.FindFirstOverlap(subRange.Address, subRange.Size);
|
||||
|
||||
ulong alignedAddress = subRange.Address & ~alignmentMask;
|
||||
ulong alignedEndAddress = (endAddress + alignmentMask) & ~alignmentMask;
|
||||
ulong alignedSize = alignedEndAddress - alignedAddress;
|
||||
|
||||
Buffer buffer = _buffers.FindFirstOverlap(alignedAddress, alignedSize);
|
||||
BufferRange bufferRange = buffer.GetRange(alignedAddress, alignedSize, false);
|
||||
|
||||
storages[i] = bufferRange;
|
||||
alignedSubRanges[i] = new MemoryRange(alignedAddress, alignedSize);
|
||||
virtualBuffer.AddPhysicalDependency(buffer, subRange.Address, dstOffset, subRange.Size);
|
||||
physicalBuffers.Add(buffer);
|
||||
}
|
||||
else
|
||||
{
|
||||
ulong alignedSize = (subRange.Size + alignmentMask) & ~alignmentMask;
|
||||
|
||||
storages[i] = new BufferRange(BufferHandle.Null, 0, (int)alignedSize);
|
||||
alignedSubRanges[i] = new MemoryRange(MemoryManager.PteUnmapped, alignedSize);
|
||||
}
|
||||
dstOffset += subRange.Size;
|
||||
}
|
||||
|
||||
MultiRangeBuffer multiRangeBuffer = new(_context, new MultiRange(alignedSubRanges), storages);
|
||||
|
||||
_multiRangeBuffers.Add(multiRangeBuffer);
|
||||
foreach (var buffer in physicalBuffers)
|
||||
{
|
||||
buffer.CopyToDependantVirtualBuffer(virtualBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -620,8 +731,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// <param name="size">Size in bytes of the copy</param>
|
||||
public void CopyBuffer(MemoryManager memoryManager, ulong srcVa, ulong dstVa, ulong size)
|
||||
{
|
||||
MultiRange srcRange = TranslateAndCreateMultiBuffers(memoryManager, srcVa, size);
|
||||
MultiRange dstRange = TranslateAndCreateMultiBuffers(memoryManager, dstVa, size);
|
||||
MultiRange srcRange = TranslateAndCreateMultiBuffersPhysicalOnly(memoryManager, srcVa, size);
|
||||
MultiRange dstRange = TranslateAndCreateMultiBuffersPhysicalOnly(memoryManager, dstVa, size);
|
||||
|
||||
if (srcRange.Count == 1 && dstRange.Count == 1)
|
||||
{
|
||||
@ -701,6 +812,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
dstBuffer.ClearModified(dstAddress, size);
|
||||
memoryManager.Physical.WriteTrackedResource(dstAddress, memoryManager.Physical.GetSpan(srcAddress, (int)size), ResourceKind.Buffer);
|
||||
}
|
||||
|
||||
dstBuffer.CopyToDependantVirtualBuffers(dstAddress, size);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -715,7 +828,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// <param name="value">Value to be written into the buffer</param>
|
||||
public void ClearBuffer(MemoryManager memoryManager, ulong gpuVa, ulong size, uint value)
|
||||
{
|
||||
MultiRange range = TranslateAndCreateMultiBuffers(memoryManager, gpuVa, size);
|
||||
MultiRange range = TranslateAndCreateMultiBuffersPhysicalOnly(memoryManager, gpuVa, size);
|
||||
|
||||
for (int index = 0; index < range.Count; index++)
|
||||
{
|
||||
@ -727,6 +840,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
_context.Renderer.Pipeline.ClearBuffer(buffer.Handle, offset, (int)subRange.Size, value);
|
||||
|
||||
memoryManager.Physical.FillTrackedResource(subRange.Address, subRange.Size, value, ResourceKind.Buffer);
|
||||
|
||||
buffer.CopyToDependantVirtualBuffers(subRange.Address, subRange.Size);
|
||||
}
|
||||
}
|
||||
|
||||
@ -806,6 +921,11 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
}
|
||||
}
|
||||
|
||||
if (write && buffer != null && !_context.Capabilities.SupportsSparseBuffer)
|
||||
{
|
||||
buffer.AddModifiedRegion(range, ++_virtualModifiedSequenceNumber);
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
@ -825,6 +945,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
{
|
||||
buffer = _buffers.FindFirstOverlap(address, size);
|
||||
|
||||
buffer.CopyFromDependantVirtualBuffers();
|
||||
buffer.SynchronizeMemory(address, size);
|
||||
|
||||
if (write)
|
||||
@ -849,14 +970,14 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
if (range.Count == 1)
|
||||
{
|
||||
MemoryRange subRange = range.GetSubRange(0);
|
||||
SynchronizeBufferRange(subRange.Address, subRange.Size);
|
||||
SynchronizeBufferRange(subRange.Address, subRange.Size, copyBackVirtual: true);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int index = 0; index < range.Count; index++)
|
||||
{
|
||||
MemoryRange subRange = range.GetSubRange(index);
|
||||
SynchronizeBufferRange(subRange.Address, subRange.Size);
|
||||
SynchronizeBufferRange(subRange.Address, subRange.Size, copyBackVirtual: false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -866,12 +987,19 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// </summary>
|
||||
/// <param name="address">Start address of the memory range</param>
|
||||
/// <param name="size">Size in bytes of the memory range</param>
|
||||
private void SynchronizeBufferRange(ulong address, ulong size)
|
||||
/// <param name="copyBackVirtual">Whether virtual buffers that uses this buffer as backing memory should have its data copied back if modified</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void SynchronizeBufferRange(ulong address, ulong size, bool copyBackVirtual)
|
||||
{
|
||||
if (size != 0)
|
||||
{
|
||||
Buffer buffer = _buffers.FindFirstOverlap(address, size);
|
||||
|
||||
if (copyBackVirtual)
|
||||
{
|
||||
buffer.CopyFromDependantVirtualBuffers();
|
||||
}
|
||||
|
||||
buffer.SynchronizeMemory(address, size);
|
||||
}
|
||||
}
|
||||
|
@ -484,7 +484,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
|
||||
if (binding.IsImage)
|
||||
{
|
||||
_context.Renderer.Pipeline.SetImage(binding.BindingInfo.Binding, binding.Texture, binding.Format);
|
||||
_context.Renderer.Pipeline.SetImage(binding.Stage, binding.BindingInfo.Binding, binding.Texture, binding.Format);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -40,9 +40,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
internal PhysicalMemory Physical { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Virtual buffer cache.
|
||||
/// Virtual range cache.
|
||||
/// </summary>
|
||||
internal VirtualBufferCache VirtualBufferCache { get; }
|
||||
internal VirtualRangeCache VirtualRangeCache { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Cache of GPU counters.
|
||||
@ -56,12 +56,12 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
internal MemoryManager(PhysicalMemory physicalMemory)
|
||||
{
|
||||
Physical = physicalMemory;
|
||||
VirtualBufferCache = new VirtualBufferCache(this);
|
||||
VirtualRangeCache = new VirtualRangeCache(this);
|
||||
CounterCache = new CounterCache();
|
||||
_pageTable = new ulong[PtLvl0Size][];
|
||||
MemoryUnmapped += Physical.TextureCache.MemoryUnmappedHandler;
|
||||
MemoryUnmapped += Physical.BufferCache.MemoryUnmappedHandler;
|
||||
MemoryUnmapped += VirtualBufferCache.MemoryUnmappedHandler;
|
||||
MemoryUnmapped += VirtualRangeCache.MemoryUnmappedHandler;
|
||||
MemoryUnmapped += CounterCache.MemoryUnmappedHandler;
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Memory.Range;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Memory
|
||||
{
|
||||
@ -21,12 +22,73 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// </summary>
|
||||
public MultiRange Range { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Ever increasing counter value indicating when the buffer was modified relative to other buffers.
|
||||
/// </summary>
|
||||
public int ModificationSequenceNumber { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Physical buffer dependency entry.
|
||||
/// </summary>
|
||||
private readonly struct PhysicalDependency
|
||||
{
|
||||
/// <summary>
|
||||
/// Physical buffer.
|
||||
/// </summary>
|
||||
public readonly Buffer PhysicalBuffer;
|
||||
|
||||
/// <summary>
|
||||
/// Offset of the range on the physical buffer.
|
||||
/// </summary>
|
||||
public readonly ulong PhysicalOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Offset of the range on the virtual buffer.
|
||||
/// </summary>
|
||||
public readonly ulong VirtualOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Size of the range.
|
||||
/// </summary>
|
||||
public readonly ulong Size;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new physical dependency.
|
||||
/// </summary>
|
||||
/// <param name="physicalBuffer">Physical buffer</param>
|
||||
/// <param name="physicalOffset">Offset of the range on the physical buffer</param>
|
||||
/// <param name="virtualOffset">Offset of the range on the virtual buffer</param>
|
||||
/// <param name="size">Size of the range</param>
|
||||
public PhysicalDependency(Buffer physicalBuffer, ulong physicalOffset, ulong virtualOffset, ulong size)
|
||||
{
|
||||
PhysicalBuffer = physicalBuffer;
|
||||
PhysicalOffset = physicalOffset;
|
||||
VirtualOffset = virtualOffset;
|
||||
Size = size;
|
||||
}
|
||||
}
|
||||
|
||||
private List<PhysicalDependency> _dependencies;
|
||||
private BufferModifiedRangeList _modifiedRanges = null;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the buffer.
|
||||
/// </summary>
|
||||
/// <param name="context">GPU context that the buffer belongs to</param>
|
||||
/// <param name="range">Range of memory where the data is mapped</param>
|
||||
/// <param name="storages">Backing memory for the buffers</param>
|
||||
public MultiRangeBuffer(GpuContext context, MultiRange range)
|
||||
{
|
||||
_context = context;
|
||||
Range = range;
|
||||
Handle = context.Renderer.CreateBuffer((int)range.GetSize());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the buffer.
|
||||
/// </summary>
|
||||
/// <param name="context">GPU context that the buffer belongs to</param>
|
||||
/// <param name="range">Range of memory where the data is mapped</param>
|
||||
/// <param name="storages">Backing memory for the buffer</param>
|
||||
public MultiRangeBuffer(GpuContext context, MultiRange range, ReadOnlySpan<BufferRange> storages)
|
||||
{
|
||||
_context = context;
|
||||
@ -49,11 +111,134 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
return new BufferRange(Handle, offset, (int)range.GetSize());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes all physical buffer dependencies.
|
||||
/// </summary>
|
||||
public void ClearPhysicalDependencies()
|
||||
{
|
||||
_dependencies?.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a physical buffer dependency.
|
||||
/// </summary>
|
||||
/// <param name="buffer">Physical buffer to be added</param>
|
||||
/// <param name="rangeAddress">Address inside the physical buffer where the virtual buffer range is located</param>
|
||||
/// <param name="dstOffset">Offset inside the virtual buffer where the physical range is located</param>
|
||||
/// <param name="rangeSize">Size of the range in bytes</param>
|
||||
public void AddPhysicalDependency(Buffer buffer, ulong rangeAddress, ulong dstOffset, ulong rangeSize)
|
||||
{
|
||||
(_dependencies ??= new()).Add(new(buffer, rangeAddress - buffer.Address, dstOffset, rangeSize));
|
||||
buffer.AddVirtualDependency(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get the physical range corresponding to the given physical buffer.
|
||||
/// </summary>
|
||||
/// <param name="buffer">Physical buffer</param>
|
||||
/// <param name="minimumVirtOffset">Minimum virtual offset that a range match can have</param>
|
||||
/// <param name="physicalOffset">Physical offset of the match</param>
|
||||
/// <param name="virtualOffset">Virtual offset of the match, always greater than or equal <paramref name="minimumVirtOffset"/></param>
|
||||
/// <param name="size">Size of the range match</param>
|
||||
/// <returns>True if a match was found for the given parameters, false otherwise</returns>
|
||||
public bool TryGetPhysicalOffset(Buffer buffer, ulong minimumVirtOffset, out ulong physicalOffset, out ulong virtualOffset, out ulong size)
|
||||
{
|
||||
physicalOffset = 0;
|
||||
virtualOffset = 0;
|
||||
size = 0;
|
||||
|
||||
if (_dependencies != null)
|
||||
{
|
||||
foreach (var dependency in _dependencies)
|
||||
{
|
||||
if (dependency.PhysicalBuffer == buffer && dependency.VirtualOffset >= minimumVirtOffset)
|
||||
{
|
||||
physicalOffset = dependency.PhysicalOffset;
|
||||
virtualOffset = dependency.VirtualOffset;
|
||||
size = dependency.Size;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a modified virtual memory range.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is only required when the host does not support sparse buffers, otherwise only physical buffers need to track modification.
|
||||
/// </remarks>
|
||||
/// <param name="range">Modified range</param>
|
||||
/// <param name="modifiedSequenceNumber">ModificationSequenceNumber</param>
|
||||
public void AddModifiedRegion(MultiRange range, int modifiedSequenceNumber)
|
||||
{
|
||||
_modifiedRanges ??= new(_context, null, null);
|
||||
|
||||
for (int i = 0; i < range.Count; i++)
|
||||
{
|
||||
MemoryRange subRange = range.GetSubRange(i);
|
||||
|
||||
_modifiedRanges.SignalModified(subRange.Address, subRange.Size);
|
||||
}
|
||||
|
||||
ModificationSequenceNumber = modifiedSequenceNumber;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calls the specified <paramref name="rangeAction"/> for all modified ranges that overlaps with <paramref name="buffer"/>.
|
||||
/// </summary>
|
||||
/// <param name="buffer">Buffer to have its range checked</param>
|
||||
/// <param name="rangeAction">Action to perform for modified ranges</param>
|
||||
public void ConsumeModifiedRegion(Buffer buffer, Action<ulong, ulong> rangeAction)
|
||||
{
|
||||
ConsumeModifiedRegion(buffer.Address, buffer.Size, rangeAction);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calls the specified <paramref name="rangeAction"/> for all modified ranges that overlaps with <paramref name="address"/> and <paramref name="size"/>.
|
||||
/// </summary>
|
||||
/// <param name="address">Address of the region to consume</param>
|
||||
/// <param name="size">Size of the region to consume</param>
|
||||
/// <param name="rangeAction">Action to perform for modified ranges</param>
|
||||
public void ConsumeModifiedRegion(ulong address, ulong size, Action<ulong, ulong> rangeAction)
|
||||
{
|
||||
if (_modifiedRanges != null)
|
||||
{
|
||||
_modifiedRanges.GetRanges(address, size, rangeAction);
|
||||
_modifiedRanges.Clear(address, size);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets data from the specified region of the buffer, and places it on <paramref name="output"/>.
|
||||
/// </summary>
|
||||
/// <param name="output">Span to put the data into</param>
|
||||
/// <param name="offset">Offset of the buffer to get the data from</param>
|
||||
/// <param name="size">Size of the data in bytes</param>
|
||||
public void GetData(Span<byte> output, int offset, int size)
|
||||
{
|
||||
using PinnedSpan<byte> data = _context.Renderer.GetBufferData(Handle, offset, size);
|
||||
data.Get().CopyTo(output);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes the host buffer.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (_dependencies != null)
|
||||
{
|
||||
foreach (var dependency in _dependencies)
|
||||
{
|
||||
dependency.PhysicalBuffer.RemoveVirtualDependency(this);
|
||||
}
|
||||
|
||||
_dependencies = null;
|
||||
}
|
||||
|
||||
_context.Renderer.DeleteBuffer(Handle);
|
||||
}
|
||||
}
|
||||
|
@ -6,9 +6,9 @@ using System.Threading;
|
||||
namespace Ryujinx.Graphics.Gpu.Memory
|
||||
{
|
||||
/// <summary>
|
||||
/// Virtual buffer cache.
|
||||
/// Virtual range cache.
|
||||
/// </summary>
|
||||
class VirtualBufferCache
|
||||
class VirtualRangeCache
|
||||
{
|
||||
private readonly MemoryManager _memoryManager;
|
||||
|
||||
@ -68,10 +68,10 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
private int _hasDeferredUnmaps;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the virtual buffer cache.
|
||||
/// Creates a new instance of the virtual range cache.
|
||||
/// </summary>
|
||||
/// <param name="memoryManager">Memory manager that the virtual buffer cache belongs to</param>
|
||||
public VirtualBufferCache(MemoryManager memoryManager)
|
||||
/// <param name="memoryManager">Memory manager that the virtual range cache belongs to</param>
|
||||
public VirtualRangeCache(MemoryManager memoryManager)
|
||||
{
|
||||
_memoryManager = memoryManager;
|
||||
_virtualRanges = new RangeList<VirtualRange>();
|
||||
@ -102,10 +102,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// </summary>
|
||||
/// <param name="gpuVa">GPU virtual address to get the physical range from</param>
|
||||
/// <param name="size">Size in bytes of the region</param>
|
||||
/// <param name="supportsSparse">Indicates host support for sparse buffer mapping of non-contiguous ranges</param>
|
||||
/// <param name="range">Physical range for the specified GPU virtual region</param>
|
||||
/// <returns>True if the range already existed, false if a new one was created and added</returns>
|
||||
public bool TryGetOrAddRange(ulong gpuVa, ulong size, bool supportsSparse, out MultiRange range)
|
||||
public bool TryGetOrAddRange(ulong gpuVa, ulong size, out MultiRange range)
|
||||
{
|
||||
VirtualRange[] overlaps = _virtualRangeOverlaps;
|
||||
int overlapsCount;
|
||||
@ -158,7 +157,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
}
|
||||
else
|
||||
{
|
||||
found = true;
|
||||
found = overlap0.Range.Count == 1 || IsSparseAligned(overlap0.Range);
|
||||
range = overlap0.Range.Slice(gpuVa - overlap0.Address, size);
|
||||
}
|
||||
}
|
||||
@ -175,11 +174,10 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
ShrinkOverlapsBufferIfNeeded();
|
||||
|
||||
// If the the range is not properly aligned for sparse mapping,
|
||||
// or if the host does not support sparse mapping, let's just
|
||||
// force it to a single range.
|
||||
// let's just force it to a single range.
|
||||
// This might cause issues in some applications that uses sparse
|
||||
// mappings.
|
||||
if (!IsSparseAligned(range) || !supportsSparse)
|
||||
if (!IsSparseAligned(range))
|
||||
{
|
||||
range = new MultiRange(range.GetSubRange(0).Address, size);
|
||||
}
|
@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
private const ushort FileFormatVersionMajor = 1;
|
||||
private const ushort FileFormatVersionMinor = 2;
|
||||
private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor;
|
||||
private const uint CodeGenVersion = 5958;
|
||||
private const uint CodeGenVersion = 6253;
|
||||
|
||||
private const string SharedTocFileName = "shared.toc";
|
||||
private const string SharedDataFileName = "shared.data";
|
||||
|
@ -30,6 +30,8 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
_thread.Start();
|
||||
}
|
||||
|
||||
public bool HasContext() => _backgroundContext.HasContext();
|
||||
|
||||
private void Run()
|
||||
{
|
||||
InBackground = true;
|
||||
|
@ -68,6 +68,7 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
Add(Format.S8Uint, new FormatInfo(1, false, false, All.StencilIndex8, PixelFormat.StencilIndex, PixelType.UnsignedByte));
|
||||
Add(Format.D16Unorm, new FormatInfo(1, false, false, All.DepthComponent16, PixelFormat.DepthComponent, PixelType.UnsignedShort));
|
||||
Add(Format.S8UintD24Unorm, new FormatInfo(1, false, false, All.Depth24Stencil8, PixelFormat.DepthStencil, PixelType.UnsignedInt248));
|
||||
Add(Format.X8UintD24Unorm, new FormatInfo(1, false, false, All.DepthComponent24, PixelFormat.DepthComponent, PixelType.UnsignedInt));
|
||||
Add(Format.D32Float, new FormatInfo(1, false, false, All.DepthComponent32f, PixelFormat.DepthComponent, PixelType.Float));
|
||||
Add(Format.D24UnormS8Uint, new FormatInfo(1, false, false, All.Depth24Stencil8, PixelFormat.DepthStencil, PixelType.UnsignedInt248));
|
||||
Add(Format.D32FloatS8Uint, new FormatInfo(1, false, false, All.Depth32fStencil8, PixelFormat.DepthStencil, PixelType.Float32UnsignedInt248Rev));
|
||||
@ -161,6 +162,7 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
Add(Format.A1B5G5R5Unorm, new FormatInfo(4, true, false, All.Rgb5A1, PixelFormat.Rgba, PixelType.UnsignedShort5551));
|
||||
Add(Format.B8G8R8A8Unorm, new FormatInfo(4, true, false, All.Rgba8, PixelFormat.Rgba, PixelType.UnsignedByte));
|
||||
Add(Format.B8G8R8A8Srgb, new FormatInfo(4, false, false, All.Srgb8Alpha8, PixelFormat.Rgba, PixelType.UnsignedByte));
|
||||
Add(Format.B10G10R10A2Unorm, new FormatInfo(4, false, false, All.Rgb10A2, PixelFormat.Rgba, PixelType.UnsignedInt2101010Reversed));
|
||||
|
||||
Add(Format.R8Unorm, SizedInternalFormat.R8);
|
||||
Add(Format.R8Uint, SizedInternalFormat.R8ui);
|
||||
@ -223,5 +225,17 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
{
|
||||
return _tableImage[(int)format];
|
||||
}
|
||||
|
||||
public static bool IsPackedDepthStencil(Format format)
|
||||
{
|
||||
return format == Format.D24UnormS8Uint ||
|
||||
format == Format.D32FloatS8Uint ||
|
||||
format == Format.S8UintD24Unorm;
|
||||
}
|
||||
|
||||
public static bool IsDepthOnly(Format format)
|
||||
{
|
||||
return format == Format.D16Unorm || format == Format.D32Float || format == Format.X8UintD24Unorm;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -119,11 +119,11 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
|
||||
private static FramebufferAttachment GetAttachment(Format format)
|
||||
{
|
||||
if (IsPackedDepthStencilFormat(format))
|
||||
if (FormatTable.IsPackedDepthStencil(format))
|
||||
{
|
||||
return FramebufferAttachment.DepthStencilAttachment;
|
||||
}
|
||||
else if (IsDepthOnlyFormat(format))
|
||||
else if (FormatTable.IsDepthOnly(format))
|
||||
{
|
||||
return FramebufferAttachment.DepthAttachment;
|
||||
}
|
||||
@ -133,18 +133,6 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsPackedDepthStencilFormat(Format format)
|
||||
{
|
||||
return format == Format.D24UnormS8Uint ||
|
||||
format == Format.D32FloatS8Uint ||
|
||||
format == Format.S8UintD24Unorm;
|
||||
}
|
||||
|
||||
private static bool IsDepthOnlyFormat(Format format)
|
||||
{
|
||||
return format == Format.D16Unorm || format == Format.D32Float;
|
||||
}
|
||||
|
||||
public int GetColorLayerCount(int index)
|
||||
{
|
||||
return _colors[index]?.Info.GetDepthOrLayers() ?? 0;
|
||||
|
@ -7,21 +7,6 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
{
|
||||
void MakeCurrent();
|
||||
|
||||
// TODO: Support more APIs per platform.
|
||||
static bool HasContext()
|
||||
{
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
return WGLHelper.GetCurrentContext() != IntPtr.Zero;
|
||||
}
|
||||
else if (OperatingSystem.IsLinux())
|
||||
{
|
||||
return GLXHelper.GetCurrentContext() != IntPtr.Zero;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool HasContext();
|
||||
}
|
||||
}
|
||||
|
@ -294,7 +294,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
||||
{
|
||||
return FramebufferAttachment.DepthStencilAttachment;
|
||||
}
|
||||
else if (IsDepthOnly(format))
|
||||
else if (FormatTable.IsDepthOnly(format))
|
||||
{
|
||||
return FramebufferAttachment.DepthAttachment;
|
||||
}
|
||||
@ -324,11 +324,11 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
||||
|
||||
private static ClearBufferMask GetMask(Format format)
|
||||
{
|
||||
if (format == Format.D24UnormS8Uint || format == Format.D32FloatS8Uint || format == Format.S8UintD24Unorm)
|
||||
if (FormatTable.IsPackedDepthStencil(format))
|
||||
{
|
||||
return ClearBufferMask.DepthBufferBit | ClearBufferMask.StencilBufferBit;
|
||||
}
|
||||
else if (IsDepthOnly(format))
|
||||
else if (FormatTable.IsDepthOnly(format))
|
||||
{
|
||||
return ClearBufferMask.DepthBufferBit;
|
||||
}
|
||||
@ -342,11 +342,6 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsDepthOnly(Format format)
|
||||
{
|
||||
return format == Format.D16Unorm || format == Format.D32Float;
|
||||
}
|
||||
|
||||
public TextureView BgraSwap(TextureView from)
|
||||
{
|
||||
TextureView to = (TextureView)_renderer.CreateTexture(from.Info);
|
||||
|
@ -121,7 +121,7 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
|
||||
public HardwareInfo GetHardwareInfo()
|
||||
{
|
||||
return new HardwareInfo(GpuVendor, GpuRenderer);
|
||||
return new HardwareInfo(GpuVendor, GpuRenderer, GpuVendor); // OpenGL does not provide a driver name, vendor name is closest analogue.
|
||||
}
|
||||
|
||||
public PinnedSpan<byte> GetBufferData(BufferHandle buffer, int offset, int size)
|
||||
@ -248,7 +248,7 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
{
|
||||
// alwaysBackground is ignored, since we cannot switch from the current context.
|
||||
|
||||
if (IOpenGLContext.HasContext())
|
||||
if (_window.BackgroundContext.HasContext())
|
||||
{
|
||||
action(); // We have a context already - use that (assuming it is the main one).
|
||||
}
|
||||
|
@ -935,7 +935,7 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
SetFrontFace(_frontFace = frontFace.Convert());
|
||||
}
|
||||
|
||||
public void SetImage(int binding, ITexture texture, Format imageFormat)
|
||||
public void SetImage(ShaderStage stage, int binding, ITexture texture, Format imageFormat)
|
||||
{
|
||||
if ((uint)binding < SavedImages)
|
||||
{
|
||||
@ -1117,7 +1117,7 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
prg.Bind();
|
||||
}
|
||||
|
||||
if (prg.HasFragmentShader && _fragmentOutputMap != (uint)prg.FragmentOutputMap)
|
||||
if (_fragmentOutputMap != (uint)prg.FragmentOutputMap)
|
||||
{
|
||||
_fragmentOutputMap = (uint)prg.FragmentOutputMap;
|
||||
|
||||
|
@ -30,7 +30,6 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
private ProgramLinkStatus _status = ProgramLinkStatus.Incomplete;
|
||||
private int[] _shaderHandles;
|
||||
|
||||
public bool HasFragmentShader;
|
||||
public int FragmentOutputMap { get; }
|
||||
|
||||
public Program(ShaderSource[] shaders, int fragmentOutputMap)
|
||||
@ -40,6 +39,7 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
GL.ProgramParameter(Handle, ProgramParameterName.ProgramBinaryRetrievableHint, 1);
|
||||
|
||||
_shaderHandles = new int[shaders.Length];
|
||||
bool hasFragmentShader = false;
|
||||
|
||||
for (int index = 0; index < shaders.Length; index++)
|
||||
{
|
||||
@ -47,7 +47,7 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
|
||||
if (shader.Stage == ShaderStage.Fragment)
|
||||
{
|
||||
HasFragmentShader = true;
|
||||
hasFragmentShader = true;
|
||||
}
|
||||
|
||||
int shaderHandle = GL.CreateShader(shader.Stage.Convert());
|
||||
@ -71,7 +71,7 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
|
||||
GL.LinkProgram(Handle);
|
||||
|
||||
FragmentOutputMap = fragmentOutputMap;
|
||||
FragmentOutputMap = hasFragmentShader ? fragmentOutputMap : 0;
|
||||
}
|
||||
|
||||
public Program(ReadOnlySpan<byte> code, bool hasFragmentShader, int fragmentOutputMap)
|
||||
@ -91,8 +91,7 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
}
|
||||
}
|
||||
|
||||
HasFragmentShader = hasFragmentShader;
|
||||
FragmentOutputMap = fragmentOutputMap;
|
||||
FragmentOutputMap = hasFragmentShader ? fragmentOutputMap : 0;
|
||||
}
|
||||
|
||||
public void Bind()
|
||||
|
@ -578,12 +578,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
||||
type = SamplerType.Texture2D;
|
||||
flags = TextureFlags.Gather;
|
||||
|
||||
if (tld4sOp.Dc)
|
||||
{
|
||||
sourcesList.Add(Rb());
|
||||
|
||||
type |= SamplerType.Shadow;
|
||||
}
|
||||
int depthCompareIndex = sourcesList.Count;
|
||||
|
||||
if (tld4sOp.Aoffi)
|
||||
{
|
||||
@ -592,7 +587,16 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
||||
flags |= TextureFlags.Offset;
|
||||
}
|
||||
|
||||
sourcesList.Add(Const((int)tld4sOp.TexComp));
|
||||
if (tld4sOp.Dc)
|
||||
{
|
||||
sourcesList.Insert(depthCompareIndex, Rb());
|
||||
|
||||
type |= SamplerType.Shadow;
|
||||
}
|
||||
else
|
||||
{
|
||||
sourcesList.Add(Const((int)tld4sOp.TexComp));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -80,9 +80,10 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
return;
|
||||
}
|
||||
|
||||
if (TranslatorContext.Definitions.Stage == ShaderStage.Vertex && TranslatorContext.Options.TargetApi == TargetApi.Vulkan)
|
||||
// Vulkan requires the point size to be always written on the shader if the primitive topology is points.
|
||||
// OpenGL requires the point size to be always written on the shader if PROGRAM_POINT_SIZE is set.
|
||||
if (TranslatorContext.Definitions.Stage == ShaderStage.Vertex)
|
||||
{
|
||||
// Vulkan requires the point size to be always written on the shader if the primitive topology is points.
|
||||
this.Store(StorageKind.Output, IoVariable.PointSize, null, ConstF(TranslatorContext.Definitions.PointSize));
|
||||
}
|
||||
|
||||
|
@ -8,7 +8,6 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
|
||||
<ProjectReference Include="..\Ryujinx.Graphics.Device\Ryujinx.Graphics.Device.csproj" />
|
||||
<ProjectReference Include="..\Ryujinx.Graphics.Host1x\Ryujinx.Graphics.Host1x.csproj" />
|
||||
<ProjectReference Include="..\Ryujinx.Graphics.Texture\Ryujinx.Graphics.Texture.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
229
src/Ryujinx.Graphics.Vulkan/BarrierBatch.cs
Normal file
229
src/Ryujinx.Graphics.Vulkan/BarrierBatch.cs
Normal file
@ -0,0 +1,229 @@
|
||||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
internal class BarrierBatch : IDisposable
|
||||
{
|
||||
private const int MaxBarriersPerCall = 16;
|
||||
|
||||
private readonly VulkanRenderer _gd;
|
||||
|
||||
private readonly NativeArray<MemoryBarrier> _memoryBarrierBatch = new(MaxBarriersPerCall);
|
||||
private readonly NativeArray<BufferMemoryBarrier> _bufferBarrierBatch = new(MaxBarriersPerCall);
|
||||
private readonly NativeArray<ImageMemoryBarrier> _imageBarrierBatch = new(MaxBarriersPerCall);
|
||||
|
||||
private readonly List<BarrierWithStageFlags<MemoryBarrier>> _memoryBarriers = new();
|
||||
private readonly List<BarrierWithStageFlags<BufferMemoryBarrier>> _bufferBarriers = new();
|
||||
private readonly List<BarrierWithStageFlags<ImageMemoryBarrier>> _imageBarriers = new();
|
||||
private int _queuedBarrierCount;
|
||||
|
||||
public BarrierBatch(VulkanRenderer gd)
|
||||
{
|
||||
_gd = gd;
|
||||
}
|
||||
|
||||
private readonly record struct StageFlags : IEquatable<StageFlags>
|
||||
{
|
||||
public readonly PipelineStageFlags Source;
|
||||
public readonly PipelineStageFlags Dest;
|
||||
|
||||
public StageFlags(PipelineStageFlags source, PipelineStageFlags dest)
|
||||
{
|
||||
Source = source;
|
||||
Dest = dest;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly struct BarrierWithStageFlags<T> where T : unmanaged
|
||||
{
|
||||
public readonly StageFlags Flags;
|
||||
public readonly T Barrier;
|
||||
|
||||
public BarrierWithStageFlags(StageFlags flags, T barrier)
|
||||
{
|
||||
Flags = flags;
|
||||
Barrier = barrier;
|
||||
}
|
||||
|
||||
public BarrierWithStageFlags(PipelineStageFlags srcStageFlags, PipelineStageFlags dstStageFlags, T barrier)
|
||||
{
|
||||
Flags = new StageFlags(srcStageFlags, dstStageFlags);
|
||||
Barrier = barrier;
|
||||
}
|
||||
}
|
||||
|
||||
private void QueueBarrier<T>(List<BarrierWithStageFlags<T>> list, T barrier, PipelineStageFlags srcStageFlags, PipelineStageFlags dstStageFlags) where T : unmanaged
|
||||
{
|
||||
list.Add(new BarrierWithStageFlags<T>(srcStageFlags, dstStageFlags, barrier));
|
||||
_queuedBarrierCount++;
|
||||
}
|
||||
|
||||
public void QueueBarrier(MemoryBarrier barrier, PipelineStageFlags srcStageFlags, PipelineStageFlags dstStageFlags)
|
||||
{
|
||||
QueueBarrier(_memoryBarriers, barrier, srcStageFlags, dstStageFlags);
|
||||
}
|
||||
|
||||
public void QueueBarrier(BufferMemoryBarrier barrier, PipelineStageFlags srcStageFlags, PipelineStageFlags dstStageFlags)
|
||||
{
|
||||
QueueBarrier(_bufferBarriers, barrier, srcStageFlags, dstStageFlags);
|
||||
}
|
||||
|
||||
public void QueueBarrier(ImageMemoryBarrier barrier, PipelineStageFlags srcStageFlags, PipelineStageFlags dstStageFlags)
|
||||
{
|
||||
QueueBarrier(_imageBarriers, barrier, srcStageFlags, dstStageFlags);
|
||||
}
|
||||
|
||||
public unsafe void Flush(CommandBuffer cb, bool insideRenderPass, Action endRenderPass)
|
||||
{
|
||||
while (_queuedBarrierCount > 0)
|
||||
{
|
||||
int memoryCount = 0;
|
||||
int bufferCount = 0;
|
||||
int imageCount = 0;
|
||||
|
||||
bool hasBarrier = false;
|
||||
StageFlags flags = default;
|
||||
|
||||
static void AddBarriers<T>(
|
||||
Span<T> target,
|
||||
ref int queuedBarrierCount,
|
||||
ref bool hasBarrier,
|
||||
ref StageFlags flags,
|
||||
ref int count,
|
||||
List<BarrierWithStageFlags<T>> list) where T : unmanaged
|
||||
{
|
||||
int firstMatch = -1;
|
||||
int end = list.Count;
|
||||
|
||||
for (int i = 0; i < list.Count; i++)
|
||||
{
|
||||
BarrierWithStageFlags<T> barrier = list[i];
|
||||
|
||||
if (!hasBarrier)
|
||||
{
|
||||
flags = barrier.Flags;
|
||||
hasBarrier = true;
|
||||
|
||||
target[count++] = barrier.Barrier;
|
||||
queuedBarrierCount--;
|
||||
firstMatch = i;
|
||||
|
||||
if (count >= target.Length)
|
||||
{
|
||||
end = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (flags.Equals(barrier.Flags))
|
||||
{
|
||||
target[count++] = barrier.Barrier;
|
||||
queuedBarrierCount--;
|
||||
|
||||
if (firstMatch == -1)
|
||||
{
|
||||
firstMatch = i;
|
||||
}
|
||||
|
||||
if (count >= target.Length)
|
||||
{
|
||||
end = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Delete consumed barriers from the first match to the current non-match.
|
||||
if (firstMatch != -1)
|
||||
{
|
||||
int deleteCount = i - firstMatch;
|
||||
list.RemoveRange(firstMatch, deleteCount);
|
||||
i -= deleteCount;
|
||||
|
||||
firstMatch = -1;
|
||||
end = list.Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (firstMatch == 0 && end == list.Count)
|
||||
{
|
||||
list.Clear();
|
||||
}
|
||||
else if (firstMatch != -1)
|
||||
{
|
||||
int deleteCount = end - firstMatch;
|
||||
|
||||
list.RemoveRange(firstMatch, deleteCount);
|
||||
}
|
||||
}
|
||||
|
||||
if (insideRenderPass)
|
||||
{
|
||||
// Image barriers queued in the batch are meant to be globally scoped,
|
||||
// but inside a render pass they're scoped to just the range of the render pass.
|
||||
|
||||
// On MoltenVK, we just break the rules and always use image barrier.
|
||||
// On desktop GPUs, all barriers are globally scoped, so we just replace it with a generic memory barrier.
|
||||
// TODO: On certain GPUs, we need to split render pass so the barrier scope is global. When this is done,
|
||||
// notify the resource that it should add a barrier as soon as a render pass ends to avoid this in future.
|
||||
|
||||
if (!_gd.IsMoltenVk)
|
||||
{
|
||||
foreach (var barrier in _imageBarriers)
|
||||
{
|
||||
_memoryBarriers.Add(new BarrierWithStageFlags<MemoryBarrier>(
|
||||
barrier.Flags,
|
||||
new MemoryBarrier()
|
||||
{
|
||||
SType = StructureType.MemoryBarrier,
|
||||
SrcAccessMask = barrier.Barrier.SrcAccessMask,
|
||||
DstAccessMask = barrier.Barrier.DstAccessMask
|
||||
}));
|
||||
}
|
||||
|
||||
_imageBarriers.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
AddBarriers(_memoryBarrierBatch.AsSpan(), ref _queuedBarrierCount, ref hasBarrier, ref flags, ref memoryCount, _memoryBarriers);
|
||||
AddBarriers(_bufferBarrierBatch.AsSpan(), ref _queuedBarrierCount, ref hasBarrier, ref flags, ref bufferCount, _bufferBarriers);
|
||||
AddBarriers(_imageBarrierBatch.AsSpan(), ref _queuedBarrierCount, ref hasBarrier, ref flags, ref imageCount, _imageBarriers);
|
||||
|
||||
if (hasBarrier)
|
||||
{
|
||||
PipelineStageFlags srcStageFlags = flags.Source;
|
||||
|
||||
if (insideRenderPass)
|
||||
{
|
||||
// Inside a render pass, barrier stages can only be from rasterization.
|
||||
srcStageFlags &= ~PipelineStageFlags.ComputeShaderBit;
|
||||
}
|
||||
|
||||
_gd.Api.CmdPipelineBarrier(
|
||||
cb,
|
||||
srcStageFlags,
|
||||
flags.Dest,
|
||||
0,
|
||||
(uint)memoryCount,
|
||||
_memoryBarrierBatch.Pointer,
|
||||
(uint)bufferCount,
|
||||
_bufferBarrierBatch.Pointer,
|
||||
(uint)imageCount,
|
||||
_imageBarrierBatch.Pointer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_memoryBarrierBatch.Dispose();
|
||||
_bufferBarrierBatch.Dispose();
|
||||
_imageBarrierBatch.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
@ -4,6 +4,7 @@ using Silk.NET.Vulkan;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using VkBuffer = Silk.NET.Vulkan.Buffer;
|
||||
using VkFormat = Silk.NET.Vulkan.Format;
|
||||
@ -384,7 +385,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
var baseData = new Span<byte>((void*)(_map + offset), size);
|
||||
var modData = _pendingData.AsSpan(offset, size);
|
||||
|
||||
StagingBufferReserved? newMirror = _gd.BufferManager.StagingBuffer.TryReserveData(cbs, size, (int)_gd.Capabilities.MinResourceAlignment);
|
||||
StagingBufferReserved? newMirror = _gd.BufferManager.StagingBuffer.TryReserveData(cbs, size);
|
||||
|
||||
if (newMirror != null)
|
||||
{
|
||||
@ -838,6 +839,11 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe void SetDataUnchecked<T>(int offset, ReadOnlySpan<T> data) where T : unmanaged
|
||||
{
|
||||
SetDataUnchecked(offset, MemoryMarshal.AsBytes(data));
|
||||
}
|
||||
|
||||
public void SetDataInline(CommandBufferScoped cbs, Action endRenderPass, int dstOffset, ReadOnlySpan<byte> data)
|
||||
{
|
||||
if (!TryPushData(cbs, endRenderPass, dstOffset, data))
|
||||
|
@ -9,6 +9,36 @@ using VkFormat = Silk.NET.Vulkan.Format;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
readonly struct ScopedTemporaryBuffer : IDisposable
|
||||
{
|
||||
private readonly BufferManager _bufferManager;
|
||||
private readonly bool _isReserved;
|
||||
|
||||
public readonly BufferRange Range;
|
||||
public readonly BufferHolder Holder;
|
||||
|
||||
public BufferHandle Handle => Range.Handle;
|
||||
public int Offset => Range.Offset;
|
||||
|
||||
public ScopedTemporaryBuffer(BufferManager bufferManager, BufferHolder holder, BufferHandle handle, int offset, int size, bool isReserved)
|
||||
{
|
||||
_bufferManager = bufferManager;
|
||||
|
||||
Range = new BufferRange(handle, offset, size);
|
||||
Holder = holder;
|
||||
|
||||
_isReserved = isReserved;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (!_isReserved)
|
||||
{
|
||||
_bufferManager.Delete(Range.Handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class BufferManager : IDisposable
|
||||
{
|
||||
public const MemoryPropertyFlags DefaultBufferMemoryFlags =
|
||||
@ -238,6 +268,23 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
return Unsafe.As<ulong, BufferHandle>(ref handle64);
|
||||
}
|
||||
|
||||
public ScopedTemporaryBuffer ReserveOrCreate(VulkanRenderer gd, CommandBufferScoped cbs, int size)
|
||||
{
|
||||
StagingBufferReserved? result = StagingBuffer.TryReserveData(cbs, size);
|
||||
|
||||
if (result.HasValue)
|
||||
{
|
||||
return new ScopedTemporaryBuffer(this, result.Value.Buffer, StagingBuffer.Handle, result.Value.Offset, result.Value.Size, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Create a temporary buffer.
|
||||
BufferHandle handle = CreateWithHandle(gd, size, out BufferHolder holder);
|
||||
|
||||
return new ScopedTemporaryBuffer(this, holder, handle, 0, size, false);
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe MemoryRequirements GetHostImportedUsageRequirements(VulkanRenderer gd)
|
||||
{
|
||||
var usage = HostImportedBufferUsageFlags;
|
||||
@ -635,13 +682,14 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
StagingBuffer.Dispose();
|
||||
|
||||
foreach (BufferHolder buffer in _buffers)
|
||||
{
|
||||
buffer.Dispose();
|
||||
}
|
||||
|
||||
_buffers.Clear();
|
||||
StagingBuffer.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
public const int MaxStorageBufferBindings = MaxStorageBuffersPerStage * MaxShaderStages;
|
||||
public const int MaxTextureBindings = MaxTexturesPerStage * MaxShaderStages;
|
||||
public const int MaxImageBindings = MaxImagesPerStage * MaxShaderStages;
|
||||
public const int MaxPushDescriptorBinding = 64;
|
||||
|
||||
public const ulong SparseBufferAlignment = 0x10000;
|
||||
}
|
||||
|
@ -1,19 +1,32 @@
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
class DescriptorSetTemplate : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Renderdoc seems to crash when doing a templated uniform update with count > 1 on a push descriptor.
|
||||
/// When this is true, consecutive buffers are always updated individually.
|
||||
/// </summary>
|
||||
private const bool RenderdocPushCountBug = true;
|
||||
|
||||
private readonly VulkanRenderer _gd;
|
||||
private readonly Device _device;
|
||||
|
||||
public readonly DescriptorUpdateTemplate Template;
|
||||
public readonly int Size;
|
||||
|
||||
public unsafe DescriptorSetTemplate(VulkanRenderer gd, Device device, ResourceBindingSegment[] segments, PipelineLayoutCacheEntry plce, PipelineBindPoint pbp, int setIndex)
|
||||
public unsafe DescriptorSetTemplate(
|
||||
VulkanRenderer gd,
|
||||
Device device,
|
||||
ResourceBindingSegment[] segments,
|
||||
PipelineLayoutCacheEntry plce,
|
||||
PipelineBindPoint pbp,
|
||||
int setIndex)
|
||||
{
|
||||
_gd = gd;
|
||||
_device = device;
|
||||
@ -137,6 +150,93 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
Template = result;
|
||||
}
|
||||
|
||||
public unsafe DescriptorSetTemplate(
|
||||
VulkanRenderer gd,
|
||||
Device device,
|
||||
ResourceDescriptorCollection descriptors,
|
||||
long updateMask,
|
||||
PipelineLayoutCacheEntry plce,
|
||||
PipelineBindPoint pbp,
|
||||
int setIndex)
|
||||
{
|
||||
_gd = gd;
|
||||
_device = device;
|
||||
|
||||
// Create a template from the set usages. Assumes the descriptor set is updated in segment order then binding order.
|
||||
int segmentCount = BitOperations.PopCount((ulong)updateMask);
|
||||
|
||||
DescriptorUpdateTemplateEntry* entries = stackalloc DescriptorUpdateTemplateEntry[segmentCount];
|
||||
int entry = 0;
|
||||
nuint structureOffset = 0;
|
||||
|
||||
void AddBinding(int binding, int count)
|
||||
{
|
||||
entries[entry++] = new DescriptorUpdateTemplateEntry()
|
||||
{
|
||||
DescriptorType = DescriptorType.UniformBuffer,
|
||||
DstBinding = (uint)binding,
|
||||
DescriptorCount = (uint)count,
|
||||
Offset = structureOffset,
|
||||
Stride = (nuint)Unsafe.SizeOf<DescriptorBufferInfo>()
|
||||
};
|
||||
|
||||
structureOffset += (nuint)(Unsafe.SizeOf<DescriptorBufferInfo>() * count);
|
||||
}
|
||||
|
||||
int startBinding = 0;
|
||||
int bindingCount = 0;
|
||||
|
||||
foreach (ResourceDescriptor descriptor in descriptors.Descriptors)
|
||||
{
|
||||
for (int i = 0; i < descriptor.Count; i++)
|
||||
{
|
||||
int binding = descriptor.Binding + i;
|
||||
|
||||
if ((updateMask & (1L << binding)) != 0)
|
||||
{
|
||||
if (bindingCount > 0 && (RenderdocPushCountBug || startBinding + bindingCount != binding))
|
||||
{
|
||||
AddBinding(startBinding, bindingCount);
|
||||
|
||||
bindingCount = 0;
|
||||
}
|
||||
|
||||
if (bindingCount == 0)
|
||||
{
|
||||
startBinding = binding;
|
||||
}
|
||||
|
||||
bindingCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bindingCount > 0)
|
||||
{
|
||||
AddBinding(startBinding, bindingCount);
|
||||
}
|
||||
|
||||
Size = (int)structureOffset;
|
||||
|
||||
var info = new DescriptorUpdateTemplateCreateInfo()
|
||||
{
|
||||
SType = StructureType.DescriptorUpdateTemplateCreateInfo,
|
||||
DescriptorUpdateEntryCount = (uint)entry,
|
||||
PDescriptorUpdateEntries = entries,
|
||||
|
||||
TemplateType = DescriptorUpdateTemplateType.PushDescriptorsKhr,
|
||||
DescriptorSetLayout = plce.DescriptorSetLayouts[setIndex],
|
||||
PipelineBindPoint = pbp,
|
||||
PipelineLayout = plce.PipelineLayout,
|
||||
Set = (uint)setIndex,
|
||||
};
|
||||
|
||||
DescriptorUpdateTemplate result;
|
||||
gd.Api.CreateDescriptorUpdateTemplate(device, &info, null, &result).ThrowOnError();
|
||||
|
||||
Template = result;
|
||||
}
|
||||
|
||||
public unsafe void Dispose()
|
||||
{
|
||||
_gd.Api.DestroyDescriptorUpdateTemplate(_device, Template, null);
|
||||
|
@ -52,11 +52,23 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
return new DescriptorSetTemplateWriter(new Span<byte>(_data.Pointer, template.Size));
|
||||
}
|
||||
|
||||
public DescriptorSetTemplateWriter Begin(int maxSize)
|
||||
{
|
||||
EnsureSize(maxSize);
|
||||
|
||||
return new DescriptorSetTemplateWriter(new Span<byte>(_data.Pointer, maxSize));
|
||||
}
|
||||
|
||||
public void Commit(VulkanRenderer gd, Device device, DescriptorSet set)
|
||||
{
|
||||
gd.Api.UpdateDescriptorSetWithTemplate(device, set, _activeTemplate.Template, _data.Pointer);
|
||||
}
|
||||
|
||||
public void CommitPushDescriptor(VulkanRenderer gd, CommandBufferScoped cbs, DescriptorSetTemplate template, PipelineLayout layout)
|
||||
{
|
||||
gd.PushDescriptorApi.CmdPushDescriptorSetWithTemplate(cbs.CommandBuffer, template.Template, layout, 0, _data.Pointer);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_data?.Dispose();
|
||||
|
@ -4,6 +4,7 @@ using Ryujinx.Graphics.Shader;
|
||||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using CompareOp = Ryujinx.Graphics.GAL.CompareOp;
|
||||
using Format = Ryujinx.Graphics.GAL.Format;
|
||||
using SamplerCreateInfo = Ryujinx.Graphics.GAL.SamplerCreateInfo;
|
||||
@ -34,6 +35,36 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
}
|
||||
}
|
||||
|
||||
private record struct TextureRef
|
||||
{
|
||||
public ShaderStage Stage;
|
||||
public TextureStorage Storage;
|
||||
public Auto<DisposableImageView> View;
|
||||
public Auto<DisposableSampler> Sampler;
|
||||
|
||||
public TextureRef(ShaderStage stage, TextureStorage storage, Auto<DisposableImageView> view, Auto<DisposableSampler> sampler)
|
||||
{
|
||||
Stage = stage;
|
||||
Storage = storage;
|
||||
View = view;
|
||||
Sampler = sampler;
|
||||
}
|
||||
}
|
||||
|
||||
private record struct ImageRef
|
||||
{
|
||||
public ShaderStage Stage;
|
||||
public TextureStorage Storage;
|
||||
public Auto<DisposableImageView> View;
|
||||
|
||||
public ImageRef(ShaderStage stage, TextureStorage storage, Auto<DisposableImageView> view)
|
||||
{
|
||||
Stage = stage;
|
||||
Storage = storage;
|
||||
View = view;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly VulkanRenderer _gd;
|
||||
private readonly Device _device;
|
||||
private readonly PipelineBase _pipeline;
|
||||
@ -41,9 +72,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
private readonly BufferRef[] _uniformBufferRefs;
|
||||
private readonly BufferRef[] _storageBufferRefs;
|
||||
private readonly Auto<DisposableImageView>[] _textureRefs;
|
||||
private readonly Auto<DisposableSampler>[] _samplerRefs;
|
||||
private readonly Auto<DisposableImageView>[] _imageRefs;
|
||||
private readonly TextureRef[] _textureRefs;
|
||||
private readonly ImageRef[] _imageRefs;
|
||||
private readonly TextureBuffer[] _bufferTextureRefs;
|
||||
private readonly TextureBuffer[] _bufferImageRefs;
|
||||
private readonly Format[] _bufferImageFormats;
|
||||
@ -61,6 +91,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
private BitMapStruct<Array2<long>> _storageSet;
|
||||
private BitMapStruct<Array2<long>> _uniformMirrored;
|
||||
private BitMapStruct<Array2<long>> _storageMirrored;
|
||||
private readonly int[] _uniformSetPd;
|
||||
private int _pdSequence = 1;
|
||||
|
||||
private bool _updateDescriptorCacheCbIndex;
|
||||
|
||||
@ -92,9 +124,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
_uniformBufferRefs = new BufferRef[Constants.MaxUniformBufferBindings];
|
||||
_storageBufferRefs = new BufferRef[Constants.MaxStorageBufferBindings];
|
||||
_textureRefs = new Auto<DisposableImageView>[Constants.MaxTextureBindings * 2];
|
||||
_samplerRefs = new Auto<DisposableSampler>[Constants.MaxTextureBindings * 2];
|
||||
_imageRefs = new Auto<DisposableImageView>[Constants.MaxImageBindings * 2];
|
||||
_textureRefs = new TextureRef[Constants.MaxTextureBindings * 2];
|
||||
_imageRefs = new ImageRef[Constants.MaxImageBindings * 2];
|
||||
_bufferTextureRefs = new TextureBuffer[Constants.MaxTextureBindings * 2];
|
||||
_bufferImageRefs = new TextureBuffer[Constants.MaxImageBindings * 2];
|
||||
_bufferImageFormats = new Format[Constants.MaxImageBindings * 2];
|
||||
@ -106,6 +137,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
_bufferTextures = new BufferView[Constants.MaxTexturesPerStage];
|
||||
_bufferImages = new BufferView[Constants.MaxImagesPerStage];
|
||||
|
||||
_uniformSetPd = new int[Constants.MaxUniformBufferBindings];
|
||||
|
||||
var initialImageInfo = new DescriptorImageInfo
|
||||
{
|
||||
ImageLayout = ImageLayout.General,
|
||||
@ -193,6 +226,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
if (BindingOverlaps(ref info, bindingOffset, offset, size))
|
||||
{
|
||||
_uniformSet.Clear(binding);
|
||||
_uniformSetPd[binding] = 0;
|
||||
SignalDirty(DirtyFlags.Uniform);
|
||||
}
|
||||
}
|
||||
@ -223,14 +257,61 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
});
|
||||
}
|
||||
|
||||
public void SetProgram(ShaderCollection program)
|
||||
public void InsertBindingBarriers(CommandBufferScoped cbs)
|
||||
{
|
||||
foreach (ResourceBindingSegment segment in _program.BindingSegments[PipelineBase.TextureSetIndex])
|
||||
{
|
||||
if (segment.Type == ResourceType.TextureAndSampler)
|
||||
{
|
||||
for (int i = 0; i < segment.Count; i++)
|
||||
{
|
||||
ref var texture = ref _textureRefs[segment.Binding + i];
|
||||
texture.Storage?.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, texture.Stage.ConvertToPipelineStageFlags());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (ResourceBindingSegment segment in _program.BindingSegments[PipelineBase.ImageSetIndex])
|
||||
{
|
||||
if (segment.Type == ResourceType.Image)
|
||||
{
|
||||
for (int i = 0; i < segment.Count; i++)
|
||||
{
|
||||
ref var image = ref _imageRefs[segment.Binding + i];
|
||||
image.Storage?.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, image.Stage.ConvertToPipelineStageFlags());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AdvancePdSequence()
|
||||
{
|
||||
if (++_pdSequence == 0)
|
||||
{
|
||||
_pdSequence = 1;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetProgram(CommandBufferScoped cbs, ShaderCollection program, bool isBound)
|
||||
{
|
||||
if (!program.HasSameLayout(_program))
|
||||
{
|
||||
// When the pipeline layout changes, push descriptor bindings are invalidated.
|
||||
|
||||
AdvancePdSequence();
|
||||
}
|
||||
|
||||
_program = program;
|
||||
_updateDescriptorCacheCbIndex = true;
|
||||
_dirty = DirtyFlags.All;
|
||||
}
|
||||
|
||||
public void SetImage(int binding, ITexture image, Format imageFormat)
|
||||
public void SetImage(
|
||||
CommandBufferScoped cbs,
|
||||
ShaderStage stage,
|
||||
int binding,
|
||||
ITexture image,
|
||||
Format imageFormat)
|
||||
{
|
||||
if (image is TextureBuffer imageBuffer)
|
||||
{
|
||||
@ -239,11 +320,13 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
}
|
||||
else if (image is TextureView view)
|
||||
{
|
||||
_imageRefs[binding] = view.GetView(imageFormat).GetIdentityImageView();
|
||||
view.Storage.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, stage.ConvertToPipelineStageFlags());
|
||||
|
||||
_imageRefs[binding] = new(stage, view.Storage, view.GetView(imageFormat).GetIdentityImageView());
|
||||
}
|
||||
else
|
||||
{
|
||||
_imageRefs[binding] = null;
|
||||
_imageRefs[binding] = default;
|
||||
_bufferImageRefs[binding] = null;
|
||||
_bufferImageFormats[binding] = default;
|
||||
}
|
||||
@ -253,7 +336,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
public void SetImage(int binding, Auto<DisposableImageView> image)
|
||||
{
|
||||
_imageRefs[binding] = image;
|
||||
_imageRefs[binding] = new(ShaderStage.Compute, null, image);
|
||||
|
||||
SignalDirty(DirtyFlags.Image);
|
||||
}
|
||||
@ -338,15 +421,13 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
}
|
||||
else if (texture is TextureView view)
|
||||
{
|
||||
view.Storage.InsertWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, stage.ConvertToPipelineStageFlags());
|
||||
view.Storage.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, stage.ConvertToPipelineStageFlags());
|
||||
|
||||
_textureRefs[binding] = view.GetImageView();
|
||||
_samplerRefs[binding] = ((SamplerHolder)sampler)?.GetSampler();
|
||||
_textureRefs[binding] = new(stage, view.Storage, view.GetImageView(), ((SamplerHolder)sampler)?.GetSampler());
|
||||
}
|
||||
else
|
||||
{
|
||||
_textureRefs[binding] = null;
|
||||
_samplerRefs[binding] = null;
|
||||
_textureRefs[binding] = default;
|
||||
_bufferTextureRefs[binding] = null;
|
||||
}
|
||||
|
||||
@ -362,10 +443,9 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
if (texture is TextureView view)
|
||||
{
|
||||
view.Storage.InsertWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, stage.ConvertToPipelineStageFlags());
|
||||
view.Storage.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, stage.ConvertToPipelineStageFlags());
|
||||
|
||||
_textureRefs[binding] = view.GetIdentityImageView();
|
||||
_samplerRefs[binding] = ((SamplerHolder)sampler)?.GetSampler();
|
||||
_textureRefs[binding] = new(stage, view.Storage, view.GetIdentityImageView(), ((SamplerHolder)sampler)?.GetSampler());
|
||||
|
||||
SignalDirty(DirtyFlags.Texture);
|
||||
}
|
||||
@ -402,6 +482,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
if (!currentBufferRef.Equals(newRef) || currentInfo.Range != info.Range)
|
||||
{
|
||||
_uniformSet.Clear(index);
|
||||
_uniformSetPd[index] = 0;
|
||||
|
||||
currentInfo = info;
|
||||
currentBufferRef = newRef;
|
||||
@ -579,9 +660,10 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
ref var texture = ref textures[i];
|
||||
ref var refs = ref _textureRefs[binding + i];
|
||||
|
||||
texture.ImageView = _textureRefs[binding + i]?.Get(cbs).Value ?? default;
|
||||
texture.Sampler = _samplerRefs[binding + i]?.Get(cbs).Value ?? default;
|
||||
texture.ImageView = refs.View?.Get(cbs).Value ?? default;
|
||||
texture.Sampler = refs.Sampler?.Get(cbs).Value ?? default;
|
||||
|
||||
if (texture.ImageView.Handle == 0)
|
||||
{
|
||||
@ -616,7 +698,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
images[i].ImageView = _imageRefs[binding + i]?.Get(cbs).Value ?? default;
|
||||
images[i].ImageView = _imageRefs[binding + i].View?.Get(cbs).Value ?? default;
|
||||
}
|
||||
|
||||
tu.Push<DescriptorImageInfo>(images[..count]);
|
||||
@ -671,15 +753,19 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void UpdateAndBindUniformBufferPd(CommandBufferScoped cbs, PipelineBindPoint pbp)
|
||||
{
|
||||
int sequence = _pdSequence;
|
||||
var bindingSegments = _program.BindingSegments[PipelineBase.UniformSetIndex];
|
||||
var dummyBuffer = _dummyBuffer?.GetBuffer();
|
||||
|
||||
long updatedBindings = 0;
|
||||
DescriptorSetTemplateWriter writer = _templateUpdater.Begin(32 * Unsafe.SizeOf<DescriptorBufferInfo>());
|
||||
|
||||
foreach (ResourceBindingSegment segment in bindingSegments)
|
||||
{
|
||||
int binding = segment.Binding;
|
||||
int count = segment.Count;
|
||||
|
||||
bool doUpdate = false;
|
||||
ReadOnlySpan<DescriptorBufferInfo> uniformBuffers = _uniformBuffers;
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
@ -688,16 +774,28 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
if (_uniformSet.Set(index))
|
||||
{
|
||||
ref BufferRef buffer = ref _uniformBufferRefs[index];
|
||||
UpdateBuffer(cbs, ref _uniformBuffers[index], ref buffer, dummyBuffer, true);
|
||||
doUpdate = true;
|
||||
|
||||
bool mirrored = UpdateBuffer(cbs, ref _uniformBuffers[index], ref buffer, dummyBuffer, true);
|
||||
|
||||
_uniformMirrored.Set(index, mirrored);
|
||||
}
|
||||
|
||||
if (_uniformSetPd[index] != sequence)
|
||||
{
|
||||
// Need to set this push descriptor (even if the buffer binding has not changed)
|
||||
|
||||
_uniformSetPd[index] = sequence;
|
||||
updatedBindings |= 1L << index;
|
||||
|
||||
writer.Push(MemoryMarshal.CreateReadOnlySpan(ref _uniformBuffers[index], 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (doUpdate)
|
||||
{
|
||||
ReadOnlySpan<DescriptorBufferInfo> uniformBuffers = _uniformBuffers;
|
||||
UpdateBuffers(cbs, pbp, binding, uniformBuffers.Slice(binding, count), DescriptorType.UniformBuffer);
|
||||
}
|
||||
if (updatedBindings > 0)
|
||||
{
|
||||
DescriptorSetTemplate template = _program.GetPushDescriptorTemplate(updatedBindings);
|
||||
_templateUpdater.CommitPushDescriptor(_gd, cbs, template, _program.PipelineLayout);
|
||||
}
|
||||
}
|
||||
|
||||
@ -724,6 +822,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
_uniformSet.Clear();
|
||||
_storageSet.Clear();
|
||||
AdvancePdSequence();
|
||||
}
|
||||
|
||||
private static void SwapBuffer(BufferRef[] list, Auto<DisposableBuffer> from, Auto<DisposableBuffer> to)
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user