Compare commits
136 Commits
Author | SHA1 | Date | |
---|---|---|---|
9e50dd99d7 | |||
0dec91bb42 | |||
d9b63353b0 | |||
eabd0ec93f | |||
138d5dc64a | |||
3e68a87d63 | |||
69b6ef7a4a | |||
40e87c634e | |||
79d1c190db | |||
2bc88467eb | |||
baf8752e74 | |||
d5e4378aea | |||
6dbcdfea47 | |||
c5258cf082 | |||
5c89e22bb9 | |||
11ecff2ff0 | |||
4c3f09644a | |||
e187a8870a | |||
a64fee29dc | |||
9ef94c8292 | |||
915d6d044c | |||
a4780ab33b | |||
a947a45d81 | |||
9db73f74cf | |||
a1efd87c45 | |||
49be977588 | |||
c95be55091 | |||
63dedbda86 | |||
c532118d94 | |||
52d6f2e656 | |||
c9bc4eaf58 | |||
3249f8ff41 | |||
1b41b285ac | |||
f5a6f45b27 | |||
210557951b | |||
4c2d9ff3ff | |||
8198b99935 | |||
460f96967d | |||
7ca779a26d | |||
b5032b3c91 | |||
f0a3dff136 | |||
f659dcb9d8 | |||
a34fb0e939 | |||
21ce8a9b80 | |||
9ecbee8032 | |||
80519af67d | |||
26e30faff3 | |||
0992310b76 | |||
009c1101d2 | |||
ba95ee54ab | |||
4ce4299ca2 | |||
17620d18db | |||
9f1cf6458c | |||
67b4e63cff | |||
c05c688ee8 | |||
b2623dc27d | |||
5131b71437 | |||
7870423671 | |||
b72916fbc1 | |||
da073fce61 | |||
1fc90e57d2 | |||
eafcc314a9 | |||
6e9bd4de13 | |||
05a41b31bc | |||
eed17f963e | |||
c09c0c002d | |||
d56d335c0b | |||
23c844b2aa | |||
81691b9e37 | |||
2dc422bc14 | |||
a80fa5e33f | |||
954e995321 | |||
dad9ab6bb6 | |||
f0562b9c75 | |||
b8556530f2 | |||
4f3af839be | |||
155736c986 | |||
dba908dc78 | |||
ecee34a50c | |||
9b5a0c3889 | |||
80b4972139 | |||
5d85468302 | |||
9b1cc2cec6 | |||
e691622f0a | |||
f663a5cd38 | |||
f7c2e867f4 | |||
cedd200745 | |||
58207685c0 | |||
095ad923ad | |||
f07ae7d53f | |||
c308f09722 | |||
f1eef29409 | |||
1f8d66db7c | |||
c3a5716a95 | |||
1f1e2a7f03 | |||
e54f9dc4b4 | |||
edfd4d70c0 | |||
fc43aecbbd | |||
58d7a1fe97 | |||
7aa430f1a5 | |||
6bf460e104 | |||
efb135b74c | |||
a707842e14 | |||
a5a9b9bc8b | |||
17078ad929 | |||
32450d45de | |||
ed7a0474c6 | |||
fe9c49949a | |||
052b23c83c | |||
e4f68592c3 | |||
1dcd44b94f | |||
61b1ce252f | |||
5f38086f94 | |||
7bae440d3a | |||
f1943fd0b6 | |||
ec8d4f3af5 | |||
b3f0978869 | |||
f614d2c435 | |||
40c9416097 | |||
618c8edc79 | |||
99fc4fa61b | |||
f6d5499a16 | |||
26bf13a65d | |||
96cf242bcf | |||
59755818ef | |||
f8beeeb7d3 | |||
cb250162cb | |||
7528f94536 | |||
43081c16c4 | |||
780627e7b0 | |||
9044cb38d1 | |||
a53cfdab78 | |||
c7f9962dde | |||
296c4a3d01 | |||
e7cf4e6eaf | |||
a1a4771ac1 |
@ -63,6 +63,10 @@ dotnet_code_quality_unused_parameters = all:suggestion
|
||||
|
||||
#### C# Coding Conventions ####
|
||||
|
||||
# Namespace preferences
|
||||
csharp_style_namespace_declarations = block_scoped:warning
|
||||
resharper_csharp_namespace_body = block_scoped
|
||||
|
||||
# var preferences
|
||||
csharp_style_var_elsewhere = false:silent
|
||||
csharp_style_var_for_built_in_types = false:silent
|
||||
|
43
.github/ISSUE_TEMPLATE/bug_report.md
vendored
43
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -1,43 +0,0 @@
|
||||
---
|
||||
name: Bug Report
|
||||
about: Something doesn't work correctly in Ryujinx. Game-specific issues should be posted at https://github.com/Ryujinx/Ryujinx-Games-List instead, unless it is a provable regression.
|
||||
#assignees:
|
||||
---
|
||||
|
||||
## Bug Report
|
||||
|
||||
[ If any section does not apply, replace its contents with "N/A". ]</br>
|
||||
[ Lines between [ ] (square brackets) should be removed before posting. ]
|
||||
|
||||
### What's the issue you encountered?
|
||||
|
||||
[ Describe the issue in detail and what you were doing beforehand. ]</br>
|
||||
[ Did you make any changes related to Ryujinx itself? ]</br>
|
||||
[ If so, make sure to include details relating to what exactly you changed. ]
|
||||
|
||||
### How can the issue be reproduced?
|
||||
|
||||
[ Include a detailed step by step process for recreating your issue. ]
|
||||
|
||||
### Log file
|
||||
|
||||
[ Logs files can be found under ``Logs`` folder in Ryujinx program folder. ]</br>
|
||||
[ If you don't include a crash report in instances of crash related issues, we will ask you one to provide one. ]
|
||||
|
||||
### Environment?
|
||||
|
||||
- Ryujinx version: 1.0.X</br>
|
||||
[ Replace X's with the Ryujinx version at time of crash. ]
|
||||
- Game version: X.X.X</br>
|
||||
[ Replace X's with the game version at time of crash. ]
|
||||
- System Specs:
|
||||
- OS: *(e.g. Windows 10)*
|
||||
- CPU: *(e.g. i7-6700)*
|
||||
- GPU: *(e.g. NVIDIA RTX 2070)*
|
||||
- RAM: *(e.g. 16GiB)*
|
||||
- Applied Mods : [ Yes (Which ones) / No ]
|
||||
|
||||
### Additional context?
|
||||
|
||||
Additional info about your environment:</br>
|
||||
[ Any other information relevant to your issue. ]
|
86
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
86
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
@ -0,0 +1,86 @@
|
||||
name: Bug Report
|
||||
description: File a bug report
|
||||
title: "[Bug]"
|
||||
labels: bug
|
||||
body:
|
||||
- type: textarea
|
||||
id: issue
|
||||
attributes:
|
||||
label: Description of the issue
|
||||
description: What's the issue you encountered?
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: repro
|
||||
attributes:
|
||||
label: Reproduction steps
|
||||
description: How can the issue be reproduced?
|
||||
placeholder: Describe each step as precisely as possible
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: log
|
||||
attributes:
|
||||
label: Log file
|
||||
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
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: os
|
||||
attributes:
|
||||
label: OS
|
||||
placeholder: "e.g. Windows 10"
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: ryujinx-version
|
||||
attributes:
|
||||
label: Ryujinx version
|
||||
placeholder: "e.g. 1.0.470"
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: game-version
|
||||
attributes:
|
||||
label: Game version
|
||||
placeholder: "e.g. 1.1.1"
|
||||
validations:
|
||||
required: false
|
||||
- type: input
|
||||
id: cpu
|
||||
attributes:
|
||||
label: CPU
|
||||
placeholder: "e.g. i7-6700"
|
||||
validations:
|
||||
required: false
|
||||
- type: input
|
||||
id: gpu
|
||||
attributes:
|
||||
label: GPU
|
||||
placeholder: "e.g. NVIDIA RTX 2070"
|
||||
validations:
|
||||
required: false
|
||||
- type: input
|
||||
id: ram
|
||||
attributes:
|
||||
label: RAM
|
||||
placeholder: "e.g. 16GB"
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
id: mods
|
||||
attributes:
|
||||
label: List of applied mods
|
||||
placeholder: You can list applied mods here.
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
id: additional-context
|
||||
attributes:
|
||||
label: Additional context?
|
||||
description: |
|
||||
- Additional info about your environment:
|
||||
- Any other information relevant to your issue.
|
||||
validations:
|
||||
required: false
|
34
.github/ISSUE_TEMPLATE/feature_request.md
vendored
34
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@ -1,34 +0,0 @@
|
||||
---
|
||||
name: Feature Request
|
||||
about: Suggest a new feature for Ryujinx.
|
||||
#assignees:
|
||||
---
|
||||
|
||||
## Feature Request
|
||||
|
||||
[ If any section does not apply, replace its contents with "N/A". ]</br>
|
||||
[ If you do not have the information needed for a section, replace its contents with "Unknown". ]</br>
|
||||
[ Lines between [ ] (square brackets) are to be removed before posting. ]</br>
|
||||
|
||||
[ Please search for existing [feature requests](https://github.com/Ryujinx/Ryujinx/issues) before you make your own request. ]</br>
|
||||
[ Duplicate requests will be marked as such and you will be referred to the original request. ]
|
||||
|
||||
### What feature are you suggesting?
|
||||
#### Overview:
|
||||
- [ Include the basic, high-level concepts for this feature here. ]
|
||||
|
||||
#### Smaller Details:
|
||||
- [ These may include specific methods of implementation etc. ]
|
||||
|
||||
#### Nature of Request:
|
||||
[ Remove all that do not apply to your request. ]
|
||||
- Addition
|
||||
- [ Ex: Addition of certain original features or features from other community projects. ]
|
||||
- [ If you are suggesting porting features or including features from other projects, include what license they are distributed under and what, if any libraries those project use. ]
|
||||
- Change
|
||||
- Removal
|
||||
- [Ex: Removal of certain features or implementation due to a specific issue/bug or because of low quality code, etc.]
|
||||
|
||||
### Why would this feature be useful?
|
||||
[ If this is a feature for an end-user, how does it benefit the end-user? ]</br>
|
||||
[ If this feature is for developers, what does it add to Ryujinx that did not already exist? ]
|
30
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
30
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
name: Feature Request
|
||||
description: Suggest a new feature for Ryujinx.
|
||||
title: "[Feature Request]"
|
||||
body:
|
||||
- type: textarea
|
||||
id: overview
|
||||
attributes:
|
||||
label: Overview
|
||||
description: Include the basic, high-level concepts for this feature here.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: details
|
||||
attributes:
|
||||
label: Smaller details
|
||||
description: These may include specific methods of implementation etc.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: request
|
||||
attributes:
|
||||
label: Nature of request
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: feature
|
||||
attributes:
|
||||
label: Why would this feature be useful?
|
||||
validations:
|
||||
required: true
|
@ -1,34 +0,0 @@
|
||||
---
|
||||
name: Missing CPU Instruction
|
||||
about: CPU Instruction is missing in Ryujinx.
|
||||
#assignees:
|
||||
---
|
||||
|
||||
## Missing CPU Instruction
|
||||
|
||||
[ If any section does not apply, replace its contents with "N/A". ]</br>
|
||||
[ If you do not have the information needed for a section, replace its contents with "Unknown". ]</br>
|
||||
[ Lines between [ ] (square brackets) are to be removed before posting. ]
|
||||
|
||||
[ Please search for existing [missing CPU instruction](https://github.com/Ryujinx/Ryujinx/issues) before you make your own issue. ]</br>
|
||||
[ See the following [issue](https://github.com/Ryujinx/Ryujinx/issues/1405) as an example ]</br>
|
||||
[ Duplicate issue will be marked as such and you will be referred to the original request. ]
|
||||
|
||||
### What CPU instruction is missing?
|
||||
|
||||
Requires the *INSTRUCTION* instruction.</br>
|
||||
[ Replace *INSTRUCTION* by the instruction name, e.g. VADDL.U16 ]
|
||||
|
||||
```
|
||||
*
|
||||
```
|
||||
[ Add the undefined instruction error message in the above code block ]
|
||||
|
||||
### Instruction name
|
||||
```
|
||||
*
|
||||
```
|
||||
[ Include the name from [armconverter.com](https://armconverter.com/?disasm) or [shell-storm.org](http://shell-storm.org/online/Online-Assembler-and-Disassembler/?arch=arm64&endianness=big&dis_with_raw=True&dis_with_ins=True) in the above code block ]
|
||||
|
||||
### Required by:
|
||||
[ Add our (games list database)[https://github.com/Ryujinx/Ryujinx-Games-List/issues] links of games who require this instruction ]
|
26
.github/ISSUE_TEMPLATE/missing_cpu_instruction.yml
vendored
Normal file
26
.github/ISSUE_TEMPLATE/missing_cpu_instruction.yml
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
name: Missing CPU Instruction
|
||||
description: CPU Instruction is missing in Ryujinx.
|
||||
title: "[CPU]"
|
||||
labels: [cpu, not-implemented]
|
||||
body:
|
||||
- type: textarea
|
||||
id: instruction
|
||||
attributes:
|
||||
label: CPU instruction
|
||||
description: What CPU instruction is missing?
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: name
|
||||
attributes:
|
||||
label: Instruction name
|
||||
description: Include the name from [armconverter.com](https://armconverter.com/?disasm) or [shell-storm.org](http://shell-storm.org/online/Online-Assembler-and-Disassembler/?arch=arm64&endianness=big&dis_with_raw=True&dis_with_ins=True) in the above code block
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: required
|
||||
attributes:
|
||||
label: Required by
|
||||
description: Add links to the [compatibility list page(s)](https://github.com/Ryujinx/Ryujinx-Games-List/issues) of the game(s) that require this instruction.
|
||||
validations:
|
||||
required: true
|
35
.github/ISSUE_TEMPLATE/missing_service_call.md
vendored
35
.github/ISSUE_TEMPLATE/missing_service_call.md
vendored
@ -1,35 +0,0 @@
|
||||
---
|
||||
name: Missing Service Call
|
||||
about: Service call is missing in Ryujinx.
|
||||
#assignees:
|
||||
---
|
||||
|
||||
## Missing Service Call
|
||||
|
||||
[ If any section does not apply, replace its contents with "N/A". ]</br>
|
||||
[ If you do not have the information needed for a section, replace its contents with "Unknown". ]</br>
|
||||
[ Lines between [ ] (square brackets) are to be removed before posting. ]
|
||||
|
||||
[ Please search for existing [missing service call](https://github.com/Ryujinx/Ryujinx/issues) before you make your own issue. ]</br>
|
||||
[ See the following [issue](https://github.com/Ryujinx/Ryujinx/issues/1431) as an example ]</br>
|
||||
[ Duplicate issue will be marked as such and you will be referred to the original request. ]
|
||||
|
||||
### What service call is missing?
|
||||
|
||||
*SERVICE* *INTERFACE*: *NUMBER* (*NAME*) is not implemented.</br>
|
||||
[ Replace *SERVICE* by the service name, e.g. appletAE ]</br>
|
||||
[ Replace *INTERFACE* by the interface name, e.g. IAllSystemAppletProxiesService ]</br>
|
||||
[ Replace *NUMBER* by the call number, e.g. 100 ]</br>
|
||||
[ Replace *NAME* by the call name, e.g. OpenSystemAppletProxy ]</br>
|
||||
[ e.g. appletAE IAllSystemAppletProxiesService: 100 (OpenSystemAppletProxy) ]
|
||||
|
||||
[ Add related links to the specific call from [Switchbrew](https://switchbrew.org/w/index.php?title=Services_API) and/or [SwIPC](https://reswitched.github.io/SwIPC/) ]
|
||||
|
||||
### Service description
|
||||
```
|
||||
*
|
||||
```
|
||||
[ Include the description/explanation from [Switchbrew](https://switchbrew.org/w/index.php?title=Services_API) and/or [SwIPC](https://reswitched.github.io/SwIPC/) in the above code block ]
|
||||
|
||||
### Required by:
|
||||
[ Add our (games list database)[https://github.com/Ryujinx/Ryujinx-Games-List/issues] links of games who require this call ]
|
25
.github/ISSUE_TEMPLATE/missing_service_call.yml
vendored
Normal file
25
.github/ISSUE_TEMPLATE/missing_service_call.yml
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
name: Missing Service Call
|
||||
description: Service call is missing in Ryujinx.
|
||||
labels: not-implemented
|
||||
body:
|
||||
- type: textarea
|
||||
id: instruction
|
||||
attributes:
|
||||
label: Service call
|
||||
description: What service call is missing?
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: name
|
||||
attributes:
|
||||
label: Service description
|
||||
description: Include the description/explanation from [Switchbrew](https://switchbrew.org/w/index.php?title=Services_API) and/or [SwIPC](https://reswitched.github.io/SwIPC/) in the above code block
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: required
|
||||
attributes:
|
||||
label: Required by
|
||||
description: Add links to the [compatibility list page(s)](https://github.com/Ryujinx/Ryujinx-Games-List/issues) of the game(s) that require this service.
|
||||
validations:
|
||||
required: true
|
171
.github/workflows/flatpak.yml
vendored
Normal file
171
.github/workflows/flatpak.yml
vendored
Normal file
@ -0,0 +1,171 @@
|
||||
name: Flatpak release job
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
ryujinx_version:
|
||||
required: true
|
||||
type: string
|
||||
|
||||
|
||||
concurrency: flatpak-release
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
env:
|
||||
NUGET_PACKAGES: ${{ github.workspace }}/.nuget/packages
|
||||
GIT_COMMITTER_NAME: "RyujinxBot"
|
||||
GIT_COMMITTER_EMAIL: "61127645+RyujinxBot@users.noreply.github.com"
|
||||
RYUJINX_PROJECT_FILE: "Ryujinx/Ryujinx.csproj"
|
||||
NUGET_SOURCES_DESTDIR: "nuget-sources"
|
||||
RYUJINX_VERSION: "${{ inputs.ryujinx_version }}"
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
path: Ryujinx
|
||||
|
||||
- uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
global-json-file: Ryujinx/global.json
|
||||
|
||||
- name: Get version info
|
||||
id: version_info
|
||||
working-directory: Ryujinx
|
||||
run: |
|
||||
echo "git_hash=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
repository: flathub/org.ryujinx.Ryujinx
|
||||
token: ${{ secrets.RYUJINX_BOT_PAT }}
|
||||
submodules: recursive
|
||||
path: flathub
|
||||
|
||||
- name: Install dependencies
|
||||
run: python -m pip install PyYAML lxml
|
||||
|
||||
- name: Restore Nuget packages
|
||||
run: dotnet restore Ryujinx/${{ env.RYUJINX_PROJECT_FILE }}
|
||||
|
||||
- name: Generate nuget_sources.json
|
||||
shell: python
|
||||
run: |
|
||||
from pathlib import Path
|
||||
import base64
|
||||
import binascii
|
||||
import json
|
||||
import os
|
||||
|
||||
sources = []
|
||||
|
||||
for path in Path(os.environ['NUGET_PACKAGES']).glob('**/*.nupkg.sha512'):
|
||||
name = path.parent.parent.name
|
||||
version = path.parent.name
|
||||
filename = '{}.{}.nupkg'.format(name, version)
|
||||
url = 'https://api.nuget.org/v3-flatcontainer/{}/{}/{}'.format(name, version, filename)
|
||||
|
||||
with path.open() as fp:
|
||||
sha512 = binascii.hexlify(base64.b64decode(fp.read())).decode('ascii')
|
||||
|
||||
sources.append({
|
||||
'type': 'file',
|
||||
'url': url,
|
||||
'sha512': sha512,
|
||||
'dest': os.environ['NUGET_SOURCES_DESTDIR'],
|
||||
'dest-filename': filename,
|
||||
})
|
||||
|
||||
with open('flathub/nuget_sources.json', 'w') as fp:
|
||||
json.dump(sources, fp, indent=4)
|
||||
|
||||
- name: Update flatpak metadata
|
||||
id: metadata
|
||||
env:
|
||||
RYUJINX_GIT_HASH: ${{ steps.version_info.outputs.git_hash }}
|
||||
shell: python
|
||||
run: |
|
||||
import hashlib
|
||||
import hmac
|
||||
import json
|
||||
import os
|
||||
import yaml
|
||||
from datetime import datetime
|
||||
from lxml import etree
|
||||
|
||||
|
||||
# Ensure we don't destroy multiline strings
|
||||
def str_presenter(dumper, data):
|
||||
if len(data.splitlines()) > 1:
|
||||
return dumper.represent_scalar("tag:yaml.org,2002:str", data, style="|")
|
||||
return dumper.represent_scalar("tag:yaml.org,2002:str", data)
|
||||
|
||||
|
||||
yaml.representer.SafeRepresenter.add_representer(str, str_presenter)
|
||||
|
||||
yaml_file = "flathub/org.ryujinx.Ryujinx.yml"
|
||||
xml_file = "flathub/org.ryujinx.Ryujinx.appdata.xml"
|
||||
|
||||
with open(yaml_file, "r") as f:
|
||||
data = yaml.safe_load(f)
|
||||
|
||||
for source in data["modules"][0]["sources"]:
|
||||
if type(source) is str:
|
||||
continue
|
||||
if (
|
||||
source["type"] == "git"
|
||||
and source["url"] == "https://github.com/Ryujinx/Ryujinx.git"
|
||||
):
|
||||
source["commit"] = os.environ['RYUJINX_GIT_HASH']
|
||||
|
||||
is_same_version = data["modules"][0]["build-options"]["env"]["RYUJINX_VERSION"] == os.environ['RYUJINX_VERSION']
|
||||
|
||||
with open(os.environ['GITHUB_OUTPUT'], "a") as gh_out:
|
||||
if is_same_version:
|
||||
gh_out.write(f"commit_message=Retry update to {os.environ['RYUJINX_VERSION']}")
|
||||
else:
|
||||
gh_out.write(f"commit_message=Update to {os.environ['RYUJINX_VERSION']}")
|
||||
|
||||
if not is_same_version:
|
||||
data["modules"][0]["build-options"]["env"]["RYUJINX_VERSION"] = os.environ['RYUJINX_VERSION']
|
||||
|
||||
with open(yaml_file, "w") as f:
|
||||
yaml.safe_dump(data, f, sort_keys=False)
|
||||
|
||||
parser = etree.XMLParser(remove_blank_text=True)
|
||||
tree = etree.parse(xml_file, parser)
|
||||
|
||||
root = tree.getroot()
|
||||
|
||||
releases = root.find("releases")
|
||||
|
||||
element = etree.Element("release")
|
||||
element.set("version", os.environ['RYUJINX_VERSION'])
|
||||
element.set("date", datetime.now().date().isoformat())
|
||||
releases.insert(0, element)
|
||||
|
||||
# Ensure 4 spaces
|
||||
etree.indent(root, space=" ")
|
||||
|
||||
with open(xml_file, "wb") as f:
|
||||
f.write(
|
||||
etree.tostring(
|
||||
tree,
|
||||
pretty_print=True,
|
||||
encoding="UTF-8",
|
||||
doctype='<?xml version="1.0" encoding="UTF-8"?>',
|
||||
)
|
||||
)
|
||||
|
||||
- name: Push flatpak update
|
||||
working-directory: flathub
|
||||
env:
|
||||
COMMIT_MESSAGE: ${{ steps.metadata.outputs.commit_message }}
|
||||
run: |
|
||||
git config user.name "${{ env.GIT_COMMITTER_NAME }}"
|
||||
git config user.email "${{ env.GIT_COMMITTER_EMAIL }}"
|
||||
git add .
|
||||
git commit -m "$COMMIT_MESSAGE"
|
||||
git push origin master
|
37
.github/workflows/release.yml
vendored
37
.github/workflows/release.yml
vendored
@ -13,23 +13,22 @@ on:
|
||||
|
||||
concurrency: release
|
||||
|
||||
env:
|
||||
POWERSHELL_TELEMETRY_OPTOUT: 1
|
||||
DOTNET_CLI_TELEMETRY_OPTOUT: 1
|
||||
RYUJINX_BASE_VERSION: "1.1"
|
||||
RYUJINX_TARGET_RELEASE_CHANNEL_NAME: "master"
|
||||
RYUJINX_TARGET_RELEASE_CHANNEL_OWNER: "Ryujinx"
|
||||
RYUJINX_TARGET_RELEASE_CHANNEL_REPO: "release-channel-master"
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: windows-latest
|
||||
|
||||
env:
|
||||
POWERSHELL_TELEMETRY_OPTOUT: 1
|
||||
DOTNET_CLI_TELEMETRY_OPTOUT: 1
|
||||
RYUJINX_BASE_VERSION: "1.1"
|
||||
RYUJINX_TARGET_RELEASE_CHANNEL_NAME: "master"
|
||||
RYUJINX_TARGET_RELEASE_CHANNEL_OWNER: "Ryujinx"
|
||||
RYUJINX_TARGET_RELEASE_CHANNEL_REPO: "release-channel-master"
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: 7.0.x
|
||||
global-json-file: global.json
|
||||
- name: Get version info
|
||||
id: version_info
|
||||
run: |
|
||||
@ -112,3 +111,21 @@ jobs:
|
||||
owner: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}
|
||||
repo: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}
|
||||
token: ${{ secrets.RELEASE_TOKEN }}
|
||||
|
||||
- name: Create tag
|
||||
uses: actions/github-script@v5
|
||||
with:
|
||||
script: |
|
||||
github.rest.git.createRef({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
ref: 'refs/tags/${{ steps.version_info.outputs.build_version }}',
|
||||
sha: context.sha
|
||||
})
|
||||
|
||||
flatpak_release:
|
||||
uses: ./.github/workflows/flatpak.yml
|
||||
needs: release
|
||||
with:
|
||||
ryujinx_version: "1.1.${{ github.run_number }}"
|
||||
secrets: inherit
|
||||
|
@ -7,6 +7,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
|
||||
<ProjectReference Include="..\Ryujinx.Memory\Ryujinx.Memory.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -1,6 +1,7 @@
|
||||
using ARMeilleure.CodeGen.Linking;
|
||||
using ARMeilleure.CodeGen.RegisterAllocators;
|
||||
using ARMeilleure.IntermediateRepresentation;
|
||||
using Ryujinx.Common.Memory;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
@ -59,7 +60,7 @@ namespace ARMeilleure.CodeGen.Arm64
|
||||
|
||||
public CodeGenContext(AllocationResult allocResult, int maxCallArgs, int blocksCount, bool relocatable)
|
||||
{
|
||||
_stream = new MemoryStream();
|
||||
_stream = MemoryStreamManager.Shared.GetStream();
|
||||
|
||||
AllocResult = allocResult;
|
||||
|
||||
@ -265,7 +266,7 @@ namespace ARMeilleure.CodeGen.Arm64
|
||||
}
|
||||
else
|
||||
{
|
||||
relocInfo = new RelocInfo(new RelocEntry[0]);
|
||||
relocInfo = new RelocInfo(Array.Empty<RelocEntry>());
|
||||
}
|
||||
|
||||
return (code, relocInfo);
|
||||
|
@ -226,6 +226,8 @@ namespace ARMeilleure.CodeGen.Arm64
|
||||
Add(Intrinsic.Arm64MlsVe, new IntrinsicInfo(0x2f004000u, IntrinsicType.VectorTernaryRdByElem));
|
||||
Add(Intrinsic.Arm64MlsV, new IntrinsicInfo(0x2e209400u, IntrinsicType.VectorTernaryRd));
|
||||
Add(Intrinsic.Arm64MoviV, new IntrinsicInfo(0x0f000400u, IntrinsicType.VectorMovi));
|
||||
Add(Intrinsic.Arm64MrsFpcr, new IntrinsicInfo(0xd53b4400u, IntrinsicType.GetRegister));
|
||||
Add(Intrinsic.Arm64MsrFpcr, new IntrinsicInfo(0xd51b4400u, IntrinsicType.SetRegister));
|
||||
Add(Intrinsic.Arm64MrsFpsr, new IntrinsicInfo(0xd53b4420u, IntrinsicType.GetRegister));
|
||||
Add(Intrinsic.Arm64MsrFpsr, new IntrinsicInfo(0xd51b4420u, IntrinsicType.SetRegister));
|
||||
Add(Intrinsic.Arm64MulVe, new IntrinsicInfo(0x0f008000u, IntrinsicType.VectorBinaryByElem));
|
||||
|
@ -9,7 +9,7 @@ using static ARMeilleure.IntermediateRepresentation.Operation.Factory;
|
||||
|
||||
namespace ARMeilleure.CodeGen.Arm64
|
||||
{
|
||||
class PreAllocator
|
||||
static class PreAllocator
|
||||
{
|
||||
private class ConstantDict
|
||||
{
|
||||
@ -54,8 +54,8 @@ namespace ARMeilleure.CodeGen.Arm64
|
||||
continue;
|
||||
}
|
||||
|
||||
HandleConstantRegCopy(constants, block.Operations, node);
|
||||
HandleDestructiveRegCopy(block.Operations, node);
|
||||
InsertConstantRegCopies(constants, block.Operations, node);
|
||||
InsertDestructiveRegCopies(block.Operations, node);
|
||||
|
||||
switch (node.Instruction)
|
||||
{
|
||||
@ -78,28 +78,28 @@ namespace ARMeilleure.CodeGen.Arm64
|
||||
|
||||
// Copy values to registers expected by the function
|
||||
// being called, as mandated by the ABI.
|
||||
HandleCall(constants, block.Operations, node);
|
||||
InsertCallCopies(constants, block.Operations, node);
|
||||
break;
|
||||
case Instruction.CompareAndSwap:
|
||||
case Instruction.CompareAndSwap16:
|
||||
case Instruction.CompareAndSwap8:
|
||||
nextNode = HandleCompareAndSwap(block.Operations, node);
|
||||
nextNode = GenerateCompareAndSwap(block.Operations, node);
|
||||
break;
|
||||
case Instruction.LoadArgument:
|
||||
nextNode = HandleLoadArgument(cctx, ref buffer, block.Operations, preservedArgs, node);
|
||||
nextNode = InsertLoadArgumentCopy(cctx, ref buffer, block.Operations, preservedArgs, node);
|
||||
break;
|
||||
case Instruction.Return:
|
||||
HandleReturn(block.Operations, node);
|
||||
InsertReturnCopy(block.Operations, node);
|
||||
break;
|
||||
case Instruction.Tailcall:
|
||||
HandleTailcall(constants, block.Operations, stackAlloc, node, node);
|
||||
InsertTailcallCopies(constants, block.Operations, stackAlloc, node, node);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void HandleConstantRegCopy(ConstantDict constants, IntrusiveList<Operation> nodes, Operation node)
|
||||
private static void InsertConstantRegCopies(ConstantDict constants, IntrusiveList<Operation> nodes, Operation node)
|
||||
{
|
||||
if (node.SourcesCount == 0 || IsIntrinsicWithConst(node))
|
||||
{
|
||||
@ -211,7 +211,7 @@ namespace ARMeilleure.CodeGen.Arm64
|
||||
}
|
||||
}
|
||||
|
||||
private static void HandleDestructiveRegCopy(IntrusiveList<Operation> nodes, Operation node)
|
||||
private static void InsertDestructiveRegCopies(IntrusiveList<Operation> nodes, Operation node)
|
||||
{
|
||||
if (node.Destination == default || node.SourcesCount == 0)
|
||||
{
|
||||
@ -259,7 +259,7 @@ namespace ARMeilleure.CodeGen.Arm64
|
||||
}
|
||||
}
|
||||
|
||||
private static void HandleCall(ConstantDict constants, IntrusiveList<Operation> nodes, Operation node)
|
||||
private static void InsertCallCopies(ConstantDict constants, IntrusiveList<Operation> nodes, Operation node)
|
||||
{
|
||||
Operation operation = node;
|
||||
|
||||
@ -319,7 +319,7 @@ namespace ARMeilleure.CodeGen.Arm64
|
||||
|
||||
Operation copyOp = Operation(Instruction.Copy, argReg, source);
|
||||
|
||||
HandleConstantRegCopy(constants, nodes, nodes.AddBefore(node, copyOp));
|
||||
InsertConstantRegCopies(constants, nodes, nodes.AddBefore(node, copyOp));
|
||||
|
||||
sources.Add(argReg);
|
||||
}
|
||||
@ -329,7 +329,7 @@ namespace ARMeilleure.CodeGen.Arm64
|
||||
|
||||
Operation spillOp = Operation(Instruction.SpillArg, default, offset, source);
|
||||
|
||||
HandleConstantRegCopy(constants, nodes, nodes.AddBefore(node, spillOp));
|
||||
InsertConstantRegCopies(constants, nodes, nodes.AddBefore(node, spillOp));
|
||||
|
||||
stackOffset += source.Type.GetSizeInBytes();
|
||||
}
|
||||
@ -364,7 +364,7 @@ namespace ARMeilleure.CodeGen.Arm64
|
||||
operation.SetSources(sources.ToArray());
|
||||
}
|
||||
|
||||
private static void HandleTailcall(
|
||||
private static void InsertTailcallCopies(
|
||||
ConstantDict constants,
|
||||
IntrusiveList<Operation> nodes,
|
||||
StackAllocator stackAlloc,
|
||||
@ -420,7 +420,7 @@ namespace ARMeilleure.CodeGen.Arm64
|
||||
|
||||
Operation copyOp = Operation(Instruction.Copy, argReg, source);
|
||||
|
||||
HandleConstantRegCopy(constants, nodes, nodes.AddBefore(node, copyOp));
|
||||
InsertConstantRegCopies(constants, nodes, nodes.AddBefore(node, copyOp));
|
||||
|
||||
sources.Add(argReg);
|
||||
}
|
||||
@ -444,7 +444,7 @@ namespace ARMeilleure.CodeGen.Arm64
|
||||
operation.SetSources(sources.ToArray());
|
||||
}
|
||||
|
||||
private static Operation HandleCompareAndSwap(IntrusiveList<Operation> nodes, Operation node)
|
||||
private static Operation GenerateCompareAndSwap(IntrusiveList<Operation> nodes, Operation node)
|
||||
{
|
||||
Operand expected = node.GetSource(1);
|
||||
|
||||
@ -508,7 +508,7 @@ namespace ARMeilleure.CodeGen.Arm64
|
||||
return node.ListNext;
|
||||
}
|
||||
|
||||
private static void HandleReturn(IntrusiveList<Operation> nodes, Operation node)
|
||||
private static void InsertReturnCopy(IntrusiveList<Operation> nodes, Operation node)
|
||||
{
|
||||
if (node.SourcesCount == 0)
|
||||
{
|
||||
@ -537,7 +537,7 @@ namespace ARMeilleure.CodeGen.Arm64
|
||||
}
|
||||
}
|
||||
|
||||
private static Operation HandleLoadArgument(
|
||||
private static Operation InsertLoadArgumentCopy(
|
||||
CompilerContext cctx,
|
||||
ref Span<Operation> buffer,
|
||||
IntrusiveList<Operation> nodes,
|
||||
@ -629,7 +629,7 @@ namespace ARMeilleure.CodeGen.Arm64
|
||||
if (dest.AssignmentsCount == 1)
|
||||
{
|
||||
// Let's propagate the argument if we can to avoid copies.
|
||||
Propagate(ref buffer, dest, preservedArgs[index]);
|
||||
PreAllocatorCommon.Propagate(ref buffer, dest, preservedArgs[index]);
|
||||
nextNode = node.ListNext;
|
||||
}
|
||||
else
|
||||
@ -648,54 +648,6 @@ namespace ARMeilleure.CodeGen.Arm64
|
||||
}
|
||||
}
|
||||
|
||||
private static void Propagate(ref Span<Operation> buffer, Operand dest, Operand value)
|
||||
{
|
||||
ReadOnlySpan<Operation> uses = dest.GetUses(ref buffer);
|
||||
|
||||
foreach (Operation use in uses)
|
||||
{
|
||||
for (int srcIndex = 0; srcIndex < use.SourcesCount; srcIndex++)
|
||||
{
|
||||
Operand useSrc = use.GetSource(srcIndex);
|
||||
|
||||
if (useSrc == dest)
|
||||
{
|
||||
use.SetSource(srcIndex, value);
|
||||
}
|
||||
else if (useSrc.Kind == OperandKind.Memory)
|
||||
{
|
||||
MemoryOperand memoryOp = useSrc.GetMemory();
|
||||
|
||||
Operand baseAddr = memoryOp.BaseAddress;
|
||||
Operand index = memoryOp.Index;
|
||||
bool changed = false;
|
||||
|
||||
if (baseAddr == dest)
|
||||
{
|
||||
baseAddr = value;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (index == dest)
|
||||
{
|
||||
index = value;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (changed)
|
||||
{
|
||||
use.SetSource(srcIndex, MemoryOp(
|
||||
useSrc.Type,
|
||||
baseAddr,
|
||||
index,
|
||||
memoryOp.Scale,
|
||||
memoryOp.Displacement));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Operand AddFloatConstantCopy(
|
||||
ConstantDict constants,
|
||||
IntrusiveList<Operation> nodes,
|
||||
|
@ -48,9 +48,21 @@ namespace ARMeilleure.CodeGen
|
||||
/// <returns>A delegate of type <typeparamref name="T"/> pointing to the mapped function</returns>
|
||||
public T Map<T>()
|
||||
{
|
||||
IntPtr codePtr = JitCache.Map(this);
|
||||
return MapWithPointer<T>(out _);
|
||||
}
|
||||
|
||||
return Marshal.GetDelegateForFunctionPointer<T>(codePtr);
|
||||
/// <summary>
|
||||
/// Maps the <see cref="CompiledFunction"/> onto the <see cref="JitCache"/> and returns a delegate of type
|
||||
/// <typeparamref name="T"/> pointing to the mapped function.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of delegate</typeparam>
|
||||
/// <param name="codePointer">Pointer to the function code in memory</param>
|
||||
/// <returns>A delegate of type <typeparamref name="T"/> pointing to the mapped function</returns>
|
||||
public T MapWithPointer<T>(out IntPtr codePointer)
|
||||
{
|
||||
codePointer = JitCache.Map(this);
|
||||
|
||||
return Marshal.GetDelegateForFunctionPointer<T>(codePointer);
|
||||
}
|
||||
}
|
||||
}
|
57
ARMeilleure/CodeGen/PreAllocatorCommon.cs
Normal file
57
ARMeilleure/CodeGen/PreAllocatorCommon.cs
Normal file
@ -0,0 +1,57 @@
|
||||
using ARMeilleure.IntermediateRepresentation;
|
||||
using System;
|
||||
using static ARMeilleure.IntermediateRepresentation.Operand.Factory;
|
||||
|
||||
namespace ARMeilleure.CodeGen
|
||||
{
|
||||
static class PreAllocatorCommon
|
||||
{
|
||||
public static void Propagate(ref Span<Operation> buffer, Operand dest, Operand value)
|
||||
{
|
||||
ReadOnlySpan<Operation> uses = dest.GetUses(ref buffer);
|
||||
|
||||
foreach (Operation use in uses)
|
||||
{
|
||||
for (int srcIndex = 0; srcIndex < use.SourcesCount; srcIndex++)
|
||||
{
|
||||
Operand useSrc = use.GetSource(srcIndex);
|
||||
|
||||
if (useSrc == dest)
|
||||
{
|
||||
use.SetSource(srcIndex, value);
|
||||
}
|
||||
else if (useSrc.Kind == OperandKind.Memory)
|
||||
{
|
||||
MemoryOperand memoryOp = useSrc.GetMemory();
|
||||
|
||||
Operand baseAddr = memoryOp.BaseAddress;
|
||||
Operand index = memoryOp.Index;
|
||||
bool changed = false;
|
||||
|
||||
if (baseAddr == dest)
|
||||
{
|
||||
baseAddr = value;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (index == dest)
|
||||
{
|
||||
index = value;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (changed)
|
||||
{
|
||||
use.SetSource(srcIndex, MemoryOp(
|
||||
useSrc.Type,
|
||||
baseAddr,
|
||||
index,
|
||||
memoryOp.Scale,
|
||||
memoryOp.Displacement));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -433,16 +433,11 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
|
||||
|
||||
private static int GetHighestValueIndex(Span<int> span)
|
||||
{
|
||||
int highest = span[0];
|
||||
|
||||
if (highest == int.MaxValue)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
int highest = int.MinValue;
|
||||
|
||||
int selected = 0;
|
||||
|
||||
for (int index = 1; index < span.Length; index++)
|
||||
for (int index = 0; index < span.Length; index++)
|
||||
{
|
||||
int current = span[index];
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
using ARMeilleure.CodeGen.Linking;
|
||||
using ARMeilleure.IntermediateRepresentation;
|
||||
using Ryujinx.Common.Memory;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
@ -1033,7 +1034,13 @@ namespace ARMeilleure.CodeGen.X86
|
||||
|
||||
Debug.Assert(opCode != BadOp, "Invalid opcode value.");
|
||||
|
||||
if ((flags & InstructionFlags.Vex) != 0 && HardwareCapabilities.SupportsVexEncoding)
|
||||
if ((flags & InstructionFlags.Evex) != 0 && HardwareCapabilities.SupportsEvexEncoding)
|
||||
{
|
||||
WriteEvexInst(dest, src1, src2, type, flags, opCode);
|
||||
|
||||
opCode &= 0xff;
|
||||
}
|
||||
else if ((flags & InstructionFlags.Vex) != 0 && HardwareCapabilities.SupportsVexEncoding)
|
||||
{
|
||||
// In a vex encoding, only one prefix can be active at a time. The active prefix is encoded in the second byte using two bits.
|
||||
|
||||
@ -1152,6 +1159,103 @@ namespace ARMeilleure.CodeGen.X86
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteEvexInst(
|
||||
Operand dest,
|
||||
Operand src1,
|
||||
Operand src2,
|
||||
OperandType type,
|
||||
InstructionFlags flags,
|
||||
int opCode,
|
||||
bool broadcast = false,
|
||||
int registerWidth = 128,
|
||||
int maskRegisterIdx = 0,
|
||||
bool zeroElements = false)
|
||||
{
|
||||
int op1Idx = dest.GetRegister().Index;
|
||||
int op2Idx = src1.GetRegister().Index;
|
||||
int op3Idx = src2.GetRegister().Index;
|
||||
|
||||
WriteByte(0x62);
|
||||
|
||||
// P0
|
||||
// Extend operand 1 register
|
||||
bool r = (op1Idx & 8) == 0;
|
||||
// Extend operand 3 register
|
||||
bool x = (op3Idx & 16) == 0;
|
||||
// Extend operand 3 register
|
||||
bool b = (op3Idx & 8) == 0;
|
||||
// Extend operand 1 register
|
||||
bool rp = (op1Idx & 16) == 0;
|
||||
// Escape code index
|
||||
byte mm = 0b00;
|
||||
|
||||
switch ((ushort)(opCode >> 8))
|
||||
{
|
||||
case 0xf00: mm = 0b01; break;
|
||||
case 0xf38: mm = 0b10; break;
|
||||
case 0xf3a: mm = 0b11; break;
|
||||
|
||||
default: Debug.Fail($"Failed to EVEX encode opcode 0x{opCode:X}."); break;
|
||||
}
|
||||
|
||||
WriteByte(
|
||||
(byte)(
|
||||
(r ? 0x80 : 0) |
|
||||
(x ? 0x40 : 0) |
|
||||
(b ? 0x20 : 0) |
|
||||
(rp ? 0x10 : 0) |
|
||||
mm));
|
||||
|
||||
// P1
|
||||
// Specify 64-bit lane mode
|
||||
bool w = Is64Bits(type);
|
||||
// Operand 2 register index
|
||||
byte vvvv = (byte)(~op2Idx & 0b1111);
|
||||
// Opcode prefix
|
||||
byte pp = (flags & InstructionFlags.PrefixMask) switch
|
||||
{
|
||||
InstructionFlags.Prefix66 => 0b01,
|
||||
InstructionFlags.PrefixF3 => 0b10,
|
||||
InstructionFlags.PrefixF2 => 0b11,
|
||||
_ => 0
|
||||
};
|
||||
WriteByte(
|
||||
(byte)(
|
||||
(w ? 0x80 : 0) |
|
||||
(vvvv << 3) |
|
||||
0b100 |
|
||||
pp));
|
||||
|
||||
// P2
|
||||
// Mask register determines what elements to zero, rather than what elements to merge
|
||||
bool z = zeroElements;
|
||||
// Specifies register-width
|
||||
byte ll = 0b00;
|
||||
switch (registerWidth)
|
||||
{
|
||||
case 128: ll = 0b00; break;
|
||||
case 256: ll = 0b01; break;
|
||||
case 512: ll = 0b10; break;
|
||||
|
||||
default: Debug.Fail($"Invalid EVEX vector register width {registerWidth}."); break;
|
||||
}
|
||||
// Embedded broadcast in the case of a memory operand
|
||||
bool bcast = broadcast;
|
||||
// Extend operand 2 register
|
||||
bool vp = (op2Idx & 16) == 0;
|
||||
// Mask register index
|
||||
Debug.Assert(maskRegisterIdx < 8, $"Invalid mask register index {maskRegisterIdx}.");
|
||||
byte aaa = (byte)(maskRegisterIdx & 0b111);
|
||||
|
||||
WriteByte(
|
||||
(byte)(
|
||||
(z ? 0x80 : 0) |
|
||||
(ll << 5) |
|
||||
(bcast ? 0x10 : 0) |
|
||||
(vp ? 8 : 0) |
|
||||
aaa));
|
||||
}
|
||||
|
||||
private void WriteCompactInst(Operand operand, int opCode)
|
||||
{
|
||||
int regIndex = operand.GetRegister().Index;
|
||||
@ -1285,7 +1389,7 @@ namespace ARMeilleure.CodeGen.X86
|
||||
// Write the code, ignoring the dummy bytes after jumps, into a new stream.
|
||||
_stream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
using var codeStream = new MemoryStream();
|
||||
using var codeStream = MemoryStreamManager.Shared.GetStream();
|
||||
var assembler = new Assembler(codeStream, HasRelocs);
|
||||
|
||||
bool hasRelocs = HasRelocs;
|
||||
|
@ -20,6 +20,7 @@ namespace ARMeilleure.CodeGen.X86
|
||||
Reg8Dest = 1 << 2,
|
||||
RexW = 1 << 3,
|
||||
Vex = 1 << 4,
|
||||
Evex = 1 << 5,
|
||||
|
||||
PrefixBit = 16,
|
||||
PrefixMask = 7 << PrefixBit,
|
||||
@ -267,17 +268,20 @@ namespace ARMeilleure.CodeGen.X86
|
||||
Add(X86Instruction.Vblendvps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3a4a, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Vcvtph2ps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3813, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Vcvtps2ph, new InstructionInfo(0x000f3a1d, BadOp, BadOp, BadOp, BadOp, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Vfmadd231pd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38b8, InstructionFlags.Vex | InstructionFlags.Prefix66 | InstructionFlags.RexW));
|
||||
Add(X86Instruction.Vfmadd231ps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38b8, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Vfmadd231sd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38b9, InstructionFlags.Vex | InstructionFlags.Prefix66 | InstructionFlags.RexW));
|
||||
Add(X86Instruction.Vfmadd231ss, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38b9, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Vfmsub231sd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38bb, InstructionFlags.Vex | InstructionFlags.Prefix66 | InstructionFlags.RexW));
|
||||
Add(X86Instruction.Vfmsub231ss, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38bb, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Vfnmadd231pd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38bc, InstructionFlags.Vex | InstructionFlags.Prefix66 | InstructionFlags.RexW));
|
||||
Add(X86Instruction.Vfnmadd231ps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38bc, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Vfnmadd231sd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38bd, InstructionFlags.Vex | InstructionFlags.Prefix66 | InstructionFlags.RexW));
|
||||
Add(X86Instruction.Vfnmadd231ss, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38bd, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Vfnmsub231sd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38bf, InstructionFlags.Vex | InstructionFlags.Prefix66 | InstructionFlags.RexW));
|
||||
Add(X86Instruction.Vfnmsub231ss, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38bf, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Vpblendvb, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3a4c, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Vpternlogd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3a25, InstructionFlags.Evex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Xor, new InstructionInfo(0x00000031, 0x06000083, 0x06000081, BadOp, 0x00000033, InstructionFlags.None));
|
||||
Add(X86Instruction.Xorpd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f57, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Xorps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f57, InstructionFlags.Vex));
|
||||
|
@ -1,5 +1,6 @@
|
||||
using ARMeilleure.CodeGen.RegisterAllocators;
|
||||
using ARMeilleure.IntermediateRepresentation;
|
||||
using Ryujinx.Common.Memory;
|
||||
using System.IO;
|
||||
using System.Numerics;
|
||||
|
||||
@ -22,7 +23,7 @@ namespace ARMeilleure.CodeGen.X86
|
||||
|
||||
public CodeGenContext(AllocationResult allocResult, int maxCallArgs, int blocksCount, bool relocatable)
|
||||
{
|
||||
_stream = new MemoryStream();
|
||||
_stream = MemoryStreamManager.Shared.GetStream();
|
||||
_blockLabels = new Operand[blocksCount];
|
||||
|
||||
AllocResult = allocResult;
|
||||
|
@ -249,10 +249,9 @@ namespace ARMeilleure.CodeGen.X86
|
||||
case IntrinsicType.Mxcsr:
|
||||
{
|
||||
Operand offset = operation.GetSource(0);
|
||||
Operand bits = operation.GetSource(1);
|
||||
|
||||
Debug.Assert(offset.Kind == OperandKind.Constant && bits.Kind == OperandKind.Constant);
|
||||
Debug.Assert(offset.Type == OperandType.I32 && bits.Type == OperandType.I32);
|
||||
Debug.Assert(offset.Kind == OperandKind.Constant);
|
||||
Debug.Assert(offset.Type == OperandType.I32);
|
||||
|
||||
int offs = offset.AsInt32() + context.CallArgsRegionSize;
|
||||
|
||||
@ -261,21 +260,23 @@ namespace ARMeilleure.CodeGen.X86
|
||||
|
||||
Debug.Assert(HardwareCapabilities.SupportsSse || HardwareCapabilities.SupportsVexEncoding);
|
||||
|
||||
context.Assembler.Stmxcsr(memOp);
|
||||
|
||||
if (operation.Intrinsic == Intrinsic.X86Mxcsrmb)
|
||||
if (operation.Intrinsic == Intrinsic.X86Ldmxcsr)
|
||||
{
|
||||
context.Assembler.Or(memOp, bits, OperandType.I32);
|
||||
Operand bits = operation.GetSource(1);
|
||||
Debug.Assert(bits.Type == OperandType.I32);
|
||||
|
||||
context.Assembler.Mov(memOp, bits, OperandType.I32);
|
||||
context.Assembler.Ldmxcsr(memOp);
|
||||
}
|
||||
else /* if (intrinOp.Intrinsic == Intrinsic.X86Mxcsrub) */
|
||||
else if (operation.Intrinsic == Intrinsic.X86Stmxcsr)
|
||||
{
|
||||
Operand notBits = Const(~bits.AsInt32());
|
||||
Operand dest = operation.Destination;
|
||||
Debug.Assert(dest.Type == OperandType.I32);
|
||||
|
||||
context.Assembler.And(memOp, notBits, OperandType.I32);
|
||||
context.Assembler.Stmxcsr(memOp);
|
||||
context.Assembler.Mov(dest, memOp, OperandType.I32);
|
||||
}
|
||||
|
||||
context.Assembler.Ldmxcsr(memOp);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -1,10 +1,14 @@
|
||||
using Ryujinx.Memory;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Intrinsics.X86;
|
||||
|
||||
namespace ARMeilleure.CodeGen.X86
|
||||
{
|
||||
static class HardwareCapabilities
|
||||
{
|
||||
private delegate uint GetXcr0();
|
||||
|
||||
static HardwareCapabilities()
|
||||
{
|
||||
if (!X86Base.IsSupported)
|
||||
@ -24,6 +28,34 @@ namespace ARMeilleure.CodeGen.X86
|
||||
FeatureInfo7Ebx = (FeatureFlags7Ebx)ebx7;
|
||||
FeatureInfo7Ecx = (FeatureFlags7Ecx)ecx7;
|
||||
}
|
||||
|
||||
Xcr0InfoEax = (Xcr0FlagsEax)GetXcr0Eax();
|
||||
}
|
||||
|
||||
private static uint GetXcr0Eax()
|
||||
{
|
||||
if (!FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Xsave))
|
||||
{
|
||||
// XSAVE feature required for xgetbv
|
||||
return 0;
|
||||
}
|
||||
|
||||
ReadOnlySpan<byte> asmGetXcr0 = new byte[]
|
||||
{
|
||||
0x31, 0xc9, // xor ecx, ecx
|
||||
0xf, 0x01, 0xd0, // xgetbv
|
||||
0xc3, // ret
|
||||
};
|
||||
|
||||
using MemoryBlock memGetXcr0 = new MemoryBlock((ulong)asmGetXcr0.Length);
|
||||
|
||||
memGetXcr0.Write(0, asmGetXcr0);
|
||||
|
||||
memGetXcr0.Reprotect(0, (ulong)asmGetXcr0.Length, MemoryPermission.ReadAndExecute);
|
||||
|
||||
var fGetXcr0 = Marshal.GetDelegateForFunctionPointer<GetXcr0>(memGetXcr0.Pointer);
|
||||
|
||||
return fGetXcr0();
|
||||
}
|
||||
|
||||
[Flags]
|
||||
@ -44,6 +76,8 @@ namespace ARMeilleure.CodeGen.X86
|
||||
Sse42 = 1 << 20,
|
||||
Popcnt = 1 << 23,
|
||||
Aes = 1 << 25,
|
||||
Xsave = 1 << 26,
|
||||
Osxsave = 1 << 27,
|
||||
Avx = 1 << 28,
|
||||
F16c = 1 << 29
|
||||
}
|
||||
@ -52,7 +86,11 @@ namespace ARMeilleure.CodeGen.X86
|
||||
public enum FeatureFlags7Ebx
|
||||
{
|
||||
Avx2 = 1 << 5,
|
||||
Sha = 1 << 29
|
||||
Avx512f = 1 << 16,
|
||||
Avx512dq = 1 << 17,
|
||||
Sha = 1 << 29,
|
||||
Avx512bw = 1 << 30,
|
||||
Avx512vl = 1 << 31
|
||||
}
|
||||
|
||||
[Flags]
|
||||
@ -61,10 +99,21 @@ namespace ARMeilleure.CodeGen.X86
|
||||
Gfni = 1 << 8,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum Xcr0FlagsEax
|
||||
{
|
||||
Sse = 1 << 1,
|
||||
YmmHi128 = 1 << 2,
|
||||
Opmask = 1 << 5,
|
||||
ZmmHi256 = 1 << 6,
|
||||
Hi16Zmm = 1 << 7
|
||||
}
|
||||
|
||||
public static FeatureFlags1Edx FeatureInfo1Edx { get; }
|
||||
public static FeatureFlags1Ecx FeatureInfo1Ecx { get; }
|
||||
public static FeatureFlags7Ebx FeatureInfo7Ebx { get; } = 0;
|
||||
public static FeatureFlags7Ecx FeatureInfo7Ecx { get; } = 0;
|
||||
public static Xcr0FlagsEax Xcr0InfoEax { get; } = 0;
|
||||
|
||||
public static bool SupportsSse => FeatureInfo1Edx.HasFlag(FeatureFlags1Edx.Sse);
|
||||
public static bool SupportsSse2 => FeatureInfo1Edx.HasFlag(FeatureFlags1Edx.Sse2);
|
||||
@ -76,8 +125,13 @@ namespace ARMeilleure.CodeGen.X86
|
||||
public static bool SupportsSse42 => FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Sse42);
|
||||
public static bool SupportsPopcnt => FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Popcnt);
|
||||
public static bool SupportsAesni => FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Aes);
|
||||
public static bool SupportsAvx => FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Avx);
|
||||
public static bool SupportsAvx => FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Avx | FeatureFlags1Ecx.Xsave | FeatureFlags1Ecx.Osxsave) && Xcr0InfoEax.HasFlag(Xcr0FlagsEax.Sse | Xcr0FlagsEax.YmmHi128);
|
||||
public static bool SupportsAvx2 => FeatureInfo7Ebx.HasFlag(FeatureFlags7Ebx.Avx2) && SupportsAvx;
|
||||
public static bool SupportsAvx512F => FeatureInfo7Ebx.HasFlag(FeatureFlags7Ebx.Avx512f) && FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Xsave | FeatureFlags1Ecx.Osxsave)
|
||||
&& Xcr0InfoEax.HasFlag(Xcr0FlagsEax.Sse | Xcr0FlagsEax.YmmHi128 | Xcr0FlagsEax.Opmask | Xcr0FlagsEax.ZmmHi256 | Xcr0FlagsEax.Hi16Zmm);
|
||||
public static bool SupportsAvx512Vl => FeatureInfo7Ebx.HasFlag(FeatureFlags7Ebx.Avx512vl) && SupportsAvx512F;
|
||||
public static bool SupportsAvx512Bw => FeatureInfo7Ebx.HasFlag(FeatureFlags7Ebx.Avx512bw) && SupportsAvx512F;
|
||||
public static bool SupportsAvx512Dq => FeatureInfo7Ebx.HasFlag(FeatureFlags7Ebx.Avx512dq) && SupportsAvx512F;
|
||||
public static bool SupportsF16c => FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.F16c);
|
||||
public static bool SupportsSha => FeatureInfo7Ebx.HasFlag(FeatureFlags7Ebx.Sha);
|
||||
public static bool SupportsGfni => FeatureInfo7Ecx.HasFlag(FeatureFlags7Ecx.Gfni);
|
||||
@ -85,5 +139,6 @@ namespace ARMeilleure.CodeGen.X86
|
||||
public static bool ForceLegacySse { get; set; }
|
||||
|
||||
public static bool SupportsVexEncoding => SupportsAvx && !ForceLegacySse;
|
||||
public static bool SupportsEvexEncoding => SupportsAvx512F && !ForceLegacySse;
|
||||
}
|
||||
}
|
@ -60,6 +60,7 @@ namespace ARMeilleure.CodeGen.X86
|
||||
Add(Intrinsic.X86Haddpd, new IntrinsicInfo(X86Instruction.Haddpd, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Haddps, new IntrinsicInfo(X86Instruction.Haddps, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Insertps, new IntrinsicInfo(X86Instruction.Insertps, IntrinsicType.TernaryImm));
|
||||
Add(Intrinsic.X86Ldmxcsr, new IntrinsicInfo(X86Instruction.None, IntrinsicType.Mxcsr));
|
||||
Add(Intrinsic.X86Maxpd, new IntrinsicInfo(X86Instruction.Maxpd, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Maxps, new IntrinsicInfo(X86Instruction.Maxps, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Maxsd, new IntrinsicInfo(X86Instruction.Maxsd, IntrinsicType.Binary));
|
||||
@ -75,8 +76,6 @@ namespace ARMeilleure.CodeGen.X86
|
||||
Add(Intrinsic.X86Mulps, new IntrinsicInfo(X86Instruction.Mulps, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Mulsd, new IntrinsicInfo(X86Instruction.Mulsd, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Mulss, new IntrinsicInfo(X86Instruction.Mulss, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Mxcsrmb, new IntrinsicInfo(X86Instruction.None, IntrinsicType.Mxcsr)); // Mask bits.
|
||||
Add(Intrinsic.X86Mxcsrub, new IntrinsicInfo(X86Instruction.None, IntrinsicType.Mxcsr)); // Unmask bits.
|
||||
Add(Intrinsic.X86Paddb, new IntrinsicInfo(X86Instruction.Paddb, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Paddd, new IntrinsicInfo(X86Instruction.Paddd, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Paddq, new IntrinsicInfo(X86Instruction.Paddq, IntrinsicType.Binary));
|
||||
@ -160,6 +159,7 @@ namespace ARMeilleure.CodeGen.X86
|
||||
Add(Intrinsic.X86Sqrtps, new IntrinsicInfo(X86Instruction.Sqrtps, IntrinsicType.Unary));
|
||||
Add(Intrinsic.X86Sqrtsd, new IntrinsicInfo(X86Instruction.Sqrtsd, IntrinsicType.Unary));
|
||||
Add(Intrinsic.X86Sqrtss, new IntrinsicInfo(X86Instruction.Sqrtss, IntrinsicType.Unary));
|
||||
Add(Intrinsic.X86Stmxcsr, new IntrinsicInfo(X86Instruction.None, IntrinsicType.Mxcsr));
|
||||
Add(Intrinsic.X86Subpd, new IntrinsicInfo(X86Instruction.Subpd, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Subps, new IntrinsicInfo(X86Instruction.Subps, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Subsd, new IntrinsicInfo(X86Instruction.Subsd, IntrinsicType.Binary));
|
||||
@ -170,16 +170,19 @@ namespace ARMeilleure.CodeGen.X86
|
||||
Add(Intrinsic.X86Unpcklps, new IntrinsicInfo(X86Instruction.Unpcklps, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Vcvtph2ps, new IntrinsicInfo(X86Instruction.Vcvtph2ps, IntrinsicType.Unary));
|
||||
Add(Intrinsic.X86Vcvtps2ph, new IntrinsicInfo(X86Instruction.Vcvtps2ph, IntrinsicType.BinaryImm));
|
||||
Add(Intrinsic.X86Vfmadd231pd, new IntrinsicInfo(X86Instruction.Vfmadd231pd, IntrinsicType.Fma));
|
||||
Add(Intrinsic.X86Vfmadd231ps, new IntrinsicInfo(X86Instruction.Vfmadd231ps, IntrinsicType.Fma));
|
||||
Add(Intrinsic.X86Vfmadd231sd, new IntrinsicInfo(X86Instruction.Vfmadd231sd, IntrinsicType.Fma));
|
||||
Add(Intrinsic.X86Vfmadd231ss, new IntrinsicInfo(X86Instruction.Vfmadd231ss, IntrinsicType.Fma));
|
||||
Add(Intrinsic.X86Vfmsub231sd, new IntrinsicInfo(X86Instruction.Vfmsub231sd, IntrinsicType.Fma));
|
||||
Add(Intrinsic.X86Vfmsub231ss, new IntrinsicInfo(X86Instruction.Vfmsub231ss, IntrinsicType.Fma));
|
||||
Add(Intrinsic.X86Vfnmadd231pd, new IntrinsicInfo(X86Instruction.Vfnmadd231pd, IntrinsicType.Fma));
|
||||
Add(Intrinsic.X86Vfnmadd231ps, new IntrinsicInfo(X86Instruction.Vfnmadd231ps, IntrinsicType.Fma));
|
||||
Add(Intrinsic.X86Vfnmadd231sd, new IntrinsicInfo(X86Instruction.Vfnmadd231sd, IntrinsicType.Fma));
|
||||
Add(Intrinsic.X86Vfnmadd231ss, new IntrinsicInfo(X86Instruction.Vfnmadd231ss, IntrinsicType.Fma));
|
||||
Add(Intrinsic.X86Vfnmsub231sd, new IntrinsicInfo(X86Instruction.Vfnmsub231sd, IntrinsicType.Fma));
|
||||
Add(Intrinsic.X86Vfnmsub231ss, new IntrinsicInfo(X86Instruction.Vfnmsub231ss, IntrinsicType.Fma));
|
||||
Add(Intrinsic.X86Vpternlogd, new IntrinsicInfo(X86Instruction.Vpternlogd, IntrinsicType.TernaryImm));
|
||||
Add(Intrinsic.X86Xorpd, new IntrinsicInfo(X86Instruction.Xorpd, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Xorps, new IntrinsicInfo(X86Instruction.Xorps, IntrinsicType.Binary));
|
||||
}
|
||||
|
15
ARMeilleure/CodeGen/X86/Mxcsr.cs
Normal file
15
ARMeilleure/CodeGen/X86/Mxcsr.cs
Normal file
@ -0,0 +1,15 @@
|
||||
using System;
|
||||
|
||||
namespace ARMeilleure.CodeGen.X86
|
||||
{
|
||||
[Flags]
|
||||
enum Mxcsr
|
||||
{
|
||||
Ftz = 1 << 15, // Flush To Zero.
|
||||
Rhi = 1 << 14, // Round Mode high bit.
|
||||
Rlo = 1 << 13, // Round Mode low bit.
|
||||
Um = 1 << 11, // Underflow Mask.
|
||||
Dm = 1 << 8, // Denormal Mask.
|
||||
Daz = 1 << 6 // Denormals Are Zero.
|
||||
}
|
||||
}
|
@ -2,19 +2,20 @@ using ARMeilleure.CodeGen.RegisterAllocators;
|
||||
using ARMeilleure.IntermediateRepresentation;
|
||||
using ARMeilleure.Translation;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using static ARMeilleure.IntermediateRepresentation.Operand.Factory;
|
||||
using static ARMeilleure.IntermediateRepresentation.Operation.Factory;
|
||||
|
||||
namespace ARMeilleure.CodeGen.X86
|
||||
{
|
||||
static class PreAllocator
|
||||
class PreAllocator
|
||||
{
|
||||
public static void RunPass(CompilerContext cctx, StackAllocator stackAlloc, out int maxCallArgs)
|
||||
{
|
||||
maxCallArgs = -1;
|
||||
|
||||
Span<Operation> buffer = default;
|
||||
|
||||
CallConvName callConv = CallingConvention.GetCurrentCallConv();
|
||||
|
||||
Operand[] preservedArgs = new Operand[CallingConvention.GetArgumentsOnRegsCount()];
|
||||
@ -32,9 +33,9 @@ namespace ARMeilleure.CodeGen.X86
|
||||
continue;
|
||||
}
|
||||
|
||||
HandleConstantRegCopy(block.Operations, node);
|
||||
HandleDestructiveRegCopy(block.Operations, node);
|
||||
HandleConstrainedRegCopy(block.Operations, node);
|
||||
InsertConstantRegCopies(block.Operations, node);
|
||||
InsertDestructiveRegCopies(block.Operations, node);
|
||||
InsertConstrainedRegCopies(block.Operations, node);
|
||||
|
||||
switch (node.Instruction)
|
||||
{
|
||||
@ -59,79 +60,85 @@ namespace ARMeilleure.CodeGen.X86
|
||||
// being called, as mandated by the ABI.
|
||||
if (callConv == CallConvName.Windows)
|
||||
{
|
||||
HandleCallWindowsAbi(block.Operations, stackAlloc, node);
|
||||
PreAllocatorWindows.InsertCallCopies(block.Operations, stackAlloc, node);
|
||||
}
|
||||
else /* if (callConv == CallConvName.SystemV) */
|
||||
{
|
||||
HandleCallSystemVAbi(block.Operations, node);
|
||||
PreAllocatorSystemV.InsertCallCopies(block.Operations, node);
|
||||
}
|
||||
break;
|
||||
|
||||
case Instruction.ConvertToFPUI:
|
||||
HandleConvertToFPUI(block.Operations, node);
|
||||
GenerateConvertToFPUI(block.Operations, node);
|
||||
break;
|
||||
|
||||
case Instruction.LoadArgument:
|
||||
if (callConv == CallConvName.Windows)
|
||||
{
|
||||
nextNode = HandleLoadArgumentWindowsAbi(cctx, block.Operations, preservedArgs, node);
|
||||
nextNode = PreAllocatorWindows.InsertLoadArgumentCopy(cctx, ref buffer, block.Operations, preservedArgs, node);
|
||||
}
|
||||
else /* if (callConv == CallConvName.SystemV) */
|
||||
{
|
||||
nextNode = HandleLoadArgumentSystemVAbi(cctx, block.Operations, preservedArgs, node);
|
||||
nextNode = PreAllocatorSystemV.InsertLoadArgumentCopy(cctx, ref buffer, block.Operations, preservedArgs, node);
|
||||
}
|
||||
break;
|
||||
|
||||
case Instruction.Negate:
|
||||
if (!node.GetSource(0).Type.IsInteger())
|
||||
{
|
||||
HandleNegate(block.Operations, node);
|
||||
GenerateNegate(block.Operations, node);
|
||||
}
|
||||
break;
|
||||
|
||||
case Instruction.Return:
|
||||
if (callConv == CallConvName.Windows)
|
||||
{
|
||||
HandleReturnWindowsAbi(cctx, block.Operations, preservedArgs, node);
|
||||
PreAllocatorWindows.InsertReturnCopy(cctx, block.Operations, preservedArgs, node);
|
||||
}
|
||||
else /* if (callConv == CallConvName.SystemV) */
|
||||
{
|
||||
HandleReturnSystemVAbi(block.Operations, node);
|
||||
PreAllocatorSystemV.InsertReturnCopy(block.Operations, node);
|
||||
}
|
||||
break;
|
||||
|
||||
case Instruction.Tailcall:
|
||||
if (callConv == CallConvName.Windows)
|
||||
{
|
||||
HandleTailcallWindowsAbi(block.Operations, stackAlloc, node);
|
||||
PreAllocatorWindows.InsertTailcallCopies(block.Operations, stackAlloc, node);
|
||||
}
|
||||
else
|
||||
{
|
||||
HandleTailcallSystemVAbi(block.Operations, stackAlloc, node);
|
||||
PreAllocatorSystemV.InsertTailcallCopies(block.Operations, stackAlloc, node);
|
||||
}
|
||||
break;
|
||||
|
||||
case Instruction.VectorInsert8:
|
||||
if (!HardwareCapabilities.SupportsSse41)
|
||||
{
|
||||
HandleVectorInsert8(block.Operations, node);
|
||||
GenerateVectorInsert8(block.Operations, node);
|
||||
}
|
||||
break;
|
||||
|
||||
case Instruction.Extended:
|
||||
if (node.Intrinsic == Intrinsic.X86Mxcsrmb || node.Intrinsic == Intrinsic.X86Mxcsrub)
|
||||
if (node.Intrinsic == Intrinsic.X86Ldmxcsr)
|
||||
{
|
||||
int stackOffset = stackAlloc.Allocate(OperandType.I32);
|
||||
|
||||
node.SetSources(new Operand[] { Const(stackOffset), node.GetSource(0) });
|
||||
}
|
||||
else if (node.Intrinsic == Intrinsic.X86Stmxcsr)
|
||||
{
|
||||
int stackOffset = stackAlloc.Allocate(OperandType.I32);
|
||||
|
||||
node.SetSources(new Operand[] { Const(stackOffset) });
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void HandleConstantRegCopy(IntrusiveList<Operation> nodes, Operation node)
|
||||
protected static void InsertConstantRegCopies(IntrusiveList<Operation> nodes, Operation node)
|
||||
{
|
||||
if (node.SourcesCount == 0 || IsXmmIntrinsic(node))
|
||||
{
|
||||
@ -212,7 +219,7 @@ namespace ARMeilleure.CodeGen.X86
|
||||
}
|
||||
}
|
||||
|
||||
private static void HandleConstrainedRegCopy(IntrusiveList<Operation> nodes, Operation node)
|
||||
protected static void InsertConstrainedRegCopies(IntrusiveList<Operation> nodes, Operation node)
|
||||
{
|
||||
Operand dest = node.Destination;
|
||||
|
||||
@ -369,7 +376,7 @@ namespace ARMeilleure.CodeGen.X86
|
||||
}
|
||||
}
|
||||
|
||||
private static void HandleDestructiveRegCopy(IntrusiveList<Operation> nodes, Operation node)
|
||||
protected static void InsertDestructiveRegCopies(IntrusiveList<Operation> nodes, Operation node)
|
||||
{
|
||||
if (node.Destination == default || node.SourcesCount == 0)
|
||||
{
|
||||
@ -447,7 +454,7 @@ namespace ARMeilleure.CodeGen.X86
|
||||
}
|
||||
}
|
||||
|
||||
private static void HandleConvertToFPUI(IntrusiveList<Operation> nodes, Operation node)
|
||||
private static void GenerateConvertToFPUI(IntrusiveList<Operation> nodes, Operation node)
|
||||
{
|
||||
// Unsigned integer to FP conversions are not supported on X86.
|
||||
// We need to turn them into signed integer to FP conversions, and
|
||||
@ -501,7 +508,7 @@ namespace ARMeilleure.CodeGen.X86
|
||||
Delete(nodes, currentNode);
|
||||
}
|
||||
|
||||
private static void HandleNegate(IntrusiveList<Operation> nodes, Operation node)
|
||||
private static void GenerateNegate(IntrusiveList<Operation> nodes, Operation node)
|
||||
{
|
||||
// There's no SSE FP negate instruction, so we need to transform that into
|
||||
// a XOR of the value to be negated with a mask with the highest bit set.
|
||||
@ -534,7 +541,7 @@ namespace ARMeilleure.CodeGen.X86
|
||||
Delete(nodes, currentNode);
|
||||
}
|
||||
|
||||
private static void HandleVectorInsert8(IntrusiveList<Operation> nodes, Operation node)
|
||||
private static void GenerateVectorInsert8(IntrusiveList<Operation> nodes, Operation node)
|
||||
{
|
||||
// Handle vector insertion, when SSE 4.1 is not supported.
|
||||
Operand dest = node.Destination;
|
||||
@ -579,620 +586,7 @@ namespace ARMeilleure.CodeGen.X86
|
||||
Delete(nodes, currentNode);
|
||||
}
|
||||
|
||||
private static void HandleCallWindowsAbi(IntrusiveList<Operation> nodes, StackAllocator stackAlloc, Operation node)
|
||||
{
|
||||
Operand dest = node.Destination;
|
||||
|
||||
// Handle struct arguments.
|
||||
int retArgs = 0;
|
||||
int stackAllocOffset = 0;
|
||||
|
||||
int AllocateOnStack(int size)
|
||||
{
|
||||
// We assume that the stack allocator is initially empty (TotalSize = 0).
|
||||
// Taking that into account, we can reuse the space allocated for other
|
||||
// calls by keeping track of our own allocated size (stackAllocOffset).
|
||||
// If the space allocated is not big enough, then we just expand it.
|
||||
int offset = stackAllocOffset;
|
||||
|
||||
if (stackAllocOffset + size > stackAlloc.TotalSize)
|
||||
{
|
||||
stackAlloc.Allocate((stackAllocOffset + size) - stackAlloc.TotalSize);
|
||||
}
|
||||
|
||||
stackAllocOffset += size;
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
Operand arg0Reg = default;
|
||||
|
||||
if (dest != default && dest.Type == OperandType.V128)
|
||||
{
|
||||
int stackOffset = AllocateOnStack(dest.Type.GetSizeInBytes());
|
||||
|
||||
arg0Reg = Gpr(CallingConvention.GetIntArgumentRegister(0), OperandType.I64);
|
||||
|
||||
Operation allocOp = Operation(Instruction.StackAlloc, arg0Reg, Const(stackOffset));
|
||||
|
||||
nodes.AddBefore(node, allocOp);
|
||||
|
||||
retArgs = 1;
|
||||
}
|
||||
|
||||
int argsCount = node.SourcesCount - 1;
|
||||
int maxArgs = CallingConvention.GetArgumentsOnRegsCount() - retArgs;
|
||||
|
||||
if (argsCount > maxArgs)
|
||||
{
|
||||
argsCount = maxArgs;
|
||||
}
|
||||
|
||||
Operand[] sources = new Operand[1 + retArgs + argsCount];
|
||||
|
||||
sources[0] = node.GetSource(0);
|
||||
|
||||
if (arg0Reg != default)
|
||||
{
|
||||
sources[1] = arg0Reg;
|
||||
}
|
||||
|
||||
for (int index = 1; index < node.SourcesCount; index++)
|
||||
{
|
||||
Operand source = node.GetSource(index);
|
||||
|
||||
if (source.Type == OperandType.V128)
|
||||
{
|
||||
Operand stackAddr = Local(OperandType.I64);
|
||||
|
||||
int stackOffset = AllocateOnStack(source.Type.GetSizeInBytes());
|
||||
|
||||
nodes.AddBefore(node, Operation(Instruction.StackAlloc, stackAddr, Const(stackOffset)));
|
||||
|
||||
Operation storeOp = Operation(Instruction.Store, default, stackAddr, source);
|
||||
|
||||
HandleConstantRegCopy(nodes, nodes.AddBefore(node, storeOp));
|
||||
|
||||
node.SetSource(index, stackAddr);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle arguments passed on registers.
|
||||
for (int index = 0; index < argsCount; index++)
|
||||
{
|
||||
Operand source = node.GetSource(index + 1);
|
||||
Operand argReg;
|
||||
|
||||
int argIndex = index + retArgs;
|
||||
|
||||
if (source.Type.IsInteger())
|
||||
{
|
||||
argReg = Gpr(CallingConvention.GetIntArgumentRegister(argIndex), source.Type);
|
||||
}
|
||||
else
|
||||
{
|
||||
argReg = Xmm(CallingConvention.GetVecArgumentRegister(argIndex), source.Type);
|
||||
}
|
||||
|
||||
Operation copyOp = Operation(Instruction.Copy, argReg, source);
|
||||
|
||||
HandleConstantRegCopy(nodes, nodes.AddBefore(node, copyOp));
|
||||
|
||||
sources[1 + retArgs + index] = argReg;
|
||||
}
|
||||
|
||||
// The remaining arguments (those that are not passed on registers)
|
||||
// should be passed on the stack, we write them to the stack with "SpillArg".
|
||||
for (int index = argsCount; index < node.SourcesCount - 1; index++)
|
||||
{
|
||||
Operand source = node.GetSource(index + 1);
|
||||
Operand offset = Const((index + retArgs) * 8);
|
||||
|
||||
Operation spillOp = Operation(Instruction.SpillArg, default, offset, source);
|
||||
|
||||
HandleConstantRegCopy(nodes, nodes.AddBefore(node, spillOp));
|
||||
}
|
||||
|
||||
if (dest != default)
|
||||
{
|
||||
if (dest.Type == OperandType.V128)
|
||||
{
|
||||
Operand retValueAddr = Local(OperandType.I64);
|
||||
|
||||
nodes.AddBefore(node, Operation(Instruction.Copy, retValueAddr, arg0Reg));
|
||||
|
||||
Operation loadOp = Operation(Instruction.Load, dest, retValueAddr);
|
||||
|
||||
nodes.AddAfter(node, loadOp);
|
||||
|
||||
node.Destination = default;
|
||||
}
|
||||
else
|
||||
{
|
||||
Operand retReg = dest.Type.IsInteger()
|
||||
? Gpr(CallingConvention.GetIntReturnRegister(), dest.Type)
|
||||
: Xmm(CallingConvention.GetVecReturnRegister(), dest.Type);
|
||||
|
||||
Operation copyOp = Operation(Instruction.Copy, dest, retReg);
|
||||
|
||||
nodes.AddAfter(node, copyOp);
|
||||
|
||||
node.Destination = retReg;
|
||||
}
|
||||
}
|
||||
|
||||
node.SetSources(sources);
|
||||
}
|
||||
|
||||
private static void HandleCallSystemVAbi(IntrusiveList<Operation> nodes, Operation node)
|
||||
{
|
||||
Operand dest = node.Destination;
|
||||
|
||||
List<Operand> sources = new List<Operand>
|
||||
{
|
||||
node.GetSource(0)
|
||||
};
|
||||
|
||||
int argsCount = node.SourcesCount - 1;
|
||||
|
||||
int intMax = CallingConvention.GetIntArgumentsOnRegsCount();
|
||||
int vecMax = CallingConvention.GetVecArgumentsOnRegsCount();
|
||||
|
||||
int intCount = 0;
|
||||
int vecCount = 0;
|
||||
|
||||
int stackOffset = 0;
|
||||
|
||||
for (int index = 0; index < argsCount; index++)
|
||||
{
|
||||
Operand source = node.GetSource(index + 1);
|
||||
|
||||
bool passOnReg;
|
||||
|
||||
if (source.Type.IsInteger())
|
||||
{
|
||||
passOnReg = intCount < intMax;
|
||||
}
|
||||
else if (source.Type == OperandType.V128)
|
||||
{
|
||||
passOnReg = intCount + 1 < intMax;
|
||||
}
|
||||
else
|
||||
{
|
||||
passOnReg = vecCount < vecMax;
|
||||
}
|
||||
|
||||
if (source.Type == OperandType.V128 && passOnReg)
|
||||
{
|
||||
// V128 is a struct, we pass each half on a GPR if possible.
|
||||
Operand argReg = Gpr(CallingConvention.GetIntArgumentRegister(intCount++), OperandType.I64);
|
||||
Operand argReg2 = Gpr(CallingConvention.GetIntArgumentRegister(intCount++), OperandType.I64);
|
||||
|
||||
nodes.AddBefore(node, Operation(Instruction.VectorExtract, argReg, source, Const(0)));
|
||||
nodes.AddBefore(node, Operation(Instruction.VectorExtract, argReg2, source, Const(1)));
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (passOnReg)
|
||||
{
|
||||
Operand argReg = source.Type.IsInteger()
|
||||
? Gpr(CallingConvention.GetIntArgumentRegister(intCount++), source.Type)
|
||||
: Xmm(CallingConvention.GetVecArgumentRegister(vecCount++), source.Type);
|
||||
|
||||
Operation copyOp = Operation(Instruction.Copy, argReg, source);
|
||||
|
||||
HandleConstantRegCopy(nodes, nodes.AddBefore(node, copyOp));
|
||||
|
||||
sources.Add(argReg);
|
||||
}
|
||||
else
|
||||
{
|
||||
Operand offset = Const(stackOffset);
|
||||
|
||||
Operation spillOp = Operation(Instruction.SpillArg, default, offset, source);
|
||||
|
||||
HandleConstantRegCopy(nodes, nodes.AddBefore(node, spillOp));
|
||||
|
||||
stackOffset += source.Type.GetSizeInBytes();
|
||||
}
|
||||
}
|
||||
|
||||
node.SetSources(sources.ToArray());
|
||||
|
||||
if (dest != default)
|
||||
{
|
||||
if (dest.Type == OperandType.V128)
|
||||
{
|
||||
Operand retLReg = Gpr(CallingConvention.GetIntReturnRegister(), OperandType.I64);
|
||||
Operand retHReg = Gpr(CallingConvention.GetIntReturnRegisterHigh(), OperandType.I64);
|
||||
|
||||
Operation operation = node;
|
||||
|
||||
node = nodes.AddAfter(node, Operation(Instruction.VectorCreateScalar, dest, retLReg));
|
||||
nodes.AddAfter(node, Operation(Instruction.VectorInsert, dest, dest, retHReg, Const(1)));
|
||||
|
||||
operation.Destination = default;
|
||||
}
|
||||
else
|
||||
{
|
||||
Operand retReg = dest.Type.IsInteger()
|
||||
? Gpr(CallingConvention.GetIntReturnRegister(), dest.Type)
|
||||
: Xmm(CallingConvention.GetVecReturnRegister(), dest.Type);
|
||||
|
||||
Operation copyOp = Operation(Instruction.Copy, dest, retReg);
|
||||
|
||||
nodes.AddAfter(node, copyOp);
|
||||
|
||||
node.Destination = retReg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void HandleTailcallSystemVAbi(IntrusiveList<Operation> nodes, StackAllocator stackAlloc, Operation node)
|
||||
{
|
||||
List<Operand> sources = new List<Operand>
|
||||
{
|
||||
node.GetSource(0)
|
||||
};
|
||||
|
||||
int argsCount = node.SourcesCount - 1;
|
||||
|
||||
int intMax = CallingConvention.GetIntArgumentsOnRegsCount();
|
||||
int vecMax = CallingConvention.GetVecArgumentsOnRegsCount();
|
||||
|
||||
int intCount = 0;
|
||||
int vecCount = 0;
|
||||
|
||||
// Handle arguments passed on registers.
|
||||
for (int index = 0; index < argsCount; index++)
|
||||
{
|
||||
Operand source = node.GetSource(1 + index);
|
||||
|
||||
bool passOnReg;
|
||||
|
||||
if (source.Type.IsInteger())
|
||||
{
|
||||
passOnReg = intCount + 1 < intMax;
|
||||
}
|
||||
else
|
||||
{
|
||||
passOnReg = vecCount < vecMax;
|
||||
}
|
||||
|
||||
if (source.Type == OperandType.V128 && passOnReg)
|
||||
{
|
||||
// V128 is a struct, we pass each half on a GPR if possible.
|
||||
Operand argReg = Gpr(CallingConvention.GetIntArgumentRegister(intCount++), OperandType.I64);
|
||||
Operand argReg2 = Gpr(CallingConvention.GetIntArgumentRegister(intCount++), OperandType.I64);
|
||||
|
||||
nodes.AddBefore(node, Operation(Instruction.VectorExtract, argReg, source, Const(0)));
|
||||
nodes.AddBefore(node, Operation(Instruction.VectorExtract, argReg2, source, Const(1)));
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (passOnReg)
|
||||
{
|
||||
Operand argReg = source.Type.IsInteger()
|
||||
? Gpr(CallingConvention.GetIntArgumentRegister(intCount++), source.Type)
|
||||
: Xmm(CallingConvention.GetVecArgumentRegister(vecCount++), source.Type);
|
||||
|
||||
Operation copyOp = Operation(Instruction.Copy, argReg, source);
|
||||
|
||||
HandleConstantRegCopy(nodes, nodes.AddBefore(node, copyOp));
|
||||
|
||||
sources.Add(argReg);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotImplementedException("Spilling is not currently supported for tail calls. (too many arguments)");
|
||||
}
|
||||
}
|
||||
|
||||
// The target address must be on the return registers, since we
|
||||
// don't return anything and it is guaranteed to not be a
|
||||
// callee saved register (which would be trashed on the epilogue).
|
||||
Operand retReg = Gpr(CallingConvention.GetIntReturnRegister(), OperandType.I64);
|
||||
|
||||
Operation addrCopyOp = Operation(Instruction.Copy, retReg, node.GetSource(0));
|
||||
|
||||
nodes.AddBefore(node, addrCopyOp);
|
||||
|
||||
sources[0] = retReg;
|
||||
|
||||
node.SetSources(sources.ToArray());
|
||||
}
|
||||
|
||||
private static void HandleTailcallWindowsAbi(IntrusiveList<Operation> nodes, StackAllocator stackAlloc, Operation node)
|
||||
{
|
||||
int argsCount = node.SourcesCount - 1;
|
||||
int maxArgs = CallingConvention.GetArgumentsOnRegsCount();
|
||||
|
||||
if (argsCount > maxArgs)
|
||||
{
|
||||
throw new NotImplementedException("Spilling is not currently supported for tail calls. (too many arguments)");
|
||||
}
|
||||
|
||||
Operand[] sources = new Operand[1 + argsCount];
|
||||
|
||||
// Handle arguments passed on registers.
|
||||
for (int index = 0; index < argsCount; index++)
|
||||
{
|
||||
Operand source = node.GetSource(1 + index);
|
||||
Operand argReg = source.Type.IsInteger()
|
||||
? Gpr(CallingConvention.GetIntArgumentRegister(index), source.Type)
|
||||
: Xmm(CallingConvention.GetVecArgumentRegister(index), source.Type);
|
||||
|
||||
Operation copyOp = Operation(Instruction.Copy, argReg, source);
|
||||
|
||||
HandleConstantRegCopy(nodes, nodes.AddBefore(node, copyOp));
|
||||
|
||||
sources[1 + index] = argReg;
|
||||
}
|
||||
|
||||
// The target address must be on the return registers, since we
|
||||
// don't return anything and it is guaranteed to not be a
|
||||
// callee saved register (which would be trashed on the epilogue).
|
||||
Operand retReg = Gpr(CallingConvention.GetIntReturnRegister(), OperandType.I64);
|
||||
|
||||
Operation addrCopyOp = Operation(Instruction.Copy, retReg, node.GetSource(0));
|
||||
|
||||
nodes.AddBefore(node, addrCopyOp);
|
||||
|
||||
sources[0] = retReg;
|
||||
|
||||
node.SetSources(sources);
|
||||
}
|
||||
|
||||
private static Operation HandleLoadArgumentWindowsAbi(
|
||||
CompilerContext cctx,
|
||||
IntrusiveList<Operation> nodes,
|
||||
Operand[] preservedArgs,
|
||||
Operation node)
|
||||
{
|
||||
Operand source = node.GetSource(0);
|
||||
|
||||
Debug.Assert(source.Kind == OperandKind.Constant, "Non-constant LoadArgument source kind.");
|
||||
|
||||
int retArgs = cctx.FuncReturnType == OperandType.V128 ? 1 : 0;
|
||||
|
||||
int index = source.AsInt32() + retArgs;
|
||||
|
||||
if (index < CallingConvention.GetArgumentsOnRegsCount())
|
||||
{
|
||||
Operand dest = node.Destination;
|
||||
|
||||
if (preservedArgs[index] == default)
|
||||
{
|
||||
Operand argReg, pArg;
|
||||
|
||||
if (dest.Type.IsInteger())
|
||||
{
|
||||
argReg = Gpr(CallingConvention.GetIntArgumentRegister(index), dest.Type);
|
||||
pArg = Local(dest.Type);
|
||||
}
|
||||
else if (dest.Type == OperandType.V128)
|
||||
{
|
||||
argReg = Gpr(CallingConvention.GetIntArgumentRegister(index), OperandType.I64);
|
||||
pArg = Local(OperandType.I64);
|
||||
}
|
||||
else
|
||||
{
|
||||
argReg = Xmm(CallingConvention.GetVecArgumentRegister(index), dest.Type);
|
||||
pArg = Local(dest.Type);
|
||||
}
|
||||
|
||||
Operation copyOp = Operation(Instruction.Copy, pArg, argReg);
|
||||
|
||||
cctx.Cfg.Entry.Operations.AddFirst(copyOp);
|
||||
|
||||
preservedArgs[index] = pArg;
|
||||
}
|
||||
|
||||
Operation argCopyOp = Operation(dest.Type == OperandType.V128
|
||||
? Instruction.Load
|
||||
: Instruction.Copy, dest, preservedArgs[index]);
|
||||
|
||||
Operation newNode = nodes.AddBefore(node, argCopyOp);
|
||||
|
||||
Delete(nodes, node);
|
||||
|
||||
return newNode;
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: Pass on stack.
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
private static Operation HandleLoadArgumentSystemVAbi(
|
||||
CompilerContext cctx,
|
||||
IntrusiveList<Operation> nodes,
|
||||
Operand[] preservedArgs,
|
||||
Operation node)
|
||||
{
|
||||
Operand source = node.GetSource(0);
|
||||
|
||||
Debug.Assert(source.Kind == OperandKind.Constant, "Non-constant LoadArgument source kind.");
|
||||
|
||||
int index = source.AsInt32();
|
||||
|
||||
int intCount = 0;
|
||||
int vecCount = 0;
|
||||
|
||||
for (int cIndex = 0; cIndex < index; cIndex++)
|
||||
{
|
||||
OperandType argType = cctx.FuncArgTypes[cIndex];
|
||||
|
||||
if (argType.IsInteger())
|
||||
{
|
||||
intCount++;
|
||||
}
|
||||
else if (argType == OperandType.V128)
|
||||
{
|
||||
intCount += 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
vecCount++;
|
||||
}
|
||||
}
|
||||
|
||||
bool passOnReg;
|
||||
|
||||
if (source.Type.IsInteger())
|
||||
{
|
||||
passOnReg = intCount < CallingConvention.GetIntArgumentsOnRegsCount();
|
||||
}
|
||||
else if (source.Type == OperandType.V128)
|
||||
{
|
||||
passOnReg = intCount + 1 < CallingConvention.GetIntArgumentsOnRegsCount();
|
||||
}
|
||||
else
|
||||
{
|
||||
passOnReg = vecCount < CallingConvention.GetVecArgumentsOnRegsCount();
|
||||
}
|
||||
|
||||
if (passOnReg)
|
||||
{
|
||||
Operand dest = node.Destination;
|
||||
|
||||
if (preservedArgs[index] == default)
|
||||
{
|
||||
if (dest.Type == OperandType.V128)
|
||||
{
|
||||
// V128 is a struct, we pass each half on a GPR if possible.
|
||||
Operand pArg = Local(OperandType.V128);
|
||||
|
||||
Operand argLReg = Gpr(CallingConvention.GetIntArgumentRegister(intCount), OperandType.I64);
|
||||
Operand argHReg = Gpr(CallingConvention.GetIntArgumentRegister(intCount + 1), OperandType.I64);
|
||||
|
||||
Operation copyL = Operation(Instruction.VectorCreateScalar, pArg, argLReg);
|
||||
Operation copyH = Operation(Instruction.VectorInsert, pArg, pArg, argHReg, Const(1));
|
||||
|
||||
cctx.Cfg.Entry.Operations.AddFirst(copyH);
|
||||
cctx.Cfg.Entry.Operations.AddFirst(copyL);
|
||||
|
||||
preservedArgs[index] = pArg;
|
||||
}
|
||||
else
|
||||
{
|
||||
Operand pArg = Local(dest.Type);
|
||||
|
||||
Operand argReg = dest.Type.IsInteger()
|
||||
? Gpr(CallingConvention.GetIntArgumentRegister(intCount), dest.Type)
|
||||
: Xmm(CallingConvention.GetVecArgumentRegister(vecCount), dest.Type);
|
||||
|
||||
Operation copyOp = Operation(Instruction.Copy, pArg, argReg);
|
||||
|
||||
cctx.Cfg.Entry.Operations.AddFirst(copyOp);
|
||||
|
||||
preservedArgs[index] = pArg;
|
||||
}
|
||||
}
|
||||
|
||||
Operation argCopyOp = Operation(Instruction.Copy, dest, preservedArgs[index]);
|
||||
|
||||
Operation newNode = nodes.AddBefore(node, argCopyOp);
|
||||
|
||||
Delete(nodes, node);
|
||||
|
||||
return newNode;
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: Pass on stack.
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
private static void HandleReturnWindowsAbi(
|
||||
CompilerContext cctx,
|
||||
IntrusiveList<Operation> nodes,
|
||||
Operand[] preservedArgs,
|
||||
Operation node)
|
||||
{
|
||||
if (node.SourcesCount == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Operand source = node.GetSource(0);
|
||||
Operand retReg;
|
||||
|
||||
if (source.Type.IsInteger())
|
||||
{
|
||||
retReg = Gpr(CallingConvention.GetIntReturnRegister(), source.Type);
|
||||
}
|
||||
else if (source.Type == OperandType.V128)
|
||||
{
|
||||
if (preservedArgs[0] == default)
|
||||
{
|
||||
Operand preservedArg = Local(OperandType.I64);
|
||||
Operand arg0 = Gpr(CallingConvention.GetIntArgumentRegister(0), OperandType.I64);
|
||||
|
||||
Operation copyOp = Operation(Instruction.Copy, preservedArg, arg0);
|
||||
|
||||
cctx.Cfg.Entry.Operations.AddFirst(copyOp);
|
||||
|
||||
preservedArgs[0] = preservedArg;
|
||||
}
|
||||
|
||||
retReg = preservedArgs[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
retReg = Xmm(CallingConvention.GetVecReturnRegister(), source.Type);
|
||||
}
|
||||
|
||||
if (source.Type == OperandType.V128)
|
||||
{
|
||||
Operation retStoreOp = Operation(Instruction.Store, default, retReg, source);
|
||||
|
||||
nodes.AddBefore(node, retStoreOp);
|
||||
}
|
||||
else
|
||||
{
|
||||
Operation retCopyOp = Operation(Instruction.Copy, retReg, source);
|
||||
|
||||
nodes.AddBefore(node, retCopyOp);
|
||||
}
|
||||
|
||||
node.SetSources(Array.Empty<Operand>());
|
||||
}
|
||||
|
||||
private static void HandleReturnSystemVAbi(IntrusiveList<Operation> nodes, Operation node)
|
||||
{
|
||||
if (node.SourcesCount == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Operand source = node.GetSource(0);
|
||||
|
||||
if (source.Type == OperandType.V128)
|
||||
{
|
||||
Operand retLReg = Gpr(CallingConvention.GetIntReturnRegister(), OperandType.I64);
|
||||
Operand retHReg = Gpr(CallingConvention.GetIntReturnRegisterHigh(), OperandType.I64);
|
||||
|
||||
nodes.AddBefore(node, Operation(Instruction.VectorExtract, retLReg, source, Const(0)));
|
||||
nodes.AddBefore(node, Operation(Instruction.VectorExtract, retHReg, source, Const(1)));
|
||||
}
|
||||
else
|
||||
{
|
||||
Operand retReg = source.Type.IsInteger()
|
||||
? Gpr(CallingConvention.GetIntReturnRegister(), source.Type)
|
||||
: Xmm(CallingConvention.GetVecReturnRegister(), source.Type);
|
||||
|
||||
Operation retCopyOp = Operation(Instruction.Copy, retReg, source);
|
||||
|
||||
nodes.AddBefore(node, retCopyOp);
|
||||
}
|
||||
}
|
||||
|
||||
private static Operand AddXmmCopy(IntrusiveList<Operation> nodes, Operation node, Operand source)
|
||||
protected static Operand AddXmmCopy(IntrusiveList<Operation> nodes, Operation node, Operand source)
|
||||
{
|
||||
Operand temp = Local(source.Type);
|
||||
Operand intConst = AddCopy(nodes, node, GetIntConst(source));
|
||||
@ -1204,7 +598,7 @@ namespace ARMeilleure.CodeGen.X86
|
||||
return temp;
|
||||
}
|
||||
|
||||
private static Operand AddCopy(IntrusiveList<Operation> nodes, Operation node, Operand source)
|
||||
protected static Operand AddCopy(IntrusiveList<Operation> nodes, Operation node, Operand source)
|
||||
{
|
||||
Operand temp = Local(source.Type);
|
||||
|
||||
@ -1229,7 +623,7 @@ namespace ARMeilleure.CodeGen.X86
|
||||
return value;
|
||||
}
|
||||
|
||||
private static void Delete(IntrusiveList<Operation> nodes, Operation node)
|
||||
protected static void Delete(IntrusiveList<Operation> nodes, Operation node)
|
||||
{
|
||||
node.Destination = default;
|
||||
|
||||
@ -1241,12 +635,12 @@ namespace ARMeilleure.CodeGen.X86
|
||||
nodes.Remove(node);
|
||||
}
|
||||
|
||||
private static Operand Gpr(X86Register register, OperandType type)
|
||||
protected static Operand Gpr(X86Register register, OperandType type)
|
||||
{
|
||||
return Register((int)register, RegisterType.Integer, type);
|
||||
}
|
||||
|
||||
private static Operand Xmm(X86Register register, OperandType type)
|
||||
protected static Operand Xmm(X86Register register, OperandType type)
|
||||
{
|
||||
return Register((int)register, RegisterType.Vector, type);
|
||||
}
|
||||
|
334
ARMeilleure/CodeGen/X86/PreAllocatorSystemV.cs
Normal file
334
ARMeilleure/CodeGen/X86/PreAllocatorSystemV.cs
Normal file
@ -0,0 +1,334 @@
|
||||
using ARMeilleure.CodeGen.RegisterAllocators;
|
||||
using ARMeilleure.IntermediateRepresentation;
|
||||
using ARMeilleure.Translation;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using static ARMeilleure.IntermediateRepresentation.Operand.Factory;
|
||||
using static ARMeilleure.IntermediateRepresentation.Operation.Factory;
|
||||
|
||||
namespace ARMeilleure.CodeGen.X86
|
||||
{
|
||||
class PreAllocatorSystemV : PreAllocator
|
||||
{
|
||||
public static void InsertCallCopies(IntrusiveList<Operation> nodes, Operation node)
|
||||
{
|
||||
Operand dest = node.Destination;
|
||||
|
||||
List<Operand> sources = new List<Operand>
|
||||
{
|
||||
node.GetSource(0)
|
||||
};
|
||||
|
||||
int argsCount = node.SourcesCount - 1;
|
||||
|
||||
int intMax = CallingConvention.GetIntArgumentsOnRegsCount();
|
||||
int vecMax = CallingConvention.GetVecArgumentsOnRegsCount();
|
||||
|
||||
int intCount = 0;
|
||||
int vecCount = 0;
|
||||
|
||||
int stackOffset = 0;
|
||||
|
||||
for (int index = 0; index < argsCount; index++)
|
||||
{
|
||||
Operand source = node.GetSource(index + 1);
|
||||
|
||||
bool passOnReg;
|
||||
|
||||
if (source.Type.IsInteger())
|
||||
{
|
||||
passOnReg = intCount < intMax;
|
||||
}
|
||||
else if (source.Type == OperandType.V128)
|
||||
{
|
||||
passOnReg = intCount + 1 < intMax;
|
||||
}
|
||||
else
|
||||
{
|
||||
passOnReg = vecCount < vecMax;
|
||||
}
|
||||
|
||||
if (source.Type == OperandType.V128 && passOnReg)
|
||||
{
|
||||
// V128 is a struct, we pass each half on a GPR if possible.
|
||||
Operand argReg = Gpr(CallingConvention.GetIntArgumentRegister(intCount++), OperandType.I64);
|
||||
Operand argReg2 = Gpr(CallingConvention.GetIntArgumentRegister(intCount++), OperandType.I64);
|
||||
|
||||
nodes.AddBefore(node, Operation(Instruction.VectorExtract, argReg, source, Const(0)));
|
||||
nodes.AddBefore(node, Operation(Instruction.VectorExtract, argReg2, source, Const(1)));
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (passOnReg)
|
||||
{
|
||||
Operand argReg = source.Type.IsInteger()
|
||||
? Gpr(CallingConvention.GetIntArgumentRegister(intCount++), source.Type)
|
||||
: Xmm(CallingConvention.GetVecArgumentRegister(vecCount++), source.Type);
|
||||
|
||||
Operation copyOp = Operation(Instruction.Copy, argReg, source);
|
||||
|
||||
InsertConstantRegCopies(nodes, nodes.AddBefore(node, copyOp));
|
||||
|
||||
sources.Add(argReg);
|
||||
}
|
||||
else
|
||||
{
|
||||
Operand offset = Const(stackOffset);
|
||||
|
||||
Operation spillOp = Operation(Instruction.SpillArg, default, offset, source);
|
||||
|
||||
InsertConstantRegCopies(nodes, nodes.AddBefore(node, spillOp));
|
||||
|
||||
stackOffset += source.Type.GetSizeInBytes();
|
||||
}
|
||||
}
|
||||
|
||||
node.SetSources(sources.ToArray());
|
||||
|
||||
if (dest != default)
|
||||
{
|
||||
if (dest.Type == OperandType.V128)
|
||||
{
|
||||
Operand retLReg = Gpr(CallingConvention.GetIntReturnRegister(), OperandType.I64);
|
||||
Operand retHReg = Gpr(CallingConvention.GetIntReturnRegisterHigh(), OperandType.I64);
|
||||
|
||||
Operation operation = node;
|
||||
|
||||
node = nodes.AddAfter(node, Operation(Instruction.VectorCreateScalar, dest, retLReg));
|
||||
nodes.AddAfter(node, Operation(Instruction.VectorInsert, dest, dest, retHReg, Const(1)));
|
||||
|
||||
operation.Destination = default;
|
||||
}
|
||||
else
|
||||
{
|
||||
Operand retReg = dest.Type.IsInteger()
|
||||
? Gpr(CallingConvention.GetIntReturnRegister(), dest.Type)
|
||||
: Xmm(CallingConvention.GetVecReturnRegister(), dest.Type);
|
||||
|
||||
Operation copyOp = Operation(Instruction.Copy, dest, retReg);
|
||||
|
||||
nodes.AddAfter(node, copyOp);
|
||||
|
||||
node.Destination = retReg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void InsertTailcallCopies(IntrusiveList<Operation> nodes, StackAllocator stackAlloc, Operation node)
|
||||
{
|
||||
List<Operand> sources = new List<Operand>
|
||||
{
|
||||
node.GetSource(0)
|
||||
};
|
||||
|
||||
int argsCount = node.SourcesCount - 1;
|
||||
|
||||
int intMax = CallingConvention.GetIntArgumentsOnRegsCount();
|
||||
int vecMax = CallingConvention.GetVecArgumentsOnRegsCount();
|
||||
|
||||
int intCount = 0;
|
||||
int vecCount = 0;
|
||||
|
||||
// Handle arguments passed on registers.
|
||||
for (int index = 0; index < argsCount; index++)
|
||||
{
|
||||
Operand source = node.GetSource(1 + index);
|
||||
|
||||
bool passOnReg;
|
||||
|
||||
if (source.Type.IsInteger())
|
||||
{
|
||||
passOnReg = intCount + 1 < intMax;
|
||||
}
|
||||
else
|
||||
{
|
||||
passOnReg = vecCount < vecMax;
|
||||
}
|
||||
|
||||
if (source.Type == OperandType.V128 && passOnReg)
|
||||
{
|
||||
// V128 is a struct, we pass each half on a GPR if possible.
|
||||
Operand argReg = Gpr(CallingConvention.GetIntArgumentRegister(intCount++), OperandType.I64);
|
||||
Operand argReg2 = Gpr(CallingConvention.GetIntArgumentRegister(intCount++), OperandType.I64);
|
||||
|
||||
nodes.AddBefore(node, Operation(Instruction.VectorExtract, argReg, source, Const(0)));
|
||||
nodes.AddBefore(node, Operation(Instruction.VectorExtract, argReg2, source, Const(1)));
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (passOnReg)
|
||||
{
|
||||
Operand argReg = source.Type.IsInteger()
|
||||
? Gpr(CallingConvention.GetIntArgumentRegister(intCount++), source.Type)
|
||||
: Xmm(CallingConvention.GetVecArgumentRegister(vecCount++), source.Type);
|
||||
|
||||
Operation copyOp = Operation(Instruction.Copy, argReg, source);
|
||||
|
||||
InsertConstantRegCopies(nodes, nodes.AddBefore(node, copyOp));
|
||||
|
||||
sources.Add(argReg);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotImplementedException("Spilling is not currently supported for tail calls. (too many arguments)");
|
||||
}
|
||||
}
|
||||
|
||||
// The target address must be on the return registers, since we
|
||||
// don't return anything and it is guaranteed to not be a
|
||||
// callee saved register (which would be trashed on the epilogue).
|
||||
Operand retReg = Gpr(CallingConvention.GetIntReturnRegister(), OperandType.I64);
|
||||
|
||||
Operation addrCopyOp = Operation(Instruction.Copy, retReg, node.GetSource(0));
|
||||
|
||||
nodes.AddBefore(node, addrCopyOp);
|
||||
|
||||
sources[0] = retReg;
|
||||
|
||||
node.SetSources(sources.ToArray());
|
||||
}
|
||||
|
||||
public static Operation InsertLoadArgumentCopy(
|
||||
CompilerContext cctx,
|
||||
ref Span<Operation> buffer,
|
||||
IntrusiveList<Operation> nodes,
|
||||
Operand[] preservedArgs,
|
||||
Operation node)
|
||||
{
|
||||
Operand source = node.GetSource(0);
|
||||
|
||||
Debug.Assert(source.Kind == OperandKind.Constant, "Non-constant LoadArgument source kind.");
|
||||
|
||||
int index = source.AsInt32();
|
||||
|
||||
int intCount = 0;
|
||||
int vecCount = 0;
|
||||
|
||||
for (int cIndex = 0; cIndex < index; cIndex++)
|
||||
{
|
||||
OperandType argType = cctx.FuncArgTypes[cIndex];
|
||||
|
||||
if (argType.IsInteger())
|
||||
{
|
||||
intCount++;
|
||||
}
|
||||
else if (argType == OperandType.V128)
|
||||
{
|
||||
intCount += 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
vecCount++;
|
||||
}
|
||||
}
|
||||
|
||||
bool passOnReg;
|
||||
|
||||
if (source.Type.IsInteger())
|
||||
{
|
||||
passOnReg = intCount < CallingConvention.GetIntArgumentsOnRegsCount();
|
||||
}
|
||||
else if (source.Type == OperandType.V128)
|
||||
{
|
||||
passOnReg = intCount + 1 < CallingConvention.GetIntArgumentsOnRegsCount();
|
||||
}
|
||||
else
|
||||
{
|
||||
passOnReg = vecCount < CallingConvention.GetVecArgumentsOnRegsCount();
|
||||
}
|
||||
|
||||
if (passOnReg)
|
||||
{
|
||||
Operand dest = node.Destination;
|
||||
|
||||
if (preservedArgs[index] == default)
|
||||
{
|
||||
if (dest.Type == OperandType.V128)
|
||||
{
|
||||
// V128 is a struct, we pass each half on a GPR if possible.
|
||||
Operand pArg = Local(OperandType.V128);
|
||||
|
||||
Operand argLReg = Gpr(CallingConvention.GetIntArgumentRegister(intCount), OperandType.I64);
|
||||
Operand argHReg = Gpr(CallingConvention.GetIntArgumentRegister(intCount + 1), OperandType.I64);
|
||||
|
||||
Operation copyL = Operation(Instruction.VectorCreateScalar, pArg, argLReg);
|
||||
Operation copyH = Operation(Instruction.VectorInsert, pArg, pArg, argHReg, Const(1));
|
||||
|
||||
cctx.Cfg.Entry.Operations.AddFirst(copyH);
|
||||
cctx.Cfg.Entry.Operations.AddFirst(copyL);
|
||||
|
||||
preservedArgs[index] = pArg;
|
||||
}
|
||||
else
|
||||
{
|
||||
Operand pArg = Local(dest.Type);
|
||||
|
||||
Operand argReg = dest.Type.IsInteger()
|
||||
? Gpr(CallingConvention.GetIntArgumentRegister(intCount), dest.Type)
|
||||
: Xmm(CallingConvention.GetVecArgumentRegister(vecCount), dest.Type);
|
||||
|
||||
Operation copyOp = Operation(Instruction.Copy, pArg, argReg);
|
||||
|
||||
cctx.Cfg.Entry.Operations.AddFirst(copyOp);
|
||||
|
||||
preservedArgs[index] = pArg;
|
||||
}
|
||||
}
|
||||
|
||||
Operation nextNode;
|
||||
|
||||
if (dest.AssignmentsCount == 1)
|
||||
{
|
||||
// Let's propagate the argument if we can to avoid copies.
|
||||
PreAllocatorCommon.Propagate(ref buffer, dest, preservedArgs[index]);
|
||||
nextNode = node.ListNext;
|
||||
}
|
||||
else
|
||||
{
|
||||
Operation argCopyOp = Operation(Instruction.Copy, dest, preservedArgs[index]);
|
||||
nextNode = nodes.AddBefore(node, argCopyOp);
|
||||
}
|
||||
|
||||
Delete(nodes, node);
|
||||
return nextNode;
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: Pass on stack.
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
public static void InsertReturnCopy(IntrusiveList<Operation> nodes, Operation node)
|
||||
{
|
||||
if (node.SourcesCount == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Operand source = node.GetSource(0);
|
||||
|
||||
if (source.Type == OperandType.V128)
|
||||
{
|
||||
Operand retLReg = Gpr(CallingConvention.GetIntReturnRegister(), OperandType.I64);
|
||||
Operand retHReg = Gpr(CallingConvention.GetIntReturnRegisterHigh(), OperandType.I64);
|
||||
|
||||
nodes.AddBefore(node, Operation(Instruction.VectorExtract, retLReg, source, Const(0)));
|
||||
nodes.AddBefore(node, Operation(Instruction.VectorExtract, retHReg, source, Const(1)));
|
||||
}
|
||||
else
|
||||
{
|
||||
Operand retReg = source.Type.IsInteger()
|
||||
? Gpr(CallingConvention.GetIntReturnRegister(), source.Type)
|
||||
: Xmm(CallingConvention.GetVecReturnRegister(), source.Type);
|
||||
|
||||
Operation retCopyOp = Operation(Instruction.Copy, retReg, source);
|
||||
|
||||
nodes.AddBefore(node, retCopyOp);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
327
ARMeilleure/CodeGen/X86/PreAllocatorWindows.cs
Normal file
327
ARMeilleure/CodeGen/X86/PreAllocatorWindows.cs
Normal file
@ -0,0 +1,327 @@
|
||||
using ARMeilleure.CodeGen.RegisterAllocators;
|
||||
using ARMeilleure.IntermediateRepresentation;
|
||||
using ARMeilleure.Translation;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using static ARMeilleure.IntermediateRepresentation.Operand.Factory;
|
||||
using static ARMeilleure.IntermediateRepresentation.Operation.Factory;
|
||||
|
||||
namespace ARMeilleure.CodeGen.X86
|
||||
{
|
||||
class PreAllocatorWindows : PreAllocator
|
||||
{
|
||||
public static void InsertCallCopies(IntrusiveList<Operation> nodes, StackAllocator stackAlloc, Operation node)
|
||||
{
|
||||
Operand dest = node.Destination;
|
||||
|
||||
// Handle struct arguments.
|
||||
int retArgs = 0;
|
||||
int stackAllocOffset = 0;
|
||||
|
||||
int AllocateOnStack(int size)
|
||||
{
|
||||
// We assume that the stack allocator is initially empty (TotalSize = 0).
|
||||
// Taking that into account, we can reuse the space allocated for other
|
||||
// calls by keeping track of our own allocated size (stackAllocOffset).
|
||||
// If the space allocated is not big enough, then we just expand it.
|
||||
int offset = stackAllocOffset;
|
||||
|
||||
if (stackAllocOffset + size > stackAlloc.TotalSize)
|
||||
{
|
||||
stackAlloc.Allocate((stackAllocOffset + size) - stackAlloc.TotalSize);
|
||||
}
|
||||
|
||||
stackAllocOffset += size;
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
Operand arg0Reg = default;
|
||||
|
||||
if (dest != default && dest.Type == OperandType.V128)
|
||||
{
|
||||
int stackOffset = AllocateOnStack(dest.Type.GetSizeInBytes());
|
||||
|
||||
arg0Reg = Gpr(CallingConvention.GetIntArgumentRegister(0), OperandType.I64);
|
||||
|
||||
Operation allocOp = Operation(Instruction.StackAlloc, arg0Reg, Const(stackOffset));
|
||||
|
||||
nodes.AddBefore(node, allocOp);
|
||||
|
||||
retArgs = 1;
|
||||
}
|
||||
|
||||
int argsCount = node.SourcesCount - 1;
|
||||
int maxArgs = CallingConvention.GetArgumentsOnRegsCount() - retArgs;
|
||||
|
||||
if (argsCount > maxArgs)
|
||||
{
|
||||
argsCount = maxArgs;
|
||||
}
|
||||
|
||||
Operand[] sources = new Operand[1 + retArgs + argsCount];
|
||||
|
||||
sources[0] = node.GetSource(0);
|
||||
|
||||
if (arg0Reg != default)
|
||||
{
|
||||
sources[1] = arg0Reg;
|
||||
}
|
||||
|
||||
for (int index = 1; index < node.SourcesCount; index++)
|
||||
{
|
||||
Operand source = node.GetSource(index);
|
||||
|
||||
if (source.Type == OperandType.V128)
|
||||
{
|
||||
Operand stackAddr = Local(OperandType.I64);
|
||||
|
||||
int stackOffset = AllocateOnStack(source.Type.GetSizeInBytes());
|
||||
|
||||
nodes.AddBefore(node, Operation(Instruction.StackAlloc, stackAddr, Const(stackOffset)));
|
||||
|
||||
Operation storeOp = Operation(Instruction.Store, default, stackAddr, source);
|
||||
|
||||
InsertConstantRegCopies(nodes, nodes.AddBefore(node, storeOp));
|
||||
|
||||
node.SetSource(index, stackAddr);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle arguments passed on registers.
|
||||
for (int index = 0; index < argsCount; index++)
|
||||
{
|
||||
Operand source = node.GetSource(index + 1);
|
||||
Operand argReg;
|
||||
|
||||
int argIndex = index + retArgs;
|
||||
|
||||
if (source.Type.IsInteger())
|
||||
{
|
||||
argReg = Gpr(CallingConvention.GetIntArgumentRegister(argIndex), source.Type);
|
||||
}
|
||||
else
|
||||
{
|
||||
argReg = Xmm(CallingConvention.GetVecArgumentRegister(argIndex), source.Type);
|
||||
}
|
||||
|
||||
Operation copyOp = Operation(Instruction.Copy, argReg, source);
|
||||
|
||||
InsertConstantRegCopies(nodes, nodes.AddBefore(node, copyOp));
|
||||
|
||||
sources[1 + retArgs + index] = argReg;
|
||||
}
|
||||
|
||||
// The remaining arguments (those that are not passed on registers)
|
||||
// should be passed on the stack, we write them to the stack with "SpillArg".
|
||||
for (int index = argsCount; index < node.SourcesCount - 1; index++)
|
||||
{
|
||||
Operand source = node.GetSource(index + 1);
|
||||
Operand offset = Const((index + retArgs) * 8);
|
||||
|
||||
Operation spillOp = Operation(Instruction.SpillArg, default, offset, source);
|
||||
|
||||
InsertConstantRegCopies(nodes, nodes.AddBefore(node, spillOp));
|
||||
}
|
||||
|
||||
if (dest != default)
|
||||
{
|
||||
if (dest.Type == OperandType.V128)
|
||||
{
|
||||
Operand retValueAddr = Local(OperandType.I64);
|
||||
|
||||
nodes.AddBefore(node, Operation(Instruction.Copy, retValueAddr, arg0Reg));
|
||||
|
||||
Operation loadOp = Operation(Instruction.Load, dest, retValueAddr);
|
||||
|
||||
nodes.AddAfter(node, loadOp);
|
||||
|
||||
node.Destination = default;
|
||||
}
|
||||
else
|
||||
{
|
||||
Operand retReg = dest.Type.IsInteger()
|
||||
? Gpr(CallingConvention.GetIntReturnRegister(), dest.Type)
|
||||
: Xmm(CallingConvention.GetVecReturnRegister(), dest.Type);
|
||||
|
||||
Operation copyOp = Operation(Instruction.Copy, dest, retReg);
|
||||
|
||||
nodes.AddAfter(node, copyOp);
|
||||
|
||||
node.Destination = retReg;
|
||||
}
|
||||
}
|
||||
|
||||
node.SetSources(sources);
|
||||
}
|
||||
|
||||
public static void InsertTailcallCopies(IntrusiveList<Operation> nodes, StackAllocator stackAlloc, Operation node)
|
||||
{
|
||||
int argsCount = node.SourcesCount - 1;
|
||||
int maxArgs = CallingConvention.GetArgumentsOnRegsCount();
|
||||
|
||||
if (argsCount > maxArgs)
|
||||
{
|
||||
throw new NotImplementedException("Spilling is not currently supported for tail calls. (too many arguments)");
|
||||
}
|
||||
|
||||
Operand[] sources = new Operand[1 + argsCount];
|
||||
|
||||
// Handle arguments passed on registers.
|
||||
for (int index = 0; index < argsCount; index++)
|
||||
{
|
||||
Operand source = node.GetSource(1 + index);
|
||||
Operand argReg = source.Type.IsInteger()
|
||||
? Gpr(CallingConvention.GetIntArgumentRegister(index), source.Type)
|
||||
: Xmm(CallingConvention.GetVecArgumentRegister(index), source.Type);
|
||||
|
||||
Operation copyOp = Operation(Instruction.Copy, argReg, source);
|
||||
|
||||
InsertConstantRegCopies(nodes, nodes.AddBefore(node, copyOp));
|
||||
|
||||
sources[1 + index] = argReg;
|
||||
}
|
||||
|
||||
// The target address must be on the return registers, since we
|
||||
// don't return anything and it is guaranteed to not be a
|
||||
// callee saved register (which would be trashed on the epilogue).
|
||||
Operand retReg = Gpr(CallingConvention.GetIntReturnRegister(), OperandType.I64);
|
||||
|
||||
Operation addrCopyOp = Operation(Instruction.Copy, retReg, node.GetSource(0));
|
||||
|
||||
nodes.AddBefore(node, addrCopyOp);
|
||||
|
||||
sources[0] = retReg;
|
||||
|
||||
node.SetSources(sources);
|
||||
}
|
||||
|
||||
public static Operation InsertLoadArgumentCopy(
|
||||
CompilerContext cctx,
|
||||
ref Span<Operation> buffer,
|
||||
IntrusiveList<Operation> nodes,
|
||||
Operand[] preservedArgs,
|
||||
Operation node)
|
||||
{
|
||||
Operand source = node.GetSource(0);
|
||||
|
||||
Debug.Assert(source.Kind == OperandKind.Constant, "Non-constant LoadArgument source kind.");
|
||||
|
||||
int retArgs = cctx.FuncReturnType == OperandType.V128 ? 1 : 0;
|
||||
|
||||
int index = source.AsInt32() + retArgs;
|
||||
|
||||
if (index < CallingConvention.GetArgumentsOnRegsCount())
|
||||
{
|
||||
Operand dest = node.Destination;
|
||||
|
||||
if (preservedArgs[index] == default)
|
||||
{
|
||||
Operand argReg, pArg;
|
||||
|
||||
if (dest.Type.IsInteger())
|
||||
{
|
||||
argReg = Gpr(CallingConvention.GetIntArgumentRegister(index), dest.Type);
|
||||
pArg = Local(dest.Type);
|
||||
}
|
||||
else if (dest.Type == OperandType.V128)
|
||||
{
|
||||
argReg = Gpr(CallingConvention.GetIntArgumentRegister(index), OperandType.I64);
|
||||
pArg = Local(OperandType.I64);
|
||||
}
|
||||
else
|
||||
{
|
||||
argReg = Xmm(CallingConvention.GetVecArgumentRegister(index), dest.Type);
|
||||
pArg = Local(dest.Type);
|
||||
}
|
||||
|
||||
Operation copyOp = Operation(Instruction.Copy, pArg, argReg);
|
||||
|
||||
cctx.Cfg.Entry.Operations.AddFirst(copyOp);
|
||||
|
||||
preservedArgs[index] = pArg;
|
||||
}
|
||||
|
||||
Operation nextNode;
|
||||
|
||||
if (dest.Type != OperandType.V128 && dest.AssignmentsCount == 1)
|
||||
{
|
||||
// Let's propagate the argument if we can to avoid copies.
|
||||
PreAllocatorCommon.Propagate(ref buffer, dest, preservedArgs[index]);
|
||||
nextNode = node.ListNext;
|
||||
}
|
||||
else
|
||||
{
|
||||
Operation argCopyOp = Operation(dest.Type == OperandType.V128
|
||||
? Instruction.Load
|
||||
: Instruction.Copy, dest, preservedArgs[index]);
|
||||
|
||||
nextNode = nodes.AddBefore(node, argCopyOp);
|
||||
}
|
||||
|
||||
Delete(nodes, node);
|
||||
return nextNode;
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: Pass on stack.
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
public static void InsertReturnCopy(
|
||||
CompilerContext cctx,
|
||||
IntrusiveList<Operation> nodes,
|
||||
Operand[] preservedArgs,
|
||||
Operation node)
|
||||
{
|
||||
if (node.SourcesCount == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Operand source = node.GetSource(0);
|
||||
Operand retReg;
|
||||
|
||||
if (source.Type.IsInteger())
|
||||
{
|
||||
retReg = Gpr(CallingConvention.GetIntReturnRegister(), source.Type);
|
||||
}
|
||||
else if (source.Type == OperandType.V128)
|
||||
{
|
||||
if (preservedArgs[0] == default)
|
||||
{
|
||||
Operand preservedArg = Local(OperandType.I64);
|
||||
Operand arg0 = Gpr(CallingConvention.GetIntArgumentRegister(0), OperandType.I64);
|
||||
|
||||
Operation copyOp = Operation(Instruction.Copy, preservedArg, arg0);
|
||||
|
||||
cctx.Cfg.Entry.Operations.AddFirst(copyOp);
|
||||
|
||||
preservedArgs[0] = preservedArg;
|
||||
}
|
||||
|
||||
retReg = preservedArgs[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
retReg = Xmm(CallingConvention.GetVecReturnRegister(), source.Type);
|
||||
}
|
||||
|
||||
if (source.Type == OperandType.V128)
|
||||
{
|
||||
Operation retStoreOp = Operation(Instruction.Store, default, retReg, source);
|
||||
|
||||
nodes.AddBefore(node, retStoreOp);
|
||||
}
|
||||
else
|
||||
{
|
||||
Operation retCopyOp = Operation(Instruction.Copy, retReg, source);
|
||||
|
||||
nodes.AddBefore(node, retCopyOp);
|
||||
}
|
||||
|
||||
node.SetSources(Array.Empty<Operand>());
|
||||
}
|
||||
}
|
||||
}
|
@ -208,17 +208,20 @@ namespace ARMeilleure.CodeGen.X86
|
||||
Vblendvps,
|
||||
Vcvtph2ps,
|
||||
Vcvtps2ph,
|
||||
Vfmadd231pd,
|
||||
Vfmadd231ps,
|
||||
Vfmadd231sd,
|
||||
Vfmadd231ss,
|
||||
Vfmsub231sd,
|
||||
Vfmsub231ss,
|
||||
Vfnmadd231pd,
|
||||
Vfnmadd231ps,
|
||||
Vfnmadd231sd,
|
||||
Vfnmadd231ss,
|
||||
Vfnmsub231sd,
|
||||
Vfnmsub231ss,
|
||||
Vpblendvb,
|
||||
Vpternlogd,
|
||||
Xor,
|
||||
Xorpd,
|
||||
Xorps,
|
||||
|
@ -17,7 +17,7 @@ namespace ARMeilleure.Decoders
|
||||
{
|
||||
uint[] tbl = new uint[256];
|
||||
|
||||
for (int idx = 0; idx < 256; idx++)
|
||||
for (int idx = 0; idx < tbl.Length; idx++)
|
||||
{
|
||||
tbl[idx] = ExpandImm8ToFP32((uint)idx);
|
||||
}
|
||||
@ -29,7 +29,7 @@ namespace ARMeilleure.Decoders
|
||||
{
|
||||
ulong[] tbl = new ulong[256];
|
||||
|
||||
for (int idx = 0; idx < 256; idx++)
|
||||
for (int idx = 0; idx < tbl.Length; idx++)
|
||||
{
|
||||
tbl[idx] = ExpandImm8ToFP64((ulong)idx);
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
namespace ARMeilleure.Decoders;
|
||||
|
||||
interface IOpCode32Exception
|
||||
namespace ARMeilleure.Decoders
|
||||
{
|
||||
int Id { get; }
|
||||
interface IOpCode32Exception
|
||||
{
|
||||
int Id { get; }
|
||||
}
|
||||
}
|
@ -108,6 +108,13 @@ namespace ARMeilleure.Decoders
|
||||
SetA64("11001010xx0xxxxxxxxxxxxxxxxxxxxx", InstName.Eor, InstEmit.Eor, OpCodeAluRs.Create);
|
||||
SetA64("00010011100xxxxx0xxxxxxxxxxxxxxx", InstName.Extr, InstEmit.Extr, OpCodeAluRs.Create);
|
||||
SetA64("10010011110xxxxxxxxxxxxxxxxxxxxx", InstName.Extr, InstEmit.Extr, OpCodeAluRs.Create);
|
||||
SetA64("11010101000000110010000011011111", InstName.Hint, InstEmit.Nop, OpCodeSystem.Create); // Reserved Hint
|
||||
SetA64("11010101000000110010000011111111", InstName.Hint, InstEmit.Nop, OpCodeSystem.Create); // Reserved Hint
|
||||
SetA64("110101010000001100100001xxx11111", InstName.Hint, InstEmit.Nop, OpCodeSystem.Create); // Reserved Hint
|
||||
SetA64("1101010100000011001000100xx11111", InstName.Hint, InstEmit.Nop, OpCodeSystem.Create); // Reserved Hint
|
||||
SetA64("1101010100000011001000101>>11111", InstName.Hint, InstEmit.Nop, OpCodeSystem.Create); // Reserved Hint
|
||||
SetA64("110101010000001100100011xxx11111", InstName.Hint, InstEmit.Nop, OpCodeSystem.Create); // Reserved Hint
|
||||
SetA64("11010101000000110010>>xxxxx11111", InstName.Hint, InstEmit.Nop, OpCodeSystem.Create); // Reserved Hint
|
||||
SetA64("11010101000000110011xxxx11011111", InstName.Isb, InstEmit.Isb, OpCodeSystem.Create);
|
||||
SetA64("xx001000110xxxxx1xxxxxxxxxxxxxxx", InstName.Ldar, InstEmit.Ldar, OpCodeMemEx.Create);
|
||||
SetA64("1x001000011xxxxx1xxxxxxxxxxxxxxx", InstName.Ldaxp, InstEmit.Ldaxp, OpCodeMemEx.Create);
|
||||
@ -1301,7 +1308,7 @@ namespace ARMeilleure.Decoders
|
||||
{
|
||||
List<InstInfo>[] temp = new List<InstInfo>[FastLookupSize];
|
||||
|
||||
for (int index = 0; index < FastLookupSize; index++)
|
||||
for (int index = 0; index < temp.Length; index++)
|
||||
{
|
||||
temp[index] = new List<InstInfo>();
|
||||
}
|
||||
@ -1311,7 +1318,7 @@ namespace ARMeilleure.Decoders
|
||||
int mask = ToFastLookupIndex(inst.Mask);
|
||||
int value = ToFastLookupIndex(inst.Value);
|
||||
|
||||
for (int index = 0; index < FastLookupSize; index++)
|
||||
for (int index = 0; index < temp.Length; index++)
|
||||
{
|
||||
if ((index & mask) == value)
|
||||
{
|
||||
@ -1320,7 +1327,7 @@ namespace ARMeilleure.Decoders
|
||||
}
|
||||
}
|
||||
|
||||
for (int index = 0; index < FastLookupSize; index++)
|
||||
for (int index = 0; index < temp.Length; index++)
|
||||
{
|
||||
table[index] = temp[index].ToArray();
|
||||
}
|
||||
|
@ -1,9 +1,8 @@
|
||||
using ARMeilleure.Decoders;
|
||||
using ARMeilleure.IntermediateRepresentation;
|
||||
using ARMeilleure.Translation;
|
||||
|
||||
using static ARMeilleure.Instructions.InstEmitHelper;
|
||||
using static ARMeilleure.Instructions.InstEmitHashHelper;
|
||||
using static ARMeilleure.Instructions.InstEmitHelper;
|
||||
|
||||
namespace ARMeilleure.Instructions
|
||||
{
|
||||
|
@ -4,9 +4,8 @@ using ARMeilleure.IntermediateRepresentation;
|
||||
using ARMeilleure.Translation;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
using static ARMeilleure.IntermediateRepresentation.Operand.Factory;
|
||||
using static ARMeilleure.Instructions.InstEmitSimdHelper;
|
||||
using static ARMeilleure.IntermediateRepresentation.Operand.Factory;
|
||||
|
||||
namespace ARMeilleure.Instructions
|
||||
{
|
||||
|
@ -615,14 +615,11 @@ namespace ARMeilleure.Instructions
|
||||
{
|
||||
return EmitSse41ProcessNaNsOpF(context, (op1, op2) =>
|
||||
{
|
||||
return EmitSseOrAvxHandleFzModeOpF(context, (op1, op2) =>
|
||||
{
|
||||
IOpCodeSimd op = (IOpCodeSimd)context.CurrOp;
|
||||
IOpCodeSimd op = (IOpCodeSimd)context.CurrOp;
|
||||
|
||||
Intrinsic addInst = (op.Size & 1) == 0 ? Intrinsic.X86Addps : Intrinsic.X86Addpd;
|
||||
Intrinsic addInst = (op.Size & 1) == 0 ? Intrinsic.X86Addps : Intrinsic.X86Addpd;
|
||||
|
||||
return context.AddIntrinsic(addInst, op1, op2);
|
||||
}, scalar: false, op1, op2);
|
||||
return context.AddIntrinsic(addInst, op1, op2);
|
||||
}, scalar: false, op1, op2);
|
||||
});
|
||||
}
|
||||
@ -696,17 +693,33 @@ namespace ARMeilleure.Instructions
|
||||
Operand n = GetVec(op.Rn);
|
||||
Operand m = GetVec(op.Rm);
|
||||
|
||||
Operand res;
|
||||
|
||||
if (op.Size == 0)
|
||||
{
|
||||
Operand res = context.AddIntrinsic(Intrinsic.X86Mulss, n, m);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Addss, a, res);
|
||||
if (Optimizations.UseFma)
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Vfmadd231ss, a, n, m);
|
||||
}
|
||||
else
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Mulss, n, m);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Addss, a, res);
|
||||
}
|
||||
|
||||
context.Copy(d, context.VectorZeroUpper96(res));
|
||||
}
|
||||
else /* if (op.Size == 1) */
|
||||
{
|
||||
Operand res = context.AddIntrinsic(Intrinsic.X86Mulsd, n, m);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Addsd, a, res);
|
||||
if (Optimizations.UseFma)
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Vfmadd231sd, a, n, m);
|
||||
}
|
||||
else
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Mulsd, n, m);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Addsd, a, res);
|
||||
}
|
||||
|
||||
context.Copy(d, context.VectorZeroUpper64(res));
|
||||
}
|
||||
@ -730,10 +743,7 @@ namespace ARMeilleure.Instructions
|
||||
{
|
||||
EmitSse41ProcessNaNsOpF(context, (op1, op2) =>
|
||||
{
|
||||
return EmitSseOrAvxHandleFzModeOpF(context, (op1, op2) =>
|
||||
{
|
||||
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: true);
|
||||
}, scalar: true, op1, op2);
|
||||
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: true);
|
||||
}, scalar: true);
|
||||
}
|
||||
else
|
||||
@ -755,10 +765,7 @@ namespace ARMeilleure.Instructions
|
||||
{
|
||||
EmitSse41ProcessNaNsOpF(context, (op1, op2) =>
|
||||
{
|
||||
return EmitSseOrAvxHandleFzModeOpF(context, (op1, op2) =>
|
||||
{
|
||||
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: true);
|
||||
}, scalar: false, op1, op2);
|
||||
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: true);
|
||||
}, scalar: false);
|
||||
}
|
||||
else
|
||||
@ -886,10 +893,7 @@ namespace ARMeilleure.Instructions
|
||||
{
|
||||
return EmitSse41ProcessNaNsOpF(context, (op1, op2) =>
|
||||
{
|
||||
return EmitSseOrAvxHandleFzModeOpF(context, (op1, op2) =>
|
||||
{
|
||||
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: true);
|
||||
}, scalar: false, op1, op2);
|
||||
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: true);
|
||||
}, scalar: false, op1, op2);
|
||||
});
|
||||
}
|
||||
@ -914,10 +918,7 @@ namespace ARMeilleure.Instructions
|
||||
{
|
||||
return EmitSse41ProcessNaNsOpF(context, (op1, op2) =>
|
||||
{
|
||||
return EmitSseOrAvxHandleFzModeOpF(context, (op1, op2) =>
|
||||
{
|
||||
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: true);
|
||||
}, scalar: false, op1, op2);
|
||||
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: true);
|
||||
}, scalar: false, op1, op2);
|
||||
});
|
||||
}
|
||||
@ -940,10 +941,7 @@ namespace ARMeilleure.Instructions
|
||||
{
|
||||
EmitSse41ProcessNaNsOpF(context, (op1, op2) =>
|
||||
{
|
||||
return EmitSseOrAvxHandleFzModeOpF(context, (op1, op2) =>
|
||||
{
|
||||
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: false);
|
||||
}, scalar: true, op1, op2);
|
||||
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: false);
|
||||
}, scalar: true);
|
||||
}
|
||||
else
|
||||
@ -965,10 +963,7 @@ namespace ARMeilleure.Instructions
|
||||
{
|
||||
EmitSse41ProcessNaNsOpF(context, (op1, op2) =>
|
||||
{
|
||||
return EmitSseOrAvxHandleFzModeOpF(context, (op1, op2) =>
|
||||
{
|
||||
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: false);
|
||||
}, scalar: false, op1, op2);
|
||||
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: false);
|
||||
}, scalar: false);
|
||||
}
|
||||
else
|
||||
@ -1096,10 +1091,7 @@ namespace ARMeilleure.Instructions
|
||||
{
|
||||
return EmitSse41ProcessNaNsOpF(context, (op1, op2) =>
|
||||
{
|
||||
return EmitSseOrAvxHandleFzModeOpF(context, (op1, op2) =>
|
||||
{
|
||||
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: false);
|
||||
}, scalar: false, op1, op2);
|
||||
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: false);
|
||||
}, scalar: false, op1, op2);
|
||||
});
|
||||
}
|
||||
@ -1124,10 +1116,7 @@ namespace ARMeilleure.Instructions
|
||||
{
|
||||
return EmitSse41ProcessNaNsOpF(context, (op1, op2) =>
|
||||
{
|
||||
return EmitSseOrAvxHandleFzModeOpF(context, (op1, op2) =>
|
||||
{
|
||||
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: false);
|
||||
}, scalar: false, op1, op2);
|
||||
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: false);
|
||||
}, scalar: false, op1, op2);
|
||||
});
|
||||
}
|
||||
@ -1146,6 +1135,37 @@ namespace ARMeilleure.Instructions
|
||||
{
|
||||
InstEmitSimdHelperArm64.EmitScalarTernaryOpFRdByElem(context, Intrinsic.Arm64FmlaSe);
|
||||
}
|
||||
else if (Optimizations.UseFma)
|
||||
{
|
||||
OpCodeSimdRegElemF op = (OpCodeSimdRegElemF)context.CurrOp;
|
||||
|
||||
Operand d = GetVec(op.Rd);
|
||||
Operand n = GetVec(op.Rn);
|
||||
Operand m = GetVec(op.Rm);
|
||||
|
||||
int sizeF = op.Size & 1;
|
||||
|
||||
if (sizeF == 0)
|
||||
{
|
||||
int shuffleMask = op.Index | op.Index << 2 | op.Index << 4 | op.Index << 6;
|
||||
|
||||
Operand res = context.AddIntrinsic(Intrinsic.X86Shufps, m, m, Const(shuffleMask));
|
||||
|
||||
res = context.AddIntrinsic(Intrinsic.X86Vfmadd231ss, d, n, res);
|
||||
|
||||
context.Copy(d, context.VectorZeroUpper96(res));
|
||||
}
|
||||
else /* if (sizeF == 1) */
|
||||
{
|
||||
int shuffleMask = op.Index | op.Index << 1;
|
||||
|
||||
Operand res = context.AddIntrinsic(Intrinsic.X86Shufpd, m, m, Const(shuffleMask));
|
||||
|
||||
res = context.AddIntrinsic(Intrinsic.X86Vfmadd231sd, d, n, res);
|
||||
|
||||
context.Copy(d, context.VectorZeroUpper64(res));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitScalarTernaryOpByElemF(context, (op1, op2, op3) =>
|
||||
@ -1171,11 +1191,19 @@ namespace ARMeilleure.Instructions
|
||||
|
||||
int sizeF = op.Size & 1;
|
||||
|
||||
Operand res;
|
||||
|
||||
if (sizeF == 0)
|
||||
{
|
||||
Operand res = context.AddIntrinsic(Intrinsic.X86Mulps, n, m);
|
||||
|
||||
res = context.AddIntrinsic(Intrinsic.X86Addps, d, res);
|
||||
if (Optimizations.UseFma)
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Vfmadd231ps, d, n, m);
|
||||
}
|
||||
else
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Mulps, n, m);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Addps, d, res);
|
||||
}
|
||||
|
||||
if (op.RegisterSize == RegisterSize.Simd64)
|
||||
{
|
||||
@ -1186,9 +1214,15 @@ namespace ARMeilleure.Instructions
|
||||
}
|
||||
else /* if (sizeF == 1) */
|
||||
{
|
||||
Operand res = context.AddIntrinsic(Intrinsic.X86Mulpd, n, m);
|
||||
|
||||
res = context.AddIntrinsic(Intrinsic.X86Addpd, d, res);
|
||||
if (Optimizations.UseFma)
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Vfmadd231pd, d, n, m);
|
||||
}
|
||||
else
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Mulpd, n, m);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Addpd, d, res);
|
||||
}
|
||||
|
||||
context.Copy(d, res);
|
||||
}
|
||||
@ -1224,8 +1258,15 @@ namespace ARMeilleure.Instructions
|
||||
|
||||
Operand res = context.AddIntrinsic(Intrinsic.X86Shufps, m, m, Const(shuffleMask));
|
||||
|
||||
res = context.AddIntrinsic(Intrinsic.X86Mulps, n, res);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Addps, d, res);
|
||||
if (Optimizations.UseFma)
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Vfmadd231ps, d, n, res);
|
||||
}
|
||||
else
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Mulps, n, res);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Addps, d, res);
|
||||
}
|
||||
|
||||
if (op.RegisterSize == RegisterSize.Simd64)
|
||||
{
|
||||
@ -1240,8 +1281,15 @@ namespace ARMeilleure.Instructions
|
||||
|
||||
Operand res = context.AddIntrinsic(Intrinsic.X86Shufpd, m, m, Const(shuffleMask));
|
||||
|
||||
res = context.AddIntrinsic(Intrinsic.X86Mulpd, n, res);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Addpd, d, res);
|
||||
if (Optimizations.UseFma)
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Vfmadd231pd, d, n, res);
|
||||
}
|
||||
else
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Mulpd, n, res);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Addpd, d, res);
|
||||
}
|
||||
|
||||
context.Copy(d, res);
|
||||
}
|
||||
@ -1261,6 +1309,37 @@ namespace ARMeilleure.Instructions
|
||||
{
|
||||
InstEmitSimdHelperArm64.EmitScalarTernaryOpFRdByElem(context, Intrinsic.Arm64FmlsSe);
|
||||
}
|
||||
else if (Optimizations.UseFma)
|
||||
{
|
||||
OpCodeSimdRegElemF op = (OpCodeSimdRegElemF)context.CurrOp;
|
||||
|
||||
Operand d = GetVec(op.Rd);
|
||||
Operand n = GetVec(op.Rn);
|
||||
Operand m = GetVec(op.Rm);
|
||||
|
||||
int sizeF = op.Size & 1;
|
||||
|
||||
if (sizeF == 0)
|
||||
{
|
||||
int shuffleMask = op.Index | op.Index << 2 | op.Index << 4 | op.Index << 6;
|
||||
|
||||
Operand res = context.AddIntrinsic(Intrinsic.X86Shufps, m, m, Const(shuffleMask));
|
||||
|
||||
res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231ss, d, n, res);
|
||||
|
||||
context.Copy(d, context.VectorZeroUpper96(res));
|
||||
}
|
||||
else /* if (sizeF == 1) */
|
||||
{
|
||||
int shuffleMask = op.Index | op.Index << 1;
|
||||
|
||||
Operand res = context.AddIntrinsic(Intrinsic.X86Shufpd, m, m, Const(shuffleMask));
|
||||
|
||||
res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231sd, d, n, res);
|
||||
|
||||
context.Copy(d, context.VectorZeroUpper64(res));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitScalarTernaryOpByElemF(context, (op1, op2, op3) =>
|
||||
@ -1286,11 +1365,19 @@ namespace ARMeilleure.Instructions
|
||||
|
||||
int sizeF = op.Size & 1;
|
||||
|
||||
Operand res;
|
||||
|
||||
if (sizeF == 0)
|
||||
{
|
||||
Operand res = context.AddIntrinsic(Intrinsic.X86Mulps, n, m);
|
||||
|
||||
res = context.AddIntrinsic(Intrinsic.X86Subps, d, res);
|
||||
if (Optimizations.UseFma)
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231ps, d, n, m);
|
||||
}
|
||||
else
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Mulps, n, m);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Subps, d, res);
|
||||
}
|
||||
|
||||
if (op.RegisterSize == RegisterSize.Simd64)
|
||||
{
|
||||
@ -1301,9 +1388,15 @@ namespace ARMeilleure.Instructions
|
||||
}
|
||||
else /* if (sizeF == 1) */
|
||||
{
|
||||
Operand res = context.AddIntrinsic(Intrinsic.X86Mulpd, n, m);
|
||||
|
||||
res = context.AddIntrinsic(Intrinsic.X86Subpd, d, res);
|
||||
if (Optimizations.UseFma)
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231pd, d, n, m);
|
||||
}
|
||||
else
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Mulpd, n, m);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Subpd, d, res);
|
||||
}
|
||||
|
||||
context.Copy(d, res);
|
||||
}
|
||||
@ -1339,8 +1432,15 @@ namespace ARMeilleure.Instructions
|
||||
|
||||
Operand res = context.AddIntrinsic(Intrinsic.X86Shufps, m, m, Const(shuffleMask));
|
||||
|
||||
res = context.AddIntrinsic(Intrinsic.X86Mulps, n, res);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Subps, d, res);
|
||||
if (Optimizations.UseFma)
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231ps, d, n, res);
|
||||
}
|
||||
else
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Mulps, n, res);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Subps, d, res);
|
||||
}
|
||||
|
||||
if (op.RegisterSize == RegisterSize.Simd64)
|
||||
{
|
||||
@ -1355,8 +1455,15 @@ namespace ARMeilleure.Instructions
|
||||
|
||||
Operand res = context.AddIntrinsic(Intrinsic.X86Shufpd, m, m, Const(shuffleMask));
|
||||
|
||||
res = context.AddIntrinsic(Intrinsic.X86Mulpd, n, res);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Subpd, d, res);
|
||||
if (Optimizations.UseFma)
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231pd, d, n, res);
|
||||
}
|
||||
else
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Mulpd, n, res);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Subpd, d, res);
|
||||
}
|
||||
|
||||
context.Copy(d, res);
|
||||
}
|
||||
@ -1385,17 +1492,33 @@ namespace ARMeilleure.Instructions
|
||||
Operand n = GetVec(op.Rn);
|
||||
Operand m = GetVec(op.Rm);
|
||||
|
||||
Operand res;
|
||||
|
||||
if (op.Size == 0)
|
||||
{
|
||||
Operand res = context.AddIntrinsic(Intrinsic.X86Mulss, n, m);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Subss, a, res);
|
||||
if (Optimizations.UseFma)
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231ss, a, n, m);
|
||||
}
|
||||
else
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Mulss, n, m);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Subss, a, res);
|
||||
}
|
||||
|
||||
context.Copy(d, context.VectorZeroUpper96(res));
|
||||
}
|
||||
else /* if (op.Size == 1) */
|
||||
{
|
||||
Operand res = context.AddIntrinsic(Intrinsic.X86Mulsd, n, m);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Subsd, a, res);
|
||||
if (Optimizations.UseFma)
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231sd, a, n, m);
|
||||
}
|
||||
else
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Mulsd, n, m);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Subsd, a, res);
|
||||
}
|
||||
|
||||
context.Copy(d, context.VectorZeroUpper64(res));
|
||||
}
|
||||
@ -1669,25 +1792,39 @@ namespace ARMeilleure.Instructions
|
||||
Operand n = GetVec(op.Rn);
|
||||
Operand m = GetVec(op.Rm);
|
||||
|
||||
Operand res;
|
||||
|
||||
if (op.Size == 0)
|
||||
{
|
||||
Operand mask = X86GetScalar(context, -0f);
|
||||
if (Optimizations.UseFma)
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Vfnmsub231ss, a, n, m);
|
||||
}
|
||||
else
|
||||
{
|
||||
Operand mask = X86GetScalar(context, -0f);
|
||||
Operand aNeg = context.AddIntrinsic(Intrinsic.X86Xorps, mask, a);
|
||||
|
||||
Operand aNeg = context.AddIntrinsic(Intrinsic.X86Xorps, mask, a);
|
||||
|
||||
Operand res = context.AddIntrinsic(Intrinsic.X86Mulss, n, m);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Subss, aNeg, res);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Mulss, n, m);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Subss, aNeg, res);
|
||||
}
|
||||
|
||||
context.Copy(d, context.VectorZeroUpper96(res));
|
||||
}
|
||||
else /* if (op.Size == 1) */
|
||||
{
|
||||
Operand mask = X86GetScalar(context, -0d);
|
||||
if (Optimizations.UseFma)
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Vfnmsub231sd, a, n, m);
|
||||
}
|
||||
else
|
||||
{
|
||||
Operand mask = X86GetScalar(context, -0d);
|
||||
Operand aNeg = context.AddIntrinsic(Intrinsic.X86Xorpd, mask, a);
|
||||
|
||||
Operand aNeg = context.AddIntrinsic(Intrinsic.X86Xorpd, mask, a);
|
||||
|
||||
Operand res = context.AddIntrinsic(Intrinsic.X86Mulsd, n, m);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Subsd, aNeg, res);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Mulsd, n, m);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Subsd, aNeg, res);
|
||||
}
|
||||
|
||||
context.Copy(d, context.VectorZeroUpper64(res));
|
||||
}
|
||||
@ -1716,25 +1853,39 @@ namespace ARMeilleure.Instructions
|
||||
Operand n = GetVec(op.Rn);
|
||||
Operand m = GetVec(op.Rm);
|
||||
|
||||
Operand res;
|
||||
|
||||
if (op.Size == 0)
|
||||
{
|
||||
Operand mask = X86GetScalar(context, -0f);
|
||||
if (Optimizations.UseFma)
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Vfmsub231ss, a, n, m);
|
||||
}
|
||||
else
|
||||
{
|
||||
Operand mask = X86GetScalar(context, -0f);
|
||||
Operand aNeg = context.AddIntrinsic(Intrinsic.X86Xorps, mask, a);
|
||||
|
||||
Operand aNeg = context.AddIntrinsic(Intrinsic.X86Xorps, mask, a);
|
||||
|
||||
Operand res = context.AddIntrinsic(Intrinsic.X86Mulss, n, m);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Addss, aNeg, res);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Mulss, n, m);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Addss, aNeg, res);
|
||||
}
|
||||
|
||||
context.Copy(d, context.VectorZeroUpper96(res));
|
||||
}
|
||||
else /* if (op.Size == 1) */
|
||||
{
|
||||
Operand mask = X86GetScalar(context, -0d);
|
||||
if (Optimizations.UseFma)
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Vfmsub231sd, a, n, m);
|
||||
}
|
||||
else
|
||||
{
|
||||
Operand mask = X86GetScalar(context, -0d);
|
||||
Operand aNeg = context.AddIntrinsic(Intrinsic.X86Xorpd, mask, a);
|
||||
|
||||
Operand aNeg = context.AddIntrinsic(Intrinsic.X86Xorpd, mask, a);
|
||||
|
||||
Operand res = context.AddIntrinsic(Intrinsic.X86Mulsd, n, m);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Addsd, aNeg, res);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Mulsd, n, m);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Addsd, aNeg, res);
|
||||
}
|
||||
|
||||
context.Copy(d, context.VectorZeroUpper64(res));
|
||||
}
|
||||
@ -1830,13 +1981,22 @@ namespace ARMeilleure.Instructions
|
||||
|
||||
int sizeF = op.Size & 1;
|
||||
|
||||
Operand res;
|
||||
|
||||
if (sizeF == 0)
|
||||
{
|
||||
Operand mask = X86GetScalar(context, 2f);
|
||||
|
||||
Operand res = context.AddIntrinsic(Intrinsic.X86Mulss, n, m);
|
||||
if (Optimizations.UseFma)
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231ss, mask, n, m);
|
||||
}
|
||||
else
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Mulss, n, m);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Subss, mask, res);
|
||||
}
|
||||
|
||||
res = context.AddIntrinsic(Intrinsic.X86Subss, mask, res);
|
||||
res = EmitSse41RecipStepSelectOpF(context, n, m, res, mask, scalar: true, sizeF);
|
||||
|
||||
context.Copy(GetVec(op.Rd), context.VectorZeroUpper96(res));
|
||||
@ -1845,9 +2005,16 @@ namespace ARMeilleure.Instructions
|
||||
{
|
||||
Operand mask = X86GetScalar(context, 2d);
|
||||
|
||||
Operand res = context.AddIntrinsic(Intrinsic.X86Mulsd, n, m);
|
||||
if (Optimizations.UseFma)
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231sd, mask, n, m);
|
||||
}
|
||||
else
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Mulsd, n, m);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Subsd, mask, res);
|
||||
}
|
||||
|
||||
res = context.AddIntrinsic(Intrinsic.X86Subsd, mask, res);
|
||||
res = EmitSse41RecipStepSelectOpF(context, n, m, res, mask, scalar: true, sizeF);
|
||||
|
||||
context.Copy(GetVec(op.Rd), context.VectorZeroUpper64(res));
|
||||
@ -1877,14 +2044,23 @@ namespace ARMeilleure.Instructions
|
||||
|
||||
int sizeF = op.Size & 1;
|
||||
|
||||
Operand res;
|
||||
|
||||
if (sizeF == 0)
|
||||
{
|
||||
Operand mask = X86GetAllElements(context, 2f);
|
||||
|
||||
Operand res = context.AddIntrinsic(Intrinsic.X86Mulps, n, m);
|
||||
res = EmitSse41RecipStepSelectOpF(context, n, m, res, mask, scalar: false, sizeF);
|
||||
if (Optimizations.UseFma)
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231ps, mask, n, m);
|
||||
}
|
||||
else
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Mulps, n, m);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Subps, mask, res);
|
||||
}
|
||||
|
||||
res = context.AddIntrinsic(Intrinsic.X86Subps, mask, res);
|
||||
res = EmitSse41RecipStepSelectOpF(context, n, m, res, mask, scalar: false, sizeF);
|
||||
|
||||
if (op.RegisterSize == RegisterSize.Simd64)
|
||||
{
|
||||
@ -1897,10 +2073,17 @@ namespace ARMeilleure.Instructions
|
||||
{
|
||||
Operand mask = X86GetAllElements(context, 2d);
|
||||
|
||||
Operand res = context.AddIntrinsic(Intrinsic.X86Mulpd, n, m);
|
||||
res = EmitSse41RecipStepSelectOpF(context, n, m, res, mask, scalar: false, sizeF);
|
||||
if (Optimizations.UseFma)
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231pd, mask, n, m);
|
||||
}
|
||||
else
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Mulpd, n, m);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Subpd, mask, res);
|
||||
}
|
||||
|
||||
res = context.AddIntrinsic(Intrinsic.X86Subpd, mask, res);
|
||||
res = EmitSse41RecipStepSelectOpF(context, n, m, res, mask, scalar: false, sizeF);
|
||||
|
||||
context.Copy(GetVec(op.Rd), res);
|
||||
}
|
||||
@ -2113,20 +2296,32 @@ namespace ARMeilleure.Instructions
|
||||
|
||||
public static void Frintx_S(ArmEmitterContext context)
|
||||
{
|
||||
// TODO Arm64: Fast path. Should we set host FPCR?
|
||||
EmitScalarUnaryOpF(context, (op1) =>
|
||||
if (Optimizations.UseAdvSimd)
|
||||
{
|
||||
return EmitRoundByRMode(context, op1);
|
||||
});
|
||||
InstEmitSimdHelperArm64.EmitScalarUnaryOpF(context, Intrinsic.Arm64FrintxS);
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitScalarUnaryOpF(context, (op1) =>
|
||||
{
|
||||
return EmitRoundByRMode(context, op1);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static void Frintx_V(ArmEmitterContext context)
|
||||
{
|
||||
// TODO Arm64: Fast path. Should we set host FPCR?
|
||||
EmitVectorUnaryOpF(context, (op1) =>
|
||||
if (Optimizations.UseAdvSimd)
|
||||
{
|
||||
return EmitRoundByRMode(context, op1);
|
||||
});
|
||||
InstEmitSimdHelperArm64.EmitVectorUnaryOpF(context, Intrinsic.Arm64FrintxV);
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitVectorUnaryOpF(context, (op1) =>
|
||||
{
|
||||
return EmitRoundByRMode(context, op1);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static void Frintz_S(ArmEmitterContext context)
|
||||
@ -2237,16 +2432,25 @@ namespace ARMeilleure.Instructions
|
||||
|
||||
int sizeF = op.Size & 1;
|
||||
|
||||
Operand res;
|
||||
|
||||
if (sizeF == 0)
|
||||
{
|
||||
Operand maskHalf = X86GetScalar(context, 0.5f);
|
||||
Operand maskThree = X86GetScalar(context, 3f);
|
||||
Operand maskOneHalf = X86GetScalar(context, 1.5f);
|
||||
|
||||
Operand res = context.AddIntrinsic(Intrinsic.X86Mulss, n, m);
|
||||
if (Optimizations.UseFma)
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231ss, maskThree, n, m);
|
||||
}
|
||||
else
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Mulss, n, m);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Subss, maskThree, res);
|
||||
}
|
||||
|
||||
res = context.AddIntrinsic(Intrinsic.X86Subss, maskThree, res);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Mulss, maskHalf, res);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Mulss, maskHalf, res);
|
||||
res = EmitSse41RecipStepSelectOpF(context, n, m, res, maskOneHalf, scalar: true, sizeF);
|
||||
|
||||
context.Copy(GetVec(op.Rd), context.VectorZeroUpper96(res));
|
||||
@ -2257,10 +2461,17 @@ namespace ARMeilleure.Instructions
|
||||
Operand maskThree = X86GetScalar(context, 3d);
|
||||
Operand maskOneHalf = X86GetScalar(context, 1.5d);
|
||||
|
||||
Operand res = context.AddIntrinsic(Intrinsic.X86Mulsd, n, m);
|
||||
if (Optimizations.UseFma)
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231sd, maskThree, n, m);
|
||||
}
|
||||
else
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Mulsd, n, m);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Subsd, maskThree, res);
|
||||
}
|
||||
|
||||
res = context.AddIntrinsic(Intrinsic.X86Subsd, maskThree, res);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Mulsd, maskHalf, res);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Mulsd, maskHalf, res);
|
||||
res = EmitSse41RecipStepSelectOpF(context, n, m, res, maskOneHalf, scalar: true, sizeF);
|
||||
|
||||
context.Copy(GetVec(op.Rd), context.VectorZeroUpper64(res));
|
||||
@ -2290,15 +2501,24 @@ namespace ARMeilleure.Instructions
|
||||
|
||||
int sizeF = op.Size & 1;
|
||||
|
||||
Operand res;
|
||||
|
||||
if (sizeF == 0)
|
||||
{
|
||||
Operand maskHalf = X86GetAllElements(context, 0.5f);
|
||||
Operand maskThree = X86GetAllElements(context, 3f);
|
||||
Operand maskOneHalf = X86GetAllElements(context, 1.5f);
|
||||
|
||||
Operand res = context.AddIntrinsic(Intrinsic.X86Mulps, n, m);
|
||||
if (Optimizations.UseFma)
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231ps, maskThree, n, m);
|
||||
}
|
||||
else
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Mulps, n, m);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Subps, maskThree, res);
|
||||
}
|
||||
|
||||
res = context.AddIntrinsic(Intrinsic.X86Subps, maskThree, res);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Mulps, maskHalf, res);
|
||||
res = EmitSse41RecipStepSelectOpF(context, n, m, res, maskOneHalf, scalar: false, sizeF);
|
||||
|
||||
@ -2315,9 +2535,16 @@ namespace ARMeilleure.Instructions
|
||||
Operand maskThree = X86GetAllElements(context, 3d);
|
||||
Operand maskOneHalf = X86GetAllElements(context, 1.5d);
|
||||
|
||||
Operand res = context.AddIntrinsic(Intrinsic.X86Mulpd, n, m);
|
||||
if (Optimizations.UseFma)
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231pd, maskThree, n, m);
|
||||
}
|
||||
else
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Mulpd, n, m);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Subpd, maskThree, res);
|
||||
}
|
||||
|
||||
res = context.AddIntrinsic(Intrinsic.X86Subpd, maskThree, res);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Mulpd, maskHalf, res);
|
||||
res = EmitSse41RecipStepSelectOpF(context, n, m, res, maskOneHalf, scalar: false, sizeF);
|
||||
|
||||
@ -4728,53 +4955,6 @@ namespace ARMeilleure.Instructions
|
||||
}
|
||||
}
|
||||
|
||||
public static Operand EmitSseOrAvxHandleFzModeOpF(
|
||||
ArmEmitterContext context,
|
||||
Func2I emit,
|
||||
bool scalar,
|
||||
Operand n = default,
|
||||
Operand m = default)
|
||||
{
|
||||
Operand nCopy = n == default ? context.Copy(GetVec(((OpCodeSimdReg)context.CurrOp).Rn)) : n;
|
||||
Operand mCopy = m == default ? context.Copy(GetVec(((OpCodeSimdReg)context.CurrOp).Rm)) : m;
|
||||
|
||||
EmitSseOrAvxEnterFtzAndDazModesOpF(context, out Operand isTrue);
|
||||
|
||||
Operand res = emit(nCopy, mCopy);
|
||||
|
||||
EmitSseOrAvxExitFtzAndDazModesOpF(context, isTrue);
|
||||
|
||||
if (n != default || m != default)
|
||||
{
|
||||
return res;
|
||||
}
|
||||
|
||||
int sizeF = ((IOpCodeSimd)context.CurrOp).Size & 1;
|
||||
|
||||
if (sizeF == 0)
|
||||
{
|
||||
if (scalar)
|
||||
{
|
||||
res = context.VectorZeroUpper96(res);
|
||||
}
|
||||
else if (((OpCodeSimdReg)context.CurrOp).RegisterSize == RegisterSize.Simd64)
|
||||
{
|
||||
res = context.VectorZeroUpper64(res);
|
||||
}
|
||||
}
|
||||
else /* if (sizeF == 1) */
|
||||
{
|
||||
if (scalar)
|
||||
{
|
||||
res = context.VectorZeroUpper64(res);
|
||||
}
|
||||
}
|
||||
|
||||
context.Copy(GetVec(((OpCodeSimdReg)context.CurrOp).Rd), res);
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
private static Operand EmitSse2VectorMaxMinOpF(ArmEmitterContext context, Operand n, Operand m, bool isMax)
|
||||
{
|
||||
IOpCodeSimd op = (IOpCodeSimd)context.CurrOp;
|
||||
@ -4834,10 +5014,7 @@ namespace ARMeilleure.Instructions
|
||||
|
||||
Operand res = EmitSse41ProcessNaNsOpF(context, (op1, op2) =>
|
||||
{
|
||||
return EmitSseOrAvxHandleFzModeOpF(context, (op1, op2) =>
|
||||
{
|
||||
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: isMaxNum);
|
||||
}, scalar: scalar, op1, op2);
|
||||
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: isMaxNum);
|
||||
}, scalar: scalar, nCopy, mCopy);
|
||||
|
||||
if (n != default || m != default)
|
||||
@ -4872,10 +5049,7 @@ namespace ARMeilleure.Instructions
|
||||
|
||||
Operand res = EmitSse41ProcessNaNsOpF(context, (op1, op2) =>
|
||||
{
|
||||
return EmitSseOrAvxHandleFzModeOpF(context, (op1, op2) =>
|
||||
{
|
||||
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: isMaxNum);
|
||||
}, scalar: scalar, op1, op2);
|
||||
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: isMaxNum);
|
||||
}, scalar: scalar, nCopy, mCopy);
|
||||
|
||||
if (n != default || m != default)
|
||||
|
@ -356,9 +356,11 @@ namespace ARMeilleure.Instructions
|
||||
? typeof(SoftFloat64_16).GetMethod(nameof(SoftFloat64_16.FPConvert))
|
||||
: typeof(SoftFloat32_16).GetMethod(nameof(SoftFloat32_16.FPConvert));
|
||||
|
||||
context.ExitArmFpMode();
|
||||
context.StoreToContext();
|
||||
Operand res = context.Call(method, src);
|
||||
context.LoadFromContext();
|
||||
context.EnterArmFpMode();
|
||||
|
||||
InsertScalar16(context, op.Vd, op.T, res);
|
||||
}
|
||||
@ -372,9 +374,11 @@ namespace ARMeilleure.Instructions
|
||||
? typeof(SoftFloat16_64).GetMethod(nameof(SoftFloat16_64.FPConvert))
|
||||
: typeof(SoftFloat16_32).GetMethod(nameof(SoftFloat16_32.FPConvert));
|
||||
|
||||
context.ExitArmFpMode();
|
||||
context.StoreToContext();
|
||||
Operand res = context.Call(method, src);
|
||||
context.LoadFromContext();
|
||||
context.EnterArmFpMode();
|
||||
|
||||
InsertScalar(context, op.Vd, res);
|
||||
}
|
||||
@ -542,10 +546,17 @@ namespace ARMeilleure.Instructions
|
||||
// VRINTX (floating-point).
|
||||
public static void Vrintx_S(ArmEmitterContext context)
|
||||
{
|
||||
EmitScalarUnaryOpF32(context, (op1) =>
|
||||
if (Optimizations.UseAdvSimd)
|
||||
{
|
||||
return EmitRoundByRMode(context, op1);
|
||||
});
|
||||
InstEmitSimdHelper32Arm64.EmitScalarUnaryOpF32(context, Intrinsic.Arm64FrintxS);
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitScalarUnaryOpF32(context, (op1) =>
|
||||
{
|
||||
return EmitRoundByRMode(context, op1);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static Operand EmitFPConvert(ArmEmitterContext context, Operand value, OperandType type, bool signed)
|
||||
|
@ -1,3 +1,4 @@
|
||||
using ARMeilleure.CodeGen.X86;
|
||||
using ARMeilleure.Decoders;
|
||||
using ARMeilleure.IntermediateRepresentation;
|
||||
using ARMeilleure.State;
|
||||
@ -158,6 +159,75 @@ namespace ARMeilleure.Instructions
|
||||
};
|
||||
#endregion
|
||||
|
||||
public static void EnterArmFpMode(EmitterContext context, Func<FPState, Operand> getFpFlag)
|
||||
{
|
||||
if (Optimizations.UseSse2)
|
||||
{
|
||||
Operand mxcsr = context.AddIntrinsicInt(Intrinsic.X86Stmxcsr);
|
||||
|
||||
Operand fzTrue = getFpFlag(FPState.FzFlag);
|
||||
Operand r0True = getFpFlag(FPState.RMode0Flag);
|
||||
Operand r1True = getFpFlag(FPState.RMode1Flag);
|
||||
|
||||
mxcsr = context.BitwiseAnd(mxcsr, Const(~(int)(Mxcsr.Ftz | Mxcsr.Daz | Mxcsr.Rhi | Mxcsr.Rlo)));
|
||||
|
||||
mxcsr = context.BitwiseOr(mxcsr, context.ConditionalSelect(fzTrue, Const((int)(Mxcsr.Ftz | Mxcsr.Daz | Mxcsr.Um | Mxcsr.Dm)), Const(0)));
|
||||
|
||||
// X86 round modes in order: nearest, negative, positive, zero
|
||||
// ARM round modes in order: nearest, positive, negative, zero
|
||||
// Read the bits backwards to correct this.
|
||||
|
||||
mxcsr = context.BitwiseOr(mxcsr, context.ConditionalSelect(r0True, Const((int)Mxcsr.Rhi), Const(0)));
|
||||
mxcsr = context.BitwiseOr(mxcsr, context.ConditionalSelect(r1True, Const((int)Mxcsr.Rlo), Const(0)));
|
||||
|
||||
context.AddIntrinsicNoRet(Intrinsic.X86Ldmxcsr, mxcsr);
|
||||
}
|
||||
else if (Optimizations.UseAdvSimd)
|
||||
{
|
||||
Operand fpcr = context.AddIntrinsicInt(Intrinsic.Arm64MrsFpcr);
|
||||
|
||||
Operand fzTrue = getFpFlag(FPState.FzFlag);
|
||||
Operand r0True = getFpFlag(FPState.RMode0Flag);
|
||||
Operand r1True = getFpFlag(FPState.RMode1Flag);
|
||||
|
||||
fpcr = context.BitwiseAnd(fpcr, Const(~(int)(FPCR.Fz | FPCR.RMode0 | FPCR.RMode1)));
|
||||
|
||||
fpcr = context.BitwiseOr(fpcr, context.ConditionalSelect(fzTrue, Const((int)FPCR.Fz), Const(0)));
|
||||
fpcr = context.BitwiseOr(fpcr, context.ConditionalSelect(r0True, Const((int)FPCR.RMode0), Const(0)));
|
||||
fpcr = context.BitwiseOr(fpcr, context.ConditionalSelect(r1True, Const((int)FPCR.RMode1), Const(0)));
|
||||
|
||||
context.AddIntrinsicNoRet(Intrinsic.Arm64MsrFpcr, fpcr);
|
||||
|
||||
// TODO: Restore FPSR
|
||||
}
|
||||
}
|
||||
|
||||
public static void ExitArmFpMode(EmitterContext context, Action<FPState, Operand> setFpFlag)
|
||||
{
|
||||
if (Optimizations.UseSse2)
|
||||
{
|
||||
Operand mxcsr = context.AddIntrinsicInt(Intrinsic.X86Stmxcsr);
|
||||
|
||||
// Unset round mode (to nearest) and ftz.
|
||||
mxcsr = context.BitwiseAnd(mxcsr, Const(~(int)(Mxcsr.Ftz | Mxcsr.Daz | Mxcsr.Rhi | Mxcsr.Rlo)));
|
||||
|
||||
context.AddIntrinsicNoRet(Intrinsic.X86Ldmxcsr, mxcsr);
|
||||
|
||||
// Status flags would be stored here if they were used.
|
||||
}
|
||||
else if (Optimizations.UseAdvSimd)
|
||||
{
|
||||
Operand fpcr = context.AddIntrinsicInt(Intrinsic.Arm64MrsFpcr);
|
||||
|
||||
// Unset round mode (to nearest) and fz.
|
||||
fpcr = context.BitwiseAnd(fpcr, Const(~(int)(FPCR.Fz | FPCR.RMode0 | FPCR.RMode1)));
|
||||
|
||||
context.AddIntrinsicNoRet(Intrinsic.Arm64MsrFpcr, fpcr);
|
||||
|
||||
// TODO: Store FPSR
|
||||
}
|
||||
}
|
||||
|
||||
public static int GetImmShl(OpCodeSimdShImm op)
|
||||
{
|
||||
return op.Imm - (8 << op.Size);
|
||||
@ -465,9 +535,11 @@ namespace ARMeilleure.Instructions
|
||||
? typeof(SoftFloat32).GetMethod(name)
|
||||
: typeof(SoftFloat64).GetMethod(name);
|
||||
|
||||
context.ExitArmFpMode();
|
||||
context.StoreToContext();
|
||||
Operand res = context.Call(info, callArgs);
|
||||
context.LoadFromContext();
|
||||
context.EnterArmFpMode();
|
||||
|
||||
return res;
|
||||
}
|
||||
@ -1358,39 +1430,6 @@ namespace ARMeilleure.Instructions
|
||||
}
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum Mxcsr
|
||||
{
|
||||
Ftz = 1 << 15, // Flush To Zero.
|
||||
Um = 1 << 11, // Underflow Mask.
|
||||
Dm = 1 << 8, // Denormal Mask.
|
||||
Daz = 1 << 6 // Denormals Are Zero.
|
||||
}
|
||||
|
||||
public static void EmitSseOrAvxEnterFtzAndDazModesOpF(ArmEmitterContext context, out Operand isTrue)
|
||||
{
|
||||
isTrue = GetFpFlag(FPState.FzFlag);
|
||||
|
||||
Operand lblTrue = Label();
|
||||
context.BranchIfFalse(lblTrue, isTrue);
|
||||
|
||||
context.AddIntrinsicNoRet(Intrinsic.X86Mxcsrmb, Const((int)(Mxcsr.Ftz | Mxcsr.Um | Mxcsr.Dm | Mxcsr.Daz)));
|
||||
|
||||
context.MarkLabel(lblTrue);
|
||||
}
|
||||
|
||||
public static void EmitSseOrAvxExitFtzAndDazModesOpF(ArmEmitterContext context, Operand isTrue = default)
|
||||
{
|
||||
isTrue = isTrue == default ? GetFpFlag(FPState.FzFlag) : isTrue;
|
||||
|
||||
Operand lblTrue = Label();
|
||||
context.BranchIfFalse(lblTrue, isTrue);
|
||||
|
||||
context.AddIntrinsicNoRet(Intrinsic.X86Mxcsrub, Const((int)(Mxcsr.Ftz | Mxcsr.Daz)));
|
||||
|
||||
context.MarkLabel(lblTrue);
|
||||
}
|
||||
|
||||
public enum CmpCondition
|
||||
{
|
||||
// Legacy Sse.
|
||||
|
@ -1197,9 +1197,11 @@ namespace ARMeilleure.Instructions
|
||||
Array.Resize(ref callArgs, callArgs.Length + 1);
|
||||
callArgs[callArgs.Length - 1] = Const(1);
|
||||
|
||||
context.ExitArmFpMode();
|
||||
context.StoreToContext();
|
||||
Operand res = context.Call(info, callArgs);
|
||||
context.LoadFromContext();
|
||||
context.EnterArmFpMode();
|
||||
|
||||
return res;
|
||||
}
|
||||
|
@ -254,7 +254,22 @@ namespace ARMeilleure.Instructions
|
||||
|
||||
public static void Not_V(ArmEmitterContext context)
|
||||
{
|
||||
if (Optimizations.UseSse2)
|
||||
if (Optimizations.UseAvx512Ortho)
|
||||
{
|
||||
OpCodeSimd op = (OpCodeSimd)context.CurrOp;
|
||||
|
||||
Operand n = GetVec(op.Rn);
|
||||
|
||||
Operand res = context.AddIntrinsic(Intrinsic.X86Vpternlogd, n, n, Const(~0b10101010));
|
||||
|
||||
if (op.RegisterSize == RegisterSize.Simd64)
|
||||
{
|
||||
res = context.VectorZeroUpper64(res);
|
||||
}
|
||||
|
||||
context.Copy(GetVec(op.Rd), res);
|
||||
}
|
||||
else if (Optimizations.UseSse2)
|
||||
{
|
||||
OpCodeSimd op = (OpCodeSimd)context.CurrOp;
|
||||
|
||||
@ -283,6 +298,22 @@ namespace ARMeilleure.Instructions
|
||||
{
|
||||
InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64OrnV);
|
||||
}
|
||||
else if (Optimizations.UseAvx512Ortho)
|
||||
{
|
||||
OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp;
|
||||
|
||||
Operand n = GetVec(op.Rn);
|
||||
Operand m = GetVec(op.Rm);
|
||||
|
||||
Operand res = context.AddIntrinsic(Intrinsic.X86Vpternlogd, n, m, Const(0b11001100 | ~0b10101010));
|
||||
|
||||
if (op.RegisterSize == RegisterSize.Simd64)
|
||||
{
|
||||
res = context.VectorZeroUpper64(res);
|
||||
}
|
||||
|
||||
context.Copy(GetVec(op.Rd), res);
|
||||
}
|
||||
else if (Optimizations.UseSse2)
|
||||
{
|
||||
OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp;
|
||||
|
@ -151,6 +151,13 @@ namespace ARMeilleure.Instructions
|
||||
{
|
||||
InstEmitSimdHelper32Arm64.EmitVectorBinaryOpSimd32(context, (n, m) => context.AddIntrinsic(Intrinsic.Arm64OrnV | Intrinsic.Arm64V128, n, m));
|
||||
}
|
||||
else if (Optimizations.UseAvx512Ortho)
|
||||
{
|
||||
EmitVectorBinaryOpSimd32(context, (n, m) =>
|
||||
{
|
||||
return context.AddIntrinsic(Intrinsic.X86Vpternlogd, n, m, Const(0b11001100 | ~0b10101010));
|
||||
});
|
||||
}
|
||||
else if (Optimizations.UseSse2)
|
||||
{
|
||||
Operand mask = context.VectorOne();
|
||||
|
@ -34,7 +34,14 @@ namespace ARMeilleure.Instructions
|
||||
|
||||
public static void Vmvn_I(ArmEmitterContext context)
|
||||
{
|
||||
if (Optimizations.UseSse2)
|
||||
if (Optimizations.UseAvx512Ortho)
|
||||
{
|
||||
EmitVectorUnaryOpSimd32(context, (op1) =>
|
||||
{
|
||||
return context.AddIntrinsic(Intrinsic.X86Vpternlogd, op1, op1, Const(0b01010101));
|
||||
});
|
||||
}
|
||||
else if (Optimizations.UseSse2)
|
||||
{
|
||||
EmitVectorUnaryOpSimd32(context, (op1) =>
|
||||
{
|
||||
|
@ -33,8 +33,8 @@ namespace ARMeilleure.Instructions
|
||||
case 0b11_011_0100_0010_000: EmitGetNzcv(context); return;
|
||||
case 0b11_011_0100_0100_000: EmitGetFpcr(context); return;
|
||||
case 0b11_011_0100_0100_001: EmitGetFpsr(context); return;
|
||||
case 0b11_011_1101_0000_010: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetTpidrEl0)); break;
|
||||
case 0b11_011_1101_0000_011: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetTpidrroEl0)); break;
|
||||
case 0b11_011_1101_0000_010: EmitGetTpidrEl0(context); return;
|
||||
case 0b11_011_1101_0000_011: EmitGetTpidrroEl0(context); return;
|
||||
case 0b11_011_1110_0000_000: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetCntfrqEl0)); break;
|
||||
case 0b11_011_1110_0000_001: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetCntpctEl0)); break;
|
||||
case 0b11_011_1110_0000_010: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetCntvctEl0)); break;
|
||||
@ -49,19 +49,15 @@ namespace ARMeilleure.Instructions
|
||||
{
|
||||
OpCodeSystem op = (OpCodeSystem)context.CurrOp;
|
||||
|
||||
MethodInfo info;
|
||||
|
||||
switch (GetPackedId(op))
|
||||
{
|
||||
case 0b11_011_0100_0010_000: EmitSetNzcv(context); return;
|
||||
case 0b11_011_0100_0100_000: EmitSetFpcr(context); return;
|
||||
case 0b11_011_0100_0100_001: EmitSetFpsr(context); return;
|
||||
case 0b11_011_1101_0000_010: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.SetTpidrEl0)); break;
|
||||
case 0b11_011_1101_0000_010: EmitSetTpidrEl0(context); return;
|
||||
|
||||
default: throw new NotImplementedException($"Unknown MSR 0x{op.RawOpCode:X8} at 0x{op.Address:X16}.");
|
||||
}
|
||||
|
||||
context.Call(info, GetIntOrZR(context, op.Rt));
|
||||
}
|
||||
|
||||
public static void Nop(ArmEmitterContext context)
|
||||
@ -165,6 +161,28 @@ namespace ARMeilleure.Instructions
|
||||
SetIntOrZR(context, op.Rt, fpsr);
|
||||
}
|
||||
|
||||
private static void EmitGetTpidrEl0(ArmEmitterContext context)
|
||||
{
|
||||
OpCodeSystem op = (OpCodeSystem)context.CurrOp;
|
||||
|
||||
Operand nativeContext = context.LoadArgument(OperandType.I64, 0);
|
||||
|
||||
Operand result = context.Load(OperandType.I64, context.Add(nativeContext, Const((ulong)NativeContext.GetTpidrEl0Offset())));
|
||||
|
||||
SetIntOrZR(context, op.Rt, result);
|
||||
}
|
||||
|
||||
private static void EmitGetTpidrroEl0(ArmEmitterContext context)
|
||||
{
|
||||
OpCodeSystem op = (OpCodeSystem)context.CurrOp;
|
||||
|
||||
Operand nativeContext = context.LoadArgument(OperandType.I64, 0);
|
||||
|
||||
Operand result = context.Load(OperandType.I64, context.Add(nativeContext, Const((ulong)NativeContext.GetTpidrroEl0Offset())));
|
||||
|
||||
SetIntOrZR(context, op.Rt, result);
|
||||
}
|
||||
|
||||
private static void EmitSetNzcv(ArmEmitterContext context)
|
||||
{
|
||||
OpCodeSystem op = (OpCodeSystem)context.CurrOp;
|
||||
@ -192,6 +210,8 @@ namespace ARMeilleure.Instructions
|
||||
SetFpFlag(context, (FPState)flag, context.BitwiseAnd(context.ShiftRightUI(fpcr, Const(flag)), Const(1)));
|
||||
}
|
||||
}
|
||||
|
||||
context.UpdateArmFpMode();
|
||||
}
|
||||
|
||||
private static void EmitSetFpsr(ArmEmitterContext context)
|
||||
@ -210,6 +230,19 @@ namespace ARMeilleure.Instructions
|
||||
SetFpFlag(context, (FPState)flag, context.BitwiseAnd(context.ShiftRightUI(fpsr, Const(flag)), Const(1)));
|
||||
}
|
||||
}
|
||||
|
||||
context.UpdateArmFpMode();
|
||||
}
|
||||
|
||||
private static void EmitSetTpidrEl0(ArmEmitterContext context)
|
||||
{
|
||||
OpCodeSystem op = (OpCodeSystem)context.CurrOp;
|
||||
|
||||
Operand value = GetIntOrZR(context, op.Rt);
|
||||
|
||||
Operand nativeContext = context.LoadArgument(OperandType.I64, 0);
|
||||
|
||||
context.Store(context.Add(nativeContext, Const((ulong)NativeContext.GetTpidrEl0Offset())), value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -23,8 +23,6 @@ namespace ARMeilleure.Instructions
|
||||
return;
|
||||
}
|
||||
|
||||
MethodInfo info;
|
||||
|
||||
switch (op.CRn)
|
||||
{
|
||||
case 13: // Process and Thread Info.
|
||||
@ -36,14 +34,12 @@ namespace ARMeilleure.Instructions
|
||||
switch (op.Opc2)
|
||||
{
|
||||
case 2:
|
||||
info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.SetTpidrEl032)); break;
|
||||
EmitSetTpidrEl0(context); return;
|
||||
|
||||
default:
|
||||
throw new NotImplementedException($"Unknown MRC Opc2 0x{op.Opc2:X} at 0x{op.Address:X} (0x{op.RawOpCode:X}).");
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 7:
|
||||
switch (op.CRm) // Cache and Memory barrier.
|
||||
{
|
||||
@ -64,8 +60,6 @@ namespace ARMeilleure.Instructions
|
||||
default:
|
||||
throw new NotImplementedException($"Unknown MRC 0x{op.RawOpCode:X8} at 0x{op.Address:X16}.");
|
||||
}
|
||||
|
||||
context.Call(info, GetIntA32(context, op.Rt));
|
||||
}
|
||||
|
||||
public static void Mrc(ArmEmitterContext context)
|
||||
@ -79,7 +73,7 @@ namespace ARMeilleure.Instructions
|
||||
return;
|
||||
}
|
||||
|
||||
MethodInfo info;
|
||||
Operand result;
|
||||
|
||||
switch (op.CRn)
|
||||
{
|
||||
@ -92,10 +86,10 @@ namespace ARMeilleure.Instructions
|
||||
switch (op.Opc2)
|
||||
{
|
||||
case 2:
|
||||
info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetTpidrEl032)); break;
|
||||
result = EmitGetTpidrEl0(context); break;
|
||||
|
||||
case 3:
|
||||
info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetTpidr32)); break;
|
||||
result = EmitGetTpidrroEl0(context); break;
|
||||
|
||||
default:
|
||||
throw new NotImplementedException($"Unknown MRC Opc2 0x{op.Opc2:X} at 0x{op.Address:X} (0x{op.RawOpCode:X}).");
|
||||
@ -110,13 +104,13 @@ namespace ARMeilleure.Instructions
|
||||
if (op.Rt == RegisterAlias.Aarch32Pc)
|
||||
{
|
||||
// Special behavior: copy NZCV flags into APSR.
|
||||
EmitSetNzcv(context, context.Call(info));
|
||||
EmitSetNzcv(context, result);
|
||||
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
SetIntA32(context, op.Rt, context.Call(info));
|
||||
SetIntA32(context, op.Rt, result);
|
||||
}
|
||||
}
|
||||
|
||||
@ -321,6 +315,37 @@ namespace ARMeilleure.Instructions
|
||||
SetFpFlag(context, (FPState)flag, context.BitwiseAnd(context.ShiftRightUI(fpscr, Const(flag)), Const(1)));
|
||||
}
|
||||
}
|
||||
|
||||
context.UpdateArmFpMode();
|
||||
}
|
||||
|
||||
private static Operand EmitGetTpidrEl0(ArmEmitterContext context)
|
||||
{
|
||||
OpCode32System op = (OpCode32System)context.CurrOp;
|
||||
|
||||
Operand nativeContext = context.LoadArgument(OperandType.I64, 0);
|
||||
|
||||
return context.Load(OperandType.I64, context.Add(nativeContext, Const((ulong)NativeContext.GetTpidrEl0Offset())));
|
||||
}
|
||||
|
||||
private static Operand EmitGetTpidrroEl0(ArmEmitterContext context)
|
||||
{
|
||||
OpCode32System op = (OpCode32System)context.CurrOp;
|
||||
|
||||
Operand nativeContext = context.LoadArgument(OperandType.I64, 0);
|
||||
|
||||
return context.Load(OperandType.I64, context.Add(nativeContext, Const((ulong)NativeContext.GetTpidrroEl0Offset())));
|
||||
}
|
||||
|
||||
private static void EmitSetTpidrEl0(ArmEmitterContext context)
|
||||
{
|
||||
OpCode32System op = (OpCode32System)context.CurrOp;
|
||||
|
||||
Operand value = GetIntA32(context, op.Rt);
|
||||
|
||||
Operand nativeContext = context.LoadArgument(OperandType.I64, 0);
|
||||
|
||||
context.Store(context.Add(nativeContext, Const((ulong)NativeContext.GetTpidrEl0Offset())), context.ZeroExtend32(OperandType.I64, value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -72,26 +72,6 @@ namespace ARMeilleure.Instructions
|
||||
return (ulong)GetContext().DczidEl0;
|
||||
}
|
||||
|
||||
public static ulong GetTpidrEl0()
|
||||
{
|
||||
return (ulong)GetContext().TpidrEl0;
|
||||
}
|
||||
|
||||
public static uint GetTpidrEl032()
|
||||
{
|
||||
return (uint)GetContext().TpidrEl0;
|
||||
}
|
||||
|
||||
public static ulong GetTpidrroEl0()
|
||||
{
|
||||
return (ulong)GetContext().TpidrroEl0;
|
||||
}
|
||||
|
||||
public static uint GetTpidr32()
|
||||
{
|
||||
return (uint)GetContext().TpidrroEl0;
|
||||
}
|
||||
|
||||
public static ulong GetCntfrqEl0()
|
||||
{
|
||||
return GetContext().CntfrqEl0;
|
||||
@ -106,16 +86,6 @@ namespace ARMeilleure.Instructions
|
||||
{
|
||||
return GetContext().CntvctEl0;
|
||||
}
|
||||
|
||||
public static void SetTpidrEl0(ulong value)
|
||||
{
|
||||
GetContext().TpidrEl0 = (long)value;
|
||||
}
|
||||
|
||||
public static void SetTpidrEl032(uint value)
|
||||
{
|
||||
GetContext().TpidrEl0 = (long)value;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region "Read"
|
||||
@ -191,7 +161,7 @@ namespace ARMeilleure.Instructions
|
||||
{
|
||||
TranslatedFunction function = Context.Translator.GetOrTranslate(address, GetContext().ExecutionMode);
|
||||
|
||||
return (ulong)function.FuncPtr.ToInt64();
|
||||
return (ulong)function.FuncPointer.ToInt64();
|
||||
}
|
||||
|
||||
public static void InvalidateCacheLine(ulong address)
|
||||
|
@ -53,6 +53,7 @@ namespace ARMeilleure.IntermediateRepresentation
|
||||
X86Haddpd,
|
||||
X86Haddps,
|
||||
X86Insertps,
|
||||
X86Ldmxcsr,
|
||||
X86Maxpd,
|
||||
X86Maxps,
|
||||
X86Maxsd,
|
||||
@ -68,8 +69,6 @@ namespace ARMeilleure.IntermediateRepresentation
|
||||
X86Mulps,
|
||||
X86Mulsd,
|
||||
X86Mulss,
|
||||
X86Mxcsrmb,
|
||||
X86Mxcsrub,
|
||||
X86Paddb,
|
||||
X86Paddd,
|
||||
X86Paddq,
|
||||
@ -153,6 +152,7 @@ namespace ARMeilleure.IntermediateRepresentation
|
||||
X86Sqrtps,
|
||||
X86Sqrtsd,
|
||||
X86Sqrtss,
|
||||
X86Stmxcsr,
|
||||
X86Subpd,
|
||||
X86Subps,
|
||||
X86Subsd,
|
||||
@ -163,16 +163,19 @@ namespace ARMeilleure.IntermediateRepresentation
|
||||
X86Unpcklps,
|
||||
X86Vcvtph2ps,
|
||||
X86Vcvtps2ph,
|
||||
X86Vfmadd231pd,
|
||||
X86Vfmadd231ps,
|
||||
X86Vfmadd231sd,
|
||||
X86Vfmadd231ss,
|
||||
X86Vfmsub231sd,
|
||||
X86Vfmsub231ss,
|
||||
X86Vfnmadd231pd,
|
||||
X86Vfnmadd231ps,
|
||||
X86Vfnmadd231sd,
|
||||
X86Vfnmadd231ss,
|
||||
X86Vfnmsub231sd,
|
||||
X86Vfnmsub231ss,
|
||||
X86Vpternlogd,
|
||||
X86Xorpd,
|
||||
X86Xorps,
|
||||
|
||||
@ -393,6 +396,8 @@ namespace ARMeilleure.IntermediateRepresentation
|
||||
Arm64MlsVe,
|
||||
Arm64MlsV,
|
||||
Arm64MoviV,
|
||||
Arm64MrsFpcr,
|
||||
Arm64MsrFpcr,
|
||||
Arm64MrsFpsr,
|
||||
Arm64MsrFpsr,
|
||||
Arm64MulVe,
|
||||
|
@ -71,6 +71,7 @@ namespace ARMeilleure.Memory
|
||||
/// <param name="size">Size of the region</param>
|
||||
/// <param name="write">True if the region was written, false if read</param>
|
||||
/// <param name="precise">True if the access is precise, false otherwise</param>
|
||||
void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false);
|
||||
/// <param name="exemptId">Optional ID of the handles that should not be signalled</param>
|
||||
void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false, int? exemptId = null);
|
||||
}
|
||||
}
|
@ -23,6 +23,10 @@ namespace ARMeilleure
|
||||
public static bool UseSse42IfAvailable { get; set; } = true;
|
||||
public static bool UsePopCntIfAvailable { get; set; } = true;
|
||||
public static bool UseAvxIfAvailable { get; set; } = true;
|
||||
public static bool UseAvx512FIfAvailable { get; set; } = true;
|
||||
public static bool UseAvx512VlIfAvailable { get; set; } = true;
|
||||
public static bool UseAvx512BwIfAvailable { get; set; } = true;
|
||||
public static bool UseAvx512DqIfAvailable { get; set; } = true;
|
||||
public static bool UseF16cIfAvailable { get; set; } = true;
|
||||
public static bool UseFmaIfAvailable { get; set; } = true;
|
||||
public static bool UseAesniIfAvailable { get; set; } = true;
|
||||
@ -47,11 +51,18 @@ namespace ARMeilleure
|
||||
internal static bool UseSse42 => UseSse42IfAvailable && X86HardwareCapabilities.SupportsSse42;
|
||||
internal static bool UsePopCnt => UsePopCntIfAvailable && X86HardwareCapabilities.SupportsPopcnt;
|
||||
internal static bool UseAvx => UseAvxIfAvailable && X86HardwareCapabilities.SupportsAvx && !ForceLegacySse;
|
||||
internal static bool UseAvx512F => UseAvx512FIfAvailable && X86HardwareCapabilities.SupportsAvx512F && !ForceLegacySse;
|
||||
internal static bool UseAvx512Vl => UseAvx512VlIfAvailable && X86HardwareCapabilities.SupportsAvx512Vl && !ForceLegacySse;
|
||||
internal static bool UseAvx512Bw => UseAvx512BwIfAvailable && X86HardwareCapabilities.SupportsAvx512Bw && !ForceLegacySse;
|
||||
internal static bool UseAvx512Dq => UseAvx512DqIfAvailable && X86HardwareCapabilities.SupportsAvx512Dq && !ForceLegacySse;
|
||||
internal static bool UseF16c => UseF16cIfAvailable && X86HardwareCapabilities.SupportsF16c;
|
||||
internal static bool UseFma => UseFmaIfAvailable && X86HardwareCapabilities.SupportsFma;
|
||||
internal static bool UseAesni => UseAesniIfAvailable && X86HardwareCapabilities.SupportsAesni;
|
||||
internal static bool UsePclmulqdq => UsePclmulqdqIfAvailable && X86HardwareCapabilities.SupportsPclmulqdq;
|
||||
internal static bool UseSha => UseShaIfAvailable && X86HardwareCapabilities.SupportsSha;
|
||||
internal static bool UseGfni => UseGfniIfAvailable && X86HardwareCapabilities.SupportsGfni;
|
||||
|
||||
internal static bool UseAvx512Ortho => UseAvx512F && UseAvx512Vl;
|
||||
internal static bool UseAvx512OrthoFloat => UseAvx512Ortho && UseAvx512Dq;
|
||||
}
|
||||
}
|
||||
|
@ -222,7 +222,7 @@ namespace ARMeilleure.Signal
|
||||
|
||||
// Tracking action should be non-null to call it, otherwise assume false return.
|
||||
context.BranchIfFalse(skipActionLabel, trackingActionPtr);
|
||||
Operand result = context.Call(trackingActionPtr, OperandType.I32, offset, Const(_pageSize), isWrite, Const(0));
|
||||
Operand result = context.Call(trackingActionPtr, OperandType.I32, offset, Const(_pageSize), isWrite);
|
||||
context.Copy(inRegionLocal, result);
|
||||
|
||||
context.MarkLabel(skipActionLabel);
|
||||
|
@ -27,8 +27,17 @@ namespace ARMeilleure.State
|
||||
// Since EL2 isn't implemented, CNTVOFF_EL2 = 0
|
||||
public ulong CntvctEl0 => CntpctEl0;
|
||||
|
||||
public long TpidrEl0 { get; set; }
|
||||
public long TpidrroEl0 { get; set; }
|
||||
public long TpidrEl0
|
||||
{
|
||||
get => _nativeContext.GetTpidrEl0();
|
||||
set => _nativeContext.SetTpidrEl0(value);
|
||||
}
|
||||
|
||||
public long TpidrroEl0
|
||||
{
|
||||
get => _nativeContext.GetTpidrroEl0();
|
||||
set => _nativeContext.SetTpidrroEl0(value);
|
||||
}
|
||||
|
||||
public uint Pstate
|
||||
{
|
||||
|
@ -13,6 +13,8 @@ namespace ARMeilleure.State
|
||||
public fixed ulong V[RegisterConsts.VecRegsCount * 2];
|
||||
public fixed uint Flags[RegisterConsts.FlagsCount];
|
||||
public fixed uint FpFlags[RegisterConsts.FpFlagsCount];
|
||||
public long TpidrEl0;
|
||||
public long TpidrroEl0;
|
||||
public int Counter;
|
||||
public ulong DispatchAddress;
|
||||
public ulong ExclusiveAddress;
|
||||
@ -168,6 +170,12 @@ namespace ARMeilleure.State
|
||||
}
|
||||
}
|
||||
|
||||
public long GetTpidrEl0() => GetStorage().TpidrEl0;
|
||||
public void SetTpidrEl0(long value) => GetStorage().TpidrEl0 = value;
|
||||
|
||||
public long GetTpidrroEl0() => GetStorage().TpidrroEl0;
|
||||
public void SetTpidrroEl0(long value) => GetStorage().TpidrroEl0 = value;
|
||||
|
||||
public int GetCounter() => GetStorage().Counter;
|
||||
public void SetCounter(int value) => GetStorage().Counter = value;
|
||||
|
||||
@ -214,6 +222,16 @@ namespace ARMeilleure.State
|
||||
}
|
||||
}
|
||||
|
||||
public static int GetTpidrEl0Offset()
|
||||
{
|
||||
return StorageOffset(ref _dummyStorage, ref _dummyStorage.TpidrEl0);
|
||||
}
|
||||
|
||||
public static int GetTpidrroEl0Offset()
|
||||
{
|
||||
return StorageOffset(ref _dummyStorage, ref _dummyStorage.TpidrroEl0);
|
||||
}
|
||||
|
||||
public static int GetCounterOffset()
|
||||
{
|
||||
return StorageOffset(ref _dummyStorage, ref _dummyStorage.Counter);
|
||||
|
@ -188,6 +188,21 @@ namespace ARMeilleure.Translation
|
||||
}
|
||||
}
|
||||
|
||||
public void EnterArmFpMode()
|
||||
{
|
||||
InstEmitSimdHelper.EnterArmFpMode(this, InstEmitHelper.GetFpFlag);
|
||||
}
|
||||
|
||||
public void UpdateArmFpMode()
|
||||
{
|
||||
EnterArmFpMode();
|
||||
}
|
||||
|
||||
public void ExitArmFpMode()
|
||||
{
|
||||
InstEmitSimdHelper.ExitArmFpMode(this, (flag, value) => InstEmitHelper.SetFpFlag(this, flag, value));
|
||||
}
|
||||
|
||||
public Operand TryGetComparisonResult(Condition condition)
|
||||
{
|
||||
if (_optOpLastCompare == null || _optOpLastCompare != _optOpLastFlagSet)
|
||||
|
@ -105,17 +105,11 @@ namespace ARMeilleure.Translation
|
||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetDczidEl0)));
|
||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetFunctionAddress)));
|
||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.InvalidateCacheLine)));
|
||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetTpidrroEl0)));
|
||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetTpidr32))); // A32 only.
|
||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetTpidrEl0)));
|
||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetTpidrEl032))); // A32 only.
|
||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadByte)));
|
||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadUInt16)));
|
||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadUInt32)));
|
||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadUInt64)));
|
||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadVector128)));
|
||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.SetTpidrEl0)));
|
||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.SetTpidrEl032))); // A32 only.
|
||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.SignalMemoryTracking)));
|
||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.SupervisorCall)));
|
||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.ThrowInvalidMemoryAccess)));
|
||||
|
@ -3,4 +3,5 @@
|
||||
namespace ARMeilleure.Translation
|
||||
{
|
||||
delegate void DispatcherFunction(IntPtr nativeContext, ulong startAddress);
|
||||
delegate ulong WrapperFunction(IntPtr nativeContext, ulong startAddress);
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ using ARMeilleure.Memory;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common.Memory;
|
||||
using System;
|
||||
using System.Buffers.Binary;
|
||||
using System.Collections.Generic;
|
||||
@ -29,7 +30,7 @@ namespace ARMeilleure.Translation.PTC
|
||||
private const string OuterHeaderMagicString = "PTCohd\0\0";
|
||||
private const string InnerHeaderMagicString = "PTCihd\0\0";
|
||||
|
||||
private const uint InternalVersion = 4328; //! To be incremented manually for each change to the ARMeilleure project.
|
||||
private const uint InternalVersion = 4661; //! To be incremented manually for each change to the ARMeilleure project.
|
||||
|
||||
private const string ActualDir = "0";
|
||||
private const string BackupDir = "1";
|
||||
@ -150,10 +151,10 @@ namespace ARMeilleure.Translation.PTC
|
||||
|
||||
private void InitializeCarriers()
|
||||
{
|
||||
_infosStream = new MemoryStream();
|
||||
_infosStream = MemoryStreamManager.Shared.GetStream();
|
||||
_codesList = new List<byte[]>();
|
||||
_relocsStream = new MemoryStream();
|
||||
_unwindInfosStream = new MemoryStream();
|
||||
_relocsStream = MemoryStreamManager.Shared.GetStream();
|
||||
_unwindInfosStream = MemoryStreamManager.Shared.GetStream();
|
||||
}
|
||||
|
||||
private void DisposeCarriers()
|
||||
@ -745,9 +746,9 @@ namespace ARMeilleure.Translation.PTC
|
||||
bool highCq)
|
||||
{
|
||||
var cFunc = new CompiledFunction(code, unwindInfo, RelocInfo.Empty);
|
||||
var gFunc = cFunc.Map<GuestFunction>();
|
||||
var gFunc = cFunc.MapWithPointer<GuestFunction>(out IntPtr gFuncPointer);
|
||||
|
||||
return new TranslatedFunction(gFunc, callCounter, guestSize, highCq);
|
||||
return new TranslatedFunction(gFunc, gFuncPointer, callCounter, guestSize, highCq);
|
||||
}
|
||||
|
||||
private void UpdateInfo(InfoEntry infoEntry)
|
||||
@ -968,6 +969,7 @@ namespace ARMeilleure.Translation.PTC
|
||||
(ulong)Arm64HardwareCapabilities.LinuxFeatureInfoHwCap,
|
||||
(ulong)Arm64HardwareCapabilities.LinuxFeatureInfoHwCap2,
|
||||
(ulong)Arm64HardwareCapabilities.MacOsFeatureInfo,
|
||||
0,
|
||||
0);
|
||||
}
|
||||
else if (RuntimeInformation.ProcessArchitecture == Architecture.X64)
|
||||
@ -976,11 +978,12 @@ namespace ARMeilleure.Translation.PTC
|
||||
(ulong)X86HardwareCapabilities.FeatureInfo1Ecx,
|
||||
(ulong)X86HardwareCapabilities.FeatureInfo1Edx,
|
||||
(ulong)X86HardwareCapabilities.FeatureInfo7Ebx,
|
||||
(ulong)X86HardwareCapabilities.FeatureInfo7Ecx);
|
||||
(ulong)X86HardwareCapabilities.FeatureInfo7Ecx,
|
||||
(ulong)X86HardwareCapabilities.Xcr0InfoEax);
|
||||
}
|
||||
else
|
||||
{
|
||||
return new FeatureInfo(0, 0, 0, 0);
|
||||
return new FeatureInfo(0, 0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1001,7 +1004,7 @@ namespace ARMeilleure.Translation.PTC
|
||||
return osPlatform;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 78*/)]
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 86*/)]
|
||||
private struct OuterHeader
|
||||
{
|
||||
public ulong Magic;
|
||||
@ -1033,8 +1036,8 @@ namespace ARMeilleure.Translation.PTC
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 32*/)]
|
||||
private record struct FeatureInfo(ulong FeatureInfo0, ulong FeatureInfo1, ulong FeatureInfo2, ulong FeatureInfo3);
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 40*/)]
|
||||
private record struct FeatureInfo(ulong FeatureInfo0, ulong FeatureInfo1, ulong FeatureInfo2, ulong FeatureInfo3, ulong FeatureInfo4);
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 128*/)]
|
||||
private struct InnerHeader
|
||||
|
@ -1,6 +1,7 @@
|
||||
using ARMeilleure.State;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common.Memory;
|
||||
using System;
|
||||
using System.Buffers.Binary;
|
||||
using System.Collections.Concurrent;
|
||||
@ -182,7 +183,7 @@ namespace ARMeilleure.Translation.PTC
|
||||
return false;
|
||||
}
|
||||
|
||||
using (MemoryStream stream = new MemoryStream())
|
||||
using (MemoryStream stream = MemoryStreamManager.Shared.GetStream())
|
||||
{
|
||||
Debug.Assert(stream.Seek(0L, SeekOrigin.Begin) == 0L && stream.Length == 0L);
|
||||
|
||||
@ -274,7 +275,7 @@ namespace ARMeilleure.Translation.PTC
|
||||
|
||||
outerHeader.SetHeaderHash();
|
||||
|
||||
using (MemoryStream stream = new MemoryStream())
|
||||
using (MemoryStream stream = MemoryStreamManager.Shared.GetStream())
|
||||
{
|
||||
Debug.Assert(stream.Seek(0L, SeekOrigin.Begin) == 0L && stream.Length == 0L);
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
using ARMeilleure.Common;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace ARMeilleure.Translation
|
||||
{
|
||||
@ -8,23 +7,28 @@ namespace ARMeilleure.Translation
|
||||
{
|
||||
private readonly GuestFunction _func; // Ensure that this delegate will not be garbage collected.
|
||||
|
||||
public IntPtr FuncPointer { get; }
|
||||
public Counter<uint> CallCounter { get; }
|
||||
public ulong GuestSize { get; }
|
||||
public bool HighCq { get; }
|
||||
public IntPtr FuncPtr { get; }
|
||||
|
||||
public TranslatedFunction(GuestFunction func, Counter<uint> callCounter, ulong guestSize, bool highCq)
|
||||
public TranslatedFunction(GuestFunction func, IntPtr funcPointer, Counter<uint> callCounter, ulong guestSize, bool highCq)
|
||||
{
|
||||
_func = func;
|
||||
FuncPointer = funcPointer;
|
||||
CallCounter = callCounter;
|
||||
GuestSize = guestSize;
|
||||
HighCq = highCq;
|
||||
FuncPtr = Marshal.GetFunctionPointerForDelegate(func);
|
||||
}
|
||||
|
||||
public ulong Execute(State.ExecutionContext context)
|
||||
{
|
||||
return _func(context.NativeContextPtr);
|
||||
}
|
||||
|
||||
public ulong Execute(WrapperFunction dispatcher, State.ExecutionContext context)
|
||||
{
|
||||
return dispatcher(context.NativeContextPtr, (ulong)FuncPointer);
|
||||
}
|
||||
}
|
||||
}
|
@ -183,7 +183,7 @@ namespace ARMeilleure.Translation
|
||||
|
||||
Statistics.StartTimer();
|
||||
|
||||
ulong nextAddr = func.Execute(context);
|
||||
ulong nextAddr = func.Execute(Stubs.ContextWrapper, context);
|
||||
|
||||
Statistics.StopTimer(address);
|
||||
|
||||
@ -194,7 +194,7 @@ namespace ARMeilleure.Translation
|
||||
{
|
||||
TranslatedFunction func = Translate(address, context.ExecutionMode, highCq: false, singleStep: true);
|
||||
|
||||
address = func.Execute(context);
|
||||
address = func.Execute(Stubs.ContextWrapper, context);
|
||||
|
||||
EnqueueForDeletion(address, func);
|
||||
|
||||
@ -211,7 +211,7 @@ namespace ARMeilleure.Translation
|
||||
|
||||
if (oldFunc != func)
|
||||
{
|
||||
JitCache.Unmap(func.FuncPtr);
|
||||
JitCache.Unmap(func.FuncPointer);
|
||||
func = oldFunc;
|
||||
}
|
||||
|
||||
@ -230,7 +230,7 @@ namespace ARMeilleure.Translation
|
||||
{
|
||||
if (FunctionTable.IsValid(guestAddress) && (Optimizations.AllowLcqInFunctionTable || func.HighCq))
|
||||
{
|
||||
Volatile.Write(ref FunctionTable.GetValue(guestAddress), (ulong)func.FuncPtr);
|
||||
Volatile.Write(ref FunctionTable.GetValue(guestAddress), (ulong)func.FuncPointer);
|
||||
}
|
||||
}
|
||||
|
||||
@ -292,11 +292,11 @@ namespace ARMeilleure.Translation
|
||||
_ptc.WriteCompiledFunction(address, funcSize, hash, highCq, compiledFunc);
|
||||
}
|
||||
|
||||
GuestFunction func = compiledFunc.Map<GuestFunction>();
|
||||
GuestFunction func = compiledFunc.MapWithPointer<GuestFunction>(out IntPtr funcPointer);
|
||||
|
||||
Allocators.ResetAll();
|
||||
|
||||
return new TranslatedFunction(func, counter, funcSize, highCq);
|
||||
return new TranslatedFunction(func, funcPointer, counter, funcSize, highCq);
|
||||
}
|
||||
|
||||
private void BackgroundTranslate()
|
||||
@ -537,7 +537,7 @@ namespace ARMeilleure.Translation
|
||||
|
||||
foreach (var func in functions)
|
||||
{
|
||||
JitCache.Unmap(func.FuncPtr);
|
||||
JitCache.Unmap(func.FuncPointer);
|
||||
|
||||
func.CallCounter?.Dispose();
|
||||
}
|
||||
@ -546,7 +546,7 @@ namespace ARMeilleure.Translation
|
||||
|
||||
while (_oldFuncs.TryDequeue(out var kv))
|
||||
{
|
||||
JitCache.Unmap(kv.Value.FuncPtr);
|
||||
JitCache.Unmap(kv.Value.FuncPointer);
|
||||
|
||||
kv.Value.CallCounter?.Dispose();
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ namespace ARMeilleure.Translation
|
||||
private readonly Translator _translator;
|
||||
private readonly Lazy<IntPtr> _dispatchStub;
|
||||
private readonly Lazy<DispatcherFunction> _dispatchLoop;
|
||||
private readonly Lazy<WrapperFunction> _contextWrapper;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the dispatch stub.
|
||||
@ -64,6 +65,20 @@ namespace ARMeilleure.Translation
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the context wrapper function.
|
||||
/// </summary>
|
||||
/// <exception cref="ObjectDisposedException"><see cref="TranslatorStubs"/> instance was disposed</exception>
|
||||
public WrapperFunction ContextWrapper
|
||||
{
|
||||
get
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
return _contextWrapper.Value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TranslatorStubs"/> class with the specified
|
||||
/// <see cref="Translator"/> instance.
|
||||
@ -77,6 +92,7 @@ namespace ARMeilleure.Translation
|
||||
_translator = translator;
|
||||
_dispatchStub = new(GenerateDispatchStub, isThreadSafe: true);
|
||||
_dispatchLoop = new(GenerateDispatchLoop, isThreadSafe: true);
|
||||
_contextWrapper = new(GenerateContextWrapper, isThreadSafe: true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -202,6 +218,32 @@ namespace ARMeilleure.Translation
|
||||
return Marshal.GetFunctionPointerForDelegate(func);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Emits code that syncs FP state before executing guest code, or returns it to normal.
|
||||
/// </summary>
|
||||
/// <param name="context">Emitter context for the method</param>
|
||||
/// <param name="nativeContext">Pointer to the native context</param>
|
||||
/// <param name="enter">True if entering guest code, false otherwise</param>
|
||||
private void EmitSyncFpContext(EmitterContext context, Operand nativeContext, bool enter)
|
||||
{
|
||||
if (enter)
|
||||
{
|
||||
InstEmitSimdHelper.EnterArmFpMode(context, (flag) =>
|
||||
{
|
||||
Operand flagAddress = context.Add(nativeContext, Const((ulong)NativeContext.GetRegisterOffset(new Register((int)flag, RegisterType.FpFlag))));
|
||||
return context.Load(OperandType.I32, flagAddress);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
InstEmitSimdHelper.ExitArmFpMode(context, (flag, value) =>
|
||||
{
|
||||
Operand flagAddress = context.Add(nativeContext, Const((ulong)NativeContext.GetRegisterOffset(new Register((int)flag, RegisterType.FpFlag))));
|
||||
context.Store(flagAddress, value);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a <see cref="DispatchLoop"/> function.
|
||||
/// </summary>
|
||||
@ -221,6 +263,8 @@ namespace ARMeilleure.Translation
|
||||
Operand runningAddress = context.Add(nativeContext, Const((ulong)NativeContext.GetRunningOffset()));
|
||||
Operand dispatchAddress = context.Add(nativeContext, Const((ulong)NativeContext.GetDispatchAddressOffset()));
|
||||
|
||||
EmitSyncFpContext(context, nativeContext, true);
|
||||
|
||||
context.MarkLabel(beginLbl);
|
||||
context.Store(dispatchAddress, guestAddress);
|
||||
context.Copy(guestAddress, context.Call(Const((ulong)DispatchStub), OperandType.I64, nativeContext));
|
||||
@ -229,6 +273,9 @@ namespace ARMeilleure.Translation
|
||||
context.Branch(beginLbl);
|
||||
|
||||
context.MarkLabel(endLbl);
|
||||
|
||||
EmitSyncFpContext(context, nativeContext, false);
|
||||
|
||||
context.Return();
|
||||
|
||||
var cfg = context.GetControlFlowGraph();
|
||||
@ -237,5 +284,29 @@ namespace ARMeilleure.Translation
|
||||
|
||||
return Compiler.Compile(cfg, argTypes, retType, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<DispatcherFunction>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a <see cref="ContextWrapper"/> function.
|
||||
/// </summary>
|
||||
/// <returns><see cref="ContextWrapper"/> function</returns>
|
||||
private WrapperFunction GenerateContextWrapper()
|
||||
{
|
||||
var context = new EmitterContext();
|
||||
|
||||
Operand nativeContext = context.LoadArgument(OperandType.I64, 0);
|
||||
Operand guestMethod = context.LoadArgument(OperandType.I64, 1);
|
||||
|
||||
EmitSyncFpContext(context, nativeContext, true);
|
||||
Operand returnValue = context.Call(guestMethod, OperandType.I64, nativeContext);
|
||||
EmitSyncFpContext(context, nativeContext, false);
|
||||
|
||||
context.Return(returnValue);
|
||||
|
||||
var cfg = context.GetControlFlowGraph();
|
||||
var retType = OperandType.I64;
|
||||
var argTypes = new[] { OperandType.I64, OperandType.I64 };
|
||||
|
||||
return Compiler.Compile(cfg, argTypes, retType, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<WrapperFunction>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
148
ARMeilleure/Translation/TranslatorTestMethods.cs
Normal file
148
ARMeilleure/Translation/TranslatorTestMethods.cs
Normal file
@ -0,0 +1,148 @@
|
||||
using ARMeilleure.CodeGen.X86;
|
||||
using ARMeilleure.IntermediateRepresentation;
|
||||
using ARMeilleure.State;
|
||||
using ARMeilleure.Translation;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using static ARMeilleure.IntermediateRepresentation.Operand.Factory;
|
||||
|
||||
namespace ARMeilleure.Translation
|
||||
{
|
||||
public static class TranslatorTestMethods
|
||||
{
|
||||
public delegate int FpFlagsPInvokeTest(IntPtr managedMethod);
|
||||
|
||||
private static bool SetPlatformFtz(EmitterContext context, bool ftz)
|
||||
{
|
||||
if (Optimizations.UseSse2)
|
||||
{
|
||||
Operand mxcsr = context.AddIntrinsicInt(Intrinsic.X86Stmxcsr);
|
||||
|
||||
if (ftz)
|
||||
{
|
||||
mxcsr = context.BitwiseOr(mxcsr, Const((int)(Mxcsr.Ftz | Mxcsr.Um | Mxcsr.Dm)));
|
||||
}
|
||||
else
|
||||
{
|
||||
mxcsr = context.BitwiseAnd(mxcsr, Const(~(int)Mxcsr.Ftz));
|
||||
}
|
||||
|
||||
context.AddIntrinsicNoRet(Intrinsic.X86Ldmxcsr, mxcsr);
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (Optimizations.UseAdvSimd)
|
||||
{
|
||||
Operand fpcr = context.AddIntrinsicInt(Intrinsic.Arm64MrsFpcr);
|
||||
|
||||
if (ftz)
|
||||
{
|
||||
fpcr = context.BitwiseOr(fpcr, Const((int)FPCR.Fz));
|
||||
}
|
||||
else
|
||||
{
|
||||
fpcr = context.BitwiseAnd(fpcr, Const(~(int)FPCR.Fz));
|
||||
}
|
||||
|
||||
context.AddIntrinsicNoRet(Intrinsic.Arm64MsrFpcr, fpcr);
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static Operand FpBitsToInt(EmitterContext context, Operand fp)
|
||||
{
|
||||
Operand vec = context.VectorInsert(context.VectorZero(), fp, 0);
|
||||
return context.VectorExtract(OperandType.I32, vec, 0);
|
||||
}
|
||||
|
||||
public static FpFlagsPInvokeTest GenerateFpFlagsPInvokeTest()
|
||||
{
|
||||
EmitterContext context = new EmitterContext();
|
||||
|
||||
Operand methodAddress = context.Copy(context.LoadArgument(OperandType.I64, 0));
|
||||
|
||||
// Verify that default dotnet fp state does not flush to zero.
|
||||
// This is required for SoftFloat to function.
|
||||
|
||||
// Denormal + zero != 0
|
||||
|
||||
Operand denormal = ConstF(BitConverter.Int32BitsToSingle(1)); // 1.40129846432e-45
|
||||
Operand zeroF = ConstF(0f);
|
||||
Operand zero = Const(0);
|
||||
|
||||
Operand result = context.Add(zeroF, denormal);
|
||||
|
||||
// Must not be zero.
|
||||
|
||||
Operand correct1Label = Label();
|
||||
|
||||
context.BranchIfFalse(correct1Label, context.ICompareEqual(FpBitsToInt(context, result), zero));
|
||||
|
||||
context.Return(Const(1));
|
||||
|
||||
context.MarkLabel(correct1Label);
|
||||
|
||||
// Set flush to zero flag. If unsupported by the backend, just return true.
|
||||
|
||||
if (!SetPlatformFtz(context, true))
|
||||
{
|
||||
context.Return(Const(0));
|
||||
}
|
||||
|
||||
// Denormal + zero == 0
|
||||
|
||||
Operand resultFz = context.Add(zeroF, denormal);
|
||||
|
||||
// Must equal zero.
|
||||
|
||||
Operand correct2Label = Label();
|
||||
|
||||
context.BranchIfTrue(correct2Label, context.ICompareEqual(FpBitsToInt(context, resultFz), zero));
|
||||
|
||||
SetPlatformFtz(context, false);
|
||||
|
||||
context.Return(Const(2));
|
||||
|
||||
context.MarkLabel(correct2Label);
|
||||
|
||||
// Call a managed method. This method should not change Fz state.
|
||||
|
||||
context.Call(methodAddress, OperandType.None);
|
||||
|
||||
// Denormal + zero == 0
|
||||
|
||||
Operand resultFz2 = context.Add(zeroF, denormal);
|
||||
|
||||
// Must equal zero.
|
||||
|
||||
Operand correct3Label = Label();
|
||||
|
||||
context.BranchIfTrue(correct3Label, context.ICompareEqual(FpBitsToInt(context, resultFz2), zero));
|
||||
|
||||
SetPlatformFtz(context, false);
|
||||
|
||||
context.Return(Const(3));
|
||||
|
||||
context.MarkLabel(correct3Label);
|
||||
|
||||
// Success.
|
||||
|
||||
SetPlatformFtz(context, false);
|
||||
|
||||
context.Return(Const(0));
|
||||
|
||||
// Compile and return the function.
|
||||
|
||||
ControlFlowGraph cfg = context.GetControlFlowGraph();
|
||||
|
||||
OperandType[] argTypes = new OperandType[] { OperandType.I64 };
|
||||
|
||||
return Compiler.Compile(cfg, argTypes, OperandType.I32, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<FpFlagsPInvokeTest>();
|
||||
}
|
||||
}
|
||||
}
|
@ -3,40 +3,40 @@
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="Avalonia" Version="0.10.18" />
|
||||
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="0.10.18" />
|
||||
<PackageVersion Include="Avalonia.Desktop" Version="0.10.18" />
|
||||
<PackageVersion Include="Avalonia.Diagnostics" Version="0.10.18" />
|
||||
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="0.10.18" />
|
||||
<PackageVersion Include="Avalonia" Version="0.10.19" />
|
||||
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="0.10.19" />
|
||||
<PackageVersion Include="Avalonia.Desktop" Version="0.10.19" />
|
||||
<PackageVersion Include="Avalonia.Diagnostics" Version="0.10.19" />
|
||||
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="0.10.19" />
|
||||
<PackageVersion Include="Avalonia.Svg" Version="0.10.18" />
|
||||
<PackageVersion Include="Avalonia.Svg.Skia" Version="0.10.18" />
|
||||
<PackageVersion Include="CommandLineParser" Version="2.9.1" />
|
||||
<PackageVersion Include="Concentus" Version="1.1.7" />
|
||||
<PackageVersion Include="Crc32.NET" Version="1.2.0" />
|
||||
<PackageVersion Include="DiscordRichPresence" Version="1.1.3.18" />
|
||||
<PackageVersion Include="DynamicData" Version="7.12.11" />
|
||||
<PackageVersion Include="DynamicData" Version="7.13.5" />
|
||||
<PackageVersion Include="FluentAvaloniaUI" Version="1.4.5" />
|
||||
<PackageVersion Include="GtkSharp.Dependencies" Version="1.1.1" />
|
||||
<PackageVersion Include="GtkSharp.Dependencies.osx" Version="0.0.5" />
|
||||
<PackageVersion Include="jp2masa.Avalonia.Flexbox" Version="0.2.0" />
|
||||
<PackageVersion Include="LibHac" Version="0.17.0" />
|
||||
<PackageVersion Include="LibHac" Version="0.18.0" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.4.0" />
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.5.0" />
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
|
||||
<PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="2.3.2" />
|
||||
<PackageVersion Include="MsgPack.Cli" Version="1.0.1" />
|
||||
<PackageVersion Include="NUnit" Version="3.13.3" />
|
||||
<PackageVersion Include="NUnit3TestAdapter" Version="4.1.0" />
|
||||
<PackageVersion Include="OpenTK.Core" Version="4.7.5" />
|
||||
<PackageVersion Include="OpenTK.Graphics" Version="4.7.5" />
|
||||
<PackageVersion Include="OpenTK.OpenAL" Version="4.7.5" />
|
||||
<PackageVersion Include="OpenTK.Windowing.GraphicsLibraryFramework" Version="4.7.5" />
|
||||
<PackageVersion Include="OpenTK.Core" Version="4.7.7" />
|
||||
<PackageVersion Include="OpenTK.Graphics" Version="4.7.7" />
|
||||
<PackageVersion Include="OpenTK.OpenAL" Version="4.7.7" />
|
||||
<PackageVersion Include="OpenTK.Windowing.GraphicsLibraryFramework" Version="4.7.7" />
|
||||
<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.Vulkan.Dependencies.MoltenVK" Version="1.2.0" />
|
||||
<PackageVersion Include="Ryujinx.GtkSharp" Version="3.24.24.59-ryujinx" />
|
||||
<PackageVersion Include="Ryujinx.SDL2-CS" Version="2.26.1-build23" />
|
||||
<PackageVersion Include="Ryujinx.SDL2-CS" Version="2.26.3-build25" />
|
||||
<PackageVersion Include="shaderc.net" Version="0.1.0" />
|
||||
<PackageVersion Include="SharpZipLib" Version="1.4.1" />
|
||||
<PackageVersion Include="SharpZipLib" Version="1.4.2" />
|
||||
<PackageVersion Include="Silk.NET.Vulkan" Version="2.16.0" />
|
||||
<PackageVersion Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.16.0" />
|
||||
<PackageVersion Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.16.0" />
|
||||
@ -44,11 +44,10 @@
|
||||
<PackageVersion Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta11" />
|
||||
<PackageVersion Include="SPB" Version="0.0.4-build28" />
|
||||
<PackageVersion Include="System.Drawing.Common" Version="7.0.0" />
|
||||
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="6.26.0" />
|
||||
<PackageVersion Include="System.IO.FileSystem.Primitives" Version="4.3.0" />
|
||||
<PackageVersion Include="System.Management" Version="7.0.0" />
|
||||
<PackageVersion Include="System.Net.NameResolution" Version="4.3.0" />
|
||||
<PackageVersion Include="System.Threading.ThreadPool" Version="4.3.0" />
|
||||
<PackageVersion Include="XamlNameReferenceGenerator" Version="1.5.1" />
|
||||
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="6.29.0" />
|
||||
<PackageVersion Include="System.IO.Hashing" Version="7.0.0" />
|
||||
<PackageVersion Include="System.Management" Version="7.0.1" />
|
||||
<PackageVersion Include="UnicornEngine.Unicorn" Version="2.0.2-rc1-fb78016" />
|
||||
<PackageVersion Include="XamlNameReferenceGenerator" Version="1.6.1" />
|
||||
</ItemGroup>
|
||||
</Project>
|
@ -40,7 +40,7 @@
|
||||
|
||||
## Compatibility
|
||||
|
||||
As of November 2022, Ryujinx has been tested on approximately 3,800 titles; over 3,600 boot past menus and into gameplay, with roughly 3,200 of those being considered playable.
|
||||
As of April 2023, Ryujinx has been tested on approximately 4,050 titles; over 4,000 boot past menus and into gameplay, with roughly 3,400 of those being considered playable.
|
||||
You can check out the compatibility list [here](https://github.com/Ryujinx/Ryujinx-Games-List/issues). Anyone is free to submit a new game test or update an existing game test entry; simply follow the new issue template and testing guidelines, or post as a reply to the applicable game issue. Use the search function to see if a game has been tested already!
|
||||
|
||||
## Usage
|
||||
@ -96,7 +96,7 @@ Ryujinx system files are stored in the `Ryujinx` folder. This folder is located
|
||||
|
||||
- **GPU**
|
||||
|
||||
The GPU emulator emulates the Switch's Maxwell GPU using either the OpenGL (version 4.5 minimum), Vulkan, or Metal (via MoltenVK) APIs through a custom build of OpenTK or Silk.NET respectively. There are currently four graphics enhancements available to the end user in Ryujinx: Disk Shader Caching, Resolution Scaling, Aspect Ratio Adjustment, and Anisotropic Filtering. These enhancements can be adjusted or toggled as desired in the GUI.
|
||||
The GPU emulator emulates the Switch's Maxwell GPU using either the OpenGL (version 4.5 minimum), Vulkan, or Metal (via MoltenVK) APIs through a custom build of OpenTK or Silk.NET respectively. There are currently six graphics enhancements available to the end user in Ryujinx: Disk Shader Caching, Resolution Scaling, Anti-Aliasing, Scaling Filters (including FSR), Anisotropic Filtering and Aspect Ratio Adjustment. These enhancements can be adjusted or toggled as desired in the GUI.
|
||||
|
||||
- **Input**
|
||||
|
||||
|
@ -18,6 +18,7 @@ namespace Ryujinx.Audio.Backends.SDL2
|
||||
private ulong _playedSampleCount;
|
||||
private ManualResetEvent _updateRequiredEvent;
|
||||
private uint _outputStream;
|
||||
private bool _hasSetupError;
|
||||
private SDL_AudioCallback _callbackDelegate;
|
||||
private int _bytesPerFrame;
|
||||
private uint _sampleCount;
|
||||
@ -42,7 +43,7 @@ namespace Ryujinx.Audio.Backends.SDL2
|
||||
private void EnsureAudioStreamSetup(AudioBuffer buffer)
|
||||
{
|
||||
uint bufferSampleCount = (uint)GetSampleCount(buffer);
|
||||
bool needAudioSetup = _outputStream == 0 ||
|
||||
bool needAudioSetup = (_outputStream == 0 && !_hasSetupError) ||
|
||||
(bufferSampleCount >= Constants.TargetSampleCount && bufferSampleCount < _sampleCount);
|
||||
|
||||
if (needAudioSetup)
|
||||
@ -51,12 +52,9 @@ namespace Ryujinx.Audio.Backends.SDL2
|
||||
|
||||
uint newOutputStream = SDL2HardwareDeviceDriver.OpenStream(RequestedSampleFormat, RequestedSampleRate, RequestedChannelCount, _sampleCount, _callbackDelegate);
|
||||
|
||||
if (newOutputStream == 0)
|
||||
{
|
||||
// No stream in place, this is unexpected.
|
||||
throw new InvalidOperationException($"OpenStream failed with error: \"{SDL_GetError()}\"");
|
||||
}
|
||||
else
|
||||
_hasSetupError = newOutputStream == 0;
|
||||
|
||||
if (!_hasSetupError)
|
||||
{
|
||||
if (_outputStream != 0)
|
||||
{
|
||||
@ -151,11 +149,20 @@ namespace Ryujinx.Audio.Backends.SDL2
|
||||
{
|
||||
EnsureAudioStreamSetup(buffer);
|
||||
|
||||
SDL2AudioBuffer driverBuffer = new SDL2AudioBuffer(buffer.DataPointer, GetSampleCount(buffer));
|
||||
if (_outputStream != 0)
|
||||
{
|
||||
SDL2AudioBuffer driverBuffer = new SDL2AudioBuffer(buffer.DataPointer, GetSampleCount(buffer));
|
||||
|
||||
_ringBuffer.Write(buffer.Data, 0, buffer.Data.Length);
|
||||
_ringBuffer.Write(buffer.Data, 0, buffer.Data.Length);
|
||||
|
||||
_queuedBuffers.Enqueue(driverBuffer);
|
||||
_queuedBuffers.Enqueue(driverBuffer);
|
||||
}
|
||||
else
|
||||
{
|
||||
Interlocked.Add(ref _playedSampleCount, GetSampleCount(buffer));
|
||||
|
||||
_updateRequiredEvent.Set();
|
||||
}
|
||||
}
|
||||
|
||||
public override void SetVolume(float volume)
|
||||
|
@ -5,9 +5,8 @@ using Ryujinx.Memory;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading;
|
||||
|
||||
using static Ryujinx.Audio.Integration.IHardwareDeviceDriver;
|
||||
using static Ryujinx.Audio.Backends.SoundIo.Native.SoundIo;
|
||||
using static Ryujinx.Audio.Integration.IHardwareDeviceDriver;
|
||||
|
||||
namespace Ryujinx.Audio.Backends.SoundIo
|
||||
{
|
||||
|
@ -400,7 +400,9 @@ namespace Ryujinx.Audio.Common
|
||||
{
|
||||
uint bufferIndex = (_releasedBufferIndex - _bufferReleasedCount) % Constants.AudioDeviceBufferCountMax;
|
||||
|
||||
for (int i = 0; i < GetTotalBufferCount(); i++)
|
||||
uint totalBufferCount = GetTotalBufferCount();
|
||||
|
||||
for (int i = 0; i < totalBufferCount; i++)
|
||||
{
|
||||
if (_buffers[bufferIndex].BufferTag == bufferTag)
|
||||
{
|
||||
|
@ -1,8 +1,8 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using Ryujinx.Audio.Renderer.Dsp.Effect;
|
||||
using Ryujinx.Audio.Renderer.Dsp.Effect;
|
||||
using Ryujinx.Audio.Renderer.Dsp.State;
|
||||
using Ryujinx.Audio.Renderer.Parameter.Effect;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
{
|
||||
|
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.Intrinsics;
|
||||
using System.Runtime.Intrinsics.X86;
|
||||
@ -380,7 +381,6 @@ namespace Ryujinx.Audio.Renderer.Dsp
|
||||
return _normalCurveLut2F;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private unsafe static void ResampleDefaultQuality(Span<float> outputBuffer, ReadOnlySpan<short> inputBuffer, float ratio, ref float fraction, int sampleCount, bool needPitch)
|
||||
{
|
||||
ReadOnlySpan<float> parameters = GetDefaultParameter(ratio);
|
||||
@ -394,35 +394,33 @@ namespace Ryujinx.Audio.Renderer.Dsp
|
||||
if (ratio == 1f)
|
||||
{
|
||||
fixed (short* pInput = inputBuffer)
|
||||
fixed (float* pOutput = outputBuffer, pParameters = parameters)
|
||||
{
|
||||
fixed (float* pOutput = outputBuffer, pParameters = parameters)
|
||||
Vector128<float> parameter = Sse.LoadVector128(pParameters);
|
||||
|
||||
for (; i < (sampleCount & ~3); i += 4)
|
||||
{
|
||||
Vector128<float> parameter = Sse.LoadVector128(pParameters);
|
||||
Vector128<int> intInput0 = Sse41.ConvertToVector128Int32(pInput + (uint)i);
|
||||
Vector128<int> intInput1 = Sse41.ConvertToVector128Int32(pInput + (uint)i + 1);
|
||||
Vector128<int> intInput2 = Sse41.ConvertToVector128Int32(pInput + (uint)i + 2);
|
||||
Vector128<int> intInput3 = Sse41.ConvertToVector128Int32(pInput + (uint)i + 3);
|
||||
|
||||
for (; i < (sampleCount & ~3); i += 4)
|
||||
{
|
||||
Vector128<int> intInput0 = Sse41.ConvertToVector128Int32(pInput + (uint)i);
|
||||
Vector128<int> intInput1 = Sse41.ConvertToVector128Int32(pInput + (uint)i + 1);
|
||||
Vector128<int> intInput2 = Sse41.ConvertToVector128Int32(pInput + (uint)i + 2);
|
||||
Vector128<int> intInput3 = Sse41.ConvertToVector128Int32(pInput + (uint)i + 3);
|
||||
Vector128<float> input0 = Sse2.ConvertToVector128Single(intInput0);
|
||||
Vector128<float> input1 = Sse2.ConvertToVector128Single(intInput1);
|
||||
Vector128<float> input2 = Sse2.ConvertToVector128Single(intInput2);
|
||||
Vector128<float> input3 = Sse2.ConvertToVector128Single(intInput3);
|
||||
|
||||
Vector128<float> input0 = Sse2.ConvertToVector128Single(intInput0);
|
||||
Vector128<float> input1 = Sse2.ConvertToVector128Single(intInput1);
|
||||
Vector128<float> input2 = Sse2.ConvertToVector128Single(intInput2);
|
||||
Vector128<float> input3 = Sse2.ConvertToVector128Single(intInput3);
|
||||
Vector128<float> mix0 = Sse.Multiply(input0, parameter);
|
||||
Vector128<float> mix1 = Sse.Multiply(input1, parameter);
|
||||
Vector128<float> mix2 = Sse.Multiply(input2, parameter);
|
||||
Vector128<float> mix3 = Sse.Multiply(input3, parameter);
|
||||
|
||||
Vector128<float> mix0 = Sse.Multiply(input0, parameter);
|
||||
Vector128<float> mix1 = Sse.Multiply(input1, parameter);
|
||||
Vector128<float> mix2 = Sse.Multiply(input2, parameter);
|
||||
Vector128<float> mix3 = Sse.Multiply(input3, parameter);
|
||||
Vector128<float> mix01 = Sse3.HorizontalAdd(mix0, mix1);
|
||||
Vector128<float> mix23 = Sse3.HorizontalAdd(mix2, mix3);
|
||||
|
||||
Vector128<float> mix01 = Sse3.HorizontalAdd(mix0, mix1);
|
||||
Vector128<float> mix23 = Sse3.HorizontalAdd(mix2, mix3);
|
||||
Vector128<float> mix0123 = Sse3.HorizontalAdd(mix01, mix23);
|
||||
|
||||
Vector128<float> mix0123 = Sse3.HorizontalAdd(mix01, mix23);
|
||||
|
||||
Sse.Store(pOutput + (uint)i, Sse41.RoundToNearestInteger(mix0123));
|
||||
}
|
||||
Sse.Store(pOutput + (uint)i, Sse41.RoundToNearestInteger(mix0123));
|
||||
}
|
||||
}
|
||||
|
||||
@ -431,62 +429,60 @@ namespace Ryujinx.Audio.Renderer.Dsp
|
||||
else
|
||||
{
|
||||
fixed (short* pInput = inputBuffer)
|
||||
fixed (float* pOutput = outputBuffer, pParameters = parameters)
|
||||
{
|
||||
fixed (float* pOutput = outputBuffer, pParameters = parameters)
|
||||
for (; i < (sampleCount & ~3); i += 4)
|
||||
{
|
||||
for (; i < (sampleCount & ~3); i += 4)
|
||||
{
|
||||
uint baseIndex0 = (uint)(fraction * 128) * 4;
|
||||
uint inputIndex0 = (uint)inputBufferIndex;
|
||||
uint baseIndex0 = (uint)(fraction * 128) * 4;
|
||||
uint inputIndex0 = (uint)inputBufferIndex;
|
||||
|
||||
fraction += ratio;
|
||||
fraction += ratio;
|
||||
|
||||
uint baseIndex1 = ((uint)(fraction * 128) & 127) * 4;
|
||||
uint inputIndex1 = (uint)inputBufferIndex + (uint)fraction;
|
||||
uint baseIndex1 = ((uint)(fraction * 128) & 127) * 4;
|
||||
uint inputIndex1 = (uint)inputBufferIndex + (uint)fraction;
|
||||
|
||||
fraction += ratio;
|
||||
fraction += ratio;
|
||||
|
||||
uint baseIndex2 = ((uint)(fraction * 128) & 127) * 4;
|
||||
uint inputIndex2 = (uint)inputBufferIndex + (uint)fraction;
|
||||
uint baseIndex2 = ((uint)(fraction * 128) & 127) * 4;
|
||||
uint inputIndex2 = (uint)inputBufferIndex + (uint)fraction;
|
||||
|
||||
fraction += ratio;
|
||||
fraction += ratio;
|
||||
|
||||
uint baseIndex3 = ((uint)(fraction * 128) & 127) * 4;
|
||||
uint inputIndex3 = (uint)inputBufferIndex + (uint)fraction;
|
||||
uint baseIndex3 = ((uint)(fraction * 128) & 127) * 4;
|
||||
uint inputIndex3 = (uint)inputBufferIndex + (uint)fraction;
|
||||
|
||||
fraction += ratio;
|
||||
inputBufferIndex += (int)fraction;
|
||||
fraction += ratio;
|
||||
inputBufferIndex += (int)fraction;
|
||||
|
||||
// Only keep lower part (safe as fraction isn't supposed to be negative)
|
||||
fraction -= (int)fraction;
|
||||
// Only keep lower part (safe as fraction isn't supposed to be negative)
|
||||
fraction -= (int)fraction;
|
||||
|
||||
Vector128<float> parameter0 = Sse.LoadVector128(pParameters + baseIndex0);
|
||||
Vector128<float> parameter1 = Sse.LoadVector128(pParameters + baseIndex1);
|
||||
Vector128<float> parameter2 = Sse.LoadVector128(pParameters + baseIndex2);
|
||||
Vector128<float> parameter3 = Sse.LoadVector128(pParameters + baseIndex3);
|
||||
Vector128<float> parameter0 = Sse.LoadVector128(pParameters + baseIndex0);
|
||||
Vector128<float> parameter1 = Sse.LoadVector128(pParameters + baseIndex1);
|
||||
Vector128<float> parameter2 = Sse.LoadVector128(pParameters + baseIndex2);
|
||||
Vector128<float> parameter3 = Sse.LoadVector128(pParameters + baseIndex3);
|
||||
|
||||
Vector128<int> intInput0 = Sse41.ConvertToVector128Int32(pInput + inputIndex0);
|
||||
Vector128<int> intInput1 = Sse41.ConvertToVector128Int32(pInput + inputIndex1);
|
||||
Vector128<int> intInput2 = Sse41.ConvertToVector128Int32(pInput + inputIndex2);
|
||||
Vector128<int> intInput3 = Sse41.ConvertToVector128Int32(pInput + inputIndex3);
|
||||
Vector128<int> intInput0 = Sse41.ConvertToVector128Int32(pInput + inputIndex0);
|
||||
Vector128<int> intInput1 = Sse41.ConvertToVector128Int32(pInput + inputIndex1);
|
||||
Vector128<int> intInput2 = Sse41.ConvertToVector128Int32(pInput + inputIndex2);
|
||||
Vector128<int> intInput3 = Sse41.ConvertToVector128Int32(pInput + inputIndex3);
|
||||
|
||||
Vector128<float> input0 = Sse2.ConvertToVector128Single(intInput0);
|
||||
Vector128<float> input1 = Sse2.ConvertToVector128Single(intInput1);
|
||||
Vector128<float> input2 = Sse2.ConvertToVector128Single(intInput2);
|
||||
Vector128<float> input3 = Sse2.ConvertToVector128Single(intInput3);
|
||||
Vector128<float> input0 = Sse2.ConvertToVector128Single(intInput0);
|
||||
Vector128<float> input1 = Sse2.ConvertToVector128Single(intInput1);
|
||||
Vector128<float> input2 = Sse2.ConvertToVector128Single(intInput2);
|
||||
Vector128<float> input3 = Sse2.ConvertToVector128Single(intInput3);
|
||||
|
||||
Vector128<float> mix0 = Sse.Multiply(input0, parameter0);
|
||||
Vector128<float> mix1 = Sse.Multiply(input1, parameter1);
|
||||
Vector128<float> mix2 = Sse.Multiply(input2, parameter2);
|
||||
Vector128<float> mix3 = Sse.Multiply(input3, parameter3);
|
||||
Vector128<float> mix0 = Sse.Multiply(input0, parameter0);
|
||||
Vector128<float> mix1 = Sse.Multiply(input1, parameter1);
|
||||
Vector128<float> mix2 = Sse.Multiply(input2, parameter2);
|
||||
Vector128<float> mix3 = Sse.Multiply(input3, parameter3);
|
||||
|
||||
Vector128<float> mix01 = Sse3.HorizontalAdd(mix0, mix1);
|
||||
Vector128<float> mix23 = Sse3.HorizontalAdd(mix2, mix3);
|
||||
Vector128<float> mix01 = Sse3.HorizontalAdd(mix0, mix1);
|
||||
Vector128<float> mix23 = Sse3.HorizontalAdd(mix2, mix3);
|
||||
|
||||
Vector128<float> mix0123 = Sse3.HorizontalAdd(mix01, mix23);
|
||||
Vector128<float> mix0123 = Sse3.HorizontalAdd(mix01, mix23);
|
||||
|
||||
Sse.Store(pOutput + (uint)i, Sse41.RoundToNearestInteger(mix0123));
|
||||
}
|
||||
Sse.Store(pOutput + (uint)i, Sse41.RoundToNearestInteger(mix0123));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -526,34 +522,59 @@ namespace Ryujinx.Audio.Renderer.Dsp
|
||||
return _highCurveLut2F;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void ResampleHighQuality(Span<float> outputBuffer, ReadOnlySpan<short> inputBuffer, float ratio, ref float fraction, int sampleCount)
|
||||
private static unsafe void ResampleHighQuality(Span<float> outputBuffer, ReadOnlySpan<short> inputBuffer, float ratio, ref float fraction, int sampleCount)
|
||||
{
|
||||
ReadOnlySpan<float> parameters = GetHighParameter(ratio);
|
||||
|
||||
int inputBufferIndex = 0;
|
||||
|
||||
// TODO: fast path
|
||||
|
||||
for (int i = 0; i < sampleCount; i++)
|
||||
if (Avx2.IsSupported)
|
||||
{
|
||||
int baseIndex = (int)(fraction * 128) * 8;
|
||||
ReadOnlySpan<float> parameter = parameters.Slice(baseIndex, 8);
|
||||
ReadOnlySpan<short> currentInput = inputBuffer.Slice(inputBufferIndex, 8);
|
||||
// Fast path; assumes 256-bit vectors for simplicity because the filter is 8 taps
|
||||
fixed (short* pInput = inputBuffer)
|
||||
fixed (float* pParameters = parameters)
|
||||
{
|
||||
for (int i = 0; i < sampleCount; i++)
|
||||
{
|
||||
int baseIndex = (int)(fraction * 128) * 8;
|
||||
|
||||
outputBuffer[i] = (float)Math.Round(currentInput[0] * parameter[0] +
|
||||
currentInput[1] * parameter[1] +
|
||||
currentInput[2] * parameter[2] +
|
||||
currentInput[3] * parameter[3] +
|
||||
currentInput[4] * parameter[4] +
|
||||
currentInput[5] * parameter[5] +
|
||||
currentInput[6] * parameter[6] +
|
||||
currentInput[7] * parameter[7]);
|
||||
Vector256<int> intInput = Avx2.ConvertToVector256Int32(pInput + inputBufferIndex);
|
||||
Vector256<float> floatInput = Avx.ConvertToVector256Single(intInput);
|
||||
Vector256<float> parameter = Avx.LoadVector256(pParameters + baseIndex);
|
||||
Vector256<float> dp = Avx.DotProduct(floatInput, parameter, control: 0xFF);
|
||||
|
||||
fraction += ratio;
|
||||
inputBufferIndex += (int)MathF.Truncate(fraction);
|
||||
// avx2 does an 8-element dot product piecewise so we have to sum up 2 intermediate results
|
||||
outputBuffer[i] = (float)Math.Round(dp[0] + dp[4]);
|
||||
|
||||
fraction -= (int)fraction;
|
||||
fraction += ratio;
|
||||
inputBufferIndex += (int)MathF.Truncate(fraction);
|
||||
|
||||
fraction -= (int)fraction;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < sampleCount; i++)
|
||||
{
|
||||
int baseIndex = (int)(fraction * 128) * 8;
|
||||
ReadOnlySpan<float> parameter = parameters.Slice(baseIndex, 8);
|
||||
ReadOnlySpan<short> currentInput = inputBuffer.Slice(inputBufferIndex, 8);
|
||||
|
||||
outputBuffer[i] = (float)Math.Round(currentInput[0] * parameter[0] +
|
||||
currentInput[1] * parameter[1] +
|
||||
currentInput[2] * parameter[2] +
|
||||
currentInput[3] * parameter[3] +
|
||||
currentInput[4] * parameter[4] +
|
||||
currentInput[5] * parameter[5] +
|
||||
currentInput[6] * parameter[6] +
|
||||
currentInput[7] * parameter[7]);
|
||||
|
||||
fraction += ratio;
|
||||
inputBufferIndex += (int)MathF.Truncate(fraction);
|
||||
|
||||
fraction -= (int)fraction;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,7 @@ using Ryujinx.Audio.Renderer.Server.Upsampler;
|
||||
using Ryujinx.Common.Memory;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp
|
||||
@ -70,16 +71,32 @@ namespace Ryujinx.Audio.Renderer.Dsp
|
||||
return;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
float DoFilterBank(ref UpsamplerBufferState state, in Array20<float> bank)
|
||||
{
|
||||
float result = 0.0f;
|
||||
|
||||
Debug.Assert(state.History.Length == HistoryLength);
|
||||
Debug.Assert(bank.Length == FilterBankLength);
|
||||
for (int j = 0; j < FilterBankLength; j++)
|
||||
|
||||
int curIdx = 0;
|
||||
if (Vector.IsHardwareAccelerated)
|
||||
{
|
||||
result += bank[j] * state.History[j];
|
||||
// Do SIMD-accelerated block operations where possible.
|
||||
// Only about a 2x speedup since filter bank length is short
|
||||
int stopIdx = FilterBankLength - (FilterBankLength % Vector<float>.Count);
|
||||
while (curIdx < stopIdx)
|
||||
{
|
||||
result += Vector.Dot(
|
||||
new Vector<float>(bank.AsSpan().Slice(curIdx, Vector<float>.Count)),
|
||||
new Vector<float>(state.History.AsSpan().Slice(curIdx, Vector<float>.Count)));
|
||||
curIdx += Vector<float>.Count;
|
||||
}
|
||||
}
|
||||
|
||||
while (curIdx < FilterBankLength)
|
||||
{
|
||||
result += bank[curIdx] * state.History[curIdx];
|
||||
curIdx++;
|
||||
}
|
||||
|
||||
return result;
|
||||
|
@ -1,7 +1,7 @@
|
||||
using Ryujinx.Audio.Renderer.Common;
|
||||
using Ryujinx.Audio.Renderer.Dsp.State;
|
||||
using Ryujinx.Audio.Renderer.Parameter.Effect;
|
||||
using Ryujinx.Audio.Renderer.Parameter;
|
||||
using Ryujinx.Audio.Renderer.Parameter.Effect;
|
||||
using Ryujinx.Audio.Renderer.Server.MemoryPool;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
@ -53,7 +53,6 @@ using Key = Ryujinx.Input.Key;
|
||||
using MouseButton = Ryujinx.Input.MouseButton;
|
||||
using Size = Avalonia.Size;
|
||||
using Switch = Ryujinx.HLE.Switch;
|
||||
using WindowState = Avalonia.Controls.WindowState;
|
||||
|
||||
namespace Ryujinx.Ava
|
||||
{
|
||||
@ -172,6 +171,13 @@ namespace Ryujinx.Ava
|
||||
ConfigurationState.Instance.Graphics.AspectRatio.Event += UpdateAspectRatioState;
|
||||
ConfigurationState.Instance.System.EnableDockedMode.Event += UpdateDockedModeState;
|
||||
ConfigurationState.Instance.System.AudioVolume.Event += UpdateAudioVolumeState;
|
||||
ConfigurationState.Instance.System.EnableDockedMode.Event += UpdateDockedModeState;
|
||||
ConfigurationState.Instance.System.AudioVolume.Event += UpdateAudioVolumeState;
|
||||
ConfigurationState.Instance.Graphics.AntiAliasing.Event += UpdateAntiAliasing;
|
||||
ConfigurationState.Instance.Graphics.ScalingFilter.Event += UpdateScalingFilter;
|
||||
ConfigurationState.Instance.Graphics.ScalingFilterLevel.Event += UpdateScalingFilterLevel;
|
||||
|
||||
ConfigurationState.Instance.Multiplayer.LanInterfaceId.Event += UpdateLanInterfaceIdState;
|
||||
|
||||
_gpuCancellationTokenSource = new CancellationTokenSource();
|
||||
}
|
||||
@ -194,6 +200,17 @@ namespace Ryujinx.Ava
|
||||
}
|
||||
}
|
||||
}
|
||||
private void UpdateScalingFilterLevel(object sender, ReactiveEventArgs<int> e)
|
||||
{
|
||||
_renderer.Window?.SetScalingFilter((Graphics.GAL.ScalingFilter)ConfigurationState.Instance.Graphics.ScalingFilter.Value);
|
||||
_renderer.Window?.SetScalingFilterLevel(ConfigurationState.Instance.Graphics.ScalingFilterLevel.Value);
|
||||
}
|
||||
|
||||
private void UpdateScalingFilter(object sender, ReactiveEventArgs<Ryujinx.Common.Configuration.ScalingFilter> e)
|
||||
{
|
||||
_renderer.Window?.SetScalingFilter((Graphics.GAL.ScalingFilter)ConfigurationState.Instance.Graphics.ScalingFilter.Value);
|
||||
_renderer.Window?.SetScalingFilterLevel(ConfigurationState.Instance.Graphics.ScalingFilterLevel.Value);
|
||||
}
|
||||
|
||||
private void ShowCursor()
|
||||
{
|
||||
@ -241,7 +258,7 @@ namespace Ryujinx.Ava
|
||||
{
|
||||
DateTime currentTime = DateTime.Now;
|
||||
string filename = $"ryujinx_capture_{currentTime.Year}-{currentTime.Month:D2}-{currentTime.Day:D2}_{currentTime.Hour:D2}-{currentTime.Minute:D2}-{currentTime.Second:D2}.png";
|
||||
|
||||
|
||||
string directory = AppDataManager.Mode switch
|
||||
{
|
||||
AppDataManager.LaunchMode.Portable => Path.Combine(AppDataManager.BaseDirPath, "screenshots"),
|
||||
@ -305,14 +322,16 @@ namespace Ryujinx.Ava
|
||||
|
||||
_viewModel.IsGameRunning = true;
|
||||
|
||||
string titleNameSection = string.IsNullOrWhiteSpace(Device.Application.TitleName) ? string.Empty : $" - {Device.Application.TitleName}";
|
||||
string titleVersionSection = string.IsNullOrWhiteSpace(Device.Application.DisplayVersion) ? string.Empty : $" v{Device.Application.DisplayVersion}";
|
||||
string titleIdSection = string.IsNullOrWhiteSpace(Device.Application.TitleIdText) ? string.Empty : $" ({Device.Application.TitleIdText.ToUpper()})";
|
||||
string titleArchSection = Device.Application.TitleIs64Bit ? " (64-bit)" : " (32-bit)";
|
||||
var activeProcess = Device.Processes.ActiveApplication;
|
||||
|
||||
string titleNameSection = string.IsNullOrWhiteSpace(activeProcess.Name) ? string.Empty : $" {activeProcess.Name}";
|
||||
string titleVersionSection = string.IsNullOrWhiteSpace(activeProcess.DisplayVersion) ? string.Empty : $" v{activeProcess.DisplayVersion}";
|
||||
string titleIdSection = $" ({activeProcess.ProgramIdText.ToUpper()})";
|
||||
string titleArchSection = activeProcess.Is64Bit ? " (64-bit)" : " (32-bit)";
|
||||
|
||||
Dispatcher.UIThread.InvokeAsync(() =>
|
||||
{
|
||||
_viewModel.Title = $"Ryujinx {Program.Version}{titleNameSection}{titleVersionSection}{titleIdSection}{titleArchSection}";
|
||||
_viewModel.Title = $"Ryujinx {Program.Version} -{titleNameSection}{titleVersionSection}{titleIdSection}{titleArchSection}";
|
||||
});
|
||||
|
||||
_viewModel.SetUIProgressHandlers(Device);
|
||||
@ -346,6 +365,11 @@ namespace Ryujinx.Ava
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateAntiAliasing(object sender, ReactiveEventArgs<Ryujinx.Common.Configuration.AntiAliasing> e)
|
||||
{
|
||||
_renderer?.Window?.SetAntiAliasing((Graphics.GAL.AntiAliasing)e.NewValue);
|
||||
}
|
||||
|
||||
private void UpdateDockedModeState(object sender, ReactiveEventArgs<bool> e)
|
||||
{
|
||||
Device?.System.ChangeDockedModeState(e.NewValue);
|
||||
@ -361,6 +385,11 @@ namespace Ryujinx.Ava
|
||||
});
|
||||
}
|
||||
|
||||
private void UpdateLanInterfaceIdState(object sender, ReactiveEventArgs<string> e)
|
||||
{
|
||||
Device.Configuration.MultiplayerLanInterfaceId = e.NewValue;
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
_isActive = false;
|
||||
@ -403,15 +432,18 @@ namespace Ryujinx.Ava
|
||||
|
||||
private void Dispose()
|
||||
{
|
||||
if (Device.Application != null)
|
||||
if (Device.Processes != null)
|
||||
{
|
||||
_viewModel.UpdateGameMetadata(Device.Application.TitleIdText);
|
||||
_viewModel.UpdateGameMetadata(Device.Processes.ActiveApplication.ProgramIdText);
|
||||
}
|
||||
|
||||
ConfigurationState.Instance.System.IgnoreMissingServices.Event -= UpdateIgnoreMissingServicesState;
|
||||
ConfigurationState.Instance.Graphics.AspectRatio.Event -= UpdateAspectRatioState;
|
||||
ConfigurationState.Instance.System.EnableDockedMode.Event -= UpdateDockedModeState;
|
||||
ConfigurationState.Instance.System.AudioVolume.Event -= UpdateAudioVolumeState;
|
||||
ConfigurationState.Instance.Graphics.ScalingFilter.Event -= UpdateScalingFilter;
|
||||
ConfigurationState.Instance.Graphics.ScalingFilterLevel.Event -= UpdateScalingFilterLevel;
|
||||
ConfigurationState.Instance.Graphics.AntiAliasing.Event -= UpdateAntiAliasing;
|
||||
|
||||
_topLevel.PointerMoved -= TopLevel_PointerMoved;
|
||||
|
||||
@ -516,7 +548,12 @@ namespace Ryujinx.Ava
|
||||
{
|
||||
Logger.Info?.Print(LogClass.Application, "Loading as Firmware Title (NCA).");
|
||||
|
||||
Device.LoadNca(ApplicationPath);
|
||||
if (!Device.LoadNca(ApplicationPath))
|
||||
{
|
||||
Device.Dispose();
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (Directory.Exists(ApplicationPath))
|
||||
{
|
||||
@ -531,13 +568,23 @@ namespace Ryujinx.Ava
|
||||
{
|
||||
Logger.Info?.Print(LogClass.Application, "Loading as cart with RomFS.");
|
||||
|
||||
Device.LoadCart(ApplicationPath, romFsFiles[0]);
|
||||
if (!Device.LoadCart(ApplicationPath, romFsFiles[0]))
|
||||
{
|
||||
Device.Dispose();
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Info?.Print(LogClass.Application, "Loading as cart WITHOUT RomFS.");
|
||||
|
||||
Device.LoadCart(ApplicationPath);
|
||||
if (!Device.LoadCart(ApplicationPath))
|
||||
{
|
||||
Device.Dispose();
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (File.Exists(ApplicationPath))
|
||||
@ -548,7 +595,12 @@ namespace Ryujinx.Ava
|
||||
{
|
||||
Logger.Info?.Print(LogClass.Application, "Loading as XCI.");
|
||||
|
||||
Device.LoadXci(ApplicationPath);
|
||||
if (!Device.LoadXci(ApplicationPath))
|
||||
{
|
||||
Device.Dispose();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
@ -556,7 +608,12 @@ namespace Ryujinx.Ava
|
||||
{
|
||||
Logger.Info?.Print(LogClass.Application, "Loading as NCA.");
|
||||
|
||||
Device.LoadNca(ApplicationPath);
|
||||
if (!Device.LoadNca(ApplicationPath))
|
||||
{
|
||||
Device.Dispose();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
@ -565,7 +622,12 @@ namespace Ryujinx.Ava
|
||||
{
|
||||
Logger.Info?.Print(LogClass.Application, "Loading as NSP.");
|
||||
|
||||
Device.LoadNsp(ApplicationPath);
|
||||
if (!Device.LoadNsp(ApplicationPath))
|
||||
{
|
||||
Device.Dispose();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
@ -575,13 +637,18 @@ namespace Ryujinx.Ava
|
||||
|
||||
try
|
||||
{
|
||||
Device.LoadProgram(ApplicationPath);
|
||||
if (!Device.LoadProgram(ApplicationPath))
|
||||
{
|
||||
Device.Dispose();
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (ArgumentOutOfRangeException)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application, "The specified file is not supported by Ryujinx.");
|
||||
|
||||
Dispose();
|
||||
Device.Dispose();
|
||||
|
||||
return false;
|
||||
}
|
||||
@ -594,14 +661,14 @@ namespace Ryujinx.Ava
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application, "Please specify a valid XCI/NCA/NSP/PFS0/NRO file.");
|
||||
|
||||
Dispose();
|
||||
Device.Dispose();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
DiscordIntegrationModule.SwitchToPlayingState(Device.Application.TitleIdText, Device.Application.TitleName);
|
||||
DiscordIntegrationModule.SwitchToPlayingState(Device.Processes.ActiveApplication.ProgramIdText, Device.Processes.ActiveApplication.Name);
|
||||
|
||||
_viewModel.ApplicationLibrary.LoadAndSaveMetaData(Device.Application.TitleIdText, appMetadata =>
|
||||
_viewModel.ApplicationLibrary.LoadAndSaveMetaData(Device.Processes.ActiveApplication.ProgramIdText, appMetadata =>
|
||||
{
|
||||
appMetadata.LastPlayed = DateTime.UtcNow.ToString();
|
||||
});
|
||||
@ -678,7 +745,9 @@ namespace Ryujinx.Ava
|
||||
ConfigurationState.Instance.System.MemoryManagerMode,
|
||||
ConfigurationState.Instance.System.IgnoreMissingServices,
|
||||
ConfigurationState.Instance.Graphics.AspectRatio,
|
||||
ConfigurationState.Instance.System.AudioVolume);
|
||||
ConfigurationState.Instance.System.AudioVolume,
|
||||
ConfigurationState.Instance.System.UseHypervisor,
|
||||
ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value);
|
||||
|
||||
Device = new Switch(configuration);
|
||||
}
|
||||
@ -765,7 +834,7 @@ namespace Ryujinx.Ava
|
||||
}
|
||||
}
|
||||
|
||||
private unsafe void RenderLoop()
|
||||
private void RenderLoop()
|
||||
{
|
||||
Dispatcher.UIThread.InvokeAsync(() =>
|
||||
{
|
||||
@ -788,6 +857,10 @@ namespace Ryujinx.Ava
|
||||
|
||||
Device.Gpu.Renderer.Initialize(_glLogLevel);
|
||||
|
||||
_renderer?.Window?.SetAntiAliasing((Graphics.GAL.AntiAliasing)ConfigurationState.Instance.Graphics.AntiAliasing.Value);
|
||||
_renderer?.Window?.SetScalingFilter((Graphics.GAL.ScalingFilter)ConfigurationState.Instance.Graphics.ScalingFilter.Value);
|
||||
_renderer?.Window?.SetScalingFilterLevel(ConfigurationState.Instance.Graphics.ScalingFilterLevel.Value);
|
||||
|
||||
Width = (int)_rendererHost.Bounds.Width;
|
||||
Height = (int)_rendererHost.Bounds.Height;
|
||||
|
||||
@ -801,6 +874,8 @@ namespace Ryujinx.Ava
|
||||
Device.Gpu.InitializeShaderCache(_gpuCancellationTokenSource.Token);
|
||||
Translator.IsReadyForTranslation.Set();
|
||||
|
||||
_renderer.Window.ChangeVSyncMode(Device.EnableDeviceVsync);
|
||||
|
||||
while (_isActive)
|
||||
{
|
||||
_ticks += _chrono.ElapsedTicks;
|
||||
@ -839,7 +914,7 @@ namespace Ryujinx.Ava
|
||||
{
|
||||
// Run a status update only when a frame is to be drawn. This prevents from updating the ui and wasting a render when no frame is queued.
|
||||
string dockedMode = ConfigurationState.Instance.System.EnableDockedMode ? LocaleManager.Instance[LocaleKeys.Docked] : LocaleManager.Instance[LocaleKeys.Handheld];
|
||||
|
||||
|
||||
if (GraphicsConfig.ResScale != 1)
|
||||
{
|
||||
dockedMode += $" ({GraphicsConfig.ResScale}x)";
|
||||
@ -920,7 +995,7 @@ namespace Ryujinx.Ava
|
||||
{
|
||||
if (_keyboardInterface.GetKeyboardStateSnapshot().IsPressed(Key.Delete) && _viewModel.WindowState != WindowState.FullScreen)
|
||||
{
|
||||
Device.Application.DiskCacheLoadState?.Cancel();
|
||||
Device.Processes.ActiveApplication.DiskCacheLoadState?.Cancel();
|
||||
}
|
||||
});
|
||||
|
||||
@ -1058,4 +1133,4 @@ namespace Ryujinx.Ava
|
||||
return state;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@
|
||||
"SettingsTabSystemMemoryManagerModeSoftware": "Software",
|
||||
"SettingsTabSystemMemoryManagerModeHost": "Host (fast)",
|
||||
"SettingsTabSystemMemoryManagerModeHostUnchecked": "Host Unchecked (fastest, unsafe)",
|
||||
"SettingsTabSystemUseHypervisor": "Use Hypervisor",
|
||||
"MenuBarFile": "_File",
|
||||
"MenuBarFileOpenFromFile": "_Load Application From File",
|
||||
"MenuBarFileOpenUnpacked": "Load _Unpacked Game",
|
||||
@ -428,6 +429,7 @@
|
||||
"DlcManagerEnableAllButton": "Enable All",
|
||||
"DlcManagerDisableAllButton": "Disable All",
|
||||
"MenuBarOptionsChangeLanguage": "Change Language",
|
||||
"MenuBarShowFileTypes": "Show File Types",
|
||||
"CommonSort": "Sort",
|
||||
"CommonShowNames": "Show Names",
|
||||
"CommonFavorite": "Favorite",
|
||||
@ -457,6 +459,7 @@
|
||||
"MemoryManagerSoftwareTooltip": "Use a software page table for address translation. Highest accuracy but slowest performance.",
|
||||
"MemoryManagerHostTooltip": "Directly map memory in the host address space. Much faster JIT compilation and execution.",
|
||||
"MemoryManagerUnsafeTooltip": "Directly map memory, but do not mask the address within the guest address space before access. Faster, but at the cost of safety. The guest application can access memory from anywhere in Ryujinx, so only run programs you trust with this mode.",
|
||||
"UseHypervisorTooltip": "Use Hypervisor instead of JIT. Greatly improves performance when available, but can be unstable in its current state.",
|
||||
"DRamTooltip": "Utilizes an alternative MemoryMode layout to mimic a Switch development model.\n\nThis is only useful for higher-resolution texture packs or 4k resolution mods. Does NOT improve performance.\n\nLeave OFF if unsure.",
|
||||
"IgnoreMissingServicesTooltip": "Ignores unimplemented Horizon OS services. This may help in bypassing crashes when booting certain games.\n\nLeave OFF if unsure.",
|
||||
"GraphicsBackendThreadingTooltip": "Executes graphics backend commands on a second thread.\n\nSpeeds up shader compilation, reduces stuttering, and improves performance on GPU drivers without multithreading support of their own. Slightly better performance on drivers with multithreading.\n\nSet to AUTO if unsure.",
|
||||
@ -507,7 +510,7 @@
|
||||
"SettingsTabNetwork": "Network",
|
||||
"SettingsTabNetworkConnection": "Network Connection",
|
||||
"SettingsTabCpuCache": "CPU Cache",
|
||||
"SettingsTabCpuMemory": "CPU Memory",
|
||||
"SettingsTabCpuMemory": "CPU Mode",
|
||||
"DialogUpdaterFlatpakNotSupportedMessage": "Please update Ryujinx via FlatHub.",
|
||||
"UpdaterDisabledWarningTitle": "Updater Disabled!",
|
||||
"GameListContextMenuOpenSdModsDirectory": "Open Atmosphere Mods Directory",
|
||||
@ -581,10 +584,10 @@
|
||||
"SelectUpdateDialogTitle": "Select update files",
|
||||
"UserProfileWindowTitle": "User Profiles Manager",
|
||||
"CheatWindowTitle": "Cheats Manager",
|
||||
"DlcWindowTitle": "Downloadable Content Manager",
|
||||
"DlcWindowTitle": "Manage Downloadable Content for {0} ({1})",
|
||||
"UpdateWindowTitle": "Title Update Manager",
|
||||
"CheatWindowHeading": "Cheats Available for {0} [{1}]",
|
||||
"DlcWindowHeading": "{0} Downloadable Content(s) available for {1} ({2})",
|
||||
"DlcWindowHeading": "{0} Downloadable Content(s)",
|
||||
"UserProfilesEditProfile": "Edit Selected",
|
||||
"Cancel": "Cancel",
|
||||
"Save": "Save",
|
||||
@ -624,6 +627,19 @@
|
||||
"Recover": "Recover",
|
||||
"UserProfilesRecoverHeading" : "Saves were found for the following accounts",
|
||||
"UserProfilesRecoverEmptyList": "No profiles to recover",
|
||||
"GraphicsAATooltip": "Applies anti-aliasing to the game render",
|
||||
"GraphicsAALabel": "Anti-Aliasing:",
|
||||
"GraphicsScalingFilterLabel": "Scaling Filter:",
|
||||
"GraphicsScalingFilterTooltip": "Enables Framebuffer Scaling",
|
||||
"GraphicsScalingFilterLevelLabel": "Level",
|
||||
"GraphicsScalingFilterLevelTooltip": "Set Scaling Filter Level",
|
||||
"SmaaLow": "SMAA Low",
|
||||
"SmaaMedium": "SMAA Medium",
|
||||
"SmaaHigh": "SMAA High",
|
||||
"SmaaUltra": "SMAA Ultra",
|
||||
"UserEditorTitle" : "Edit User",
|
||||
"UserEditorTitleCreate" : "Create User"
|
||||
"UserEditorTitleCreate" : "Create User",
|
||||
"SettingsTabNetworkInterface": "Network Interface:",
|
||||
"NetworkInterfaceTooltip": "The network interface used for LAN features",
|
||||
"NetworkInterfaceDefault": "Default"
|
||||
}
|
||||
|
@ -13,12 +13,14 @@ using LibHac.Tools.Fs;
|
||||
using LibHac.Tools.FsSystem;
|
||||
using LibHac.Tools.FsSystem.NcaUtils;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.UI.Controls;
|
||||
using Ryujinx.Ava.UI.Helpers;
|
||||
using Ryujinx.Ava.UI.Windows;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using Ryujinx.HLE.HOS;
|
||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||
using Ryujinx.Ui.App.Common;
|
||||
using Ryujinx.Ui.Common.Helper;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
@ -151,25 +153,17 @@ namespace Ryujinx.Ava.Common
|
||||
string destination = await folderDialog.ShowAsync(_owner);
|
||||
var cancellationToken = new CancellationTokenSource();
|
||||
|
||||
UpdateWaitWindow waitingDialog = new(
|
||||
LocaleManager.Instance[LocaleKeys.DialogNcaExtractionTitle],
|
||||
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogNcaExtractionMessage, ncaSectionType, Path.GetFileName(titleFilePath)),
|
||||
cancellationToken);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(destination))
|
||||
{
|
||||
Thread extractorThread = new(() =>
|
||||
{
|
||||
Dispatcher.UIThread.Post(async () =>
|
||||
{
|
||||
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(
|
||||
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogNcaExtractionMessage, ncaSectionType, Path.GetFileName(titleFilePath)),
|
||||
"",
|
||||
"",
|
||||
LocaleManager.Instance[LocaleKeys.InputDialogCancel],
|
||||
LocaleManager.Instance[LocaleKeys.DialogNcaExtractionTitle]);
|
||||
Dispatcher.UIThread.Post(waitingDialog.Show);
|
||||
|
||||
if (result == UserResult.Cancel)
|
||||
{
|
||||
cancellationToken.Cancel();
|
||||
}
|
||||
});
|
||||
|
||||
using FileStream file = new(titleFilePath, FileMode.Open, FileAccess.Read);
|
||||
|
||||
Nca mainNca = null;
|
||||
@ -193,7 +187,7 @@ namespace Ryujinx.Ava.Common
|
||||
{
|
||||
using var ncaFile = new UniqueRef<IFile>();
|
||||
|
||||
pfs.OpenFile(ref ncaFile.Ref(), fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||
pfs.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||
|
||||
Nca nca = new(_virtualFileSystem.KeySet, ncaFile.Get.AsStorage());
|
||||
if (nca.Header.ContentType == NcaContentType.Program)
|
||||
@ -221,13 +215,15 @@ namespace Ryujinx.Ava.Common
|
||||
|
||||
Dispatcher.UIThread.InvokeAsync(async () =>
|
||||
{
|
||||
waitingDialog.Close();
|
||||
|
||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogNcaExtractionMainNcaNotFoundErrorMessage]);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
(Nca updatePatchNca, _) = ApplicationLoader.GetGameUpdateData(_virtualFileSystem, mainNca.Header.TitleId.ToString("x16"), programIndex, out _);
|
||||
(Nca updatePatchNca, _) = ApplicationLibrary.GetGameUpdateData(_virtualFileSystem, mainNca.Header.TitleId.ToString("x16"), programIndex, out _);
|
||||
if (updatePatchNca != null)
|
||||
{
|
||||
patchNca = updatePatchNca;
|
||||
@ -249,8 +245,8 @@ namespace Ryujinx.Ava.Common
|
||||
using var uniqueSourceFs = new UniqueRef<IFileSystem>(ncaFileSystem);
|
||||
using var uniqueOutputFs = new UniqueRef<IFileSystem>(new LocalFileSystem(destination));
|
||||
|
||||
fsClient.Register(source.ToU8Span(), ref uniqueSourceFs.Ref());
|
||||
fsClient.Register(output.ToU8Span(), ref uniqueOutputFs.Ref());
|
||||
fsClient.Register(source.ToU8Span(), ref uniqueSourceFs.Ref);
|
||||
fsClient.Register(output.ToU8Span(), ref uniqueOutputFs.Ref);
|
||||
|
||||
(Result? resultCode, bool canceled) = CopyDirectory(fsClient, $"{source}:/", $"{output}:/", cancellationToken.Token);
|
||||
|
||||
@ -262,11 +258,15 @@ namespace Ryujinx.Ava.Common
|
||||
|
||||
Dispatcher.UIThread.InvokeAsync(async () =>
|
||||
{
|
||||
waitingDialog.Close();
|
||||
|
||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogNcaExtractionCheckLogErrorMessage]);
|
||||
});
|
||||
}
|
||||
else if (resultCode.Value.IsSuccess())
|
||||
{
|
||||
Dispatcher.UIThread.Post(waitingDialog.Close);
|
||||
|
||||
NotificationHelper.Show(
|
||||
LocaleManager.Instance[LocaleKeys.DialogNcaExtractionTitle],
|
||||
$"{titleName}\n\n{LocaleManager.Instance[LocaleKeys.DialogNcaExtractionSuccessMessage]}",
|
||||
@ -283,6 +283,8 @@ namespace Ryujinx.Ava.Common
|
||||
|
||||
Dispatcher.UIThread.InvokeAsync(async () =>
|
||||
{
|
||||
waitingDialog.Close();
|
||||
|
||||
await ContentDialogHelper.CreateErrorDialog(ex.Message);
|
||||
});
|
||||
}
|
||||
|
@ -130,7 +130,7 @@ namespace Ryujinx.Ava.Common.Locale
|
||||
{
|
||||
var localeStrings = new Dictionary<LocaleKeys, string>();
|
||||
string languageJson = EmbeddedResources.ReadAllText($"Ryujinx.Ava/Assets/Locales/{languageCode}.json");
|
||||
var strings = JsonHelper.Deserialize<Dictionary<string, string>>(languageJson);
|
||||
var strings = JsonHelper.Deserialize(languageJson, CommonJsonContext.Default.StringDictionary);
|
||||
|
||||
foreach (var item in strings)
|
||||
{
|
||||
|
@ -4,13 +4,14 @@ using FluentAvalonia.UI.Controls;
|
||||
using ICSharpCode.SharpZipLib.GZip;
|
||||
using ICSharpCode.SharpZipLib.Tar;
|
||||
using ICSharpCode.SharpZipLib.Zip;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Ryujinx.Ava;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.UI.Helpers;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common.Utilities;
|
||||
using Ryujinx.Ui.Common.Helper;
|
||||
using Ryujinx.Ui.Common.Models.Github;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
@ -21,6 +22,7 @@ using System.Net.Http;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@ -30,6 +32,7 @@ namespace Ryujinx.Modules
|
||||
internal static class Updater
|
||||
{
|
||||
private const string GitHubApiURL = "https://api.github.com";
|
||||
private static readonly GithubReleasesJsonSerializerContext SerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
||||
|
||||
private static readonly string HomeDir = AppDomain.CurrentDomain.BaseDirectory;
|
||||
private static readonly string UpdateDir = Path.Combine(Path.GetTempPath(), "Ryujinx", "update");
|
||||
@ -57,7 +60,7 @@ namespace Ryujinx.Modules
|
||||
// Detect current platform
|
||||
if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
_platformExt = "osx_x64.zip";
|
||||
_platformExt = "macos_universal.app.tar.gz";
|
||||
}
|
||||
else if (OperatingSystem.IsWindows())
|
||||
{
|
||||
@ -98,22 +101,16 @@ namespace Ryujinx.Modules
|
||||
|
||||
string buildInfoURL = $"{GitHubApiURL}/repos/{ReleaseInformation.ReleaseChannelOwner}/{ReleaseInformation.ReleaseChannelRepo}/releases/latest";
|
||||
string fetchedJson = await jsonClient.GetStringAsync(buildInfoURL);
|
||||
JObject jsonRoot = JObject.Parse(fetchedJson);
|
||||
JToken assets = jsonRoot["assets"];
|
||||
var fetched = JsonHelper.Deserialize(fetchedJson, SerializerContext.GithubReleasesJsonResponse);
|
||||
_buildVer = fetched.Name;
|
||||
|
||||
_buildVer = (string)jsonRoot["name"];
|
||||
|
||||
foreach (JToken asset in assets)
|
||||
foreach (var asset in fetched.Assets)
|
||||
{
|
||||
string assetName = (string)asset["name"];
|
||||
string assetState = (string)asset["state"];
|
||||
string downloadURL = (string)asset["browser_download_url"];
|
||||
|
||||
if (assetName.StartsWith("test-ava-ryujinx") && assetName.EndsWith(_platformExt))
|
||||
if (asset.Name.StartsWith("test-ava-ryujinx") && asset.Name.EndsWith(_platformExt))
|
||||
{
|
||||
_buildUrl = downloadURL;
|
||||
_buildUrl = asset.BrowserDownloadUrl;
|
||||
|
||||
if (assetState != "uploaded")
|
||||
if (asset.State != "uploaded")
|
||||
{
|
||||
if (showVersionUpToDate)
|
||||
{
|
||||
@ -132,8 +129,8 @@ namespace Ryujinx.Modules
|
||||
}
|
||||
}
|
||||
|
||||
// If build not done, assume no new update are availaible.
|
||||
if (_buildUrl == null)
|
||||
// If build not done, assume no new update are available.
|
||||
if (_buildUrl is null)
|
||||
{
|
||||
if (showVersionUpToDate)
|
||||
{
|
||||
@ -240,13 +237,13 @@ namespace Ryujinx.Modules
|
||||
{
|
||||
HttpClient result = new();
|
||||
|
||||
// Required by GitHub to interract with APIs.
|
||||
// Required by GitHub to interact with APIs.
|
||||
result.DefaultRequestHeaders.Add("User-Agent", "Ryujinx-Updater/1.0.0");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static async void UpdateRyujinx(Window parent, string downloadUrl)
|
||||
private static async void UpdateRyujinx(Window parent, string downloadUrl)
|
||||
{
|
||||
_updateSuccessful = false;
|
||||
|
||||
@ -286,24 +283,40 @@ namespace Ryujinx.Modules
|
||||
|
||||
if (_updateSuccessful)
|
||||
{
|
||||
var shouldRestart = await ContentDialogHelper.CreateChoiceDialog(LocaleManager.Instance[LocaleKeys.RyujinxUpdater],
|
||||
LocaleManager.Instance[LocaleKeys.DialogUpdaterCompleteMessage],
|
||||
LocaleManager.Instance[LocaleKeys.DialogUpdaterRestartMessage]);
|
||||
bool shouldRestart = true;
|
||||
|
||||
if (!OperatingSystem.IsMacOS())
|
||||
{
|
||||
shouldRestart = await ContentDialogHelper.CreateChoiceDialog(LocaleManager.Instance[LocaleKeys.RyujinxUpdater],
|
||||
LocaleManager.Instance[LocaleKeys.DialogUpdaterCompleteMessage],
|
||||
LocaleManager.Instance[LocaleKeys.DialogUpdaterRestartMessage]);
|
||||
}
|
||||
|
||||
if (shouldRestart)
|
||||
{
|
||||
List<string> arguments = CommandLineState.Arguments.ToList();
|
||||
string ryuName = Path.GetFileName(Environment.ProcessPath);
|
||||
string ryuExe = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ryuName);
|
||||
string executableDirectory = AppDomain.CurrentDomain.BaseDirectory;
|
||||
string executablePath = Path.Combine(executableDirectory, ryuName);
|
||||
|
||||
if (!Path.Exists(ryuExe))
|
||||
if (!Path.Exists(executablePath))
|
||||
{
|
||||
ryuExe = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, OperatingSystem.IsWindows() ? "Ryujinx.exe" : "Ryujinx");
|
||||
executablePath = Path.Combine(executableDirectory, OperatingSystem.IsWindows() ? "Ryujinx.exe" : "Ryujinx");
|
||||
}
|
||||
|
||||
SetFileExecutable(ryuExe);
|
||||
// On macOS we perform the update at relaunch.
|
||||
if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
string baseBundlePath = Path.GetFullPath(Path.Combine(executableDirectory, "..", ".."));
|
||||
string newBundlePath = Path.Combine(UpdateDir, "Ryujinx.app");
|
||||
string updaterScriptPath = Path.Combine(newBundlePath, "Contents", "Resources", "updater.sh");
|
||||
string currentPid = Process.GetCurrentProcess().Id.ToString();
|
||||
|
||||
Process.Start(ryuExe, CommandLineState.Arguments);
|
||||
executablePath = "/bin/bash";
|
||||
arguments.InsertRange(0, new List<string> { updaterScriptPath, baseBundlePath, newBundlePath, currentPid });
|
||||
}
|
||||
|
||||
Process.Start(executablePath, arguments);
|
||||
Environment.Exit(0);
|
||||
}
|
||||
}
|
||||
@ -383,6 +396,15 @@ namespace Ryujinx.Modules
|
||||
|
||||
File.WriteAllBytes(updateFile, mergedFileBytes);
|
||||
|
||||
// On macOS, ensure that we remove the quarantine bit to prevent Gatekeeper from blocking execution.
|
||||
if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
using (Process xattrProcess = Process.Start("xattr", new List<string> { "-d", "com.apple.quarantine", updateFile }))
|
||||
{
|
||||
xattrProcess.WaitForExit();
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
InstallUpdate(taskDialog, updateFile);
|
||||
@ -408,9 +430,9 @@ namespace Ryujinx.Modules
|
||||
Logger.Warning?.Print(LogClass.Application, ex.Message);
|
||||
Logger.Warning?.Print(LogClass.Application, "Multi-Threaded update failed, falling back to single-threaded updater.");
|
||||
|
||||
for (int j = 0; j < webClients.Count; j++)
|
||||
foreach (WebClient webClient in webClients)
|
||||
{
|
||||
webClients[j].CancelAsync();
|
||||
webClient.CancelAsync();
|
||||
}
|
||||
|
||||
DoUpdateWithSingleThread(taskDialog, downloadUrl, updateFile);
|
||||
@ -472,19 +494,74 @@ namespace Ryujinx.Modules
|
||||
worker.Start();
|
||||
}
|
||||
|
||||
private static void SetFileExecutable(string path)
|
||||
[SupportedOSPlatform("linux")]
|
||||
[SupportedOSPlatform("macos")]
|
||||
private static void ExtractTarGzipFile(TaskDialog taskDialog, string archivePath, string outputDirectoryPath)
|
||||
{
|
||||
const UnixFileMode ExecutableFileMode = UnixFileMode.UserExecute |
|
||||
UnixFileMode.UserWrite |
|
||||
UnixFileMode.UserRead |
|
||||
UnixFileMode.GroupRead |
|
||||
UnixFileMode.GroupWrite |
|
||||
UnixFileMode.OtherRead |
|
||||
UnixFileMode.OtherWrite;
|
||||
using Stream inStream = File.OpenRead(archivePath);
|
||||
using GZipInputStream gzipStream = new(inStream);
|
||||
using TarInputStream tarStream = new(gzipStream, Encoding.ASCII);
|
||||
|
||||
if (!OperatingSystem.IsWindows() && File.Exists(path))
|
||||
TarEntry tarEntry;
|
||||
|
||||
while ((tarEntry = tarStream.GetNextEntry()) is not null)
|
||||
{
|
||||
File.SetUnixFileMode(path, ExecutableFileMode);
|
||||
if (tarEntry.IsDirectory)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string outPath = Path.Combine(outputDirectoryPath, tarEntry.Name);
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(outPath));
|
||||
|
||||
using (FileStream outStream = File.OpenWrite(outPath))
|
||||
{
|
||||
tarStream.CopyEntryContents(outStream);
|
||||
}
|
||||
|
||||
File.SetUnixFileMode(outPath, (UnixFileMode)tarEntry.TarHeader.Mode);
|
||||
File.SetLastWriteTime(outPath, DateTime.SpecifyKind(tarEntry.ModTime, DateTimeKind.Utc));
|
||||
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
if (tarEntry is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
taskDialog.SetProgressBarState(GetPercentage(tarEntry.Size, inStream.Length), TaskDialogProgressState.Normal);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static void ExtractZipFile(TaskDialog taskDialog, string archivePath, string outputDirectoryPath)
|
||||
{
|
||||
using Stream inStream = File.OpenRead(archivePath);
|
||||
using ZipFile zipFile = new(inStream);
|
||||
|
||||
double count = 0;
|
||||
foreach (ZipEntry zipEntry in zipFile)
|
||||
{
|
||||
count++;
|
||||
if (zipEntry.IsDirectory) continue;
|
||||
|
||||
string outPath = Path.Combine(outputDirectoryPath, zipEntry.Name);
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(outPath));
|
||||
|
||||
using (Stream zipStream = zipFile.GetInputStream(zipEntry))
|
||||
using (FileStream outStream = File.OpenWrite(outPath))
|
||||
{
|
||||
zipStream.CopyTo(outStream);
|
||||
}
|
||||
|
||||
File.SetLastWriteTime(outPath, DateTime.SpecifyKind(zipEntry.DateTime, DateTimeKind.Utc));
|
||||
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
taskDialog.SetProgressBarState(GetPercentage(count, zipFile.Count), TaskDialogProgressState.Normal);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -494,73 +571,21 @@ namespace Ryujinx.Modules
|
||||
taskDialog.SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterExtracting];
|
||||
taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal);
|
||||
|
||||
if (OperatingSystem.IsLinux())
|
||||
await Task.Run(() =>
|
||||
{
|
||||
using Stream inStream = File.OpenRead(updateFile);
|
||||
using GZipInputStream gzipStream = new(inStream);
|
||||
using TarInputStream tarStream = new(gzipStream, Encoding.ASCII);
|
||||
|
||||
await Task.Run(() =>
|
||||
if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
|
||||
{
|
||||
TarEntry tarEntry;
|
||||
while ((tarEntry = tarStream.GetNextEntry()) != null)
|
||||
{
|
||||
if (tarEntry.IsDirectory) continue;
|
||||
|
||||
string outPath = Path.Combine(UpdateDir, tarEntry.Name);
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(outPath));
|
||||
|
||||
using (FileStream outStream = File.OpenWrite(outPath))
|
||||
{
|
||||
tarStream.CopyEntryContents(outStream);
|
||||
}
|
||||
|
||||
File.SetLastWriteTime(outPath, DateTime.SpecifyKind(tarEntry.ModTime, DateTimeKind.Utc));
|
||||
|
||||
TarEntry entry = tarEntry;
|
||||
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
taskDialog.SetProgressBarState(GetPercentage(entry.Size, inStream.Length), TaskDialogProgressState.Normal);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
taskDialog.SetProgressBarState(100, TaskDialogProgressState.Normal);
|
||||
}
|
||||
else
|
||||
{
|
||||
using Stream inStream = File.OpenRead(updateFile);
|
||||
using ZipFile zipFile = new(inStream);
|
||||
|
||||
await Task.Run(() =>
|
||||
ExtractTarGzipFile(taskDialog, updateFile, UpdateDir);
|
||||
}
|
||||
else if (OperatingSystem.IsWindows())
|
||||
{
|
||||
double count = 0;
|
||||
foreach (ZipEntry zipEntry in zipFile)
|
||||
{
|
||||
count++;
|
||||
if (zipEntry.IsDirectory) continue;
|
||||
|
||||
string outPath = Path.Combine(UpdateDir, zipEntry.Name);
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(outPath));
|
||||
|
||||
using (Stream zipStream = zipFile.GetInputStream(zipEntry))
|
||||
using (FileStream outStream = File.OpenWrite(outPath))
|
||||
{
|
||||
zipStream.CopyTo(outStream);
|
||||
}
|
||||
|
||||
File.SetLastWriteTime(outPath, DateTime.SpecifyKind(zipEntry.DateTime, DateTimeKind.Utc));
|
||||
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
taskDialog.SetProgressBarState(GetPercentage(count, zipFile.Count), TaskDialogProgressState.Normal);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
ExtractZipFile(taskDialog, updateFile, UpdateDir);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
});
|
||||
|
||||
// Delete downloaded zip
|
||||
File.Delete(updateFile);
|
||||
@ -570,40 +595,42 @@ namespace Ryujinx.Modules
|
||||
taskDialog.SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterRenaming];
|
||||
taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal);
|
||||
|
||||
// Replace old files
|
||||
await Task.Run(() =>
|
||||
// NOTE: On macOS, replacement is delayed to the restart phase.
|
||||
if (!OperatingSystem.IsMacOS())
|
||||
{
|
||||
double count = 0;
|
||||
foreach (string file in allFiles)
|
||||
// Replace old files
|
||||
await Task.Run(() =>
|
||||
{
|
||||
count++;
|
||||
try
|
||||
double count = 0;
|
||||
foreach (string file in allFiles)
|
||||
{
|
||||
File.Move(file, file + ".ryuold");
|
||||
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
count++;
|
||||
try
|
||||
{
|
||||
taskDialog.SetProgressBarState(GetPercentage(count, allFiles.Count), TaskDialogProgressState.Normal);
|
||||
});
|
||||
}
|
||||
catch
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application, LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.UpdaterRenameFailed, file));
|
||||
}
|
||||
}
|
||||
File.Move(file, file + ".ryuold");
|
||||
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
taskDialog.SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterAddingFiles];
|
||||
taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal);
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
taskDialog.SetProgressBarState(GetPercentage(count, allFiles.Count), TaskDialogProgressState.Normal);
|
||||
});
|
||||
}
|
||||
catch
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application, LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.UpdaterRenameFailed, file));
|
||||
}
|
||||
}
|
||||
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
taskDialog.SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterAddingFiles];
|
||||
taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal);
|
||||
});
|
||||
|
||||
MoveAllFilesOver(UpdatePublishDir, HomeDir, taskDialog);
|
||||
});
|
||||
|
||||
MoveAllFilesOver(UpdatePublishDir, HomeDir, taskDialog);
|
||||
});
|
||||
|
||||
Directory.Delete(UpdateDir, true);
|
||||
|
||||
SetFileExecutable(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Ryujinx"));
|
||||
Directory.Delete(UpdateDir, true);
|
||||
}
|
||||
|
||||
_updateSuccessful = true;
|
||||
|
||||
@ -613,7 +640,7 @@ namespace Ryujinx.Modules
|
||||
public static bool CanUpdate(bool showWarnings)
|
||||
{
|
||||
#if !DISABLE_UPDATER
|
||||
if (RuntimeInformation.OSArchitecture != Architecture.X64)
|
||||
if (RuntimeInformation.OSArchitecture != Architecture.X64 && !OperatingSystem.IsMacOS())
|
||||
{
|
||||
if (showWarnings)
|
||||
{
|
||||
@ -686,7 +713,7 @@ namespace Ryujinx.Modules
|
||||
#endif
|
||||
}
|
||||
|
||||
// NOTE: This method should always reflect the latest build layout.s
|
||||
// NOTE: This method should always reflect the latest build layout.
|
||||
private static IEnumerable<string> EnumerateFilesToDelete()
|
||||
{
|
||||
var files = Directory.EnumerateFiles(HomeDir); // All files directly in base dir.
|
||||
@ -703,7 +730,7 @@ namespace Ryujinx.Modules
|
||||
}
|
||||
}
|
||||
|
||||
return files;
|
||||
return files.Where(f => !new FileInfo(f).Attributes.HasFlag(FileAttributes.Hidden | FileAttributes.System));
|
||||
}
|
||||
|
||||
private static void MoveAllFilesOver(string root, string dest, TaskDialog taskDialog)
|
||||
|
@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<RuntimeIdentifiers>win10-x64;osx-x64;linux-x64</RuntimeIdentifiers>
|
||||
@ -6,11 +6,16 @@
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<Version>1.0.0-dirty</Version>
|
||||
<DefineConstants Condition=" '$(ExtraDefineConstants)' != '' ">$(DefineConstants);$(ExtraDefineConstants)</DefineConstants>
|
||||
<SigningCertificate Condition=" '$(SigningCertificate)' == '' ">-</SigningCertificate>
|
||||
<RootNamespace>Ryujinx.Ava</RootNamespace>
|
||||
<ApplicationIcon>Ryujinx.ico</ApplicationIcon>
|
||||
<TieredPGO>true</TieredPGO>
|
||||
</PropertyGroup>
|
||||
|
||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent" Condition="$([MSBuild]::IsOSPlatform('OSX'))">
|
||||
<Exec Command="codesign --entitlements '$(ProjectDir)..\distribution\macos\entitlements.xml' -f --deep -s $(SigningCertificate) '$(TargetDir)$(TargetName)'" />
|
||||
</Target>
|
||||
|
||||
<PropertyGroup Condition="'$(RuntimeIdentifier)' != ''">
|
||||
<PublishSingleFile>true</PublishSingleFile>
|
||||
<PublishTrimmed>true</PublishTrimmed>
|
||||
|
@ -53,6 +53,8 @@ namespace Ryujinx.Ava.UI.Applet
|
||||
|
||||
bool opened = false;
|
||||
|
||||
_parent.Activate();
|
||||
|
||||
UserResult response = await ContentDialogHelper.ShowDeferredContentDialog(_parent,
|
||||
title,
|
||||
message,
|
||||
|
@ -48,6 +48,7 @@
|
||||
Grid.Column="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
Focusable="True"
|
||||
KeyUp="Message_KeyUp"
|
||||
Text="{Binding Message}"
|
||||
TextInput="Message_TextInput"
|
||||
@ -61,4 +62,4 @@
|
||||
HorizontalAlignment="Stretch"
|
||||
TextWrapping="Wrap" />
|
||||
</Grid>
|
||||
</UserControl>
|
||||
</UserControl>
|
||||
|
@ -23,10 +23,11 @@ namespace Ryujinx.Ava.UI.Controls
|
||||
|
||||
private ContentDialog _host;
|
||||
|
||||
public SwkbdAppletDialog(string mainText, string secondaryText, string placeholder)
|
||||
public SwkbdAppletDialog(string mainText, string secondaryText, string placeholder, string message)
|
||||
{
|
||||
MainText = mainText;
|
||||
SecondaryText = secondaryText;
|
||||
Message = message ?? "";
|
||||
DataContext = this;
|
||||
_placeholder = placeholder;
|
||||
InitializeComponent();
|
||||
@ -44,6 +45,13 @@ namespace Ryujinx.Ava.UI.Controls
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
protected override void OnGotFocus(GotFocusEventArgs e)
|
||||
{
|
||||
// FIXME: This does not work. Might be a bug in Avalonia with DialogHost
|
||||
// Currently focus will be redirected to the overlay window instead.
|
||||
Input.Focus();
|
||||
}
|
||||
|
||||
public string Message { get; set; } = "";
|
||||
public string MainText { get; set; } = "";
|
||||
public string SecondaryText { get; set; } = "";
|
||||
@ -54,31 +62,10 @@ namespace Ryujinx.Ava.UI.Controls
|
||||
|
||||
UserResult result = UserResult.Cancel;
|
||||
|
||||
SwkbdAppletDialog content = new SwkbdAppletDialog(args.HeaderText, args.SubtitleText, args.GuideText)
|
||||
{
|
||||
Message = args.InitialText ?? ""
|
||||
};
|
||||
SwkbdAppletDialog content = new SwkbdAppletDialog(args.HeaderText, args.SubtitleText, args.GuideText, args.InitialText);
|
||||
|
||||
string input = string.Empty;
|
||||
|
||||
var overlay = new ContentDialogOverlayWindow()
|
||||
{
|
||||
Height = window.Bounds.Height,
|
||||
Width = window.Bounds.Width,
|
||||
Position = window.PointToScreen(new Point())
|
||||
};
|
||||
|
||||
window.PositionChanged += OverlayOnPositionChanged;
|
||||
|
||||
void OverlayOnPositionChanged(object sender, PixelPointEventArgs e)
|
||||
{
|
||||
overlay.Position = window.PointToScreen(new Point());
|
||||
}
|
||||
|
||||
contentDialog = overlay.ContentDialog;
|
||||
|
||||
bool opened = false;
|
||||
|
||||
content.SetInputLengthValidation(args.StringLengthMin, args.StringLengthMax);
|
||||
|
||||
content._host = contentDialog;
|
||||
@ -99,25 +86,7 @@ namespace Ryujinx.Ava.UI.Controls
|
||||
};
|
||||
contentDialog.Closed += handler;
|
||||
|
||||
overlay.Opened += OverlayOnActivated;
|
||||
|
||||
async void OverlayOnActivated(object sender, EventArgs e)
|
||||
{
|
||||
if (opened)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
opened = true;
|
||||
|
||||
overlay.Position = window.PointToScreen(new Point());
|
||||
|
||||
await contentDialog.ShowAsync(overlay);
|
||||
contentDialog.Closed -= handler;
|
||||
overlay.Close();
|
||||
};
|
||||
|
||||
await overlay.ShowDialog(window);
|
||||
await ContentDialogHelper.ShowAsync(contentDialog);
|
||||
|
||||
return (result, input);
|
||||
}
|
||||
|
@ -1,32 +0,0 @@
|
||||
<UserControl
|
||||
x:Class="Ryujinx.Ava.UI.Controls.InputDialog"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d"
|
||||
Focusable="True">
|
||||
<Grid
|
||||
Margin="5,10,5,5"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock HorizontalAlignment="Center" Text="{Binding Message}" />
|
||||
<TextBox
|
||||
Grid.Row="1"
|
||||
Width="300"
|
||||
Margin="10"
|
||||
HorizontalAlignment="Center"
|
||||
MaxLength="{Binding MaxLength}"
|
||||
Text="{Binding Input, Mode=TwoWay}" />
|
||||
<TextBlock
|
||||
Grid.Row="2"
|
||||
Margin="5,5,5,10"
|
||||
HorizontalAlignment="Center"
|
||||
Text="{Binding SubMessage}" />
|
||||
</Grid>
|
||||
</UserControl>
|
@ -1,57 +0,0 @@
|
||||
using Avalonia.Controls;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.UI.Helpers;
|
||||
using Ryujinx.Ava.UI.Models;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Controls
|
||||
{
|
||||
public partial class InputDialog : UserControl
|
||||
{
|
||||
public string Message { get; set; }
|
||||
public string Input { get; set; }
|
||||
public string SubMessage { get; set; }
|
||||
|
||||
public uint MaxLength { get; }
|
||||
|
||||
public InputDialog(string message, string input = "", string subMessage = "", uint maxLength = int.MaxValue)
|
||||
{
|
||||
Message = message;
|
||||
Input = input;
|
||||
SubMessage = subMessage;
|
||||
MaxLength = maxLength;
|
||||
|
||||
DataContext = this;
|
||||
}
|
||||
|
||||
public InputDialog()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public static async Task<(UserResult Result, string Input)> ShowInputDialog(string title, string message,
|
||||
string input = "", string subMessage = "", uint maxLength = int.MaxValue)
|
||||
{
|
||||
UserResult result = UserResult.Cancel;
|
||||
|
||||
InputDialog content = new InputDialog(message, input, subMessage, maxLength);
|
||||
ContentDialog contentDialog = new ContentDialog
|
||||
{
|
||||
Title = title,
|
||||
PrimaryButtonText = LocaleManager.Instance[LocaleKeys.InputDialogOk],
|
||||
SecondaryButtonText = "",
|
||||
CloseButtonText = LocaleManager.Instance[LocaleKeys.InputDialogCancel],
|
||||
Content = content,
|
||||
PrimaryButtonCommand = MiniCommand.Create(() =>
|
||||
{
|
||||
result = UserResult.Ok;
|
||||
input = content.Input;
|
||||
})
|
||||
};
|
||||
await contentDialog.ShowAsync();
|
||||
|
||||
return (result, input);
|
||||
}
|
||||
}
|
||||
}
|
@ -16,9 +16,9 @@ using Ryujinx.Ava.UI.Views.User;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using UserProfile = Ryujinx.Ava.UI.Models.UserProfile;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Controls
|
||||
@ -121,7 +121,7 @@ namespace Ryujinx.Ava.UI.Controls
|
||||
|
||||
using var saveDataIterator = new UniqueRef<SaveDataIterator>();
|
||||
|
||||
HorizonClient.Fs.OpenSaveDataIterator(ref saveDataIterator.Ref(), SaveDataSpaceId.User, in saveDataFilter).ThrowIfFailure();
|
||||
HorizonClient.Fs.OpenSaveDataIterator(ref saveDataIterator.Ref, SaveDataSpaceId.User, in saveDataFilter).ThrowIfFailure();
|
||||
|
||||
Span<SaveDataInfo> saveDataInfo = stackalloc SaveDataInfo[10];
|
||||
|
||||
|
@ -1,15 +1,26 @@
|
||||
using Avalonia.Controls;
|
||||
using Ryujinx.Ava.UI.Windows;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Controls
|
||||
{
|
||||
public partial class UpdateWaitWindow : StyleableWindow
|
||||
{
|
||||
public UpdateWaitWindow(string primaryText, string secondaryText, CancellationTokenSource cancellationToken) : this(primaryText, secondaryText)
|
||||
{
|
||||
SystemDecorations = SystemDecorations.Full;
|
||||
ShowInTaskbar = true;
|
||||
|
||||
Closing += (_, _) => cancellationToken.Cancel();
|
||||
}
|
||||
|
||||
public UpdateWaitWindow(string primaryText, string secondaryText) : this()
|
||||
{
|
||||
PrimaryText.Text = primaryText;
|
||||
SecondaryText.Text = secondaryText;
|
||||
WindowStartupLocation = WindowStartupLocation.CenterOwner;
|
||||
SystemDecorations = SystemDecorations.BorderOnly;
|
||||
ShowInTaskbar = false;
|
||||
}
|
||||
|
||||
public UpdateWaitWindow()
|
||||
|
@ -27,7 +27,6 @@ namespace Ryujinx.Ava.UI.Helpers
|
||||
string closeButton,
|
||||
UserResult primaryButtonResult = UserResult.Ok,
|
||||
ManualResetEvent deferResetEvent = null,
|
||||
Func<Window, Task> doWhileDeferred = null,
|
||||
TypedEventHandler<ContentDialog, ContentDialogButtonClickEventArgs> deferCloseAction = null)
|
||||
{
|
||||
UserResult result = UserResult.None;
|
||||
@ -78,12 +77,11 @@ namespace Ryujinx.Ava.UI.Helpers
|
||||
int iconSymbol,
|
||||
UserResult primaryButtonResult = UserResult.Ok,
|
||||
ManualResetEvent deferResetEvent = null,
|
||||
Func<Window, Task> doWhileDeferred = null,
|
||||
TypedEventHandler<ContentDialog, ContentDialogButtonClickEventArgs> deferCloseAction = null)
|
||||
{
|
||||
Grid content = CreateTextDialogContent(primaryText, secondaryText, iconSymbol);
|
||||
|
||||
return await ShowContentDialog(title, content, primaryButton, secondaryButton, closeButton, primaryButtonResult, deferResetEvent, doWhileDeferred, deferCloseAction);
|
||||
return await ShowContentDialog(title, content, primaryButton, secondaryButton, closeButton, primaryButtonResult, deferResetEvent, deferCloseAction);
|
||||
}
|
||||
|
||||
public async static Task<UserResult> ShowDeferredContentDialog(
|
||||
@ -111,7 +109,6 @@ namespace Ryujinx.Ava.UI.Helpers
|
||||
iconSymbol,
|
||||
primaryButton == LocaleManager.Instance[LocaleKeys.InputDialogYes] ? UserResult.Yes : UserResult.Ok,
|
||||
deferResetEvent,
|
||||
doWhileDeferred,
|
||||
DeferClose);
|
||||
|
||||
async void DeferClose(ContentDialog sender, ContentDialogButtonClickEventArgs args)
|
||||
@ -236,11 +233,6 @@ namespace Ryujinx.Ava.UI.Helpers
|
||||
primaryButtonResult);
|
||||
}
|
||||
|
||||
internal static UpdateWaitWindow CreateWaitingDialog(string mainText, string secondaryText)
|
||||
{
|
||||
return new(mainText, secondaryText);
|
||||
}
|
||||
|
||||
internal static async Task CreateUpdaterInfoDialog(string primary, string secondaryText)
|
||||
{
|
||||
await ShowTextDialog(
|
||||
@ -319,28 +311,6 @@ namespace Ryujinx.Ava.UI.Helpers
|
||||
LocaleManager.Instance[LocaleKeys.DialogExitSubMessage]);
|
||||
}
|
||||
|
||||
internal static async Task<string> CreateInputDialog(
|
||||
string title,
|
||||
string mainText,
|
||||
string subText,
|
||||
uint maxLength = int.MaxValue,
|
||||
string input = "")
|
||||
{
|
||||
var result = await InputDialog.ShowInputDialog(
|
||||
title,
|
||||
mainText,
|
||||
input,
|
||||
subText,
|
||||
maxLength);
|
||||
|
||||
if (result.Result == UserResult.Ok)
|
||||
{
|
||||
return result.Input;
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
public static async Task<ContentDialogResult> ShowAsync(ContentDialog contentDialog)
|
||||
{
|
||||
ContentDialogResult result;
|
||||
|
@ -6,8 +6,8 @@ namespace Ryujinx.Ava.UI.Helpers
|
||||
{
|
||||
using AvaLogger = Avalonia.Logging.Logger;
|
||||
using AvaLogLevel = Avalonia.Logging.LogEventLevel;
|
||||
using RyuLogger = Ryujinx.Common.Logging.Logger;
|
||||
using RyuLogClass = Ryujinx.Common.Logging.LogClass;
|
||||
using RyuLogger = Ryujinx.Common.Logging.Logger;
|
||||
|
||||
internal class LoggerAdapter : Avalonia.Logging.ILogSink
|
||||
{
|
||||
|
@ -1,127 +0,0 @@
|
||||
using System;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Runtime.InteropServices;
|
||||
using Avalonia;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Helpers
|
||||
{
|
||||
public delegate void UpdateBoundsCallbackDelegate(Rect rect);
|
||||
|
||||
[SupportedOSPlatform("macos")]
|
||||
static partial class MetalHelper
|
||||
{
|
||||
private const string LibObjCImport = "/usr/lib/libobjc.A.dylib";
|
||||
|
||||
private struct Selector
|
||||
{
|
||||
public readonly IntPtr NativePtr;
|
||||
|
||||
public unsafe Selector(string value)
|
||||
{
|
||||
int size = System.Text.Encoding.UTF8.GetMaxByteCount(value.Length);
|
||||
byte* data = stackalloc byte[size];
|
||||
|
||||
fixed (char* pValue = value)
|
||||
{
|
||||
System.Text.Encoding.UTF8.GetBytes(pValue, value.Length, data, size);
|
||||
}
|
||||
|
||||
NativePtr = sel_registerName(data);
|
||||
}
|
||||
|
||||
public static implicit operator Selector(string value) => new Selector(value);
|
||||
}
|
||||
|
||||
private static unsafe IntPtr GetClass(string value)
|
||||
{
|
||||
int size = System.Text.Encoding.UTF8.GetMaxByteCount(value.Length);
|
||||
byte* data = stackalloc byte[size];
|
||||
|
||||
fixed (char* pValue = value)
|
||||
{
|
||||
System.Text.Encoding.UTF8.GetBytes(pValue, value.Length, data, size);
|
||||
}
|
||||
|
||||
return objc_getClass(data);
|
||||
}
|
||||
|
||||
private struct NSPoint
|
||||
{
|
||||
public double X;
|
||||
public double Y;
|
||||
|
||||
public NSPoint(double x, double y)
|
||||
{
|
||||
X = x;
|
||||
Y = y;
|
||||
}
|
||||
}
|
||||
|
||||
private struct NSRect
|
||||
{
|
||||
public NSPoint Pos;
|
||||
public NSPoint Size;
|
||||
|
||||
public NSRect(double x, double y, double width, double height)
|
||||
{
|
||||
Pos = new NSPoint(x, y);
|
||||
Size = new NSPoint(width, height);
|
||||
}
|
||||
}
|
||||
|
||||
public static IntPtr GetMetalLayer(out IntPtr nsView, out UpdateBoundsCallbackDelegate updateBounds)
|
||||
{
|
||||
// Create a new CAMetalLayer.
|
||||
IntPtr layerClass = GetClass("CAMetalLayer");
|
||||
IntPtr metalLayer = IntPtr_objc_msgSend(layerClass, "alloc");
|
||||
objc_msgSend(metalLayer, "init");
|
||||
|
||||
// Create a child NSView to render into.
|
||||
IntPtr nsViewClass = GetClass("NSView");
|
||||
IntPtr child = IntPtr_objc_msgSend(nsViewClass, "alloc");
|
||||
objc_msgSend(child, "init", new NSRect(0, 0, 0, 0));
|
||||
|
||||
// Make its renderer our metal layer.
|
||||
objc_msgSend(child, "setWantsLayer:", (byte)1);
|
||||
objc_msgSend(child, "setLayer:", metalLayer);
|
||||
objc_msgSend(metalLayer, "setContentsScale:", Program.DesktopScaleFactor);
|
||||
|
||||
// Ensure the scale factor is up to date.
|
||||
updateBounds = (Rect rect) => {
|
||||
objc_msgSend(metalLayer, "setContentsScale:", Program.DesktopScaleFactor);
|
||||
};
|
||||
|
||||
nsView = child;
|
||||
return metalLayer;
|
||||
}
|
||||
|
||||
public static void DestroyMetalLayer(IntPtr nsView, IntPtr metalLayer)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
[LibraryImport(LibObjCImport)]
|
||||
private static unsafe partial IntPtr sel_registerName(byte* data);
|
||||
|
||||
[LibraryImport(LibObjCImport)]
|
||||
private static unsafe partial IntPtr objc_getClass(byte* data);
|
||||
|
||||
[LibraryImport(LibObjCImport)]
|
||||
private static partial void objc_msgSend(IntPtr receiver, Selector selector);
|
||||
|
||||
[LibraryImport(LibObjCImport)]
|
||||
private static partial void objc_msgSend(IntPtr receiver, Selector selector, byte value);
|
||||
|
||||
[LibraryImport(LibObjCImport)]
|
||||
private static partial void objc_msgSend(IntPtr receiver, Selector selector, IntPtr value);
|
||||
|
||||
[LibraryImport(LibObjCImport)]
|
||||
private static partial void objc_msgSend(IntPtr receiver, Selector selector, NSRect point);
|
||||
|
||||
[LibraryImport(LibObjCImport)]
|
||||
private static partial void objc_msgSend(IntPtr receiver, Selector selector, double value);
|
||||
|
||||
[LibraryImport(LibObjCImport, EntryPoint = "objc_msgSend")]
|
||||
private static partial IntPtr IntPtr_objc_msgSend(IntPtr receiver, Selector selector);
|
||||
}
|
||||
}
|
@ -1,72 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Models
|
||||
{
|
||||
public class Amiibo
|
||||
{
|
||||
public struct AmiiboJson
|
||||
{
|
||||
[JsonPropertyName("amiibo")] public List<AmiiboApi> Amiibo { get; set; }
|
||||
[JsonPropertyName("lastUpdated")] public DateTime LastUpdated { get; set; }
|
||||
}
|
||||
|
||||
public struct AmiiboApi
|
||||
{
|
||||
[JsonPropertyName("name")] public string Name { get; set; }
|
||||
[JsonPropertyName("head")] public string Head { get; set; }
|
||||
[JsonPropertyName("tail")] public string Tail { get; set; }
|
||||
[JsonPropertyName("image")] public string Image { get; set; }
|
||||
[JsonPropertyName("amiiboSeries")] public string AmiiboSeries { get; set; }
|
||||
[JsonPropertyName("character")] public string Character { get; set; }
|
||||
[JsonPropertyName("gameSeries")] public string GameSeries { get; set; }
|
||||
[JsonPropertyName("type")] public string Type { get; set; }
|
||||
|
||||
[JsonPropertyName("release")] public Dictionary<string, string> Release { get; set; }
|
||||
|
||||
[JsonPropertyName("gamesSwitch")] public List<AmiiboApiGamesSwitch> GamesSwitch { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Name;
|
||||
}
|
||||
|
||||
public string GetId()
|
||||
{
|
||||
return Head + Tail;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj is AmiiboApi amiibo)
|
||||
{
|
||||
return amiibo.Head + amiibo.Tail == Head + Tail;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return base.GetHashCode();
|
||||
}
|
||||
}
|
||||
|
||||
public class AmiiboApiGamesSwitch
|
||||
{
|
||||
[JsonPropertyName("amiiboUsage")] public List<AmiiboApiUsage> AmiiboUsage { get; set; }
|
||||
|
||||
[JsonPropertyName("gameID")] public List<string> GameId { get; set; }
|
||||
|
||||
[JsonPropertyName("gameName")] public string GameName { get; set; }
|
||||
}
|
||||
|
||||
public class AmiiboApiUsage
|
||||
{
|
||||
[JsonPropertyName("Usage")] public string Usage { get; set; }
|
||||
|
||||
[JsonPropertyName("write")] public bool Write { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
using Ryujinx.Ava.UI.ViewModels;
|
||||
using System.IO;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Models
|
||||
{
|
||||
@ -21,6 +22,8 @@ namespace Ryujinx.Ava.UI.Models
|
||||
public string ContainerPath { get; }
|
||||
public string FullPath { get; }
|
||||
|
||||
public string FileName => Path.GetFileName(ContainerPath);
|
||||
|
||||
public DownloadableContentModel(string titleId, string containerPath, string fullPath, bool enabled)
|
||||
{
|
||||
TitleId = titleId;
|
||||
|
@ -2,9 +2,9 @@ using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Platform;
|
||||
using Ryujinx.Ava.UI.Helpers;
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Ui.Common.Configuration;
|
||||
using Ryujinx.Ui.Common.Helper;
|
||||
using SPB.Graphics;
|
||||
using SPB.Platform;
|
||||
using SPB.Platform.GLX;
|
||||
@ -30,6 +30,7 @@ namespace Ryujinx.Ava.UI.Renderer
|
||||
protected IntPtr NsView { get; set; }
|
||||
protected IntPtr MetalLayer { get; set; }
|
||||
|
||||
public delegate void UpdateBoundsCallbackDelegate(Rect rect);
|
||||
private UpdateBoundsCallbackDelegate _updateBoundsCallback;
|
||||
|
||||
public event EventHandler<IntPtr> WindowCreated;
|
||||
@ -237,8 +238,29 @@ namespace Ryujinx.Ava.UI.Renderer
|
||||
[SupportedOSPlatform("macos")]
|
||||
IPlatformHandle CreateMacOS()
|
||||
{
|
||||
MetalLayer = MetalHelper.GetMetalLayer(out IntPtr nsView, out _updateBoundsCallback);
|
||||
// Create a new CAMetalLayer.
|
||||
IntPtr layerClass = ObjectiveC.objc_getClass("CAMetalLayer");
|
||||
IntPtr metalLayer = ObjectiveC.IntPtr_objc_msgSend(layerClass, "alloc");
|
||||
ObjectiveC.objc_msgSend(metalLayer, "init");
|
||||
|
||||
// Create a child NSView to render into.
|
||||
IntPtr nsViewClass = ObjectiveC.objc_getClass("NSView");
|
||||
IntPtr child = ObjectiveC.IntPtr_objc_msgSend(nsViewClass, "alloc");
|
||||
ObjectiveC.objc_msgSend(child, "init", new ObjectiveC.NSRect(0, 0, 0, 0));
|
||||
|
||||
// Make its renderer our metal layer.
|
||||
ObjectiveC.objc_msgSend(child, "setWantsLayer:", 1);
|
||||
ObjectiveC.objc_msgSend(child, "setLayer:", metalLayer);
|
||||
ObjectiveC.objc_msgSend(metalLayer, "setContentsScale:", Program.DesktopScaleFactor);
|
||||
|
||||
// Ensure the scale factor is up to date.
|
||||
_updateBoundsCallback = rect =>
|
||||
{
|
||||
ObjectiveC.objc_msgSend(metalLayer, "setContentsScale:", Program.DesktopScaleFactor);
|
||||
};
|
||||
|
||||
IntPtr nsView = child;
|
||||
MetalLayer = metalLayer;
|
||||
NsView = nsView;
|
||||
|
||||
return new PlatformHandle(nsView, "NSView");
|
||||
@ -260,7 +282,7 @@ namespace Ryujinx.Ava.UI.Renderer
|
||||
[SupportedOSPlatform("macos")]
|
||||
void DestroyMacOS()
|
||||
{
|
||||
MetalHelper.DestroyMetalLayer(NsView, MetalLayer);
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
}
|
@ -122,7 +122,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
{
|
||||
string patreonJsonString = await httpClient.GetStringAsync("https://patreon.ryujinx.org/");
|
||||
|
||||
Supporters = string.Join(", ", JsonHelper.Deserialize<string[]>(patreonJsonString)) + "\n\n";
|
||||
Supporters = string.Join(", ", JsonHelper.Deserialize(patreonJsonString, CommonJsonContext.Default.StringArray)) + "\n\n";
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
@ -3,13 +3,12 @@ using Avalonia.Collections;
|
||||
using Avalonia.Media.Imaging;
|
||||
using Avalonia.Threading;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.UI.Controls;
|
||||
using Ryujinx.Ava.UI.Helpers;
|
||||
using Ryujinx.Ava.UI.Models;
|
||||
using Ryujinx.Ava.UI.Windows;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Common.Utilities;
|
||||
using Ryujinx.Ui.Common.Models.Amiibo;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
@ -17,8 +16,8 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using AmiiboJsonSerializerContext = Ryujinx.Ui.Common.Models.Amiibo.AmiiboJsonSerializerContext;
|
||||
|
||||
namespace Ryujinx.Ava.UI.ViewModels
|
||||
{
|
||||
@ -31,10 +30,10 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
private readonly byte[] _amiiboLogoBytes;
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly StyleableWindow _owner;
|
||||
|
||||
|
||||
private Bitmap _amiiboImage;
|
||||
private List<Amiibo.AmiiboApi> _amiiboList;
|
||||
private AvaloniaList<Amiibo.AmiiboApi> _amiibos;
|
||||
private List<AmiiboApi> _amiiboList;
|
||||
private AvaloniaList<AmiiboApi> _amiibos;
|
||||
private ObservableCollection<string> _amiiboSeries;
|
||||
|
||||
private int _amiiboSelectedIndex;
|
||||
@ -43,6 +42,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
private bool _showAllAmiibo;
|
||||
private bool _useRandomUuid;
|
||||
private string _usage;
|
||||
|
||||
private static readonly AmiiboJsonSerializerContext SerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
||||
|
||||
public AmiiboWindowViewModel(StyleableWindow owner, string lastScannedAmiiboId, string titleId)
|
||||
{
|
||||
@ -54,9 +55,9 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
Directory.CreateDirectory(Path.Join(AppDataManager.BaseDirPath, "system", "amiibo"));
|
||||
|
||||
_amiiboJsonPath = Path.Join(AppDataManager.BaseDirPath, "system", "amiibo", "Amiibo.json");
|
||||
_amiiboList = new List<Amiibo.AmiiboApi>();
|
||||
_amiiboList = new List<AmiiboApi>();
|
||||
_amiiboSeries = new ObservableCollection<string>();
|
||||
_amiibos = new AvaloniaList<Amiibo.AmiiboApi>();
|
||||
_amiibos = new AvaloniaList<AmiiboApi>();
|
||||
|
||||
_amiiboLogoBytes = EmbeddedResources.Read("Ryujinx.Ui.Common/Resources/Logo_Amiibo.png");
|
||||
|
||||
@ -96,7 +97,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
public AvaloniaList<Amiibo.AmiiboApi> AmiiboList
|
||||
public AvaloniaList<AmiiboApi> AmiiboList
|
||||
{
|
||||
get => _amiibos;
|
||||
set
|
||||
@ -189,9 +190,9 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
|
||||
if (File.Exists(_amiiboJsonPath))
|
||||
{
|
||||
amiiboJsonString = File.ReadAllText(_amiiboJsonPath);
|
||||
amiiboJsonString = await File.ReadAllTextAsync(_amiiboJsonPath);
|
||||
|
||||
if (await NeedsUpdate(JsonHelper.Deserialize<Amiibo.AmiiboJson>(amiiboJsonString).LastUpdated))
|
||||
if (await NeedsUpdate(JsonHelper.Deserialize(amiiboJsonString, SerializerContext.AmiiboJson).LastUpdated))
|
||||
{
|
||||
amiiboJsonString = await DownloadAmiiboJson();
|
||||
}
|
||||
@ -208,7 +209,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
_amiiboList = JsonHelper.Deserialize<Amiibo.AmiiboJson>(amiiboJsonString).Amiibo;
|
||||
_amiiboList = JsonHelper.Deserialize(amiiboJsonString, SerializerContext.AmiiboJson).Amiibo;
|
||||
_amiiboList = _amiiboList.OrderBy(amiibo => amiibo.AmiiboSeries).ToList();
|
||||
|
||||
ParseAmiiboData();
|
||||
@ -225,7 +226,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
{
|
||||
if (!ShowAllAmiibo)
|
||||
{
|
||||
foreach (Amiibo.AmiiboApiGamesSwitch game in _amiiboList[i].GamesSwitch)
|
||||
foreach (AmiiboApiGamesSwitch game in _amiiboList[i].GamesSwitch)
|
||||
{
|
||||
if (game != null)
|
||||
{
|
||||
@ -257,7 +258,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
|
||||
private void SelectLastScannedAmiibo()
|
||||
{
|
||||
Amiibo.AmiiboApi scanned = _amiiboList.FirstOrDefault(amiibo => amiibo.GetId() == LastScannedAmiiboId);
|
||||
AmiiboApi scanned = _amiiboList.FirstOrDefault(amiibo => amiibo.GetId() == LastScannedAmiiboId);
|
||||
|
||||
SeriesSelectedIndex = AmiiboSeries.IndexOf(scanned.AmiiboSeries);
|
||||
AmiiboSelectedIndex = AmiiboList.IndexOf(scanned);
|
||||
@ -272,7 +273,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
return;
|
||||
}
|
||||
|
||||
List<Amiibo.AmiiboApi> amiiboSortedList = _amiiboList
|
||||
List<AmiiboApi> amiiboSortedList = _amiiboList
|
||||
.Where(amiibo => amiibo.AmiiboSeries == _amiiboSeries[SeriesSelectedIndex])
|
||||
.OrderBy(amiibo => amiibo.Name).ToList();
|
||||
|
||||
@ -282,7 +283,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
{
|
||||
if (!_showAllAmiibo)
|
||||
{
|
||||
foreach (Amiibo.AmiiboApiGamesSwitch game in amiiboSortedList[i].GamesSwitch)
|
||||
foreach (AmiiboApiGamesSwitch game in amiiboSortedList[i].GamesSwitch)
|
||||
{
|
||||
if (game != null)
|
||||
{
|
||||
@ -316,7 +317,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
return;
|
||||
}
|
||||
|
||||
Amiibo.AmiiboApi selected = _amiibos[_amiiboSelectedIndex];
|
||||
AmiiboApi selected = _amiibos[_amiiboSelectedIndex];
|
||||
|
||||
string imageUrl = _amiiboList.FirstOrDefault(amiibo => amiibo.Equals(selected)).Image;
|
||||
|
||||
@ -328,11 +329,11 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
{
|
||||
bool writable = false;
|
||||
|
||||
foreach (Amiibo.AmiiboApiGamesSwitch item in _amiiboList[i].GamesSwitch)
|
||||
foreach (AmiiboApiGamesSwitch item in _amiiboList[i].GamesSwitch)
|
||||
{
|
||||
if (item.GameId.Contains(TitleId))
|
||||
{
|
||||
foreach (Amiibo.AmiiboApiUsage usageItem in item.AmiiboUsage)
|
||||
foreach (AmiiboApiUsage usageItem in item.AmiiboUsage)
|
||||
{
|
||||
usageString += Environment.NewLine +
|
||||
$"- {usageItem.Usage.Replace("/", Environment.NewLine + "-")}";
|
||||
|
@ -246,7 +246,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
{
|
||||
using var file = new UniqueRef<IFile>();
|
||||
|
||||
romfs.OpenFile(ref file.Ref(), ("/" + item.FullPath).ToU8Span(), OpenMode.Read)
|
||||
romfs.OpenFile(ref file.Ref, ("/" + item.FullPath).ToU8Span(), OpenMode.Read)
|
||||
.ThrowIfFailure();
|
||||
|
||||
using (MemoryStream stream = new())
|
||||
|
@ -3,11 +3,8 @@ using Avalonia.Controls;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Svg.Skia;
|
||||
using Avalonia.Threading;
|
||||
using LibHac.Bcat;
|
||||
using LibHac.Tools.Fs;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.Input;
|
||||
using Ryujinx.Ava.UI.Controls;
|
||||
using Ryujinx.Ava.UI.Helpers;
|
||||
using Ryujinx.Ava.UI.Models;
|
||||
using Ryujinx.Ava.UI.Windows;
|
||||
@ -54,6 +51,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
private bool _isLoaded;
|
||||
private readonly UserControl _owner;
|
||||
|
||||
private static readonly InputConfigJsonSerializerContext SerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
||||
|
||||
public IGamepadDriver AvaloniaKeyboardDriver { get; }
|
||||
public IGamepad SelectedGamepad { get; private set; }
|
||||
|
||||
@ -709,10 +708,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
|
||||
try
|
||||
{
|
||||
using (Stream stream = File.OpenRead(path))
|
||||
{
|
||||
config = JsonHelper.Deserialize<InputConfig>(stream);
|
||||
}
|
||||
config = JsonHelper.DeserializeFromFile(path, SerializerContext.InputConfig);
|
||||
}
|
||||
catch (JsonException) { }
|
||||
catch (InvalidOperationException)
|
||||
@ -778,7 +774,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
|
||||
config.ControllerType = Controllers[_controller].Type;
|
||||
|
||||
string jsonString = JsonHelper.Serialize(config, true);
|
||||
string jsonString = JsonHelper.Serialize(config, SerializerContext.InputConfig);
|
||||
|
||||
await File.WriteAllTextAsync(path, jsonString);
|
||||
|
||||
|
338
Ryujinx.Ava/UI/ViewModels/DownloadableContentManagerViewModel.cs
Normal file
338
Ryujinx.Ava/UI/ViewModels/DownloadableContentManagerViewModel.cs
Normal file
@ -0,0 +1,338 @@
|
||||
using Avalonia.Collections;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Threading;
|
||||
using DynamicData;
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Fs.Fsa;
|
||||
using LibHac.FsSystem;
|
||||
using LibHac.Tools.Fs;
|
||||
using LibHac.Tools.FsSystem;
|
||||
using LibHac.Tools.FsSystem.NcaUtils;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.UI.Helpers;
|
||||
using Ryujinx.Ava.UI.Models;
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common.Utilities;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Path = System.IO.Path;
|
||||
|
||||
namespace Ryujinx.Ava.UI.ViewModels
|
||||
{
|
||||
public class DownloadableContentManagerViewModel : BaseModel
|
||||
{
|
||||
private readonly List<DownloadableContentContainer> _downloadableContentContainerList;
|
||||
private readonly string _downloadableContentJsonPath;
|
||||
|
||||
private VirtualFileSystem _virtualFileSystem;
|
||||
private AvaloniaList<DownloadableContentModel> _downloadableContents = new();
|
||||
private AvaloniaList<DownloadableContentModel> _views = new();
|
||||
private AvaloniaList<DownloadableContentModel> _selectedDownloadableContents = new();
|
||||
|
||||
private string _search;
|
||||
private ulong _titleId;
|
||||
private string _titleName;
|
||||
|
||||
private static readonly DownloadableContentJsonSerializerContext SerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
||||
|
||||
public AvaloniaList<DownloadableContentModel> DownloadableContents
|
||||
{
|
||||
get => _downloadableContents;
|
||||
set
|
||||
{
|
||||
_downloadableContents = value;
|
||||
OnPropertyChanged();
|
||||
OnPropertyChanged(nameof(UpdateCount));
|
||||
Sort();
|
||||
}
|
||||
}
|
||||
|
||||
public AvaloniaList<DownloadableContentModel> Views
|
||||
{
|
||||
get => _views;
|
||||
set
|
||||
{
|
||||
_views = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public AvaloniaList<DownloadableContentModel> SelectedDownloadableContents
|
||||
{
|
||||
get => _selectedDownloadableContents;
|
||||
set
|
||||
{
|
||||
_selectedDownloadableContents = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public string Search
|
||||
{
|
||||
get => _search;
|
||||
set
|
||||
{
|
||||
_search = value;
|
||||
OnPropertyChanged();
|
||||
Sort();
|
||||
}
|
||||
}
|
||||
|
||||
public string UpdateCount
|
||||
{
|
||||
get => string.Format(LocaleManager.Instance[LocaleKeys.DlcWindowHeading], DownloadableContents.Count);
|
||||
}
|
||||
|
||||
public DownloadableContentManagerViewModel(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName)
|
||||
{
|
||||
_virtualFileSystem = virtualFileSystem;
|
||||
|
||||
_titleId = titleId;
|
||||
_titleName = titleName;
|
||||
|
||||
_downloadableContentJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId.ToString("x16"), "dlc.json");
|
||||
|
||||
try
|
||||
{
|
||||
_downloadableContentContainerList = JsonHelper.DeserializeFromFile(_downloadableContentJsonPath, SerializerContext.ListDownloadableContentContainer);
|
||||
}
|
||||
catch
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Configuration, "Downloadable Content JSON failed to deserialize.");
|
||||
_downloadableContentContainerList = new List<DownloadableContentContainer>();
|
||||
}
|
||||
|
||||
LoadDownloadableContents();
|
||||
}
|
||||
|
||||
private void LoadDownloadableContents()
|
||||
{
|
||||
foreach (DownloadableContentContainer downloadableContentContainer in _downloadableContentContainerList)
|
||||
{
|
||||
if (File.Exists(downloadableContentContainer.ContainerPath))
|
||||
{
|
||||
using FileStream containerFile = File.OpenRead(downloadableContentContainer.ContainerPath);
|
||||
|
||||
PartitionFileSystem partitionFileSystem = new(containerFile.AsStorage());
|
||||
|
||||
_virtualFileSystem.ImportTickets(partitionFileSystem);
|
||||
|
||||
foreach (DownloadableContentNca downloadableContentNca in downloadableContentContainer.DownloadableContentNcaList)
|
||||
{
|
||||
using UniqueRef<IFile> ncaFile = new();
|
||||
|
||||
partitionFileSystem.OpenFile(ref ncaFile.Ref, downloadableContentNca.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||
|
||||
Nca nca = TryOpenNca(ncaFile.Get.AsStorage(), downloadableContentContainer.ContainerPath);
|
||||
if (nca != null)
|
||||
{
|
||||
var content = new DownloadableContentModel(nca.Header.TitleId.ToString("X16"),
|
||||
downloadableContentContainer.ContainerPath,
|
||||
downloadableContentNca.FullPath,
|
||||
downloadableContentNca.Enabled);
|
||||
|
||||
DownloadableContents.Add(content);
|
||||
|
||||
if (content.Enabled)
|
||||
{
|
||||
SelectedDownloadableContents.Add(content);
|
||||
}
|
||||
|
||||
OnPropertyChanged(nameof(UpdateCount));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: Save the list again to remove leftovers.
|
||||
Save();
|
||||
Sort();
|
||||
}
|
||||
|
||||
public void Sort()
|
||||
{
|
||||
DownloadableContents.AsObservableChangeSet()
|
||||
.Filter(Filter)
|
||||
.Bind(out var view).AsObservableList();
|
||||
|
||||
_views.Clear();
|
||||
_views.AddRange(view);
|
||||
OnPropertyChanged(nameof(Views));
|
||||
}
|
||||
|
||||
private bool Filter(object arg)
|
||||
{
|
||||
if (arg is DownloadableContentModel content)
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(_search) || content.FileName.ToLower().Contains(_search.ToLower()) || content.TitleId.ToLower().Contains(_search.ToLower());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private Nca TryOpenNca(IStorage ncaStorage, string containerPath)
|
||||
{
|
||||
try
|
||||
{
|
||||
return new Nca(_virtualFileSystem.KeySet, ncaStorage);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Dispatcher.UIThread.InvokeAsync(async () =>
|
||||
{
|
||||
await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance[LocaleKeys.DialogLoadNcaErrorMessage], ex.Message, containerPath));
|
||||
});
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public async void Add()
|
||||
{
|
||||
OpenFileDialog dialog = new OpenFileDialog()
|
||||
{
|
||||
Title = LocaleManager.Instance[LocaleKeys.SelectDlcDialogTitle],
|
||||
AllowMultiple = true
|
||||
};
|
||||
|
||||
dialog.Filters.Add(new FileDialogFilter
|
||||
{
|
||||
Name = "NSP",
|
||||
Extensions = { "nsp" }
|
||||
});
|
||||
|
||||
if (Avalonia.Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
string[] files = await dialog.ShowAsync(desktop.MainWindow);
|
||||
|
||||
if (files != null)
|
||||
{
|
||||
foreach (string file in files)
|
||||
{
|
||||
await AddDownloadableContent(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task AddDownloadableContent(string path)
|
||||
{
|
||||
if (!File.Exists(path) || DownloadableContents.FirstOrDefault(x => x.ContainerPath == path) != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using FileStream containerFile = File.OpenRead(path);
|
||||
|
||||
PartitionFileSystem partitionFileSystem = new(containerFile.AsStorage());
|
||||
bool containsDownloadableContent = false;
|
||||
|
||||
_virtualFileSystem.ImportTickets(partitionFileSystem);
|
||||
|
||||
foreach (DirectoryEntryEx fileEntry in partitionFileSystem.EnumerateEntries("/", "*.nca"))
|
||||
{
|
||||
using var ncaFile = new UniqueRef<IFile>();
|
||||
|
||||
partitionFileSystem.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||
|
||||
Nca nca = TryOpenNca(ncaFile.Get.AsStorage(), path);
|
||||
if (nca == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (nca.Header.ContentType == NcaContentType.PublicData)
|
||||
{
|
||||
if ((nca.Header.TitleId & 0xFFFFFFFFFFFFE000) != _titleId)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
var content = new DownloadableContentModel(nca.Header.TitleId.ToString("X16"), path, fileEntry.FullPath, true);
|
||||
DownloadableContents.Add(content);
|
||||
SelectedDownloadableContents.Add(content);
|
||||
|
||||
OnPropertyChanged(nameof(UpdateCount));
|
||||
Sort();
|
||||
|
||||
containsDownloadableContent = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!containsDownloadableContent)
|
||||
{
|
||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogDlcNoDlcErrorMessage]);
|
||||
}
|
||||
}
|
||||
|
||||
public void Remove(DownloadableContentModel model)
|
||||
{
|
||||
DownloadableContents.Remove(model);
|
||||
OnPropertyChanged(nameof(UpdateCount));
|
||||
Sort();
|
||||
}
|
||||
|
||||
public void RemoveAll()
|
||||
{
|
||||
DownloadableContents.Clear();
|
||||
OnPropertyChanged(nameof(UpdateCount));
|
||||
Sort();
|
||||
}
|
||||
|
||||
public void EnableAll()
|
||||
{
|
||||
SelectedDownloadableContents = new(DownloadableContents);
|
||||
}
|
||||
|
||||
public void DisableAll()
|
||||
{
|
||||
SelectedDownloadableContents.Clear();
|
||||
}
|
||||
|
||||
public void Save()
|
||||
{
|
||||
_downloadableContentContainerList.Clear();
|
||||
|
||||
DownloadableContentContainer container = default;
|
||||
|
||||
foreach (DownloadableContentModel downloadableContent in DownloadableContents)
|
||||
{
|
||||
if (container.ContainerPath != downloadableContent.ContainerPath)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(container.ContainerPath))
|
||||
{
|
||||
_downloadableContentContainerList.Add(container);
|
||||
}
|
||||
|
||||
container = new DownloadableContentContainer
|
||||
{
|
||||
ContainerPath = downloadableContent.ContainerPath,
|
||||
DownloadableContentNcaList = new List<DownloadableContentNca>()
|
||||
};
|
||||
}
|
||||
|
||||
container.DownloadableContentNcaList.Add(new DownloadableContentNca
|
||||
{
|
||||
Enabled = downloadableContent.Enabled,
|
||||
TitleId = Convert.ToUInt64(downloadableContent.TitleId, 16),
|
||||
FullPath = downloadableContent.FullPath
|
||||
});
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(container.ContainerPath))
|
||||
{
|
||||
_downloadableContentContainerList.Add(container);
|
||||
}
|
||||
|
||||
JsonHelper.SerializeToFile(_downloadableContentJsonPath, _downloadableContentContainerList, SerializerContext.ListDownloadableContentContainer);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -7,8 +7,7 @@ using DynamicData;
|
||||
using DynamicData.Binding;
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs;
|
||||
using LibHac.FsSystem;
|
||||
using LibHac.Tools.Fs;
|
||||
using LibHac.Tools.FsSystem.NcaUtils;
|
||||
using Ryujinx.Ava.Common;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.Input;
|
||||
@ -973,7 +972,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
LocaleManager.Instance[LocaleKeys.InputDialogNo],
|
||||
LocaleManager.Instance[LocaleKeys.RyujinxConfirm]);
|
||||
|
||||
UpdateWaitWindow waitingDialog = ContentDialogHelper.CreateWaitingDialog(dialogTitle, LocaleManager.Instance[LocaleKeys.DialogFirmwareInstallerFirmwareInstallWaitMessage]);
|
||||
UpdateWaitWindow waitingDialog = new(dialogTitle, LocaleManager.Instance[LocaleKeys.DialogFirmwareInstallerFirmwareInstallWaitMessage]);
|
||||
|
||||
if (result == UserResult.Yes)
|
||||
{
|
||||
@ -1209,10 +1208,10 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
|
||||
public void SetUIProgressHandlers(Switch emulationContext)
|
||||
{
|
||||
if (emulationContext.Application.DiskCacheLoadState != null)
|
||||
if (emulationContext.Processes.ActiveApplication.DiskCacheLoadState != null)
|
||||
{
|
||||
emulationContext.Application.DiskCacheLoadState.StateChanged -= ProgressHandler;
|
||||
emulationContext.Application.DiskCacheLoadState.StateChanged += ProgressHandler;
|
||||
emulationContext.Processes.ActiveApplication.DiskCacheLoadState.StateChanged -= ProgressHandler;
|
||||
emulationContext.Processes.ActiveApplication.DiskCacheLoadState.StateChanged += ProgressHandler;
|
||||
}
|
||||
|
||||
emulationContext.Gpu.ShaderCacheStateChanged -= ProgressHandler;
|
||||
@ -1337,6 +1336,23 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
public void ToggleFileType(string fileType)
|
||||
{
|
||||
_ = fileType switch
|
||||
{
|
||||
"NSP" => ConfigurationState.Instance.Ui.ShownFileTypes.NSP.Value = !ConfigurationState.Instance.Ui.ShownFileTypes.NSP,
|
||||
"PFS0" => ConfigurationState.Instance.Ui.ShownFileTypes.PFS0.Value = !ConfigurationState.Instance.Ui.ShownFileTypes.PFS0,
|
||||
"XCI" => ConfigurationState.Instance.Ui.ShownFileTypes.XCI.Value = !ConfigurationState.Instance.Ui.ShownFileTypes.XCI,
|
||||
"NCA" => ConfigurationState.Instance.Ui.ShownFileTypes.NCA.Value = !ConfigurationState.Instance.Ui.ShownFileTypes.NCA,
|
||||
"NRO" => ConfigurationState.Instance.Ui.ShownFileTypes.NRO.Value = !ConfigurationState.Instance.Ui.ShownFileTypes.NRO,
|
||||
"NSO" => ConfigurationState.Instance.Ui.ShownFileTypes.NSO.Value = !ConfigurationState.Instance.Ui.ShownFileTypes.NSO,
|
||||
_ => throw new ArgumentOutOfRangeException(fileType),
|
||||
};
|
||||
|
||||
ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);
|
||||
LoadApplications();
|
||||
}
|
||||
|
||||
public async void ManageProfiles()
|
||||
{
|
||||
await NavigationDialogHost.Show(AccountManager, ContentManager, VirtualFileSystem, LibHacHorizonManager.RyujinxClient);
|
||||
@ -1565,7 +1581,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
{
|
||||
if (SelectedApplication != null)
|
||||
{
|
||||
await new DownloadableContentManagerWindow(VirtualFileSystem, ulong.Parse(SelectedApplication.TitleId, NumberStyles.HexNumber), SelectedApplication.TitleName).ShowDialog(TopLevel as Window);
|
||||
await DownloadableContentManagerWindow.Show(VirtualFileSystem, ulong.Parse(SelectedApplication.TitleId, NumberStyles.HexNumber), SelectedApplication.TitleName);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1706,8 +1722,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
|
||||
if (string.IsNullOrWhiteSpace(titleName))
|
||||
{
|
||||
LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, AppHost.Device.Application.TitleName);
|
||||
TitleName = AppHost.Device.Application.TitleName;
|
||||
LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, AppHost.Device.Processes.ActiveApplication.Name);
|
||||
TitleName = AppHost.Device.Processes.ActiveApplication.Name;
|
||||
}
|
||||
|
||||
SwitchToRenderer(startFullscreen);
|
||||
|
@ -22,6 +22,8 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Net.NetworkInformation;
|
||||
using TimeZone = Ryujinx.Ava.UI.Models.TimeZone;
|
||||
|
||||
namespace Ryujinx.Ava.UI.ViewModels
|
||||
@ -34,6 +36,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
|
||||
private readonly List<string> _validTzRegions;
|
||||
|
||||
private readonly Dictionary<string, string> _networkInterfaces;
|
||||
|
||||
private float _customResolutionScale;
|
||||
private int _resolutionScale;
|
||||
private int _graphicsBackendMultithreadingIndex;
|
||||
@ -44,9 +48,12 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
private KeyboardHotkeys _keyboardHotkeys;
|
||||
private int _graphicsBackendIndex;
|
||||
private string _customThemePath;
|
||||
private int _scalingFilter;
|
||||
private int _scalingFilterLevel;
|
||||
|
||||
public event Action CloseWindow;
|
||||
public event Action SaveSettingsEvent;
|
||||
private int _networkInterfaceIndex;
|
||||
|
||||
public int ResolutionScale
|
||||
{
|
||||
@ -59,6 +66,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
OnPropertyChanged(nameof(IsCustomResolutionScaleActive));
|
||||
}
|
||||
}
|
||||
|
||||
public int GraphicsBackendMultithreadingIndex
|
||||
{
|
||||
get => _graphicsBackendMultithreadingIndex;
|
||||
@ -106,6 +114,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
|
||||
public bool IsOpenGLAvailable => !OperatingSystem.IsMacOS();
|
||||
|
||||
public bool IsHypervisorAvailable => OperatingSystem.IsMacOS() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64;
|
||||
|
||||
public bool DirectoryChanged
|
||||
{
|
||||
get => _directoryChanged;
|
||||
@ -117,10 +127,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsMacOS
|
||||
{
|
||||
get => OperatingSystem.IsMacOS();
|
||||
}
|
||||
public bool IsMacOS => OperatingSystem.IsMacOS();
|
||||
|
||||
public bool EnableDiscordIntegration { get; set; }
|
||||
public bool CheckUpdatesOnStart { get; set; }
|
||||
@ -152,7 +159,10 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
public bool IsSDL2Enabled { get; set; }
|
||||
public bool EnableCustomTheme { get; set; }
|
||||
public bool IsCustomResolutionScaleActive => _resolutionScale == 4;
|
||||
public bool IsScalingFilterActive => _scalingFilter == (int)Ryujinx.Common.Configuration.ScalingFilter.Fsr;
|
||||
|
||||
public bool IsVulkanSelected => GraphicsBackendIndex == 0;
|
||||
public bool UseHypervisor { get; set; }
|
||||
|
||||
public string TimeZone { get; set; }
|
||||
public string ShaderDumpPath { get; set; }
|
||||
@ -177,6 +187,18 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
public int AudioBackend { get; set; }
|
||||
public int MaxAnisotropy { get; set; }
|
||||
public int AspectRatio { get; set; }
|
||||
public int AntiAliasingEffect { get; set; }
|
||||
public string ScalingFilterLevelText => ScalingFilterLevel.ToString("0");
|
||||
public int ScalingFilterLevel
|
||||
{
|
||||
get => _scalingFilterLevel;
|
||||
set
|
||||
{
|
||||
_scalingFilterLevel = value;
|
||||
OnPropertyChanged();
|
||||
OnPropertyChanged(nameof(ScalingFilterLevelText));
|
||||
}
|
||||
}
|
||||
public int OpenglDebugLevel { get; set; }
|
||||
public int MemoryMode { get; set; }
|
||||
public int BaseStyleIndex { get; set; }
|
||||
@ -190,6 +212,16 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
OnPropertyChanged(nameof(IsVulkanSelected));
|
||||
}
|
||||
}
|
||||
public int ScalingFilter
|
||||
{
|
||||
get => _scalingFilter;
|
||||
set
|
||||
{
|
||||
_scalingFilter = value;
|
||||
OnPropertyChanged();
|
||||
OnPropertyChanged(nameof(IsScalingFilterActive));
|
||||
}
|
||||
}
|
||||
|
||||
public int PreferredGpuIndex { get; set; }
|
||||
|
||||
@ -208,10 +240,15 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
|
||||
public DateTimeOffset DateOffset { get; set; }
|
||||
public TimeSpan TimeOffset { get; set; }
|
||||
private AvaloniaList<TimeZone> TimeZones { get; set; }
|
||||
internal AvaloniaList<TimeZone> TimeZones { get; set; }
|
||||
public AvaloniaList<string> GameDirectories { get; set; }
|
||||
public ObservableCollection<ComboBoxItem> AvailableGpus { get; set; }
|
||||
|
||||
public AvaloniaList<string> NetworkInterfaceList
|
||||
{
|
||||
get => new AvaloniaList<string>(_networkInterfaces.Keys);
|
||||
}
|
||||
|
||||
public KeyboardHotkeys KeyboardHotkeys
|
||||
{
|
||||
get => _keyboardHotkeys;
|
||||
@ -223,6 +260,16 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
public int NetworkInterfaceIndex
|
||||
{
|
||||
get => _networkInterfaceIndex;
|
||||
set
|
||||
{
|
||||
_networkInterfaceIndex = value != -1 ? value : 0;
|
||||
ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value = _networkInterfaces[NetworkInterfaceList[_networkInterfaceIndex]];
|
||||
}
|
||||
}
|
||||
|
||||
public SettingsViewModel(VirtualFileSystem virtualFileSystem, ContentManager contentManager) : this()
|
||||
{
|
||||
_virtualFileSystem = virtualFileSystem;
|
||||
@ -239,8 +286,10 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
TimeZones = new AvaloniaList<TimeZone>();
|
||||
AvailableGpus = new ObservableCollection<ComboBoxItem>();
|
||||
_validTzRegions = new List<string>();
|
||||
_networkInterfaces = new Dictionary<string, string>();
|
||||
|
||||
CheckSoundBackends();
|
||||
PopulateNetworkInterfaces();
|
||||
|
||||
if (Program.PreviewerDetached)
|
||||
{
|
||||
@ -299,6 +348,17 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
private void PopulateNetworkInterfaces()
|
||||
{
|
||||
_networkInterfaces.Clear();
|
||||
_networkInterfaces.Add(LocaleManager.Instance[LocaleKeys.NetworkInterfaceDefault], "0");
|
||||
|
||||
foreach (NetworkInterface networkInterface in NetworkInterface.GetAllNetworkInterfaces())
|
||||
{
|
||||
_networkInterfaces.Add(networkInterface.Name, networkInterface.Id);
|
||||
}
|
||||
}
|
||||
|
||||
public void ValidateAndSetTimeZone(string location)
|
||||
{
|
||||
if (_validTzRegions.Contains(location))
|
||||
@ -349,6 +409,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
// CPU
|
||||
EnablePptc = config.System.EnablePtc;
|
||||
MemoryMode = (int)config.System.MemoryManagerMode.Value;
|
||||
UseHypervisor = config.System.UseHypervisor;
|
||||
|
||||
// Graphics
|
||||
GraphicsBackendIndex = (int)config.Graphics.GraphicsBackend.Value;
|
||||
@ -362,6 +423,9 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
AspectRatio = (int)config.Graphics.AspectRatio.Value;
|
||||
GraphicsBackendMultithreadingIndex = (int)config.Graphics.BackendThreading.Value;
|
||||
ShaderDumpPath = config.Graphics.ShadersDumpPath;
|
||||
AntiAliasingEffect = (int)config.Graphics.AntiAliasing.Value;
|
||||
ScalingFilter = (int)config.Graphics.ScalingFilter.Value;
|
||||
ScalingFilterLevel = config.Graphics.ScalingFilterLevel.Value;
|
||||
|
||||
// Audio
|
||||
AudioBackend = (int)config.System.AudioBackend.Value;
|
||||
@ -369,7 +433,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
|
||||
// Network
|
||||
EnableInternetAccess = config.System.EnableInternetAccess;
|
||||
|
||||
|
||||
// Logging
|
||||
EnableFileLog = config.Logger.EnableFileLog;
|
||||
EnableStub = config.Logger.EnableStub;
|
||||
@ -382,6 +446,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
EnableFsAccessLog = config.Logger.EnableFsAccessLog;
|
||||
FsGlobalAccessLogMode = config.System.FsGlobalAccessLogMode;
|
||||
OpenglDebugLevel = (int)config.Logger.GraphicsDebugLevel.Value;
|
||||
|
||||
NetworkInterfaceIndex = _networkInterfaces.Values.ToList().IndexOf(config.Multiplayer.LanInterfaceId.Value);
|
||||
}
|
||||
|
||||
public void SaveSettings()
|
||||
@ -432,6 +498,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
// CPU
|
||||
config.System.EnablePtc.Value = EnablePptc;
|
||||
config.System.MemoryManagerMode.Value = (MemoryManagerMode)MemoryMode;
|
||||
config.System.UseHypervisor.Value = UseHypervisor;
|
||||
|
||||
// Graphics
|
||||
config.Graphics.GraphicsBackend.Value = (GraphicsBackend)GraphicsBackendIndex;
|
||||
@ -443,6 +510,9 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
config.Graphics.ResScaleCustom.Value = CustomResolutionScale;
|
||||
config.Graphics.MaxAnisotropy.Value = MaxAnisotropy == 0 ? -1 : MathF.Pow(2, MaxAnisotropy);
|
||||
config.Graphics.AspectRatio.Value = (AspectRatio)AspectRatio;
|
||||
config.Graphics.AntiAliasing.Value = (AntiAliasing)AntiAliasingEffect;
|
||||
config.Graphics.ScalingFilter.Value = (ScalingFilter)ScalingFilter;
|
||||
config.Graphics.ScalingFilterLevel.Value = ScalingFilterLevel;
|
||||
|
||||
if (ConfigurationState.Instance.Graphics.BackendThreading != (BackendThreading)GraphicsBackendMultithreadingIndex)
|
||||
{
|
||||
@ -479,6 +549,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
config.System.FsGlobalAccessLogMode.Value = FsGlobalAccessLogMode;
|
||||
config.Logger.GraphicsDebugLevel.Value = (GraphicsDebugLevel)OpenglDebugLevel;
|
||||
|
||||
config.Multiplayer.LanInterfaceId.Value = _networkInterfaces[NetworkInterfaceList[NetworkInterfaceIndex]];
|
||||
|
||||
config.ToFileFormat().SaveConfig(Program.ConfigurationPath);
|
||||
|
||||
MainWindow.UpdateGraphicsConfig();
|
||||
|
@ -17,234 +17,236 @@ using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common.Utilities;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using Ryujinx.HLE.HOS;
|
||||
using Ryujinx.Ui.App.Common;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Path = System.IO.Path;
|
||||
using SpanHelpers = LibHac.Common.SpanHelpers;
|
||||
|
||||
namespace Ryujinx.Ava.UI.ViewModels;
|
||||
|
||||
public class TitleUpdateViewModel : BaseModel
|
||||
namespace Ryujinx.Ava.UI.ViewModels
|
||||
{
|
||||
public TitleUpdateMetadata _titleUpdateWindowData;
|
||||
public readonly string _titleUpdateJsonPath;
|
||||
private VirtualFileSystem _virtualFileSystem { get; }
|
||||
private ulong _titleId { get; }
|
||||
private string _titleName { get; }
|
||||
|
||||
private AvaloniaList<TitleUpdateModel> _titleUpdates = new();
|
||||
private AvaloniaList<object> _views = new();
|
||||
private object _selectedUpdate;
|
||||
|
||||
public AvaloniaList<TitleUpdateModel> TitleUpdates
|
||||
public class TitleUpdateViewModel : BaseModel
|
||||
{
|
||||
get => _titleUpdates;
|
||||
set
|
||||
public TitleUpdateMetadata _titleUpdateWindowData;
|
||||
public readonly string _titleUpdateJsonPath;
|
||||
private VirtualFileSystem _virtualFileSystem { get; }
|
||||
private ulong _titleId { get; }
|
||||
private string _titleName { get; }
|
||||
|
||||
private AvaloniaList<TitleUpdateModel> _titleUpdates = new();
|
||||
private AvaloniaList<object> _views = new();
|
||||
private object _selectedUpdate;
|
||||
|
||||
private static readonly TitleUpdateMetadataJsonSerializerContext SerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
||||
|
||||
public AvaloniaList<TitleUpdateModel> TitleUpdates
|
||||
{
|
||||
_titleUpdates = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public AvaloniaList<object> Views
|
||||
{
|
||||
get => _views;
|
||||
set
|
||||
{
|
||||
_views = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public object SelectedUpdate
|
||||
{
|
||||
get => _selectedUpdate;
|
||||
set
|
||||
{
|
||||
_selectedUpdate = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public TitleUpdateViewModel(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName)
|
||||
{
|
||||
_virtualFileSystem = virtualFileSystem;
|
||||
|
||||
_titleId = titleId;
|
||||
_titleName = titleName;
|
||||
|
||||
_titleUpdateJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId.ToString("x16"), "updates.json");
|
||||
|
||||
try
|
||||
{
|
||||
_titleUpdateWindowData = JsonHelper.DeserializeFromFile<TitleUpdateMetadata>(_titleUpdateJsonPath);
|
||||
}
|
||||
catch
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application, $"Failed to deserialize title update data for {_titleId} at {_titleUpdateJsonPath}");
|
||||
|
||||
_titleUpdateWindowData = new TitleUpdateMetadata
|
||||
get => _titleUpdates;
|
||||
set
|
||||
{
|
||||
Selected = "",
|
||||
Paths = new List<string>()
|
||||
};
|
||||
|
||||
Save();
|
||||
}
|
||||
|
||||
LoadUpdates();
|
||||
}
|
||||
|
||||
private void LoadUpdates()
|
||||
{
|
||||
foreach (string path in _titleUpdateWindowData.Paths)
|
||||
{
|
||||
AddUpdate(path);
|
||||
}
|
||||
|
||||
// NOTE: Save the list again to remove leftovers.
|
||||
Save();
|
||||
|
||||
TitleUpdateModel selected = TitleUpdates.FirstOrDefault(x => x.Path == _titleUpdateWindowData.Selected, null);
|
||||
|
||||
SelectedUpdate = selected;
|
||||
|
||||
SortUpdates();
|
||||
}
|
||||
|
||||
public void SortUpdates()
|
||||
{
|
||||
var list = TitleUpdates.ToList();
|
||||
|
||||
list.Sort((first, second) =>
|
||||
{
|
||||
if (string.IsNullOrEmpty(first.Control.DisplayVersionString.ToString()))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else if (string.IsNullOrEmpty(second.Control.DisplayVersionString.ToString()))
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
return Version.Parse(first.Control.DisplayVersionString.ToString()).CompareTo(Version.Parse(second.Control.DisplayVersionString.ToString())) * -1;
|
||||
});
|
||||
|
||||
Views.Clear();
|
||||
Views.Add(new BaseModel());
|
||||
Views.AddRange(list);
|
||||
|
||||
if (SelectedUpdate == null)
|
||||
{
|
||||
SelectedUpdate = Views[0];
|
||||
}
|
||||
else if (!TitleUpdates.Contains(SelectedUpdate))
|
||||
{
|
||||
if (Views.Count > 1)
|
||||
{
|
||||
SelectedUpdate = Views[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
SelectedUpdate = Views[0];
|
||||
_titleUpdates = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AddUpdate(string path)
|
||||
{
|
||||
if (File.Exists(path) && TitleUpdates.All(x => x.Path != path))
|
||||
public AvaloniaList<object> Views
|
||||
{
|
||||
using FileStream file = new(path, FileMode.Open, FileAccess.Read);
|
||||
get => _views;
|
||||
set
|
||||
{
|
||||
_views = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public object SelectedUpdate
|
||||
{
|
||||
get => _selectedUpdate;
|
||||
set
|
||||
{
|
||||
_selectedUpdate = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public TitleUpdateViewModel(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName)
|
||||
{
|
||||
_virtualFileSystem = virtualFileSystem;
|
||||
|
||||
_titleId = titleId;
|
||||
_titleName = titleName;
|
||||
|
||||
_titleUpdateJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId.ToString("x16"), "updates.json");
|
||||
|
||||
try
|
||||
{
|
||||
(Nca patchNca, Nca controlNca) = ApplicationLoader.GetGameUpdateDataFromPartition(_virtualFileSystem, new PartitionFileSystem(file.AsStorage()), _titleId.ToString("x16"), 0);
|
||||
_titleUpdateWindowData = JsonHelper.DeserializeFromFile(_titleUpdateJsonPath, SerializerContext.TitleUpdateMetadata);
|
||||
}
|
||||
catch
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application, $"Failed to deserialize title update data for {_titleId} at {_titleUpdateJsonPath}");
|
||||
|
||||
if (controlNca != null && patchNca != null)
|
||||
_titleUpdateWindowData = new TitleUpdateMetadata
|
||||
{
|
||||
ApplicationControlProperty controlData = new();
|
||||
Selected = "",
|
||||
Paths = new List<string>()
|
||||
};
|
||||
|
||||
using UniqueRef<IFile> nacpFile = new();
|
||||
Save();
|
||||
}
|
||||
|
||||
controlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None).OpenFile(ref nacpFile.Ref(), "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||
nacpFile.Get.Read(out _, 0, SpanHelpers.AsByteSpan(ref controlData), ReadOption.None).ThrowIfFailure();
|
||||
LoadUpdates();
|
||||
}
|
||||
|
||||
TitleUpdates.Add(new TitleUpdateModel(controlData, path));
|
||||
private void LoadUpdates()
|
||||
{
|
||||
foreach (string path in _titleUpdateWindowData.Paths)
|
||||
{
|
||||
AddUpdate(path);
|
||||
}
|
||||
|
||||
TitleUpdateModel selected = TitleUpdates.FirstOrDefault(x => x.Path == _titleUpdateWindowData.Selected, null);
|
||||
|
||||
SelectedUpdate = selected;
|
||||
|
||||
// NOTE: Save the list again to remove leftovers.
|
||||
Save();
|
||||
SortUpdates();
|
||||
}
|
||||
|
||||
public void SortUpdates()
|
||||
{
|
||||
var list = TitleUpdates.ToList();
|
||||
|
||||
list.Sort((first, second) =>
|
||||
{
|
||||
if (string.IsNullOrEmpty(first.Control.DisplayVersionString.ToString()))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else if (string.IsNullOrEmpty(second.Control.DisplayVersionString.ToString()))
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
return Version.Parse(first.Control.DisplayVersionString.ToString()).CompareTo(Version.Parse(second.Control.DisplayVersionString.ToString())) * -1;
|
||||
});
|
||||
|
||||
Views.Clear();
|
||||
Views.Add(new BaseModel());
|
||||
Views.AddRange(list);
|
||||
|
||||
if (SelectedUpdate == null)
|
||||
{
|
||||
SelectedUpdate = Views[0];
|
||||
}
|
||||
else if (!TitleUpdates.Contains(SelectedUpdate))
|
||||
{
|
||||
if (Views.Count > 1)
|
||||
{
|
||||
SelectedUpdate = Views[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
SelectedUpdate = Views[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AddUpdate(string path)
|
||||
{
|
||||
if (File.Exists(path) && TitleUpdates.All(x => x.Path != path))
|
||||
{
|
||||
using FileStream file = new(path, FileMode.Open, FileAccess.Read);
|
||||
|
||||
try
|
||||
{
|
||||
(Nca patchNca, Nca controlNca) = ApplicationLibrary.GetGameUpdateDataFromPartition(_virtualFileSystem, new PartitionFileSystem(file.AsStorage()), _titleId.ToString("x16"), 0);
|
||||
|
||||
if (controlNca != null && patchNca != null)
|
||||
{
|
||||
ApplicationControlProperty controlData = new();
|
||||
|
||||
using UniqueRef<IFile> nacpFile = new();
|
||||
|
||||
controlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None).OpenFile(ref nacpFile.Ref, "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||
nacpFile.Get.Read(out _, 0, SpanHelpers.AsByteSpan(ref controlData), ReadOption.None).ThrowIfFailure();
|
||||
|
||||
TitleUpdates.Add(new TitleUpdateModel(controlData, path));
|
||||
}
|
||||
else
|
||||
{
|
||||
Dispatcher.UIThread.Post(async () =>
|
||||
{
|
||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogUpdateAddUpdateErrorMessage]);
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Dispatcher.UIThread.Post(async () =>
|
||||
{
|
||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogUpdateAddUpdateErrorMessage]);
|
||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogLoadNcaErrorMessage, ex.Message, path));
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Dispatcher.UIThread.Post(async () =>
|
||||
{
|
||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogLoadNcaErrorMessage, ex.Message, path));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveUpdate(TitleUpdateModel update)
|
||||
{
|
||||
TitleUpdates.Remove(update);
|
||||
|
||||
SortUpdates();
|
||||
}
|
||||
|
||||
public async void Add()
|
||||
{
|
||||
OpenFileDialog dialog = new()
|
||||
public void RemoveUpdate(TitleUpdateModel update)
|
||||
{
|
||||
Title = LocaleManager.Instance[LocaleKeys.SelectUpdateDialogTitle],
|
||||
AllowMultiple = true
|
||||
};
|
||||
TitleUpdates.Remove(update);
|
||||
|
||||
dialog.Filters.Add(new FileDialogFilter
|
||||
SortUpdates();
|
||||
}
|
||||
|
||||
public async void Add()
|
||||
{
|
||||
Name = "NSP",
|
||||
Extensions = { "nsp" }
|
||||
});
|
||||
|
||||
if (Avalonia.Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
string[] files = await dialog.ShowAsync(desktop.MainWindow);
|
||||
|
||||
if (files != null)
|
||||
OpenFileDialog dialog = new()
|
||||
{
|
||||
foreach (string file in files)
|
||||
Title = LocaleManager.Instance[LocaleKeys.SelectUpdateDialogTitle],
|
||||
AllowMultiple = true
|
||||
};
|
||||
|
||||
dialog.Filters.Add(new FileDialogFilter
|
||||
{
|
||||
Name = "NSP",
|
||||
Extensions = { "nsp" }
|
||||
});
|
||||
|
||||
if (Avalonia.Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
string[] files = await dialog.ShowAsync(desktop.MainWindow);
|
||||
|
||||
if (files != null)
|
||||
{
|
||||
AddUpdate(file);
|
||||
foreach (string file in files)
|
||||
{
|
||||
AddUpdate(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SortUpdates();
|
||||
}
|
||||
|
||||
SortUpdates();
|
||||
}
|
||||
|
||||
public void Save()
|
||||
{
|
||||
_titleUpdateWindowData.Paths.Clear();
|
||||
_titleUpdateWindowData.Selected = "";
|
||||
|
||||
foreach (TitleUpdateModel update in TitleUpdates)
|
||||
public void Save()
|
||||
{
|
||||
_titleUpdateWindowData.Paths.Add(update.Path);
|
||||
_titleUpdateWindowData.Paths.Clear();
|
||||
_titleUpdateWindowData.Selected = "";
|
||||
|
||||
if (update == SelectedUpdate)
|
||||
foreach (TitleUpdateModel update in TitleUpdates)
|
||||
{
|
||||
_titleUpdateWindowData.Selected = update.Path;
|
||||
}
|
||||
}
|
||||
_titleUpdateWindowData.Paths.Add(update.Path);
|
||||
|
||||
File.WriteAllBytes(_titleUpdateJsonPath, Encoding.UTF8.GetBytes(JsonHelper.Serialize(_titleUpdateWindowData, true)));
|
||||
if (update == SelectedUpdate)
|
||||
{
|
||||
_titleUpdateWindowData.Selected = update.Path;
|
||||
}
|
||||
}
|
||||
|
||||
JsonHelper.SerializeToFile(_titleUpdateJsonPath, _titleUpdateWindowData, SerializerContext.TitleUpdateMetadata);
|
||||
}
|
||||
}
|
||||
}
|
@ -126,7 +126,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
{
|
||||
using var file = new UniqueRef<IFile>();
|
||||
|
||||
romfs.OpenFile(ref file.Ref(), ("/" + item.FullPath).ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||
romfs.OpenFile(ref file.Ref, ("/" + item.FullPath).ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||
|
||||
using (MemoryStream stream = new())
|
||||
using (MemoryStream streamPng = new())
|
||||
|
@ -78,6 +78,7 @@
|
||||
</MenuItem>
|
||||
<Separator />
|
||||
<MenuItem Name="ChangeLanguageMenuItem" Header="{locale:Locale MenuBarOptionsChangeLanguage}" />
|
||||
<MenuItem Name="ToggleFileTypesMenuItem" Header="{locale:Locale MenuBarShowFileTypes}" />
|
||||
<Separator />
|
||||
<MenuItem
|
||||
Click="OpenSettings"
|
||||
|
@ -1,8 +1,8 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using LibHac.FsSystem;
|
||||
using LibHac.Ncm;
|
||||
using LibHac.Tools.FsSystem.NcaUtils;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.UI.Helpers;
|
||||
using Ryujinx.Ava.UI.ViewModels;
|
||||
@ -11,6 +11,8 @@ using Ryujinx.Common;
|
||||
using Ryujinx.Common.Utilities;
|
||||
using Ryujinx.HLE.HOS;
|
||||
using Ryujinx.Modules;
|
||||
using Ryujinx.Ui.Common;
|
||||
using Ryujinx.Ui.Common.Configuration;
|
||||
using Ryujinx.Ui.Common.Helper;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@ -29,6 +31,30 @@ namespace Ryujinx.Ava.UI.Views.Main
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
ToggleFileTypesMenuItem.Items = GenerateToggleFileTypeItems();
|
||||
ChangeLanguageMenuItem.Items = GenerateLanguageMenuItems();
|
||||
}
|
||||
|
||||
private CheckBox[] GenerateToggleFileTypeItems()
|
||||
{
|
||||
List<CheckBox> checkBoxes = new();
|
||||
|
||||
foreach (var item in Enum.GetValues(typeof (FileTypes)))
|
||||
{
|
||||
string fileName = Enum.GetName(typeof (FileTypes), item);
|
||||
checkBoxes.Add(new CheckBox()
|
||||
{
|
||||
Content = $".{fileName}",
|
||||
IsChecked = ((FileTypes)item).GetConfigValue(ConfigurationState.Instance.Ui.ShownFileTypes),
|
||||
Command = MiniCommand.Create(() => ViewModel.ToggleFileType(fileName))
|
||||
});
|
||||
}
|
||||
|
||||
return checkBoxes.ToArray();
|
||||
}
|
||||
|
||||
private MenuItem[] GenerateLanguageMenuItems()
|
||||
{
|
||||
List<MenuItem> menuItems = new();
|
||||
|
||||
string localePath = "Ryujinx.Ava/Assets/Locales";
|
||||
@ -42,7 +68,7 @@ namespace Ryujinx.Ava.UI.Views.Main
|
||||
{
|
||||
string languageCode = Path.GetFileNameWithoutExtension(locale).Split('.').Last();
|
||||
string languageJson = EmbeddedResources.ReadAllText($"{localePath}/{languageCode}{localeExt}");
|
||||
var strings = JsonHelper.Deserialize<Dictionary<string, string>>(languageJson);
|
||||
var strings = JsonHelper.Deserialize(languageJson, CommonJsonContext.Default.StringDictionary);
|
||||
|
||||
if (!strings.TryGetValue("Language", out string languageName))
|
||||
{
|
||||
@ -61,7 +87,7 @@ namespace Ryujinx.Ava.UI.Views.Main
|
||||
menuItems.Add(menuItem);
|
||||
}
|
||||
|
||||
ChangeLanguageMenuItem.Items = menuItems.ToArray();
|
||||
return menuItems.ToArray();
|
||||
}
|
||||
|
||||
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
|
||||
@ -126,7 +152,7 @@ namespace Ryujinx.Ava.UI.Views.Main
|
||||
|
||||
if (ViewModel.AppHost.Device.System.SearchingForAmiibo(out int deviceId))
|
||||
{
|
||||
string titleId = ViewModel.AppHost.Device.Application.TitleIdText.ToUpper();
|
||||
string titleId = ViewModel.AppHost.Device.Processes.ActiveApplication.ProgramIdText.ToUpper();
|
||||
AmiiboWindow window = new(ViewModel.ShowAll, ViewModel.LastScannedAmiiboId, titleId);
|
||||
|
||||
await window.ShowDialog(Window);
|
||||
@ -148,13 +174,11 @@ namespace Ryujinx.Ava.UI.Views.Main
|
||||
return;
|
||||
}
|
||||
|
||||
ApplicationLoader application = ViewModel.AppHost.Device.Application;
|
||||
if (application != null)
|
||||
{
|
||||
await new CheatWindow(Window.VirtualFileSystem, application.TitleIdText, application.TitleName).ShowDialog(Window);
|
||||
string name = ViewModel.AppHost.Device.Processes.ActiveApplication.ApplicationControlProperties.Title[(int)ViewModel.AppHost.Device.System.State.DesiredTitleLanguage].NameString.ToString();
|
||||
|
||||
ViewModel.AppHost.Device.EnableCheats();
|
||||
}
|
||||
await new CheatWindow(Window.VirtualFileSystem, ViewModel.AppHost.Device.Processes.ActiveApplication.ProgramIdText, name).ShowDialog(Window);
|
||||
|
||||
ViewModel.AppHost.Device.EnableCheats();
|
||||
}
|
||||
|
||||
private void ScanAmiiboMenuItem_AttachedToVisualTree(object sender, VisualTreeAttachmentEventArgs e)
|
||||
|
@ -1,4 +1,4 @@
|
||||
<UserControl
|
||||
<UserControl
|
||||
x:Class="Ryujinx.Ava.UI.Views.Settings.SettingsCPUView"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
@ -65,8 +65,14 @@
|
||||
</ComboBoxItem>
|
||||
</ComboBox>
|
||||
</StackPanel>
|
||||
<CheckBox IsChecked="{Binding UseHypervisor}"
|
||||
IsVisible="{Binding IsHypervisorAvailable}"
|
||||
ToolTip.Tip="{locale:Locale UseHypervisorTooltip}">
|
||||
<TextBlock Text="{locale:Locale SettingsTabSystemUseHypervisor}"
|
||||
ToolTip.Tip="{locale:Locale UseHypervisorTooltip}" />
|
||||
</CheckBox>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</ScrollViewer>
|
||||
</UserControl>
|
||||
</UserControl>
|
||||
|
@ -7,6 +7,7 @@
|
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
|
||||
Design.Width="1000"
|
||||
mc:Ignorable="d"
|
||||
x:CompileBindings="True"
|
||||
x:DataType="viewModels:SettingsViewModel">
|
||||
@ -111,6 +112,83 @@
|
||||
Minimum="0.1"
|
||||
Value="{Binding CustomResolutionScale}" />
|
||||
</StackPanel>
|
||||
<StackPanel
|
||||
HorizontalAlignment="Stretch"
|
||||
Orientation="Vertical"
|
||||
Spacing="10">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center"
|
||||
ToolTip.Tip="{locale:Locale GraphicsAATooltip}"
|
||||
Text="{locale:Locale GraphicsAALabel}"
|
||||
Width="250" />
|
||||
<ComboBox Width="350"
|
||||
HorizontalContentAlignment="Left"
|
||||
ToolTip.Tip="{locale:Locale GraphicsAATooltip}"
|
||||
SelectedIndex="{Binding AntiAliasingEffect}">
|
||||
<ComboBoxItem>
|
||||
<TextBlock Text="{locale:Locale SettingsTabLoggingGraphicsBackendLogLevelNone}" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem>
|
||||
<TextBlock Text="FXAA" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem>
|
||||
<TextBlock Text="{locale:Locale SmaaLow}" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem>
|
||||
<TextBlock Text="{locale:Locale SmaaMedium}" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem>
|
||||
<TextBlock Text="{locale:Locale SmaaHigh}" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem>
|
||||
<TextBlock Text="{locale:Locale SmaaUltra}" />
|
||||
</ComboBoxItem>
|
||||
</ComboBox>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
<StackPanel
|
||||
HorizontalAlignment="Stretch"
|
||||
Orientation="Vertical"
|
||||
Spacing="10">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center"
|
||||
ToolTip.Tip="{locale:Locale GraphicsScalingFilterTooltip}"
|
||||
Text="{locale:Locale GraphicsScalingFilterLabel}"
|
||||
Width="250" />
|
||||
<ComboBox Width="350"
|
||||
HorizontalContentAlignment="Left"
|
||||
ToolTip.Tip="{locale:Locale GraphicsScalingFilterTooltip}"
|
||||
SelectedIndex="{Binding ScalingFilter}">
|
||||
<ComboBoxItem>
|
||||
<TextBlock Text="Bilinear" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem>
|
||||
<TextBlock Text="Nearest" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem>
|
||||
<TextBlock Text="FSR" />
|
||||
</ComboBoxItem>
|
||||
</ComboBox>
|
||||
<Slider Value="{Binding ScalingFilterLevel}"
|
||||
ToolTip.Tip="{locale:Locale GraphicsScalingFilterLevelTooltip}"
|
||||
MinWidth="150"
|
||||
Margin="10,-3,0,0"
|
||||
Height="32"
|
||||
Padding="0,-5"
|
||||
IsVisible="{Binding IsScalingFilterActive}"
|
||||
TickFrequency="1"
|
||||
IsSnapToTickEnabled="True"
|
||||
LargeChange="10"
|
||||
SmallChange="1"
|
||||
VerticalAlignment="Center"
|
||||
Minimum="0"
|
||||
Maximum="100" />
|
||||
<TextBlock Margin="5,0"
|
||||
Width="40"
|
||||
IsVisible="{Binding IsScalingFilterActive}"
|
||||
Text="{Binding ScalingFilterLevelText}"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center"
|
||||
ToolTip.Tip="{locale:Locale AnisotropyTooltip}"
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user