forked from MeloNX/MeloNX
Merge pull request 'Feat: Implement Latest Ryujinx core into MeloNX' (#18) from melo-latest-ryu into XC-ios-ht
Reviewed-on: MeloNX/MeloNX#18
This commit is contained in:
commit
b45af91523
@ -17,8 +17,8 @@ tab_width = 4
|
|||||||
end_of_line = lf
|
end_of_line = lf
|
||||||
insert_final_newline = true
|
insert_final_newline = true
|
||||||
|
|
||||||
# JSON files
|
# Markdown, JSON, YAML, props and csproj files
|
||||||
[*.json]
|
[*.{md,json,yml,props,csproj}]
|
||||||
|
|
||||||
# Indentation and spacing
|
# Indentation and spacing
|
||||||
indent_size = 2
|
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'"
|
# 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
|
dotnet_diagnostic.CA1862.severity = none
|
||||||
|
|
||||||
[src/Ryujinx.HLE/HOS/Services/**.cs]
|
[src/Ryujinx/UI/ViewModels/**.cs]
|
||||||
# Disable "mark members as static" rule for services
|
# Disable "mark members as static" rule for ViewModels
|
||||||
dotnet_diagnostic.CA1822.severity = none
|
dotnet_diagnostic.CA1822.severity = none
|
||||||
|
|
||||||
[src/Ryujinx.Ava/UI/ViewModels/**.cs]
|
[src/Ryujinx.HLE/HOS/Services/**.cs]
|
||||||
# Disable "mark members as static" rule for ViewModels
|
# Disable "mark members as static" rule for services
|
||||||
dotnet_diagnostic.CA1822.severity = none
|
dotnet_diagnostic.CA1822.severity = none
|
||||||
|
|
||||||
[src/Ryujinx.Tests/Cpu/*.cs]
|
[src/Ryujinx.Tests/Cpu/*.cs]
|
||||||
|
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@ -23,7 +23,7 @@ body:
|
|||||||
attributes:
|
attributes:
|
||||||
label: Log file
|
label: Log file
|
||||||
description: A log file will help our developers to better diagnose and fix the issue.
|
description: A log file will help our developers to better diagnose and fix the issue.
|
||||||
placeholder: Logs files can be found under "Logs" folder in Ryujinx program folder. You can drag and drop the log on to the text area
|
placeholder: Logs files can be found under "Logs" folder in Ryujinx program folder. They can also be accessed by opening Ryujinx, then going to File > Open Logs Folder. You can drag and drop the log on to the text area (do not copy paste).
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: input
|
- type: input
|
||||||
|
1
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
1
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
@ -1,6 +1,7 @@
|
|||||||
name: Feature Request
|
name: Feature Request
|
||||||
description: Suggest a new feature for Ryujinx.
|
description: Suggest a new feature for Ryujinx.
|
||||||
title: "[Feature Request]"
|
title: "[Feature Request]"
|
||||||
|
labels: enhancement
|
||||||
body:
|
body:
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: overview
|
id: overview
|
||||||
|
22
.github/dependabot.yml
vendored
22
.github/dependabot.yml
vendored
@ -7,18 +7,34 @@ updates:
|
|||||||
labels:
|
labels:
|
||||||
- "infra"
|
- "infra"
|
||||||
reviewers:
|
reviewers:
|
||||||
- marysaka
|
- TSRBerry
|
||||||
commit-message:
|
commit-message:
|
||||||
prefix: "ci"
|
prefix: "ci"
|
||||||
|
|
||||||
- package-ecosystem: nuget
|
- package-ecosystem: nuget
|
||||||
directory: /
|
directory: /
|
||||||
open-pull-requests-limit: 5
|
open-pull-requests-limit: 10
|
||||||
schedule:
|
schedule:
|
||||||
interval: daily
|
interval: daily
|
||||||
labels:
|
labels:
|
||||||
- "infra"
|
- "infra"
|
||||||
reviewers:
|
reviewers:
|
||||||
- marysaka
|
- TSRBerry
|
||||||
commit-message:
|
commit-message:
|
||||||
prefix: nuget
|
prefix: nuget
|
||||||
|
groups:
|
||||||
|
Avalonia:
|
||||||
|
patterns:
|
||||||
|
- "*Avalonia*"
|
||||||
|
Silk.NET:
|
||||||
|
patterns:
|
||||||
|
- "Silk.NET*"
|
||||||
|
OpenTK:
|
||||||
|
patterns:
|
||||||
|
- "OpenTK*"
|
||||||
|
SixLabors:
|
||||||
|
patterns:
|
||||||
|
- "SixLabors*"
|
||||||
|
NUnit:
|
||||||
|
patterns:
|
||||||
|
- "NUnit*"
|
||||||
|
2
.github/labeler.yml
vendored
2
.github/labeler.yml
vendored
@ -20,7 +20,7 @@ gpu:
|
|||||||
|
|
||||||
gui:
|
gui:
|
||||||
- changed-files:
|
- 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:
|
horizon:
|
||||||
- changed-files:
|
- changed-files:
|
||||||
|
7
.github/reviewers.yml
vendored
7
.github/reviewers.yml
vendored
@ -1,31 +1,24 @@
|
|||||||
audio:
|
|
||||||
- marysaka
|
|
||||||
|
|
||||||
cpu:
|
cpu:
|
||||||
- gdkchan
|
- gdkchan
|
||||||
- riperiperi
|
- riperiperi
|
||||||
- marysaka
|
|
||||||
- LDj3SNuD
|
- LDj3SNuD
|
||||||
|
|
||||||
gpu:
|
gpu:
|
||||||
- gdkchan
|
- gdkchan
|
||||||
- riperiperi
|
- riperiperi
|
||||||
- marysaka
|
|
||||||
|
|
||||||
gui:
|
gui:
|
||||||
- Ack77
|
- Ack77
|
||||||
- emmauss
|
- emmauss
|
||||||
- TSRBerry
|
- TSRBerry
|
||||||
- marysaka
|
|
||||||
|
|
||||||
horizon:
|
horizon:
|
||||||
- gdkchan
|
- gdkchan
|
||||||
- Ack77
|
- Ack77
|
||||||
- marysaka
|
|
||||||
- TSRBerry
|
- TSRBerry
|
||||||
|
|
||||||
infra:
|
infra:
|
||||||
- marysaka
|
|
||||||
- TSRBerry
|
- TSRBerry
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
85
.github/workflows/build.yml
vendored
85
.github/workflows/build.yml
vendored
@ -10,28 +10,17 @@ env:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: ${{ matrix.OS_NAME }} (${{ matrix.configuration }})
|
name: ${{ matrix.platform.name }} (${{ matrix.configuration }})
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.platform.os }}
|
||||||
timeout-minutes: 45
|
timeout-minutes: 45
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-latest, macOS-latest, windows-latest]
|
|
||||||
configuration: [Debug, Release]
|
configuration: [Debug, Release]
|
||||||
include:
|
platform:
|
||||||
- os: ubuntu-latest
|
- { name: win-x64, os: windows-latest, zip_os_name: win_x64 }
|
||||||
OS_NAME: Linux x64
|
- { name: linux-x64, os: ubuntu-latest, zip_os_name: linux_x64 }
|
||||||
DOTNET_RUNTIME_IDENTIFIER: linux-x64
|
- { name: linux-arm64, os: ubuntu-latest, zip_os_name: linux_arm64 }
|
||||||
RELEASE_ZIP_OS_NAME: linux_x64
|
- { name: osx-x64, os: macos-13, zip_os_name: osx_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
|
|
||||||
|
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
steps:
|
steps:
|
||||||
@ -49,6 +38,16 @@ jobs:
|
|||||||
run: echo "result=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
|
run: echo "result=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
|
||||||
shell: bash
|
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-13'
|
||||||
|
|
||||||
|
- 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-13'
|
||||||
|
|
||||||
- name: Build
|
- 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
|
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 }}"
|
commands: dotnet test --no-build -c "${{ matrix.configuration }}"
|
||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
retry-codes: 139
|
retry-codes: 139
|
||||||
|
if: matrix.platform.name != 'linux-arm64'
|
||||||
|
|
||||||
- name: Publish Ryujinx
|
- 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
|
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.os != 'macOS-latest'
|
if: github.event_name == 'pull_request' && matrix.platform.os != 'macos-13'
|
||||||
|
|
||||||
- name: Publish Ryujinx.Headless.SDL2
|
- 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
|
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.os != 'macOS-latest'
|
if: github.event_name == 'pull_request' && matrix.platform.os != 'macos-13'
|
||||||
|
|
||||||
- name: Publish Ryujinx.Ava
|
- name: Publish Ryujinx.Gtk3
|
||||||
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
|
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.os != 'macOS-latest'
|
if: github.event_name == 'pull_request' && matrix.platform.os != 'macos-13'
|
||||||
|
|
||||||
- name: Set executable bit
|
- name: Set executable bit
|
||||||
run: |
|
run: |
|
||||||
chmod +x ./publish/Ryujinx ./publish/Ryujinx.sh
|
chmod +x ./publish/Ryujinx ./publish/Ryujinx.sh
|
||||||
chmod +x ./publish_sdl2_headless/Ryujinx.Headless.SDL2 ./publish_sdl2_headless/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
|
chmod +x ./publish_gtk/Ryujinx.Gtk3 ./publish_gtk/Ryujinx.sh
|
||||||
if: github.event_name == 'pull_request' && matrix.os == 'ubuntu-latest'
|
if: github.event_name == 'pull_request' && matrix.platform.os == 'ubuntu-latest'
|
||||||
|
|
||||||
- name: Upload Ryujinx artifact
|
- name: Upload Ryujinx artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
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
|
path: publish
|
||||||
if: github.event_name == 'pull_request' && matrix.os != 'macOS-latest'
|
if: github.event_name == 'pull_request' && matrix.platform.os != 'macos-13'
|
||||||
|
|
||||||
- name: Upload Ryujinx.Headless.SDL2 artifact
|
- name: Upload Ryujinx.Headless.SDL2 artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
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
|
path: publish_sdl2_headless
|
||||||
if: github.event_name == 'pull_request' && matrix.os != 'macOS-latest'
|
if: github.event_name == 'pull_request' && matrix.platform.os != 'macos-13'
|
||||||
|
|
||||||
- name: Upload Ryujinx.Ava artifact
|
- name: Upload Ryujinx.Gtk3 artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: ava-ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.RELEASE_ZIP_OS_NAME }}
|
name: gtk-ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.platform.zip_os_name }}
|
||||||
path: publish_ava
|
path: publish_gtk
|
||||||
if: github.event_name == 'pull_request' && matrix.os != 'macOS-latest'
|
if: github.event_name == 'pull_request' && matrix.platform.os != 'macos-13'
|
||||||
|
|
||||||
build_macos:
|
build_macos:
|
||||||
name: macOS Universal (${{ matrix.configuration }})
|
name: macOS Universal (${{ matrix.configuration }})
|
||||||
@ -135,19 +135,24 @@ jobs:
|
|||||||
id: git_short_hash
|
id: git_short_hash
|
||||||
run: echo "result=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
|
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: |
|
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
|
- name: Publish macOS Ryujinx.Headless.SDL2
|
||||||
run: |
|
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"
|
./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
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: ava-ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-macos_universal
|
name: ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-macos_universal
|
||||||
path: "publish_ava/*.tar.gz"
|
path: "publish/*.tar.gz"
|
||||||
if: github.event_name == 'pull_request'
|
if: github.event_name == 'pull_request'
|
||||||
|
|
||||||
- name: Upload Ryujinx.Headless.SDL2 artifact
|
- name: Upload Ryujinx.Headless.SDL2 artifact
|
||||||
|
2
.github/workflows/checks.yml
vendored
2
.github/workflows/checks.yml
vendored
@ -8,7 +8,7 @@ on:
|
|||||||
- '!.github/**'
|
- '!.github/**'
|
||||||
- '!*.yml'
|
- '!*.yml'
|
||||||
- '!*.config'
|
- '!*.config'
|
||||||
- '!README.md'
|
- '!*.md'
|
||||||
- '.github/workflows/*.yml'
|
- '.github/workflows/*.yml'
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
|
72
.github/workflows/flatpak.yml
vendored
72
.github/workflows/flatpak.yml
vendored
@ -51,38 +51,76 @@ jobs:
|
|||||||
- name: Restore Nuget packages
|
- 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.
|
# 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
|
# 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
|
- name: Generate nuget_sources.json
|
||||||
shell: python
|
shell: python
|
||||||
run: |
|
run: |
|
||||||
|
import hashlib
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import base64
|
import base64
|
||||||
import binascii
|
import binascii
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
import urllib.request
|
||||||
|
|
||||||
sources = []
|
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:
|
def create_source_from_external(name, version):
|
||||||
sha512 = binascii.hexlify(base64.b64decode(fp.read())).decode('ascii')
|
full_dir_path = Path(os.environ["NUGET_PACKAGES"]).joinpath(name).joinpath(version)
|
||||||
|
os.makedirs(full_dir_path, exist_ok=True)
|
||||||
|
|
||||||
sources.append({
|
filename = "{}.{}.nupkg".format(name, version)
|
||||||
'type': 'file',
|
url = "https://api.nuget.org/v3-flatcontainer/{}/{}/{}".format(
|
||||||
'url': url,
|
name, version, filename
|
||||||
'sha512': sha512,
|
)
|
||||||
'dest': os.environ['NUGET_SOURCES_DESTDIR'],
|
|
||||||
'dest-filename': filename,
|
|
||||||
})
|
|
||||||
|
|
||||||
with open('flathub/nuget_sources.json', 'w') as fp:
|
print(f"Processing {url}...")
|
||||||
json.dump(sources, fp, indent=4)
|
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
|
- name: Update flatpak metadata
|
||||||
id: 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`);
|
return core.error(`No artifacts found`);
|
||||||
}
|
}
|
||||||
let body = `Download the artifacts for this pull request:\n`;
|
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_headless_artifacts = `\n\n <details><summary>GUI-less (SDL2)</summary>\n`;
|
||||||
let hidden_debug_artifacts = `\n\n <details><summary>Only for Developers</summary>\n`;
|
let hidden_debug_artifacts = `\n\n <details><summary>Only for Developers</summary>\n`;
|
||||||
for (const art of artifacts) {
|
for (const art of artifacts) {
|
||||||
if(art.name.includes('Debug')) {
|
if(art.name.includes('Debug')) {
|
||||||
hidden_debug_artifacts += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
|
hidden_debug_artifacts += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
|
||||||
} else if(art.name.includes('ava-ryujinx')) {
|
} else if(art.name.includes('gtk-ryujinx')) {
|
||||||
hidden_avalonia_artifacts += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
|
hidden_gtk_artifacts += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
|
||||||
} else if(art.name.includes('sdl2-ryujinx-headless')) {
|
} else if(art.name.includes('sdl2-ryujinx-headless')) {
|
||||||
hidden_headless_artifacts += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
|
hidden_headless_artifacts += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
|
||||||
} else {
|
} else {
|
||||||
body += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
|
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_headless_artifacts += `\n</details>`;
|
||||||
hidden_debug_artifacts += `\n</details>`;
|
hidden_debug_artifacts += `\n</details>`;
|
||||||
body += hidden_avalonia_artifacts;
|
body += hidden_gtk_artifacts;
|
||||||
body += hidden_headless_artifacts;
|
body += hidden_headless_artifacts;
|
||||||
body += hidden_debug_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
|
repository: Ryujinx/Ryujinx
|
||||||
ref: master
|
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
|
- name: Update labels based on changes
|
||||||
uses: actions/labeler@v5
|
uses: actions/labeler@v5
|
||||||
with:
|
with:
|
||||||
sync-labels: true
|
sync-labels: true
|
||||||
dot: 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 }}
|
|
||||||
|
72
.github/workflows/release.yml
vendored
72
.github/workflows/release.yml
vendored
@ -10,7 +10,7 @@ on:
|
|||||||
- '*.yml'
|
- '*.yml'
|
||||||
- '*.json'
|
- '*.json'
|
||||||
- '*.config'
|
- '*.config'
|
||||||
- 'README.md'
|
- '*.md'
|
||||||
|
|
||||||
concurrency: release
|
concurrency: release
|
||||||
|
|
||||||
@ -44,23 +44,27 @@ jobs:
|
|||||||
sha: context.sha
|
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:
|
release:
|
||||||
name: Release ${{ matrix.OS_NAME }}
|
name: Release for ${{ matrix.platform.name }}
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.platform.os }}
|
||||||
timeout-minutes: ${{ fromJSON(vars.JOB_TIMEOUT) }}
|
timeout-minutes: ${{ fromJSON(vars.JOB_TIMEOUT) }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [ ubuntu-latest, windows-latest ]
|
platform:
|
||||||
include:
|
- { name: win-x64, os: windows-latest, zip_os_name: win_x64 }
|
||||||
- os: ubuntu-latest
|
- { name: linux-x64, os: ubuntu-latest, zip_os_name: linux_x64 }
|
||||||
OS_NAME: Linux x64
|
- { name: linux-arm64, os: ubuntu-latest, zip_os_name: linux_arm64 }
|
||||||
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
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- 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_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_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_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
|
shell: bash
|
||||||
|
|
||||||
- name: Create output dir
|
- name: Create output dir
|
||||||
@ -92,42 +97,36 @@ jobs:
|
|||||||
|
|
||||||
- name: Publish
|
- name: Publish
|
||||||
run: |
|
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.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.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.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
|
||||||
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
|
|
||||||
|
|
||||||
- name: Packing Windows builds
|
- name: Packing Windows builds
|
||||||
if: matrix.os == 'windows-latest'
|
if: matrix.platform.os == 'windows-latest'
|
||||||
run: |
|
run: |
|
||||||
pushd publish_gtk
|
pushd publish_ava
|
||||||
7z a ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-win_x64.zip publish
|
cp publish/Ryujinx.exe publish/Ryujinx.Ava.exe
|
||||||
|
7z a ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip publish
|
||||||
|
7z a ../release_output/test-ava-ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip publish
|
||||||
popd
|
popd
|
||||||
|
|
||||||
pushd publish_sdl2_headless
|
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
|
|
||||||
popd
|
popd
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
- name: Packing Linux builds
|
- name: Packing Linux builds
|
||||||
if: matrix.os == 'ubuntu-latest'
|
if: matrix.platform.os == 'ubuntu-latest'
|
||||||
run: |
|
run: |
|
||||||
pushd publish_gtk
|
pushd publish_ava
|
||||||
chmod +x publish/Ryujinx.sh publish/Ryujinx
|
cp publish/Ryujinx publish/Ryujinx.Ava
|
||||||
tar -czvf ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-linux_x64.tar.gz publish
|
chmod +x publish/Ryujinx.sh publish/Ryujinx.Ava publish/Ryujinx
|
||||||
|
tar -czvf ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.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
|
popd
|
||||||
|
|
||||||
pushd publish_sdl2_headless
|
pushd publish_sdl2_headless
|
||||||
chmod +x publish/Ryujinx.sh publish/Ryujinx.Headless.SDL2
|
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
|
|
||||||
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
|
|
||||||
popd
|
popd
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
@ -186,9 +185,10 @@ 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_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_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_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
|
shell: bash
|
||||||
|
|
||||||
- name: Publish macOS Ryujinx.Ava
|
- name: Publish macOS Ryujinx
|
||||||
run: |
|
run: |
|
||||||
./distribution/macos/create_macos_build_ava.sh . publish_tmp_ava publish_ava ./distribution/macos/entitlements.xml "${{ steps.version_info.outputs.build_version }}" "${{ steps.version_info.outputs.git_short_hash }}" Release
|
./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
|
||||||
|
|
||||||
|
@ -3,50 +3,48 @@
|
|||||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageVersion Include="Avalonia" Version="11.0.5" />
|
<PackageVersion Include="Avalonia" Version="11.0.10" />
|
||||||
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.0.5" />
|
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.0.10" />
|
||||||
<PackageVersion Include="Avalonia.Desktop" Version="11.0.5" />
|
<PackageVersion Include="Avalonia.Desktop" Version="11.0.10" />
|
||||||
<PackageVersion Include="Avalonia.Diagnostics" Version="11.0.5" />
|
<PackageVersion Include="Avalonia.Diagnostics" Version="11.0.10" />
|
||||||
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="11.0.5" />
|
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="11.0.10" />
|
||||||
<PackageVersion Include="Avalonia.Svg" Version="11.0.0.3" />
|
<PackageVersion Include="Avalonia.Svg" Version="11.0.0.18" />
|
||||||
<PackageVersion Include="Avalonia.Svg.Skia" Version="11.0.0.3" />
|
<PackageVersion Include="Avalonia.Svg.Skia" Version="11.0.0.18" />
|
||||||
<PackageVersion Include="CommandLineParser" Version="2.9.1" />
|
<PackageVersion Include="CommandLineParser" Version="2.9.1" />
|
||||||
<PackageVersion Include="Concentus" Version="1.1.7" />
|
<PackageVersion Include="Concentus" Version="2.2.0" />
|
||||||
<PackageVersion Include="DiscordRichPresence" Version="1.2.1.24" />
|
<PackageVersion Include="DiscordRichPresence" Version="1.2.1.24" />
|
||||||
<PackageVersion Include="DynamicData" Version="7.14.2" />
|
<PackageVersion Include="DynamicData" Version="9.0.4" />
|
||||||
<PackageVersion Include="FluentAvaloniaUI" Version="2.0.4" />
|
<PackageVersion Include="FluentAvaloniaUI" Version="2.0.5" />
|
||||||
<PackageVersion Include="GtkSharp.Dependencies" Version="1.1.1" />
|
<PackageVersion Include="GtkSharp.Dependencies" Version="1.1.1" />
|
||||||
<PackageVersion Include="GtkSharp.Dependencies.osx" Version="0.0.5" />
|
<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="LibHac" Version="0.19.0" />
|
||||||
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
|
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
|
||||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.7.0" />
|
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.2" />
|
||||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
|
<PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.0.1" />
|
||||||
<PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="2.3.2" />
|
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
|
||||||
|
<PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.1" />
|
||||||
<PackageVersion Include="MsgPack.Cli" Version="1.0.1" />
|
<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="NUnit" Version="3.13.3" />
|
||||||
<PackageVersion Include="NUnit3TestAdapter" Version="4.1.0" />
|
<PackageVersion Include="NUnit3TestAdapter" Version="4.1.0" />
|
||||||
<PackageVersion Include="OpenTK.Core" Version="4.8.1" />
|
<PackageVersion Include="OpenTK.Core" Version="4.8.2" />
|
||||||
<PackageVersion Include="OpenTK.Graphics" Version="4.8.1" />
|
<PackageVersion Include="OpenTK.Graphics" Version="4.8.2" />
|
||||||
<PackageVersion Include="OpenTK.Audio.OpenAL" Version="4.8.1" />
|
<PackageVersion Include="OpenTK.Audio.OpenAL" Version="4.8.2" />
|
||||||
<PackageVersion Include="OpenTK.Windowing.GraphicsLibraryFramework" Version="4.8.1" />
|
<PackageVersion Include="OpenTK.Windowing.GraphicsLibraryFramework" Version="4.8.2" />
|
||||||
<PackageVersion Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" />
|
<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.3" />
|
<PackageVersion Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" />
|
||||||
<PackageVersion Include="Ryujinx.GtkSharp" Version="3.24.24.59-ryujinx" />
|
<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="securifybv.ShellLink" Version="0.1.0" />
|
||||||
<PackageVersion Include="shaderc.net" Version="0.1.0" />
|
<PackageVersion Include="shaderc.net" Version="0.1.0" />
|
||||||
<PackageVersion Include="SharpZipLib" Version="1.4.2" />
|
<PackageVersion Include="SharpZipLib" Version="1.4.2" />
|
||||||
<PackageVersion Include="Silk.NET.Vulkan" Version="2.22.0" />
|
<PackageVersion Include="Silk.NET.Vulkan" Version="2.21.0" />
|
||||||
<PackageVersion Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.22.0" />
|
<PackageVersion Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.21.0" />
|
||||||
<PackageVersion Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.22.0" />
|
<PackageVersion Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.21.0" />
|
||||||
<PackageVersion Include="SixLabors.ImageSharp" Version="1.0.4" />
|
<PackageVersion Include="SkiaSharp" Version="2.88.7" />
|
||||||
<PackageVersion Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta11" />
|
<PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="2.88.7" />
|
||||||
<PackageVersion Include="SPB" Version="0.0.4-build28" />
|
<PackageVersion Include="SPB" Version="0.0.4-build32" />
|
||||||
<PackageVersion Include="System.Drawing.Common" Version="8.0.0" />
|
|
||||||
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="7.0.3" />
|
|
||||||
<PackageVersion Include="System.IO.Hashing" Version="8.0.0" />
|
<PackageVersion Include="System.IO.Hashing" Version="8.0.0" />
|
||||||
<PackageVersion Include="System.Management" Version="8.0.0" />
|
<PackageVersion Include="System.Management" Version="8.0.0" />
|
||||||
<PackageVersion Include="UnicornEngine.Unicorn" Version="2.0.2-rc1-fb78016" />
|
<PackageVersion Include="UnicornEngine.Unicorn" Version="2.0.2-rc1-fb78016" />
|
||||||
|
@ -6,6 +6,9 @@
|
|||||||
|
|
||||||
<h1 align="center">MeloNX</h1>
|
<h1 align="center">MeloNX</h1>
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
If you are planning to contribute or just want to learn more about this project please read through our [documentation](docs/README.md).
|
||||||
|
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
|
14
Ryujinx.sln
14
Ryujinx.sln
@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00
|
|||||||
# Visual Studio Version 17
|
# Visual Studio Version 17
|
||||||
VisualStudioVersion = 17.1.32228.430
|
VisualStudioVersion = 17.1.32228.430
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
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
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Tests", "src\Ryujinx.Tests\Ryujinx.Tests.csproj", "{EBB55AEA-C7D7-4DEB-BF96-FA1789E225E9}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Tests", "src\Ryujinx.Tests\Ryujinx.Tests.csproj", "{EBB55AEA-C7D7-4DEB-BF96-FA1789E225E9}"
|
||||||
EndProject
|
EndProject
|
||||||
@ -69,9 +69,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Headless.SDL2", "sr
|
|||||||
EndProject
|
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}"
|
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
|
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
|
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
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Horizon.Generators", "src\Ryujinx.Horizon.Generators\Ryujinx.Horizon.Generators.csproj", "{6AE2A5E8-4C5A-48B9-997B-E1455C0355C6}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Horizon.Generators", "src\Ryujinx.Horizon.Generators\Ryujinx.Horizon.Generators.csproj", "{6AE2A5E8-4C5A-48B9-997B-E1455C0355C6}"
|
||||||
EndProject
|
EndProject
|
||||||
@ -79,7 +79,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Vulkan", "
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spv.Generator", "src\Spv.Generator\Spv.Generator.csproj", "{2BCB3D7A-38C0-4FE7-8FDA-374C6AD56D0E}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spv.Generator", "src\Spv.Generator\Spv.Generator.csproj", "{2BCB3D7A-38C0-4FE7-8FDA-374C6AD56D0E}"
|
||||||
EndProject
|
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
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Horizon.Common", "src\Ryujinx.Horizon.Common\Ryujinx.Horizon.Common.csproj", "{77F96ECE-4952-42DB-A528-DED25572A573}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Horizon.Common", "src\Ryujinx.Horizon.Common\Ryujinx.Horizon.Common.csproj", "{77F96ECE-4952-42DB-A528-DED25572A573}"
|
||||||
EndProject
|
EndProject
|
||||||
@ -87,6 +87,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Horizon", "src\Ryuj
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Horizon.Kernel.Generators", "src\Ryujinx.Horizon.Kernel.Generators\Ryujinx.Horizon.Kernel.Generators.csproj", "{7F55A45D-4E1D-4A36-ADD3-87F29A285AA2}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Horizon.Kernel.Generators", "src\Ryujinx.Horizon.Kernel.Generators\Ryujinx.Horizon.Kernel.Generators.csproj", "{7F55A45D-4E1D-4A36-ADD3-87F29A285AA2}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.HLE.Generators", "src\Ryujinx.HLE.Generators\Ryujinx.HLE.Generators.csproj", "{B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@ -249,6 +251,10 @@ Global
|
|||||||
{7F55A45D-4E1D-4A36-ADD3-87F29A285AA2}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{7F55A45D-4E1D-4A36-ADD3-87F29A285AA2}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{7F55A45D-4E1D-4A36-ADD3-87F29A285AA2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{7F55A45D-4E1D-4A36-ADD3-87F29A285AA2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{7F55A45D-4E1D-4A36-ADD3-87F29A285AA2}.Release|Any CPU.Build.0 = Release|Any CPU
|
{7F55A45D-4E1D-4A36-ADD3-87F29A285AA2}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
@ -4,6 +4,8 @@
|
|||||||
<s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForOtherTypes/@EntryValue">UseExplicitType</s:String>
|
<s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForOtherTypes/@EntryValue">UseExplicitType</s:String>
|
||||||
<s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForSimpleTypes/@EntryValue">UseExplicitType</s:String>
|
<s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForSimpleTypes/@EntryValue">UseExplicitType</s:String>
|
||||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=TypesAndNamespaces/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"><ExtraRule Prefix="I" Suffix="" Style="AaBb" /></Policy></s:String>
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=TypesAndNamespaces/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"><ExtraRule Prefix="I" Suffix="" Style="AaBb" /></Policy></s:String>
|
||||||
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=a0b4bc4d_002Dd13b_002D4a37_002Db37e_002Dc9c6864e4302/@EntryIndexedValue"><Policy><Descriptor Staticness="Any" AccessRightKinds="Any" Description="Types and namespaces"><ElementKinds><Kind Name="NAMESPACE" /><Kind Name="CLASS" /><Kind Name="STRUCT" /><Kind Name="ENUM" /><Kind Name="DELEGATE" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"><ExtraRule Prefix="I" Suffix="" Style="AaBb" /></Policy></Policy></s:String>
|
||||||
|
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EPredefinedNamingRulesToUserRulesUpgrade/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=ASET/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=ASET/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Astc/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=Astc/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Luma/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=Luma/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
@ -4,7 +4,7 @@ Name=Ryujinx
|
|||||||
Type=Application
|
Type=Application
|
||||||
Icon=Ryujinx
|
Icon=Ryujinx
|
||||||
Exec=Ryujinx.sh %f
|
Exec=Ryujinx.sh %f
|
||||||
Comment=Plays Nintendo Switch applications
|
Comment=A Nintendo Switch Emulator
|
||||||
GenericName=Nintendo Switch Emulator
|
GenericName=Nintendo Switch Emulator
|
||||||
Terminal=false
|
Terminal=false
|
||||||
Categories=Game;Emulator;
|
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
|
#!/bin/sh
|
||||||
|
|
||||||
SCRIPT_DIR=$(dirname "$(realpath "$0")")
|
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
|
if [ -f "$SCRIPT_DIR/Ryujinx.Headless.SDL2" ]; then
|
||||||
RYUJINX_BIN="Ryujinx.Headless.SDL2"
|
RYUJINX_BIN="Ryujinx.Headless.SDL2"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [ -f "$SCRIPT_DIR/Ryujinx" ]; then
|
||||||
|
RYUJINX_BIN="Ryujinx"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$RYUJINX_BIN" ]; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
COMMAND="env DOTNET_EnableAlternateStackCheck=1"
|
COMMAND="env DOTNET_EnableAlternateStackCheck=1"
|
||||||
|
|
||||||
if command -v gamemoderun > /dev/null 2>&1; then
|
if command -v gamemoderun > /dev/null 2>&1; then
|
||||||
COMMAND="$COMMAND gamemoderun"
|
COMMAND="$COMMAND gamemoderun"
|
||||||
fi
|
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/MacOS"
|
||||||
mkdir "$APP_BUNDLE_DIRECTORY/Contents/Resources"
|
mkdir "$APP_BUNDLE_DIRECTORY/Contents/Resources"
|
||||||
|
|
||||||
# Copy executables first
|
# Copy executable and nsure executable can be executed
|
||||||
cp "$PUBLISH_DIRECTORY/Ryujinx.Ava" "$APP_BUNDLE_DIRECTORY/Contents/MacOS/Ryujinx"
|
cp "$PUBLISH_DIRECTORY/Ryujinx" "$APP_BUNDLE_DIRECTORY/Contents/MacOS/Ryujinx"
|
||||||
chmod u+x "$APP_BUNDLE_DIRECTORY/Contents/MacOS/Ryujinx"
|
chmod u+x "$APP_BUNDLE_DIRECTORY/Contents/MacOS/Ryujinx"
|
||||||
|
|
||||||
# Then all libraries
|
# Then all libraries
|
||||||
|
@ -22,9 +22,9 @@ EXTRA_ARGS=$8
|
|||||||
|
|
||||||
if [ "$VERSION" == "1.1.0" ];
|
if [ "$VERSION" == "1.1.0" ];
|
||||||
then
|
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
|
else
|
||||||
RELEASE_TAR_FILE_NAME=test-ava-ryujinx-$VERSION-macos_universal.app.tar
|
RELEASE_TAR_FILE_NAME=ryujinx-$VERSION-macos_universal.app.tar
|
||||||
fi
|
fi
|
||||||
|
|
||||||
ARM64_APP_BUNDLE="$TEMP_DIRECTORY/output_arm64/Ryujinx.app"
|
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_COMMON_ARGS=(-p:DebugType=embedded -p:Version="$VERSION" -p:SourceRevisionId="$SOURCE_REVISION_ID" --self-contained true $EXTRA_ARGS)
|
||||||
|
|
||||||
dotnet restore
|
dotnet restore
|
||||||
dotnet build -c "$CONFIGURATION" 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.Ava
|
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.Ava
|
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)
|
# Get rid of the support library for ARMeilleure for x64 (that's only for arm64)
|
||||||
rm -rf "$TEMP_DIRECTORY/publish_x64/libarmeilleure-jitsupport.dylib"
|
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"
|
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"
|
gzip -9 < "$RELEASE_TAR_FILE_NAME" > "$RELEASE_TAR_FILE_NAME.gz"
|
||||||
rm "$RELEASE_TAR_FILE_NAME"
|
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
|
popd
|
||||||
|
|
||||||
echo "Done"
|
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}
|
@ -33,8 +33,3 @@ Project Docs
|
|||||||
=================
|
=================
|
||||||
|
|
||||||
To be added. Many project files will contain basic XML docs for key functions and classes in the meantime.
|
To be added. Many project files will contain basic XML docs for key functions and classes in the meantime.
|
||||||
|
|
||||||
Other Information
|
|
||||||
=================
|
|
||||||
|
|
||||||
- N/A
|
|
||||||
|
@ -237,7 +237,7 @@ namespace ARMeilleure.CodeGen.Arm64
|
|||||||
long originalPosition = _stream.Position;
|
long originalPosition = _stream.Position;
|
||||||
|
|
||||||
_stream.Seek(0, SeekOrigin.Begin);
|
_stream.Seek(0, SeekOrigin.Begin);
|
||||||
_stream.Read(code, 0, code.Length);
|
_stream.ReadExactly(code, 0, code.Length);
|
||||||
_stream.Seek(originalPosition, SeekOrigin.Begin);
|
_stream.Seek(originalPosition, SeekOrigin.Begin);
|
||||||
|
|
||||||
RelocInfo relocInfo;
|
RelocInfo relocInfo;
|
||||||
|
@ -251,7 +251,20 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int selectedReg = GetHighestValueIndex(freePositions);
|
// If this is a copy destination variable, we prefer the register used for the copy source.
|
||||||
|
// If the register is available, then the copy can be eliminated later as both source
|
||||||
|
// and destination will use the same register.
|
||||||
|
int selectedReg;
|
||||||
|
|
||||||
|
if (current.TryGetCopySourceRegister(out int preferredReg) && freePositions[preferredReg] >= current.GetEnd())
|
||||||
|
{
|
||||||
|
selectedReg = preferredReg;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
selectedReg = GetHighestValueIndex(freePositions);
|
||||||
|
}
|
||||||
|
|
||||||
int selectedNextUse = freePositions[selectedReg];
|
int selectedNextUse = freePositions[selectedReg];
|
||||||
|
|
||||||
// Intervals starts and ends at odd positions, unless they span an entire
|
// Intervals starts and ends at odd positions, unless they span an entire
|
||||||
@ -431,7 +444,7 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int GetHighestValueIndex(Span<int> span)
|
private static int GetHighestValueIndex(ReadOnlySpan<int> span)
|
||||||
{
|
{
|
||||||
int highest = int.MinValue;
|
int highest = int.MinValue;
|
||||||
|
|
||||||
@ -798,12 +811,12 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
|
|||||||
// The "visited" state is stored in the MSB of the local's value.
|
// The "visited" state is stored in the MSB of the local's value.
|
||||||
const ulong VisitedMask = 1ul << 63;
|
const ulong VisitedMask = 1ul << 63;
|
||||||
|
|
||||||
bool IsVisited(Operand local)
|
static bool IsVisited(Operand local)
|
||||||
{
|
{
|
||||||
return (local.GetValueUnsafe() & VisitedMask) != 0;
|
return (local.GetValueUnsafe() & VisitedMask) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetVisited(Operand local)
|
static void SetVisited(Operand local)
|
||||||
{
|
{
|
||||||
local.GetValueUnsafe() |= VisitedMask;
|
local.GetValueUnsafe() |= VisitedMask;
|
||||||
}
|
}
|
||||||
@ -826,9 +839,25 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
|
|||||||
{
|
{
|
||||||
dest.NumberLocal(_intervals.Count);
|
dest.NumberLocal(_intervals.Count);
|
||||||
|
|
||||||
_intervals.Add(new LiveInterval(dest));
|
LiveInterval interval = new LiveInterval(dest);
|
||||||
|
_intervals.Add(interval);
|
||||||
|
|
||||||
SetVisited(dest);
|
SetVisited(dest);
|
||||||
|
|
||||||
|
// If this is a copy (or copy-like operation), set the copy source interval as well.
|
||||||
|
// This is used for register preferencing later on, which allows the copy to be eliminated
|
||||||
|
// in some cases.
|
||||||
|
if (node.Instruction == Instruction.Copy || node.Instruction == Instruction.ZeroExtend32)
|
||||||
|
{
|
||||||
|
Operand source = node.GetSource(0);
|
||||||
|
|
||||||
|
if (source.Kind == OperandKind.LocalVariable &&
|
||||||
|
source.GetLocalNumber() > 0 &&
|
||||||
|
(node.Instruction == Instruction.Copy || source.Type == OperandType.I32))
|
||||||
|
{
|
||||||
|
interval.SetCopySource(_intervals[source.GetLocalNumber()]);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
|
|||||||
public LiveRange CurrRange;
|
public LiveRange CurrRange;
|
||||||
|
|
||||||
public LiveInterval Parent;
|
public LiveInterval Parent;
|
||||||
|
public LiveInterval CopySource;
|
||||||
|
|
||||||
public UseList Uses;
|
public UseList Uses;
|
||||||
public LiveIntervalList Children;
|
public LiveIntervalList Children;
|
||||||
@ -37,6 +38,7 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
|
|||||||
private ref LiveRange CurrRange => ref _data->CurrRange;
|
private ref LiveRange CurrRange => ref _data->CurrRange;
|
||||||
private ref LiveRange PrevRange => ref _data->PrevRange;
|
private ref LiveRange PrevRange => ref _data->PrevRange;
|
||||||
private ref LiveInterval Parent => ref _data->Parent;
|
private ref LiveInterval Parent => ref _data->Parent;
|
||||||
|
private ref LiveInterval CopySource => ref _data->CopySource;
|
||||||
private ref UseList Uses => ref _data->Uses;
|
private ref UseList Uses => ref _data->Uses;
|
||||||
private ref LiveIntervalList Children => ref _data->Children;
|
private ref LiveIntervalList Children => ref _data->Children;
|
||||||
|
|
||||||
@ -78,6 +80,25 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
|
|||||||
Register = register;
|
Register = register;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetCopySource(LiveInterval copySource)
|
||||||
|
{
|
||||||
|
CopySource = copySource;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetCopySourceRegister(out int copySourceRegIndex)
|
||||||
|
{
|
||||||
|
if (CopySource._data != null)
|
||||||
|
{
|
||||||
|
copySourceRegIndex = CopySource.Register.Index;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
copySourceRegIndex = 0;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public void Reset()
|
public void Reset()
|
||||||
{
|
{
|
||||||
PrevRange = default;
|
PrevRange = default;
|
||||||
|
@ -1444,7 +1444,7 @@ namespace ARMeilleure.CodeGen.X86
|
|||||||
|
|
||||||
Span<byte> buffer = new byte[jump.JumpPosition - _stream.Position];
|
Span<byte> buffer = new byte[jump.JumpPosition - _stream.Position];
|
||||||
|
|
||||||
_stream.Read(buffer);
|
_stream.ReadExactly(buffer);
|
||||||
_stream.Seek(ReservedBytesForJump, SeekOrigin.Current);
|
_stream.Seek(ReservedBytesForJump, SeekOrigin.Current);
|
||||||
|
|
||||||
codeStream.Write(buffer);
|
codeStream.Write(buffer);
|
||||||
|
@ -517,7 +517,10 @@ namespace ARMeilleure.Decoders
|
|||||||
SetA64("0x00111100>>>xxx100111xxxxxxxxxx", InstName.Sqrshrn_V, InstEmit.Sqrshrn_V, OpCodeSimdShImm.Create);
|
SetA64("0x00111100>>>xxx100111xxxxxxxxxx", InstName.Sqrshrn_V, InstEmit.Sqrshrn_V, OpCodeSimdShImm.Create);
|
||||||
SetA64("0111111100>>>xxx100011xxxxxxxxxx", InstName.Sqrshrun_S, InstEmit.Sqrshrun_S, 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("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("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("0101111100>>>xxx100101xxxxxxxxxx", InstName.Sqshrn_S, InstEmit.Sqshrn_S, OpCodeSimdShImm.Create);
|
||||||
SetA64("0x00111100>>>xxx100101xxxxxxxxxx", InstName.Sqshrn_V, InstEmit.Sqshrn_V, OpCodeSimdShImm.Create);
|
SetA64("0x00111100>>>xxx100101xxxxxxxxxx", InstName.Sqshrn_V, InstEmit.Sqshrn_V, OpCodeSimdShImm.Create);
|
||||||
SetA64("0111111100>>>xxx100001xxxxxxxxxx", InstName.Sqshrun_S, InstEmit.Sqshrun_S, OpCodeSimdShImm.Create);
|
SetA64("0111111100>>>xxx100001xxxxxxxxxx", InstName.Sqshrun_S, InstEmit.Sqshrun_S, OpCodeSimdShImm.Create);
|
||||||
@ -743,6 +746,7 @@ namespace ARMeilleure.Decoders
|
|||||||
SetA32("<<<<01101000xxxxxxxxxxxxxx01xxxx", InstName.Pkh, InstEmit32.Pkh, OpCode32AluRsImm.Create);
|
SetA32("<<<<01101000xxxxxxxxxxxxxx01xxxx", InstName.Pkh, InstEmit32.Pkh, OpCode32AluRsImm.Create);
|
||||||
SetA32("11110101xx01xxxx1111xxxxxxxxxxxx", InstName.Pld, InstEmit32.Nop, OpCode32.Create);
|
SetA32("11110101xx01xxxx1111xxxxxxxxxxxx", InstName.Pld, InstEmit32.Nop, OpCode32.Create);
|
||||||
SetA32("11110111xx01xxxx1111xxxxxxx0xxxx", InstName.Pld, InstEmit32.Nop, OpCode32.Create);
|
SetA32("11110111xx01xxxx1111xxxxxxx0xxxx", InstName.Pld, InstEmit32.Nop, OpCode32.Create);
|
||||||
|
SetA32("<<<<01100010xxxxxxxx11110001xxxx", InstName.Qadd16, InstEmit32.Qadd16, OpCode32AluReg.Create);
|
||||||
SetA32("<<<<011011111111xxxx11110011xxxx", InstName.Rbit, InstEmit32.Rbit, OpCode32AluReg.Create);
|
SetA32("<<<<011011111111xxxx11110011xxxx", InstName.Rbit, InstEmit32.Rbit, OpCode32AluReg.Create);
|
||||||
SetA32("<<<<011010111111xxxx11110011xxxx", InstName.Rev, InstEmit32.Rev, OpCode32AluReg.Create);
|
SetA32("<<<<011010111111xxxx11110011xxxx", InstName.Rev, InstEmit32.Rev, OpCode32AluReg.Create);
|
||||||
SetA32("<<<<011010111111xxxx11111011xxxx", InstName.Rev16, InstEmit32.Rev16, OpCode32AluReg.Create);
|
SetA32("<<<<011010111111xxxx11111011xxxx", InstName.Rev16, InstEmit32.Rev16, OpCode32AluReg.Create);
|
||||||
@ -819,6 +823,10 @@ namespace ARMeilleure.Decoders
|
|||||||
SetA32("<<<<00000100xxxxxxxxxxxx1001xxxx", InstName.Umaal, InstEmit32.Umaal, OpCode32AluUmull.Create);
|
SetA32("<<<<00000100xxxxxxxxxxxx1001xxxx", InstName.Umaal, InstEmit32.Umaal, OpCode32AluUmull.Create);
|
||||||
SetA32("<<<<0000101xxxxxxxxxxxxx1001xxxx", InstName.Umlal, InstEmit32.Umlal, OpCode32AluUmull.Create);
|
SetA32("<<<<0000101xxxxxxxxxxxxx1001xxxx", InstName.Umlal, InstEmit32.Umlal, OpCode32AluUmull.Create);
|
||||||
SetA32("<<<<0000100xxxxxxxxxxxxx1001xxxx", InstName.Umull, InstEmit32.Umull, OpCode32AluUmull.Create);
|
SetA32("<<<<0000100xxxxxxxxxxxxx1001xxxx", InstName.Umull, InstEmit32.Umull, OpCode32AluUmull.Create);
|
||||||
|
SetA32("<<<<01100110xxxxxxxx11110001xxxx", InstName.Uqadd16, InstEmit32.Uqadd16, OpCode32AluReg.Create);
|
||||||
|
SetA32("<<<<01100110xxxxxxxx11111001xxxx", InstName.Uqadd8, InstEmit32.Uqadd8, OpCode32AluReg.Create);
|
||||||
|
SetA32("<<<<01100110xxxxxxxx11110111xxxx", InstName.Uqsub16, InstEmit32.Uqsub16, OpCode32AluReg.Create);
|
||||||
|
SetA32("<<<<01100110xxxxxxxx11111111xxxx", InstName.Uqsub8, InstEmit32.Uqsub8, OpCode32AluReg.Create);
|
||||||
SetA32("<<<<0110111xxxxxxxxxxxxxxx01xxxx", InstName.Usat, InstEmit32.Usat, OpCode32Sat.Create);
|
SetA32("<<<<0110111xxxxxxxxxxxxxxx01xxxx", InstName.Usat, InstEmit32.Usat, OpCode32Sat.Create);
|
||||||
SetA32("<<<<01101110xxxxxxxx11110011xxxx", InstName.Usat16, InstEmit32.Usat16, OpCode32Sat16.Create);
|
SetA32("<<<<01101110xxxxxxxx11110011xxxx", InstName.Usat16, InstEmit32.Usat16, OpCode32Sat16.Create);
|
||||||
SetA32("<<<<01100101xxxxxxxx11111111xxxx", InstName.Usub8, InstEmit32.Usub8, OpCode32AluReg.Create);
|
SetA32("<<<<01100101xxxxxxxx11111111xxxx", InstName.Usub8, InstEmit32.Usub8, OpCode32AluReg.Create);
|
||||||
@ -872,6 +880,7 @@ namespace ARMeilleure.Decoders
|
|||||||
SetVfp("<<<<11100x10xxxxxxxx101xx1x0xxxx", InstName.Vnmul, InstEmit32.Vnmul_S, OpCode32SimdRegS.Create, OpCode32SimdRegS.CreateT32);
|
SetVfp("<<<<11100x10xxxxxxxx101xx1x0xxxx", InstName.Vnmul, InstEmit32.Vnmul_S, OpCode32SimdRegS.Create, OpCode32SimdRegS.CreateT32);
|
||||||
SetVfp("111111101x1110xxxxxx101x01x0xxxx", InstName.Vrint, InstEmit32.Vrint_RM, OpCode32SimdS.Create, OpCode32SimdS.CreateT32);
|
SetVfp("111111101x1110xxxxxx101x01x0xxxx", InstName.Vrint, InstEmit32.Vrint_RM, OpCode32SimdS.Create, OpCode32SimdS.CreateT32);
|
||||||
SetVfp("<<<<11101x110110xxxx101x11x0xxxx", InstName.Vrint, InstEmit32.Vrint_Z, 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("<<<<11101x110111xxxx101x01x0xxxx", InstName.Vrintx, InstEmit32.Vrintx_S, OpCode32SimdS.Create, OpCode32SimdS.CreateT32);
|
||||||
SetVfp("<<<<11101x110001xxxx101x11x0xxxx", InstName.Vsqrt, InstEmit32.Vsqrt_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);
|
SetVfp("111111100xxxxxxxxxxx101xx0x0xxxx", InstName.Vsel, InstEmit32.Vsel, OpCode32SimdSel.Create, OpCode32SimdSel.CreateT32);
|
||||||
@ -992,6 +1001,7 @@ namespace ARMeilleure.Decoders
|
|||||||
SetAsimd("1111001x1x000xxxxxxx<<x10x01xxxx", InstName.Vorr, InstEmit32.Vorr_II, OpCode32SimdImm.Create, OpCode32SimdImm.CreateT32);
|
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("111100100x<<xxxxxxxx1011x0x1xxxx", InstName.Vpadd, InstEmit32.Vpadd_I, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);
|
||||||
SetAsimd("111100110x00xxxxxxxx1101x0x0xxxx", InstName.Vpadd, InstEmit32.Vpadd_V, 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("111100111x11<<00xxxx0010xxx0xxxx", InstName.Vpaddl, InstEmit32.Vpaddl, OpCode32SimdCmpZ.Create, OpCode32SimdCmpZ.CreateT32);
|
||||||
SetAsimd("1111001x0x<<xxxxxxxx1010x0x0xxxx", InstName.Vpmax, InstEmit32.Vpmax_I, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);
|
SetAsimd("1111001x0x<<xxxxxxxx1010x0x0xxxx", InstName.Vpmax, InstEmit32.Vpmax_I, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);
|
||||||
SetAsimd("111100110x00xxxxxxxx1111x0x0xxxx", InstName.Vpmax, InstEmit32.Vpmax_V, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);
|
SetAsimd("111100110x00xxxxxxxx1111x0x0xxxx", InstName.Vpmax, InstEmit32.Vpmax_V, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);
|
||||||
@ -1002,6 +1012,8 @@ namespace ARMeilleure.Decoders
|
|||||||
SetAsimd("111100100x10xxxxxxxx1011xxx0xxxx", InstName.Vqdmulh, InstEmit32.Vqdmulh, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);
|
SetAsimd("111100100x10xxxxxxxx1011xxx0xxxx", InstName.Vqdmulh, InstEmit32.Vqdmulh, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);
|
||||||
SetAsimd("111100111x11<<10xxxx00101xx0xxx0", InstName.Vqmovn, InstEmit32.Vqmovn, OpCode32SimdMovn.Create, OpCode32SimdMovn.CreateT32);
|
SetAsimd("111100111x11<<10xxxx00101xx0xxx0", InstName.Vqmovn, InstEmit32.Vqmovn, OpCode32SimdMovn.Create, OpCode32SimdMovn.CreateT32);
|
||||||
SetAsimd("111100111x11<<10xxxx001001x0xxx0", InstName.Vqmovun, InstEmit32.Vqmovun, OpCode32SimdMovn.Create, OpCode32SimdMovn.CreateT32);
|
SetAsimd("111100111x11<<10xxxx001001x0xxx0", InstName.Vqmovun, InstEmit32.Vqmovun, OpCode32SimdMovn.Create, OpCode32SimdMovn.CreateT32);
|
||||||
|
SetAsimd("111100110x01xxxxxxxx1011xxx0xxxx", InstName.Vqrdmulh, InstEmit32.Vqrdmulh, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);
|
||||||
|
SetAsimd("111100110x10xxxxxxxx1011xxx0xxxx", InstName.Vqrdmulh, InstEmit32.Vqrdmulh, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);
|
||||||
SetAsimd("1111001x1x>>>xxxxxxx100101x1xxx0", InstName.Vqrshrn, InstEmit32.Vqrshrn, OpCode32SimdShImmNarrow.Create, OpCode32SimdShImmNarrow.CreateT32);
|
SetAsimd("1111001x1x>>>xxxxxxx100101x1xxx0", InstName.Vqrshrn, InstEmit32.Vqrshrn, OpCode32SimdShImmNarrow.Create, OpCode32SimdShImmNarrow.CreateT32);
|
||||||
SetAsimd("111100111x>>>xxxxxxx100001x1xxx0", InstName.Vqrshrun, InstEmit32.Vqrshrun, OpCode32SimdShImmNarrow.Create, OpCode32SimdShImmNarrow.CreateT32);
|
SetAsimd("111100111x>>>xxxxxxx100001x1xxx0", InstName.Vqrshrun, InstEmit32.Vqrshrun, OpCode32SimdShImmNarrow.Create, OpCode32SimdShImmNarrow.CreateT32);
|
||||||
SetAsimd("1111001x1x>>>xxxxxxx100100x1xxx0", InstName.Vqshrn, InstEmit32.Vqshrn, OpCode32SimdShImmNarrow.Create, OpCode32SimdShImmNarrow.CreateT32);
|
SetAsimd("1111001x1x>>>xxxxxxx100100x1xxx0", InstName.Vqshrn, InstEmit32.Vqshrn, OpCode32SimdShImmNarrow.Create, OpCode32SimdShImmNarrow.CreateT32);
|
||||||
@ -1023,8 +1035,10 @@ namespace ARMeilleure.Decoders
|
|||||||
SetAsimd("111100101x>>>xxxxxxx0101>xx1xxxx", InstName.Vshl, InstEmit32.Vshl, OpCode32SimdShImm.Create, OpCode32SimdShImm.CreateT32);
|
SetAsimd("111100101x>>>xxxxxxx0101>xx1xxxx", InstName.Vshl, InstEmit32.Vshl, OpCode32SimdShImm.Create, OpCode32SimdShImm.CreateT32);
|
||||||
SetAsimd("1111001x0xxxxxxxxxxx0100xxx0xxxx", InstName.Vshl, InstEmit32.Vshl_I, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);
|
SetAsimd("1111001x0xxxxxxxxxxx0100xxx0xxxx", InstName.Vshl, InstEmit32.Vshl_I, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);
|
||||||
SetAsimd("1111001x1x>>>xxxxxxx101000x1xxxx", InstName.Vshll, InstEmit32.Vshll, OpCode32SimdShImmLong.Create, OpCode32SimdShImmLong.CreateT32); // A1 encoding.
|
SetAsimd("1111001x1x>>>xxxxxxx101000x1xxxx", InstName.Vshll, InstEmit32.Vshll, OpCode32SimdShImmLong.Create, OpCode32SimdShImmLong.CreateT32); // A1 encoding.
|
||||||
|
SetAsimd("111100111x11<<10xxxx001100x0xxxx", InstName.Vshll, InstEmit32.Vshll2, OpCode32SimdMovn.Create, OpCode32SimdMovn.CreateT32); // A2 encoding.
|
||||||
SetAsimd("1111001x1x>>>xxxxxxx0000>xx1xxxx", InstName.Vshr, InstEmit32.Vshr, OpCode32SimdShImm.Create, OpCode32SimdShImm.CreateT32);
|
SetAsimd("1111001x1x>>>xxxxxxx0000>xx1xxxx", InstName.Vshr, InstEmit32.Vshr, OpCode32SimdShImm.Create, OpCode32SimdShImm.CreateT32);
|
||||||
SetAsimd("111100101x>>>xxxxxxx100000x1xxx0", InstName.Vshrn, InstEmit32.Vshrn, OpCode32SimdShImmNarrow.Create, OpCode32SimdShImmNarrow.CreateT32);
|
SetAsimd("111100101x>>>xxxxxxx100000x1xxx0", InstName.Vshrn, InstEmit32.Vshrn, OpCode32SimdShImmNarrow.Create, OpCode32SimdShImmNarrow.CreateT32);
|
||||||
|
SetAsimd("111100111x>>>xxxxxxx0101>xx1xxxx", InstName.Vsli, InstEmit32.Vsli_I, OpCode32SimdShImm.Create, OpCode32SimdShImm.CreateT32);
|
||||||
SetAsimd("1111001x1x>>>xxxxxxx0001>xx1xxxx", InstName.Vsra, InstEmit32.Vsra, OpCode32SimdShImm.Create, OpCode32SimdShImm.CreateT32);
|
SetAsimd("1111001x1x>>>xxxxxxx0001>xx1xxxx", InstName.Vsra, InstEmit32.Vsra, OpCode32SimdShImm.Create, OpCode32SimdShImm.CreateT32);
|
||||||
SetAsimd("111101001x00xxxxxxxx0000xxx0xxxx", InstName.Vst1, InstEmit32.Vst1, OpCode32SimdMemSingle.Create, OpCode32SimdMemSingle.CreateT32);
|
SetAsimd("111101001x00xxxxxxxx0000xxx0xxxx", InstName.Vst1, InstEmit32.Vst1, OpCode32SimdMemSingle.Create, OpCode32SimdMemSingle.CreateT32);
|
||||||
SetAsimd("111101001x00xxxxxxxx0100xx0xxxxx", InstName.Vst1, InstEmit32.Vst1, OpCode32SimdMemSingle.Create, OpCode32SimdMemSingle.CreateT32);
|
SetAsimd("111101001x00xxxxxxxx0100xx0xxxxx", InstName.Vst1, InstEmit32.Vst1, OpCode32SimdMemSingle.Create, OpCode32SimdMemSingle.CreateT32);
|
||||||
@ -1049,6 +1063,7 @@ namespace ARMeilleure.Decoders
|
|||||||
SetAsimd("111100100x10xxxxxxxx1101xxx0xxxx", InstName.Vsub, InstEmit32.Vsub_V, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);
|
SetAsimd("111100100x10xxxxxxxx1101xxx0xxxx", InstName.Vsub, InstEmit32.Vsub_V, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);
|
||||||
SetAsimd("1111001x1x<<xxxxxxx00010x0x0xxxx", InstName.Vsubl, InstEmit32.Vsubl_I, OpCode32SimdRegLong.Create, OpCode32SimdRegLong.CreateT32);
|
SetAsimd("1111001x1x<<xxxxxxx00010x0x0xxxx", InstName.Vsubl, InstEmit32.Vsubl_I, OpCode32SimdRegLong.Create, OpCode32SimdRegLong.CreateT32);
|
||||||
SetAsimd("1111001x1x<<xxxxxxx00011x0x0xxxx", InstName.Vsubw, InstEmit32.Vsubw_I, OpCode32SimdRegWide.Create, OpCode32SimdRegWide.CreateT32);
|
SetAsimd("1111001x1x<<xxxxxxx00011x0x0xxxx", InstName.Vsubw, InstEmit32.Vsubw_I, OpCode32SimdRegWide.Create, OpCode32SimdRegWide.CreateT32);
|
||||||
|
SetAsimd("111100111x110010xxxx00000xx0xxxx", InstName.Vswp, InstEmit32.Vswp, OpCode32Simd.Create, OpCode32Simd.CreateT32);
|
||||||
SetAsimd("111100111x11xxxxxxxx10xxxxx0xxxx", InstName.Vtbl, InstEmit32.Vtbl, OpCode32SimdTbl.Create, OpCode32SimdTbl.CreateT32);
|
SetAsimd("111100111x11xxxxxxxx10xxxxx0xxxx", InstName.Vtbl, InstEmit32.Vtbl, OpCode32SimdTbl.Create, OpCode32SimdTbl.CreateT32);
|
||||||
SetAsimd("111100111x11<<10xxxx00001xx0xxxx", InstName.Vtrn, InstEmit32.Vtrn, OpCode32SimdCmpZ.Create, OpCode32SimdCmpZ.CreateT32);
|
SetAsimd("111100111x11<<10xxxx00001xx0xxxx", InstName.Vtrn, InstEmit32.Vtrn, OpCode32SimdCmpZ.Create, OpCode32SimdCmpZ.CreateT32);
|
||||||
SetAsimd("111100100x<<xxxxxxxx1000xxx1xxxx", InstName.Vtst, InstEmit32.Vtst, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);
|
SetAsimd("111100100x<<xxxxxxxx1000xxx1xxxx", InstName.Vtst, InstEmit32.Vtst, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);
|
||||||
|
@ -2,6 +2,8 @@ using ARMeilleure.Decoders;
|
|||||||
using ARMeilleure.IntermediateRepresentation;
|
using ARMeilleure.IntermediateRepresentation;
|
||||||
using ARMeilleure.State;
|
using ARMeilleure.State;
|
||||||
using ARMeilleure.Translation;
|
using ARMeilleure.Translation;
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using static ARMeilleure.Instructions.InstEmitAluHelper;
|
using static ARMeilleure.Instructions.InstEmitAluHelper;
|
||||||
using static ARMeilleure.Instructions.InstEmitHelper;
|
using static ARMeilleure.Instructions.InstEmitHelper;
|
||||||
@ -19,6 +21,12 @@ namespace ARMeilleure.Instructions
|
|||||||
Operand n = GetAluN(context);
|
Operand n = GetAluN(context);
|
||||||
Operand m = GetAluM(context, setCarry: false);
|
Operand m = GetAluM(context, setCarry: false);
|
||||||
|
|
||||||
|
if (op.Rn == RegisterAlias.Aarch32Pc && op is OpCodeT32AluImm12)
|
||||||
|
{
|
||||||
|
// For ADR, PC is always 4 bytes aligned, even in Thumb mode.
|
||||||
|
n = context.BitwiseAnd(n, Const(~3u));
|
||||||
|
}
|
||||||
|
|
||||||
Operand res = context.Add(n, m);
|
Operand res = context.Add(n, m);
|
||||||
|
|
||||||
if (ShouldSetFlags(context))
|
if (ShouldSetFlags(context))
|
||||||
@ -284,6 +292,16 @@ namespace ARMeilleure.Instructions
|
|||||||
EmitAluStore(context, res);
|
EmitAluStore(context, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void Qadd16(ArmEmitterContext context)
|
||||||
|
{
|
||||||
|
OpCode32AluReg op = (OpCode32AluReg)context.CurrOp;
|
||||||
|
|
||||||
|
SetIntA32(context, op.Rd, EmitSigned16BitPair(context, GetIntA32(context, op.Rn), GetIntA32(context, op.Rm), (d, n, m) =>
|
||||||
|
{
|
||||||
|
EmitSaturateRange(context, d, context.Add(n, m), 16, unsigned: false, setQ: false);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
public static void Rbit(ArmEmitterContext context)
|
public static void Rbit(ArmEmitterContext context)
|
||||||
{
|
{
|
||||||
Operand m = GetAluM(context);
|
Operand m = GetAluM(context);
|
||||||
@ -467,6 +485,12 @@ namespace ARMeilleure.Instructions
|
|||||||
Operand n = GetAluN(context);
|
Operand n = GetAluN(context);
|
||||||
Operand m = GetAluM(context, setCarry: false);
|
Operand m = GetAluM(context, setCarry: false);
|
||||||
|
|
||||||
|
if (op.Rn == RegisterAlias.Aarch32Pc && op is OpCodeT32AluImm12)
|
||||||
|
{
|
||||||
|
// For ADR, PC is always 4 bytes aligned, even in Thumb mode.
|
||||||
|
n = context.BitwiseAnd(n, Const(~3u));
|
||||||
|
}
|
||||||
|
|
||||||
Operand res = context.Subtract(n, m);
|
Operand res = context.Subtract(n, m);
|
||||||
|
|
||||||
if (ShouldSetFlags(context))
|
if (ShouldSetFlags(context))
|
||||||
@ -546,6 +570,46 @@ namespace ARMeilleure.Instructions
|
|||||||
EmitHsub8(context, unsigned: true);
|
EmitHsub8(context, unsigned: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void Uqadd16(ArmEmitterContext context)
|
||||||
|
{
|
||||||
|
OpCode32AluReg op = (OpCode32AluReg)context.CurrOp;
|
||||||
|
|
||||||
|
SetIntA32(context, op.Rd, EmitUnsigned16BitPair(context, GetIntA32(context, op.Rn), GetIntA32(context, op.Rm), (d, n, m) =>
|
||||||
|
{
|
||||||
|
EmitSaturateUqadd(context, d, context.Add(n, m), 16);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Uqadd8(ArmEmitterContext context)
|
||||||
|
{
|
||||||
|
OpCode32AluReg op = (OpCode32AluReg)context.CurrOp;
|
||||||
|
|
||||||
|
SetIntA32(context, op.Rd, EmitUnsigned8BitPair(context, GetIntA32(context, op.Rn), GetIntA32(context, op.Rm), (d, n, m) =>
|
||||||
|
{
|
||||||
|
EmitSaturateUqadd(context, d, context.Add(n, m), 8);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Uqsub16(ArmEmitterContext context)
|
||||||
|
{
|
||||||
|
OpCode32AluReg op = (OpCode32AluReg)context.CurrOp;
|
||||||
|
|
||||||
|
SetIntA32(context, op.Rd, EmitUnsigned16BitPair(context, GetIntA32(context, op.Rn), GetIntA32(context, op.Rm), (d, n, m) =>
|
||||||
|
{
|
||||||
|
EmitSaturateUqsub(context, d, context.Subtract(n, m), 16);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Uqsub8(ArmEmitterContext context)
|
||||||
|
{
|
||||||
|
OpCode32AluReg op = (OpCode32AluReg)context.CurrOp;
|
||||||
|
|
||||||
|
SetIntA32(context, op.Rd, EmitUnsigned8BitPair(context, GetIntA32(context, op.Rn), GetIntA32(context, op.Rm), (d, n, m) =>
|
||||||
|
{
|
||||||
|
EmitSaturateUqsub(context, d, context.Subtract(n, m), 8);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
public static void Usat(ArmEmitterContext context)
|
public static void Usat(ArmEmitterContext context)
|
||||||
{
|
{
|
||||||
OpCode32Sat op = (OpCode32Sat)context.CurrOp;
|
OpCode32Sat op = (OpCode32Sat)context.CurrOp;
|
||||||
@ -922,6 +986,251 @@ namespace ARMeilleure.Instructions
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void EmitSaturateRange(ArmEmitterContext context, Operand result, Operand value, uint saturateTo, bool unsigned, bool setQ = true)
|
||||||
|
{
|
||||||
|
Debug.Assert(saturateTo <= 32);
|
||||||
|
Debug.Assert(!unsigned || saturateTo < 32);
|
||||||
|
|
||||||
|
if (!unsigned && saturateTo == 32)
|
||||||
|
{
|
||||||
|
// No saturation possible for this case.
|
||||||
|
|
||||||
|
context.Copy(result, value);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (saturateTo == 0)
|
||||||
|
{
|
||||||
|
// Result is always zero if we saturate 0 bits.
|
||||||
|
|
||||||
|
context.Copy(result, Const(0));
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Operand satValue;
|
||||||
|
|
||||||
|
if (unsigned)
|
||||||
|
{
|
||||||
|
// Negative values always saturate (to zero).
|
||||||
|
// So we must always ignore the sign bit when masking, so that the truncated value will differ from the original one.
|
||||||
|
|
||||||
|
satValue = context.BitwiseAnd(value, Const((int)(uint.MaxValue >> (32 - (int)saturateTo))));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
satValue = context.ShiftLeft(value, Const(32 - (int)saturateTo));
|
||||||
|
satValue = context.ShiftRightSI(satValue, Const(32 - (int)saturateTo));
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the result is 0, the values are equal and we don't need saturation.
|
||||||
|
Operand lblNoSat = Label();
|
||||||
|
context.BranchIfFalse(lblNoSat, context.Subtract(value, satValue));
|
||||||
|
|
||||||
|
// Saturate and set Q flag.
|
||||||
|
if (unsigned)
|
||||||
|
{
|
||||||
|
if (saturateTo == 31)
|
||||||
|
{
|
||||||
|
// Only saturation case possible when going from 32 bits signed to 32 or 31 bits unsigned
|
||||||
|
// is when the signed input is negative, as all positive values are representable on a 31 bits range.
|
||||||
|
|
||||||
|
satValue = Const(0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
satValue = context.ShiftRightSI(value, Const(31));
|
||||||
|
satValue = context.BitwiseNot(satValue);
|
||||||
|
satValue = context.ShiftRightUI(satValue, Const(32 - (int)saturateTo));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (saturateTo == 1)
|
||||||
|
{
|
||||||
|
satValue = context.ShiftRightSI(value, Const(31));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
satValue = Const(uint.MaxValue >> (33 - (int)saturateTo));
|
||||||
|
satValue = context.BitwiseExclusiveOr(satValue, context.ShiftRightSI(value, Const(31)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (setQ)
|
||||||
|
{
|
||||||
|
SetFlag(context, PState.QFlag, Const(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Copy(result, satValue);
|
||||||
|
|
||||||
|
Operand lblExit = Label();
|
||||||
|
context.Branch(lblExit);
|
||||||
|
|
||||||
|
context.MarkLabel(lblNoSat);
|
||||||
|
|
||||||
|
context.Copy(result, value);
|
||||||
|
|
||||||
|
context.MarkLabel(lblExit);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void EmitSaturateUqadd(ArmEmitterContext context, Operand result, Operand value, uint saturateTo)
|
||||||
|
{
|
||||||
|
Debug.Assert(saturateTo <= 32);
|
||||||
|
|
||||||
|
if (saturateTo == 32)
|
||||||
|
{
|
||||||
|
// No saturation possible for this case.
|
||||||
|
|
||||||
|
context.Copy(result, value);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (saturateTo == 0)
|
||||||
|
{
|
||||||
|
// Result is always zero if we saturate 0 bits.
|
||||||
|
|
||||||
|
context.Copy(result, Const(0));
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the result is 0, the values are equal and we don't need saturation.
|
||||||
|
Operand lblNoSat = Label();
|
||||||
|
context.BranchIfFalse(lblNoSat, context.ShiftRightUI(value, Const((int)saturateTo)));
|
||||||
|
|
||||||
|
// Saturate.
|
||||||
|
context.Copy(result, Const(uint.MaxValue >> (32 - (int)saturateTo)));
|
||||||
|
|
||||||
|
Operand lblExit = Label();
|
||||||
|
context.Branch(lblExit);
|
||||||
|
|
||||||
|
context.MarkLabel(lblNoSat);
|
||||||
|
|
||||||
|
context.Copy(result, value);
|
||||||
|
|
||||||
|
context.MarkLabel(lblExit);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void EmitSaturateUqsub(ArmEmitterContext context, Operand result, Operand value, uint saturateTo)
|
||||||
|
{
|
||||||
|
Debug.Assert(saturateTo <= 32);
|
||||||
|
|
||||||
|
if (saturateTo == 32)
|
||||||
|
{
|
||||||
|
// No saturation possible for this case.
|
||||||
|
|
||||||
|
context.Copy(result, value);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (saturateTo == 0)
|
||||||
|
{
|
||||||
|
// Result is always zero if we saturate 0 bits.
|
||||||
|
|
||||||
|
context.Copy(result, Const(0));
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the result is 0, the values are equal and we don't need saturation.
|
||||||
|
Operand lblNoSat = Label();
|
||||||
|
context.BranchIf(lblNoSat, value, Const(0), Comparison.GreaterOrEqual);
|
||||||
|
|
||||||
|
// Saturate.
|
||||||
|
// Assumes that the value can only underflow, since this is only used for unsigned subtraction.
|
||||||
|
context.Copy(result, Const(0));
|
||||||
|
|
||||||
|
Operand lblExit = Label();
|
||||||
|
context.Branch(lblExit);
|
||||||
|
|
||||||
|
context.MarkLabel(lblNoSat);
|
||||||
|
|
||||||
|
context.Copy(result, value);
|
||||||
|
|
||||||
|
context.MarkLabel(lblExit);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Operand EmitSigned16BitPair(ArmEmitterContext context, Operand rn, Operand rm, Action<Operand, Operand, Operand> elementAction)
|
||||||
|
{
|
||||||
|
Operand tempD = context.AllocateLocal(OperandType.I32);
|
||||||
|
|
||||||
|
Operand tempN = context.SignExtend16(OperandType.I32, rn);
|
||||||
|
Operand tempM = context.SignExtend16(OperandType.I32, rm);
|
||||||
|
elementAction(tempD, tempN, tempM);
|
||||||
|
Operand tempD2 = context.ZeroExtend16(OperandType.I32, tempD);
|
||||||
|
|
||||||
|
tempN = context.ShiftRightSI(rn, Const(16));
|
||||||
|
tempM = context.ShiftRightSI(rm, Const(16));
|
||||||
|
elementAction(tempD, tempN, tempM);
|
||||||
|
return context.BitwiseOr(tempD2, context.ShiftLeft(tempD, Const(16)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Operand EmitUnsigned16BitPair(ArmEmitterContext context, Operand rn, Operand rm, Action<Operand, Operand, Operand> elementAction)
|
||||||
|
{
|
||||||
|
Operand tempD = context.AllocateLocal(OperandType.I32);
|
||||||
|
|
||||||
|
Operand tempN = context.ZeroExtend16(OperandType.I32, rn);
|
||||||
|
Operand tempM = context.ZeroExtend16(OperandType.I32, rm);
|
||||||
|
elementAction(tempD, tempN, tempM);
|
||||||
|
Operand tempD2 = context.ZeroExtend16(OperandType.I32, tempD);
|
||||||
|
|
||||||
|
tempN = context.ShiftRightUI(rn, Const(16));
|
||||||
|
tempM = context.ShiftRightUI(rm, Const(16));
|
||||||
|
elementAction(tempD, tempN, tempM);
|
||||||
|
return context.BitwiseOr(tempD2, context.ShiftLeft(tempD, Const(16)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Operand EmitSigned8BitPair(ArmEmitterContext context, Operand rn, Operand rm, Action<Operand, Operand, Operand> elementAction)
|
||||||
|
{
|
||||||
|
return Emit8BitPair(context, rn, rm, elementAction, unsigned: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Operand EmitUnsigned8BitPair(ArmEmitterContext context, Operand rn, Operand rm, Action<Operand, Operand, Operand> elementAction)
|
||||||
|
{
|
||||||
|
return Emit8BitPair(context, rn, rm, elementAction, unsigned: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Operand Emit8BitPair(ArmEmitterContext context, Operand rn, Operand rm, Action<Operand, Operand, Operand> elementAction, bool unsigned)
|
||||||
|
{
|
||||||
|
Operand tempD = context.AllocateLocal(OperandType.I32);
|
||||||
|
Operand result = default;
|
||||||
|
|
||||||
|
for (int b = 0; b < 4; b++)
|
||||||
|
{
|
||||||
|
Operand nByte = b != 0 ? context.ShiftRightUI(rn, Const(b * 8)) : rn;
|
||||||
|
Operand mByte = b != 0 ? context.ShiftRightUI(rm, Const(b * 8)) : rm;
|
||||||
|
|
||||||
|
if (unsigned)
|
||||||
|
{
|
||||||
|
nByte = context.ZeroExtend8(OperandType.I32, nByte);
|
||||||
|
mByte = context.ZeroExtend8(OperandType.I32, mByte);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
nByte = context.SignExtend8(OperandType.I32, nByte);
|
||||||
|
mByte = context.SignExtend8(OperandType.I32, mByte);
|
||||||
|
}
|
||||||
|
|
||||||
|
elementAction(tempD, nByte, mByte);
|
||||||
|
|
||||||
|
if (b == 0)
|
||||||
|
{
|
||||||
|
result = context.ZeroExtend8(OperandType.I32, tempD);
|
||||||
|
}
|
||||||
|
else if (b < 3)
|
||||||
|
{
|
||||||
|
result = context.BitwiseOr(result, context.ShiftLeft(context.ZeroExtend8(OperandType.I32, tempD), Const(b * 8)));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result = context.BitwiseOr(result, context.ShiftLeft(tempD, Const(24)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
private static void EmitAluStore(ArmEmitterContext context, Operand value)
|
private static void EmitAluStore(ArmEmitterContext context, Operand value)
|
||||||
{
|
{
|
||||||
IOpCode32Alu op = (IOpCode32Alu)context.CurrOp;
|
IOpCode32Alu op = (IOpCode32Alu)context.CurrOp;
|
||||||
|
@ -403,19 +403,25 @@ namespace ARMeilleure.Instructions
|
|||||||
{
|
{
|
||||||
return EmitHostMappedPointer(context, address);
|
return EmitHostMappedPointer(context, address);
|
||||||
}
|
}
|
||||||
else if (context.Memory.Type == MemoryManagerType.HostTracked)
|
else if (context.Memory.Type.IsHostTracked())
|
||||||
{
|
{
|
||||||
|
if (address.Type == OperandType.I32)
|
||||||
|
{
|
||||||
|
address = context.ZeroExtend32(OperandType.I64, address);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context.Memory.Type == MemoryManagerType.HostTracked)
|
||||||
|
{
|
||||||
|
Operand mask = Const(ulong.MaxValue >> (64 - context.Memory.AddressSpaceBits));
|
||||||
|
address = context.BitwiseAnd(address, mask);
|
||||||
|
}
|
||||||
|
|
||||||
Operand ptBase = !context.HasPtc
|
Operand ptBase = !context.HasPtc
|
||||||
? Const(context.Memory.PageTablePointer.ToInt64())
|
? Const(context.Memory.PageTablePointer.ToInt64())
|
||||||
: Const(context.Memory.PageTablePointer.ToInt64(), Ptc.PageTableSymbol);
|
: Const(context.Memory.PageTablePointer.ToInt64(), Ptc.PageTableSymbol);
|
||||||
|
|
||||||
Operand ptOffset = context.ShiftRightUI(address, Const(PageBits));
|
Operand ptOffset = context.ShiftRightUI(address, Const(PageBits));
|
||||||
|
|
||||||
if (ptOffset.Type == OperandType.I32)
|
|
||||||
{
|
|
||||||
ptOffset = context.ZeroExtend32(OperandType.I64, ptOffset);
|
|
||||||
}
|
|
||||||
|
|
||||||
return context.Add(address, context.Load(OperandType.I64, context.Add(ptBase, context.ShiftLeft(ptOffset, Const(3)))));
|
return context.Add(address, context.Load(OperandType.I64, context.Add(ptBase, context.ShiftLeft(ptOffset, Const(3)))));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2426,7 +2426,11 @@ namespace ARMeilleure.Instructions
|
|||||||
}
|
}
|
||||||
else if (Optimizations.FastFP && Optimizations.UseSse41 && sizeF == 0)
|
else if (Optimizations.FastFP && Optimizations.UseSse41 && sizeF == 0)
|
||||||
{
|
{
|
||||||
Operand res = EmitSse41Round32Exp8OpF(context, context.AddIntrinsic(Intrinsic.X86Rsqrtss, GetVec(op.Rn)), scalar: true);
|
// RSQRTSS handles subnormals as zero, which differs from Arm, so we can't use it here.
|
||||||
|
|
||||||
|
Operand res = context.AddIntrinsic(Intrinsic.X86Sqrtss, GetVec(op.Rn));
|
||||||
|
res = context.AddIntrinsic(Intrinsic.X86Rcpss, res);
|
||||||
|
res = EmitSse41Round32Exp8OpF(context, res, scalar: true);
|
||||||
|
|
||||||
context.Copy(GetVec(op.Rd), context.VectorZeroUpper96(res));
|
context.Copy(GetVec(op.Rd), context.VectorZeroUpper96(res));
|
||||||
}
|
}
|
||||||
@ -2451,7 +2455,11 @@ namespace ARMeilleure.Instructions
|
|||||||
}
|
}
|
||||||
else if (Optimizations.FastFP && Optimizations.UseSse41 && sizeF == 0)
|
else if (Optimizations.FastFP && Optimizations.UseSse41 && sizeF == 0)
|
||||||
{
|
{
|
||||||
Operand res = EmitSse41Round32Exp8OpF(context, context.AddIntrinsic(Intrinsic.X86Rsqrtps, GetVec(op.Rn)), scalar: false);
|
// RSQRTPS handles subnormals as zero, which differs from Arm, so we can't use it here.
|
||||||
|
|
||||||
|
Operand res = context.AddIntrinsic(Intrinsic.X86Sqrtps, GetVec(op.Rn));
|
||||||
|
res = context.AddIntrinsic(Intrinsic.X86Rcpps, res);
|
||||||
|
res = EmitSse41Round32Exp8OpF(context, res, scalar: false);
|
||||||
|
|
||||||
if (op.RegisterSize == RegisterSize.Simd64)
|
if (op.RegisterSize == RegisterSize.Simd64)
|
||||||
{
|
{
|
||||||
|
@ -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)
|
public static void Vpaddl(ArmEmitterContext context)
|
||||||
{
|
{
|
||||||
OpCode32Simd op = (OpCode32Simd)context.CurrOp;
|
OpCode32Simd op = (OpCode32Simd)context.CurrOp;
|
||||||
@ -1239,6 +1246,33 @@ namespace ARMeilleure.Instructions
|
|||||||
EmitVectorUnaryNarrowOp32(context, (op1) => EmitSatQ(context, op1, 8 << op.Size, signedSrc: true, signedDst: false), signed: true);
|
EmitVectorUnaryNarrowOp32(context, (op1) => EmitSatQ(context, op1, 8 << op.Size, signedSrc: true, signedDst: false), signed: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void Vqrdmulh(ArmEmitterContext context)
|
||||||
|
{
|
||||||
|
OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp;
|
||||||
|
int eSize = 8 << op.Size;
|
||||||
|
|
||||||
|
EmitVectorBinaryOpI32(context, (op1, op2) =>
|
||||||
|
{
|
||||||
|
if (op.Size == 2)
|
||||||
|
{
|
||||||
|
op1 = context.SignExtend32(OperandType.I64, op1);
|
||||||
|
op2 = context.SignExtend32(OperandType.I64, op2);
|
||||||
|
}
|
||||||
|
|
||||||
|
Operand res = context.Multiply(op1, op2);
|
||||||
|
res = context.Add(res, Const(res.Type, 1L << (eSize - 2)));
|
||||||
|
res = context.ShiftRightSI(res, Const(eSize - 1));
|
||||||
|
res = EmitSatQ(context, res, eSize, signedSrc: true, signedDst: true);
|
||||||
|
|
||||||
|
if (op.Size == 2)
|
||||||
|
{
|
||||||
|
res = context.ConvertI64ToI32(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}, signed: true);
|
||||||
|
}
|
||||||
|
|
||||||
public static void Vqsub(ArmEmitterContext context)
|
public static void Vqsub(ArmEmitterContext context)
|
||||||
{
|
{
|
||||||
OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp;
|
OpCode32SimdReg op = (OpCode32SimdReg)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).
|
// VRINTZ (floating-point).
|
||||||
public static void Vrint_Z(ArmEmitterContext context)
|
public static void Vrint_Z(ArmEmitterContext context)
|
||||||
{
|
{
|
||||||
|
@ -673,6 +673,35 @@ namespace ARMeilleure.Instructions
|
|||||||
context.Copy(GetVecA32(op.Qd), res);
|
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
|
// Narrow
|
||||||
|
|
||||||
public static void EmitVectorUnaryNarrowOp32(ArmEmitterContext context, Func1I emit, bool signed = false)
|
public static void EmitVectorUnaryNarrowOp32(ArmEmitterContext context, Func1I emit, bool signed = false)
|
||||||
|
@ -191,6 +191,26 @@ namespace ARMeilleure.Instructions
|
|||||||
context.Copy(GetVecA32(op.Qd), res);
|
context.Copy(GetVecA32(op.Qd), res);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void Vswp(ArmEmitterContext context)
|
||||||
|
{
|
||||||
|
OpCode32Simd op = (OpCode32Simd)context.CurrOp;
|
||||||
|
|
||||||
|
if (op.Q)
|
||||||
|
{
|
||||||
|
Operand temp = context.Copy(GetVecA32(op.Qd));
|
||||||
|
|
||||||
|
context.Copy(GetVecA32(op.Qd), GetVecA32(op.Qm));
|
||||||
|
context.Copy(GetVecA32(op.Qm), temp);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Operand temp = ExtractScalar(context, OperandType.I64, op.Vd);
|
||||||
|
|
||||||
|
InsertScalar(context, op.Vd, ExtractScalar(context, OperandType.I64, op.Vm));
|
||||||
|
InsertScalar(context, op.Vm, temp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static void Vtbl(ArmEmitterContext context)
|
public static void Vtbl(ArmEmitterContext context)
|
||||||
{
|
{
|
||||||
OpCode32SimdTbl op = (OpCode32SimdTbl)context.CurrOp;
|
OpCode32SimdTbl op = (OpCode32SimdTbl)context.CurrOp;
|
||||||
|
@ -116,7 +116,7 @@ namespace ARMeilleure.Instructions
|
|||||||
}
|
}
|
||||||
else if (shift >= eSize)
|
else if (shift >= eSize)
|
||||||
{
|
{
|
||||||
if ((op.RegisterSize == RegisterSize.Simd64))
|
if (op.RegisterSize == RegisterSize.Simd64)
|
||||||
{
|
{
|
||||||
Operand res = context.VectorZeroUpper64(GetVec(op.Rd));
|
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)
|
public static void Sqshrn_S(ArmEmitterContext context)
|
||||||
{
|
{
|
||||||
if (Optimizations.UseAdvSimd)
|
if (Optimizations.UseAdvSimd)
|
||||||
@ -1593,6 +1603,99 @@ namespace ARMeilleure.Instructions
|
|||||||
Saturating = 1 << 3,
|
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)
|
private static void EmitShlRegOp(ArmEmitterContext context, ShlRegFlags flags = ShlRegFlags.None)
|
||||||
{
|
{
|
||||||
bool scalar = flags.HasFlag(ShlRegFlags.Scalar);
|
bool scalar = flags.HasFlag(ShlRegFlags.Scalar);
|
||||||
|
@ -106,6 +106,38 @@ namespace ARMeilleure.Instructions
|
|||||||
context.Copy(GetVecA32(op.Qd), res);
|
context.Copy(GetVecA32(op.Qd), res);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void Vshll2(ArmEmitterContext context)
|
||||||
|
{
|
||||||
|
OpCode32Simd op = (OpCode32Simd)context.CurrOp;
|
||||||
|
|
||||||
|
Operand res = context.VectorZero();
|
||||||
|
|
||||||
|
int elems = op.GetBytesCount() >> op.Size;
|
||||||
|
|
||||||
|
for (int index = 0; index < elems; index++)
|
||||||
|
{
|
||||||
|
Operand me = EmitVectorExtract32(context, op.Qm, op.Im + index, op.Size, !op.U);
|
||||||
|
|
||||||
|
if (op.Size == 2)
|
||||||
|
{
|
||||||
|
if (op.U)
|
||||||
|
{
|
||||||
|
me = context.ZeroExtend32(OperandType.I64, me);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
me = context.SignExtend32(OperandType.I64, me);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
me = context.ShiftLeft(me, Const(8 << op.Size));
|
||||||
|
|
||||||
|
res = EmitVectorInsert(context, res, me, index, op.Size + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Copy(GetVecA32(op.Qd), res);
|
||||||
|
}
|
||||||
|
|
||||||
public static void Vshr(ArmEmitterContext context)
|
public static void Vshr(ArmEmitterContext context)
|
||||||
{
|
{
|
||||||
OpCode32SimdShImm op = (OpCode32SimdShImm)context.CurrOp;
|
OpCode32SimdShImm op = (OpCode32SimdShImm)context.CurrOp;
|
||||||
@ -130,6 +162,36 @@ namespace ARMeilleure.Instructions
|
|||||||
EmitVectorUnaryNarrowOp32(context, (op1) => context.ShiftRightUI(op1, Const(shift)));
|
EmitVectorUnaryNarrowOp32(context, (op1) => context.ShiftRightUI(op1, Const(shift)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void Vsli_I(ArmEmitterContext context)
|
||||||
|
{
|
||||||
|
OpCode32SimdShImm op = (OpCode32SimdShImm)context.CurrOp;
|
||||||
|
int shift = op.Shift;
|
||||||
|
int eSize = 8 << op.Size;
|
||||||
|
|
||||||
|
ulong mask = shift != 0 ? ulong.MaxValue >> (64 - shift) : 0UL;
|
||||||
|
|
||||||
|
Operand res = GetVec(op.Qd);
|
||||||
|
|
||||||
|
int elems = op.GetBytesCount() >> op.Size;
|
||||||
|
|
||||||
|
for (int index = 0; index < elems; index++)
|
||||||
|
{
|
||||||
|
Operand me = EmitVectorExtractZx(context, op.Qm, op.Im + index, op.Size);
|
||||||
|
|
||||||
|
Operand neShifted = context.ShiftLeft(me, Const(shift));
|
||||||
|
|
||||||
|
Operand de = EmitVectorExtractZx(context, op.Qd, op.Id + index, op.Size);
|
||||||
|
|
||||||
|
Operand deMasked = context.BitwiseAnd(de, Const(mask));
|
||||||
|
|
||||||
|
Operand e = context.BitwiseOr(neShifted, deMasked);
|
||||||
|
|
||||||
|
res = EmitVectorInsert(context, res, e, op.Id + index, op.Size);
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Copy(GetVec(op.Qd), res);
|
||||||
|
}
|
||||||
|
|
||||||
public static void Vsra(ArmEmitterContext context)
|
public static void Vsra(ArmEmitterContext context)
|
||||||
{
|
{
|
||||||
OpCode32SimdShImm op = (OpCode32SimdShImm)context.CurrOp;
|
OpCode32SimdShImm op = (OpCode32SimdShImm)context.CurrOp;
|
||||||
|
@ -384,7 +384,9 @@ namespace ARMeilleure.Instructions
|
|||||||
Sqrshrn_V,
|
Sqrshrn_V,
|
||||||
Sqrshrun_S,
|
Sqrshrun_S,
|
||||||
Sqrshrun_V,
|
Sqrshrun_V,
|
||||||
|
Sqshl_Si,
|
||||||
Sqshl_V,
|
Sqshl_V,
|
||||||
|
Sqshl_Vi,
|
||||||
Sqshrn_S,
|
Sqshrn_S,
|
||||||
Sqshrn_V,
|
Sqshrn_V,
|
||||||
Sqshrun_S,
|
Sqshrun_S,
|
||||||
@ -525,6 +527,7 @@ namespace ARMeilleure.Instructions
|
|||||||
Pld,
|
Pld,
|
||||||
Pop,
|
Pop,
|
||||||
Push,
|
Push,
|
||||||
|
Qadd16,
|
||||||
Rev,
|
Rev,
|
||||||
Revsh,
|
Revsh,
|
||||||
Rsb,
|
Rsb,
|
||||||
@ -569,6 +572,10 @@ namespace ARMeilleure.Instructions
|
|||||||
Umaal,
|
Umaal,
|
||||||
Umlal,
|
Umlal,
|
||||||
Umull,
|
Umull,
|
||||||
|
Uqadd16,
|
||||||
|
Uqadd8,
|
||||||
|
Uqsub16,
|
||||||
|
Uqsub8,
|
||||||
Usat,
|
Usat,
|
||||||
Usat16,
|
Usat16,
|
||||||
Usub8,
|
Usub8,
|
||||||
@ -635,6 +642,7 @@ namespace ARMeilleure.Instructions
|
|||||||
Vorn,
|
Vorn,
|
||||||
Vorr,
|
Vorr,
|
||||||
Vpadd,
|
Vpadd,
|
||||||
|
Vpadal,
|
||||||
Vpaddl,
|
Vpaddl,
|
||||||
Vpmax,
|
Vpmax,
|
||||||
Vpmin,
|
Vpmin,
|
||||||
@ -642,6 +650,7 @@ namespace ARMeilleure.Instructions
|
|||||||
Vqdmulh,
|
Vqdmulh,
|
||||||
Vqmovn,
|
Vqmovn,
|
||||||
Vqmovun,
|
Vqmovun,
|
||||||
|
Vqrdmulh,
|
||||||
Vqrshrn,
|
Vqrshrn,
|
||||||
Vqrshrun,
|
Vqrshrun,
|
||||||
Vqshrn,
|
Vqshrn,
|
||||||
@ -654,6 +663,7 @@ namespace ARMeilleure.Instructions
|
|||||||
Vrintm,
|
Vrintm,
|
||||||
Vrintn,
|
Vrintn,
|
||||||
Vrintp,
|
Vrintp,
|
||||||
|
Vrintr,
|
||||||
Vrintx,
|
Vrintx,
|
||||||
Vrshr,
|
Vrshr,
|
||||||
Vrshrn,
|
Vrshrn,
|
||||||
@ -662,6 +672,7 @@ namespace ARMeilleure.Instructions
|
|||||||
Vshll,
|
Vshll,
|
||||||
Vshr,
|
Vshr,
|
||||||
Vshrn,
|
Vshrn,
|
||||||
|
Vsli,
|
||||||
Vst1,
|
Vst1,
|
||||||
Vst2,
|
Vst2,
|
||||||
Vst3,
|
Vst3,
|
||||||
@ -678,6 +689,7 @@ namespace ARMeilleure.Instructions
|
|||||||
Vsub,
|
Vsub,
|
||||||
Vsubl,
|
Vsubl,
|
||||||
Vsubw,
|
Vsubw,
|
||||||
|
Vswp,
|
||||||
Vtbl,
|
Vtbl,
|
||||||
Vtrn,
|
Vtrn,
|
||||||
Vtst,
|
Vtst,
|
||||||
|
@ -91,54 +91,54 @@ namespace ARMeilleure.Instructions
|
|||||||
#region "Read"
|
#region "Read"
|
||||||
public static byte ReadByte(ulong address)
|
public static byte ReadByte(ulong address)
|
||||||
{
|
{
|
||||||
return GetMemoryManager().ReadTracked<byte>(address);
|
return GetMemoryManager().ReadGuest<byte>(address);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ushort ReadUInt16(ulong address)
|
public static ushort ReadUInt16(ulong address)
|
||||||
{
|
{
|
||||||
return GetMemoryManager().ReadTracked<ushort>(address);
|
return GetMemoryManager().ReadGuest<ushort>(address);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static uint ReadUInt32(ulong address)
|
public static uint ReadUInt32(ulong address)
|
||||||
{
|
{
|
||||||
return GetMemoryManager().ReadTracked<uint>(address);
|
return GetMemoryManager().ReadGuest<uint>(address);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ulong ReadUInt64(ulong address)
|
public static ulong ReadUInt64(ulong address)
|
||||||
{
|
{
|
||||||
return GetMemoryManager().ReadTracked<ulong>(address);
|
return GetMemoryManager().ReadGuest<ulong>(address);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static V128 ReadVector128(ulong address)
|
public static V128 ReadVector128(ulong address)
|
||||||
{
|
{
|
||||||
return GetMemoryManager().ReadTracked<V128>(address);
|
return GetMemoryManager().ReadGuest<V128>(address);
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region "Write"
|
#region "Write"
|
||||||
public static void WriteByte(ulong address, byte value)
|
public static void WriteByte(ulong address, byte value)
|
||||||
{
|
{
|
||||||
GetMemoryManager().Write(address, value);
|
GetMemoryManager().WriteGuest(address, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void WriteUInt16(ulong address, ushort value)
|
public static void WriteUInt16(ulong address, ushort value)
|
||||||
{
|
{
|
||||||
GetMemoryManager().Write(address, value);
|
GetMemoryManager().WriteGuest(address, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void WriteUInt32(ulong address, uint value)
|
public static void WriteUInt32(ulong address, uint value)
|
||||||
{
|
{
|
||||||
GetMemoryManager().Write(address, value);
|
GetMemoryManager().WriteGuest(address, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void WriteUInt64(ulong address, ulong value)
|
public static void WriteUInt64(ulong address, ulong value)
|
||||||
{
|
{
|
||||||
GetMemoryManager().Write(address, value);
|
GetMemoryManager().WriteGuest(address, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void WriteVector128(ulong address, V128 value)
|
public static void WriteVector128(ulong address, V128 value)
|
||||||
{
|
{
|
||||||
GetMemoryManager().Write(address, value);
|
GetMemoryManager().WriteGuest(address, value);
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
@ -4,7 +4,5 @@ namespace ARMeilleure.Memory
|
|||||||
{
|
{
|
||||||
IJitMemoryBlock Allocate(ulong size);
|
IJitMemoryBlock Allocate(ulong size);
|
||||||
IJitMemoryBlock Reserve(ulong size);
|
IJitMemoryBlock Reserve(ulong size);
|
||||||
|
|
||||||
ulong GetPageSize();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,6 +28,17 @@ namespace ARMeilleure.Memory
|
|||||||
/// <returns>The data</returns>
|
/// <returns>The data</returns>
|
||||||
T ReadTracked<T>(ulong va) where T : unmanaged;
|
T ReadTracked<T>(ulong va) where T : unmanaged;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads data from CPU mapped memory, from guest code. (with read tracking)
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">Type of the data being read</typeparam>
|
||||||
|
/// <param name="va">Virtual address of the data in memory</param>
|
||||||
|
/// <returns>The data</returns>
|
||||||
|
T ReadGuest<T>(ulong va) where T : unmanaged
|
||||||
|
{
|
||||||
|
return ReadTracked<T>(va);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Writes data to CPU mapped memory.
|
/// Writes data to CPU mapped memory.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -36,6 +47,17 @@ namespace ARMeilleure.Memory
|
|||||||
/// <param name="value">Data to be written</param>
|
/// <param name="value">Data to be written</param>
|
||||||
void Write<T>(ulong va, T value) where T : unmanaged;
|
void Write<T>(ulong va, T value) where T : unmanaged;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes data to CPU mapped memory, from guest code.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">Type of the data being written</typeparam>
|
||||||
|
/// <param name="va">Virtual address to write the data into</param>
|
||||||
|
/// <param name="value">Data to be written</param>
|
||||||
|
void WriteGuest<T>(ulong va, T value) where T : unmanaged
|
||||||
|
{
|
||||||
|
Write(va, value);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a read-only span of data from CPU mapped memory.
|
/// Gets a read-only span of data from CPU mapped memory.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -35,18 +35,29 @@ namespace ARMeilleure.Memory
|
|||||||
/// Allows invalid access from JIT code to the rest of the program, but is faster.
|
/// Allows invalid access from JIT code to the rest of the program, but is faster.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
HostMappedUnsafe,
|
HostMappedUnsafe,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// High level implementation using a software flat page table for address translation
|
||||||
|
/// without masking the address and no support for handling invalid or non-contiguous memory access.
|
||||||
|
/// </summary>
|
||||||
|
HostTrackedUnsafe,
|
||||||
}
|
}
|
||||||
|
|
||||||
static class MemoryManagerTypeExtensions
|
public static class MemoryManagerTypeExtensions
|
||||||
{
|
{
|
||||||
public static bool IsHostMapped(this MemoryManagerType type)
|
public static bool IsHostMapped(this MemoryManagerType type)
|
||||||
{
|
{
|
||||||
return type == MemoryManagerType.HostMapped || type == MemoryManagerType.HostMappedUnsafe;
|
return type == MemoryManagerType.HostMapped || type == MemoryManagerType.HostMappedUnsafe;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static bool IsHostTracked(this MemoryManagerType type)
|
||||||
|
{
|
||||||
|
return type == MemoryManagerType.HostTracked || type == MemoryManagerType.HostTrackedUnsafe;
|
||||||
|
}
|
||||||
|
|
||||||
public static bool IsHostMappedOrTracked(this MemoryManagerType type)
|
public static bool IsHostMappedOrTracked(this MemoryManagerType type)
|
||||||
{
|
{
|
||||||
return type == MemoryManagerType.HostTracked || type == MemoryManagerType.HostMapped || type == MemoryManagerType.HostMappedUnsafe;
|
return type.IsHostMapped() || type.IsHostTracked();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,63 +1,14 @@
|
|||||||
using ARMeilleure.IntermediateRepresentation;
|
using ARMeilleure.IntermediateRepresentation;
|
||||||
using ARMeilleure.Memory;
|
|
||||||
using ARMeilleure.Translation;
|
using ARMeilleure.Translation;
|
||||||
using ARMeilleure.Translation.Cache;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using static ARMeilleure.IntermediateRepresentation.Operand.Factory;
|
using static ARMeilleure.IntermediateRepresentation.Operand.Factory;
|
||||||
|
|
||||||
namespace ARMeilleure.Signal
|
namespace ARMeilleure.Signal
|
||||||
{
|
{
|
||||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
public static class NativeSignalHandlerGenerator
|
||||||
struct SignalHandlerRange
|
|
||||||
{
|
{
|
||||||
public int IsActive;
|
public const int MaxTrackedRanges = 8;
|
||||||
public nuint RangeAddress;
|
|
||||||
public nuint RangeEndAddress;
|
|
||||||
public IntPtr ActionPointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
|
||||||
struct SignalHandlerConfig
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The byte offset of the faulting address in the SigInfo or ExceptionRecord struct.
|
|
||||||
/// </summary>
|
|
||||||
public int StructAddressOffset;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The byte offset of the write flag in the SigInfo or ExceptionRecord struct.
|
|
||||||
/// </summary>
|
|
||||||
public int StructWriteOffset;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The sigaction handler that was registered before this one. (unix only)
|
|
||||||
/// </summary>
|
|
||||||
public nuint UnixOldSigaction;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The type of the previous sigaction. True for the 3 argument variant. (unix only)
|
|
||||||
/// </summary>
|
|
||||||
public int UnixOldSigaction3Arg;
|
|
||||||
|
|
||||||
public SignalHandlerRange Range0;
|
|
||||||
public SignalHandlerRange Range1;
|
|
||||||
public SignalHandlerRange Range2;
|
|
||||||
public SignalHandlerRange Range3;
|
|
||||||
public SignalHandlerRange Range4;
|
|
||||||
public SignalHandlerRange Range5;
|
|
||||||
public SignalHandlerRange Range6;
|
|
||||||
public SignalHandlerRange Range7;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class NativeSignalHandler
|
|
||||||
{
|
|
||||||
private delegate void UnixExceptionHandler(int sig, IntPtr info, IntPtr ucontext);
|
|
||||||
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
|
|
||||||
private delegate int VectoredExceptionHandler(IntPtr exceptionInfo);
|
|
||||||
|
|
||||||
private const int MaxTrackedRanges = 8;
|
|
||||||
|
|
||||||
private const int StructAddressOffset = 0;
|
private const int StructAddressOffset = 0;
|
||||||
private const int StructWriteOffset = 4;
|
private const int StructWriteOffset = 4;
|
||||||
@ -70,124 +21,7 @@ namespace ARMeilleure.Signal
|
|||||||
|
|
||||||
private const uint EXCEPTION_ACCESS_VIOLATION = 0xc0000005;
|
private const uint EXCEPTION_ACCESS_VIOLATION = 0xc0000005;
|
||||||
|
|
||||||
private static ulong _pageSize;
|
private static Operand EmitGenericRegionCheck(EmitterContext context, IntPtr signalStructPtr, Operand faultAddress, Operand isWrite, int rangeStructSize)
|
||||||
private static ulong _pageMask;
|
|
||||||
|
|
||||||
private static readonly IntPtr _handlerConfig;
|
|
||||||
private static IntPtr _signalHandlerPtr;
|
|
||||||
private static IntPtr _signalHandlerHandle;
|
|
||||||
|
|
||||||
private static readonly object _lock = new();
|
|
||||||
private static bool _initialized;
|
|
||||||
|
|
||||||
static NativeSignalHandler()
|
|
||||||
{
|
|
||||||
_handlerConfig = Marshal.AllocHGlobal(Unsafe.SizeOf<SignalHandlerConfig>());
|
|
||||||
ref SignalHandlerConfig config = ref GetConfigRef();
|
|
||||||
|
|
||||||
config = new SignalHandlerConfig();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void Initialize(IJitMemoryAllocator allocator)
|
|
||||||
{
|
|
||||||
JitCache.Initialize(allocator);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void InitializeSignalHandler(ulong pageSize, Func<IntPtr, IntPtr, IntPtr> customSignalHandlerFactory = null)
|
|
||||||
{
|
|
||||||
if (_initialized)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
lock (_lock)
|
|
||||||
{
|
|
||||||
if (_initialized)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_pageSize = pageSize;
|
|
||||||
_pageMask = pageSize - 1;
|
|
||||||
|
|
||||||
ref SignalHandlerConfig config = ref GetConfigRef();
|
|
||||||
|
|
||||||
if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS() || OperatingSystem.IsIOS())
|
|
||||||
{
|
|
||||||
_signalHandlerPtr = Marshal.GetFunctionPointerForDelegate(GenerateUnixSignalHandler(_handlerConfig));
|
|
||||||
|
|
||||||
if (customSignalHandlerFactory != null)
|
|
||||||
{
|
|
||||||
_signalHandlerPtr = customSignalHandlerFactory(UnixSignalHandlerRegistration.GetSegfaultExceptionHandler().sa_handler, _signalHandlerPtr);
|
|
||||||
}
|
|
||||||
|
|
||||||
var old = UnixSignalHandlerRegistration.RegisterExceptionHandler(_signalHandlerPtr);
|
|
||||||
|
|
||||||
config.UnixOldSigaction = (nuint)(ulong)old.sa_handler;
|
|
||||||
config.UnixOldSigaction3Arg = old.sa_flags & 4;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
config.StructAddressOffset = 40; // ExceptionInformation1
|
|
||||||
config.StructWriteOffset = 32; // ExceptionInformation0
|
|
||||||
|
|
||||||
_signalHandlerPtr = Marshal.GetFunctionPointerForDelegate(GenerateWindowsSignalHandler(_handlerConfig));
|
|
||||||
|
|
||||||
if (customSignalHandlerFactory != null)
|
|
||||||
{
|
|
||||||
_signalHandlerPtr = customSignalHandlerFactory(IntPtr.Zero, _signalHandlerPtr);
|
|
||||||
}
|
|
||||||
|
|
||||||
_signalHandlerHandle = WindowsSignalHandlerRegistration.RegisterExceptionHandler(_signalHandlerPtr);
|
|
||||||
}
|
|
||||||
|
|
||||||
_initialized = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static unsafe ref SignalHandlerConfig GetConfigRef()
|
|
||||||
{
|
|
||||||
return ref Unsafe.AsRef<SignalHandlerConfig>((void*)_handlerConfig);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static unsafe bool AddTrackedRegion(nuint address, nuint endAddress, IntPtr action)
|
|
||||||
{
|
|
||||||
var ranges = &((SignalHandlerConfig*)_handlerConfig)->Range0;
|
|
||||||
|
|
||||||
for (int i = 0; i < MaxTrackedRanges; i++)
|
|
||||||
{
|
|
||||||
if (ranges[i].IsActive == 0)
|
|
||||||
{
|
|
||||||
ranges[i].RangeAddress = address;
|
|
||||||
ranges[i].RangeEndAddress = endAddress;
|
|
||||||
ranges[i].ActionPointer = action;
|
|
||||||
ranges[i].IsActive = 1;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static unsafe bool RemoveTrackedRegion(nuint address)
|
|
||||||
{
|
|
||||||
var ranges = &((SignalHandlerConfig*)_handlerConfig)->Range0;
|
|
||||||
|
|
||||||
for (int i = 0; i < MaxTrackedRanges; i++)
|
|
||||||
{
|
|
||||||
if (ranges[i].IsActive == 1 && ranges[i].RangeAddress == address)
|
|
||||||
{
|
|
||||||
ranges[i].IsActive = 0;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Operand EmitGenericRegionCheck(EmitterContext context, IntPtr signalStructPtr, Operand faultAddress, Operand isWrite)
|
|
||||||
{
|
{
|
||||||
Operand inRegionLocal = context.AllocateLocal(OperandType.I32);
|
Operand inRegionLocal = context.AllocateLocal(OperandType.I32);
|
||||||
context.Copy(inRegionLocal, Const(0));
|
context.Copy(inRegionLocal, Const(0));
|
||||||
@ -196,7 +30,7 @@ namespace ARMeilleure.Signal
|
|||||||
|
|
||||||
for (int i = 0; i < MaxTrackedRanges; i++)
|
for (int i = 0; i < MaxTrackedRanges; i++)
|
||||||
{
|
{
|
||||||
ulong rangeBaseOffset = (ulong)(RangeOffset + i * Unsafe.SizeOf<SignalHandlerRange>());
|
ulong rangeBaseOffset = (ulong)(RangeOffset + i * rangeStructSize);
|
||||||
|
|
||||||
Operand nextLabel = Label();
|
Operand nextLabel = Label();
|
||||||
|
|
||||||
@ -210,13 +44,12 @@ namespace ARMeilleure.Signal
|
|||||||
// Is the fault address within this tracked region?
|
// Is the fault address within this tracked region?
|
||||||
Operand inRange = context.BitwiseAnd(
|
Operand inRange = context.BitwiseAnd(
|
||||||
context.ICompare(faultAddress, rangeAddress, Comparison.GreaterOrEqualUI),
|
context.ICompare(faultAddress, rangeAddress, Comparison.GreaterOrEqualUI),
|
||||||
context.ICompare(faultAddress, rangeEndAddress, Comparison.LessUI)
|
context.ICompare(faultAddress, rangeEndAddress, Comparison.LessUI));
|
||||||
);
|
|
||||||
|
|
||||||
// Only call tracking if in range.
|
// Only call tracking if in range.
|
||||||
context.BranchIfFalse(nextLabel, inRange, BasicBlockFrequency.Cold);
|
context.BranchIfFalse(nextLabel, inRange, BasicBlockFrequency.Cold);
|
||||||
|
|
||||||
Operand offset = context.BitwiseAnd(context.Subtract(faultAddress, rangeAddress), Const(~_pageMask));
|
Operand offset = context.Subtract(faultAddress, rangeAddress);
|
||||||
|
|
||||||
// Call the tracking action, with the pointer's relative offset to the base address.
|
// Call the tracking action, with the pointer's relative offset to the base address.
|
||||||
Operand trackingActionPtr = context.Load(OperandType.I64, Const((ulong)signalStructPtr + rangeBaseOffset + 20));
|
Operand trackingActionPtr = context.Load(OperandType.I64, Const((ulong)signalStructPtr + rangeBaseOffset + 20));
|
||||||
@ -227,8 +60,10 @@ namespace ARMeilleure.Signal
|
|||||||
|
|
||||||
// Tracking action should be non-null to call it, otherwise assume false return.
|
// Tracking action should be non-null to call it, otherwise assume false return.
|
||||||
context.BranchIfFalse(skipActionLabel, trackingActionPtr);
|
context.BranchIfFalse(skipActionLabel, trackingActionPtr);
|
||||||
Operand result = context.Call(trackingActionPtr, OperandType.I32, offset, Const(_pageSize), isWrite);
|
Operand result = context.Call(trackingActionPtr, OperandType.I64, offset, Const(1UL), isWrite);
|
||||||
context.Copy(inRegionLocal, result);
|
context.Copy(inRegionLocal, context.ICompareNotEqual(result, Const(0UL)));
|
||||||
|
|
||||||
|
GenerateFaultAddressPatchCode(context, faultAddress, result);
|
||||||
|
|
||||||
context.MarkLabel(skipActionLabel);
|
context.MarkLabel(skipActionLabel);
|
||||||
|
|
||||||
@ -269,8 +104,7 @@ namespace ARMeilleure.Signal
|
|||||||
Operand esr = context.Load(OperandType.I64, context.Add(ctxPtr, Const(EsrOffset)));
|
Operand esr = context.Load(OperandType.I64, context.Add(ctxPtr, Const(EsrOffset)));
|
||||||
return context.BitwiseAnd(esr, Const(0x40ul));
|
return context.BitwiseAnd(esr, Const(0x40ul));
|
||||||
}
|
}
|
||||||
|
else if (RuntimeInformation.ProcessArchitecture == Architecture.X64)
|
||||||
if (RuntimeInformation.ProcessArchitecture == Architecture.X64)
|
|
||||||
{
|
{
|
||||||
const ulong ErrOffset = 4; // __es.__err
|
const ulong ErrOffset = 4; // __es.__err
|
||||||
Operand err = context.Load(OperandType.I64, context.Add(ctxPtr, Const(ErrOffset)));
|
Operand err = context.Load(OperandType.I64, context.Add(ctxPtr, Const(ErrOffset)));
|
||||||
@ -310,8 +144,7 @@ namespace ARMeilleure.Signal
|
|||||||
Operand esr = context.Load(OperandType.I64, context.Add(auxPtr, Const(8ul)));
|
Operand esr = context.Load(OperandType.I64, context.Add(auxPtr, Const(8ul)));
|
||||||
return context.BitwiseAnd(esr, Const(0x40ul));
|
return context.BitwiseAnd(esr, Const(0x40ul));
|
||||||
}
|
}
|
||||||
|
else if (RuntimeInformation.ProcessArchitecture == Architecture.X64)
|
||||||
if (RuntimeInformation.ProcessArchitecture == Architecture.X64)
|
|
||||||
{
|
{
|
||||||
const int ErrOffset = 192; // uc_mcontext.gregs[REG_ERR]
|
const int ErrOffset = 192; // uc_mcontext.gregs[REG_ERR]
|
||||||
Operand err = context.Load(OperandType.I64, context.Add(ucontextPtr, Const(ErrOffset)));
|
Operand err = context.Load(OperandType.I64, context.Add(ucontextPtr, Const(ErrOffset)));
|
||||||
@ -322,7 +155,7 @@ namespace ARMeilleure.Signal
|
|||||||
throw new PlatformNotSupportedException();
|
throw new PlatformNotSupportedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static UnixExceptionHandler GenerateUnixSignalHandler(IntPtr signalStructPtr)
|
public static byte[] GenerateUnixSignalHandler(IntPtr signalStructPtr, int rangeStructSize)
|
||||||
{
|
{
|
||||||
EmitterContext context = new();
|
EmitterContext context = new();
|
||||||
|
|
||||||
@ -335,7 +168,7 @@ namespace ARMeilleure.Signal
|
|||||||
|
|
||||||
Operand isWrite = context.ICompareNotEqual(writeFlag, Const(0L)); // Normalize to 0/1.
|
Operand isWrite = context.ICompareNotEqual(writeFlag, Const(0L)); // Normalize to 0/1.
|
||||||
|
|
||||||
Operand isInRegion = EmitGenericRegionCheck(context, signalStructPtr, faultAddress, isWrite);
|
Operand isInRegion = EmitGenericRegionCheck(context, signalStructPtr, faultAddress, isWrite, rangeStructSize);
|
||||||
|
|
||||||
Operand endLabel = Label();
|
Operand endLabel = Label();
|
||||||
|
|
||||||
@ -367,10 +200,10 @@ namespace ARMeilleure.Signal
|
|||||||
|
|
||||||
OperandType[] argTypes = new OperandType[] { OperandType.I32, OperandType.I64, OperandType.I64 };
|
OperandType[] argTypes = new OperandType[] { OperandType.I32, OperandType.I64, OperandType.I64 };
|
||||||
|
|
||||||
return Compiler.Compile(cfg, argTypes, OperandType.None, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<UnixExceptionHandler>();
|
return Compiler.Compile(cfg, argTypes, OperandType.None, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Code;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static VectoredExceptionHandler GenerateWindowsSignalHandler(IntPtr signalStructPtr)
|
public static byte[] GenerateWindowsSignalHandler(IntPtr signalStructPtr, int rangeStructSize)
|
||||||
{
|
{
|
||||||
EmitterContext context = new();
|
EmitterContext context = new();
|
||||||
|
|
||||||
@ -399,7 +232,7 @@ namespace ARMeilleure.Signal
|
|||||||
|
|
||||||
Operand isWrite = context.ICompareNotEqual(writeFlag, Const(0L)); // Normalize to 0/1.
|
Operand isWrite = context.ICompareNotEqual(writeFlag, Const(0L)); // Normalize to 0/1.
|
||||||
|
|
||||||
Operand isInRegion = EmitGenericRegionCheck(context, signalStructPtr, faultAddress, isWrite);
|
Operand isInRegion = EmitGenericRegionCheck(context, signalStructPtr, faultAddress, isWrite, rangeStructSize);
|
||||||
|
|
||||||
Operand endLabel = Label();
|
Operand endLabel = Label();
|
||||||
|
|
||||||
@ -421,7 +254,88 @@ namespace ARMeilleure.Signal
|
|||||||
|
|
||||||
OperandType[] argTypes = new OperandType[] { OperandType.I64 };
|
OperandType[] argTypes = new OperandType[] { OperandType.I64 };
|
||||||
|
|
||||||
return Compiler.Compile(cfg, argTypes, OperandType.I32, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<VectoredExceptionHandler>();
|
return Compiler.Compile(cfg, argTypes, OperandType.I32, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Code;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void GenerateFaultAddressPatchCode(EmitterContext context, Operand faultAddress, Operand newAddress)
|
||||||
|
{
|
||||||
|
if (RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
|
||||||
|
{
|
||||||
|
if (SupportsFaultAddressPatchingForHostOs())
|
||||||
|
{
|
||||||
|
Operand lblSkip = Label();
|
||||||
|
|
||||||
|
context.BranchIf(lblSkip, faultAddress, newAddress, Comparison.Equal);
|
||||||
|
|
||||||
|
Operand ucontextPtr = context.LoadArgument(OperandType.I64, 2);
|
||||||
|
Operand pcCtxAddress = default;
|
||||||
|
ulong baseRegsOffset = 0;
|
||||||
|
|
||||||
|
if (OperatingSystem.IsLinux())
|
||||||
|
{
|
||||||
|
pcCtxAddress = context.Add(ucontextPtr, Const(440UL));
|
||||||
|
baseRegsOffset = 184UL;
|
||||||
|
}
|
||||||
|
else if (OperatingSystem.IsMacOS() || OperatingSystem.IsIOS())
|
||||||
|
{
|
||||||
|
ucontextPtr = context.Load(OperandType.I64, context.Add(ucontextPtr, Const(48UL)));
|
||||||
|
|
||||||
|
pcCtxAddress = context.Add(ucontextPtr, Const(272UL));
|
||||||
|
baseRegsOffset = 16UL;
|
||||||
|
}
|
||||||
|
|
||||||
|
Operand pc = context.Load(OperandType.I64, pcCtxAddress);
|
||||||
|
|
||||||
|
Operand reg = GetAddressRegisterFromArm64Instruction(context, pc);
|
||||||
|
Operand reg64 = context.ZeroExtend32(OperandType.I64, reg);
|
||||||
|
Operand regCtxAddress = context.Add(ucontextPtr, context.Add(context.ShiftLeft(reg64, Const(3)), Const(baseRegsOffset)));
|
||||||
|
Operand regAddress = context.Load(OperandType.I64, regCtxAddress);
|
||||||
|
|
||||||
|
Operand addressDelta = context.Subtract(regAddress, faultAddress);
|
||||||
|
|
||||||
|
context.Store(regCtxAddress, context.Add(newAddress, addressDelta));
|
||||||
|
|
||||||
|
context.MarkLabel(lblSkip);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Operand GetAddressRegisterFromArm64Instruction(EmitterContext context, Operand pc)
|
||||||
|
{
|
||||||
|
Operand inst = context.Load(OperandType.I32, pc);
|
||||||
|
Operand reg = context.AllocateLocal(OperandType.I32);
|
||||||
|
|
||||||
|
Operand isSysInst = context.ICompareEqual(context.BitwiseAnd(inst, Const(0xFFF80000)), Const(0xD5080000));
|
||||||
|
|
||||||
|
Operand lblSys = Label();
|
||||||
|
Operand lblEnd = Label();
|
||||||
|
|
||||||
|
context.BranchIfTrue(lblSys, isSysInst, BasicBlockFrequency.Cold);
|
||||||
|
|
||||||
|
context.Copy(reg, context.BitwiseAnd(context.ShiftRightUI(inst, Const(5)), Const(0x1F)));
|
||||||
|
context.Branch(lblEnd);
|
||||||
|
|
||||||
|
context.MarkLabel(lblSys);
|
||||||
|
context.Copy(reg, context.BitwiseAnd(inst, Const(0x1F)));
|
||||||
|
|
||||||
|
context.MarkLabel(lblEnd);
|
||||||
|
|
||||||
|
return reg;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool SupportsFaultAddressPatchingForHost()
|
||||||
|
{
|
||||||
|
return SupportsFaultAddressPatchingForHostArch() && SupportsFaultAddressPatchingForHostOs();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool SupportsFaultAddressPatchingForHostArch()
|
||||||
|
{
|
||||||
|
return RuntimeInformation.ProcessArchitecture == Architecture.Arm64;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool SupportsFaultAddressPatchingForHostOs()
|
||||||
|
{
|
||||||
|
return OperatingSystem.IsLinux() || OperatingSystem.IsMacOS() || OperatingSystem.IsIOS();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -2,7 +2,7 @@ using ARMeilleure.IntermediateRepresentation;
|
|||||||
using ARMeilleure.Translation;
|
using ARMeilleure.Translation;
|
||||||
using Ryujinx.Common.Memory.PartialUnmaps;
|
using Ryujinx.Common.Memory.PartialUnmaps;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
using static ARMeilleure.IntermediateRepresentation.Operand.Factory;
|
using static ARMeilleure.IntermediateRepresentation.Operand.Factory;
|
||||||
|
|
||||||
namespace ARMeilleure.Signal
|
namespace ARMeilleure.Signal
|
||||||
@ -10,8 +10,28 @@ namespace ARMeilleure.Signal
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Methods to handle signals caused by partial unmaps. See the structs for C# implementations of the methods.
|
/// Methods to handle signals caused by partial unmaps. See the structs for C# implementations of the methods.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static class WindowsPartialUnmapHandler
|
internal static partial class WindowsPartialUnmapHandler
|
||||||
{
|
{
|
||||||
|
[LibraryImport("kernel32.dll", SetLastError = true, EntryPoint = "LoadLibraryA")]
|
||||||
|
private static partial IntPtr LoadLibrary([MarshalAs(UnmanagedType.LPStr)] string lpFileName);
|
||||||
|
|
||||||
|
[LibraryImport("kernel32.dll", SetLastError = true)]
|
||||||
|
private static partial IntPtr GetProcAddress(IntPtr hModule, [MarshalAs(UnmanagedType.LPStr)] string procName);
|
||||||
|
|
||||||
|
private static IntPtr _getCurrentThreadIdPtr;
|
||||||
|
|
||||||
|
public static IntPtr GetCurrentThreadIdFunc()
|
||||||
|
{
|
||||||
|
if (_getCurrentThreadIdPtr == IntPtr.Zero)
|
||||||
|
{
|
||||||
|
IntPtr handle = LoadLibrary("kernel32.dll");
|
||||||
|
|
||||||
|
_getCurrentThreadIdPtr = GetProcAddress(handle, "GetCurrentThreadId");
|
||||||
|
}
|
||||||
|
|
||||||
|
return _getCurrentThreadIdPtr;
|
||||||
|
}
|
||||||
|
|
||||||
public static Operand EmitRetryFromAccessViolation(EmitterContext context)
|
public static Operand EmitRetryFromAccessViolation(EmitterContext context)
|
||||||
{
|
{
|
||||||
IntPtr partialRemapStatePtr = PartialUnmapState.GlobalState;
|
IntPtr partialRemapStatePtr = PartialUnmapState.GlobalState;
|
||||||
@ -20,7 +40,7 @@ namespace ARMeilleure.Signal
|
|||||||
// Get the lock first.
|
// Get the lock first.
|
||||||
EmitNativeReaderLockAcquire(context, IntPtr.Add(partialRemapStatePtr, PartialUnmapState.PartialUnmapLockOffset));
|
EmitNativeReaderLockAcquire(context, IntPtr.Add(partialRemapStatePtr, PartialUnmapState.PartialUnmapLockOffset));
|
||||||
|
|
||||||
IntPtr getCurrentThreadId = WindowsSignalHandlerRegistration.GetCurrentThreadIdFunc();
|
IntPtr getCurrentThreadId = GetCurrentThreadIdFunc();
|
||||||
Operand threadId = context.Call(Const((ulong)getCurrentThreadId), OperandType.I32);
|
Operand threadId = context.Call(Const((ulong)getCurrentThreadId), OperandType.I32);
|
||||||
Operand threadIndex = EmitThreadLocalMapIntGetOrReserve(context, localCountsPtr, threadId, Const(0));
|
Operand threadIndex = EmitThreadLocalMapIntGetOrReserve(context, localCountsPtr, threadId, Const(0));
|
||||||
|
|
||||||
@ -137,17 +157,6 @@ namespace ARMeilleure.Signal
|
|||||||
return context.Add(structsPtr, context.SignExtend32(OperandType.I64, offset));
|
return context.Add(structsPtr, context.SignExtend32(OperandType.I64, offset));
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma warning disable IDE0051 // Remove unused private member
|
|
||||||
private static void EmitThreadLocalMapIntRelease(EmitterContext context, IntPtr threadLocalMapPtr, Operand threadId, Operand index)
|
|
||||||
{
|
|
||||||
Operand offset = context.Multiply(index, Const(sizeof(int)));
|
|
||||||
Operand idsPtr = Const((ulong)IntPtr.Add(threadLocalMapPtr, ThreadLocalMap<int>.ThreadIdsOffset));
|
|
||||||
Operand idPtr = context.Add(idsPtr, context.SignExtend32(OperandType.I64, offset));
|
|
||||||
|
|
||||||
context.CompareAndSwap(idPtr, threadId, Const(0));
|
|
||||||
}
|
|
||||||
#pragma warning restore IDE0051
|
|
||||||
|
|
||||||
private static void EmitAtomicAddI32(EmitterContext context, Operand ptr, Operand additive)
|
private static void EmitAtomicAddI32(EmitterContext context, Operand ptr, Operand additive)
|
||||||
{
|
{
|
||||||
Operand loop = Label();
|
Operand loop = Label();
|
||||||
|
@ -1,44 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace ARMeilleure.Signal
|
|
||||||
{
|
|
||||||
unsafe partial class WindowsSignalHandlerRegistration
|
|
||||||
{
|
|
||||||
[LibraryImport("kernel32.dll")]
|
|
||||||
private static partial IntPtr AddVectoredExceptionHandler(uint first, IntPtr handler);
|
|
||||||
|
|
||||||
[LibraryImport("kernel32.dll")]
|
|
||||||
private static partial ulong RemoveVectoredExceptionHandler(IntPtr handle);
|
|
||||||
|
|
||||||
[LibraryImport("kernel32.dll", SetLastError = true, EntryPoint = "LoadLibraryA")]
|
|
||||||
private static partial IntPtr LoadLibrary([MarshalAs(UnmanagedType.LPStr)] string lpFileName);
|
|
||||||
|
|
||||||
[LibraryImport("kernel32.dll", SetLastError = true)]
|
|
||||||
private static partial IntPtr GetProcAddress(IntPtr hModule, [MarshalAs(UnmanagedType.LPStr)] string procName);
|
|
||||||
|
|
||||||
private static IntPtr _getCurrentThreadIdPtr;
|
|
||||||
|
|
||||||
public static IntPtr RegisterExceptionHandler(IntPtr action)
|
|
||||||
{
|
|
||||||
return AddVectoredExceptionHandler(1, action);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool RemoveExceptionHandler(IntPtr handle)
|
|
||||||
{
|
|
||||||
return RemoveVectoredExceptionHandler(handle) != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IntPtr GetCurrentThreadIdFunc()
|
|
||||||
{
|
|
||||||
if (_getCurrentThreadIdPtr == IntPtr.Zero)
|
|
||||||
{
|
|
||||||
IntPtr handle = LoadLibrary("kernel32.dll");
|
|
||||||
|
|
||||||
_getCurrentThreadIdPtr = GetProcAddress(handle, "GetCurrentThreadId");
|
|
||||||
}
|
|
||||||
|
|
||||||
return _getCurrentThreadIdPtr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -117,8 +117,15 @@ namespace ARMeilleure.Translation.Cache
|
|||||||
if (OperatingSystem.IsIOS())
|
if (OperatingSystem.IsIOS())
|
||||||
{
|
{
|
||||||
Marshal.Copy(code, 0, funcPtr, code.Length);
|
Marshal.Copy(code, 0, funcPtr, code.Length);
|
||||||
ReprotectAsExecutable(targetRegion, funcOffset, code.Length);
|
if (deferProtect)
|
||||||
JitSupportDarwinAot.Invalidate(funcPtr, (ulong)code.Length);
|
{
|
||||||
|
_deferredRxProtect.Enqueue((funcOffset, code.Length));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ReprotectAsExecutable(targetRegion, funcOffset, code.Length);
|
||||||
|
JitSupportDarwinAot.Invalidate(funcPtr, (ulong)code.Length);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (OperatingSystem.IsMacOS()&& RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
|
else if (OperatingSystem.IsMacOS()&& RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
|
||||||
{
|
{
|
||||||
|
@ -11,7 +11,7 @@ namespace ARMeilleure.Translation
|
|||||||
private int[] _postOrderMap;
|
private int[] _postOrderMap;
|
||||||
|
|
||||||
public int LocalsCount { get; private set; }
|
public int LocalsCount { get; private set; }
|
||||||
public BasicBlock Entry { get; }
|
public BasicBlock Entry { get; private set; }
|
||||||
public IntrusiveList<BasicBlock> Blocks { get; }
|
public IntrusiveList<BasicBlock> Blocks { get; }
|
||||||
public BasicBlock[] PostOrderBlocks => _postOrderBlocks;
|
public BasicBlock[] PostOrderBlocks => _postOrderBlocks;
|
||||||
public int[] PostOrderMap => _postOrderMap;
|
public int[] PostOrderMap => _postOrderMap;
|
||||||
@ -34,6 +34,15 @@ namespace ARMeilleure.Translation
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void UpdateEntry(BasicBlock newEntry)
|
||||||
|
{
|
||||||
|
newEntry.AddSuccessor(Entry);
|
||||||
|
|
||||||
|
Entry = newEntry;
|
||||||
|
Blocks.AddFirst(newEntry);
|
||||||
|
Update();
|
||||||
|
}
|
||||||
|
|
||||||
public void Update()
|
public void Update()
|
||||||
{
|
{
|
||||||
RemoveUnreachableBlocks(Blocks);
|
RemoveUnreachableBlocks(Blocks);
|
||||||
|
@ -30,7 +30,7 @@ namespace ARMeilleure.Translation.PTC
|
|||||||
private const string OuterHeaderMagicString = "PTCohd\0\0";
|
private const string OuterHeaderMagicString = "PTCohd\0\0";
|
||||||
private const string InnerHeaderMagicString = "PTCihd\0\0";
|
private const string InnerHeaderMagicString = "PTCihd\0\0";
|
||||||
|
|
||||||
private const uint InternalVersion = 5518; //! To be incremented manually for each change to the ARMeilleure project.
|
private const uint InternalVersion = 6950; //! To be incremented manually for each change to the ARMeilleure project.
|
||||||
|
|
||||||
private const string ActualDir = "0";
|
private const string ActualDir = "0";
|
||||||
private const string BackupDir = "1";
|
private const string BackupDir = "1";
|
||||||
@ -858,8 +858,14 @@ namespace ARMeilleure.Translation.PTC
|
|||||||
|
|
||||||
Stopwatch sw = Stopwatch.StartNew();
|
Stopwatch sw = Stopwatch.StartNew();
|
||||||
|
|
||||||
threads.ForEach((thread) => thread.Start());
|
foreach (var thread in threads)
|
||||||
threads.ForEach((thread) => thread.Join());
|
{
|
||||||
|
thread.Start();
|
||||||
|
}
|
||||||
|
foreach (var thread in threads)
|
||||||
|
{
|
||||||
|
thread.Join();
|
||||||
|
}
|
||||||
|
|
||||||
threads.Clear();
|
threads.Clear();
|
||||||
|
|
||||||
|
@ -89,6 +89,17 @@ namespace ARMeilleure.Translation
|
|||||||
|
|
||||||
public static void RunPass(ControlFlowGraph cfg, ExecutionMode mode)
|
public static void RunPass(ControlFlowGraph cfg, ExecutionMode mode)
|
||||||
{
|
{
|
||||||
|
if (cfg.Entry.Predecessors.Count != 0)
|
||||||
|
{
|
||||||
|
// We expect the entry block to have no predecessors.
|
||||||
|
// This is required because we have a implicit context load at the start of the function,
|
||||||
|
// but if there is a jump to the start of the function, the context load would trash the modified values.
|
||||||
|
// Here we insert a new entry block that will jump to the existing entry block.
|
||||||
|
BasicBlock newEntry = new BasicBlock(cfg.Blocks.Count);
|
||||||
|
|
||||||
|
cfg.UpdateEntry(newEntry);
|
||||||
|
}
|
||||||
|
|
||||||
// Compute local register inputs and outputs used inside blocks.
|
// Compute local register inputs and outputs used inside blocks.
|
||||||
RegisterMask[] localInputs = new RegisterMask[cfg.Blocks.Count];
|
RegisterMask[] localInputs = new RegisterMask[cfg.Blocks.Count];
|
||||||
RegisterMask[] localOutputs = new RegisterMask[cfg.Blocks.Count];
|
RegisterMask[] localOutputs = new RegisterMask[cfg.Blocks.Count];
|
||||||
@ -201,7 +212,7 @@ namespace ARMeilleure.Translation
|
|||||||
|
|
||||||
// The only block without any predecessor should be the entry block.
|
// The only block without any predecessor should be the entry block.
|
||||||
// It always needs a context load as it is the first block to run.
|
// It always needs a context load as it is the first block to run.
|
||||||
if (block.Predecessors.Count == 0 || hasContextLoad)
|
if (block == cfg.Entry || hasContextLoad)
|
||||||
{
|
{
|
||||||
long vecMask = globalInputs[block.Index].VecMask;
|
long vecMask = globalInputs[block.Index].VecMask;
|
||||||
long intMask = globalInputs[block.Index].IntMask;
|
long intMask = globalInputs[block.Index].IntMask;
|
||||||
|
@ -57,9 +57,6 @@ namespace ARMeilleure.Translation
|
|||||||
private Thread[] _backgroundTranslationThreads;
|
private Thread[] _backgroundTranslationThreads;
|
||||||
private volatile int _threadCount;
|
private volatile int _threadCount;
|
||||||
|
|
||||||
// FIXME: Remove this once the init logic of the emulator will be redone.
|
|
||||||
public static readonly ManualResetEvent IsReadyForTranslation = new(false);
|
|
||||||
|
|
||||||
public Translator(IJitMemoryAllocator allocator, IMemoryManager memory, bool for64Bits)
|
public Translator(IJitMemoryAllocator allocator, IMemoryManager memory, bool for64Bits)
|
||||||
{
|
{
|
||||||
_allocator = allocator;
|
_allocator = allocator;
|
||||||
@ -79,11 +76,6 @@ namespace ARMeilleure.Translation
|
|||||||
Stubs = new TranslatorStubs(FunctionTable);
|
Stubs = new TranslatorStubs(FunctionTable);
|
||||||
|
|
||||||
FunctionTable.Fill = (ulong)Stubs.SlowDispatchStub;
|
FunctionTable.Fill = (ulong)Stubs.SlowDispatchStub;
|
||||||
|
|
||||||
if (memory.Type.IsHostMappedOrTracked())
|
|
||||||
{
|
|
||||||
NativeSignalHandler.InitializeSignalHandler(allocator.GetPageSize());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public IPtcLoadState LoadDiskCache(string titleIdText, string displayVersion, bool enabled)
|
public IPtcLoadState LoadDiskCache(string titleIdText, string displayVersion, bool enabled)
|
||||||
@ -105,8 +97,6 @@ namespace ARMeilleure.Translation
|
|||||||
{
|
{
|
||||||
if (Interlocked.Increment(ref _threadCount) == 1)
|
if (Interlocked.Increment(ref _threadCount) == 1)
|
||||||
{
|
{
|
||||||
IsReadyForTranslation.WaitOne();
|
|
||||||
|
|
||||||
if (_ptc.State == PtcState.Enabled)
|
if (_ptc.State == PtcState.Enabled)
|
||||||
{
|
{
|
||||||
Debug.Assert(Functions.Count == 0);
|
Debug.Assert(Functions.Count == 0);
|
||||||
|
@ -80,7 +80,10 @@ namespace ARMeilleure.Translation
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Monitor.Wait(Sync);
|
if (!_disposed)
|
||||||
|
{
|
||||||
|
Monitor.Wait(Sync);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,13 +32,6 @@
|
|||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXContainerItemProxy section */
|
/* Begin PBXContainerItemProxy section */
|
||||||
4E12B3A12D798BD100FB2271 /* PBXContainerItemProxy */ = {
|
|
||||||
isa = PBXContainerItemProxy;
|
|
||||||
containerPortal = 4E80A9852CD6F54500029585 /* Project object */;
|
|
||||||
proxyType = 1;
|
|
||||||
remoteGlobalIDString = BD43C6212D1B248D003BBC42;
|
|
||||||
remoteInfo = com.Stossy11.MeloNX.RyujinxAg;
|
|
||||||
};
|
|
||||||
4E80A99E2CD6F54700029585 /* PBXContainerItemProxy */ = {
|
4E80A99E2CD6F54700029585 /* PBXContainerItemProxy */ = {
|
||||||
isa = PBXContainerItemProxy;
|
isa = PBXContainerItemProxy;
|
||||||
containerPortal = 4E80A9852CD6F54500029585 /* Project object */;
|
containerPortal = 4E80A9852CD6F54500029585 /* Project object */;
|
||||||
@ -294,7 +287,6 @@
|
|||||||
buildRules = (
|
buildRules = (
|
||||||
);
|
);
|
||||||
dependencies = (
|
dependencies = (
|
||||||
4E12B3A22D798BD100FB2271 /* PBXTargetDependency */,
|
|
||||||
);
|
);
|
||||||
fileSystemSynchronizedGroups = (
|
fileSystemSynchronizedGroups = (
|
||||||
4E80A98F2CD6F54500029585 /* MeloNX */,
|
4E80A98F2CD6F54500029585 /* MeloNX */,
|
||||||
@ -482,11 +474,6 @@
|
|||||||
/* End PBXSourcesBuildPhase section */
|
/* End PBXSourcesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXTargetDependency section */
|
/* Begin PBXTargetDependency section */
|
||||||
4E12B3A22D798BD100FB2271 /* PBXTargetDependency */ = {
|
|
||||||
isa = PBXTargetDependency;
|
|
||||||
target = BD43C6212D1B248D003BBC42 /* com.Stossy11.MeloNX.RyujinxAg */;
|
|
||||||
targetProxy = 4E12B3A12D798BD100FB2271 /* PBXContainerItemProxy */;
|
|
||||||
};
|
|
||||||
4E80A99F2CD6F54700029585 /* PBXTargetDependency */ = {
|
4E80A99F2CD6F54700029585 /* PBXTargetDependency */ = {
|
||||||
isa = PBXTargetDependency;
|
isa = PBXTargetDependency;
|
||||||
target = 4E80A98C2CD6F54500029585 /* MeloNX */;
|
target = 4E80A98C2CD6F54500029585 /* MeloNX */;
|
||||||
@ -704,6 +691,8 @@
|
|||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
);
|
);
|
||||||
GCC_OPTIMIZATION_LEVEL = fast;
|
GCC_OPTIMIZATION_LEVEL = fast;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
@ -815,6 +804,10 @@
|
|||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = "$(VERSION)";
|
MARKETING_VERSION = "$(VERSION)";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX;
|
PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX;
|
||||||
@ -889,6 +882,8 @@
|
|||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
);
|
);
|
||||||
GCC_OPTIMIZATION_LEVEL = fast;
|
GCC_OPTIMIZATION_LEVEL = fast;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
@ -1000,6 +995,10 @@
|
|||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = "$(VERSION)";
|
MARKETING_VERSION = "$(VERSION)";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX;
|
PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX;
|
||||||
|
Binary file not shown.
@ -71,8 +71,9 @@ struct ContentView: View {
|
|||||||
MoltenVKSettings(string: "MVK_USE_METAL_PRIVATE_API", value: "1"),
|
MoltenVKSettings(string: "MVK_USE_METAL_PRIVATE_API", value: "1"),
|
||||||
MoltenVKSettings(string: "MVK_CONFIG_USE_METAL_PRIVATE_API", value: "1"),
|
MoltenVKSettings(string: "MVK_CONFIG_USE_METAL_PRIVATE_API", value: "1"),
|
||||||
MoltenVKSettings(string: "MVK_DEBUG", value: "0"),
|
MoltenVKSettings(string: "MVK_DEBUG", value: "0"),
|
||||||
MoltenVKSettings(string: "MVK_CONFIG_SYNCHRONOUS_QUEUE_SUBMITS", value: "0"),
|
MoltenVKSettings(string: "MVK_CONFIG_LOG_LEVEL", value: "0"),
|
||||||
MoltenVKSettings(string: "MVK_CONFIG_PREFILL_METAL_COMMAND_BUFFERS", value: "0"),
|
MoltenVKSettings(string: "MVK_CONFIG_SYNCHRONOUS_QUEUE_SUBMITS", value: "1"),
|
||||||
|
// MoltenVKSettings(string: "MVK_CONFIG_PREFILL_METAL_COMMAND_BUFFERS", value: "0"),
|
||||||
// Uses more ram but makes performance higher, may add an option in settings to change or enable / disable this value (default 64)
|
// Uses more ram but makes performance higher, may add an option in settings to change or enable / disable this value (default 64)
|
||||||
MoltenVKSettings(string: "MVK_CONFIG_MAX_ACTIVE_METAL_COMMAND_BUFFERS_PER_QUEUE", value: "512"),
|
MoltenVKSettings(string: "MVK_CONFIG_MAX_ACTIVE_METAL_COMMAND_BUFFERS_PER_QUEUE", value: "512"),
|
||||||
]
|
]
|
||||||
|
@ -52,11 +52,11 @@ struct LogFileView: View {
|
|||||||
let logFiles = try fileManager.contentsOfDirectory(at: logsDirectory, includingPropertiesForKeys: [.creationDateKey])
|
let logFiles = try fileManager.contentsOfDirectory(at: logsDirectory, includingPropertiesForKeys: [.creationDateKey])
|
||||||
.filter {
|
.filter {
|
||||||
let filename = $0.lastPathComponent
|
let filename = $0.lastPathComponent
|
||||||
guard filename.hasPrefix("Ryujinx_ios_") && filename.hasSuffix(".log") else {
|
guard filename.hasPrefix("MeloNX_") && filename.hasSuffix(".log") else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
let dateString = filename.replacingOccurrences(of: "Ryujinx_ios_", with: "").replacingOccurrences(of: ".log", with: "")
|
let dateString = filename.replacingOccurrences(of: "MeloNX_", with: "").replacingOccurrences(of: ".log", with: "")
|
||||||
guard let logDate = dateFormatter.date(from: dateString) else {
|
guard let logDate = dateFormatter.date(from: dateString) else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,25 @@ namespace Ryujinx.Audio.Backends.OpenAL
|
|||||||
private bool _stillRunning;
|
private bool _stillRunning;
|
||||||
private readonly Thread _updaterThread;
|
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()
|
public OpenALHardwareDeviceDriver()
|
||||||
{
|
{
|
||||||
_device = ALC.OpenDevice("");
|
_device = ALC.OpenDevice("");
|
||||||
@ -34,6 +53,8 @@ namespace Ryujinx.Audio.Backends.OpenAL
|
|||||||
Name = "HardwareDeviceDriver.OpenAL",
|
Name = "HardwareDeviceDriver.OpenAL",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
_volume = 1f;
|
||||||
|
|
||||||
_updaterThread.Start();
|
_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)
|
if (channelCount == 0)
|
||||||
{
|
{
|
||||||
@ -73,7 +94,7 @@ namespace Ryujinx.Audio.Backends.OpenAL
|
|||||||
throw new ArgumentException($"{channelCount}");
|
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);
|
_sessions.TryAdd(session, 0);
|
||||||
|
|
||||||
|
@ -16,10 +16,11 @@ namespace Ryujinx.Audio.Backends.OpenAL
|
|||||||
private bool _isActive;
|
private bool _isActive;
|
||||||
private readonly Queue<OpenALAudioBuffer> _queuedBuffers;
|
private readonly Queue<OpenALAudioBuffer> _queuedBuffers;
|
||||||
private ulong _playedSampleCount;
|
private ulong _playedSampleCount;
|
||||||
|
private float _volume;
|
||||||
|
|
||||||
private readonly object _lock = new();
|
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;
|
_driver = driver;
|
||||||
_queuedBuffers = new Queue<OpenALAudioBuffer>();
|
_queuedBuffers = new Queue<OpenALAudioBuffer>();
|
||||||
@ -27,7 +28,7 @@ namespace Ryujinx.Audio.Backends.OpenAL
|
|||||||
_targetFormat = GetALFormat();
|
_targetFormat = GetALFormat();
|
||||||
_isActive = false;
|
_isActive = false;
|
||||||
_playedSampleCount = 0;
|
_playedSampleCount = 0;
|
||||||
SetVolume(requestedVolume);
|
SetVolume(1f);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ALFormat GetALFormat()
|
private ALFormat GetALFormat()
|
||||||
@ -85,17 +86,22 @@ namespace Ryujinx.Audio.Backends.OpenAL
|
|||||||
|
|
||||||
public override void SetVolume(float volume)
|
public override void SetVolume(float volume)
|
||||||
{
|
{
|
||||||
lock (_lock)
|
_volume = volume;
|
||||||
{
|
|
||||||
AL.Source(_sourceId, ALSourcef.Gain, volume);
|
UpdateMasterVolume(_driver.Volume);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override float GetVolume()
|
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()
|
public override void Start()
|
||||||
|
@ -20,6 +20,8 @@ namespace Ryujinx.Audio.Backends.SDL2
|
|||||||
|
|
||||||
private readonly bool _supportSurroundConfiguration;
|
private readonly bool _supportSurroundConfiguration;
|
||||||
|
|
||||||
|
public float Volume { get; set; }
|
||||||
|
|
||||||
// TODO: Add this to SDL2-CS
|
// TODO: Add this to SDL2-CS
|
||||||
// NOTE: We use a DllImport here because of marshaling issue for spec.
|
// NOTE: We use a DllImport here because of marshaling issue for spec.
|
||||||
#pragma warning disable SYSLIB1054
|
#pragma warning disable SYSLIB1054
|
||||||
@ -48,6 +50,8 @@ namespace Ryujinx.Audio.Backends.SDL2
|
|||||||
{
|
{
|
||||||
_supportSurroundConfiguration = spec.channels >= 6;
|
_supportSurroundConfiguration = spec.channels >= 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Volume = 1f;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool IsSupported => IsSupportedInternal();
|
public static bool IsSupported => IsSupportedInternal();
|
||||||
@ -74,7 +78,7 @@ namespace Ryujinx.Audio.Backends.SDL2
|
|||||||
return _pauseEvent;
|
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)
|
if (channelCount == 0)
|
||||||
{
|
{
|
||||||
@ -91,7 +95,7 @@ namespace Ryujinx.Audio.Backends.SDL2
|
|||||||
throw new NotImplementedException("Input direction is currently not implemented on SDL2 backend!");
|
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);
|
_sessions.TryAdd(session, 0);
|
||||||
|
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
using Ryujinx.Audio.Backends.Common;
|
using Ryujinx.Audio.Backends.Common;
|
||||||
using Ryujinx.Audio.Common;
|
using Ryujinx.Audio.Common;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
|
using Ryujinx.Common.Memory;
|
||||||
using Ryujinx.Memory;
|
using Ryujinx.Memory;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Buffers;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
|
||||||
@ -26,7 +28,7 @@ namespace Ryujinx.Audio.Backends.SDL2
|
|||||||
private float _volume;
|
private float _volume;
|
||||||
private readonly ushort _nativeSampleFormat;
|
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;
|
_driver = driver;
|
||||||
_updateRequiredEvent = _driver.GetUpdateRequiredEvent();
|
_updateRequiredEvent = _driver.GetUpdateRequiredEvent();
|
||||||
@ -37,7 +39,7 @@ namespace Ryujinx.Audio.Backends.SDL2
|
|||||||
_nativeSampleFormat = SDL2HardwareDeviceDriver.GetSDL2Format(RequestedSampleFormat);
|
_nativeSampleFormat = SDL2HardwareDeviceDriver.GetSDL2Format(RequestedSampleFormat);
|
||||||
_sampleCount = uint.MaxValue;
|
_sampleCount = uint.MaxValue;
|
||||||
_started = false;
|
_started = false;
|
||||||
_volume = requestedVolume;
|
_volume = 1f;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void EnsureAudioStreamSetup(AudioBuffer buffer)
|
private void EnsureAudioStreamSetup(AudioBuffer buffer)
|
||||||
@ -87,7 +89,9 @@ namespace Ryujinx.Audio.Backends.SDL2
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] samples = new byte[frameCount * _bytesPerFrame];
|
using SpanOwner<byte> samplesOwner = SpanOwner<byte>.Rent(frameCount * _bytesPerFrame);
|
||||||
|
|
||||||
|
Span<byte> samples = samplesOwner.Span;
|
||||||
|
|
||||||
_ringBuffer.Read(samples, 0, samples.Length);
|
_ringBuffer.Read(samples, 0, samples.Length);
|
||||||
|
|
||||||
@ -99,7 +103,7 @@ namespace Ryujinx.Audio.Backends.SDL2
|
|||||||
streamSpan.Clear();
|
streamSpan.Clear();
|
||||||
|
|
||||||
// Apply volume to written data
|
// 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);
|
ulong sampleCount = GetSampleCount(samples.Length);
|
||||||
|
@ -11,15 +11,15 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<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>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
<TargetPath>libsoundio.dll</TargetPath>
|
<TargetPath>libsoundio.dll</TargetPath>
|
||||||
</ContentWithTargetPath>
|
</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>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
<TargetPath>libsoundio.dylib</TargetPath>
|
<TargetPath>libsoundio.dylib</TargetPath>
|
||||||
</ContentWithTargetPath>
|
</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>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
<TargetPath>libsoundio.so</TargetPath>
|
<TargetPath>libsoundio.so</TargetPath>
|
||||||
</ContentWithTargetPath>
|
</ContentWithTargetPath>
|
||||||
|
@ -19,6 +19,25 @@ namespace Ryujinx.Audio.Backends.SoundIo
|
|||||||
private readonly ConcurrentDictionary<SoundIoHardwareDeviceSession, byte> _sessions;
|
private readonly ConcurrentDictionary<SoundIoHardwareDeviceSession, byte> _sessions;
|
||||||
private int _disposeState;
|
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()
|
public SoundIoHardwareDeviceDriver()
|
||||||
{
|
{
|
||||||
_audioContext = SoundIoContext.Create();
|
_audioContext = SoundIoContext.Create();
|
||||||
@ -122,7 +141,7 @@ namespace Ryujinx.Audio.Backends.SoundIo
|
|||||||
return _pauseEvent;
|
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)
|
if (channelCount == 0)
|
||||||
{
|
{
|
||||||
@ -134,14 +153,12 @@ namespace Ryujinx.Audio.Backends.SoundIo
|
|||||||
sampleRate = Constants.TargetSampleRate;
|
sampleRate = Constants.TargetSampleRate;
|
||||||
}
|
}
|
||||||
|
|
||||||
volume = Math.Clamp(volume, 0, 1);
|
|
||||||
|
|
||||||
if (direction != Direction.Output)
|
if (direction != Direction.Output)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException("Input direction is currently not implemented on SoundIO backend!");
|
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);
|
_sessions.TryAdd(session, 0);
|
||||||
|
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
using Ryujinx.Audio.Backends.Common;
|
using Ryujinx.Audio.Backends.Common;
|
||||||
using Ryujinx.Audio.Backends.SoundIo.Native;
|
using Ryujinx.Audio.Backends.SoundIo.Native;
|
||||||
using Ryujinx.Audio.Common;
|
using Ryujinx.Audio.Common;
|
||||||
|
using Ryujinx.Common.Memory;
|
||||||
using Ryujinx.Memory;
|
using Ryujinx.Memory;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Buffers;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
@ -18,16 +20,18 @@ namespace Ryujinx.Audio.Backends.SoundIo
|
|||||||
private readonly DynamicRingBuffer _ringBuffer;
|
private readonly DynamicRingBuffer _ringBuffer;
|
||||||
private ulong _playedSampleCount;
|
private ulong _playedSampleCount;
|
||||||
private readonly ManualResetEvent _updateRequiredEvent;
|
private readonly ManualResetEvent _updateRequiredEvent;
|
||||||
|
private float _volume;
|
||||||
private int _disposeState;
|
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;
|
_driver = driver;
|
||||||
_updateRequiredEvent = _driver.GetUpdateRequiredEvent();
|
_updateRequiredEvent = _driver.GetUpdateRequiredEvent();
|
||||||
_queuedBuffers = new ConcurrentQueue<SoundIoAudioBuffer>();
|
_queuedBuffers = new ConcurrentQueue<SoundIoAudioBuffer>();
|
||||||
_ringBuffer = new DynamicRingBuffer();
|
_ringBuffer = new DynamicRingBuffer();
|
||||||
|
_volume = 1f;
|
||||||
|
|
||||||
SetupOutputStream(requestedVolume);
|
SetupOutputStream(driver.Volume);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetupOutputStream(float requestedVolume)
|
private void SetupOutputStream(float requestedVolume)
|
||||||
@ -35,7 +39,7 @@ namespace Ryujinx.Audio.Backends.SoundIo
|
|||||||
_outputStream = _driver.OpenStream(RequestedSampleFormat, RequestedSampleRate, RequestedChannelCount);
|
_outputStream = _driver.OpenStream(RequestedSampleFormat, RequestedSampleRate, RequestedChannelCount);
|
||||||
_outputStream.WriteCallback += Update;
|
_outputStream.WriteCallback += Update;
|
||||||
_outputStream.Volume = requestedVolume;
|
_outputStream.Volume = requestedVolume;
|
||||||
// TODO: Setup other callbacks (errors, ect).
|
// TODO: Setup other callbacks (errors, etc.)
|
||||||
|
|
||||||
_outputStream.Open();
|
_outputStream.Open();
|
||||||
}
|
}
|
||||||
@ -47,7 +51,7 @@ namespace Ryujinx.Audio.Backends.SoundIo
|
|||||||
|
|
||||||
public override float GetVolume()
|
public override float GetVolume()
|
||||||
{
|
{
|
||||||
return _outputStream.Volume;
|
return _volume;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void PrepareToClose() { }
|
public override void PrepareToClose() { }
|
||||||
@ -63,7 +67,14 @@ namespace Ryujinx.Audio.Backends.SoundIo
|
|||||||
|
|
||||||
public override void SetVolume(float volume)
|
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()
|
public override void Start()
|
||||||
@ -111,7 +122,9 @@ namespace Ryujinx.Audio.Backends.SoundIo
|
|||||||
|
|
||||||
int channelCount = areas.Length;
|
int channelCount = areas.Length;
|
||||||
|
|
||||||
byte[] samples = new byte[frameCount * bytesPerFrame];
|
using SpanOwner<byte> samplesOwner = SpanOwner<byte>.Rent(frameCount * bytesPerFrame);
|
||||||
|
|
||||||
|
Span<byte> samples = samplesOwner.Span;
|
||||||
|
|
||||||
_ringBuffer.Read(samples, 0, samples.Length);
|
_ringBuffer.Read(samples, 0, samples.Length);
|
||||||
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
using Ryujinx.Common;
|
using Ryujinx.Common;
|
||||||
|
using Ryujinx.Common.Memory;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Buffers;
|
||||||
|
|
||||||
namespace Ryujinx.Audio.Backends.Common
|
namespace Ryujinx.Audio.Backends.Common
|
||||||
{
|
{
|
||||||
@ -12,7 +14,8 @@ namespace Ryujinx.Audio.Backends.Common
|
|||||||
|
|
||||||
private readonly object _lock = new();
|
private readonly object _lock = new();
|
||||||
|
|
||||||
private byte[] _buffer;
|
private MemoryOwner<byte> _bufferOwner;
|
||||||
|
private Memory<byte> _buffer;
|
||||||
private int _size;
|
private int _size;
|
||||||
private int _headOffset;
|
private int _headOffset;
|
||||||
private int _tailOffset;
|
private int _tailOffset;
|
||||||
@ -21,7 +24,8 @@ namespace Ryujinx.Audio.Backends.Common
|
|||||||
|
|
||||||
public DynamicRingBuffer(int initialCapacity = RingBufferAlignment)
|
public DynamicRingBuffer(int initialCapacity = RingBufferAlignment)
|
||||||
{
|
{
|
||||||
_buffer = new byte[initialCapacity];
|
_bufferOwner = MemoryOwner<byte>.RentCleared(initialCapacity);
|
||||||
|
_buffer = _bufferOwner.Memory;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Clear()
|
public void Clear()
|
||||||
@ -33,6 +37,11 @@ namespace Ryujinx.Audio.Backends.Common
|
|||||||
|
|
||||||
public void Clear(int size)
|
public void Clear(int size)
|
||||||
{
|
{
|
||||||
|
if (size == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
lock (_lock)
|
lock (_lock)
|
||||||
{
|
{
|
||||||
if (size > _size)
|
if (size > _size)
|
||||||
@ -40,11 +49,6 @@ namespace Ryujinx.Audio.Backends.Common
|
|||||||
size = _size;
|
size = _size;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (size == 0)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_headOffset = (_headOffset + size) % _buffer.Length;
|
_headOffset = (_headOffset + size) % _buffer.Length;
|
||||||
_size -= size;
|
_size -= size;
|
||||||
|
|
||||||
@ -58,28 +62,31 @@ namespace Ryujinx.Audio.Backends.Common
|
|||||||
|
|
||||||
private void SetCapacityLocked(int capacity)
|
private void SetCapacityLocked(int capacity)
|
||||||
{
|
{
|
||||||
byte[] buffer = new byte[capacity];
|
MemoryOwner<byte> newBufferOwner = MemoryOwner<byte>.RentCleared(capacity);
|
||||||
|
Memory<byte> newBuffer = newBufferOwner.Memory;
|
||||||
|
|
||||||
if (_size > 0)
|
if (_size > 0)
|
||||||
{
|
{
|
||||||
if (_headOffset < _tailOffset)
|
if (_headOffset < _tailOffset)
|
||||||
{
|
{
|
||||||
Buffer.BlockCopy(_buffer, _headOffset, buffer, 0, _size);
|
_buffer.Slice(_headOffset, _size).CopyTo(newBuffer);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Buffer.BlockCopy(_buffer, _headOffset, buffer, 0, _buffer.Length - _headOffset);
|
_buffer[_headOffset..].CopyTo(newBuffer);
|
||||||
Buffer.BlockCopy(_buffer, 0, buffer, _buffer.Length - _headOffset, _tailOffset);
|
_buffer[.._tailOffset].CopyTo(newBuffer[(_buffer.Length - _headOffset)..]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_buffer = buffer;
|
_bufferOwner.Dispose();
|
||||||
|
|
||||||
|
_bufferOwner = newBufferOwner;
|
||||||
|
_buffer = newBuffer;
|
||||||
_headOffset = 0;
|
_headOffset = 0;
|
||||||
_tailOffset = _size;
|
_tailOffset = _size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Write(ReadOnlySpan<byte> buffer, int index, int count)
|
||||||
public void Write<T>(T[] buffer, int index, int count)
|
|
||||||
{
|
{
|
||||||
if (count == 0)
|
if (count == 0)
|
||||||
{
|
{
|
||||||
@ -99,17 +106,17 @@ namespace Ryujinx.Audio.Backends.Common
|
|||||||
|
|
||||||
if (tailLength >= count)
|
if (tailLength >= count)
|
||||||
{
|
{
|
||||||
Buffer.BlockCopy(buffer, index, _buffer, _tailOffset, count);
|
buffer.Slice(index, count).CopyTo(_buffer.Span[_tailOffset..]);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Buffer.BlockCopy(buffer, index, _buffer, _tailOffset, tailLength);
|
buffer.Slice(index, tailLength).CopyTo(_buffer.Span[_tailOffset..]);
|
||||||
Buffer.BlockCopy(buffer, index + tailLength, _buffer, 0, count - tailLength);
|
buffer.Slice(index + tailLength, count - tailLength).CopyTo(_buffer.Span);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Buffer.BlockCopy(buffer, index, _buffer, _tailOffset, count);
|
buffer.Slice(index, count).CopyTo(_buffer.Span[_tailOffset..]);
|
||||||
}
|
}
|
||||||
|
|
||||||
_size += count;
|
_size += count;
|
||||||
@ -117,8 +124,13 @@ namespace Ryujinx.Audio.Backends.Common
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public int Read<T>(T[] buffer, int index, int count)
|
public int Read(Span<byte> buffer, int index, int count)
|
||||||
{
|
{
|
||||||
|
if (count == 0)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
lock (_lock)
|
lock (_lock)
|
||||||
{
|
{
|
||||||
if (count > _size)
|
if (count > _size)
|
||||||
@ -126,14 +138,9 @@ namespace Ryujinx.Audio.Backends.Common
|
|||||||
count = _size;
|
count = _size;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (count == 0)
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_headOffset < _tailOffset)
|
if (_headOffset < _tailOffset)
|
||||||
{
|
{
|
||||||
Buffer.BlockCopy(_buffer, _headOffset, buffer, index, count);
|
_buffer.Span.Slice(_headOffset, count).CopyTo(buffer[index..]);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -141,12 +148,12 @@ namespace Ryujinx.Audio.Backends.Common
|
|||||||
|
|
||||||
if (tailLength >= count)
|
if (tailLength >= count)
|
||||||
{
|
{
|
||||||
Buffer.BlockCopy(_buffer, _headOffset, buffer, index, count);
|
_buffer.Span.Slice(_headOffset, count).CopyTo(buffer[index..]);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Buffer.BlockCopy(_buffer, _headOffset, buffer, index, tailLength);
|
_buffer.Span.Slice(_headOffset, tailLength).CopyTo(buffer[index..]);
|
||||||
Buffer.BlockCopy(_buffer, 0, buffer, index + tailLength, count - tailLength);
|
_buffer.Span[..(count - tailLength)].CopyTo(buffer[(index + tailLength)..]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,6 +16,12 @@ namespace Ryujinx.Audio.Backends.CompatLayer
|
|||||||
|
|
||||||
public static bool IsSupported => true;
|
public static bool IsSupported => true;
|
||||||
|
|
||||||
|
public float Volume
|
||||||
|
{
|
||||||
|
get => _realDriver.Volume;
|
||||||
|
set => _realDriver.Volume = value;
|
||||||
|
}
|
||||||
|
|
||||||
public CompatLayerHardwareDeviceDriver(IHardwareDeviceDriver realDevice)
|
public CompatLayerHardwareDeviceDriver(IHardwareDeviceDriver realDevice)
|
||||||
{
|
{
|
||||||
_realDriver = realDevice;
|
_realDriver = realDevice;
|
||||||
@ -90,7 +96,7 @@ namespace Ryujinx.Audio.Backends.CompatLayer
|
|||||||
throw new ArgumentException("No valid sample format configuration found!");
|
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)
|
if (channelCount == 0)
|
||||||
{
|
{
|
||||||
@ -102,8 +108,6 @@ namespace Ryujinx.Audio.Backends.CompatLayer
|
|||||||
sampleRate = Constants.TargetSampleRate;
|
sampleRate = Constants.TargetSampleRate;
|
||||||
}
|
}
|
||||||
|
|
||||||
volume = Math.Clamp(volume, 0, 1);
|
|
||||||
|
|
||||||
if (!_realDriver.SupportsDirection(direction))
|
if (!_realDriver.SupportsDirection(direction))
|
||||||
{
|
{
|
||||||
if (direction == Direction.Input)
|
if (direction == Direction.Input)
|
||||||
@ -119,7 +123,7 @@ namespace Ryujinx.Audio.Backends.CompatLayer
|
|||||||
SampleFormat hardwareSampleFormat = SelectHardwareSampleFormat(sampleFormat);
|
SampleFormat hardwareSampleFormat = SelectHardwareSampleFormat(sampleFormat);
|
||||||
uint hardwareChannelCount = SelectHardwareChannelCount(channelCount);
|
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)
|
if (hardwareChannelCount == channelCount && hardwareSampleFormat == sampleFormat)
|
||||||
{
|
{
|
||||||
|
@ -31,7 +31,7 @@ namespace Ryujinx.Audio.Backends.CompatLayer
|
|||||||
private const int Minus6dBInQ15 = (int)(0.501f * RawQ15One);
|
private const int Minus6dBInQ15 = (int)(0.501f * RawQ15One);
|
||||||
private const int Minus12dBInQ15 = (int)(0.251f * RawQ15One);
|
private const int Minus12dBInQ15 = (int)(0.251f * RawQ15One);
|
||||||
|
|
||||||
private static readonly int[] _defaultSurroundToStereoCoefficients = new int[4]
|
private static readonly long[] _defaultSurroundToStereoCoefficients = new long[4]
|
||||||
{
|
{
|
||||||
RawQ15One,
|
RawQ15One,
|
||||||
Minus3dBInQ15,
|
Minus3dBInQ15,
|
||||||
@ -39,7 +39,7 @@ namespace Ryujinx.Audio.Backends.CompatLayer
|
|||||||
Minus3dBInQ15,
|
Minus3dBInQ15,
|
||||||
};
|
};
|
||||||
|
|
||||||
private static readonly int[] _defaultStereoToMonoCoefficients = new int[2]
|
private static readonly long[] _defaultStereoToMonoCoefficients = new long[2]
|
||||||
{
|
{
|
||||||
Minus6dBInQ15,
|
Minus6dBInQ15,
|
||||||
Minus6dBInQ15,
|
Minus6dBInQ15,
|
||||||
@ -62,19 +62,23 @@ namespace Ryujinx.Audio.Backends.CompatLayer
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static short DownMixStereoToMono(ReadOnlySpan<int> coefficients, short left, short right)
|
private static short DownMixStereoToMono(ReadOnlySpan<long> coefficients, short left, short right)
|
||||||
{
|
{
|
||||||
return (short)((left * coefficients[0] + right * coefficients[1]) >> Q15Bits);
|
return (short)Math.Clamp((left * coefficients[0] + right * coefficients[1]) >> Q15Bits, short.MinValue, short.MaxValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static short DownMixSurroundToStereo(ReadOnlySpan<int> coefficients, short back, short lfe, short center, short front)
|
private static short DownMixSurroundToStereo(ReadOnlySpan<long> coefficients, short back, short lfe, short center, short front)
|
||||||
{
|
{
|
||||||
return (short)((coefficients[3] * back + coefficients[2] * lfe + coefficients[1] * center + coefficients[0] * front + RawQ15HalfOne) >> Q15Bits);
|
return (short)Math.Clamp(
|
||||||
|
(coefficients[3] * back +
|
||||||
|
coefficients[2] * lfe +
|
||||||
|
coefficients[1] * center +
|
||||||
|
coefficients[0] * front + RawQ15HalfOne) >> Q15Bits, short.MinValue, short.MaxValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static short[] DownMixSurroundToStereo(ReadOnlySpan<int> coefficients, ReadOnlySpan<short> data)
|
private static short[] DownMixSurroundToStereo(ReadOnlySpan<long> coefficients, ReadOnlySpan<short> data)
|
||||||
{
|
{
|
||||||
int samplePerChannelCount = data.Length / SurroundChannelCount;
|
int samplePerChannelCount = data.Length / SurroundChannelCount;
|
||||||
|
|
||||||
@ -94,7 +98,7 @@ namespace Ryujinx.Audio.Backends.CompatLayer
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static short[] DownMixStereoToMono(ReadOnlySpan<int> coefficients, ReadOnlySpan<short> data)
|
private static short[] DownMixStereoToMono(ReadOnlySpan<long> coefficients, ReadOnlySpan<short> data)
|
||||||
{
|
{
|
||||||
int samplePerChannelCount = data.Length / StereoChannelCount;
|
int samplePerChannelCount = data.Length / StereoChannelCount;
|
||||||
|
|
||||||
|
@ -16,15 +16,17 @@ namespace Ryujinx.Audio.Backends.DelayLayer
|
|||||||
|
|
||||||
public ulong SampleDelay48k;
|
public ulong SampleDelay48k;
|
||||||
|
|
||||||
|
public float Volume { get; set; }
|
||||||
|
|
||||||
public DelayLayerHardwareDeviceDriver(IHardwareDeviceDriver realDevice, ulong sampleDelay48k)
|
public DelayLayerHardwareDeviceDriver(IHardwareDeviceDriver realDevice, ulong sampleDelay48k)
|
||||||
{
|
{
|
||||||
_realDriver = realDevice;
|
_realDriver = realDevice;
|
||||||
SampleDelay48k = sampleDelay48k;
|
SampleDelay48k = sampleDelay48k;
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
{
|
{
|
||||||
IHardwareDeviceSession session = _realDriver.OpenDeviceSession(direction, memoryManager, sampleFormat, sampleRate, channelCount, volume);
|
IHardwareDeviceSession session = _realDriver.OpenDeviceSession(direction, memoryManager, sampleFormat, sampleRate, channelCount);
|
||||||
|
|
||||||
if (direction == Direction.Output)
|
if (direction == Direction.Output)
|
||||||
{
|
{
|
||||||
|
@ -14,13 +14,17 @@ namespace Ryujinx.Audio.Backends.Dummy
|
|||||||
|
|
||||||
public static bool IsSupported => true;
|
public static bool IsSupported => true;
|
||||||
|
|
||||||
|
public float Volume { get; set; }
|
||||||
|
|
||||||
public DummyHardwareDeviceDriver()
|
public DummyHardwareDeviceDriver()
|
||||||
{
|
{
|
||||||
_updateRequiredEvent = new ManualResetEvent(false);
|
_updateRequiredEvent = new ManualResetEvent(false);
|
||||||
_pauseEvent = new ManualResetEvent(true);
|
_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)
|
if (sampleRate == 0)
|
||||||
{
|
{
|
||||||
@ -34,7 +38,7 @@ namespace Ryujinx.Audio.Backends.Dummy
|
|||||||
|
|
||||||
if (direction == Direction.Output)
|
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);
|
return new DummyHardwareDeviceSessionInput(this, memoryManager);
|
||||||
|
@ -13,9 +13,9 @@ namespace Ryujinx.Audio.Backends.Dummy
|
|||||||
|
|
||||||
private ulong _playedSampleCount;
|
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;
|
_manager = manager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -166,7 +166,6 @@ namespace Ryujinx.Audio.Input
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="filtered">If true, filter disconnected devices</param>
|
/// <param name="filtered">If true, filter disconnected devices</param>
|
||||||
/// <returns>The list of all audio inputs name</returns>
|
/// <returns>The list of all audio inputs name</returns>
|
||||||
#pragma warning disable CA1822 // Mark member as static
|
|
||||||
public string[] ListAudioIns(bool filtered)
|
public string[] ListAudioIns(bool filtered)
|
||||||
{
|
{
|
||||||
if (filtered)
|
if (filtered)
|
||||||
@ -176,7 +175,6 @@ namespace Ryujinx.Audio.Input
|
|||||||
|
|
||||||
return new[] { Constants.DefaultDeviceInputName };
|
return new[] { Constants.DefaultDeviceInputName };
|
||||||
}
|
}
|
||||||
#pragma warning restore CA1822
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Open a new <see cref="AudioInputSystem"/>.
|
/// 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="inputDeviceName">The input device name wanted by the user</param>
|
||||||
/// <param name="sampleFormat">The sample format to use</param>
|
/// <param name="sampleFormat">The sample format to use</param>
|
||||||
/// <param name="parameter">The user configuration</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>
|
/// <returns>A <see cref="ResultCode"/> reporting an error or a success</returns>
|
||||||
public ResultCode OpenAudioIn(out string outputDeviceName,
|
public ResultCode OpenAudioIn(out string outputDeviceName,
|
||||||
out AudioOutputConfiguration outputConfiguration,
|
out AudioOutputConfiguration outputConfiguration,
|
||||||
@ -197,9 +193,7 @@ namespace Ryujinx.Audio.Input
|
|||||||
IVirtualMemoryManager memoryManager,
|
IVirtualMemoryManager memoryManager,
|
||||||
string inputDeviceName,
|
string inputDeviceName,
|
||||||
SampleFormat sampleFormat,
|
SampleFormat sampleFormat,
|
||||||
ref AudioInputConfiguration parameter,
|
ref AudioInputConfiguration parameter)
|
||||||
ulong appletResourceUserId,
|
|
||||||
uint processHandle)
|
|
||||||
{
|
{
|
||||||
int sessionId = AcquireSessionId();
|
int sessionId = AcquireSessionId();
|
||||||
|
|
||||||
|
@ -13,9 +13,9 @@ namespace Ryujinx.Audio.Integration
|
|||||||
|
|
||||||
private readonly byte[] _buffer;
|
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;
|
_channelCount = channelCount;
|
||||||
_sampleRate = sampleRate;
|
_sampleRate = sampleRate;
|
||||||
_currentBufferTag = 0;
|
_currentBufferTag = 0;
|
||||||
|
@ -16,7 +16,9 @@ namespace Ryujinx.Audio.Integration
|
|||||||
Output,
|
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 GetUpdateRequiredEvent();
|
||||||
ManualResetEvent GetPauseEvent();
|
ManualResetEvent GetPauseEvent();
|
||||||
|
@ -165,12 +165,10 @@ namespace Ryujinx.Audio.Output
|
|||||||
/// Get the list of all audio outputs name.
|
/// Get the list of all audio outputs name.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>The list of all audio outputs name</returns>
|
/// <returns>The list of all audio outputs name</returns>
|
||||||
#pragma warning disable CA1822 // Mark member as static
|
|
||||||
public string[] ListAudioOuts()
|
public string[] ListAudioOuts()
|
||||||
{
|
{
|
||||||
return new[] { Constants.DefaultDeviceOutputName };
|
return new[] { Constants.DefaultDeviceOutputName };
|
||||||
}
|
}
|
||||||
#pragma warning restore CA1822
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Open a new <see cref="AudioOutputSystem"/>.
|
/// 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="inputDeviceName">The input device name wanted by the user</param>
|
||||||
/// <param name="sampleFormat">The sample format to use</param>
|
/// <param name="sampleFormat">The sample format to use</param>
|
||||||
/// <param name="parameter">The user configuration</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>
|
/// <returns>A <see cref="ResultCode"/> reporting an error or a success</returns>
|
||||||
public ResultCode OpenAudioOut(out string outputDeviceName,
|
public ResultCode OpenAudioOut(out string outputDeviceName,
|
||||||
out AudioOutputConfiguration outputConfiguration,
|
out AudioOutputConfiguration outputConfiguration,
|
||||||
@ -192,16 +187,13 @@ namespace Ryujinx.Audio.Output
|
|||||||
IVirtualMemoryManager memoryManager,
|
IVirtualMemoryManager memoryManager,
|
||||||
string inputDeviceName,
|
string inputDeviceName,
|
||||||
SampleFormat sampleFormat,
|
SampleFormat sampleFormat,
|
||||||
ref AudioInputConfiguration parameter,
|
ref AudioInputConfiguration parameter)
|
||||||
ulong appletResourceUserId,
|
|
||||||
uint processHandle,
|
|
||||||
float volume)
|
|
||||||
{
|
{
|
||||||
int sessionId = AcquireSessionId();
|
int sessionId = AcquireSessionId();
|
||||||
|
|
||||||
_sessionsBufferEvents[sessionId].Clear();
|
_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]);
|
AudioOutputSystem audioOut = new(this, _lock, deviceSession, _sessionsBufferEvents[sessionId]);
|
||||||
|
|
||||||
@ -234,41 +226,6 @@ namespace Ryujinx.Audio.Output
|
|||||||
return result;
|
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()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
GC.SuppressFinalize(this);
|
GC.SuppressFinalize(this);
|
||||||
|
@ -25,7 +25,7 @@ namespace Ryujinx.Audio.Renderer.Common
|
|||||||
public ulong Flags;
|
public ulong Flags;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents an error during <see cref="Server.AudioRenderSystem.Update(System.Memory{byte}, System.Memory{byte}, System.ReadOnlyMemory{byte})"/>.
|
/// Represents an error during <see cref="Server.AudioRenderSystem.Update(System.Memory{byte}, System.Memory{byte}, System.Buffers.ReadOnlySequence{byte})"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
public struct ErrorInfo
|
public struct ErrorInfo
|
||||||
|
@ -4,7 +4,7 @@ using System.Runtime.CompilerServices;
|
|||||||
namespace Ryujinx.Audio.Renderer.Common
|
namespace Ryujinx.Audio.Renderer.Common
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Update data header used for input and output of <see cref="Server.AudioRenderSystem.Update(System.Memory{byte}, System.Memory{byte}, System.ReadOnlyMemory{byte})"/>.
|
/// Update data header used for input and output of <see cref="Server.AudioRenderSystem.Update(System.Memory{byte}, System.Memory{byte}, System.Buffers.ReadOnlySequence{byte})"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public struct UpdateDataHeader
|
public struct UpdateDataHeader
|
||||||
{
|
{
|
||||||
|
@ -15,7 +15,6 @@ namespace Ryujinx.Audio.Renderer.Common
|
|||||||
{
|
{
|
||||||
public const int Align = 0x10;
|
public const int Align = 0x10;
|
||||||
public const int BiquadStateOffset = 0x0;
|
public const int BiquadStateOffset = 0x0;
|
||||||
public const int BiquadStateSize = 0x10;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The state of the biquad filters of this voice.
|
/// The state of the biquad filters of this voice.
|
||||||
|
@ -45,7 +45,6 @@ namespace Ryujinx.Audio.Renderer.Dsp
|
|||||||
_event = new ManualResetEvent(false);
|
_event = new ManualResetEvent(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma warning disable IDE0051 // Remove unused private member
|
|
||||||
private static uint GetHardwareChannelCount(IHardwareDeviceDriver deviceDriver)
|
private static uint GetHardwareChannelCount(IHardwareDeviceDriver deviceDriver)
|
||||||
{
|
{
|
||||||
// Get the real device driver (In case the compat layer is on top of it).
|
// 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.
|
// NOTE: We default to stereo as this will get downmixed to mono by the compat layer if it's not compatible.
|
||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
#pragma warning restore IDE0051
|
|
||||||
|
|
||||||
public void Start(IHardwareDeviceDriver deviceDriver, float volume)
|
public void Start(IHardwareDeviceDriver deviceDriver)
|
||||||
{
|
{
|
||||||
OutputDevices = new IHardwareDevice[Constants.AudioRendererSessionCountMax];
|
OutputDevices = new IHardwareDevice[Constants.AudioRendererSessionCountMax];
|
||||||
|
|
||||||
@ -70,7 +68,7 @@ namespace Ryujinx.Audio.Renderer.Dsp
|
|||||||
for (int i = 0; i < OutputDevices.Length; i++)
|
for (int i = 0; i < OutputDevices.Length; i++)
|
||||||
{
|
{
|
||||||
// TODO: Don't hardcode sample rate.
|
// 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>();
|
_mailbox = new Mailbox<MailboxMessage>();
|
||||||
@ -231,33 +229,6 @@ namespace Ryujinx.Audio.Renderer.Dsp
|
|||||||
_mailbox.SendResponse(MailboxMessage.Stop);
|
_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()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
GC.SuppressFinalize(this);
|
GC.SuppressFinalize(this);
|
||||||
@ -269,6 +240,7 @@ namespace Ryujinx.Audio.Renderer.Dsp
|
|||||||
if (disposing)
|
if (disposing)
|
||||||
{
|
{
|
||||||
_event.Dispose();
|
_event.Dispose();
|
||||||
|
_mailbox?.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,10 +16,15 @@ namespace Ryujinx.Audio.Renderer.Dsp
|
|||||||
/// <param name="parameter">The biquad filter parameter</param>
|
/// <param name="parameter">The biquad filter parameter</param>
|
||||||
/// <param name="state">The biquad filter state</param>
|
/// <param name="state">The biquad filter state</param>
|
||||||
/// <param name="outputBuffer">The output buffer to write the result</param>
|
/// <param name="outputBuffer">The output buffer to write the result</param>
|
||||||
/// <param name="inputBuffer">The input buffer to write the result</param>
|
/// <param name="inputBuffer">The input buffer to read the samples from</param>
|
||||||
/// <param name="sampleCount">The count of samples to process</param>
|
/// <param name="sampleCount">The count of samples to process</param>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static void ProcessBiquadFilter(ref BiquadFilterParameter parameter, ref BiquadFilterState state, Span<float> outputBuffer, ReadOnlySpan<float> inputBuffer, uint sampleCount)
|
public static void ProcessBiquadFilter(
|
||||||
|
ref BiquadFilterParameter parameter,
|
||||||
|
ref BiquadFilterState state,
|
||||||
|
Span<float> outputBuffer,
|
||||||
|
ReadOnlySpan<float> inputBuffer,
|
||||||
|
uint sampleCount)
|
||||||
{
|
{
|
||||||
float a0 = FixedPointHelper.ToFloat(parameter.Numerator[0], FixedPointPrecisionForParameter);
|
float a0 = FixedPointHelper.ToFloat(parameter.Numerator[0], FixedPointPrecisionForParameter);
|
||||||
float a1 = FixedPointHelper.ToFloat(parameter.Numerator[1], FixedPointPrecisionForParameter);
|
float a1 = FixedPointHelper.ToFloat(parameter.Numerator[1], FixedPointPrecisionForParameter);
|
||||||
@ -40,6 +45,96 @@ namespace Ryujinx.Audio.Renderer.Dsp
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Apply a single biquad filter and mix the result into the output buffer.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>This is implemented with a direct form 1.</remarks>
|
||||||
|
/// <param name="parameter">The biquad filter parameter</param>
|
||||||
|
/// <param name="state">The biquad filter state</param>
|
||||||
|
/// <param name="outputBuffer">The output buffer to write the result</param>
|
||||||
|
/// <param name="inputBuffer">The input buffer to read the samples from</param>
|
||||||
|
/// <param name="sampleCount">The count of samples to process</param>
|
||||||
|
/// <param name="volume">Mix volume</param>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static void ProcessBiquadFilterAndMix(
|
||||||
|
ref BiquadFilterParameter parameter,
|
||||||
|
ref BiquadFilterState state,
|
||||||
|
Span<float> outputBuffer,
|
||||||
|
ReadOnlySpan<float> inputBuffer,
|
||||||
|
uint sampleCount,
|
||||||
|
float volume)
|
||||||
|
{
|
||||||
|
float a0 = FixedPointHelper.ToFloat(parameter.Numerator[0], FixedPointPrecisionForParameter);
|
||||||
|
float a1 = FixedPointHelper.ToFloat(parameter.Numerator[1], FixedPointPrecisionForParameter);
|
||||||
|
float a2 = FixedPointHelper.ToFloat(parameter.Numerator[2], FixedPointPrecisionForParameter);
|
||||||
|
|
||||||
|
float b1 = FixedPointHelper.ToFloat(parameter.Denominator[0], FixedPointPrecisionForParameter);
|
||||||
|
float b2 = FixedPointHelper.ToFloat(parameter.Denominator[1], FixedPointPrecisionForParameter);
|
||||||
|
|
||||||
|
for (int i = 0; i < sampleCount; i++)
|
||||||
|
{
|
||||||
|
float input = inputBuffer[i];
|
||||||
|
float output = input * a0 + state.State0 * a1 + state.State1 * a2 + state.State2 * b1 + state.State3 * b2;
|
||||||
|
|
||||||
|
state.State1 = state.State0;
|
||||||
|
state.State0 = input;
|
||||||
|
state.State3 = state.State2;
|
||||||
|
state.State2 = output;
|
||||||
|
|
||||||
|
outputBuffer[i] += FloatingPointHelper.MultiplyRoundUp(output, volume);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Apply a single biquad filter and mix the result into the output buffer with volume ramp.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>This is implemented with a direct form 1.</remarks>
|
||||||
|
/// <param name="parameter">The biquad filter parameter</param>
|
||||||
|
/// <param name="state">The biquad filter state</param>
|
||||||
|
/// <param name="outputBuffer">The output buffer to write the result</param>
|
||||||
|
/// <param name="inputBuffer">The input buffer to read the samples from</param>
|
||||||
|
/// <param name="sampleCount">The count of samples to process</param>
|
||||||
|
/// <param name="volume">Initial mix volume</param>
|
||||||
|
/// <param name="ramp">Volume increment step</param>
|
||||||
|
/// <returns>Last filtered sample value</returns>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static float ProcessBiquadFilterAndMixRamp(
|
||||||
|
ref BiquadFilterParameter parameter,
|
||||||
|
ref BiquadFilterState state,
|
||||||
|
Span<float> outputBuffer,
|
||||||
|
ReadOnlySpan<float> inputBuffer,
|
||||||
|
uint sampleCount,
|
||||||
|
float volume,
|
||||||
|
float ramp)
|
||||||
|
{
|
||||||
|
float a0 = FixedPointHelper.ToFloat(parameter.Numerator[0], FixedPointPrecisionForParameter);
|
||||||
|
float a1 = FixedPointHelper.ToFloat(parameter.Numerator[1], FixedPointPrecisionForParameter);
|
||||||
|
float a2 = FixedPointHelper.ToFloat(parameter.Numerator[2], FixedPointPrecisionForParameter);
|
||||||
|
|
||||||
|
float b1 = FixedPointHelper.ToFloat(parameter.Denominator[0], FixedPointPrecisionForParameter);
|
||||||
|
float b2 = FixedPointHelper.ToFloat(parameter.Denominator[1], FixedPointPrecisionForParameter);
|
||||||
|
|
||||||
|
float mixState = 0f;
|
||||||
|
|
||||||
|
for (int i = 0; i < sampleCount; i++)
|
||||||
|
{
|
||||||
|
float input = inputBuffer[i];
|
||||||
|
float output = input * a0 + state.State0 * a1 + state.State1 * a2 + state.State2 * b1 + state.State3 * b2;
|
||||||
|
|
||||||
|
state.State1 = state.State0;
|
||||||
|
state.State0 = input;
|
||||||
|
state.State3 = state.State2;
|
||||||
|
state.State2 = output;
|
||||||
|
|
||||||
|
mixState = FloatingPointHelper.MultiplyRoundUp(output, volume);
|
||||||
|
|
||||||
|
outputBuffer[i] += mixState;
|
||||||
|
volume += ramp;
|
||||||
|
}
|
||||||
|
|
||||||
|
return mixState;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Apply multiple biquad filter.
|
/// Apply multiple biquad filter.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -47,10 +142,15 @@ namespace Ryujinx.Audio.Renderer.Dsp
|
|||||||
/// <param name="parameters">The biquad filter parameter</param>
|
/// <param name="parameters">The biquad filter parameter</param>
|
||||||
/// <param name="states">The biquad filter state</param>
|
/// <param name="states">The biquad filter state</param>
|
||||||
/// <param name="outputBuffer">The output buffer to write the result</param>
|
/// <param name="outputBuffer">The output buffer to write the result</param>
|
||||||
/// <param name="inputBuffer">The input buffer to write the result</param>
|
/// <param name="inputBuffer">The input buffer to read the samples from</param>
|
||||||
/// <param name="sampleCount">The count of samples to process</param>
|
/// <param name="sampleCount">The count of samples to process</param>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static void ProcessBiquadFilter(ReadOnlySpan<BiquadFilterParameter> parameters, Span<BiquadFilterState> states, Span<float> outputBuffer, ReadOnlySpan<float> inputBuffer, uint sampleCount)
|
public static void ProcessBiquadFilter(
|
||||||
|
ReadOnlySpan<BiquadFilterParameter> parameters,
|
||||||
|
Span<BiquadFilterState> states,
|
||||||
|
Span<float> outputBuffer,
|
||||||
|
ReadOnlySpan<float> inputBuffer,
|
||||||
|
uint sampleCount)
|
||||||
{
|
{
|
||||||
for (int stageIndex = 0; stageIndex < parameters.Length; stageIndex++)
|
for (int stageIndex = 0; stageIndex < parameters.Length; stageIndex++)
|
||||||
{
|
{
|
||||||
@ -67,7 +167,7 @@ namespace Ryujinx.Audio.Renderer.Dsp
|
|||||||
|
|
||||||
for (int i = 0; i < sampleCount; i++)
|
for (int i = 0; i < sampleCount; i++)
|
||||||
{
|
{
|
||||||
float input = inputBuffer[i];
|
float input = stageIndex != 0 ? outputBuffer[i] : inputBuffer[i];
|
||||||
float output = input * a0 + state.State0 * a1 + state.State1 * a2 + state.State2 * b1 + state.State3 * b2;
|
float output = input * a0 + state.State0 * a1 + state.State1 * a2 + state.State2 * b1 + state.State3 * b2;
|
||||||
|
|
||||||
state.State1 = state.State0;
|
state.State1 = state.State0;
|
||||||
@ -79,5 +179,129 @@ namespace Ryujinx.Audio.Renderer.Dsp
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Apply double biquad filter and mix the result into the output buffer.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>This is implemented with a direct form 1.</remarks>
|
||||||
|
/// <param name="parameters">The biquad filter parameter</param>
|
||||||
|
/// <param name="states">The biquad filter state</param>
|
||||||
|
/// <param name="outputBuffer">The output buffer to write the result</param>
|
||||||
|
/// <param name="inputBuffer">The input buffer to read the samples from</param>
|
||||||
|
/// <param name="sampleCount">The count of samples to process</param>
|
||||||
|
/// <param name="volume">Mix volume</param>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static void ProcessDoubleBiquadFilterAndMix(
|
||||||
|
ref BiquadFilterParameter parameter0,
|
||||||
|
ref BiquadFilterParameter parameter1,
|
||||||
|
ref BiquadFilterState state0,
|
||||||
|
ref BiquadFilterState state1,
|
||||||
|
Span<float> outputBuffer,
|
||||||
|
ReadOnlySpan<float> inputBuffer,
|
||||||
|
uint sampleCount,
|
||||||
|
float volume)
|
||||||
|
{
|
||||||
|
float a00 = FixedPointHelper.ToFloat(parameter0.Numerator[0], FixedPointPrecisionForParameter);
|
||||||
|
float a10 = FixedPointHelper.ToFloat(parameter0.Numerator[1], FixedPointPrecisionForParameter);
|
||||||
|
float a20 = FixedPointHelper.ToFloat(parameter0.Numerator[2], FixedPointPrecisionForParameter);
|
||||||
|
|
||||||
|
float b10 = FixedPointHelper.ToFloat(parameter0.Denominator[0], FixedPointPrecisionForParameter);
|
||||||
|
float b20 = FixedPointHelper.ToFloat(parameter0.Denominator[1], FixedPointPrecisionForParameter);
|
||||||
|
|
||||||
|
float a01 = FixedPointHelper.ToFloat(parameter1.Numerator[0], FixedPointPrecisionForParameter);
|
||||||
|
float a11 = FixedPointHelper.ToFloat(parameter1.Numerator[1], FixedPointPrecisionForParameter);
|
||||||
|
float a21 = FixedPointHelper.ToFloat(parameter1.Numerator[2], FixedPointPrecisionForParameter);
|
||||||
|
|
||||||
|
float b11 = FixedPointHelper.ToFloat(parameter1.Denominator[0], FixedPointPrecisionForParameter);
|
||||||
|
float b21 = FixedPointHelper.ToFloat(parameter1.Denominator[1], FixedPointPrecisionForParameter);
|
||||||
|
|
||||||
|
for (int i = 0; i < sampleCount; i++)
|
||||||
|
{
|
||||||
|
float input = inputBuffer[i];
|
||||||
|
float output = input * a00 + state0.State0 * a10 + state0.State1 * a20 + state0.State2 * b10 + state0.State3 * b20;
|
||||||
|
|
||||||
|
state0.State1 = state0.State0;
|
||||||
|
state0.State0 = input;
|
||||||
|
state0.State3 = state0.State2;
|
||||||
|
state0.State2 = output;
|
||||||
|
|
||||||
|
input = output;
|
||||||
|
output = input * a01 + state1.State0 * a11 + state1.State1 * a21 + state1.State2 * b11 + state1.State3 * b21;
|
||||||
|
|
||||||
|
state1.State1 = state1.State0;
|
||||||
|
state1.State0 = input;
|
||||||
|
state1.State3 = state1.State2;
|
||||||
|
state1.State2 = output;
|
||||||
|
|
||||||
|
outputBuffer[i] += FloatingPointHelper.MultiplyRoundUp(output, volume);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Apply double biquad filter and mix the result into the output buffer with volume ramp.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>This is implemented with a direct form 1.</remarks>
|
||||||
|
/// <param name="parameters">The biquad filter parameter</param>
|
||||||
|
/// <param name="states">The biquad filter state</param>
|
||||||
|
/// <param name="outputBuffer">The output buffer to write the result</param>
|
||||||
|
/// <param name="inputBuffer">The input buffer to read the samples from</param>
|
||||||
|
/// <param name="sampleCount">The count of samples to process</param>
|
||||||
|
/// <param name="volume">Initial mix volume</param>
|
||||||
|
/// <param name="ramp">Volume increment step</param>
|
||||||
|
/// <returns>Last filtered sample value</returns>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static float ProcessDoubleBiquadFilterAndMixRamp(
|
||||||
|
ref BiquadFilterParameter parameter0,
|
||||||
|
ref BiquadFilterParameter parameter1,
|
||||||
|
ref BiquadFilterState state0,
|
||||||
|
ref BiquadFilterState state1,
|
||||||
|
Span<float> outputBuffer,
|
||||||
|
ReadOnlySpan<float> inputBuffer,
|
||||||
|
uint sampleCount,
|
||||||
|
float volume,
|
||||||
|
float ramp)
|
||||||
|
{
|
||||||
|
float a00 = FixedPointHelper.ToFloat(parameter0.Numerator[0], FixedPointPrecisionForParameter);
|
||||||
|
float a10 = FixedPointHelper.ToFloat(parameter0.Numerator[1], FixedPointPrecisionForParameter);
|
||||||
|
float a20 = FixedPointHelper.ToFloat(parameter0.Numerator[2], FixedPointPrecisionForParameter);
|
||||||
|
|
||||||
|
float b10 = FixedPointHelper.ToFloat(parameter0.Denominator[0], FixedPointPrecisionForParameter);
|
||||||
|
float b20 = FixedPointHelper.ToFloat(parameter0.Denominator[1], FixedPointPrecisionForParameter);
|
||||||
|
|
||||||
|
float a01 = FixedPointHelper.ToFloat(parameter1.Numerator[0], FixedPointPrecisionForParameter);
|
||||||
|
float a11 = FixedPointHelper.ToFloat(parameter1.Numerator[1], FixedPointPrecisionForParameter);
|
||||||
|
float a21 = FixedPointHelper.ToFloat(parameter1.Numerator[2], FixedPointPrecisionForParameter);
|
||||||
|
|
||||||
|
float b11 = FixedPointHelper.ToFloat(parameter1.Denominator[0], FixedPointPrecisionForParameter);
|
||||||
|
float b21 = FixedPointHelper.ToFloat(parameter1.Denominator[1], FixedPointPrecisionForParameter);
|
||||||
|
|
||||||
|
float mixState = 0f;
|
||||||
|
|
||||||
|
for (int i = 0; i < sampleCount; i++)
|
||||||
|
{
|
||||||
|
float input = inputBuffer[i];
|
||||||
|
float output = input * a00 + state0.State0 * a10 + state0.State1 * a20 + state0.State2 * b10 + state0.State3 * b20;
|
||||||
|
|
||||||
|
state0.State1 = state0.State0;
|
||||||
|
state0.State0 = input;
|
||||||
|
state0.State3 = state0.State2;
|
||||||
|
state0.State2 = output;
|
||||||
|
|
||||||
|
input = output;
|
||||||
|
output = input * a01 + state1.State0 * a11 + state1.State1 * a21 + state1.State2 * b11 + state1.State3 * b21;
|
||||||
|
|
||||||
|
state1.State1 = state1.State0;
|
||||||
|
state1.State0 = input;
|
||||||
|
state1.State3 = state1.State2;
|
||||||
|
state1.State2 = output;
|
||||||
|
|
||||||
|
mixState = FloatingPointHelper.MultiplyRoundUp(output, volume);
|
||||||
|
|
||||||
|
outputBuffer[i] += mixState;
|
||||||
|
volume += ramp;
|
||||||
|
}
|
||||||
|
|
||||||
|
return mixState;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,123 @@
|
|||||||
|
using Ryujinx.Audio.Renderer.Common;
|
||||||
|
using Ryujinx.Audio.Renderer.Dsp.State;
|
||||||
|
using Ryujinx.Audio.Renderer.Parameter;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||||
|
{
|
||||||
|
public class BiquadFilterAndMixCommand : ICommand
|
||||||
|
{
|
||||||
|
public bool Enabled { get; set; }
|
||||||
|
|
||||||
|
public int NodeId { get; }
|
||||||
|
|
||||||
|
public CommandType CommandType => CommandType.BiquadFilterAndMix;
|
||||||
|
|
||||||
|
public uint EstimatedProcessingTime { get; set; }
|
||||||
|
|
||||||
|
public ushort InputBufferIndex { get; }
|
||||||
|
public ushort OutputBufferIndex { get; }
|
||||||
|
|
||||||
|
private BiquadFilterParameter _parameter;
|
||||||
|
|
||||||
|
public Memory<BiquadFilterState> BiquadFilterState { get; }
|
||||||
|
public Memory<BiquadFilterState> PreviousBiquadFilterState { get; }
|
||||||
|
|
||||||
|
public Memory<VoiceUpdateState> State { get; }
|
||||||
|
|
||||||
|
public int LastSampleIndex { get; }
|
||||||
|
|
||||||
|
public float Volume0 { get; }
|
||||||
|
public float Volume1 { get; }
|
||||||
|
|
||||||
|
public bool NeedInitialization { get; }
|
||||||
|
public bool HasVolumeRamp { get; }
|
||||||
|
public bool IsFirstMixBuffer { get; }
|
||||||
|
|
||||||
|
public BiquadFilterAndMixCommand(
|
||||||
|
float volume0,
|
||||||
|
float volume1,
|
||||||
|
uint inputBufferIndex,
|
||||||
|
uint outputBufferIndex,
|
||||||
|
int lastSampleIndex,
|
||||||
|
Memory<VoiceUpdateState> state,
|
||||||
|
ref BiquadFilterParameter filter,
|
||||||
|
Memory<BiquadFilterState> biquadFilterState,
|
||||||
|
Memory<BiquadFilterState> previousBiquadFilterState,
|
||||||
|
bool needInitialization,
|
||||||
|
bool hasVolumeRamp,
|
||||||
|
bool isFirstMixBuffer,
|
||||||
|
int nodeId)
|
||||||
|
{
|
||||||
|
Enabled = true;
|
||||||
|
NodeId = nodeId;
|
||||||
|
|
||||||
|
InputBufferIndex = (ushort)inputBufferIndex;
|
||||||
|
OutputBufferIndex = (ushort)outputBufferIndex;
|
||||||
|
|
||||||
|
_parameter = filter;
|
||||||
|
BiquadFilterState = biquadFilterState;
|
||||||
|
PreviousBiquadFilterState = previousBiquadFilterState;
|
||||||
|
|
||||||
|
State = state;
|
||||||
|
LastSampleIndex = lastSampleIndex;
|
||||||
|
|
||||||
|
Volume0 = volume0;
|
||||||
|
Volume1 = volume1;
|
||||||
|
|
||||||
|
NeedInitialization = needInitialization;
|
||||||
|
HasVolumeRamp = hasVolumeRamp;
|
||||||
|
IsFirstMixBuffer = isFirstMixBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Process(CommandList context)
|
||||||
|
{
|
||||||
|
ReadOnlySpan<float> inputBuffer = context.GetBuffer(InputBufferIndex);
|
||||||
|
Span<float> outputBuffer = context.GetBuffer(OutputBufferIndex);
|
||||||
|
|
||||||
|
if (NeedInitialization)
|
||||||
|
{
|
||||||
|
// If there is no previous state, initialize to zero.
|
||||||
|
|
||||||
|
BiquadFilterState.Span[0] = new BiquadFilterState();
|
||||||
|
}
|
||||||
|
else if (IsFirstMixBuffer)
|
||||||
|
{
|
||||||
|
// This is the first buffer, set previous state to current state.
|
||||||
|
|
||||||
|
PreviousBiquadFilterState.Span[0] = BiquadFilterState.Span[0];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Rewind the current state by copying back the previous state.
|
||||||
|
|
||||||
|
BiquadFilterState.Span[0] = PreviousBiquadFilterState.Span[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (HasVolumeRamp)
|
||||||
|
{
|
||||||
|
float volume = Volume0;
|
||||||
|
float ramp = (Volume1 - Volume0) / (int)context.SampleCount;
|
||||||
|
|
||||||
|
State.Span[0].LastSamples[LastSampleIndex] = BiquadFilterHelper.ProcessBiquadFilterAndMixRamp(
|
||||||
|
ref _parameter,
|
||||||
|
ref BiquadFilterState.Span[0],
|
||||||
|
outputBuffer,
|
||||||
|
inputBuffer,
|
||||||
|
context.SampleCount,
|
||||||
|
volume,
|
||||||
|
ramp);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BiquadFilterHelper.ProcessBiquadFilterAndMix(
|
||||||
|
ref _parameter,
|
||||||
|
ref BiquadFilterState.Span[0],
|
||||||
|
outputBuffer,
|
||||||
|
inputBuffer,
|
||||||
|
context.SampleCount,
|
||||||
|
Volume1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -30,8 +30,10 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
|||||||
CopyMixBuffer,
|
CopyMixBuffer,
|
||||||
LimiterVersion1,
|
LimiterVersion1,
|
||||||
LimiterVersion2,
|
LimiterVersion2,
|
||||||
GroupedBiquadFilter,
|
MultiTapBiquadFilter,
|
||||||
CaptureBuffer,
|
CaptureBuffer,
|
||||||
Compressor,
|
Compressor,
|
||||||
|
BiquadFilterAndMix,
|
||||||
|
MultiTapBiquadFilterAndMix,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
using Ryujinx.Audio.Renderer.Dsp.Effect;
|
using Ryujinx.Audio.Renderer.Dsp.Effect;
|
||||||
using Ryujinx.Audio.Renderer.Dsp.State;
|
using Ryujinx.Audio.Renderer.Dsp.State;
|
||||||
|
using Ryujinx.Audio.Renderer.Parameter;
|
||||||
using Ryujinx.Audio.Renderer.Parameter.Effect;
|
using Ryujinx.Audio.Renderer.Parameter.Effect;
|
||||||
using Ryujinx.Audio.Renderer.Server.Effect;
|
using Ryujinx.Audio.Renderer.Server.Effect;
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace Ryujinx.Audio.Renderer.Dsp.Command
|
namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||||
{
|
{
|
||||||
@ -21,18 +23,20 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
|||||||
|
|
||||||
public CompressorParameter Parameter => _parameter;
|
public CompressorParameter Parameter => _parameter;
|
||||||
public Memory<CompressorState> State { get; }
|
public Memory<CompressorState> State { get; }
|
||||||
|
public Memory<EffectResultState> ResultState { get; }
|
||||||
public ushort[] OutputBufferIndices { get; }
|
public ushort[] OutputBufferIndices { get; }
|
||||||
public ushort[] InputBufferIndices { get; }
|
public ushort[] InputBufferIndices { get; }
|
||||||
public bool IsEffectEnabled { get; }
|
public bool IsEffectEnabled { get; }
|
||||||
|
|
||||||
private CompressorParameter _parameter;
|
private CompressorParameter _parameter;
|
||||||
|
|
||||||
public CompressorCommand(uint bufferOffset, CompressorParameter parameter, Memory<CompressorState> state, bool isEnabled, int nodeId)
|
public CompressorCommand(uint bufferOffset, CompressorParameter parameter, Memory<CompressorState> state, Memory<EffectResultState> resultState, bool isEnabled, int nodeId)
|
||||||
{
|
{
|
||||||
Enabled = true;
|
Enabled = true;
|
||||||
NodeId = nodeId;
|
NodeId = nodeId;
|
||||||
_parameter = parameter;
|
_parameter = parameter;
|
||||||
State = state;
|
State = state;
|
||||||
|
ResultState = resultState;
|
||||||
|
|
||||||
IsEffectEnabled = isEnabled;
|
IsEffectEnabled = isEnabled;
|
||||||
|
|
||||||
@ -71,9 +75,16 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
|||||||
|
|
||||||
if (IsEffectEnabled && _parameter.IsChannelCountValid())
|
if (IsEffectEnabled && _parameter.IsChannelCountValid())
|
||||||
{
|
{
|
||||||
Span<IntPtr> inputBuffers = stackalloc IntPtr[Parameter.ChannelCount];
|
if (!ResultState.IsEmpty && _parameter.StatisticsReset)
|
||||||
Span<IntPtr> outputBuffers = stackalloc IntPtr[Parameter.ChannelCount];
|
{
|
||||||
Span<float> channelInput = stackalloc float[Parameter.ChannelCount];
|
ref CompressorStatistics statistics = ref MemoryMarshal.Cast<byte, CompressorStatistics>(ResultState.Span[0].SpecificData)[0];
|
||||||
|
|
||||||
|
statistics.Reset(_parameter.ChannelCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
Span<IntPtr> inputBuffers = stackalloc IntPtr[_parameter.ChannelCount];
|
||||||
|
Span<IntPtr> outputBuffers = stackalloc IntPtr[_parameter.ChannelCount];
|
||||||
|
Span<float> channelInput = stackalloc float[_parameter.ChannelCount];
|
||||||
ExponentialMovingAverage inputMovingAverage = state.InputMovingAverage;
|
ExponentialMovingAverage inputMovingAverage = state.InputMovingAverage;
|
||||||
float unknown4 = state.Unknown4;
|
float unknown4 = state.Unknown4;
|
||||||
ExponentialMovingAverage compressionGainAverage = state.CompressionGainAverage;
|
ExponentialMovingAverage compressionGainAverage = state.CompressionGainAverage;
|
||||||
@ -92,7 +103,8 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
|||||||
channelInput[channelIndex] = *((float*)inputBuffers[channelIndex] + sampleIndex);
|
channelInput[channelIndex] = *((float*)inputBuffers[channelIndex] + sampleIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
float newMean = inputMovingAverage.Update(FloatingPointHelper.MeanSquare(channelInput), _parameter.InputGain);
|
float mean = FloatingPointHelper.MeanSquare(channelInput);
|
||||||
|
float newMean = inputMovingAverage.Update(mean, _parameter.InputGain);
|
||||||
float y = FloatingPointHelper.Log10(newMean) * 10.0f;
|
float y = FloatingPointHelper.Log10(newMean) * 10.0f;
|
||||||
float z = 1.0f;
|
float z = 1.0f;
|
||||||
|
|
||||||
@ -111,7 +123,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
|||||||
|
|
||||||
if (y >= state.Unknown14)
|
if (y >= state.Unknown14)
|
||||||
{
|
{
|
||||||
tmpGain = ((1.0f / Parameter.Ratio) - 1.0f) * (y - Parameter.Threshold);
|
tmpGain = ((1.0f / _parameter.Ratio) - 1.0f) * (y - _parameter.Threshold);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -126,7 +138,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
|||||||
|
|
||||||
if ((unknown4 - z) <= 0.08f)
|
if ((unknown4 - z) <= 0.08f)
|
||||||
{
|
{
|
||||||
compressionEmaAlpha = Parameter.ReleaseCoefficient;
|
compressionEmaAlpha = _parameter.ReleaseCoefficient;
|
||||||
|
|
||||||
if ((unknown4 - z) >= -0.08f)
|
if ((unknown4 - z) >= -0.08f)
|
||||||
{
|
{
|
||||||
@ -140,18 +152,31 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
compressionEmaAlpha = Parameter.AttackCoefficient;
|
compressionEmaAlpha = _parameter.AttackCoefficient;
|
||||||
}
|
}
|
||||||
|
|
||||||
float compressionGain = compressionGainAverage.Update(z, compressionEmaAlpha);
|
float compressionGain = compressionGainAverage.Update(z, compressionEmaAlpha);
|
||||||
|
|
||||||
for (int channelIndex = 0; channelIndex < Parameter.ChannelCount; channelIndex++)
|
for (int channelIndex = 0; channelIndex < _parameter.ChannelCount; channelIndex++)
|
||||||
{
|
{
|
||||||
*((float*)outputBuffers[channelIndex] + sampleIndex) = channelInput[channelIndex] * compressionGain * state.OutputGain;
|
*((float*)outputBuffers[channelIndex] + sampleIndex) = channelInput[channelIndex] * compressionGain * state.OutputGain;
|
||||||
}
|
}
|
||||||
|
|
||||||
unknown4 = unknown4New;
|
unknown4 = unknown4New;
|
||||||
previousCompressionEmaAlpha = compressionEmaAlpha;
|
previousCompressionEmaAlpha = compressionEmaAlpha;
|
||||||
|
|
||||||
|
if (!ResultState.IsEmpty)
|
||||||
|
{
|
||||||
|
ref CompressorStatistics statistics = ref MemoryMarshal.Cast<byte, CompressorStatistics>(ResultState.Span[0].SpecificData)[0];
|
||||||
|
|
||||||
|
statistics.MinimumGain = MathF.Min(statistics.MinimumGain, compressionGain * state.OutputGain);
|
||||||
|
statistics.MaximumMean = MathF.Max(statistics.MaximumMean, mean);
|
||||||
|
|
||||||
|
for (int channelIndex = 0; channelIndex < _parameter.ChannelCount; channelIndex++)
|
||||||
|
{
|
||||||
|
statistics.LastSamples[channelIndex] = MathF.Abs(channelInput[channelIndex] * (1f / 32768f));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
state.InputMovingAverage = inputMovingAverage;
|
state.InputMovingAverage = inputMovingAverage;
|
||||||
@ -161,7 +186,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
for (int i = 0; i < Parameter.ChannelCount; i++)
|
for (int i = 0; i < _parameter.ChannelCount; i++)
|
||||||
{
|
{
|
||||||
if (InputBufferIndices[i] != OutputBufferIndices[i])
|
if (InputBufferIndices[i] != OutputBufferIndices[i])
|
||||||
{
|
{
|
||||||
|
@ -38,10 +38,10 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
|||||||
InputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
|
InputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
|
||||||
OutputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
|
OutputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
|
||||||
|
|
||||||
for (int i = 0; i < Parameter.ChannelCount; i++)
|
for (int i = 0; i < _parameter.ChannelCount; i++)
|
||||||
{
|
{
|
||||||
InputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Input[i]);
|
InputBufferIndices[i] = (ushort)(bufferOffset + _parameter.Input[i]);
|
||||||
OutputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Output[i]);
|
OutputBufferIndices[i] = (ushort)(bufferOffset + _parameter.Output[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,11 +51,11 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
|||||||
|
|
||||||
if (IsEffectEnabled)
|
if (IsEffectEnabled)
|
||||||
{
|
{
|
||||||
if (Parameter.Status == UsageState.Invalid)
|
if (_parameter.Status == UsageState.Invalid)
|
||||||
{
|
{
|
||||||
state = new LimiterState(ref _parameter, WorkBuffer);
|
state = new LimiterState(ref _parameter, WorkBuffer);
|
||||||
}
|
}
|
||||||
else if (Parameter.Status == UsageState.New)
|
else if (_parameter.Status == UsageState.New)
|
||||||
{
|
{
|
||||||
LimiterState.UpdateParameter(ref _parameter);
|
LimiterState.UpdateParameter(ref _parameter);
|
||||||
}
|
}
|
||||||
@ -66,56 +66,56 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
|||||||
|
|
||||||
private unsafe void ProcessLimiter(CommandList context, ref LimiterState state)
|
private unsafe void ProcessLimiter(CommandList context, ref LimiterState state)
|
||||||
{
|
{
|
||||||
Debug.Assert(Parameter.IsChannelCountValid());
|
Debug.Assert(_parameter.IsChannelCountValid());
|
||||||
|
|
||||||
if (IsEffectEnabled && Parameter.IsChannelCountValid())
|
if (IsEffectEnabled && _parameter.IsChannelCountValid())
|
||||||
{
|
{
|
||||||
Span<IntPtr> inputBuffers = stackalloc IntPtr[Parameter.ChannelCount];
|
Span<IntPtr> inputBuffers = stackalloc IntPtr[_parameter.ChannelCount];
|
||||||
Span<IntPtr> outputBuffers = stackalloc IntPtr[Parameter.ChannelCount];
|
Span<IntPtr> outputBuffers = stackalloc IntPtr[_parameter.ChannelCount];
|
||||||
|
|
||||||
for (int i = 0; i < Parameter.ChannelCount; i++)
|
for (int i = 0; i < _parameter.ChannelCount; i++)
|
||||||
{
|
{
|
||||||
inputBuffers[i] = context.GetBufferPointer(InputBufferIndices[i]);
|
inputBuffers[i] = context.GetBufferPointer(InputBufferIndices[i]);
|
||||||
outputBuffers[i] = context.GetBufferPointer(OutputBufferIndices[i]);
|
outputBuffers[i] = context.GetBufferPointer(OutputBufferIndices[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int channelIndex = 0; channelIndex < Parameter.ChannelCount; channelIndex++)
|
for (int channelIndex = 0; channelIndex < _parameter.ChannelCount; channelIndex++)
|
||||||
{
|
{
|
||||||
for (int sampleIndex = 0; sampleIndex < context.SampleCount; sampleIndex++)
|
for (int sampleIndex = 0; sampleIndex < context.SampleCount; sampleIndex++)
|
||||||
{
|
{
|
||||||
float rawInputSample = *((float*)inputBuffers[channelIndex] + sampleIndex);
|
float rawInputSample = *((float*)inputBuffers[channelIndex] + sampleIndex);
|
||||||
|
|
||||||
float inputSample = (rawInputSample / short.MaxValue) * Parameter.InputGain;
|
float inputSample = (rawInputSample / short.MaxValue) * _parameter.InputGain;
|
||||||
|
|
||||||
float sampleInputMax = Math.Abs(inputSample);
|
float sampleInputMax = Math.Abs(inputSample);
|
||||||
|
|
||||||
float inputCoefficient = Parameter.ReleaseCoefficient;
|
float inputCoefficient = _parameter.ReleaseCoefficient;
|
||||||
|
|
||||||
if (sampleInputMax > state.DetectorAverage[channelIndex].Read())
|
if (sampleInputMax > state.DetectorAverage[channelIndex].Read())
|
||||||
{
|
{
|
||||||
inputCoefficient = Parameter.AttackCoefficient;
|
inputCoefficient = _parameter.AttackCoefficient;
|
||||||
}
|
}
|
||||||
|
|
||||||
float detectorValue = state.DetectorAverage[channelIndex].Update(sampleInputMax, inputCoefficient);
|
float detectorValue = state.DetectorAverage[channelIndex].Update(sampleInputMax, inputCoefficient);
|
||||||
float attenuation = 1.0f;
|
float attenuation = 1.0f;
|
||||||
|
|
||||||
if (detectorValue > Parameter.Threshold)
|
if (detectorValue > _parameter.Threshold)
|
||||||
{
|
{
|
||||||
attenuation = Parameter.Threshold / detectorValue;
|
attenuation = _parameter.Threshold / detectorValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
float outputCoefficient = Parameter.ReleaseCoefficient;
|
float outputCoefficient = _parameter.ReleaseCoefficient;
|
||||||
|
|
||||||
if (state.CompressionGainAverage[channelIndex].Read() > attenuation)
|
if (state.CompressionGainAverage[channelIndex].Read() > attenuation)
|
||||||
{
|
{
|
||||||
outputCoefficient = Parameter.AttackCoefficient;
|
outputCoefficient = _parameter.AttackCoefficient;
|
||||||
}
|
}
|
||||||
|
|
||||||
float compressionGain = state.CompressionGainAverage[channelIndex].Update(attenuation, outputCoefficient);
|
float compressionGain = state.CompressionGainAverage[channelIndex].Update(attenuation, outputCoefficient);
|
||||||
|
|
||||||
ref float delayedSample = ref state.DelayedSampleBuffer[channelIndex * Parameter.DelayBufferSampleCountMax + state.DelayedSampleBufferPosition[channelIndex]];
|
ref float delayedSample = ref state.DelayedSampleBuffer[channelIndex * _parameter.DelayBufferSampleCountMax + state.DelayedSampleBufferPosition[channelIndex]];
|
||||||
|
|
||||||
float outputSample = delayedSample * compressionGain * Parameter.OutputGain;
|
float outputSample = delayedSample * compressionGain * _parameter.OutputGain;
|
||||||
|
|
||||||
*((float*)outputBuffers[channelIndex] + sampleIndex) = outputSample * short.MaxValue;
|
*((float*)outputBuffers[channelIndex] + sampleIndex) = outputSample * short.MaxValue;
|
||||||
|
|
||||||
@ -123,16 +123,16 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
|||||||
|
|
||||||
state.DelayedSampleBufferPosition[channelIndex]++;
|
state.DelayedSampleBufferPosition[channelIndex]++;
|
||||||
|
|
||||||
while (state.DelayedSampleBufferPosition[channelIndex] >= Parameter.DelayBufferSampleCountMin)
|
while (state.DelayedSampleBufferPosition[channelIndex] >= _parameter.DelayBufferSampleCountMin)
|
||||||
{
|
{
|
||||||
state.DelayedSampleBufferPosition[channelIndex] -= Parameter.DelayBufferSampleCountMin;
|
state.DelayedSampleBufferPosition[channelIndex] -= _parameter.DelayBufferSampleCountMin;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
for (int i = 0; i < Parameter.ChannelCount; i++)
|
for (int i = 0; i < _parameter.ChannelCount; i++)
|
||||||
{
|
{
|
||||||
if (InputBufferIndices[i] != OutputBufferIndices[i])
|
if (InputBufferIndices[i] != OutputBufferIndices[i])
|
||||||
{
|
{
|
||||||
|
@ -49,10 +49,10 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
|||||||
InputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
|
InputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
|
||||||
OutputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
|
OutputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
|
||||||
|
|
||||||
for (int i = 0; i < Parameter.ChannelCount; i++)
|
for (int i = 0; i < _parameter.ChannelCount; i++)
|
||||||
{
|
{
|
||||||
InputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Input[i]);
|
InputBufferIndices[i] = (ushort)(bufferOffset + _parameter.Input[i]);
|
||||||
OutputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Output[i]);
|
OutputBufferIndices[i] = (ushort)(bufferOffset + _parameter.Output[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,11 +62,11 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
|||||||
|
|
||||||
if (IsEffectEnabled)
|
if (IsEffectEnabled)
|
||||||
{
|
{
|
||||||
if (Parameter.Status == UsageState.Invalid)
|
if (_parameter.Status == UsageState.Invalid)
|
||||||
{
|
{
|
||||||
state = new LimiterState(ref _parameter, WorkBuffer);
|
state = new LimiterState(ref _parameter, WorkBuffer);
|
||||||
}
|
}
|
||||||
else if (Parameter.Status == UsageState.New)
|
else if (_parameter.Status == UsageState.New)
|
||||||
{
|
{
|
||||||
LimiterState.UpdateParameter(ref _parameter);
|
LimiterState.UpdateParameter(ref _parameter);
|
||||||
}
|
}
|
||||||
@ -77,63 +77,63 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
|||||||
|
|
||||||
private unsafe void ProcessLimiter(CommandList context, ref LimiterState state)
|
private unsafe void ProcessLimiter(CommandList context, ref LimiterState state)
|
||||||
{
|
{
|
||||||
Debug.Assert(Parameter.IsChannelCountValid());
|
Debug.Assert(_parameter.IsChannelCountValid());
|
||||||
|
|
||||||
if (IsEffectEnabled && Parameter.IsChannelCountValid())
|
if (IsEffectEnabled && _parameter.IsChannelCountValid())
|
||||||
{
|
{
|
||||||
if (!ResultState.IsEmpty && Parameter.StatisticsReset)
|
if (!ResultState.IsEmpty && _parameter.StatisticsReset)
|
||||||
{
|
{
|
||||||
ref LimiterStatistics statistics = ref MemoryMarshal.Cast<byte, LimiterStatistics>(ResultState.Span[0].SpecificData)[0];
|
ref LimiterStatistics statistics = ref MemoryMarshal.Cast<byte, LimiterStatistics>(ResultState.Span[0].SpecificData)[0];
|
||||||
|
|
||||||
statistics.Reset();
|
statistics.Reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
Span<IntPtr> inputBuffers = stackalloc IntPtr[Parameter.ChannelCount];
|
Span<IntPtr> inputBuffers = stackalloc IntPtr[_parameter.ChannelCount];
|
||||||
Span<IntPtr> outputBuffers = stackalloc IntPtr[Parameter.ChannelCount];
|
Span<IntPtr> outputBuffers = stackalloc IntPtr[_parameter.ChannelCount];
|
||||||
|
|
||||||
for (int i = 0; i < Parameter.ChannelCount; i++)
|
for (int i = 0; i < _parameter.ChannelCount; i++)
|
||||||
{
|
{
|
||||||
inputBuffers[i] = context.GetBufferPointer(InputBufferIndices[i]);
|
inputBuffers[i] = context.GetBufferPointer(InputBufferIndices[i]);
|
||||||
outputBuffers[i] = context.GetBufferPointer(OutputBufferIndices[i]);
|
outputBuffers[i] = context.GetBufferPointer(OutputBufferIndices[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int channelIndex = 0; channelIndex < Parameter.ChannelCount; channelIndex++)
|
for (int channelIndex = 0; channelIndex < _parameter.ChannelCount; channelIndex++)
|
||||||
{
|
{
|
||||||
for (int sampleIndex = 0; sampleIndex < context.SampleCount; sampleIndex++)
|
for (int sampleIndex = 0; sampleIndex < context.SampleCount; sampleIndex++)
|
||||||
{
|
{
|
||||||
float rawInputSample = *((float*)inputBuffers[channelIndex] + sampleIndex);
|
float rawInputSample = *((float*)inputBuffers[channelIndex] + sampleIndex);
|
||||||
|
|
||||||
float inputSample = (rawInputSample / short.MaxValue) * Parameter.InputGain;
|
float inputSample = (rawInputSample / short.MaxValue) * _parameter.InputGain;
|
||||||
|
|
||||||
float sampleInputMax = Math.Abs(inputSample);
|
float sampleInputMax = Math.Abs(inputSample);
|
||||||
|
|
||||||
float inputCoefficient = Parameter.ReleaseCoefficient;
|
float inputCoefficient = _parameter.ReleaseCoefficient;
|
||||||
|
|
||||||
if (sampleInputMax > state.DetectorAverage[channelIndex].Read())
|
if (sampleInputMax > state.DetectorAverage[channelIndex].Read())
|
||||||
{
|
{
|
||||||
inputCoefficient = Parameter.AttackCoefficient;
|
inputCoefficient = _parameter.AttackCoefficient;
|
||||||
}
|
}
|
||||||
|
|
||||||
float detectorValue = state.DetectorAverage[channelIndex].Update(sampleInputMax, inputCoefficient);
|
float detectorValue = state.DetectorAverage[channelIndex].Update(sampleInputMax, inputCoefficient);
|
||||||
float attenuation = 1.0f;
|
float attenuation = 1.0f;
|
||||||
|
|
||||||
if (detectorValue > Parameter.Threshold)
|
if (detectorValue > _parameter.Threshold)
|
||||||
{
|
{
|
||||||
attenuation = Parameter.Threshold / detectorValue;
|
attenuation = _parameter.Threshold / detectorValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
float outputCoefficient = Parameter.ReleaseCoefficient;
|
float outputCoefficient = _parameter.ReleaseCoefficient;
|
||||||
|
|
||||||
if (state.CompressionGainAverage[channelIndex].Read() > attenuation)
|
if (state.CompressionGainAverage[channelIndex].Read() > attenuation)
|
||||||
{
|
{
|
||||||
outputCoefficient = Parameter.AttackCoefficient;
|
outputCoefficient = _parameter.AttackCoefficient;
|
||||||
}
|
}
|
||||||
|
|
||||||
float compressionGain = state.CompressionGainAverage[channelIndex].Update(attenuation, outputCoefficient);
|
float compressionGain = state.CompressionGainAverage[channelIndex].Update(attenuation, outputCoefficient);
|
||||||
|
|
||||||
ref float delayedSample = ref state.DelayedSampleBuffer[channelIndex * Parameter.DelayBufferSampleCountMax + state.DelayedSampleBufferPosition[channelIndex]];
|
ref float delayedSample = ref state.DelayedSampleBuffer[channelIndex * _parameter.DelayBufferSampleCountMax + state.DelayedSampleBufferPosition[channelIndex]];
|
||||||
|
|
||||||
float outputSample = delayedSample * compressionGain * Parameter.OutputGain;
|
float outputSample = delayedSample * compressionGain * _parameter.OutputGain;
|
||||||
|
|
||||||
*((float*)outputBuffers[channelIndex] + sampleIndex) = outputSample * short.MaxValue;
|
*((float*)outputBuffers[channelIndex] + sampleIndex) = outputSample * short.MaxValue;
|
||||||
|
|
||||||
@ -141,9 +141,9 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
|||||||
|
|
||||||
state.DelayedSampleBufferPosition[channelIndex]++;
|
state.DelayedSampleBufferPosition[channelIndex]++;
|
||||||
|
|
||||||
while (state.DelayedSampleBufferPosition[channelIndex] >= Parameter.DelayBufferSampleCountMin)
|
while (state.DelayedSampleBufferPosition[channelIndex] >= _parameter.DelayBufferSampleCountMin)
|
||||||
{
|
{
|
||||||
state.DelayedSampleBufferPosition[channelIndex] -= Parameter.DelayBufferSampleCountMin;
|
state.DelayedSampleBufferPosition[channelIndex] -= _parameter.DelayBufferSampleCountMin;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ResultState.IsEmpty)
|
if (!ResultState.IsEmpty)
|
||||||
@ -158,7 +158,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
for (int i = 0; i < Parameter.ChannelCount; i++)
|
for (int i = 0; i < _parameter.ChannelCount; i++)
|
||||||
{
|
{
|
||||||
if (InputBufferIndices[i] != OutputBufferIndices[i])
|
if (InputBufferIndices[i] != OutputBufferIndices[i])
|
||||||
{
|
{
|
||||||
|
@ -24,7 +24,14 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
|||||||
|
|
||||||
public Memory<VoiceUpdateState> State { get; }
|
public Memory<VoiceUpdateState> State { get; }
|
||||||
|
|
||||||
public MixRampGroupedCommand(uint mixBufferCount, uint inputBufferIndex, uint outputBufferIndex, Span<float> volume0, Span<float> volume1, Memory<VoiceUpdateState> state, int nodeId)
|
public MixRampGroupedCommand(
|
||||||
|
uint mixBufferCount,
|
||||||
|
uint inputBufferIndex,
|
||||||
|
uint outputBufferIndex,
|
||||||
|
ReadOnlySpan<float> volume0,
|
||||||
|
ReadOnlySpan<float> volume1,
|
||||||
|
Memory<VoiceUpdateState> state,
|
||||||
|
int nodeId)
|
||||||
{
|
{
|
||||||
Enabled = true;
|
Enabled = true;
|
||||||
MixBufferCount = mixBufferCount;
|
MixBufferCount = mixBufferCount;
|
||||||
@ -48,7 +55,12 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static float ProcessMixRampGrouped(Span<float> outputBuffer, ReadOnlySpan<float> inputBuffer, float volume0, float volume1, int sampleCount)
|
private static float ProcessMixRampGrouped(
|
||||||
|
Span<float> outputBuffer,
|
||||||
|
ReadOnlySpan<float> inputBuffer,
|
||||||
|
float volume0,
|
||||||
|
float volume1,
|
||||||
|
int sampleCount)
|
||||||
{
|
{
|
||||||
float ramp = (volume1 - volume0) / sampleCount;
|
float ramp = (volume1 - volume0) / sampleCount;
|
||||||
float volume = volume0;
|
float volume = volume0;
|
||||||
|
@ -0,0 +1,145 @@
|
|||||||
|
using Ryujinx.Audio.Renderer.Common;
|
||||||
|
using Ryujinx.Audio.Renderer.Dsp.State;
|
||||||
|
using Ryujinx.Audio.Renderer.Parameter;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||||
|
{
|
||||||
|
public class MultiTapBiquadFilterAndMixCommand : ICommand
|
||||||
|
{
|
||||||
|
public bool Enabled { get; set; }
|
||||||
|
|
||||||
|
public int NodeId { get; }
|
||||||
|
|
||||||
|
public CommandType CommandType => CommandType.MultiTapBiquadFilterAndMix;
|
||||||
|
|
||||||
|
public uint EstimatedProcessingTime { get; set; }
|
||||||
|
|
||||||
|
public ushort InputBufferIndex { get; }
|
||||||
|
public ushort OutputBufferIndex { get; }
|
||||||
|
|
||||||
|
private BiquadFilterParameter _parameter0;
|
||||||
|
private BiquadFilterParameter _parameter1;
|
||||||
|
|
||||||
|
public Memory<BiquadFilterState> BiquadFilterState0 { get; }
|
||||||
|
public Memory<BiquadFilterState> BiquadFilterState1 { get; }
|
||||||
|
public Memory<BiquadFilterState> PreviousBiquadFilterState0 { get; }
|
||||||
|
public Memory<BiquadFilterState> PreviousBiquadFilterState1 { get; }
|
||||||
|
|
||||||
|
public Memory<VoiceUpdateState> State { get; }
|
||||||
|
|
||||||
|
public int LastSampleIndex { get; }
|
||||||
|
|
||||||
|
public float Volume0 { get; }
|
||||||
|
public float Volume1 { get; }
|
||||||
|
|
||||||
|
public bool NeedInitialization0 { get; }
|
||||||
|
public bool NeedInitialization1 { get; }
|
||||||
|
public bool HasVolumeRamp { get; }
|
||||||
|
public bool IsFirstMixBuffer { get; }
|
||||||
|
|
||||||
|
public MultiTapBiquadFilterAndMixCommand(
|
||||||
|
float volume0,
|
||||||
|
float volume1,
|
||||||
|
uint inputBufferIndex,
|
||||||
|
uint outputBufferIndex,
|
||||||
|
int lastSampleIndex,
|
||||||
|
Memory<VoiceUpdateState> state,
|
||||||
|
ref BiquadFilterParameter filter0,
|
||||||
|
ref BiquadFilterParameter filter1,
|
||||||
|
Memory<BiquadFilterState> biquadFilterState0,
|
||||||
|
Memory<BiquadFilterState> biquadFilterState1,
|
||||||
|
Memory<BiquadFilterState> previousBiquadFilterState0,
|
||||||
|
Memory<BiquadFilterState> previousBiquadFilterState1,
|
||||||
|
bool needInitialization0,
|
||||||
|
bool needInitialization1,
|
||||||
|
bool hasVolumeRamp,
|
||||||
|
bool isFirstMixBuffer,
|
||||||
|
int nodeId)
|
||||||
|
{
|
||||||
|
Enabled = true;
|
||||||
|
NodeId = nodeId;
|
||||||
|
|
||||||
|
InputBufferIndex = (ushort)inputBufferIndex;
|
||||||
|
OutputBufferIndex = (ushort)outputBufferIndex;
|
||||||
|
|
||||||
|
_parameter0 = filter0;
|
||||||
|
_parameter1 = filter1;
|
||||||
|
BiquadFilterState0 = biquadFilterState0;
|
||||||
|
BiquadFilterState1 = biquadFilterState1;
|
||||||
|
PreviousBiquadFilterState0 = previousBiquadFilterState0;
|
||||||
|
PreviousBiquadFilterState1 = previousBiquadFilterState1;
|
||||||
|
|
||||||
|
State = state;
|
||||||
|
LastSampleIndex = lastSampleIndex;
|
||||||
|
|
||||||
|
Volume0 = volume0;
|
||||||
|
Volume1 = volume1;
|
||||||
|
|
||||||
|
NeedInitialization0 = needInitialization0;
|
||||||
|
NeedInitialization1 = needInitialization1;
|
||||||
|
HasVolumeRamp = hasVolumeRamp;
|
||||||
|
IsFirstMixBuffer = isFirstMixBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateState(Memory<BiquadFilterState> state, Memory<BiquadFilterState> previousState, bool needInitialization)
|
||||||
|
{
|
||||||
|
if (needInitialization)
|
||||||
|
{
|
||||||
|
// If there is no previous state, initialize to zero.
|
||||||
|
|
||||||
|
state.Span[0] = new BiquadFilterState();
|
||||||
|
}
|
||||||
|
else if (IsFirstMixBuffer)
|
||||||
|
{
|
||||||
|
// This is the first buffer, set previous state to current state.
|
||||||
|
|
||||||
|
previousState.Span[0] = state.Span[0];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Rewind the current state by copying back the previous state.
|
||||||
|
|
||||||
|
state.Span[0] = previousState.Span[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Process(CommandList context)
|
||||||
|
{
|
||||||
|
ReadOnlySpan<float> inputBuffer = context.GetBuffer(InputBufferIndex);
|
||||||
|
Span<float> outputBuffer = context.GetBuffer(OutputBufferIndex);
|
||||||
|
|
||||||
|
UpdateState(BiquadFilterState0, PreviousBiquadFilterState0, NeedInitialization0);
|
||||||
|
UpdateState(BiquadFilterState1, PreviousBiquadFilterState1, NeedInitialization1);
|
||||||
|
|
||||||
|
if (HasVolumeRamp)
|
||||||
|
{
|
||||||
|
float volume = Volume0;
|
||||||
|
float ramp = (Volume1 - Volume0) / (int)context.SampleCount;
|
||||||
|
|
||||||
|
State.Span[0].LastSamples[LastSampleIndex] = BiquadFilterHelper.ProcessDoubleBiquadFilterAndMixRamp(
|
||||||
|
ref _parameter0,
|
||||||
|
ref _parameter1,
|
||||||
|
ref BiquadFilterState0.Span[0],
|
||||||
|
ref BiquadFilterState1.Span[0],
|
||||||
|
outputBuffer,
|
||||||
|
inputBuffer,
|
||||||
|
context.SampleCount,
|
||||||
|
volume,
|
||||||
|
ramp);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BiquadFilterHelper.ProcessDoubleBiquadFilterAndMix(
|
||||||
|
ref _parameter0,
|
||||||
|
ref _parameter1,
|
||||||
|
ref BiquadFilterState0.Span[0],
|
||||||
|
ref BiquadFilterState1.Span[0],
|
||||||
|
outputBuffer,
|
||||||
|
inputBuffer,
|
||||||
|
context.SampleCount,
|
||||||
|
Volume1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -4,13 +4,13 @@ using System;
|
|||||||
|
|
||||||
namespace Ryujinx.Audio.Renderer.Dsp.Command
|
namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||||
{
|
{
|
||||||
public class GroupedBiquadFilterCommand : ICommand
|
public class MultiTapBiquadFilterCommand : ICommand
|
||||||
{
|
{
|
||||||
public bool Enabled { get; set; }
|
public bool Enabled { get; set; }
|
||||||
|
|
||||||
public int NodeId { get; }
|
public int NodeId { get; }
|
||||||
|
|
||||||
public CommandType CommandType => CommandType.GroupedBiquadFilter;
|
public CommandType CommandType => CommandType.MultiTapBiquadFilter;
|
||||||
|
|
||||||
public uint EstimatedProcessingTime { get; set; }
|
public uint EstimatedProcessingTime { get; set; }
|
||||||
|
|
||||||
@ -20,7 +20,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
|||||||
private readonly int _outputBufferIndex;
|
private readonly int _outputBufferIndex;
|
||||||
private readonly bool[] _isInitialized;
|
private readonly bool[] _isInitialized;
|
||||||
|
|
||||||
public GroupedBiquadFilterCommand(int baseIndex, ReadOnlySpan<BiquadFilterParameter> filters, Memory<BiquadFilterState> biquadFilterStateMemory, int inputBufferOffset, int outputBufferOffset, ReadOnlySpan<bool> isInitialized, int nodeId)
|
public MultiTapBiquadFilterCommand(int baseIndex, ReadOnlySpan<BiquadFilterParameter> filters, Memory<BiquadFilterState> biquadFilterStateMemory, int inputBufferOffset, int outputBufferOffset, ReadOnlySpan<bool> isInitialized, int nodeId)
|
||||||
{
|
{
|
||||||
_parameters = filters.ToArray();
|
_parameters = filters.ToArray();
|
||||||
_biquadFilterStates = biquadFilterStateMemory;
|
_biquadFilterStates = biquadFilterStateMemory;
|
@ -2,12 +2,16 @@ using System.Runtime.InteropServices;
|
|||||||
|
|
||||||
namespace Ryujinx.Audio.Renderer.Dsp.State
|
namespace Ryujinx.Audio.Renderer.Dsp.State
|
||||||
{
|
{
|
||||||
[StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x10)]
|
[StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x20)]
|
||||||
public struct BiquadFilterState
|
public struct BiquadFilterState
|
||||||
{
|
{
|
||||||
public float State0;
|
public float State0;
|
||||||
public float State1;
|
public float State1;
|
||||||
public float State2;
|
public float State2;
|
||||||
public float State3;
|
public float State3;
|
||||||
|
public float State4;
|
||||||
|
public float State5;
|
||||||
|
public float State6;
|
||||||
|
public float State7;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ namespace Ryujinx.Audio.Renderer.Parameter
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Output information for behaviour.
|
/// Output information for behaviour.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>This is used to report errors to the user during <see cref="Server.AudioRenderSystem.Update(Memory{byte}, Memory{byte}, ReadOnlyMemory{byte})"/> processing.</remarks>
|
/// <remarks>This is used to report errors to the user during <see cref="Server.AudioRenderSystem.Update(Memory{byte}, Memory{byte}, System.Buffers.ReadOnlySequence{byte})"/> processing.</remarks>
|
||||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
public struct BehaviourErrorInfoOutStatus
|
public struct BehaviourErrorInfoOutStatus
|
||||||
{
|
{
|
||||||
|
@ -90,9 +90,16 @@ namespace Ryujinx.Audio.Renderer.Parameter.Effect
|
|||||||
public bool MakeupGainEnabled;
|
public bool MakeupGainEnabled;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reserved/padding.
|
/// Indicate if the compressor effect should output statistics.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private Array2<byte> _reserved;
|
[MarshalAs(UnmanagedType.I1)]
|
||||||
|
public bool StatisticsEnabled;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicate to the DSP that the user did a statistics reset.
|
||||||
|
/// </summary>
|
||||||
|
[MarshalAs(UnmanagedType.I1)]
|
||||||
|
public bool StatisticsReset;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Check if the <see cref="ChannelCount"/> is valid.
|
/// Check if the <see cref="ChannelCount"/> is valid.
|
||||||
|
@ -0,0 +1,38 @@
|
|||||||
|
using Ryujinx.Common.Memory;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Ryujinx.Audio.Renderer.Parameter.Effect
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Effect result state for <seealso cref="Common.EffectType.Compressor"/>.
|
||||||
|
/// </summary>
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
public struct CompressorStatistics
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Maximum input mean value since last reset.
|
||||||
|
/// </summary>
|
||||||
|
public float MaximumMean;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Minimum output gain since last reset.
|
||||||
|
/// </summary>
|
||||||
|
public float MinimumGain;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Last processed input sample, per channel.
|
||||||
|
/// </summary>
|
||||||
|
public Array6<float> LastSamples;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reset the statistics.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="channelCount">Number of channels to reset.</param>
|
||||||
|
public void Reset(ushort channelCount)
|
||||||
|
{
|
||||||
|
MaximumMean = 0.0f;
|
||||||
|
MinimumGain = 1.0f;
|
||||||
|
LastSamples.AsSpan()[..channelCount].Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,48 @@
|
|||||||
|
using Ryujinx.Common.Memory;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Ryujinx.Audio.Renderer.Parameter
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Generic interface for the splitter destination parameters.
|
||||||
|
/// </summary>
|
||||||
|
public interface ISplitterDestinationInParameter
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Target splitter destination data id.
|
||||||
|
/// </summary>
|
||||||
|
int Id { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The mix to output the result of the splitter.
|
||||||
|
/// </summary>
|
||||||
|
int DestinationId { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Biquad filter parameters.
|
||||||
|
/// </summary>
|
||||||
|
Array2<BiquadFilterParameter> BiquadFilters { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set to true if in use.
|
||||||
|
/// </summary>
|
||||||
|
bool IsUsed { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set to true to force resetting the previous mix volumes.
|
||||||
|
/// </summary>
|
||||||
|
bool ResetPrevVolume { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mix buffer volumes.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>Used when a splitter id is specified in the mix.</remarks>
|
||||||
|
Span<float> MixBufferVolume { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check if the magic is valid.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Returns true if the magic is valid.</returns>
|
||||||
|
bool IsMagicValid();
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +1,4 @@
|
|||||||
|
using Ryujinx.Common.Memory;
|
||||||
using Ryujinx.Common.Utilities;
|
using Ryujinx.Common.Utilities;
|
||||||
using System;
|
using System;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
@ -5,10 +6,10 @@ using System.Runtime.InteropServices;
|
|||||||
namespace Ryujinx.Audio.Renderer.Parameter
|
namespace Ryujinx.Audio.Renderer.Parameter
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Input header for a splitter destination update.
|
/// Input header for a splitter destination version 1 update.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
public struct SplitterDestinationInParameter
|
public struct SplitterDestinationInParameterVersion1 : ISplitterDestinationInParameter
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Magic of the input header.
|
/// Magic of the input header.
|
||||||
@ -36,12 +37,18 @@ namespace Ryujinx.Audio.Renderer.Parameter
|
|||||||
[MarshalAs(UnmanagedType.I1)]
|
[MarshalAs(UnmanagedType.I1)]
|
||||||
public bool IsUsed;
|
public bool IsUsed;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set to true to force resetting the previous mix volumes.
|
||||||
|
/// </summary>
|
||||||
|
[MarshalAs(UnmanagedType.I1)]
|
||||||
|
public bool ResetPrevVolume;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reserved/padding.
|
/// Reserved/padding.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private unsafe fixed byte _reserved[3];
|
private unsafe fixed byte _reserved[2];
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential, Size = 4 * Constants.MixBufferCountMax, Pack = 1)]
|
[StructLayout(LayoutKind.Sequential, Size = sizeof(float) * Constants.MixBufferCountMax, Pack = 1)]
|
||||||
private struct MixArray { }
|
private struct MixArray { }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -50,6 +57,15 @@ namespace Ryujinx.Audio.Renderer.Parameter
|
|||||||
/// <remarks>Used when a splitter id is specified in the mix.</remarks>
|
/// <remarks>Used when a splitter id is specified in the mix.</remarks>
|
||||||
public Span<float> MixBufferVolume => SpanHelpers.AsSpan<MixArray, float>(ref _mixBufferVolume);
|
public Span<float> MixBufferVolume => SpanHelpers.AsSpan<MixArray, float>(ref _mixBufferVolume);
|
||||||
|
|
||||||
|
readonly int ISplitterDestinationInParameter.Id => Id;
|
||||||
|
|
||||||
|
readonly int ISplitterDestinationInParameter.DestinationId => DestinationId;
|
||||||
|
|
||||||
|
readonly Array2<BiquadFilterParameter> ISplitterDestinationInParameter.BiquadFilters => default;
|
||||||
|
|
||||||
|
readonly bool ISplitterDestinationInParameter.IsUsed => IsUsed;
|
||||||
|
readonly bool ISplitterDestinationInParameter.ResetPrevVolume => ResetPrevVolume;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The expected constant of any input header.
|
/// The expected constant of any input header.
|
||||||
/// </summary>
|
/// </summary>
|
@ -0,0 +1,88 @@
|
|||||||
|
using Ryujinx.Common.Memory;
|
||||||
|
using Ryujinx.Common.Utilities;
|
||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Ryujinx.Audio.Renderer.Parameter
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Input header for a splitter destination version 2 update.
|
||||||
|
/// </summary>
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
public struct SplitterDestinationInParameterVersion2 : ISplitterDestinationInParameter
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Magic of the input header.
|
||||||
|
/// </summary>
|
||||||
|
public uint Magic;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Target splitter destination data id.
|
||||||
|
/// </summary>
|
||||||
|
public int Id;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mix buffer volumes storage.
|
||||||
|
/// </summary>
|
||||||
|
private MixArray _mixBufferVolume;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The mix to output the result of the splitter.
|
||||||
|
/// </summary>
|
||||||
|
public int DestinationId;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Biquad filter parameters.
|
||||||
|
/// </summary>
|
||||||
|
public Array2<BiquadFilterParameter> BiquadFilters;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set to true if in use.
|
||||||
|
/// </summary>
|
||||||
|
[MarshalAs(UnmanagedType.I1)]
|
||||||
|
public bool IsUsed;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set to true to force resetting the previous mix volumes.
|
||||||
|
/// </summary>
|
||||||
|
[MarshalAs(UnmanagedType.I1)]
|
||||||
|
public bool ResetPrevVolume;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reserved/padding.
|
||||||
|
/// </summary>
|
||||||
|
private unsafe fixed byte _reserved[10];
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Size = sizeof(float) * Constants.MixBufferCountMax, Pack = 1)]
|
||||||
|
private struct MixArray { }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mix buffer volumes.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>Used when a splitter id is specified in the mix.</remarks>
|
||||||
|
public Span<float> MixBufferVolume => SpanHelpers.AsSpan<MixArray, float>(ref _mixBufferVolume);
|
||||||
|
|
||||||
|
readonly int ISplitterDestinationInParameter.Id => Id;
|
||||||
|
|
||||||
|
readonly int ISplitterDestinationInParameter.DestinationId => DestinationId;
|
||||||
|
|
||||||
|
readonly Array2<BiquadFilterParameter> ISplitterDestinationInParameter.BiquadFilters => BiquadFilters;
|
||||||
|
|
||||||
|
readonly bool ISplitterDestinationInParameter.IsUsed => IsUsed;
|
||||||
|
readonly bool ISplitterDestinationInParameter.ResetPrevVolume => ResetPrevVolume;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The expected constant of any input header.
|
||||||
|
/// </summary>
|
||||||
|
private const uint ValidMagic = 0x44444E53;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check if the magic is valid.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Returns true if the magic is valid.</returns>
|
||||||
|
public readonly bool IsMagicValid()
|
||||||
|
{
|
||||||
|
return Magic == ValidMagic;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
using Ryujinx.Audio.Integration;
|
using Ryujinx.Audio.Integration;
|
||||||
using Ryujinx.Audio.Renderer.Common;
|
using Ryujinx.Audio.Renderer.Common;
|
||||||
using Ryujinx.Audio.Renderer.Dsp.Command;
|
using Ryujinx.Audio.Renderer.Dsp.Command;
|
||||||
|
using Ryujinx.Audio.Renderer.Dsp.State;
|
||||||
using Ryujinx.Audio.Renderer.Parameter;
|
using Ryujinx.Audio.Renderer.Parameter;
|
||||||
using Ryujinx.Audio.Renderer.Server.Effect;
|
using Ryujinx.Audio.Renderer.Server.Effect;
|
||||||
using Ryujinx.Audio.Renderer.Server.MemoryPool;
|
using Ryujinx.Audio.Renderer.Server.MemoryPool;
|
||||||
@ -173,6 +174,22 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||||||
return ResultCode.WorkBufferTooSmall;
|
return ResultCode.WorkBufferTooSmall;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Memory<BiquadFilterState> splitterBqfStates = Memory<BiquadFilterState>.Empty;
|
||||||
|
|
||||||
|
if (_behaviourContext.IsBiquadFilterParameterForSplitterEnabled() &&
|
||||||
|
parameter.SplitterCount > 0 &&
|
||||||
|
parameter.SplitterDestinationCount > 0)
|
||||||
|
{
|
||||||
|
splitterBqfStates = workBufferAllocator.Allocate<BiquadFilterState>(parameter.SplitterDestinationCount * SplitterContext.BqfStatesPerDestination, 0x10);
|
||||||
|
|
||||||
|
if (splitterBqfStates.IsEmpty)
|
||||||
|
{
|
||||||
|
return ResultCode.WorkBufferTooSmall;
|
||||||
|
}
|
||||||
|
|
||||||
|
splitterBqfStates.Span.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
// Invalidate DSP cache on what was currently allocated with workBuffer.
|
// Invalidate DSP cache on what was currently allocated with workBuffer.
|
||||||
AudioProcessorMemoryManager.InvalidateDspCache(_dspMemoryPoolState.Translate(workBuffer, workBufferAllocator.Offset), workBufferAllocator.Offset);
|
AudioProcessorMemoryManager.InvalidateDspCache(_dspMemoryPoolState.Translate(workBuffer, workBufferAllocator.Offset), workBufferAllocator.Offset);
|
||||||
|
|
||||||
@ -292,7 +309,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||||||
state = MemoryPoolState.Create(MemoryPoolState.LocationType.Cpu);
|
state = MemoryPoolState.Create(MemoryPoolState.LocationType.Cpu);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_splitterContext.Initialize(ref _behaviourContext, ref parameter, workBufferAllocator))
|
if (!_splitterContext.Initialize(ref _behaviourContext, ref parameter, workBufferAllocator, splitterBqfStates))
|
||||||
{
|
{
|
||||||
return ResultCode.WorkBufferTooSmall;
|
return ResultCode.WorkBufferTooSmall;
|
||||||
}
|
}
|
||||||
@ -386,7 +403,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ResultCode Update(Memory<byte> output, Memory<byte> performanceOutput, ReadOnlyMemory<byte> input)
|
public ResultCode Update(Memory<byte> output, Memory<byte> performanceOutput, ReadOnlySequence<byte> input)
|
||||||
{
|
{
|
||||||
lock (_lock)
|
lock (_lock)
|
||||||
{
|
{
|
||||||
@ -419,14 +436,16 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
result = stateUpdater.UpdateVoices(_voiceContext, _memoryPools);
|
PoolMapper poolMapper = new PoolMapper(_processHandle, _memoryPools, _behaviourContext.IsMemoryPoolForceMappingEnabled());
|
||||||
|
|
||||||
|
result = stateUpdater.UpdateVoices(_voiceContext, poolMapper);
|
||||||
|
|
||||||
if (result != ResultCode.Success)
|
if (result != ResultCode.Success)
|
||||||
{
|
{
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
result = stateUpdater.UpdateEffects(_effectContext, _isActive, _memoryPools);
|
result = stateUpdater.UpdateEffects(_effectContext, _isActive, poolMapper);
|
||||||
|
|
||||||
if (result != ResultCode.Success)
|
if (result != ResultCode.Success)
|
||||||
{
|
{
|
||||||
@ -450,7 +469,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
result = stateUpdater.UpdateSinks(_sinkContext, _memoryPools);
|
result = stateUpdater.UpdateSinks(_sinkContext, poolMapper);
|
||||||
|
|
||||||
if (result != ResultCode.Success)
|
if (result != ResultCode.Success)
|
||||||
{
|
{
|
||||||
@ -773,6 +792,13 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||||||
// Splitter
|
// Splitter
|
||||||
size = SplitterContext.GetWorkBufferSize(size, ref behaviourContext, ref parameter);
|
size = SplitterContext.GetWorkBufferSize(size, ref behaviourContext, ref parameter);
|
||||||
|
|
||||||
|
if (behaviourContext.IsBiquadFilterParameterForSplitterEnabled() &&
|
||||||
|
parameter.SplitterCount > 0 &&
|
||||||
|
parameter.SplitterDestinationCount > 0)
|
||||||
|
{
|
||||||
|
size = WorkBufferAllocator.GetTargetSize<BiquadFilterState>(size, parameter.SplitterDestinationCount * SplitterContext.BqfStatesPerDestination, 0x10);
|
||||||
|
}
|
||||||
|
|
||||||
// DSP Voice
|
// DSP Voice
|
||||||
size = WorkBufferAllocator.GetTargetSize<VoiceUpdateState>(size, parameter.VoiceCount, VoiceUpdateState.Align);
|
size = WorkBufferAllocator.GetTargetSize<VoiceUpdateState>(size, parameter.VoiceCount, VoiceUpdateState.Align);
|
||||||
|
|
||||||
|
@ -177,12 +177,12 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Start the <see cref="AudioProcessor"/> and worker thread.
|
/// Start the <see cref="AudioProcessor"/> and worker thread.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void StartLocked(float volume)
|
private void StartLocked()
|
||||||
{
|
{
|
||||||
_isRunning = true;
|
_isRunning = true;
|
||||||
|
|
||||||
// TODO: virtual device mapping (IAudioDevice)
|
// TODO: virtual device mapping (IAudioDevice)
|
||||||
Processor.Start(_deviceDriver, volume);
|
Processor.Start(_deviceDriver);
|
||||||
|
|
||||||
_workerThread = new Thread(SendCommands)
|
_workerThread = new Thread(SendCommands)
|
||||||
{
|
{
|
||||||
@ -254,7 +254,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||||||
/// Register a new <see cref="AudioRenderSystem"/>.
|
/// Register a new <see cref="AudioRenderSystem"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="renderer">The <see cref="AudioRenderSystem"/> to register.</param>
|
/// <param name="renderer">The <see cref="AudioRenderSystem"/> to register.</param>
|
||||||
private void Register(AudioRenderSystem renderer, float volume)
|
private void Register(AudioRenderSystem renderer)
|
||||||
{
|
{
|
||||||
lock (_sessionLock)
|
lock (_sessionLock)
|
||||||
{
|
{
|
||||||
@ -265,7 +265,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||||||
{
|
{
|
||||||
if (!_isRunning)
|
if (!_isRunning)
|
||||||
{
|
{
|
||||||
StartLocked(volume);
|
StartLocked();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -312,8 +312,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||||||
ulong appletResourceUserId,
|
ulong appletResourceUserId,
|
||||||
ulong workBufferAddress,
|
ulong workBufferAddress,
|
||||||
ulong workBufferSize,
|
ulong workBufferSize,
|
||||||
uint processHandle,
|
uint processHandle)
|
||||||
float volume)
|
|
||||||
{
|
{
|
||||||
int sessionId = AcquireSessionId();
|
int sessionId = AcquireSessionId();
|
||||||
|
|
||||||
@ -338,7 +337,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||||||
{
|
{
|
||||||
renderer = audioRenderer;
|
renderer = audioRenderer;
|
||||||
|
|
||||||
Register(renderer, volume);
|
Register(renderer);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -350,21 +349,6 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public float GetVolume()
|
|
||||||
{
|
|
||||||
if (Processor != null)
|
|
||||||
{
|
|
||||||
return Processor.GetVolume();
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetVolume(float volume)
|
|
||||||
{
|
|
||||||
Processor?.SetVolume(volume);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
GC.SuppressFinalize(this);
|
GC.SuppressFinalize(this);
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Buffers;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using static Ryujinx.Audio.Renderer.Common.BehaviourParameter;
|
using static Ryujinx.Audio.Renderer.Common.BehaviourParameter;
|
||||||
|
|
||||||
@ -44,7 +45,6 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||||||
/// <see cref="Parameter.RendererInfoOutStatus"/> was added to supply the count of update done sent to the DSP.
|
/// <see cref="Parameter.RendererInfoOutStatus"/> was added to supply the count of update done sent to the DSP.
|
||||||
/// A new version of the command estimator was added to address timing changes caused by the voice changes.
|
/// A new version of the command estimator was added to address timing changes caused by the voice changes.
|
||||||
/// Additionally, the rendering limit percent was incremented to 80%.
|
/// Additionally, the rendering limit percent was incremented to 80%.
|
||||||
///
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>This was added in system update 6.0.0</remarks>
|
/// <remarks>This was added in system update 6.0.0</remarks>
|
||||||
public const int Revision5 = 5 << 24;
|
public const int Revision5 = 5 << 24;
|
||||||
@ -100,10 +100,26 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||||||
/// <remarks>This was added in system update 14.0.0 but some changes were made in 15.0.0</remarks>
|
/// <remarks>This was added in system update 14.0.0 but some changes were made in 15.0.0</remarks>
|
||||||
public const int Revision11 = 11 << 24;
|
public const int Revision11 = 11 << 24;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// REV12:
|
||||||
|
/// Two new commands were added to for biquad filtering and mixing (with optinal volume ramp) on the same command.
|
||||||
|
/// Splitter destinations can now specify up to two biquad filtering parameters, used for filtering the buffer before mixing.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>This was added in system update 17.0.0</remarks>
|
||||||
|
public const int Revision12 = 12 << 24;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// REV13:
|
||||||
|
/// The compressor effect can now output statistics.
|
||||||
|
/// Splitter destinations now explicitly reset the previous mix volume, instead of doing so on first use.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>This was added in system update 18.0.0</remarks>
|
||||||
|
public const int Revision13 = 13 << 24;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Last revision supported by the implementation.
|
/// Last revision supported by the implementation.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const int LastRevision = Revision11;
|
public const int LastRevision = Revision13;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Target revision magic supported by the implementation.
|
/// Target revision magic supported by the implementation.
|
||||||
@ -211,7 +227,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Check if the audio renderer should fix the GC-ADPCM context not being provided to the DSP.
|
/// Check if the audio renderer should fix the GC-ADPCM context not being provided to the DSP.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>True if if the audio renderer should fix it.</returns>
|
/// <returns>True if the audio renderer should fix it.</returns>
|
||||||
public bool IsAdpcmLoopContextBugFixed()
|
public bool IsAdpcmLoopContextBugFixed()
|
||||||
{
|
{
|
||||||
return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision2);
|
return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision2);
|
||||||
@ -273,7 +289,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Check if the audio renderer should trust the user destination count in <see cref="Splitter.SplitterState.Update(Splitter.SplitterContext, ref Parameter.SplitterInParameter, ReadOnlySpan{byte})"/>.
|
/// Check if the audio renderer should trust the user destination count in <see cref="Renderer.Server.Splitter.SplitterState.Update(Renderer.Server.Splitter.SplitterContext, Renderer.Parameter.SplitterInParameter, SequenceReader{byte})"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>True if the audio renderer should trust the user destination count.</returns>
|
/// <returns>True if the audio renderer should trust the user destination count.</returns>
|
||||||
public bool IsSplitterBugFixed()
|
public bool IsSplitterBugFixed()
|
||||||
@ -353,7 +369,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||||||
/// Check if the audio renderer should use an optimized Biquad Filter (Direct Form 1) in case of two biquad filters are defined on a voice.
|
/// Check if the audio renderer should use an optimized Biquad Filter (Direct Form 1) in case of two biquad filters are defined on a voice.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>True if the audio renderer should use the optimization.</returns>
|
/// <returns>True if the audio renderer should use the optimization.</returns>
|
||||||
public bool IsBiquadFilterGroupedOptimizationSupported()
|
public bool UseMultiTapBiquadFilterProcessing()
|
||||||
{
|
{
|
||||||
return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision10);
|
return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision10);
|
||||||
}
|
}
|
||||||
@ -367,6 +383,24 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||||||
return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision11);
|
return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision11);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check if the audio renderer should support biquad filter on splitter.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>True if the audio renderer support biquad filter on splitter</returns>
|
||||||
|
public bool IsBiquadFilterParameterForSplitterEnabled()
|
||||||
|
{
|
||||||
|
return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision12);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check if the audio renderer should support explicit previous mix volume reset on splitter.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>True if the audio renderer support explicit previous mix volume reset on splitter</returns>
|
||||||
|
public bool IsSplitterPrevVolumeResetSupported()
|
||||||
|
{
|
||||||
|
return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision13);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get the version of the <see cref="ICommandProcessingTimeEstimator"/>.
|
/// Get the version of the <see cref="ICommandProcessingTimeEstimator"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -204,7 +204,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create a new <see cref="GroupedBiquadFilterCommand"/>.
|
/// Create a new <see cref="MultiTapBiquadFilterCommand"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="baseIndex">The base index of the input and output buffer.</param>
|
/// <param name="baseIndex">The base index of the input and output buffer.</param>
|
||||||
/// <param name="filters">The biquad filter parameters.</param>
|
/// <param name="filters">The biquad filter parameters.</param>
|
||||||
@ -213,9 +213,9 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||||||
/// <param name="outputBufferOffset">The output buffer offset.</param>
|
/// <param name="outputBufferOffset">The output buffer offset.</param>
|
||||||
/// <param name="isInitialized">Set to true if the biquad filter state is initialized.</param>
|
/// <param name="isInitialized">Set to true if the biquad filter state is initialized.</param>
|
||||||
/// <param name="nodeId">The node id associated to this command.</param>
|
/// <param name="nodeId">The node id associated to this command.</param>
|
||||||
public void GenerateGroupedBiquadFilter(int baseIndex, ReadOnlySpan<BiquadFilterParameter> filters, Memory<BiquadFilterState> biquadFilterStatesMemory, int inputBufferOffset, int outputBufferOffset, ReadOnlySpan<bool> isInitialized, int nodeId)
|
public void GenerateMultiTapBiquadFilter(int baseIndex, ReadOnlySpan<BiquadFilterParameter> filters, Memory<BiquadFilterState> biquadFilterStatesMemory, int inputBufferOffset, int outputBufferOffset, ReadOnlySpan<bool> isInitialized, int nodeId)
|
||||||
{
|
{
|
||||||
GroupedBiquadFilterCommand command = new(baseIndex, filters, biquadFilterStatesMemory, inputBufferOffset, outputBufferOffset, isInitialized, nodeId);
|
MultiTapBiquadFilterCommand command = new(baseIndex, filters, biquadFilterStatesMemory, inputBufferOffset, outputBufferOffset, isInitialized, nodeId);
|
||||||
|
|
||||||
command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
|
command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
|
||||||
|
|
||||||
@ -232,7 +232,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||||||
/// <param name="volume">The new volume.</param>
|
/// <param name="volume">The new volume.</param>
|
||||||
/// <param name="state">The <see cref="VoiceUpdateState"/> to generate the command from.</param>
|
/// <param name="state">The <see cref="VoiceUpdateState"/> to generate the command from.</param>
|
||||||
/// <param name="nodeId">The node id associated to this command.</param>
|
/// <param name="nodeId">The node id associated to this command.</param>
|
||||||
public void GenerateMixRampGrouped(uint mixBufferCount, uint inputBufferIndex, uint outputBufferIndex, Span<float> previousVolume, Span<float> volume, Memory<VoiceUpdateState> state, int nodeId)
|
public void GenerateMixRampGrouped(uint mixBufferCount, uint inputBufferIndex, uint outputBufferIndex, ReadOnlySpan<float> previousVolume, ReadOnlySpan<float> volume, Memory<VoiceUpdateState> state, int nodeId)
|
||||||
{
|
{
|
||||||
MixRampGroupedCommand command = new(mixBufferCount, inputBufferIndex, outputBufferIndex, previousVolume, volume, state, nodeId);
|
MixRampGroupedCommand command = new(mixBufferCount, inputBufferIndex, outputBufferIndex, previousVolume, volume, state, nodeId);
|
||||||
|
|
||||||
@ -260,6 +260,120 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||||||
AddCommand(command);
|
AddCommand(command);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generate a new <see cref="BiquadFilterAndMixCommand"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="previousVolume">The previous volume.</param>
|
||||||
|
/// <param name="volume">The new volume.</param>
|
||||||
|
/// <param name="inputBufferIndex">The input buffer index.</param>
|
||||||
|
/// <param name="outputBufferIndex">The output buffer index.</param>
|
||||||
|
/// <param name="lastSampleIndex">The index in the <see cref="VoiceUpdateState.LastSamples"/> array to store the ramped sample.</param>
|
||||||
|
/// <param name="state">The <see cref="VoiceUpdateState"/> to generate the command from.</param>
|
||||||
|
/// <param name="filter">The biquad filter parameter.</param>
|
||||||
|
/// <param name="biquadFilterState">The biquad state.</param>
|
||||||
|
/// <param name="previousBiquadFilterState">The previous biquad state.</param>
|
||||||
|
/// <param name="needInitialization">Set to true if the biquad filter state needs to be initialized.</param>
|
||||||
|
/// <param name="hasVolumeRamp">Set to true if the mix has volume ramp, and <paramref name="previousVolume"/> should be taken into account.</param>
|
||||||
|
/// <param name="isFirstMixBuffer">Set to true if the buffer is the first mix buffer.</param>
|
||||||
|
/// <param name="nodeId">The node id associated to this command.</param>
|
||||||
|
public void GenerateBiquadFilterAndMix(
|
||||||
|
float previousVolume,
|
||||||
|
float volume,
|
||||||
|
uint inputBufferIndex,
|
||||||
|
uint outputBufferIndex,
|
||||||
|
int lastSampleIndex,
|
||||||
|
Memory<VoiceUpdateState> state,
|
||||||
|
ref BiquadFilterParameter filter,
|
||||||
|
Memory<BiquadFilterState> biquadFilterState,
|
||||||
|
Memory<BiquadFilterState> previousBiquadFilterState,
|
||||||
|
bool needInitialization,
|
||||||
|
bool hasVolumeRamp,
|
||||||
|
bool isFirstMixBuffer,
|
||||||
|
int nodeId)
|
||||||
|
{
|
||||||
|
BiquadFilterAndMixCommand command = new(
|
||||||
|
previousVolume,
|
||||||
|
volume,
|
||||||
|
inputBufferIndex,
|
||||||
|
outputBufferIndex,
|
||||||
|
lastSampleIndex,
|
||||||
|
state,
|
||||||
|
ref filter,
|
||||||
|
biquadFilterState,
|
||||||
|
previousBiquadFilterState,
|
||||||
|
needInitialization,
|
||||||
|
hasVolumeRamp,
|
||||||
|
isFirstMixBuffer,
|
||||||
|
nodeId);
|
||||||
|
|
||||||
|
command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
|
||||||
|
|
||||||
|
AddCommand(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generate a new <see cref="MultiTapBiquadFilterAndMixCommand"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="previousVolume">The previous volume.</param>
|
||||||
|
/// <param name="volume">The new volume.</param>
|
||||||
|
/// <param name="inputBufferIndex">The input buffer index.</param>
|
||||||
|
/// <param name="outputBufferIndex">The output buffer index.</param>
|
||||||
|
/// <param name="lastSampleIndex">The index in the <see cref="VoiceUpdateState.LastSamples"/> array to store the ramped sample.</param>
|
||||||
|
/// <param name="state">The <see cref="VoiceUpdateState"/> to generate the command from.</param>
|
||||||
|
/// <param name="filter0">First biquad filter parameter.</param>
|
||||||
|
/// <param name="filter1">Second biquad filter parameter.</param>
|
||||||
|
/// <param name="biquadFilterState0">First biquad state.</param>
|
||||||
|
/// <param name="biquadFilterState1">Second biquad state.</param>
|
||||||
|
/// <param name="previousBiquadFilterState0">First previous biquad state.</param>
|
||||||
|
/// <param name="previousBiquadFilterState1">Second previous biquad state.</param>
|
||||||
|
/// <param name="needInitialization0">Set to true if the first biquad filter state needs to be initialized.</param>
|
||||||
|
/// <param name="needInitialization1">Set to true if the second biquad filter state needs to be initialized.</param>
|
||||||
|
/// <param name="hasVolumeRamp">Set to true if the mix has volume ramp, and <paramref name="previousVolume"/> should be taken into account.</param>
|
||||||
|
/// <param name="isFirstMixBuffer">Set to true if the buffer is the first mix buffer.</param>
|
||||||
|
/// <param name="nodeId">The node id associated to this command.</param>
|
||||||
|
public void GenerateMultiTapBiquadFilterAndMix(
|
||||||
|
float previousVolume,
|
||||||
|
float volume,
|
||||||
|
uint inputBufferIndex,
|
||||||
|
uint outputBufferIndex,
|
||||||
|
int lastSampleIndex,
|
||||||
|
Memory<VoiceUpdateState> state,
|
||||||
|
ref BiquadFilterParameter filter0,
|
||||||
|
ref BiquadFilterParameter filter1,
|
||||||
|
Memory<BiquadFilterState> biquadFilterState0,
|
||||||
|
Memory<BiquadFilterState> biquadFilterState1,
|
||||||
|
Memory<BiquadFilterState> previousBiquadFilterState0,
|
||||||
|
Memory<BiquadFilterState> previousBiquadFilterState1,
|
||||||
|
bool needInitialization0,
|
||||||
|
bool needInitialization1,
|
||||||
|
bool hasVolumeRamp,
|
||||||
|
bool isFirstMixBuffer,
|
||||||
|
int nodeId)
|
||||||
|
{
|
||||||
|
MultiTapBiquadFilterAndMixCommand command = new(
|
||||||
|
previousVolume,
|
||||||
|
volume,
|
||||||
|
inputBufferIndex,
|
||||||
|
outputBufferIndex,
|
||||||
|
lastSampleIndex,
|
||||||
|
state,
|
||||||
|
ref filter0,
|
||||||
|
ref filter1,
|
||||||
|
biquadFilterState0,
|
||||||
|
biquadFilterState1,
|
||||||
|
previousBiquadFilterState0,
|
||||||
|
previousBiquadFilterState1,
|
||||||
|
needInitialization0,
|
||||||
|
needInitialization1,
|
||||||
|
hasVolumeRamp,
|
||||||
|
isFirstMixBuffer,
|
||||||
|
nodeId);
|
||||||
|
|
||||||
|
command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
|
||||||
|
|
||||||
|
AddCommand(command);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Generate a new <see cref="DepopForMixBuffersCommand"/>.
|
/// Generate a new <see cref="DepopForMixBuffersCommand"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -268,7 +382,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||||||
/// <param name="bufferCount">The buffer count.</param>
|
/// <param name="bufferCount">The buffer count.</param>
|
||||||
/// <param name="nodeId">The node id associated to this command.</param>
|
/// <param name="nodeId">The node id associated to this command.</param>
|
||||||
/// <param name="sampleRate">The target sample rate in use.</param>
|
/// <param name="sampleRate">The target sample rate in use.</param>
|
||||||
public void GenerateDepopForMixBuffersCommand(Memory<float> depopBuffer, uint bufferOffset, uint bufferCount, int nodeId, uint sampleRate)
|
public void GenerateDepopForMixBuffers(Memory<float> depopBuffer, uint bufferOffset, uint bufferCount, int nodeId, uint sampleRate)
|
||||||
{
|
{
|
||||||
DepopForMixBuffersCommand command = new(depopBuffer, bufferOffset, bufferCount, nodeId, sampleRate);
|
DepopForMixBuffersCommand command = new(depopBuffer, bufferOffset, bufferCount, nodeId, sampleRate);
|
||||||
|
|
||||||
@ -469,11 +583,20 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void GenerateCompressorEffect(uint bufferOffset, CompressorParameter parameter, Memory<CompressorState> state, bool isEnabled, int nodeId)
|
/// <summary>
|
||||||
|
/// Generate a new <see cref="CompressorCommand"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="bufferOffset">The target buffer offset.</param>
|
||||||
|
/// <param name="parameter">The compressor parameter.</param>
|
||||||
|
/// <param name="state">The compressor state.</param>
|
||||||
|
/// <param name="effectResultState">The DSP effect result state.</param>
|
||||||
|
/// <param name="isEnabled">Set to true if the effect should be active.</param>
|
||||||
|
/// <param name="nodeId">The node id associated to this command.</param>
|
||||||
|
public void GenerateCompressorEffect(uint bufferOffset, CompressorParameter parameter, Memory<CompressorState> state, Memory<EffectResultState> effectResultState, bool isEnabled, int nodeId)
|
||||||
{
|
{
|
||||||
if (parameter.IsChannelCountValid())
|
if (parameter.IsChannelCountValid())
|
||||||
{
|
{
|
||||||
CompressorCommand command = new(bufferOffset, parameter, state, isEnabled, nodeId);
|
CompressorCommand command = new(bufferOffset, parameter, state, effectResultState, isEnabled, nodeId);
|
||||||
|
|
||||||
command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
|
command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
|
||||||
|
|
||||||
|
@ -12,6 +12,7 @@ using Ryujinx.Audio.Renderer.Server.Voice;
|
|||||||
using Ryujinx.Audio.Renderer.Utils;
|
using Ryujinx.Audio.Renderer.Utils;
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
namespace Ryujinx.Audio.Renderer.Server
|
namespace Ryujinx.Audio.Renderer.Server
|
||||||
{
|
{
|
||||||
@ -46,12 +47,13 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||||||
{
|
{
|
||||||
ref MixState mix = ref _mixContext.GetState(voiceState.MixId);
|
ref MixState mix = ref _mixContext.GetState(voiceState.MixId);
|
||||||
|
|
||||||
_commandBuffer.GenerateDepopPrepare(dspState,
|
_commandBuffer.GenerateDepopPrepare(
|
||||||
_rendererContext.DepopBuffer,
|
dspState,
|
||||||
mix.BufferCount,
|
_rendererContext.DepopBuffer,
|
||||||
mix.BufferOffset,
|
mix.BufferCount,
|
||||||
voiceState.NodeId,
|
mix.BufferOffset,
|
||||||
voiceState.WasPlaying);
|
voiceState.NodeId,
|
||||||
|
voiceState.WasPlaying);
|
||||||
}
|
}
|
||||||
else if (voiceState.SplitterId != Constants.UnusedSplitterId)
|
else if (voiceState.SplitterId != Constants.UnusedSplitterId)
|
||||||
{
|
{
|
||||||
@ -59,15 +61,13 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
Span<SplitterDestination> destinationSpan = _splitterContext.GetDestination((int)voiceState.SplitterId, destinationId++);
|
SplitterDestination destination = _splitterContext.GetDestination((int)voiceState.SplitterId, destinationId++);
|
||||||
|
|
||||||
if (destinationSpan.IsEmpty)
|
if (destination.IsNull)
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
ref SplitterDestination destination = ref destinationSpan[0];
|
|
||||||
|
|
||||||
if (destination.IsConfigured())
|
if (destination.IsConfigured())
|
||||||
{
|
{
|
||||||
int mixId = destination.DestinationId;
|
int mixId = destination.DestinationId;
|
||||||
@ -76,12 +76,13 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||||||
{
|
{
|
||||||
ref MixState mix = ref _mixContext.GetState(mixId);
|
ref MixState mix = ref _mixContext.GetState(mixId);
|
||||||
|
|
||||||
_commandBuffer.GenerateDepopPrepare(dspState,
|
_commandBuffer.GenerateDepopPrepare(
|
||||||
_rendererContext.DepopBuffer,
|
dspState,
|
||||||
mix.BufferCount,
|
_rendererContext.DepopBuffer,
|
||||||
mix.BufferOffset,
|
mix.BufferCount,
|
||||||
voiceState.NodeId,
|
mix.BufferOffset,
|
||||||
voiceState.WasPlaying);
|
voiceState.NodeId,
|
||||||
|
voiceState.WasPlaying);
|
||||||
|
|
||||||
destination.MarkAsNeedToUpdateInternalState();
|
destination.MarkAsNeedToUpdateInternalState();
|
||||||
}
|
}
|
||||||
@ -95,35 +96,39 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||||||
|
|
||||||
if (_rendererContext.BehaviourContext.IsWaveBufferVersion2Supported())
|
if (_rendererContext.BehaviourContext.IsWaveBufferVersion2Supported())
|
||||||
{
|
{
|
||||||
_commandBuffer.GenerateDataSourceVersion2(ref voiceState,
|
_commandBuffer.GenerateDataSourceVersion2(
|
||||||
dspState,
|
ref voiceState,
|
||||||
(ushort)_rendererContext.MixBufferCount,
|
dspState,
|
||||||
(ushort)channelIndex,
|
(ushort)_rendererContext.MixBufferCount,
|
||||||
voiceState.NodeId);
|
(ushort)channelIndex,
|
||||||
|
voiceState.NodeId);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
switch (voiceState.SampleFormat)
|
switch (voiceState.SampleFormat)
|
||||||
{
|
{
|
||||||
case SampleFormat.PcmInt16:
|
case SampleFormat.PcmInt16:
|
||||||
_commandBuffer.GeneratePcmInt16DataSourceVersion1(ref voiceState,
|
_commandBuffer.GeneratePcmInt16DataSourceVersion1(
|
||||||
dspState,
|
ref voiceState,
|
||||||
(ushort)_rendererContext.MixBufferCount,
|
dspState,
|
||||||
(ushort)channelIndex,
|
(ushort)_rendererContext.MixBufferCount,
|
||||||
voiceState.NodeId);
|
(ushort)channelIndex,
|
||||||
|
voiceState.NodeId);
|
||||||
break;
|
break;
|
||||||
case SampleFormat.PcmFloat:
|
case SampleFormat.PcmFloat:
|
||||||
_commandBuffer.GeneratePcmFloatDataSourceVersion1(ref voiceState,
|
_commandBuffer.GeneratePcmFloatDataSourceVersion1(
|
||||||
dspState,
|
ref voiceState,
|
||||||
(ushort)_rendererContext.MixBufferCount,
|
dspState,
|
||||||
(ushort)channelIndex,
|
(ushort)_rendererContext.MixBufferCount,
|
||||||
voiceState.NodeId);
|
(ushort)channelIndex,
|
||||||
|
voiceState.NodeId);
|
||||||
break;
|
break;
|
||||||
case SampleFormat.Adpcm:
|
case SampleFormat.Adpcm:
|
||||||
_commandBuffer.GenerateAdpcmDataSourceVersion1(ref voiceState,
|
_commandBuffer.GenerateAdpcmDataSourceVersion1(
|
||||||
dspState,
|
ref voiceState,
|
||||||
(ushort)_rendererContext.MixBufferCount,
|
dspState,
|
||||||
voiceState.NodeId);
|
(ushort)_rendererContext.MixBufferCount,
|
||||||
|
voiceState.NodeId);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new NotImplementedException($"Unsupported data source {voiceState.SampleFormat}");
|
throw new NotImplementedException($"Unsupported data source {voiceState.SampleFormat}");
|
||||||
@ -134,14 +139,14 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||||||
|
|
||||||
private void GenerateBiquadFilterForVoice(ref VoiceState voiceState, Memory<VoiceUpdateState> state, int baseIndex, int bufferOffset, int nodeId)
|
private void GenerateBiquadFilterForVoice(ref VoiceState voiceState, Memory<VoiceUpdateState> state, int baseIndex, int bufferOffset, int nodeId)
|
||||||
{
|
{
|
||||||
bool supportsOptimizedPath = _rendererContext.BehaviourContext.IsBiquadFilterGroupedOptimizationSupported();
|
bool supportsOptimizedPath = _rendererContext.BehaviourContext.UseMultiTapBiquadFilterProcessing();
|
||||||
|
|
||||||
if (supportsOptimizedPath && voiceState.BiquadFilters[0].Enable && voiceState.BiquadFilters[1].Enable)
|
if (supportsOptimizedPath && voiceState.BiquadFilters[0].Enable && voiceState.BiquadFilters[1].Enable)
|
||||||
{
|
{
|
||||||
Memory<byte> biquadStateRawMemory = SpanMemoryManager<byte>.Cast(state)[..(VoiceUpdateState.BiquadStateSize * Constants.VoiceBiquadFilterCount)];
|
Memory<byte> biquadStateRawMemory = SpanMemoryManager<byte>.Cast(state)[..(Unsafe.SizeOf<BiquadFilterState>() * Constants.VoiceBiquadFilterCount)];
|
||||||
Memory<BiquadFilterState> stateMemory = SpanMemoryManager<BiquadFilterState>.Cast(biquadStateRawMemory);
|
Memory<BiquadFilterState> stateMemory = SpanMemoryManager<BiquadFilterState>.Cast(biquadStateRawMemory);
|
||||||
|
|
||||||
_commandBuffer.GenerateGroupedBiquadFilter(baseIndex, voiceState.BiquadFilters.AsSpan(), stateMemory, bufferOffset, bufferOffset, voiceState.BiquadFilterNeedInitialization, nodeId);
|
_commandBuffer.GenerateMultiTapBiquadFilter(baseIndex, voiceState.BiquadFilters.AsSpan(), stateMemory, bufferOffset, bufferOffset, voiceState.BiquadFilterNeedInitialization, nodeId);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -151,33 +156,134 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||||||
|
|
||||||
if (filter.Enable)
|
if (filter.Enable)
|
||||||
{
|
{
|
||||||
Memory<byte> biquadStateRawMemory = SpanMemoryManager<byte>.Cast(state)[..(VoiceUpdateState.BiquadStateSize * Constants.VoiceBiquadFilterCount)];
|
Memory<byte> biquadStateRawMemory = SpanMemoryManager<byte>.Cast(state)[..(Unsafe.SizeOf<BiquadFilterState>() * Constants.VoiceBiquadFilterCount)];
|
||||||
|
|
||||||
Memory<BiquadFilterState> stateMemory = SpanMemoryManager<BiquadFilterState>.Cast(biquadStateRawMemory);
|
Memory<BiquadFilterState> stateMemory = SpanMemoryManager<BiquadFilterState>.Cast(biquadStateRawMemory);
|
||||||
|
|
||||||
_commandBuffer.GenerateBiquadFilter(baseIndex,
|
_commandBuffer.GenerateBiquadFilter(
|
||||||
ref filter,
|
baseIndex,
|
||||||
stateMemory.Slice(i, 1),
|
ref filter,
|
||||||
bufferOffset,
|
stateMemory.Slice(i, 1),
|
||||||
bufferOffset,
|
bufferOffset,
|
||||||
!voiceState.BiquadFilterNeedInitialization[i],
|
bufferOffset,
|
||||||
nodeId);
|
!voiceState.BiquadFilterNeedInitialization[i],
|
||||||
|
nodeId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GenerateVoiceMix(Span<float> mixVolumes, Span<float> previousMixVolumes, Memory<VoiceUpdateState> state, uint bufferOffset, uint bufferCount, uint bufferIndex, int nodeId)
|
private void GenerateVoiceMixWithSplitter(
|
||||||
|
SplitterDestination destination,
|
||||||
|
Memory<VoiceUpdateState> state,
|
||||||
|
uint bufferOffset,
|
||||||
|
uint bufferCount,
|
||||||
|
uint bufferIndex,
|
||||||
|
int nodeId)
|
||||||
|
{
|
||||||
|
ReadOnlySpan<float> mixVolumes = destination.MixBufferVolume;
|
||||||
|
ReadOnlySpan<float> previousMixVolumes = destination.PreviousMixBufferVolume;
|
||||||
|
|
||||||
|
ref BiquadFilterParameter bqf0 = ref destination.GetBiquadFilterParameter(0);
|
||||||
|
ref BiquadFilterParameter bqf1 = ref destination.GetBiquadFilterParameter(1);
|
||||||
|
|
||||||
|
Memory<BiquadFilterState> bqfState = _splitterContext.GetBiquadFilterState(destination);
|
||||||
|
|
||||||
|
bool isFirstMixBuffer = true;
|
||||||
|
|
||||||
|
for (int i = 0; i < bufferCount; i++)
|
||||||
|
{
|
||||||
|
float previousMixVolume = previousMixVolumes[i];
|
||||||
|
float mixVolume = mixVolumes[i];
|
||||||
|
|
||||||
|
if (mixVolume != 0.0f || previousMixVolume != 0.0f)
|
||||||
|
{
|
||||||
|
if (bqf0.Enable && bqf1.Enable)
|
||||||
|
{
|
||||||
|
_commandBuffer.GenerateMultiTapBiquadFilterAndMix(
|
||||||
|
previousMixVolume,
|
||||||
|
mixVolume,
|
||||||
|
bufferIndex,
|
||||||
|
bufferOffset + (uint)i,
|
||||||
|
i,
|
||||||
|
state,
|
||||||
|
ref bqf0,
|
||||||
|
ref bqf1,
|
||||||
|
bqfState[..1],
|
||||||
|
bqfState.Slice(1, 1),
|
||||||
|
bqfState.Slice(2, 1),
|
||||||
|
bqfState.Slice(3, 1),
|
||||||
|
!destination.IsBiquadFilterEnabledPrev(),
|
||||||
|
!destination.IsBiquadFilterEnabledPrev(),
|
||||||
|
true,
|
||||||
|
isFirstMixBuffer,
|
||||||
|
nodeId);
|
||||||
|
|
||||||
|
destination.UpdateBiquadFilterEnabledPrev(0);
|
||||||
|
destination.UpdateBiquadFilterEnabledPrev(1);
|
||||||
|
}
|
||||||
|
else if (bqf0.Enable)
|
||||||
|
{
|
||||||
|
_commandBuffer.GenerateBiquadFilterAndMix(
|
||||||
|
previousMixVolume,
|
||||||
|
mixVolume,
|
||||||
|
bufferIndex,
|
||||||
|
bufferOffset + (uint)i,
|
||||||
|
i,
|
||||||
|
state,
|
||||||
|
ref bqf0,
|
||||||
|
bqfState[..1],
|
||||||
|
bqfState.Slice(1, 1),
|
||||||
|
!destination.IsBiquadFilterEnabledPrev(),
|
||||||
|
true,
|
||||||
|
isFirstMixBuffer,
|
||||||
|
nodeId);
|
||||||
|
|
||||||
|
destination.UpdateBiquadFilterEnabledPrev(0);
|
||||||
|
}
|
||||||
|
else if (bqf1.Enable)
|
||||||
|
{
|
||||||
|
_commandBuffer.GenerateBiquadFilterAndMix(
|
||||||
|
previousMixVolume,
|
||||||
|
mixVolume,
|
||||||
|
bufferIndex,
|
||||||
|
bufferOffset + (uint)i,
|
||||||
|
i,
|
||||||
|
state,
|
||||||
|
ref bqf1,
|
||||||
|
bqfState[..1],
|
||||||
|
bqfState.Slice(1, 1),
|
||||||
|
!destination.IsBiquadFilterEnabledPrev(),
|
||||||
|
true,
|
||||||
|
isFirstMixBuffer,
|
||||||
|
nodeId);
|
||||||
|
|
||||||
|
destination.UpdateBiquadFilterEnabledPrev(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
isFirstMixBuffer = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GenerateVoiceMix(
|
||||||
|
ReadOnlySpan<float> mixVolumes,
|
||||||
|
ReadOnlySpan<float> previousMixVolumes,
|
||||||
|
Memory<VoiceUpdateState> state,
|
||||||
|
uint bufferOffset,
|
||||||
|
uint bufferCount,
|
||||||
|
uint bufferIndex,
|
||||||
|
int nodeId)
|
||||||
{
|
{
|
||||||
if (bufferCount > Constants.VoiceChannelCountMax)
|
if (bufferCount > Constants.VoiceChannelCountMax)
|
||||||
{
|
{
|
||||||
_commandBuffer.GenerateMixRampGrouped(bufferCount,
|
_commandBuffer.GenerateMixRampGrouped(
|
||||||
bufferIndex,
|
bufferCount,
|
||||||
bufferOffset,
|
bufferIndex,
|
||||||
previousMixVolumes,
|
bufferOffset,
|
||||||
mixVolumes,
|
previousMixVolumes,
|
||||||
state,
|
mixVolumes,
|
||||||
nodeId);
|
state,
|
||||||
|
nodeId);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -188,13 +294,14 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||||||
|
|
||||||
if (mixVolume != 0.0f || previousMixVolume != 0.0f)
|
if (mixVolume != 0.0f || previousMixVolume != 0.0f)
|
||||||
{
|
{
|
||||||
_commandBuffer.GenerateMixRamp(previousMixVolume,
|
_commandBuffer.GenerateMixRamp(
|
||||||
mixVolume,
|
previousMixVolume,
|
||||||
bufferIndex,
|
mixVolume,
|
||||||
bufferOffset + (uint)i,
|
bufferIndex,
|
||||||
i,
|
bufferOffset + (uint)i,
|
||||||
state,
|
i,
|
||||||
nodeId);
|
state,
|
||||||
|
nodeId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -271,10 +378,11 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||||||
GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId);
|
GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
_commandBuffer.GenerateVolumeRamp(voiceState.PreviousVolume,
|
_commandBuffer.GenerateVolumeRamp(
|
||||||
voiceState.Volume,
|
voiceState.PreviousVolume,
|
||||||
_rendererContext.MixBufferCount + (uint)channelIndex,
|
voiceState.Volume,
|
||||||
nodeId);
|
_rendererContext.MixBufferCount + (uint)channelIndex,
|
||||||
|
nodeId);
|
||||||
|
|
||||||
if (performanceInitialized)
|
if (performanceInitialized)
|
||||||
{
|
{
|
||||||
@ -291,15 +399,13 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
Span<SplitterDestination> destinationSpan = _splitterContext.GetDestination((int)voiceState.SplitterId, destinationId);
|
SplitterDestination destination = _splitterContext.GetDestination((int)voiceState.SplitterId, destinationId);
|
||||||
|
|
||||||
if (destinationSpan.IsEmpty)
|
if (destination.IsNull)
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
ref SplitterDestination destination = ref destinationSpan[0];
|
|
||||||
|
|
||||||
destinationId += (int)channelsCount;
|
destinationId += (int)channelsCount;
|
||||||
|
|
||||||
if (destination.IsConfigured())
|
if (destination.IsConfigured())
|
||||||
@ -310,13 +416,27 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||||||
{
|
{
|
||||||
ref MixState mix = ref _mixContext.GetState(mixId);
|
ref MixState mix = ref _mixContext.GetState(mixId);
|
||||||
|
|
||||||
GenerateVoiceMix(destination.MixBufferVolume,
|
if (destination.IsBiquadFilterEnabled())
|
||||||
destination.PreviousMixBufferVolume,
|
{
|
||||||
dspStateMemory,
|
GenerateVoiceMixWithSplitter(
|
||||||
mix.BufferOffset,
|
destination,
|
||||||
mix.BufferCount,
|
dspStateMemory,
|
||||||
_rendererContext.MixBufferCount + (uint)channelIndex,
|
mix.BufferOffset,
|
||||||
nodeId);
|
mix.BufferCount,
|
||||||
|
_rendererContext.MixBufferCount + (uint)channelIndex,
|
||||||
|
nodeId);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GenerateVoiceMix(
|
||||||
|
destination.MixBufferVolume,
|
||||||
|
destination.PreviousMixBufferVolume,
|
||||||
|
dspStateMemory,
|
||||||
|
mix.BufferOffset,
|
||||||
|
mix.BufferCount,
|
||||||
|
_rendererContext.MixBufferCount + (uint)channelIndex,
|
||||||
|
nodeId);
|
||||||
|
}
|
||||||
|
|
||||||
destination.MarkAsNeedToUpdateInternalState();
|
destination.MarkAsNeedToUpdateInternalState();
|
||||||
}
|
}
|
||||||
@ -337,13 +457,14 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||||||
GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId);
|
GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
GenerateVoiceMix(channelResource.Mix.AsSpan(),
|
GenerateVoiceMix(
|
||||||
channelResource.PreviousMix.AsSpan(),
|
channelResource.Mix.AsSpan(),
|
||||||
dspStateMemory,
|
channelResource.PreviousMix.AsSpan(),
|
||||||
mix.BufferOffset,
|
dspStateMemory,
|
||||||
mix.BufferCount,
|
mix.BufferOffset,
|
||||||
_rendererContext.MixBufferCount + (uint)channelIndex,
|
mix.BufferCount,
|
||||||
nodeId);
|
_rendererContext.MixBufferCount + (uint)channelIndex,
|
||||||
|
nodeId);
|
||||||
|
|
||||||
if (performanceInitialized)
|
if (performanceInitialized)
|
||||||
{
|
{
|
||||||
@ -409,10 +530,11 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||||||
{
|
{
|
||||||
if (effect.Parameter.Volumes[i] != 0.0f)
|
if (effect.Parameter.Volumes[i] != 0.0f)
|
||||||
{
|
{
|
||||||
_commandBuffer.GenerateMix((uint)bufferOffset + effect.Parameter.Input[i],
|
_commandBuffer.GenerateMix(
|
||||||
(uint)bufferOffset + effect.Parameter.Output[i],
|
(uint)bufferOffset + effect.Parameter.Input[i],
|
||||||
nodeId,
|
(uint)bufferOffset + effect.Parameter.Output[i],
|
||||||
effect.Parameter.Volumes[i]);
|
nodeId,
|
||||||
|
effect.Parameter.Volumes[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -447,17 +569,18 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||||||
updateCount = newUpdateCount;
|
updateCount = newUpdateCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
_commandBuffer.GenerateAuxEffect(bufferOffset,
|
_commandBuffer.GenerateAuxEffect(
|
||||||
effect.Parameter.Input[i],
|
bufferOffset,
|
||||||
effect.Parameter.Output[i],
|
effect.Parameter.Input[i],
|
||||||
ref effect.State,
|
effect.Parameter.Output[i],
|
||||||
effect.IsEnabled,
|
ref effect.State,
|
||||||
effect.Parameter.BufferStorageSize,
|
effect.IsEnabled,
|
||||||
effect.State.SendBufferInfoBase,
|
effect.Parameter.BufferStorageSize,
|
||||||
effect.State.ReturnBufferInfoBase,
|
effect.State.SendBufferInfoBase,
|
||||||
updateCount,
|
effect.State.ReturnBufferInfoBase,
|
||||||
writeOffset,
|
updateCount,
|
||||||
nodeId);
|
writeOffset,
|
||||||
|
nodeId);
|
||||||
|
|
||||||
writeOffset = newUpdateCount;
|
writeOffset = newUpdateCount;
|
||||||
|
|
||||||
@ -500,7 +623,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||||||
if (effect.IsEnabled)
|
if (effect.IsEnabled)
|
||||||
{
|
{
|
||||||
bool needInitialization = effect.Parameter.Status == UsageState.Invalid ||
|
bool needInitialization = effect.Parameter.Status == UsageState.Invalid ||
|
||||||
(effect.Parameter.Status == UsageState.New && !_rendererContext.BehaviourContext.IsBiquadFilterEffectStateClearBugFixed());
|
(effect.Parameter.Status == UsageState.New && !_rendererContext.BehaviourContext.IsBiquadFilterEffectStateClearBugFixed());
|
||||||
|
|
||||||
BiquadFilterParameter parameter = new()
|
BiquadFilterParameter parameter = new()
|
||||||
{
|
{
|
||||||
@ -512,11 +635,14 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||||||
|
|
||||||
for (int i = 0; i < effect.Parameter.ChannelCount; i++)
|
for (int i = 0; i < effect.Parameter.ChannelCount; i++)
|
||||||
{
|
{
|
||||||
_commandBuffer.GenerateBiquadFilter((int)bufferOffset, ref parameter, effect.State.Slice(i, 1),
|
_commandBuffer.GenerateBiquadFilter(
|
||||||
effect.Parameter.Input[i],
|
(int)bufferOffset,
|
||||||
effect.Parameter.Output[i],
|
ref parameter,
|
||||||
needInitialization,
|
effect.State.Slice(i, 1),
|
||||||
nodeId);
|
effect.Parameter.Input[i],
|
||||||
|
effect.Parameter.Output[i],
|
||||||
|
needInitialization,
|
||||||
|
nodeId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -591,15 +717,16 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||||||
updateCount = newUpdateCount;
|
updateCount = newUpdateCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
_commandBuffer.GenerateCaptureEffect(bufferOffset,
|
_commandBuffer.GenerateCaptureEffect(
|
||||||
effect.Parameter.Input[i],
|
bufferOffset,
|
||||||
effect.State.SendBufferInfo,
|
effect.Parameter.Input[i],
|
||||||
effect.IsEnabled,
|
effect.State.SendBufferInfo,
|
||||||
effect.Parameter.BufferStorageSize,
|
effect.IsEnabled,
|
||||||
effect.State.SendBufferInfoBase,
|
effect.Parameter.BufferStorageSize,
|
||||||
updateCount,
|
effect.State.SendBufferInfoBase,
|
||||||
writeOffset,
|
updateCount,
|
||||||
nodeId);
|
writeOffset,
|
||||||
|
nodeId);
|
||||||
|
|
||||||
writeOffset = newUpdateCount;
|
writeOffset = newUpdateCount;
|
||||||
|
|
||||||
@ -608,15 +735,28 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GenerateCompressorEffect(uint bufferOffset, CompressorEffect effect, int nodeId)
|
private void GenerateCompressorEffect(uint bufferOffset, CompressorEffect effect, int nodeId, int effectId)
|
||||||
{
|
{
|
||||||
Debug.Assert(effect.Type == EffectType.Compressor);
|
Debug.Assert(effect.Type == EffectType.Compressor);
|
||||||
|
|
||||||
_commandBuffer.GenerateCompressorEffect(bufferOffset,
|
Memory<EffectResultState> dspResultState;
|
||||||
effect.Parameter,
|
|
||||||
effect.State,
|
if (effect.Parameter.StatisticsEnabled)
|
||||||
effect.IsEnabled,
|
{
|
||||||
nodeId);
|
dspResultState = _effectContext.GetDspStateMemory(effectId);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
dspResultState = Memory<EffectResultState>.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
_commandBuffer.GenerateCompressorEffect(
|
||||||
|
bufferOffset,
|
||||||
|
effect.Parameter,
|
||||||
|
effect.State,
|
||||||
|
dspResultState,
|
||||||
|
effect.IsEnabled,
|
||||||
|
nodeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GenerateEffect(ref MixState mix, int effectId, BaseEffect effect)
|
private void GenerateEffect(ref MixState mix, int effectId, BaseEffect effect)
|
||||||
@ -629,8 +769,11 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||||||
|
|
||||||
bool performanceInitialized = false;
|
bool performanceInitialized = false;
|
||||||
|
|
||||||
if (_performanceManager != null && _performanceManager.GetNextEntry(out performanceEntry, effect.GetPerformanceDetailType(),
|
if (_performanceManager != null && _performanceManager.GetNextEntry(
|
||||||
isFinalMix ? PerformanceEntryType.FinalMix : PerformanceEntryType.SubMix, nodeId))
|
out performanceEntry,
|
||||||
|
effect.GetPerformanceDetailType(),
|
||||||
|
isFinalMix ? PerformanceEntryType.FinalMix : PerformanceEntryType.SubMix,
|
||||||
|
nodeId))
|
||||||
{
|
{
|
||||||
performanceInitialized = true;
|
performanceInitialized = true;
|
||||||
|
|
||||||
@ -664,7 +807,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||||||
GenerateCaptureEffect(mix.BufferOffset, (CaptureBufferEffect)effect, nodeId);
|
GenerateCaptureEffect(mix.BufferOffset, (CaptureBufferEffect)effect, nodeId);
|
||||||
break;
|
break;
|
||||||
case EffectType.Compressor:
|
case EffectType.Compressor:
|
||||||
GenerateCompressorEffect(mix.BufferOffset, (CompressorEffect)effect, nodeId);
|
GenerateCompressorEffect(mix.BufferOffset, (CompressorEffect)effect, nodeId, effectId);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new NotImplementedException($"Unsupported effect type {effect.Type}");
|
throw new NotImplementedException($"Unsupported effect type {effect.Type}");
|
||||||
@ -706,6 +849,85 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void GenerateMixWithSplitter(
|
||||||
|
uint inputBufferIndex,
|
||||||
|
uint outputBufferIndex,
|
||||||
|
float volume,
|
||||||
|
SplitterDestination destination,
|
||||||
|
ref bool isFirstMixBuffer,
|
||||||
|
int nodeId)
|
||||||
|
{
|
||||||
|
ref BiquadFilterParameter bqf0 = ref destination.GetBiquadFilterParameter(0);
|
||||||
|
ref BiquadFilterParameter bqf1 = ref destination.GetBiquadFilterParameter(1);
|
||||||
|
|
||||||
|
Memory<BiquadFilterState> bqfState = _splitterContext.GetBiquadFilterState(destination);
|
||||||
|
|
||||||
|
if (bqf0.Enable && bqf1.Enable)
|
||||||
|
{
|
||||||
|
_commandBuffer.GenerateMultiTapBiquadFilterAndMix(
|
||||||
|
0f,
|
||||||
|
volume,
|
||||||
|
inputBufferIndex,
|
||||||
|
outputBufferIndex,
|
||||||
|
0,
|
||||||
|
Memory<VoiceUpdateState>.Empty,
|
||||||
|
ref bqf0,
|
||||||
|
ref bqf1,
|
||||||
|
bqfState[..1],
|
||||||
|
bqfState.Slice(1, 1),
|
||||||
|
bqfState.Slice(2, 1),
|
||||||
|
bqfState.Slice(3, 1),
|
||||||
|
!destination.IsBiquadFilterEnabledPrev(),
|
||||||
|
!destination.IsBiquadFilterEnabledPrev(),
|
||||||
|
false,
|
||||||
|
isFirstMixBuffer,
|
||||||
|
nodeId);
|
||||||
|
|
||||||
|
destination.UpdateBiquadFilterEnabledPrev(0);
|
||||||
|
destination.UpdateBiquadFilterEnabledPrev(1);
|
||||||
|
}
|
||||||
|
else if (bqf0.Enable)
|
||||||
|
{
|
||||||
|
_commandBuffer.GenerateBiquadFilterAndMix(
|
||||||
|
0f,
|
||||||
|
volume,
|
||||||
|
inputBufferIndex,
|
||||||
|
outputBufferIndex,
|
||||||
|
0,
|
||||||
|
Memory<VoiceUpdateState>.Empty,
|
||||||
|
ref bqf0,
|
||||||
|
bqfState[..1],
|
||||||
|
bqfState.Slice(1, 1),
|
||||||
|
!destination.IsBiquadFilterEnabledPrev(),
|
||||||
|
false,
|
||||||
|
isFirstMixBuffer,
|
||||||
|
nodeId);
|
||||||
|
|
||||||
|
destination.UpdateBiquadFilterEnabledPrev(0);
|
||||||
|
}
|
||||||
|
else if (bqf1.Enable)
|
||||||
|
{
|
||||||
|
_commandBuffer.GenerateBiquadFilterAndMix(
|
||||||
|
0f,
|
||||||
|
volume,
|
||||||
|
inputBufferIndex,
|
||||||
|
outputBufferIndex,
|
||||||
|
0,
|
||||||
|
Memory<VoiceUpdateState>.Empty,
|
||||||
|
ref bqf1,
|
||||||
|
bqfState[..1],
|
||||||
|
bqfState.Slice(1, 1),
|
||||||
|
!destination.IsBiquadFilterEnabledPrev(),
|
||||||
|
false,
|
||||||
|
isFirstMixBuffer,
|
||||||
|
nodeId);
|
||||||
|
|
||||||
|
destination.UpdateBiquadFilterEnabledPrev(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
isFirstMixBuffer = false;
|
||||||
|
}
|
||||||
|
|
||||||
private void GenerateMix(ref MixState mix)
|
private void GenerateMix(ref MixState mix)
|
||||||
{
|
{
|
||||||
if (mix.HasAnyDestination())
|
if (mix.HasAnyDestination())
|
||||||
@ -722,15 +944,13 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||||||
{
|
{
|
||||||
int destinationIndex = destinationId++;
|
int destinationIndex = destinationId++;
|
||||||
|
|
||||||
Span<SplitterDestination> destinationSpan = _splitterContext.GetDestination((int)mix.DestinationSplitterId, destinationIndex);
|
SplitterDestination destination = _splitterContext.GetDestination((int)mix.DestinationSplitterId, destinationIndex);
|
||||||
|
|
||||||
if (destinationSpan.IsEmpty)
|
if (destination.IsNull)
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
ref SplitterDestination destination = ref destinationSpan[0];
|
|
||||||
|
|
||||||
if (destination.IsConfigured())
|
if (destination.IsConfigured())
|
||||||
{
|
{
|
||||||
int mixId = destination.DestinationId;
|
int mixId = destination.DestinationId;
|
||||||
@ -741,16 +961,32 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||||||
|
|
||||||
uint inputBufferIndex = mix.BufferOffset + ((uint)destinationIndex % mix.BufferCount);
|
uint inputBufferIndex = mix.BufferOffset + ((uint)destinationIndex % mix.BufferCount);
|
||||||
|
|
||||||
|
bool isFirstMixBuffer = true;
|
||||||
|
|
||||||
for (uint bufferDestinationIndex = 0; bufferDestinationIndex < destinationMix.BufferCount; bufferDestinationIndex++)
|
for (uint bufferDestinationIndex = 0; bufferDestinationIndex < destinationMix.BufferCount; bufferDestinationIndex++)
|
||||||
{
|
{
|
||||||
float volume = mix.Volume * destination.GetMixVolume((int)bufferDestinationIndex);
|
float volume = mix.Volume * destination.GetMixVolume((int)bufferDestinationIndex);
|
||||||
|
|
||||||
if (volume != 0.0f)
|
if (volume != 0.0f)
|
||||||
{
|
{
|
||||||
_commandBuffer.GenerateMix(inputBufferIndex,
|
if (destination.IsBiquadFilterEnabled())
|
||||||
destinationMix.BufferOffset + bufferDestinationIndex,
|
{
|
||||||
mix.NodeId,
|
GenerateMixWithSplitter(
|
||||||
volume);
|
inputBufferIndex,
|
||||||
|
destinationMix.BufferOffset + bufferDestinationIndex,
|
||||||
|
volume,
|
||||||
|
destination,
|
||||||
|
ref isFirstMixBuffer,
|
||||||
|
mix.NodeId);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_commandBuffer.GenerateMix(
|
||||||
|
inputBufferIndex,
|
||||||
|
destinationMix.BufferOffset + bufferDestinationIndex,
|
||||||
|
mix.NodeId,
|
||||||
|
volume);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -770,10 +1006,11 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||||||
|
|
||||||
if (volume != 0.0f)
|
if (volume != 0.0f)
|
||||||
{
|
{
|
||||||
_commandBuffer.GenerateMix(mix.BufferOffset + bufferIndex,
|
_commandBuffer.GenerateMix(
|
||||||
destinationMix.BufferOffset + bufferDestinationIndex,
|
mix.BufferOffset + bufferIndex,
|
||||||
mix.NodeId,
|
destinationMix.BufferOffset + bufferDestinationIndex,
|
||||||
volume);
|
mix.NodeId,
|
||||||
|
volume);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -783,11 +1020,12 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||||||
|
|
||||||
private void GenerateSubMix(ref MixState subMix)
|
private void GenerateSubMix(ref MixState subMix)
|
||||||
{
|
{
|
||||||
_commandBuffer.GenerateDepopForMixBuffersCommand(_rendererContext.DepopBuffer,
|
_commandBuffer.GenerateDepopForMixBuffers(
|
||||||
subMix.BufferOffset,
|
_rendererContext.DepopBuffer,
|
||||||
subMix.BufferCount,
|
subMix.BufferOffset,
|
||||||
subMix.NodeId,
|
subMix.BufferCount,
|
||||||
subMix.SampleRate);
|
subMix.NodeId,
|
||||||
|
subMix.SampleRate);
|
||||||
|
|
||||||
GenerateEffects(ref subMix);
|
GenerateEffects(ref subMix);
|
||||||
|
|
||||||
@ -847,11 +1085,12 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||||||
{
|
{
|
||||||
ref MixState finalMix = ref _mixContext.GetFinalState();
|
ref MixState finalMix = ref _mixContext.GetFinalState();
|
||||||
|
|
||||||
_commandBuffer.GenerateDepopForMixBuffersCommand(_rendererContext.DepopBuffer,
|
_commandBuffer.GenerateDepopForMixBuffers(
|
||||||
finalMix.BufferOffset,
|
_rendererContext.DepopBuffer,
|
||||||
finalMix.BufferCount,
|
finalMix.BufferOffset,
|
||||||
finalMix.NodeId,
|
finalMix.BufferCount,
|
||||||
finalMix.SampleRate);
|
finalMix.NodeId,
|
||||||
|
finalMix.SampleRate);
|
||||||
|
|
||||||
GenerateEffects(ref finalMix);
|
GenerateEffects(ref finalMix);
|
||||||
|
|
||||||
@ -882,9 +1121,10 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||||||
GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId);
|
GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
_commandBuffer.GenerateVolume(finalMix.Volume,
|
_commandBuffer.GenerateVolume(
|
||||||
finalMix.BufferOffset + bufferIndex,
|
finalMix.Volume,
|
||||||
nodeId);
|
finalMix.BufferOffset + bufferIndex,
|
||||||
|
nodeId);
|
||||||
|
|
||||||
if (performanceSubInitialized)
|
if (performanceSubInitialized)
|
||||||
{
|
{
|
||||||
@ -938,41 +1178,45 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||||||
|
|
||||||
if (useCustomDownMixingCommand)
|
if (useCustomDownMixingCommand)
|
||||||
{
|
{
|
||||||
_commandBuffer.GenerateDownMixSurroundToStereo(finalMix.BufferOffset,
|
_commandBuffer.GenerateDownMixSurroundToStereo(
|
||||||
sink.Parameter.Input.AsSpan(),
|
finalMix.BufferOffset,
|
||||||
sink.Parameter.Input.AsSpan(),
|
sink.Parameter.Input.AsSpan(),
|
||||||
sink.DownMixCoefficients,
|
sink.Parameter.Input.AsSpan(),
|
||||||
Constants.InvalidNodeId);
|
sink.DownMixCoefficients,
|
||||||
|
Constants.InvalidNodeId);
|
||||||
}
|
}
|
||||||
// NOTE: We do the downmixing at the DSP level as it's easier that way.
|
// NOTE: We do the downmixing at the DSP level as it's easier that way.
|
||||||
else if (_rendererContext.ChannelCount == 2 && sink.Parameter.InputCount == 6)
|
else if (_rendererContext.ChannelCount == 2 && sink.Parameter.InputCount == 6)
|
||||||
{
|
{
|
||||||
_commandBuffer.GenerateDownMixSurroundToStereo(finalMix.BufferOffset,
|
_commandBuffer.GenerateDownMixSurroundToStereo(
|
||||||
sink.Parameter.Input.AsSpan(),
|
finalMix.BufferOffset,
|
||||||
sink.Parameter.Input.AsSpan(),
|
sink.Parameter.Input.AsSpan(),
|
||||||
Constants.DefaultSurroundToStereoCoefficients,
|
sink.Parameter.Input.AsSpan(),
|
||||||
Constants.InvalidNodeId);
|
Constants.DefaultSurroundToStereoCoefficients,
|
||||||
|
Constants.InvalidNodeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
CommandList commandList = _commandBuffer.CommandList;
|
CommandList commandList = _commandBuffer.CommandList;
|
||||||
|
|
||||||
if (sink.UpsamplerState != null)
|
if (sink.UpsamplerState != null)
|
||||||
{
|
{
|
||||||
_commandBuffer.GenerateUpsample(finalMix.BufferOffset,
|
_commandBuffer.GenerateUpsample(
|
||||||
sink.UpsamplerState,
|
finalMix.BufferOffset,
|
||||||
sink.Parameter.InputCount,
|
sink.UpsamplerState,
|
||||||
sink.Parameter.Input.AsSpan(),
|
sink.Parameter.InputCount,
|
||||||
commandList.BufferCount,
|
sink.Parameter.Input.AsSpan(),
|
||||||
commandList.SampleCount,
|
commandList.BufferCount,
|
||||||
commandList.SampleRate,
|
commandList.SampleCount,
|
||||||
Constants.InvalidNodeId);
|
commandList.SampleRate,
|
||||||
|
Constants.InvalidNodeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
_commandBuffer.GenerateDeviceSink(finalMix.BufferOffset,
|
_commandBuffer.GenerateDeviceSink(
|
||||||
sink,
|
finalMix.BufferOffset,
|
||||||
_rendererContext.SessionId,
|
sink,
|
||||||
commandList.Buffers,
|
_rendererContext.SessionId,
|
||||||
Constants.InvalidNodeId);
|
commandList.Buffers,
|
||||||
|
Constants.InvalidNodeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GenerateSink(BaseSink sink, ref MixState finalMix)
|
private void GenerateSink(BaseSink sink, ref MixState finalMix)
|
||||||
|
@ -170,7 +170,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public uint Estimate(GroupedBiquadFilterCommand command)
|
public uint Estimate(MultiTapBiquadFilterCommand command)
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -184,5 +184,15 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public uint Estimate(BiquadFilterAndMixCommand command)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public uint Estimate(MultiTapBiquadFilterAndMixCommand command)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -462,7 +462,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public uint Estimate(GroupedBiquadFilterCommand command)
|
public uint Estimate(MultiTapBiquadFilterCommand command)
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -476,5 +476,15 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public uint Estimate(BiquadFilterAndMixCommand command)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public uint Estimate(MultiTapBiquadFilterAndMixCommand command)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -632,7 +632,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual uint Estimate(GroupedBiquadFilterCommand command)
|
public virtual uint Estimate(MultiTapBiquadFilterCommand command)
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -646,5 +646,15 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public virtual uint Estimate(BiquadFilterAndMixCommand command)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual uint Estimate(MultiTapBiquadFilterAndMixCommand command)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user