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 ####
|
#### C# Coding Conventions ####
|
||||||
|
|
||||||
|
# Namespace preferences
|
||||||
|
csharp_style_namespace_declarations = block_scoped:warning
|
||||||
|
resharper_csharp_namespace_body = block_scoped
|
||||||
|
|
||||||
# var preferences
|
# var preferences
|
||||||
csharp_style_var_elsewhere = false:silent
|
csharp_style_var_elsewhere = false:silent
|
||||||
csharp_style_var_for_built_in_types = 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
|
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:
|
jobs:
|
||||||
release:
|
release:
|
||||||
runs-on: windows-latest
|
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:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-dotnet@v3
|
- uses: actions/setup-dotnet@v3
|
||||||
with:
|
with:
|
||||||
dotnet-version: 7.0.x
|
global-json-file: global.json
|
||||||
- name: Get version info
|
- name: Get version info
|
||||||
id: version_info
|
id: version_info
|
||||||
run: |
|
run: |
|
||||||
@ -112,3 +111,21 @@ jobs:
|
|||||||
owner: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}
|
owner: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}
|
||||||
repo: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}
|
repo: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}
|
||||||
token: ${{ secrets.RELEASE_TOKEN }}
|
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>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
|
<ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
|
||||||
|
<ProjectReference Include="..\Ryujinx.Memory\Ryujinx.Memory.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using ARMeilleure.CodeGen.Linking;
|
using ARMeilleure.CodeGen.Linking;
|
||||||
using ARMeilleure.CodeGen.RegisterAllocators;
|
using ARMeilleure.CodeGen.RegisterAllocators;
|
||||||
using ARMeilleure.IntermediateRepresentation;
|
using ARMeilleure.IntermediateRepresentation;
|
||||||
|
using Ryujinx.Common.Memory;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
@ -59,7 +60,7 @@ namespace ARMeilleure.CodeGen.Arm64
|
|||||||
|
|
||||||
public CodeGenContext(AllocationResult allocResult, int maxCallArgs, int blocksCount, bool relocatable)
|
public CodeGenContext(AllocationResult allocResult, int maxCallArgs, int blocksCount, bool relocatable)
|
||||||
{
|
{
|
||||||
_stream = new MemoryStream();
|
_stream = MemoryStreamManager.Shared.GetStream();
|
||||||
|
|
||||||
AllocResult = allocResult;
|
AllocResult = allocResult;
|
||||||
|
|
||||||
@ -265,7 +266,7 @@ namespace ARMeilleure.CodeGen.Arm64
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
relocInfo = new RelocInfo(new RelocEntry[0]);
|
relocInfo = new RelocInfo(Array.Empty<RelocEntry>());
|
||||||
}
|
}
|
||||||
|
|
||||||
return (code, relocInfo);
|
return (code, relocInfo);
|
||||||
|
@ -226,6 +226,8 @@ namespace ARMeilleure.CodeGen.Arm64
|
|||||||
Add(Intrinsic.Arm64MlsVe, new IntrinsicInfo(0x2f004000u, IntrinsicType.VectorTernaryRdByElem));
|
Add(Intrinsic.Arm64MlsVe, new IntrinsicInfo(0x2f004000u, IntrinsicType.VectorTernaryRdByElem));
|
||||||
Add(Intrinsic.Arm64MlsV, new IntrinsicInfo(0x2e209400u, IntrinsicType.VectorTernaryRd));
|
Add(Intrinsic.Arm64MlsV, new IntrinsicInfo(0x2e209400u, IntrinsicType.VectorTernaryRd));
|
||||||
Add(Intrinsic.Arm64MoviV, new IntrinsicInfo(0x0f000400u, IntrinsicType.VectorMovi));
|
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.Arm64MrsFpsr, new IntrinsicInfo(0xd53b4420u, IntrinsicType.GetRegister));
|
||||||
Add(Intrinsic.Arm64MsrFpsr, new IntrinsicInfo(0xd51b4420u, IntrinsicType.SetRegister));
|
Add(Intrinsic.Arm64MsrFpsr, new IntrinsicInfo(0xd51b4420u, IntrinsicType.SetRegister));
|
||||||
Add(Intrinsic.Arm64MulVe, new IntrinsicInfo(0x0f008000u, IntrinsicType.VectorBinaryByElem));
|
Add(Intrinsic.Arm64MulVe, new IntrinsicInfo(0x0f008000u, IntrinsicType.VectorBinaryByElem));
|
||||||
|
@ -9,7 +9,7 @@ using static ARMeilleure.IntermediateRepresentation.Operation.Factory;
|
|||||||
|
|
||||||
namespace ARMeilleure.CodeGen.Arm64
|
namespace ARMeilleure.CodeGen.Arm64
|
||||||
{
|
{
|
||||||
class PreAllocator
|
static class PreAllocator
|
||||||
{
|
{
|
||||||
private class ConstantDict
|
private class ConstantDict
|
||||||
{
|
{
|
||||||
@ -54,8 +54,8 @@ namespace ARMeilleure.CodeGen.Arm64
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
HandleConstantRegCopy(constants, block.Operations, node);
|
InsertConstantRegCopies(constants, block.Operations, node);
|
||||||
HandleDestructiveRegCopy(block.Operations, node);
|
InsertDestructiveRegCopies(block.Operations, node);
|
||||||
|
|
||||||
switch (node.Instruction)
|
switch (node.Instruction)
|
||||||
{
|
{
|
||||||
@ -78,28 +78,28 @@ namespace ARMeilleure.CodeGen.Arm64
|
|||||||
|
|
||||||
// Copy values to registers expected by the function
|
// Copy values to registers expected by the function
|
||||||
// being called, as mandated by the ABI.
|
// being called, as mandated by the ABI.
|
||||||
HandleCall(constants, block.Operations, node);
|
InsertCallCopies(constants, block.Operations, node);
|
||||||
break;
|
break;
|
||||||
case Instruction.CompareAndSwap:
|
case Instruction.CompareAndSwap:
|
||||||
case Instruction.CompareAndSwap16:
|
case Instruction.CompareAndSwap16:
|
||||||
case Instruction.CompareAndSwap8:
|
case Instruction.CompareAndSwap8:
|
||||||
nextNode = HandleCompareAndSwap(block.Operations, node);
|
nextNode = GenerateCompareAndSwap(block.Operations, node);
|
||||||
break;
|
break;
|
||||||
case Instruction.LoadArgument:
|
case Instruction.LoadArgument:
|
||||||
nextNode = HandleLoadArgument(cctx, ref buffer, block.Operations, preservedArgs, node);
|
nextNode = InsertLoadArgumentCopy(cctx, ref buffer, block.Operations, preservedArgs, node);
|
||||||
break;
|
break;
|
||||||
case Instruction.Return:
|
case Instruction.Return:
|
||||||
HandleReturn(block.Operations, node);
|
InsertReturnCopy(block.Operations, node);
|
||||||
break;
|
break;
|
||||||
case Instruction.Tailcall:
|
case Instruction.Tailcall:
|
||||||
HandleTailcall(constants, block.Operations, stackAlloc, node, node);
|
InsertTailcallCopies(constants, block.Operations, stackAlloc, node, node);
|
||||||
break;
|
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))
|
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)
|
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;
|
Operation operation = node;
|
||||||
|
|
||||||
@ -319,7 +319,7 @@ namespace ARMeilleure.CodeGen.Arm64
|
|||||||
|
|
||||||
Operation copyOp = Operation(Instruction.Copy, argReg, source);
|
Operation copyOp = Operation(Instruction.Copy, argReg, source);
|
||||||
|
|
||||||
HandleConstantRegCopy(constants, nodes, nodes.AddBefore(node, copyOp));
|
InsertConstantRegCopies(constants, nodes, nodes.AddBefore(node, copyOp));
|
||||||
|
|
||||||
sources.Add(argReg);
|
sources.Add(argReg);
|
||||||
}
|
}
|
||||||
@ -329,7 +329,7 @@ namespace ARMeilleure.CodeGen.Arm64
|
|||||||
|
|
||||||
Operation spillOp = Operation(Instruction.SpillArg, default, offset, source);
|
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();
|
stackOffset += source.Type.GetSizeInBytes();
|
||||||
}
|
}
|
||||||
@ -364,7 +364,7 @@ namespace ARMeilleure.CodeGen.Arm64
|
|||||||
operation.SetSources(sources.ToArray());
|
operation.SetSources(sources.ToArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void HandleTailcall(
|
private static void InsertTailcallCopies(
|
||||||
ConstantDict constants,
|
ConstantDict constants,
|
||||||
IntrusiveList<Operation> nodes,
|
IntrusiveList<Operation> nodes,
|
||||||
StackAllocator stackAlloc,
|
StackAllocator stackAlloc,
|
||||||
@ -420,7 +420,7 @@ namespace ARMeilleure.CodeGen.Arm64
|
|||||||
|
|
||||||
Operation copyOp = Operation(Instruction.Copy, argReg, source);
|
Operation copyOp = Operation(Instruction.Copy, argReg, source);
|
||||||
|
|
||||||
HandleConstantRegCopy(constants, nodes, nodes.AddBefore(node, copyOp));
|
InsertConstantRegCopies(constants, nodes, nodes.AddBefore(node, copyOp));
|
||||||
|
|
||||||
sources.Add(argReg);
|
sources.Add(argReg);
|
||||||
}
|
}
|
||||||
@ -444,7 +444,7 @@ namespace ARMeilleure.CodeGen.Arm64
|
|||||||
operation.SetSources(sources.ToArray());
|
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);
|
Operand expected = node.GetSource(1);
|
||||||
|
|
||||||
@ -508,7 +508,7 @@ namespace ARMeilleure.CodeGen.Arm64
|
|||||||
return node.ListNext;
|
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)
|
if (node.SourcesCount == 0)
|
||||||
{
|
{
|
||||||
@ -537,7 +537,7 @@ namespace ARMeilleure.CodeGen.Arm64
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Operation HandleLoadArgument(
|
private static Operation InsertLoadArgumentCopy(
|
||||||
CompilerContext cctx,
|
CompilerContext cctx,
|
||||||
ref Span<Operation> buffer,
|
ref Span<Operation> buffer,
|
||||||
IntrusiveList<Operation> nodes,
|
IntrusiveList<Operation> nodes,
|
||||||
@ -629,7 +629,7 @@ namespace ARMeilleure.CodeGen.Arm64
|
|||||||
if (dest.AssignmentsCount == 1)
|
if (dest.AssignmentsCount == 1)
|
||||||
{
|
{
|
||||||
// Let's propagate the argument if we can to avoid copies.
|
// 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;
|
nextNode = node.ListNext;
|
||||||
}
|
}
|
||||||
else
|
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(
|
private static Operand AddFloatConstantCopy(
|
||||||
ConstantDict constants,
|
ConstantDict constants,
|
||||||
IntrusiveList<Operation> nodes,
|
IntrusiveList<Operation> nodes,
|
||||||
|
@ -48,9 +48,21 @@ namespace ARMeilleure.CodeGen
|
|||||||
/// <returns>A delegate of type <typeparamref name="T"/> pointing to the mapped function</returns>
|
/// <returns>A delegate of type <typeparamref name="T"/> pointing to the mapped function</returns>
|
||||||
public T Map<T>()
|
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)
|
private static int GetHighestValueIndex(Span<int> span)
|
||||||
{
|
{
|
||||||
int highest = span[0];
|
int highest = int.MinValue;
|
||||||
|
|
||||||
if (highest == int.MaxValue)
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int selected = 0;
|
int selected = 0;
|
||||||
|
|
||||||
for (int index = 1; index < span.Length; index++)
|
for (int index = 0; index < span.Length; index++)
|
||||||
{
|
{
|
||||||
int current = span[index];
|
int current = span[index];
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
using ARMeilleure.CodeGen.Linking;
|
using ARMeilleure.CodeGen.Linking;
|
||||||
using ARMeilleure.IntermediateRepresentation;
|
using ARMeilleure.IntermediateRepresentation;
|
||||||
|
using Ryujinx.Common.Memory;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
@ -1033,7 +1034,13 @@ namespace ARMeilleure.CodeGen.X86
|
|||||||
|
|
||||||
Debug.Assert(opCode != BadOp, "Invalid opcode value.");
|
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.
|
// 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)
|
private void WriteCompactInst(Operand operand, int opCode)
|
||||||
{
|
{
|
||||||
int regIndex = operand.GetRegister().Index;
|
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.
|
// Write the code, ignoring the dummy bytes after jumps, into a new stream.
|
||||||
_stream.Seek(0, SeekOrigin.Begin);
|
_stream.Seek(0, SeekOrigin.Begin);
|
||||||
|
|
||||||
using var codeStream = new MemoryStream();
|
using var codeStream = MemoryStreamManager.Shared.GetStream();
|
||||||
var assembler = new Assembler(codeStream, HasRelocs);
|
var assembler = new Assembler(codeStream, HasRelocs);
|
||||||
|
|
||||||
bool hasRelocs = HasRelocs;
|
bool hasRelocs = HasRelocs;
|
||||||
|
@ -20,6 +20,7 @@ namespace ARMeilleure.CodeGen.X86
|
|||||||
Reg8Dest = 1 << 2,
|
Reg8Dest = 1 << 2,
|
||||||
RexW = 1 << 3,
|
RexW = 1 << 3,
|
||||||
Vex = 1 << 4,
|
Vex = 1 << 4,
|
||||||
|
Evex = 1 << 5,
|
||||||
|
|
||||||
PrefixBit = 16,
|
PrefixBit = 16,
|
||||||
PrefixMask = 7 << PrefixBit,
|
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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.Xorpd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f57, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||||
Add(X86Instruction.Xorps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f57, InstructionFlags.Vex));
|
Add(X86Instruction.Xorps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f57, InstructionFlags.Vex));
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
using ARMeilleure.CodeGen.RegisterAllocators;
|
using ARMeilleure.CodeGen.RegisterAllocators;
|
||||||
using ARMeilleure.IntermediateRepresentation;
|
using ARMeilleure.IntermediateRepresentation;
|
||||||
|
using Ryujinx.Common.Memory;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
|
||||||
@ -22,7 +23,7 @@ namespace ARMeilleure.CodeGen.X86
|
|||||||
|
|
||||||
public CodeGenContext(AllocationResult allocResult, int maxCallArgs, int blocksCount, bool relocatable)
|
public CodeGenContext(AllocationResult allocResult, int maxCallArgs, int blocksCount, bool relocatable)
|
||||||
{
|
{
|
||||||
_stream = new MemoryStream();
|
_stream = MemoryStreamManager.Shared.GetStream();
|
||||||
_blockLabels = new Operand[blocksCount];
|
_blockLabels = new Operand[blocksCount];
|
||||||
|
|
||||||
AllocResult = allocResult;
|
AllocResult = allocResult;
|
||||||
|
@ -249,10 +249,9 @@ namespace ARMeilleure.CodeGen.X86
|
|||||||
case IntrinsicType.Mxcsr:
|
case IntrinsicType.Mxcsr:
|
||||||
{
|
{
|
||||||
Operand offset = operation.GetSource(0);
|
Operand offset = operation.GetSource(0);
|
||||||
Operand bits = operation.GetSource(1);
|
|
||||||
|
|
||||||
Debug.Assert(offset.Kind == OperandKind.Constant && bits.Kind == OperandKind.Constant);
|
Debug.Assert(offset.Kind == OperandKind.Constant);
|
||||||
Debug.Assert(offset.Type == OperandType.I32 && bits.Type == OperandType.I32);
|
Debug.Assert(offset.Type == OperandType.I32);
|
||||||
|
|
||||||
int offs = offset.AsInt32() + context.CallArgsRegionSize;
|
int offs = offset.AsInt32() + context.CallArgsRegionSize;
|
||||||
|
|
||||||
@ -261,21 +260,23 @@ namespace ARMeilleure.CodeGen.X86
|
|||||||
|
|
||||||
Debug.Assert(HardwareCapabilities.SupportsSse || HardwareCapabilities.SupportsVexEncoding);
|
Debug.Assert(HardwareCapabilities.SupportsSse || HardwareCapabilities.SupportsVexEncoding);
|
||||||
|
|
||||||
context.Assembler.Stmxcsr(memOp);
|
if (operation.Intrinsic == Intrinsic.X86Ldmxcsr)
|
||||||
|
|
||||||
if (operation.Intrinsic == Intrinsic.X86Mxcsrmb)
|
|
||||||
{
|
{
|
||||||
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;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,10 +1,14 @@
|
|||||||
|
using Ryujinx.Memory;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
using System.Runtime.Intrinsics.X86;
|
using System.Runtime.Intrinsics.X86;
|
||||||
|
|
||||||
namespace ARMeilleure.CodeGen.X86
|
namespace ARMeilleure.CodeGen.X86
|
||||||
{
|
{
|
||||||
static class HardwareCapabilities
|
static class HardwareCapabilities
|
||||||
{
|
{
|
||||||
|
private delegate uint GetXcr0();
|
||||||
|
|
||||||
static HardwareCapabilities()
|
static HardwareCapabilities()
|
||||||
{
|
{
|
||||||
if (!X86Base.IsSupported)
|
if (!X86Base.IsSupported)
|
||||||
@ -24,6 +28,34 @@ namespace ARMeilleure.CodeGen.X86
|
|||||||
FeatureInfo7Ebx = (FeatureFlags7Ebx)ebx7;
|
FeatureInfo7Ebx = (FeatureFlags7Ebx)ebx7;
|
||||||
FeatureInfo7Ecx = (FeatureFlags7Ecx)ecx7;
|
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]
|
[Flags]
|
||||||
@ -44,6 +76,8 @@ namespace ARMeilleure.CodeGen.X86
|
|||||||
Sse42 = 1 << 20,
|
Sse42 = 1 << 20,
|
||||||
Popcnt = 1 << 23,
|
Popcnt = 1 << 23,
|
||||||
Aes = 1 << 25,
|
Aes = 1 << 25,
|
||||||
|
Xsave = 1 << 26,
|
||||||
|
Osxsave = 1 << 27,
|
||||||
Avx = 1 << 28,
|
Avx = 1 << 28,
|
||||||
F16c = 1 << 29
|
F16c = 1 << 29
|
||||||
}
|
}
|
||||||
@ -52,7 +86,11 @@ namespace ARMeilleure.CodeGen.X86
|
|||||||
public enum FeatureFlags7Ebx
|
public enum FeatureFlags7Ebx
|
||||||
{
|
{
|
||||||
Avx2 = 1 << 5,
|
Avx2 = 1 << 5,
|
||||||
Sha = 1 << 29
|
Avx512f = 1 << 16,
|
||||||
|
Avx512dq = 1 << 17,
|
||||||
|
Sha = 1 << 29,
|
||||||
|
Avx512bw = 1 << 30,
|
||||||
|
Avx512vl = 1 << 31
|
||||||
}
|
}
|
||||||
|
|
||||||
[Flags]
|
[Flags]
|
||||||
@ -61,10 +99,21 @@ namespace ARMeilleure.CodeGen.X86
|
|||||||
Gfni = 1 << 8,
|
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 FeatureFlags1Edx FeatureInfo1Edx { get; }
|
||||||
public static FeatureFlags1Ecx FeatureInfo1Ecx { get; }
|
public static FeatureFlags1Ecx FeatureInfo1Ecx { get; }
|
||||||
public static FeatureFlags7Ebx FeatureInfo7Ebx { get; } = 0;
|
public static FeatureFlags7Ebx FeatureInfo7Ebx { get; } = 0;
|
||||||
public static FeatureFlags7Ecx FeatureInfo7Ecx { 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 SupportsSse => FeatureInfo1Edx.HasFlag(FeatureFlags1Edx.Sse);
|
||||||
public static bool SupportsSse2 => FeatureInfo1Edx.HasFlag(FeatureFlags1Edx.Sse2);
|
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 SupportsSse42 => FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Sse42);
|
||||||
public static bool SupportsPopcnt => FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Popcnt);
|
public static bool SupportsPopcnt => FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Popcnt);
|
||||||
public static bool SupportsAesni => FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Aes);
|
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 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 SupportsF16c => FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.F16c);
|
||||||
public static bool SupportsSha => FeatureInfo7Ebx.HasFlag(FeatureFlags7Ebx.Sha);
|
public static bool SupportsSha => FeatureInfo7Ebx.HasFlag(FeatureFlags7Ebx.Sha);
|
||||||
public static bool SupportsGfni => FeatureInfo7Ecx.HasFlag(FeatureFlags7Ecx.Gfni);
|
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 ForceLegacySse { get; set; }
|
||||||
|
|
||||||
public static bool SupportsVexEncoding => SupportsAvx && !ForceLegacySse;
|
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.X86Haddpd, new IntrinsicInfo(X86Instruction.Haddpd, IntrinsicType.Binary));
|
||||||
Add(Intrinsic.X86Haddps, new IntrinsicInfo(X86Instruction.Haddps, IntrinsicType.Binary));
|
Add(Intrinsic.X86Haddps, new IntrinsicInfo(X86Instruction.Haddps, IntrinsicType.Binary));
|
||||||
Add(Intrinsic.X86Insertps, new IntrinsicInfo(X86Instruction.Insertps, IntrinsicType.TernaryImm));
|
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.X86Maxpd, new IntrinsicInfo(X86Instruction.Maxpd, IntrinsicType.Binary));
|
||||||
Add(Intrinsic.X86Maxps, new IntrinsicInfo(X86Instruction.Maxps, IntrinsicType.Binary));
|
Add(Intrinsic.X86Maxps, new IntrinsicInfo(X86Instruction.Maxps, IntrinsicType.Binary));
|
||||||
Add(Intrinsic.X86Maxsd, new IntrinsicInfo(X86Instruction.Maxsd, 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.X86Mulps, new IntrinsicInfo(X86Instruction.Mulps, IntrinsicType.Binary));
|
||||||
Add(Intrinsic.X86Mulsd, new IntrinsicInfo(X86Instruction.Mulsd, IntrinsicType.Binary));
|
Add(Intrinsic.X86Mulsd, new IntrinsicInfo(X86Instruction.Mulsd, IntrinsicType.Binary));
|
||||||
Add(Intrinsic.X86Mulss, new IntrinsicInfo(X86Instruction.Mulss, 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.X86Paddb, new IntrinsicInfo(X86Instruction.Paddb, IntrinsicType.Binary));
|
||||||
Add(Intrinsic.X86Paddd, new IntrinsicInfo(X86Instruction.Paddd, IntrinsicType.Binary));
|
Add(Intrinsic.X86Paddd, new IntrinsicInfo(X86Instruction.Paddd, IntrinsicType.Binary));
|
||||||
Add(Intrinsic.X86Paddq, new IntrinsicInfo(X86Instruction.Paddq, 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.X86Sqrtps, new IntrinsicInfo(X86Instruction.Sqrtps, IntrinsicType.Unary));
|
||||||
Add(Intrinsic.X86Sqrtsd, new IntrinsicInfo(X86Instruction.Sqrtsd, IntrinsicType.Unary));
|
Add(Intrinsic.X86Sqrtsd, new IntrinsicInfo(X86Instruction.Sqrtsd, IntrinsicType.Unary));
|
||||||
Add(Intrinsic.X86Sqrtss, new IntrinsicInfo(X86Instruction.Sqrtss, 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.X86Subpd, new IntrinsicInfo(X86Instruction.Subpd, IntrinsicType.Binary));
|
||||||
Add(Intrinsic.X86Subps, new IntrinsicInfo(X86Instruction.Subps, IntrinsicType.Binary));
|
Add(Intrinsic.X86Subps, new IntrinsicInfo(X86Instruction.Subps, IntrinsicType.Binary));
|
||||||
Add(Intrinsic.X86Subsd, new IntrinsicInfo(X86Instruction.Subsd, 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.X86Unpcklps, new IntrinsicInfo(X86Instruction.Unpcklps, IntrinsicType.Binary));
|
||||||
Add(Intrinsic.X86Vcvtph2ps, new IntrinsicInfo(X86Instruction.Vcvtph2ps, IntrinsicType.Unary));
|
Add(Intrinsic.X86Vcvtph2ps, new IntrinsicInfo(X86Instruction.Vcvtph2ps, IntrinsicType.Unary));
|
||||||
Add(Intrinsic.X86Vcvtps2ph, new IntrinsicInfo(X86Instruction.Vcvtps2ph, IntrinsicType.BinaryImm));
|
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.X86Vfmadd231ps, new IntrinsicInfo(X86Instruction.Vfmadd231ps, IntrinsicType.Fma));
|
||||||
Add(Intrinsic.X86Vfmadd231sd, new IntrinsicInfo(X86Instruction.Vfmadd231sd, IntrinsicType.Fma));
|
Add(Intrinsic.X86Vfmadd231sd, new IntrinsicInfo(X86Instruction.Vfmadd231sd, IntrinsicType.Fma));
|
||||||
Add(Intrinsic.X86Vfmadd231ss, new IntrinsicInfo(X86Instruction.Vfmadd231ss, IntrinsicType.Fma));
|
Add(Intrinsic.X86Vfmadd231ss, new IntrinsicInfo(X86Instruction.Vfmadd231ss, IntrinsicType.Fma));
|
||||||
Add(Intrinsic.X86Vfmsub231sd, new IntrinsicInfo(X86Instruction.Vfmsub231sd, IntrinsicType.Fma));
|
Add(Intrinsic.X86Vfmsub231sd, new IntrinsicInfo(X86Instruction.Vfmsub231sd, IntrinsicType.Fma));
|
||||||
Add(Intrinsic.X86Vfmsub231ss, new IntrinsicInfo(X86Instruction.Vfmsub231ss, 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.X86Vfnmadd231ps, new IntrinsicInfo(X86Instruction.Vfnmadd231ps, IntrinsicType.Fma));
|
||||||
Add(Intrinsic.X86Vfnmadd231sd, new IntrinsicInfo(X86Instruction.Vfnmadd231sd, IntrinsicType.Fma));
|
Add(Intrinsic.X86Vfnmadd231sd, new IntrinsicInfo(X86Instruction.Vfnmadd231sd, IntrinsicType.Fma));
|
||||||
Add(Intrinsic.X86Vfnmadd231ss, new IntrinsicInfo(X86Instruction.Vfnmadd231ss, IntrinsicType.Fma));
|
Add(Intrinsic.X86Vfnmadd231ss, new IntrinsicInfo(X86Instruction.Vfnmadd231ss, IntrinsicType.Fma));
|
||||||
Add(Intrinsic.X86Vfnmsub231sd, new IntrinsicInfo(X86Instruction.Vfnmsub231sd, IntrinsicType.Fma));
|
Add(Intrinsic.X86Vfnmsub231sd, new IntrinsicInfo(X86Instruction.Vfnmsub231sd, IntrinsicType.Fma));
|
||||||
Add(Intrinsic.X86Vfnmsub231ss, new IntrinsicInfo(X86Instruction.Vfnmsub231ss, 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.X86Xorpd, new IntrinsicInfo(X86Instruction.Xorpd, IntrinsicType.Binary));
|
||||||
Add(Intrinsic.X86Xorps, new IntrinsicInfo(X86Instruction.Xorps, 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.IntermediateRepresentation;
|
||||||
using ARMeilleure.Translation;
|
using ARMeilleure.Translation;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using static ARMeilleure.IntermediateRepresentation.Operand.Factory;
|
using static ARMeilleure.IntermediateRepresentation.Operand.Factory;
|
||||||
using static ARMeilleure.IntermediateRepresentation.Operation.Factory;
|
using static ARMeilleure.IntermediateRepresentation.Operation.Factory;
|
||||||
|
|
||||||
namespace ARMeilleure.CodeGen.X86
|
namespace ARMeilleure.CodeGen.X86
|
||||||
{
|
{
|
||||||
static class PreAllocator
|
class PreAllocator
|
||||||
{
|
{
|
||||||
public static void RunPass(CompilerContext cctx, StackAllocator stackAlloc, out int maxCallArgs)
|
public static void RunPass(CompilerContext cctx, StackAllocator stackAlloc, out int maxCallArgs)
|
||||||
{
|
{
|
||||||
maxCallArgs = -1;
|
maxCallArgs = -1;
|
||||||
|
|
||||||
|
Span<Operation> buffer = default;
|
||||||
|
|
||||||
CallConvName callConv = CallingConvention.GetCurrentCallConv();
|
CallConvName callConv = CallingConvention.GetCurrentCallConv();
|
||||||
|
|
||||||
Operand[] preservedArgs = new Operand[CallingConvention.GetArgumentsOnRegsCount()];
|
Operand[] preservedArgs = new Operand[CallingConvention.GetArgumentsOnRegsCount()];
|
||||||
@ -32,9 +33,9 @@ namespace ARMeilleure.CodeGen.X86
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
HandleConstantRegCopy(block.Operations, node);
|
InsertConstantRegCopies(block.Operations, node);
|
||||||
HandleDestructiveRegCopy(block.Operations, node);
|
InsertDestructiveRegCopies(block.Operations, node);
|
||||||
HandleConstrainedRegCopy(block.Operations, node);
|
InsertConstrainedRegCopies(block.Operations, node);
|
||||||
|
|
||||||
switch (node.Instruction)
|
switch (node.Instruction)
|
||||||
{
|
{
|
||||||
@ -59,79 +60,85 @@ namespace ARMeilleure.CodeGen.X86
|
|||||||
// being called, as mandated by the ABI.
|
// being called, as mandated by the ABI.
|
||||||
if (callConv == CallConvName.Windows)
|
if (callConv == CallConvName.Windows)
|
||||||
{
|
{
|
||||||
HandleCallWindowsAbi(block.Operations, stackAlloc, node);
|
PreAllocatorWindows.InsertCallCopies(block.Operations, stackAlloc, node);
|
||||||
}
|
}
|
||||||
else /* if (callConv == CallConvName.SystemV) */
|
else /* if (callConv == CallConvName.SystemV) */
|
||||||
{
|
{
|
||||||
HandleCallSystemVAbi(block.Operations, node);
|
PreAllocatorSystemV.InsertCallCopies(block.Operations, node);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Instruction.ConvertToFPUI:
|
case Instruction.ConvertToFPUI:
|
||||||
HandleConvertToFPUI(block.Operations, node);
|
GenerateConvertToFPUI(block.Operations, node);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Instruction.LoadArgument:
|
case Instruction.LoadArgument:
|
||||||
if (callConv == CallConvName.Windows)
|
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) */
|
else /* if (callConv == CallConvName.SystemV) */
|
||||||
{
|
{
|
||||||
nextNode = HandleLoadArgumentSystemVAbi(cctx, block.Operations, preservedArgs, node);
|
nextNode = PreAllocatorSystemV.InsertLoadArgumentCopy(cctx, ref buffer, block.Operations, preservedArgs, node);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Instruction.Negate:
|
case Instruction.Negate:
|
||||||
if (!node.GetSource(0).Type.IsInteger())
|
if (!node.GetSource(0).Type.IsInteger())
|
||||||
{
|
{
|
||||||
HandleNegate(block.Operations, node);
|
GenerateNegate(block.Operations, node);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Instruction.Return:
|
case Instruction.Return:
|
||||||
if (callConv == CallConvName.Windows)
|
if (callConv == CallConvName.Windows)
|
||||||
{
|
{
|
||||||
HandleReturnWindowsAbi(cctx, block.Operations, preservedArgs, node);
|
PreAllocatorWindows.InsertReturnCopy(cctx, block.Operations, preservedArgs, node);
|
||||||
}
|
}
|
||||||
else /* if (callConv == CallConvName.SystemV) */
|
else /* if (callConv == CallConvName.SystemV) */
|
||||||
{
|
{
|
||||||
HandleReturnSystemVAbi(block.Operations, node);
|
PreAllocatorSystemV.InsertReturnCopy(block.Operations, node);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Instruction.Tailcall:
|
case Instruction.Tailcall:
|
||||||
if (callConv == CallConvName.Windows)
|
if (callConv == CallConvName.Windows)
|
||||||
{
|
{
|
||||||
HandleTailcallWindowsAbi(block.Operations, stackAlloc, node);
|
PreAllocatorWindows.InsertTailcallCopies(block.Operations, stackAlloc, node);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
HandleTailcallSystemVAbi(block.Operations, stackAlloc, node);
|
PreAllocatorSystemV.InsertTailcallCopies(block.Operations, stackAlloc, node);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Instruction.VectorInsert8:
|
case Instruction.VectorInsert8:
|
||||||
if (!HardwareCapabilities.SupportsSse41)
|
if (!HardwareCapabilities.SupportsSse41)
|
||||||
{
|
{
|
||||||
HandleVectorInsert8(block.Operations, node);
|
GenerateVectorInsert8(block.Operations, node);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Instruction.Extended:
|
case Instruction.Extended:
|
||||||
if (node.Intrinsic == Intrinsic.X86Mxcsrmb || node.Intrinsic == Intrinsic.X86Mxcsrub)
|
if (node.Intrinsic == Intrinsic.X86Ldmxcsr)
|
||||||
{
|
{
|
||||||
int stackOffset = stackAlloc.Allocate(OperandType.I32);
|
int stackOffset = stackAlloc.Allocate(OperandType.I32);
|
||||||
|
|
||||||
node.SetSources(new Operand[] { Const(stackOffset), node.GetSource(0) });
|
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;
|
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))
|
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;
|
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)
|
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.
|
// Unsigned integer to FP conversions are not supported on X86.
|
||||||
// We need to turn them into signed integer to FP conversions, and
|
// We need to turn them into signed integer to FP conversions, and
|
||||||
@ -501,7 +508,7 @@ namespace ARMeilleure.CodeGen.X86
|
|||||||
Delete(nodes, currentNode);
|
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
|
// 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.
|
// 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);
|
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.
|
// Handle vector insertion, when SSE 4.1 is not supported.
|
||||||
Operand dest = node.Destination;
|
Operand dest = node.Destination;
|
||||||
@ -579,620 +586,7 @@ namespace ARMeilleure.CodeGen.X86
|
|||||||
Delete(nodes, currentNode);
|
Delete(nodes, currentNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void HandleCallWindowsAbi(IntrusiveList<Operation> nodes, StackAllocator stackAlloc, Operation node)
|
protected static Operand AddXmmCopy(IntrusiveList<Operation> nodes, Operation node, Operand source)
|
||||||
{
|
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
Operand temp = Local(source.Type);
|
Operand temp = Local(source.Type);
|
||||||
Operand intConst = AddCopy(nodes, node, GetIntConst(source));
|
Operand intConst = AddCopy(nodes, node, GetIntConst(source));
|
||||||
@ -1204,7 +598,7 @@ namespace ARMeilleure.CodeGen.X86
|
|||||||
return temp;
|
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);
|
Operand temp = Local(source.Type);
|
||||||
|
|
||||||
@ -1229,7 +623,7 @@ namespace ARMeilleure.CodeGen.X86
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void Delete(IntrusiveList<Operation> nodes, Operation node)
|
protected static void Delete(IntrusiveList<Operation> nodes, Operation node)
|
||||||
{
|
{
|
||||||
node.Destination = default;
|
node.Destination = default;
|
||||||
|
|
||||||
@ -1241,12 +635,12 @@ namespace ARMeilleure.CodeGen.X86
|
|||||||
nodes.Remove(node);
|
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);
|
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);
|
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,
|
Vblendvps,
|
||||||
Vcvtph2ps,
|
Vcvtph2ps,
|
||||||
Vcvtps2ph,
|
Vcvtps2ph,
|
||||||
|
Vfmadd231pd,
|
||||||
Vfmadd231ps,
|
Vfmadd231ps,
|
||||||
Vfmadd231sd,
|
Vfmadd231sd,
|
||||||
Vfmadd231ss,
|
Vfmadd231ss,
|
||||||
Vfmsub231sd,
|
Vfmsub231sd,
|
||||||
Vfmsub231ss,
|
Vfmsub231ss,
|
||||||
|
Vfnmadd231pd,
|
||||||
Vfnmadd231ps,
|
Vfnmadd231ps,
|
||||||
Vfnmadd231sd,
|
Vfnmadd231sd,
|
||||||
Vfnmadd231ss,
|
Vfnmadd231ss,
|
||||||
Vfnmsub231sd,
|
Vfnmsub231sd,
|
||||||
Vfnmsub231ss,
|
Vfnmsub231ss,
|
||||||
Vpblendvb,
|
Vpblendvb,
|
||||||
|
Vpternlogd,
|
||||||
Xor,
|
Xor,
|
||||||
Xorpd,
|
Xorpd,
|
||||||
Xorps,
|
Xorps,
|
||||||
|
@ -17,7 +17,7 @@ namespace ARMeilleure.Decoders
|
|||||||
{
|
{
|
||||||
uint[] tbl = new uint[256];
|
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);
|
tbl[idx] = ExpandImm8ToFP32((uint)idx);
|
||||||
}
|
}
|
||||||
@ -29,7 +29,7 @@ namespace ARMeilleure.Decoders
|
|||||||
{
|
{
|
||||||
ulong[] tbl = new ulong[256];
|
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);
|
tbl[idx] = ExpandImm8ToFP64((ulong)idx);
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
namespace ARMeilleure.Decoders;
|
namespace ARMeilleure.Decoders
|
||||||
|
|
||||||
interface IOpCode32Exception
|
|
||||||
{
|
{
|
||||||
int Id { get; }
|
interface IOpCode32Exception
|
||||||
|
{
|
||||||
|
int Id { get; }
|
||||||
|
}
|
||||||
}
|
}
|
@ -108,6 +108,13 @@ namespace ARMeilleure.Decoders
|
|||||||
SetA64("11001010xx0xxxxxxxxxxxxxxxxxxxxx", InstName.Eor, InstEmit.Eor, OpCodeAluRs.Create);
|
SetA64("11001010xx0xxxxxxxxxxxxxxxxxxxxx", InstName.Eor, InstEmit.Eor, OpCodeAluRs.Create);
|
||||||
SetA64("00010011100xxxxx0xxxxxxxxxxxxxxx", InstName.Extr, InstEmit.Extr, OpCodeAluRs.Create);
|
SetA64("00010011100xxxxx0xxxxxxxxxxxxxxx", InstName.Extr, InstEmit.Extr, OpCodeAluRs.Create);
|
||||||
SetA64("10010011110xxxxxxxxxxxxxxxxxxxxx", 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("11010101000000110011xxxx11011111", InstName.Isb, InstEmit.Isb, OpCodeSystem.Create);
|
||||||
SetA64("xx001000110xxxxx1xxxxxxxxxxxxxxx", InstName.Ldar, InstEmit.Ldar, OpCodeMemEx.Create);
|
SetA64("xx001000110xxxxx1xxxxxxxxxxxxxxx", InstName.Ldar, InstEmit.Ldar, OpCodeMemEx.Create);
|
||||||
SetA64("1x001000011xxxxx1xxxxxxxxxxxxxxx", InstName.Ldaxp, InstEmit.Ldaxp, OpCodeMemEx.Create);
|
SetA64("1x001000011xxxxx1xxxxxxxxxxxxxxx", InstName.Ldaxp, InstEmit.Ldaxp, OpCodeMemEx.Create);
|
||||||
@ -1301,7 +1308,7 @@ namespace ARMeilleure.Decoders
|
|||||||
{
|
{
|
||||||
List<InstInfo>[] temp = new List<InstInfo>[FastLookupSize];
|
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>();
|
temp[index] = new List<InstInfo>();
|
||||||
}
|
}
|
||||||
@ -1311,7 +1318,7 @@ namespace ARMeilleure.Decoders
|
|||||||
int mask = ToFastLookupIndex(inst.Mask);
|
int mask = ToFastLookupIndex(inst.Mask);
|
||||||
int value = ToFastLookupIndex(inst.Value);
|
int value = ToFastLookupIndex(inst.Value);
|
||||||
|
|
||||||
for (int index = 0; index < FastLookupSize; index++)
|
for (int index = 0; index < temp.Length; index++)
|
||||||
{
|
{
|
||||||
if ((index & mask) == value)
|
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();
|
table[index] = temp[index].ToArray();
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
using ARMeilleure.Decoders;
|
using ARMeilleure.Decoders;
|
||||||
using ARMeilleure.IntermediateRepresentation;
|
using ARMeilleure.IntermediateRepresentation;
|
||||||
using ARMeilleure.Translation;
|
using ARMeilleure.Translation;
|
||||||
|
|
||||||
using static ARMeilleure.Instructions.InstEmitHelper;
|
|
||||||
using static ARMeilleure.Instructions.InstEmitHashHelper;
|
using static ARMeilleure.Instructions.InstEmitHashHelper;
|
||||||
|
using static ARMeilleure.Instructions.InstEmitHelper;
|
||||||
|
|
||||||
namespace ARMeilleure.Instructions
|
namespace ARMeilleure.Instructions
|
||||||
{
|
{
|
||||||
|
@ -4,9 +4,8 @@ using ARMeilleure.IntermediateRepresentation;
|
|||||||
using ARMeilleure.Translation;
|
using ARMeilleure.Translation;
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
|
||||||
using static ARMeilleure.IntermediateRepresentation.Operand.Factory;
|
|
||||||
using static ARMeilleure.Instructions.InstEmitSimdHelper;
|
using static ARMeilleure.Instructions.InstEmitSimdHelper;
|
||||||
|
using static ARMeilleure.IntermediateRepresentation.Operand.Factory;
|
||||||
|
|
||||||
namespace ARMeilleure.Instructions
|
namespace ARMeilleure.Instructions
|
||||||
{
|
{
|
||||||
|
@ -615,14 +615,11 @@ namespace ARMeilleure.Instructions
|
|||||||
{
|
{
|
||||||
return EmitSse41ProcessNaNsOpF(context, (op1, op2) =>
|
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);
|
return context.AddIntrinsic(addInst, op1, op2);
|
||||||
}, scalar: false, op1, op2);
|
|
||||||
}, scalar: false, op1, op2);
|
}, scalar: false, op1, op2);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -696,17 +693,33 @@ namespace ARMeilleure.Instructions
|
|||||||
Operand n = GetVec(op.Rn);
|
Operand n = GetVec(op.Rn);
|
||||||
Operand m = GetVec(op.Rm);
|
Operand m = GetVec(op.Rm);
|
||||||
|
|
||||||
|
Operand res;
|
||||||
|
|
||||||
if (op.Size == 0)
|
if (op.Size == 0)
|
||||||
{
|
{
|
||||||
Operand res = context.AddIntrinsic(Intrinsic.X86Mulss, n, m);
|
if (Optimizations.UseFma)
|
||||||
res = context.AddIntrinsic(Intrinsic.X86Addss, a, res);
|
{
|
||||||
|
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));
|
context.Copy(d, context.VectorZeroUpper96(res));
|
||||||
}
|
}
|
||||||
else /* if (op.Size == 1) */
|
else /* if (op.Size == 1) */
|
||||||
{
|
{
|
||||||
Operand res = context.AddIntrinsic(Intrinsic.X86Mulsd, n, m);
|
if (Optimizations.UseFma)
|
||||||
res = context.AddIntrinsic(Intrinsic.X86Addsd, a, res);
|
{
|
||||||
|
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));
|
context.Copy(d, context.VectorZeroUpper64(res));
|
||||||
}
|
}
|
||||||
@ -730,10 +743,7 @@ namespace ARMeilleure.Instructions
|
|||||||
{
|
{
|
||||||
EmitSse41ProcessNaNsOpF(context, (op1, op2) =>
|
EmitSse41ProcessNaNsOpF(context, (op1, op2) =>
|
||||||
{
|
{
|
||||||
return EmitSseOrAvxHandleFzModeOpF(context, (op1, op2) =>
|
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: true);
|
||||||
{
|
|
||||||
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: true);
|
|
||||||
}, scalar: true, op1, op2);
|
|
||||||
}, scalar: true);
|
}, scalar: true);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -755,10 +765,7 @@ namespace ARMeilleure.Instructions
|
|||||||
{
|
{
|
||||||
EmitSse41ProcessNaNsOpF(context, (op1, op2) =>
|
EmitSse41ProcessNaNsOpF(context, (op1, op2) =>
|
||||||
{
|
{
|
||||||
return EmitSseOrAvxHandleFzModeOpF(context, (op1, op2) =>
|
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: true);
|
||||||
{
|
|
||||||
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: true);
|
|
||||||
}, scalar: false, op1, op2);
|
|
||||||
}, scalar: false);
|
}, scalar: false);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -886,10 +893,7 @@ namespace ARMeilleure.Instructions
|
|||||||
{
|
{
|
||||||
return EmitSse41ProcessNaNsOpF(context, (op1, op2) =>
|
return EmitSse41ProcessNaNsOpF(context, (op1, op2) =>
|
||||||
{
|
{
|
||||||
return EmitSseOrAvxHandleFzModeOpF(context, (op1, op2) =>
|
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: true);
|
||||||
{
|
|
||||||
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: true);
|
|
||||||
}, scalar: false, op1, op2);
|
|
||||||
}, scalar: false, op1, op2);
|
}, scalar: false, op1, op2);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -914,10 +918,7 @@ namespace ARMeilleure.Instructions
|
|||||||
{
|
{
|
||||||
return EmitSse41ProcessNaNsOpF(context, (op1, op2) =>
|
return EmitSse41ProcessNaNsOpF(context, (op1, op2) =>
|
||||||
{
|
{
|
||||||
return EmitSseOrAvxHandleFzModeOpF(context, (op1, op2) =>
|
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: true);
|
||||||
{
|
|
||||||
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: true);
|
|
||||||
}, scalar: false, op1, op2);
|
|
||||||
}, scalar: false, op1, op2);
|
}, scalar: false, op1, op2);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -940,10 +941,7 @@ namespace ARMeilleure.Instructions
|
|||||||
{
|
{
|
||||||
EmitSse41ProcessNaNsOpF(context, (op1, op2) =>
|
EmitSse41ProcessNaNsOpF(context, (op1, op2) =>
|
||||||
{
|
{
|
||||||
return EmitSseOrAvxHandleFzModeOpF(context, (op1, op2) =>
|
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: false);
|
||||||
{
|
|
||||||
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: false);
|
|
||||||
}, scalar: true, op1, op2);
|
|
||||||
}, scalar: true);
|
}, scalar: true);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -965,10 +963,7 @@ namespace ARMeilleure.Instructions
|
|||||||
{
|
{
|
||||||
EmitSse41ProcessNaNsOpF(context, (op1, op2) =>
|
EmitSse41ProcessNaNsOpF(context, (op1, op2) =>
|
||||||
{
|
{
|
||||||
return EmitSseOrAvxHandleFzModeOpF(context, (op1, op2) =>
|
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: false);
|
||||||
{
|
|
||||||
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: false);
|
|
||||||
}, scalar: false, op1, op2);
|
|
||||||
}, scalar: false);
|
}, scalar: false);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -1096,10 +1091,7 @@ namespace ARMeilleure.Instructions
|
|||||||
{
|
{
|
||||||
return EmitSse41ProcessNaNsOpF(context, (op1, op2) =>
|
return EmitSse41ProcessNaNsOpF(context, (op1, op2) =>
|
||||||
{
|
{
|
||||||
return EmitSseOrAvxHandleFzModeOpF(context, (op1, op2) =>
|
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: false);
|
||||||
{
|
|
||||||
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: false);
|
|
||||||
}, scalar: false, op1, op2);
|
|
||||||
}, scalar: false, op1, op2);
|
}, scalar: false, op1, op2);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -1124,10 +1116,7 @@ namespace ARMeilleure.Instructions
|
|||||||
{
|
{
|
||||||
return EmitSse41ProcessNaNsOpF(context, (op1, op2) =>
|
return EmitSse41ProcessNaNsOpF(context, (op1, op2) =>
|
||||||
{
|
{
|
||||||
return EmitSseOrAvxHandleFzModeOpF(context, (op1, op2) =>
|
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: false);
|
||||||
{
|
|
||||||
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: false);
|
|
||||||
}, scalar: false, op1, op2);
|
|
||||||
}, scalar: false, op1, op2);
|
}, scalar: false, op1, op2);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -1146,6 +1135,37 @@ namespace ARMeilleure.Instructions
|
|||||||
{
|
{
|
||||||
InstEmitSimdHelperArm64.EmitScalarTernaryOpFRdByElem(context, Intrinsic.Arm64FmlaSe);
|
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
|
else
|
||||||
{
|
{
|
||||||
EmitScalarTernaryOpByElemF(context, (op1, op2, op3) =>
|
EmitScalarTernaryOpByElemF(context, (op1, op2, op3) =>
|
||||||
@ -1171,11 +1191,19 @@ namespace ARMeilleure.Instructions
|
|||||||
|
|
||||||
int sizeF = op.Size & 1;
|
int sizeF = op.Size & 1;
|
||||||
|
|
||||||
|
Operand res;
|
||||||
|
|
||||||
if (sizeF == 0)
|
if (sizeF == 0)
|
||||||
{
|
{
|
||||||
Operand res = context.AddIntrinsic(Intrinsic.X86Mulps, n, m);
|
if (Optimizations.UseFma)
|
||||||
|
{
|
||||||
res = context.AddIntrinsic(Intrinsic.X86Addps, d, res);
|
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)
|
if (op.RegisterSize == RegisterSize.Simd64)
|
||||||
{
|
{
|
||||||
@ -1186,9 +1214,15 @@ namespace ARMeilleure.Instructions
|
|||||||
}
|
}
|
||||||
else /* if (sizeF == 1) */
|
else /* if (sizeF == 1) */
|
||||||
{
|
{
|
||||||
Operand res = context.AddIntrinsic(Intrinsic.X86Mulpd, n, m);
|
if (Optimizations.UseFma)
|
||||||
|
{
|
||||||
res = context.AddIntrinsic(Intrinsic.X86Addpd, d, res);
|
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);
|
context.Copy(d, res);
|
||||||
}
|
}
|
||||||
@ -1224,8 +1258,15 @@ namespace ARMeilleure.Instructions
|
|||||||
|
|
||||||
Operand res = context.AddIntrinsic(Intrinsic.X86Shufps, m, m, Const(shuffleMask));
|
Operand res = context.AddIntrinsic(Intrinsic.X86Shufps, m, m, Const(shuffleMask));
|
||||||
|
|
||||||
res = context.AddIntrinsic(Intrinsic.X86Mulps, n, res);
|
if (Optimizations.UseFma)
|
||||||
res = context.AddIntrinsic(Intrinsic.X86Addps, d, res);
|
{
|
||||||
|
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)
|
if (op.RegisterSize == RegisterSize.Simd64)
|
||||||
{
|
{
|
||||||
@ -1240,8 +1281,15 @@ namespace ARMeilleure.Instructions
|
|||||||
|
|
||||||
Operand res = context.AddIntrinsic(Intrinsic.X86Shufpd, m, m, Const(shuffleMask));
|
Operand res = context.AddIntrinsic(Intrinsic.X86Shufpd, m, m, Const(shuffleMask));
|
||||||
|
|
||||||
res = context.AddIntrinsic(Intrinsic.X86Mulpd, n, res);
|
if (Optimizations.UseFma)
|
||||||
res = context.AddIntrinsic(Intrinsic.X86Addpd, d, res);
|
{
|
||||||
|
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);
|
context.Copy(d, res);
|
||||||
}
|
}
|
||||||
@ -1261,6 +1309,37 @@ namespace ARMeilleure.Instructions
|
|||||||
{
|
{
|
||||||
InstEmitSimdHelperArm64.EmitScalarTernaryOpFRdByElem(context, Intrinsic.Arm64FmlsSe);
|
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
|
else
|
||||||
{
|
{
|
||||||
EmitScalarTernaryOpByElemF(context, (op1, op2, op3) =>
|
EmitScalarTernaryOpByElemF(context, (op1, op2, op3) =>
|
||||||
@ -1286,11 +1365,19 @@ namespace ARMeilleure.Instructions
|
|||||||
|
|
||||||
int sizeF = op.Size & 1;
|
int sizeF = op.Size & 1;
|
||||||
|
|
||||||
|
Operand res;
|
||||||
|
|
||||||
if (sizeF == 0)
|
if (sizeF == 0)
|
||||||
{
|
{
|
||||||
Operand res = context.AddIntrinsic(Intrinsic.X86Mulps, n, m);
|
if (Optimizations.UseFma)
|
||||||
|
{
|
||||||
res = context.AddIntrinsic(Intrinsic.X86Subps, d, res);
|
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)
|
if (op.RegisterSize == RegisterSize.Simd64)
|
||||||
{
|
{
|
||||||
@ -1301,9 +1388,15 @@ namespace ARMeilleure.Instructions
|
|||||||
}
|
}
|
||||||
else /* if (sizeF == 1) */
|
else /* if (sizeF == 1) */
|
||||||
{
|
{
|
||||||
Operand res = context.AddIntrinsic(Intrinsic.X86Mulpd, n, m);
|
if (Optimizations.UseFma)
|
||||||
|
{
|
||||||
res = context.AddIntrinsic(Intrinsic.X86Subpd, d, res);
|
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);
|
context.Copy(d, res);
|
||||||
}
|
}
|
||||||
@ -1339,8 +1432,15 @@ namespace ARMeilleure.Instructions
|
|||||||
|
|
||||||
Operand res = context.AddIntrinsic(Intrinsic.X86Shufps, m, m, Const(shuffleMask));
|
Operand res = context.AddIntrinsic(Intrinsic.X86Shufps, m, m, Const(shuffleMask));
|
||||||
|
|
||||||
res = context.AddIntrinsic(Intrinsic.X86Mulps, n, res);
|
if (Optimizations.UseFma)
|
||||||
res = context.AddIntrinsic(Intrinsic.X86Subps, d, res);
|
{
|
||||||
|
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)
|
if (op.RegisterSize == RegisterSize.Simd64)
|
||||||
{
|
{
|
||||||
@ -1355,8 +1455,15 @@ namespace ARMeilleure.Instructions
|
|||||||
|
|
||||||
Operand res = context.AddIntrinsic(Intrinsic.X86Shufpd, m, m, Const(shuffleMask));
|
Operand res = context.AddIntrinsic(Intrinsic.X86Shufpd, m, m, Const(shuffleMask));
|
||||||
|
|
||||||
res = context.AddIntrinsic(Intrinsic.X86Mulpd, n, res);
|
if (Optimizations.UseFma)
|
||||||
res = context.AddIntrinsic(Intrinsic.X86Subpd, d, res);
|
{
|
||||||
|
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);
|
context.Copy(d, res);
|
||||||
}
|
}
|
||||||
@ -1385,17 +1492,33 @@ namespace ARMeilleure.Instructions
|
|||||||
Operand n = GetVec(op.Rn);
|
Operand n = GetVec(op.Rn);
|
||||||
Operand m = GetVec(op.Rm);
|
Operand m = GetVec(op.Rm);
|
||||||
|
|
||||||
|
Operand res;
|
||||||
|
|
||||||
if (op.Size == 0)
|
if (op.Size == 0)
|
||||||
{
|
{
|
||||||
Operand res = context.AddIntrinsic(Intrinsic.X86Mulss, n, m);
|
if (Optimizations.UseFma)
|
||||||
res = context.AddIntrinsic(Intrinsic.X86Subss, a, res);
|
{
|
||||||
|
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));
|
context.Copy(d, context.VectorZeroUpper96(res));
|
||||||
}
|
}
|
||||||
else /* if (op.Size == 1) */
|
else /* if (op.Size == 1) */
|
||||||
{
|
{
|
||||||
Operand res = context.AddIntrinsic(Intrinsic.X86Mulsd, n, m);
|
if (Optimizations.UseFma)
|
||||||
res = context.AddIntrinsic(Intrinsic.X86Subsd, a, res);
|
{
|
||||||
|
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));
|
context.Copy(d, context.VectorZeroUpper64(res));
|
||||||
}
|
}
|
||||||
@ -1669,25 +1792,39 @@ namespace ARMeilleure.Instructions
|
|||||||
Operand n = GetVec(op.Rn);
|
Operand n = GetVec(op.Rn);
|
||||||
Operand m = GetVec(op.Rm);
|
Operand m = GetVec(op.Rm);
|
||||||
|
|
||||||
|
Operand res;
|
||||||
|
|
||||||
if (op.Size == 0)
|
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);
|
res = context.AddIntrinsic(Intrinsic.X86Mulss, n, m);
|
||||||
|
res = context.AddIntrinsic(Intrinsic.X86Subss, aNeg, res);
|
||||||
Operand res = context.AddIntrinsic(Intrinsic.X86Mulss, n, m);
|
}
|
||||||
res = context.AddIntrinsic(Intrinsic.X86Subss, aNeg, res);
|
|
||||||
|
|
||||||
context.Copy(d, context.VectorZeroUpper96(res));
|
context.Copy(d, context.VectorZeroUpper96(res));
|
||||||
}
|
}
|
||||||
else /* if (op.Size == 1) */
|
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);
|
res = context.AddIntrinsic(Intrinsic.X86Mulsd, n, m);
|
||||||
|
res = context.AddIntrinsic(Intrinsic.X86Subsd, aNeg, res);
|
||||||
Operand res = context.AddIntrinsic(Intrinsic.X86Mulsd, n, m);
|
}
|
||||||
res = context.AddIntrinsic(Intrinsic.X86Subsd, aNeg, res);
|
|
||||||
|
|
||||||
context.Copy(d, context.VectorZeroUpper64(res));
|
context.Copy(d, context.VectorZeroUpper64(res));
|
||||||
}
|
}
|
||||||
@ -1716,25 +1853,39 @@ namespace ARMeilleure.Instructions
|
|||||||
Operand n = GetVec(op.Rn);
|
Operand n = GetVec(op.Rn);
|
||||||
Operand m = GetVec(op.Rm);
|
Operand m = GetVec(op.Rm);
|
||||||
|
|
||||||
|
Operand res;
|
||||||
|
|
||||||
if (op.Size == 0)
|
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);
|
res = context.AddIntrinsic(Intrinsic.X86Mulss, n, m);
|
||||||
|
res = context.AddIntrinsic(Intrinsic.X86Addss, aNeg, res);
|
||||||
Operand res = context.AddIntrinsic(Intrinsic.X86Mulss, n, m);
|
}
|
||||||
res = context.AddIntrinsic(Intrinsic.X86Addss, aNeg, res);
|
|
||||||
|
|
||||||
context.Copy(d, context.VectorZeroUpper96(res));
|
context.Copy(d, context.VectorZeroUpper96(res));
|
||||||
}
|
}
|
||||||
else /* if (op.Size == 1) */
|
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);
|
res = context.AddIntrinsic(Intrinsic.X86Mulsd, n, m);
|
||||||
|
res = context.AddIntrinsic(Intrinsic.X86Addsd, aNeg, res);
|
||||||
Operand res = context.AddIntrinsic(Intrinsic.X86Mulsd, n, m);
|
}
|
||||||
res = context.AddIntrinsic(Intrinsic.X86Addsd, aNeg, res);
|
|
||||||
|
|
||||||
context.Copy(d, context.VectorZeroUpper64(res));
|
context.Copy(d, context.VectorZeroUpper64(res));
|
||||||
}
|
}
|
||||||
@ -1830,13 +1981,22 @@ namespace ARMeilleure.Instructions
|
|||||||
|
|
||||||
int sizeF = op.Size & 1;
|
int sizeF = op.Size & 1;
|
||||||
|
|
||||||
|
Operand res;
|
||||||
|
|
||||||
if (sizeF == 0)
|
if (sizeF == 0)
|
||||||
{
|
{
|
||||||
Operand mask = X86GetScalar(context, 2f);
|
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);
|
res = EmitSse41RecipStepSelectOpF(context, n, m, res, mask, scalar: true, sizeF);
|
||||||
|
|
||||||
context.Copy(GetVec(op.Rd), context.VectorZeroUpper96(res));
|
context.Copy(GetVec(op.Rd), context.VectorZeroUpper96(res));
|
||||||
@ -1845,9 +2005,16 @@ namespace ARMeilleure.Instructions
|
|||||||
{
|
{
|
||||||
Operand mask = X86GetScalar(context, 2d);
|
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);
|
res = EmitSse41RecipStepSelectOpF(context, n, m, res, mask, scalar: true, sizeF);
|
||||||
|
|
||||||
context.Copy(GetVec(op.Rd), context.VectorZeroUpper64(res));
|
context.Copy(GetVec(op.Rd), context.VectorZeroUpper64(res));
|
||||||
@ -1877,14 +2044,23 @@ namespace ARMeilleure.Instructions
|
|||||||
|
|
||||||
int sizeF = op.Size & 1;
|
int sizeF = op.Size & 1;
|
||||||
|
|
||||||
|
Operand res;
|
||||||
|
|
||||||
if (sizeF == 0)
|
if (sizeF == 0)
|
||||||
{
|
{
|
||||||
Operand mask = X86GetAllElements(context, 2f);
|
Operand mask = X86GetAllElements(context, 2f);
|
||||||
|
|
||||||
Operand res = context.AddIntrinsic(Intrinsic.X86Mulps, n, m);
|
if (Optimizations.UseFma)
|
||||||
res = EmitSse41RecipStepSelectOpF(context, n, m, res, mask, scalar: false, sizeF);
|
{
|
||||||
|
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)
|
if (op.RegisterSize == RegisterSize.Simd64)
|
||||||
{
|
{
|
||||||
@ -1897,10 +2073,17 @@ namespace ARMeilleure.Instructions
|
|||||||
{
|
{
|
||||||
Operand mask = X86GetAllElements(context, 2d);
|
Operand mask = X86GetAllElements(context, 2d);
|
||||||
|
|
||||||
Operand res = context.AddIntrinsic(Intrinsic.X86Mulpd, n, m);
|
if (Optimizations.UseFma)
|
||||||
res = EmitSse41RecipStepSelectOpF(context, n, m, res, mask, scalar: false, sizeF);
|
{
|
||||||
|
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);
|
context.Copy(GetVec(op.Rd), res);
|
||||||
}
|
}
|
||||||
@ -2113,20 +2296,32 @@ namespace ARMeilleure.Instructions
|
|||||||
|
|
||||||
public static void Frintx_S(ArmEmitterContext context)
|
public static void Frintx_S(ArmEmitterContext context)
|
||||||
{
|
{
|
||||||
// TODO Arm64: Fast path. Should we set host FPCR?
|
if (Optimizations.UseAdvSimd)
|
||||||
EmitScalarUnaryOpF(context, (op1) =>
|
|
||||||
{
|
{
|
||||||
return EmitRoundByRMode(context, op1);
|
InstEmitSimdHelperArm64.EmitScalarUnaryOpF(context, Intrinsic.Arm64FrintxS);
|
||||||
});
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
EmitScalarUnaryOpF(context, (op1) =>
|
||||||
|
{
|
||||||
|
return EmitRoundByRMode(context, op1);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Frintx_V(ArmEmitterContext context)
|
public static void Frintx_V(ArmEmitterContext context)
|
||||||
{
|
{
|
||||||
// TODO Arm64: Fast path. Should we set host FPCR?
|
if (Optimizations.UseAdvSimd)
|
||||||
EmitVectorUnaryOpF(context, (op1) =>
|
|
||||||
{
|
{
|
||||||
return EmitRoundByRMode(context, op1);
|
InstEmitSimdHelperArm64.EmitVectorUnaryOpF(context, Intrinsic.Arm64FrintxV);
|
||||||
});
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
EmitVectorUnaryOpF(context, (op1) =>
|
||||||
|
{
|
||||||
|
return EmitRoundByRMode(context, op1);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Frintz_S(ArmEmitterContext context)
|
public static void Frintz_S(ArmEmitterContext context)
|
||||||
@ -2237,16 +2432,25 @@ namespace ARMeilleure.Instructions
|
|||||||
|
|
||||||
int sizeF = op.Size & 1;
|
int sizeF = op.Size & 1;
|
||||||
|
|
||||||
|
Operand res;
|
||||||
|
|
||||||
if (sizeF == 0)
|
if (sizeF == 0)
|
||||||
{
|
{
|
||||||
Operand maskHalf = X86GetScalar(context, 0.5f);
|
Operand maskHalf = X86GetScalar(context, 0.5f);
|
||||||
Operand maskThree = X86GetScalar(context, 3f);
|
Operand maskThree = X86GetScalar(context, 3f);
|
||||||
Operand maskOneHalf = X86GetScalar(context, 1.5f);
|
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);
|
res = EmitSse41RecipStepSelectOpF(context, n, m, res, maskOneHalf, scalar: true, sizeF);
|
||||||
|
|
||||||
context.Copy(GetVec(op.Rd), context.VectorZeroUpper96(res));
|
context.Copy(GetVec(op.Rd), context.VectorZeroUpper96(res));
|
||||||
@ -2257,10 +2461,17 @@ namespace ARMeilleure.Instructions
|
|||||||
Operand maskThree = X86GetScalar(context, 3d);
|
Operand maskThree = X86GetScalar(context, 3d);
|
||||||
Operand maskOneHalf = X86GetScalar(context, 1.5d);
|
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);
|
res = EmitSse41RecipStepSelectOpF(context, n, m, res, maskOneHalf, scalar: true, sizeF);
|
||||||
|
|
||||||
context.Copy(GetVec(op.Rd), context.VectorZeroUpper64(res));
|
context.Copy(GetVec(op.Rd), context.VectorZeroUpper64(res));
|
||||||
@ -2290,15 +2501,24 @@ namespace ARMeilleure.Instructions
|
|||||||
|
|
||||||
int sizeF = op.Size & 1;
|
int sizeF = op.Size & 1;
|
||||||
|
|
||||||
|
Operand res;
|
||||||
|
|
||||||
if (sizeF == 0)
|
if (sizeF == 0)
|
||||||
{
|
{
|
||||||
Operand maskHalf = X86GetAllElements(context, 0.5f);
|
Operand maskHalf = X86GetAllElements(context, 0.5f);
|
||||||
Operand maskThree = X86GetAllElements(context, 3f);
|
Operand maskThree = X86GetAllElements(context, 3f);
|
||||||
Operand maskOneHalf = X86GetAllElements(context, 1.5f);
|
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 = context.AddIntrinsic(Intrinsic.X86Mulps, maskHalf, res);
|
||||||
res = EmitSse41RecipStepSelectOpF(context, n, m, res, maskOneHalf, scalar: false, sizeF);
|
res = EmitSse41RecipStepSelectOpF(context, n, m, res, maskOneHalf, scalar: false, sizeF);
|
||||||
|
|
||||||
@ -2315,9 +2535,16 @@ namespace ARMeilleure.Instructions
|
|||||||
Operand maskThree = X86GetAllElements(context, 3d);
|
Operand maskThree = X86GetAllElements(context, 3d);
|
||||||
Operand maskOneHalf = X86GetAllElements(context, 1.5d);
|
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 = context.AddIntrinsic(Intrinsic.X86Mulpd, maskHalf, res);
|
||||||
res = EmitSse41RecipStepSelectOpF(context, n, m, res, maskOneHalf, scalar: false, sizeF);
|
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)
|
private static Operand EmitSse2VectorMaxMinOpF(ArmEmitterContext context, Operand n, Operand m, bool isMax)
|
||||||
{
|
{
|
||||||
IOpCodeSimd op = (IOpCodeSimd)context.CurrOp;
|
IOpCodeSimd op = (IOpCodeSimd)context.CurrOp;
|
||||||
@ -4834,10 +5014,7 @@ namespace ARMeilleure.Instructions
|
|||||||
|
|
||||||
Operand res = EmitSse41ProcessNaNsOpF(context, (op1, op2) =>
|
Operand res = EmitSse41ProcessNaNsOpF(context, (op1, op2) =>
|
||||||
{
|
{
|
||||||
return EmitSseOrAvxHandleFzModeOpF(context, (op1, op2) =>
|
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: isMaxNum);
|
||||||
{
|
|
||||||
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: isMaxNum);
|
|
||||||
}, scalar: scalar, op1, op2);
|
|
||||||
}, scalar: scalar, nCopy, mCopy);
|
}, scalar: scalar, nCopy, mCopy);
|
||||||
|
|
||||||
if (n != default || m != default)
|
if (n != default || m != default)
|
||||||
@ -4872,10 +5049,7 @@ namespace ARMeilleure.Instructions
|
|||||||
|
|
||||||
Operand res = EmitSse41ProcessNaNsOpF(context, (op1, op2) =>
|
Operand res = EmitSse41ProcessNaNsOpF(context, (op1, op2) =>
|
||||||
{
|
{
|
||||||
return EmitSseOrAvxHandleFzModeOpF(context, (op1, op2) =>
|
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: isMaxNum);
|
||||||
{
|
|
||||||
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: isMaxNum);
|
|
||||||
}, scalar: scalar, op1, op2);
|
|
||||||
}, scalar: scalar, nCopy, mCopy);
|
}, scalar: scalar, nCopy, mCopy);
|
||||||
|
|
||||||
if (n != default || m != default)
|
if (n != default || m != default)
|
||||||
|
@ -356,9 +356,11 @@ namespace ARMeilleure.Instructions
|
|||||||
? typeof(SoftFloat64_16).GetMethod(nameof(SoftFloat64_16.FPConvert))
|
? typeof(SoftFloat64_16).GetMethod(nameof(SoftFloat64_16.FPConvert))
|
||||||
: typeof(SoftFloat32_16).GetMethod(nameof(SoftFloat32_16.FPConvert));
|
: typeof(SoftFloat32_16).GetMethod(nameof(SoftFloat32_16.FPConvert));
|
||||||
|
|
||||||
|
context.ExitArmFpMode();
|
||||||
context.StoreToContext();
|
context.StoreToContext();
|
||||||
Operand res = context.Call(method, src);
|
Operand res = context.Call(method, src);
|
||||||
context.LoadFromContext();
|
context.LoadFromContext();
|
||||||
|
context.EnterArmFpMode();
|
||||||
|
|
||||||
InsertScalar16(context, op.Vd, op.T, res);
|
InsertScalar16(context, op.Vd, op.T, res);
|
||||||
}
|
}
|
||||||
@ -372,9 +374,11 @@ namespace ARMeilleure.Instructions
|
|||||||
? typeof(SoftFloat16_64).GetMethod(nameof(SoftFloat16_64.FPConvert))
|
? typeof(SoftFloat16_64).GetMethod(nameof(SoftFloat16_64.FPConvert))
|
||||||
: typeof(SoftFloat16_32).GetMethod(nameof(SoftFloat16_32.FPConvert));
|
: typeof(SoftFloat16_32).GetMethod(nameof(SoftFloat16_32.FPConvert));
|
||||||
|
|
||||||
|
context.ExitArmFpMode();
|
||||||
context.StoreToContext();
|
context.StoreToContext();
|
||||||
Operand res = context.Call(method, src);
|
Operand res = context.Call(method, src);
|
||||||
context.LoadFromContext();
|
context.LoadFromContext();
|
||||||
|
context.EnterArmFpMode();
|
||||||
|
|
||||||
InsertScalar(context, op.Vd, res);
|
InsertScalar(context, op.Vd, res);
|
||||||
}
|
}
|
||||||
@ -542,10 +546,17 @@ namespace ARMeilleure.Instructions
|
|||||||
// VRINTX (floating-point).
|
// VRINTX (floating-point).
|
||||||
public static void Vrintx_S(ArmEmitterContext context)
|
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)
|
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.Decoders;
|
||||||
using ARMeilleure.IntermediateRepresentation;
|
using ARMeilleure.IntermediateRepresentation;
|
||||||
using ARMeilleure.State;
|
using ARMeilleure.State;
|
||||||
@ -158,6 +159,75 @@ namespace ARMeilleure.Instructions
|
|||||||
};
|
};
|
||||||
#endregion
|
#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)
|
public static int GetImmShl(OpCodeSimdShImm op)
|
||||||
{
|
{
|
||||||
return op.Imm - (8 << op.Size);
|
return op.Imm - (8 << op.Size);
|
||||||
@ -465,9 +535,11 @@ namespace ARMeilleure.Instructions
|
|||||||
? typeof(SoftFloat32).GetMethod(name)
|
? typeof(SoftFloat32).GetMethod(name)
|
||||||
: typeof(SoftFloat64).GetMethod(name);
|
: typeof(SoftFloat64).GetMethod(name);
|
||||||
|
|
||||||
|
context.ExitArmFpMode();
|
||||||
context.StoreToContext();
|
context.StoreToContext();
|
||||||
Operand res = context.Call(info, callArgs);
|
Operand res = context.Call(info, callArgs);
|
||||||
context.LoadFromContext();
|
context.LoadFromContext();
|
||||||
|
context.EnterArmFpMode();
|
||||||
|
|
||||||
return res;
|
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
|
public enum CmpCondition
|
||||||
{
|
{
|
||||||
// Legacy Sse.
|
// Legacy Sse.
|
||||||
|
@ -1197,9 +1197,11 @@ namespace ARMeilleure.Instructions
|
|||||||
Array.Resize(ref callArgs, callArgs.Length + 1);
|
Array.Resize(ref callArgs, callArgs.Length + 1);
|
||||||
callArgs[callArgs.Length - 1] = Const(1);
|
callArgs[callArgs.Length - 1] = Const(1);
|
||||||
|
|
||||||
|
context.ExitArmFpMode();
|
||||||
context.StoreToContext();
|
context.StoreToContext();
|
||||||
Operand res = context.Call(info, callArgs);
|
Operand res = context.Call(info, callArgs);
|
||||||
context.LoadFromContext();
|
context.LoadFromContext();
|
||||||
|
context.EnterArmFpMode();
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
@ -254,7 +254,22 @@ namespace ARMeilleure.Instructions
|
|||||||
|
|
||||||
public static void Not_V(ArmEmitterContext context)
|
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;
|
OpCodeSimd op = (OpCodeSimd)context.CurrOp;
|
||||||
|
|
||||||
@ -283,6 +298,22 @@ namespace ARMeilleure.Instructions
|
|||||||
{
|
{
|
||||||
InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64OrnV);
|
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)
|
else if (Optimizations.UseSse2)
|
||||||
{
|
{
|
||||||
OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp;
|
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));
|
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)
|
else if (Optimizations.UseSse2)
|
||||||
{
|
{
|
||||||
Operand mask = context.VectorOne();
|
Operand mask = context.VectorOne();
|
||||||
|
@ -34,7 +34,14 @@ namespace ARMeilleure.Instructions
|
|||||||
|
|
||||||
public static void Vmvn_I(ArmEmitterContext context)
|
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) =>
|
EmitVectorUnaryOpSimd32(context, (op1) =>
|
||||||
{
|
{
|
||||||
|
@ -33,8 +33,8 @@ namespace ARMeilleure.Instructions
|
|||||||
case 0b11_011_0100_0010_000: EmitGetNzcv(context); return;
|
case 0b11_011_0100_0010_000: EmitGetNzcv(context); return;
|
||||||
case 0b11_011_0100_0100_000: EmitGetFpcr(context); return;
|
case 0b11_011_0100_0100_000: EmitGetFpcr(context); return;
|
||||||
case 0b11_011_0100_0100_001: EmitGetFpsr(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_010: EmitGetTpidrEl0(context); return;
|
||||||
case 0b11_011_1101_0000_011: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetTpidrroEl0)); break;
|
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_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_001: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetCntpctEl0)); break;
|
||||||
case 0b11_011_1110_0000_010: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetCntvctEl0)); 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;
|
OpCodeSystem op = (OpCodeSystem)context.CurrOp;
|
||||||
|
|
||||||
MethodInfo info;
|
|
||||||
|
|
||||||
switch (GetPackedId(op))
|
switch (GetPackedId(op))
|
||||||
{
|
{
|
||||||
case 0b11_011_0100_0010_000: EmitSetNzcv(context); return;
|
case 0b11_011_0100_0010_000: EmitSetNzcv(context); return;
|
||||||
case 0b11_011_0100_0100_000: EmitSetFpcr(context); return;
|
case 0b11_011_0100_0100_000: EmitSetFpcr(context); return;
|
||||||
case 0b11_011_0100_0100_001: EmitSetFpsr(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}.");
|
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)
|
public static void Nop(ArmEmitterContext context)
|
||||||
@ -165,6 +161,28 @@ namespace ARMeilleure.Instructions
|
|||||||
SetIntOrZR(context, op.Rt, fpsr);
|
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)
|
private static void EmitSetNzcv(ArmEmitterContext context)
|
||||||
{
|
{
|
||||||
OpCodeSystem op = (OpCodeSystem)context.CurrOp;
|
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)));
|
SetFpFlag(context, (FPState)flag, context.BitwiseAnd(context.ShiftRightUI(fpcr, Const(flag)), Const(1)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
context.UpdateArmFpMode();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void EmitSetFpsr(ArmEmitterContext context)
|
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)));
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
MethodInfo info;
|
|
||||||
|
|
||||||
switch (op.CRn)
|
switch (op.CRn)
|
||||||
{
|
{
|
||||||
case 13: // Process and Thread Info.
|
case 13: // Process and Thread Info.
|
||||||
@ -36,14 +34,12 @@ namespace ARMeilleure.Instructions
|
|||||||
switch (op.Opc2)
|
switch (op.Opc2)
|
||||||
{
|
{
|
||||||
case 2:
|
case 2:
|
||||||
info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.SetTpidrEl032)); break;
|
EmitSetTpidrEl0(context); return;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new NotImplementedException($"Unknown MRC Opc2 0x{op.Opc2:X} at 0x{op.Address:X} (0x{op.RawOpCode:X}).");
|
throw new NotImplementedException($"Unknown MRC Opc2 0x{op.Opc2:X} at 0x{op.Address:X} (0x{op.RawOpCode:X}).");
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 7:
|
case 7:
|
||||||
switch (op.CRm) // Cache and Memory barrier.
|
switch (op.CRm) // Cache and Memory barrier.
|
||||||
{
|
{
|
||||||
@ -64,8 +60,6 @@ namespace ARMeilleure.Instructions
|
|||||||
default:
|
default:
|
||||||
throw new NotImplementedException($"Unknown MRC 0x{op.RawOpCode:X8} at 0x{op.Address:X16}.");
|
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)
|
public static void Mrc(ArmEmitterContext context)
|
||||||
@ -79,7 +73,7 @@ namespace ARMeilleure.Instructions
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
MethodInfo info;
|
Operand result;
|
||||||
|
|
||||||
switch (op.CRn)
|
switch (op.CRn)
|
||||||
{
|
{
|
||||||
@ -92,10 +86,10 @@ namespace ARMeilleure.Instructions
|
|||||||
switch (op.Opc2)
|
switch (op.Opc2)
|
||||||
{
|
{
|
||||||
case 2:
|
case 2:
|
||||||
info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetTpidrEl032)); break;
|
result = EmitGetTpidrEl0(context); break;
|
||||||
|
|
||||||
case 3:
|
case 3:
|
||||||
info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetTpidr32)); break;
|
result = EmitGetTpidrroEl0(context); break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new NotImplementedException($"Unknown MRC Opc2 0x{op.Opc2:X} at 0x{op.Address:X} (0x{op.RawOpCode:X}).");
|
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)
|
if (op.Rt == RegisterAlias.Aarch32Pc)
|
||||||
{
|
{
|
||||||
// Special behavior: copy NZCV flags into APSR.
|
// Special behavior: copy NZCV flags into APSR.
|
||||||
EmitSetNzcv(context, context.Call(info));
|
EmitSetNzcv(context, result);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else
|
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)));
|
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;
|
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()
|
public static ulong GetCntfrqEl0()
|
||||||
{
|
{
|
||||||
return GetContext().CntfrqEl0;
|
return GetContext().CntfrqEl0;
|
||||||
@ -106,16 +86,6 @@ namespace ARMeilleure.Instructions
|
|||||||
{
|
{
|
||||||
return GetContext().CntvctEl0;
|
return GetContext().CntvctEl0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void SetTpidrEl0(ulong value)
|
|
||||||
{
|
|
||||||
GetContext().TpidrEl0 = (long)value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void SetTpidrEl032(uint value)
|
|
||||||
{
|
|
||||||
GetContext().TpidrEl0 = (long)value;
|
|
||||||
}
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region "Read"
|
#region "Read"
|
||||||
@ -191,7 +161,7 @@ namespace ARMeilleure.Instructions
|
|||||||
{
|
{
|
||||||
TranslatedFunction function = Context.Translator.GetOrTranslate(address, GetContext().ExecutionMode);
|
TranslatedFunction function = Context.Translator.GetOrTranslate(address, GetContext().ExecutionMode);
|
||||||
|
|
||||||
return (ulong)function.FuncPtr.ToInt64();
|
return (ulong)function.FuncPointer.ToInt64();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void InvalidateCacheLine(ulong address)
|
public static void InvalidateCacheLine(ulong address)
|
||||||
|
@ -53,6 +53,7 @@ namespace ARMeilleure.IntermediateRepresentation
|
|||||||
X86Haddpd,
|
X86Haddpd,
|
||||||
X86Haddps,
|
X86Haddps,
|
||||||
X86Insertps,
|
X86Insertps,
|
||||||
|
X86Ldmxcsr,
|
||||||
X86Maxpd,
|
X86Maxpd,
|
||||||
X86Maxps,
|
X86Maxps,
|
||||||
X86Maxsd,
|
X86Maxsd,
|
||||||
@ -68,8 +69,6 @@ namespace ARMeilleure.IntermediateRepresentation
|
|||||||
X86Mulps,
|
X86Mulps,
|
||||||
X86Mulsd,
|
X86Mulsd,
|
||||||
X86Mulss,
|
X86Mulss,
|
||||||
X86Mxcsrmb,
|
|
||||||
X86Mxcsrub,
|
|
||||||
X86Paddb,
|
X86Paddb,
|
||||||
X86Paddd,
|
X86Paddd,
|
||||||
X86Paddq,
|
X86Paddq,
|
||||||
@ -153,6 +152,7 @@ namespace ARMeilleure.IntermediateRepresentation
|
|||||||
X86Sqrtps,
|
X86Sqrtps,
|
||||||
X86Sqrtsd,
|
X86Sqrtsd,
|
||||||
X86Sqrtss,
|
X86Sqrtss,
|
||||||
|
X86Stmxcsr,
|
||||||
X86Subpd,
|
X86Subpd,
|
||||||
X86Subps,
|
X86Subps,
|
||||||
X86Subsd,
|
X86Subsd,
|
||||||
@ -163,16 +163,19 @@ namespace ARMeilleure.IntermediateRepresentation
|
|||||||
X86Unpcklps,
|
X86Unpcklps,
|
||||||
X86Vcvtph2ps,
|
X86Vcvtph2ps,
|
||||||
X86Vcvtps2ph,
|
X86Vcvtps2ph,
|
||||||
|
X86Vfmadd231pd,
|
||||||
X86Vfmadd231ps,
|
X86Vfmadd231ps,
|
||||||
X86Vfmadd231sd,
|
X86Vfmadd231sd,
|
||||||
X86Vfmadd231ss,
|
X86Vfmadd231ss,
|
||||||
X86Vfmsub231sd,
|
X86Vfmsub231sd,
|
||||||
X86Vfmsub231ss,
|
X86Vfmsub231ss,
|
||||||
|
X86Vfnmadd231pd,
|
||||||
X86Vfnmadd231ps,
|
X86Vfnmadd231ps,
|
||||||
X86Vfnmadd231sd,
|
X86Vfnmadd231sd,
|
||||||
X86Vfnmadd231ss,
|
X86Vfnmadd231ss,
|
||||||
X86Vfnmsub231sd,
|
X86Vfnmsub231sd,
|
||||||
X86Vfnmsub231ss,
|
X86Vfnmsub231ss,
|
||||||
|
X86Vpternlogd,
|
||||||
X86Xorpd,
|
X86Xorpd,
|
||||||
X86Xorps,
|
X86Xorps,
|
||||||
|
|
||||||
@ -393,6 +396,8 @@ namespace ARMeilleure.IntermediateRepresentation
|
|||||||
Arm64MlsVe,
|
Arm64MlsVe,
|
||||||
Arm64MlsV,
|
Arm64MlsV,
|
||||||
Arm64MoviV,
|
Arm64MoviV,
|
||||||
|
Arm64MrsFpcr,
|
||||||
|
Arm64MsrFpcr,
|
||||||
Arm64MrsFpsr,
|
Arm64MrsFpsr,
|
||||||
Arm64MsrFpsr,
|
Arm64MsrFpsr,
|
||||||
Arm64MulVe,
|
Arm64MulVe,
|
||||||
|
@ -71,6 +71,7 @@ namespace ARMeilleure.Memory
|
|||||||
/// <param name="size">Size of the region</param>
|
/// <param name="size">Size of the region</param>
|
||||||
/// <param name="write">True if the region was written, false if read</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>
|
/// <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 UseSse42IfAvailable { get; set; } = true;
|
||||||
public static bool UsePopCntIfAvailable { get; set; } = true;
|
public static bool UsePopCntIfAvailable { get; set; } = true;
|
||||||
public static bool UseAvxIfAvailable { 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 UseF16cIfAvailable { get; set; } = true;
|
||||||
public static bool UseFmaIfAvailable { get; set; } = true;
|
public static bool UseFmaIfAvailable { get; set; } = true;
|
||||||
public static bool UseAesniIfAvailable { 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 UseSse42 => UseSse42IfAvailable && X86HardwareCapabilities.SupportsSse42;
|
||||||
internal static bool UsePopCnt => UsePopCntIfAvailable && X86HardwareCapabilities.SupportsPopcnt;
|
internal static bool UsePopCnt => UsePopCntIfAvailable && X86HardwareCapabilities.SupportsPopcnt;
|
||||||
internal static bool UseAvx => UseAvxIfAvailable && X86HardwareCapabilities.SupportsAvx && !ForceLegacySse;
|
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 UseF16c => UseF16cIfAvailable && X86HardwareCapabilities.SupportsF16c;
|
||||||
internal static bool UseFma => UseFmaIfAvailable && X86HardwareCapabilities.SupportsFma;
|
internal static bool UseFma => UseFmaIfAvailable && X86HardwareCapabilities.SupportsFma;
|
||||||
internal static bool UseAesni => UseAesniIfAvailable && X86HardwareCapabilities.SupportsAesni;
|
internal static bool UseAesni => UseAesniIfAvailable && X86HardwareCapabilities.SupportsAesni;
|
||||||
internal static bool UsePclmulqdq => UsePclmulqdqIfAvailable && X86HardwareCapabilities.SupportsPclmulqdq;
|
internal static bool UsePclmulqdq => UsePclmulqdqIfAvailable && X86HardwareCapabilities.SupportsPclmulqdq;
|
||||||
internal static bool UseSha => UseShaIfAvailable && X86HardwareCapabilities.SupportsSha;
|
internal static bool UseSha => UseShaIfAvailable && X86HardwareCapabilities.SupportsSha;
|
||||||
internal static bool UseGfni => UseGfniIfAvailable && X86HardwareCapabilities.SupportsGfni;
|
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.
|
// Tracking action should be non-null to call it, otherwise assume false return.
|
||||||
context.BranchIfFalse(skipActionLabel, trackingActionPtr);
|
context.BranchIfFalse(skipActionLabel, trackingActionPtr);
|
||||||
Operand result = context.Call(trackingActionPtr, OperandType.I32, offset, Const(_pageSize), isWrite, Const(0));
|
Operand result = context.Call(trackingActionPtr, OperandType.I32, offset, Const(_pageSize), isWrite);
|
||||||
context.Copy(inRegionLocal, result);
|
context.Copy(inRegionLocal, result);
|
||||||
|
|
||||||
context.MarkLabel(skipActionLabel);
|
context.MarkLabel(skipActionLabel);
|
||||||
|
@ -27,8 +27,17 @@ namespace ARMeilleure.State
|
|||||||
// Since EL2 isn't implemented, CNTVOFF_EL2 = 0
|
// Since EL2 isn't implemented, CNTVOFF_EL2 = 0
|
||||||
public ulong CntvctEl0 => CntpctEl0;
|
public ulong CntvctEl0 => CntpctEl0;
|
||||||
|
|
||||||
public long TpidrEl0 { get; set; }
|
public long TpidrEl0
|
||||||
public long TpidrroEl0 { get; set; }
|
{
|
||||||
|
get => _nativeContext.GetTpidrEl0();
|
||||||
|
set => _nativeContext.SetTpidrEl0(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public long TpidrroEl0
|
||||||
|
{
|
||||||
|
get => _nativeContext.GetTpidrroEl0();
|
||||||
|
set => _nativeContext.SetTpidrroEl0(value);
|
||||||
|
}
|
||||||
|
|
||||||
public uint Pstate
|
public uint Pstate
|
||||||
{
|
{
|
||||||
|
@ -13,6 +13,8 @@ namespace ARMeilleure.State
|
|||||||
public fixed ulong V[RegisterConsts.VecRegsCount * 2];
|
public fixed ulong V[RegisterConsts.VecRegsCount * 2];
|
||||||
public fixed uint Flags[RegisterConsts.FlagsCount];
|
public fixed uint Flags[RegisterConsts.FlagsCount];
|
||||||
public fixed uint FpFlags[RegisterConsts.FpFlagsCount];
|
public fixed uint FpFlags[RegisterConsts.FpFlagsCount];
|
||||||
|
public long TpidrEl0;
|
||||||
|
public long TpidrroEl0;
|
||||||
public int Counter;
|
public int Counter;
|
||||||
public ulong DispatchAddress;
|
public ulong DispatchAddress;
|
||||||
public ulong ExclusiveAddress;
|
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 int GetCounter() => GetStorage().Counter;
|
||||||
public void SetCounter(int value) => GetStorage().Counter = value;
|
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()
|
public static int GetCounterOffset()
|
||||||
{
|
{
|
||||||
return StorageOffset(ref _dummyStorage, ref _dummyStorage.Counter);
|
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)
|
public Operand TryGetComparisonResult(Condition condition)
|
||||||
{
|
{
|
||||||
if (_optOpLastCompare == null || _optOpLastCompare != _optOpLastFlagSet)
|
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.GetDczidEl0)));
|
||||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetFunctionAddress)));
|
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetFunctionAddress)));
|
||||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.InvalidateCacheLine)));
|
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.ReadByte)));
|
||||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadUInt16)));
|
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadUInt16)));
|
||||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadUInt32)));
|
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadUInt32)));
|
||||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadUInt64)));
|
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadUInt64)));
|
||||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadVector128)));
|
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.SignalMemoryTracking)));
|
||||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.SupervisorCall)));
|
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.SupervisorCall)));
|
||||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.ThrowInvalidMemoryAccess)));
|
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.ThrowInvalidMemoryAccess)));
|
||||||
|
@ -3,4 +3,5 @@
|
|||||||
namespace ARMeilleure.Translation
|
namespace ARMeilleure.Translation
|
||||||
{
|
{
|
||||||
delegate void DispatcherFunction(IntPtr nativeContext, ulong startAddress);
|
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;
|
||||||
using Ryujinx.Common.Configuration;
|
using Ryujinx.Common.Configuration;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
|
using Ryujinx.Common.Memory;
|
||||||
using System;
|
using System;
|
||||||
using System.Buffers.Binary;
|
using System.Buffers.Binary;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@ -29,7 +30,7 @@ namespace ARMeilleure.Translation.PTC
|
|||||||
private const string OuterHeaderMagicString = "PTCohd\0\0";
|
private const string OuterHeaderMagicString = "PTCohd\0\0";
|
||||||
private const string InnerHeaderMagicString = "PTCihd\0\0";
|
private const string InnerHeaderMagicString = "PTCihd\0\0";
|
||||||
|
|
||||||
private const uint InternalVersion = 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 ActualDir = "0";
|
||||||
private const string BackupDir = "1";
|
private const string BackupDir = "1";
|
||||||
@ -150,10 +151,10 @@ namespace ARMeilleure.Translation.PTC
|
|||||||
|
|
||||||
private void InitializeCarriers()
|
private void InitializeCarriers()
|
||||||
{
|
{
|
||||||
_infosStream = new MemoryStream();
|
_infosStream = MemoryStreamManager.Shared.GetStream();
|
||||||
_codesList = new List<byte[]>();
|
_codesList = new List<byte[]>();
|
||||||
_relocsStream = new MemoryStream();
|
_relocsStream = MemoryStreamManager.Shared.GetStream();
|
||||||
_unwindInfosStream = new MemoryStream();
|
_unwindInfosStream = MemoryStreamManager.Shared.GetStream();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DisposeCarriers()
|
private void DisposeCarriers()
|
||||||
@ -745,9 +746,9 @@ namespace ARMeilleure.Translation.PTC
|
|||||||
bool highCq)
|
bool highCq)
|
||||||
{
|
{
|
||||||
var cFunc = new CompiledFunction(code, unwindInfo, RelocInfo.Empty);
|
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)
|
private void UpdateInfo(InfoEntry infoEntry)
|
||||||
@ -968,6 +969,7 @@ namespace ARMeilleure.Translation.PTC
|
|||||||
(ulong)Arm64HardwareCapabilities.LinuxFeatureInfoHwCap,
|
(ulong)Arm64HardwareCapabilities.LinuxFeatureInfoHwCap,
|
||||||
(ulong)Arm64HardwareCapabilities.LinuxFeatureInfoHwCap2,
|
(ulong)Arm64HardwareCapabilities.LinuxFeatureInfoHwCap2,
|
||||||
(ulong)Arm64HardwareCapabilities.MacOsFeatureInfo,
|
(ulong)Arm64HardwareCapabilities.MacOsFeatureInfo,
|
||||||
|
0,
|
||||||
0);
|
0);
|
||||||
}
|
}
|
||||||
else if (RuntimeInformation.ProcessArchitecture == Architecture.X64)
|
else if (RuntimeInformation.ProcessArchitecture == Architecture.X64)
|
||||||
@ -976,11 +978,12 @@ namespace ARMeilleure.Translation.PTC
|
|||||||
(ulong)X86HardwareCapabilities.FeatureInfo1Ecx,
|
(ulong)X86HardwareCapabilities.FeatureInfo1Ecx,
|
||||||
(ulong)X86HardwareCapabilities.FeatureInfo1Edx,
|
(ulong)X86HardwareCapabilities.FeatureInfo1Edx,
|
||||||
(ulong)X86HardwareCapabilities.FeatureInfo7Ebx,
|
(ulong)X86HardwareCapabilities.FeatureInfo7Ebx,
|
||||||
(ulong)X86HardwareCapabilities.FeatureInfo7Ecx);
|
(ulong)X86HardwareCapabilities.FeatureInfo7Ecx,
|
||||||
|
(ulong)X86HardwareCapabilities.Xcr0InfoEax);
|
||||||
}
|
}
|
||||||
else
|
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;
|
return osPlatform;
|
||||||
}
|
}
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 78*/)]
|
[StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 86*/)]
|
||||||
private struct OuterHeader
|
private struct OuterHeader
|
||||||
{
|
{
|
||||||
public ulong Magic;
|
public ulong Magic;
|
||||||
@ -1033,8 +1036,8 @@ namespace ARMeilleure.Translation.PTC
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 32*/)]
|
[StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 40*/)]
|
||||||
private record struct FeatureInfo(ulong FeatureInfo0, ulong FeatureInfo1, ulong FeatureInfo2, ulong FeatureInfo3);
|
private record struct FeatureInfo(ulong FeatureInfo0, ulong FeatureInfo1, ulong FeatureInfo2, ulong FeatureInfo3, ulong FeatureInfo4);
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 128*/)]
|
[StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 128*/)]
|
||||||
private struct InnerHeader
|
private struct InnerHeader
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using ARMeilleure.State;
|
using ARMeilleure.State;
|
||||||
using Ryujinx.Common;
|
using Ryujinx.Common;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
|
using Ryujinx.Common.Memory;
|
||||||
using System;
|
using System;
|
||||||
using System.Buffers.Binary;
|
using System.Buffers.Binary;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
@ -182,7 +183,7 @@ namespace ARMeilleure.Translation.PTC
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
using (MemoryStream stream = new MemoryStream())
|
using (MemoryStream stream = MemoryStreamManager.Shared.GetStream())
|
||||||
{
|
{
|
||||||
Debug.Assert(stream.Seek(0L, SeekOrigin.Begin) == 0L && stream.Length == 0L);
|
Debug.Assert(stream.Seek(0L, SeekOrigin.Begin) == 0L && stream.Length == 0L);
|
||||||
|
|
||||||
@ -274,7 +275,7 @@ namespace ARMeilleure.Translation.PTC
|
|||||||
|
|
||||||
outerHeader.SetHeaderHash();
|
outerHeader.SetHeaderHash();
|
||||||
|
|
||||||
using (MemoryStream stream = new MemoryStream())
|
using (MemoryStream stream = MemoryStreamManager.Shared.GetStream())
|
||||||
{
|
{
|
||||||
Debug.Assert(stream.Seek(0L, SeekOrigin.Begin) == 0L && stream.Length == 0L);
|
Debug.Assert(stream.Seek(0L, SeekOrigin.Begin) == 0L && stream.Length == 0L);
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
using ARMeilleure.Common;
|
using ARMeilleure.Common;
|
||||||
using System;
|
using System;
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace ARMeilleure.Translation
|
namespace ARMeilleure.Translation
|
||||||
{
|
{
|
||||||
@ -8,23 +7,28 @@ namespace ARMeilleure.Translation
|
|||||||
{
|
{
|
||||||
private readonly GuestFunction _func; // Ensure that this delegate will not be garbage collected.
|
private readonly GuestFunction _func; // Ensure that this delegate will not be garbage collected.
|
||||||
|
|
||||||
|
public IntPtr FuncPointer { get; }
|
||||||
public Counter<uint> CallCounter { get; }
|
public Counter<uint> CallCounter { get; }
|
||||||
public ulong GuestSize { get; }
|
public ulong GuestSize { get; }
|
||||||
public bool HighCq { 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;
|
_func = func;
|
||||||
|
FuncPointer = funcPointer;
|
||||||
CallCounter = callCounter;
|
CallCounter = callCounter;
|
||||||
GuestSize = guestSize;
|
GuestSize = guestSize;
|
||||||
HighCq = highCq;
|
HighCq = highCq;
|
||||||
FuncPtr = Marshal.GetFunctionPointerForDelegate(func);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ulong Execute(State.ExecutionContext context)
|
public ulong Execute(State.ExecutionContext context)
|
||||||
{
|
{
|
||||||
return _func(context.NativeContextPtr);
|
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();
|
Statistics.StartTimer();
|
||||||
|
|
||||||
ulong nextAddr = func.Execute(context);
|
ulong nextAddr = func.Execute(Stubs.ContextWrapper, context);
|
||||||
|
|
||||||
Statistics.StopTimer(address);
|
Statistics.StopTimer(address);
|
||||||
|
|
||||||
@ -194,7 +194,7 @@ namespace ARMeilleure.Translation
|
|||||||
{
|
{
|
||||||
TranslatedFunction func = Translate(address, context.ExecutionMode, highCq: false, singleStep: true);
|
TranslatedFunction func = Translate(address, context.ExecutionMode, highCq: false, singleStep: true);
|
||||||
|
|
||||||
address = func.Execute(context);
|
address = func.Execute(Stubs.ContextWrapper, context);
|
||||||
|
|
||||||
EnqueueForDeletion(address, func);
|
EnqueueForDeletion(address, func);
|
||||||
|
|
||||||
@ -211,7 +211,7 @@ namespace ARMeilleure.Translation
|
|||||||
|
|
||||||
if (oldFunc != func)
|
if (oldFunc != func)
|
||||||
{
|
{
|
||||||
JitCache.Unmap(func.FuncPtr);
|
JitCache.Unmap(func.FuncPointer);
|
||||||
func = oldFunc;
|
func = oldFunc;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -230,7 +230,7 @@ namespace ARMeilleure.Translation
|
|||||||
{
|
{
|
||||||
if (FunctionTable.IsValid(guestAddress) && (Optimizations.AllowLcqInFunctionTable || func.HighCq))
|
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);
|
_ptc.WriteCompiledFunction(address, funcSize, hash, highCq, compiledFunc);
|
||||||
}
|
}
|
||||||
|
|
||||||
GuestFunction func = compiledFunc.Map<GuestFunction>();
|
GuestFunction func = compiledFunc.MapWithPointer<GuestFunction>(out IntPtr funcPointer);
|
||||||
|
|
||||||
Allocators.ResetAll();
|
Allocators.ResetAll();
|
||||||
|
|
||||||
return new TranslatedFunction(func, counter, funcSize, highCq);
|
return new TranslatedFunction(func, funcPointer, counter, funcSize, highCq);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void BackgroundTranslate()
|
private void BackgroundTranslate()
|
||||||
@ -537,7 +537,7 @@ namespace ARMeilleure.Translation
|
|||||||
|
|
||||||
foreach (var func in functions)
|
foreach (var func in functions)
|
||||||
{
|
{
|
||||||
JitCache.Unmap(func.FuncPtr);
|
JitCache.Unmap(func.FuncPointer);
|
||||||
|
|
||||||
func.CallCounter?.Dispose();
|
func.CallCounter?.Dispose();
|
||||||
}
|
}
|
||||||
@ -546,7 +546,7 @@ namespace ARMeilleure.Translation
|
|||||||
|
|
||||||
while (_oldFuncs.TryDequeue(out var kv))
|
while (_oldFuncs.TryDequeue(out var kv))
|
||||||
{
|
{
|
||||||
JitCache.Unmap(kv.Value.FuncPtr);
|
JitCache.Unmap(kv.Value.FuncPointer);
|
||||||
|
|
||||||
kv.Value.CallCounter?.Dispose();
|
kv.Value.CallCounter?.Dispose();
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@ namespace ARMeilleure.Translation
|
|||||||
private readonly Translator _translator;
|
private readonly Translator _translator;
|
||||||
private readonly Lazy<IntPtr> _dispatchStub;
|
private readonly Lazy<IntPtr> _dispatchStub;
|
||||||
private readonly Lazy<DispatcherFunction> _dispatchLoop;
|
private readonly Lazy<DispatcherFunction> _dispatchLoop;
|
||||||
|
private readonly Lazy<WrapperFunction> _contextWrapper;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the dispatch stub.
|
/// 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>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="TranslatorStubs"/> class with the specified
|
/// Initializes a new instance of the <see cref="TranslatorStubs"/> class with the specified
|
||||||
/// <see cref="Translator"/> instance.
|
/// <see cref="Translator"/> instance.
|
||||||
@ -77,6 +92,7 @@ namespace ARMeilleure.Translation
|
|||||||
_translator = translator;
|
_translator = translator;
|
||||||
_dispatchStub = new(GenerateDispatchStub, isThreadSafe: true);
|
_dispatchStub = new(GenerateDispatchStub, isThreadSafe: true);
|
||||||
_dispatchLoop = new(GenerateDispatchLoop, isThreadSafe: true);
|
_dispatchLoop = new(GenerateDispatchLoop, isThreadSafe: true);
|
||||||
|
_contextWrapper = new(GenerateContextWrapper, isThreadSafe: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -202,6 +218,32 @@ namespace ARMeilleure.Translation
|
|||||||
return Marshal.GetFunctionPointerForDelegate(func);
|
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>
|
/// <summary>
|
||||||
/// Generates a <see cref="DispatchLoop"/> function.
|
/// Generates a <see cref="DispatchLoop"/> function.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -221,6 +263,8 @@ namespace ARMeilleure.Translation
|
|||||||
Operand runningAddress = context.Add(nativeContext, Const((ulong)NativeContext.GetRunningOffset()));
|
Operand runningAddress = context.Add(nativeContext, Const((ulong)NativeContext.GetRunningOffset()));
|
||||||
Operand dispatchAddress = context.Add(nativeContext, Const((ulong)NativeContext.GetDispatchAddressOffset()));
|
Operand dispatchAddress = context.Add(nativeContext, Const((ulong)NativeContext.GetDispatchAddressOffset()));
|
||||||
|
|
||||||
|
EmitSyncFpContext(context, nativeContext, true);
|
||||||
|
|
||||||
context.MarkLabel(beginLbl);
|
context.MarkLabel(beginLbl);
|
||||||
context.Store(dispatchAddress, guestAddress);
|
context.Store(dispatchAddress, guestAddress);
|
||||||
context.Copy(guestAddress, context.Call(Const((ulong)DispatchStub), OperandType.I64, nativeContext));
|
context.Copy(guestAddress, context.Call(Const((ulong)DispatchStub), OperandType.I64, nativeContext));
|
||||||
@ -229,6 +273,9 @@ namespace ARMeilleure.Translation
|
|||||||
context.Branch(beginLbl);
|
context.Branch(beginLbl);
|
||||||
|
|
||||||
context.MarkLabel(endLbl);
|
context.MarkLabel(endLbl);
|
||||||
|
|
||||||
|
EmitSyncFpContext(context, nativeContext, false);
|
||||||
|
|
||||||
context.Return();
|
context.Return();
|
||||||
|
|
||||||
var cfg = context.GetControlFlowGraph();
|
var cfg = context.GetControlFlowGraph();
|
||||||
@ -237,5 +284,29 @@ namespace ARMeilleure.Translation
|
|||||||
|
|
||||||
return Compiler.Compile(cfg, argTypes, retType, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<DispatcherFunction>();
|
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>
|
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageVersion Include="Avalonia" Version="0.10.18" />
|
<PackageVersion Include="Avalonia" Version="0.10.19" />
|
||||||
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="0.10.18" />
|
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="0.10.19" />
|
||||||
<PackageVersion Include="Avalonia.Desktop" Version="0.10.18" />
|
<PackageVersion Include="Avalonia.Desktop" Version="0.10.19" />
|
||||||
<PackageVersion Include="Avalonia.Diagnostics" Version="0.10.18" />
|
<PackageVersion Include="Avalonia.Diagnostics" Version="0.10.19" />
|
||||||
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="0.10.18" />
|
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="0.10.19" />
|
||||||
<PackageVersion Include="Avalonia.Svg" Version="0.10.18" />
|
<PackageVersion Include="Avalonia.Svg" Version="0.10.18" />
|
||||||
<PackageVersion Include="Avalonia.Svg.Skia" Version="0.10.18" />
|
<PackageVersion Include="Avalonia.Svg.Skia" Version="0.10.18" />
|
||||||
<PackageVersion Include="CommandLineParser" Version="2.9.1" />
|
<PackageVersion Include="CommandLineParser" Version="2.9.1" />
|
||||||
<PackageVersion Include="Concentus" Version="1.1.7" />
|
<PackageVersion Include="Concentus" Version="1.1.7" />
|
||||||
<PackageVersion Include="Crc32.NET" Version="1.2.0" />
|
|
||||||
<PackageVersion Include="DiscordRichPresence" Version="1.1.3.18" />
|
<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="FluentAvaloniaUI" Version="1.4.5" />
|
||||||
<PackageVersion Include="GtkSharp.Dependencies" Version="1.1.1" />
|
<PackageVersion Include="GtkSharp.Dependencies" Version="1.1.1" />
|
||||||
<PackageVersion Include="GtkSharp.Dependencies.osx" Version="0.0.5" />
|
<PackageVersion Include="GtkSharp.Dependencies.osx" Version="0.0.5" />
|
||||||
<PackageVersion Include="jp2masa.Avalonia.Flexbox" Version="0.2.0" />
|
<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.Analyzers" Version="3.3.4" />
|
||||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.4.0" />
|
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.5.0" />
|
||||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
|
<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="MsgPack.Cli" Version="1.0.1" />
|
||||||
<PackageVersion Include="NUnit" Version="3.13.3" />
|
<PackageVersion Include="NUnit" Version="3.13.3" />
|
||||||
<PackageVersion Include="NUnit3TestAdapter" Version="4.1.0" />
|
<PackageVersion Include="NUnit3TestAdapter" Version="4.1.0" />
|
||||||
<PackageVersion Include="OpenTK.Core" Version="4.7.5" />
|
<PackageVersion Include="OpenTK.Core" Version="4.7.7" />
|
||||||
<PackageVersion Include="OpenTK.Graphics" Version="4.7.5" />
|
<PackageVersion Include="OpenTK.Graphics" Version="4.7.7" />
|
||||||
<PackageVersion Include="OpenTK.OpenAL" Version="4.7.5" />
|
<PackageVersion Include="OpenTK.OpenAL" Version="4.7.7" />
|
||||||
<PackageVersion Include="OpenTK.Windowing.GraphicsLibraryFramework" Version="4.7.5" />
|
<PackageVersion Include="OpenTK.Windowing.GraphicsLibraryFramework" Version="4.7.7" />
|
||||||
<PackageVersion Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" />
|
<PackageVersion Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" />
|
||||||
<PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.1-build13" />
|
<PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.1-build13" />
|
||||||
<PackageVersion Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" />
|
<PackageVersion Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" />
|
||||||
<PackageVersion Include="Ryujinx.GtkSharp" Version="3.24.24.59-ryujinx" />
|
<PackageVersion Include="Ryujinx.GtkSharp" Version="3.24.24.59-ryujinx" />
|
||||||
<PackageVersion Include="Ryujinx.SDL2-CS" Version="2.26.1-build23" />
|
<PackageVersion Include="Ryujinx.SDL2-CS" Version="2.26.3-build25" />
|
||||||
<PackageVersion Include="shaderc.net" Version="0.1.0" />
|
<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" Version="2.16.0" />
|
||||||
<PackageVersion Include="Silk.NET.Vulkan.Extensions.EXT" 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" />
|
<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="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta11" />
|
||||||
<PackageVersion Include="SPB" Version="0.0.4-build28" />
|
<PackageVersion Include="SPB" Version="0.0.4-build28" />
|
||||||
<PackageVersion Include="System.Drawing.Common" Version="7.0.0" />
|
<PackageVersion Include="System.Drawing.Common" Version="7.0.0" />
|
||||||
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="6.26.0" />
|
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="6.29.0" />
|
||||||
<PackageVersion Include="System.IO.FileSystem.Primitives" Version="4.3.0" />
|
<PackageVersion Include="System.IO.Hashing" Version="7.0.0" />
|
||||||
<PackageVersion Include="System.Management" Version="7.0.0" />
|
<PackageVersion Include="System.Management" Version="7.0.1" />
|
||||||
<PackageVersion Include="System.Net.NameResolution" Version="4.3.0" />
|
<PackageVersion Include="UnicornEngine.Unicorn" Version="2.0.2-rc1-fb78016" />
|
||||||
<PackageVersion Include="System.Threading.ThreadPool" Version="4.3.0" />
|
<PackageVersion Include="XamlNameReferenceGenerator" Version="1.6.1" />
|
||||||
<PackageVersion Include="XamlNameReferenceGenerator" Version="1.5.1" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
@ -40,7 +40,7 @@
|
|||||||
|
|
||||||
## Compatibility
|
## 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!
|
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
|
## Usage
|
||||||
@ -96,7 +96,7 @@ Ryujinx system files are stored in the `Ryujinx` folder. This folder is located
|
|||||||
|
|
||||||
- **GPU**
|
- **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**
|
- **Input**
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@ namespace Ryujinx.Audio.Backends.SDL2
|
|||||||
private ulong _playedSampleCount;
|
private ulong _playedSampleCount;
|
||||||
private ManualResetEvent _updateRequiredEvent;
|
private ManualResetEvent _updateRequiredEvent;
|
||||||
private uint _outputStream;
|
private uint _outputStream;
|
||||||
|
private bool _hasSetupError;
|
||||||
private SDL_AudioCallback _callbackDelegate;
|
private SDL_AudioCallback _callbackDelegate;
|
||||||
private int _bytesPerFrame;
|
private int _bytesPerFrame;
|
||||||
private uint _sampleCount;
|
private uint _sampleCount;
|
||||||
@ -42,7 +43,7 @@ namespace Ryujinx.Audio.Backends.SDL2
|
|||||||
private void EnsureAudioStreamSetup(AudioBuffer buffer)
|
private void EnsureAudioStreamSetup(AudioBuffer buffer)
|
||||||
{
|
{
|
||||||
uint bufferSampleCount = (uint)GetSampleCount(buffer);
|
uint bufferSampleCount = (uint)GetSampleCount(buffer);
|
||||||
bool needAudioSetup = _outputStream == 0 ||
|
bool needAudioSetup = (_outputStream == 0 && !_hasSetupError) ||
|
||||||
(bufferSampleCount >= Constants.TargetSampleCount && bufferSampleCount < _sampleCount);
|
(bufferSampleCount >= Constants.TargetSampleCount && bufferSampleCount < _sampleCount);
|
||||||
|
|
||||||
if (needAudioSetup)
|
if (needAudioSetup)
|
||||||
@ -51,12 +52,9 @@ namespace Ryujinx.Audio.Backends.SDL2
|
|||||||
|
|
||||||
uint newOutputStream = SDL2HardwareDeviceDriver.OpenStream(RequestedSampleFormat, RequestedSampleRate, RequestedChannelCount, _sampleCount, _callbackDelegate);
|
uint newOutputStream = SDL2HardwareDeviceDriver.OpenStream(RequestedSampleFormat, RequestedSampleRate, RequestedChannelCount, _sampleCount, _callbackDelegate);
|
||||||
|
|
||||||
if (newOutputStream == 0)
|
_hasSetupError = newOutputStream == 0;
|
||||||
{
|
|
||||||
// No stream in place, this is unexpected.
|
if (!_hasSetupError)
|
||||||
throw new InvalidOperationException($"OpenStream failed with error: \"{SDL_GetError()}\"");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
if (_outputStream != 0)
|
if (_outputStream != 0)
|
||||||
{
|
{
|
||||||
@ -151,11 +149,20 @@ namespace Ryujinx.Audio.Backends.SDL2
|
|||||||
{
|
{
|
||||||
EnsureAudioStreamSetup(buffer);
|
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)
|
public override void SetVolume(float volume)
|
||||||
|
@ -5,9 +5,8 @@ using Ryujinx.Memory;
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
|
||||||
using static Ryujinx.Audio.Integration.IHardwareDeviceDriver;
|
|
||||||
using static Ryujinx.Audio.Backends.SoundIo.Native.SoundIo;
|
using static Ryujinx.Audio.Backends.SoundIo.Native.SoundIo;
|
||||||
|
using static Ryujinx.Audio.Integration.IHardwareDeviceDriver;
|
||||||
|
|
||||||
namespace Ryujinx.Audio.Backends.SoundIo
|
namespace Ryujinx.Audio.Backends.SoundIo
|
||||||
{
|
{
|
||||||
|
@ -400,7 +400,9 @@ namespace Ryujinx.Audio.Common
|
|||||||
{
|
{
|
||||||
uint bufferIndex = (_releasedBufferIndex - _bufferReleasedCount) % Constants.AudioDeviceBufferCountMax;
|
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)
|
if (_buffers[bufferIndex].BufferTag == bufferTag)
|
||||||
{
|
{
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
using System;
|
using Ryujinx.Audio.Renderer.Dsp.Effect;
|
||||||
using System.Diagnostics;
|
|
||||||
using Ryujinx.Audio.Renderer.Dsp.Effect;
|
|
||||||
using Ryujinx.Audio.Renderer.Dsp.State;
|
using Ryujinx.Audio.Renderer.Dsp.State;
|
||||||
using Ryujinx.Audio.Renderer.Parameter.Effect;
|
using Ryujinx.Audio.Renderer.Parameter.Effect;
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
namespace Ryujinx.Audio.Renderer.Dsp.Command
|
namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||||
{
|
{
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Numerics;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.Intrinsics;
|
using System.Runtime.Intrinsics;
|
||||||
using System.Runtime.Intrinsics.X86;
|
using System.Runtime.Intrinsics.X86;
|
||||||
@ -380,7 +381,6 @@ namespace Ryujinx.Audio.Renderer.Dsp
|
|||||||
return _normalCurveLut2F;
|
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)
|
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);
|
ReadOnlySpan<float> parameters = GetDefaultParameter(ratio);
|
||||||
@ -394,35 +394,33 @@ namespace Ryujinx.Audio.Renderer.Dsp
|
|||||||
if (ratio == 1f)
|
if (ratio == 1f)
|
||||||
{
|
{
|
||||||
fixed (short* pInput = inputBuffer)
|
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<float> input0 = Sse2.ConvertToVector128Single(intInput0);
|
||||||
{
|
Vector128<float> input1 = Sse2.ConvertToVector128Single(intInput1);
|
||||||
Vector128<int> intInput0 = Sse41.ConvertToVector128Int32(pInput + (uint)i);
|
Vector128<float> input2 = Sse2.ConvertToVector128Single(intInput2);
|
||||||
Vector128<int> intInput1 = Sse41.ConvertToVector128Int32(pInput + (uint)i + 1);
|
Vector128<float> input3 = Sse2.ConvertToVector128Single(intInput3);
|
||||||
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> mix0 = Sse.Multiply(input0, parameter);
|
||||||
Vector128<float> input1 = Sse2.ConvertToVector128Single(intInput1);
|
Vector128<float> mix1 = Sse.Multiply(input1, parameter);
|
||||||
Vector128<float> input2 = Sse2.ConvertToVector128Single(intInput2);
|
Vector128<float> mix2 = Sse.Multiply(input2, parameter);
|
||||||
Vector128<float> input3 = Sse2.ConvertToVector128Single(intInput3);
|
Vector128<float> mix3 = Sse.Multiply(input3, parameter);
|
||||||
|
|
||||||
Vector128<float> mix0 = Sse.Multiply(input0, parameter);
|
Vector128<float> mix01 = Sse3.HorizontalAdd(mix0, mix1);
|
||||||
Vector128<float> mix1 = Sse.Multiply(input1, parameter);
|
Vector128<float> mix23 = Sse3.HorizontalAdd(mix2, mix3);
|
||||||
Vector128<float> mix2 = Sse.Multiply(input2, parameter);
|
|
||||||
Vector128<float> mix3 = Sse.Multiply(input3, parameter);
|
|
||||||
|
|
||||||
Vector128<float> mix01 = Sse3.HorizontalAdd(mix0, mix1);
|
Vector128<float> mix0123 = Sse3.HorizontalAdd(mix01, mix23);
|
||||||
Vector128<float> mix23 = Sse3.HorizontalAdd(mix2, mix3);
|
|
||||||
|
|
||||||
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
|
else
|
||||||
{
|
{
|
||||||
fixed (short* pInput = inputBuffer)
|
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 baseIndex1 = ((uint)(fraction * 128) & 127) * 4;
|
||||||
uint inputIndex1 = (uint)inputBufferIndex + (uint)fraction;
|
uint inputIndex1 = (uint)inputBufferIndex + (uint)fraction;
|
||||||
|
|
||||||
fraction += ratio;
|
fraction += ratio;
|
||||||
|
|
||||||
uint baseIndex2 = ((uint)(fraction * 128) & 127) * 4;
|
uint baseIndex2 = ((uint)(fraction * 128) & 127) * 4;
|
||||||
uint inputIndex2 = (uint)inputBufferIndex + (uint)fraction;
|
uint inputIndex2 = (uint)inputBufferIndex + (uint)fraction;
|
||||||
|
|
||||||
fraction += ratio;
|
fraction += ratio;
|
||||||
|
|
||||||
uint baseIndex3 = ((uint)(fraction * 128) & 127) * 4;
|
uint baseIndex3 = ((uint)(fraction * 128) & 127) * 4;
|
||||||
uint inputIndex3 = (uint)inputBufferIndex + (uint)fraction;
|
uint inputIndex3 = (uint)inputBufferIndex + (uint)fraction;
|
||||||
|
|
||||||
fraction += ratio;
|
fraction += ratio;
|
||||||
inputBufferIndex += (int)fraction;
|
inputBufferIndex += (int)fraction;
|
||||||
|
|
||||||
// Only keep lower part (safe as fraction isn't supposed to be negative)
|
// Only keep lower part (safe as fraction isn't supposed to be negative)
|
||||||
fraction -= (int)fraction;
|
fraction -= (int)fraction;
|
||||||
|
|
||||||
Vector128<float> parameter0 = Sse.LoadVector128(pParameters + baseIndex0);
|
Vector128<float> parameter0 = Sse.LoadVector128(pParameters + baseIndex0);
|
||||||
Vector128<float> parameter1 = Sse.LoadVector128(pParameters + baseIndex1);
|
Vector128<float> parameter1 = Sse.LoadVector128(pParameters + baseIndex1);
|
||||||
Vector128<float> parameter2 = Sse.LoadVector128(pParameters + baseIndex2);
|
Vector128<float> parameter2 = Sse.LoadVector128(pParameters + baseIndex2);
|
||||||
Vector128<float> parameter3 = Sse.LoadVector128(pParameters + baseIndex3);
|
Vector128<float> parameter3 = Sse.LoadVector128(pParameters + baseIndex3);
|
||||||
|
|
||||||
Vector128<int> intInput0 = Sse41.ConvertToVector128Int32(pInput + inputIndex0);
|
Vector128<int> intInput0 = Sse41.ConvertToVector128Int32(pInput + inputIndex0);
|
||||||
Vector128<int> intInput1 = Sse41.ConvertToVector128Int32(pInput + inputIndex1);
|
Vector128<int> intInput1 = Sse41.ConvertToVector128Int32(pInput + inputIndex1);
|
||||||
Vector128<int> intInput2 = Sse41.ConvertToVector128Int32(pInput + inputIndex2);
|
Vector128<int> intInput2 = Sse41.ConvertToVector128Int32(pInput + inputIndex2);
|
||||||
Vector128<int> intInput3 = Sse41.ConvertToVector128Int32(pInput + inputIndex3);
|
Vector128<int> intInput3 = Sse41.ConvertToVector128Int32(pInput + inputIndex3);
|
||||||
|
|
||||||
Vector128<float> input0 = Sse2.ConvertToVector128Single(intInput0);
|
Vector128<float> input0 = Sse2.ConvertToVector128Single(intInput0);
|
||||||
Vector128<float> input1 = Sse2.ConvertToVector128Single(intInput1);
|
Vector128<float> input1 = Sse2.ConvertToVector128Single(intInput1);
|
||||||
Vector128<float> input2 = Sse2.ConvertToVector128Single(intInput2);
|
Vector128<float> input2 = Sse2.ConvertToVector128Single(intInput2);
|
||||||
Vector128<float> input3 = Sse2.ConvertToVector128Single(intInput3);
|
Vector128<float> input3 = Sse2.ConvertToVector128Single(intInput3);
|
||||||
|
|
||||||
Vector128<float> mix0 = Sse.Multiply(input0, parameter0);
|
Vector128<float> mix0 = Sse.Multiply(input0, parameter0);
|
||||||
Vector128<float> mix1 = Sse.Multiply(input1, parameter1);
|
Vector128<float> mix1 = Sse.Multiply(input1, parameter1);
|
||||||
Vector128<float> mix2 = Sse.Multiply(input2, parameter2);
|
Vector128<float> mix2 = Sse.Multiply(input2, parameter2);
|
||||||
Vector128<float> mix3 = Sse.Multiply(input3, parameter3);
|
Vector128<float> mix3 = Sse.Multiply(input3, parameter3);
|
||||||
|
|
||||||
Vector128<float> mix01 = Sse3.HorizontalAdd(mix0, mix1);
|
Vector128<float> mix01 = Sse3.HorizontalAdd(mix0, mix1);
|
||||||
Vector128<float> mix23 = Sse3.HorizontalAdd(mix2, mix3);
|
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;
|
return _highCurveLut2F;
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
private static unsafe void ResampleHighQuality(Span<float> outputBuffer, ReadOnlySpan<short> inputBuffer, float ratio, ref float fraction, int sampleCount)
|
||||||
private static void ResampleHighQuality(Span<float> outputBuffer, ReadOnlySpan<short> inputBuffer, float ratio, ref float fraction, int sampleCount)
|
|
||||||
{
|
{
|
||||||
ReadOnlySpan<float> parameters = GetHighParameter(ratio);
|
ReadOnlySpan<float> parameters = GetHighParameter(ratio);
|
||||||
|
|
||||||
int inputBufferIndex = 0;
|
int inputBufferIndex = 0;
|
||||||
|
|
||||||
// TODO: fast path
|
if (Avx2.IsSupported)
|
||||||
|
|
||||||
for (int i = 0; i < sampleCount; i++)
|
|
||||||
{
|
{
|
||||||
int baseIndex = (int)(fraction * 128) * 8;
|
// Fast path; assumes 256-bit vectors for simplicity because the filter is 8 taps
|
||||||
ReadOnlySpan<float> parameter = parameters.Slice(baseIndex, 8);
|
fixed (short* pInput = inputBuffer)
|
||||||
ReadOnlySpan<short> currentInput = inputBuffer.Slice(inputBufferIndex, 8);
|
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] +
|
Vector256<int> intInput = Avx2.ConvertToVector256Int32(pInput + inputBufferIndex);
|
||||||
currentInput[1] * parameter[1] +
|
Vector256<float> floatInput = Avx.ConvertToVector256Single(intInput);
|
||||||
currentInput[2] * parameter[2] +
|
Vector256<float> parameter = Avx.LoadVector256(pParameters + baseIndex);
|
||||||
currentInput[3] * parameter[3] +
|
Vector256<float> dp = Avx.DotProduct(floatInput, parameter, control: 0xFF);
|
||||||
currentInput[4] * parameter[4] +
|
|
||||||
currentInput[5] * parameter[5] +
|
|
||||||
currentInput[6] * parameter[6] +
|
|
||||||
currentInput[7] * parameter[7]);
|
|
||||||
|
|
||||||
fraction += ratio;
|
// avx2 does an 8-element dot product piecewise so we have to sum up 2 intermediate results
|
||||||
inputBufferIndex += (int)MathF.Truncate(fraction);
|
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 Ryujinx.Common.Memory;
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.Numerics;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
namespace Ryujinx.Audio.Renderer.Dsp
|
namespace Ryujinx.Audio.Renderer.Dsp
|
||||||
@ -70,16 +71,32 @@ namespace Ryujinx.Audio.Renderer.Dsp
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
float DoFilterBank(ref UpsamplerBufferState state, in Array20<float> bank)
|
float DoFilterBank(ref UpsamplerBufferState state, in Array20<float> bank)
|
||||||
{
|
{
|
||||||
float result = 0.0f;
|
float result = 0.0f;
|
||||||
|
|
||||||
Debug.Assert(state.History.Length == HistoryLength);
|
Debug.Assert(state.History.Length == HistoryLength);
|
||||||
Debug.Assert(bank.Length == FilterBankLength);
|
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;
|
return result;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
using Ryujinx.Audio.Renderer.Common;
|
using Ryujinx.Audio.Renderer.Common;
|
||||||
using Ryujinx.Audio.Renderer.Dsp.State;
|
using Ryujinx.Audio.Renderer.Dsp.State;
|
||||||
using Ryujinx.Audio.Renderer.Parameter.Effect;
|
|
||||||
using Ryujinx.Audio.Renderer.Parameter;
|
using Ryujinx.Audio.Renderer.Parameter;
|
||||||
|
using Ryujinx.Audio.Renderer.Parameter.Effect;
|
||||||
using Ryujinx.Audio.Renderer.Server.MemoryPool;
|
using Ryujinx.Audio.Renderer.Server.MemoryPool;
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
@ -53,7 +53,6 @@ using Key = Ryujinx.Input.Key;
|
|||||||
using MouseButton = Ryujinx.Input.MouseButton;
|
using MouseButton = Ryujinx.Input.MouseButton;
|
||||||
using Size = Avalonia.Size;
|
using Size = Avalonia.Size;
|
||||||
using Switch = Ryujinx.HLE.Switch;
|
using Switch = Ryujinx.HLE.Switch;
|
||||||
using WindowState = Avalonia.Controls.WindowState;
|
|
||||||
|
|
||||||
namespace Ryujinx.Ava
|
namespace Ryujinx.Ava
|
||||||
{
|
{
|
||||||
@ -172,6 +171,13 @@ namespace Ryujinx.Ava
|
|||||||
ConfigurationState.Instance.Graphics.AspectRatio.Event += UpdateAspectRatioState;
|
ConfigurationState.Instance.Graphics.AspectRatio.Event += UpdateAspectRatioState;
|
||||||
ConfigurationState.Instance.System.EnableDockedMode.Event += UpdateDockedModeState;
|
ConfigurationState.Instance.System.EnableDockedMode.Event += UpdateDockedModeState;
|
||||||
ConfigurationState.Instance.System.AudioVolume.Event += UpdateAudioVolumeState;
|
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();
|
_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()
|
private void ShowCursor()
|
||||||
{
|
{
|
||||||
@ -241,7 +258,7 @@ namespace Ryujinx.Ava
|
|||||||
{
|
{
|
||||||
DateTime currentTime = DateTime.Now;
|
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 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
|
string directory = AppDataManager.Mode switch
|
||||||
{
|
{
|
||||||
AppDataManager.LaunchMode.Portable => Path.Combine(AppDataManager.BaseDirPath, "screenshots"),
|
AppDataManager.LaunchMode.Portable => Path.Combine(AppDataManager.BaseDirPath, "screenshots"),
|
||||||
@ -305,14 +322,16 @@ namespace Ryujinx.Ava
|
|||||||
|
|
||||||
_viewModel.IsGameRunning = true;
|
_viewModel.IsGameRunning = true;
|
||||||
|
|
||||||
string titleNameSection = string.IsNullOrWhiteSpace(Device.Application.TitleName) ? string.Empty : $" - {Device.Application.TitleName}";
|
var activeProcess = Device.Processes.ActiveApplication;
|
||||||
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 titleNameSection = string.IsNullOrWhiteSpace(activeProcess.Name) ? string.Empty : $" {activeProcess.Name}";
|
||||||
string titleArchSection = Device.Application.TitleIs64Bit ? " (64-bit)" : " (32-bit)";
|
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(() =>
|
Dispatcher.UIThread.InvokeAsync(() =>
|
||||||
{
|
{
|
||||||
_viewModel.Title = $"Ryujinx {Program.Version}{titleNameSection}{titleVersionSection}{titleIdSection}{titleArchSection}";
|
_viewModel.Title = $"Ryujinx {Program.Version} -{titleNameSection}{titleVersionSection}{titleIdSection}{titleArchSection}";
|
||||||
});
|
});
|
||||||
|
|
||||||
_viewModel.SetUIProgressHandlers(Device);
|
_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)
|
private void UpdateDockedModeState(object sender, ReactiveEventArgs<bool> e)
|
||||||
{
|
{
|
||||||
Device?.System.ChangeDockedModeState(e.NewValue);
|
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()
|
public void Stop()
|
||||||
{
|
{
|
||||||
_isActive = false;
|
_isActive = false;
|
||||||
@ -403,15 +432,18 @@ namespace Ryujinx.Ava
|
|||||||
|
|
||||||
private void Dispose()
|
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.System.IgnoreMissingServices.Event -= UpdateIgnoreMissingServicesState;
|
||||||
ConfigurationState.Instance.Graphics.AspectRatio.Event -= UpdateAspectRatioState;
|
ConfigurationState.Instance.Graphics.AspectRatio.Event -= UpdateAspectRatioState;
|
||||||
ConfigurationState.Instance.System.EnableDockedMode.Event -= UpdateDockedModeState;
|
ConfigurationState.Instance.System.EnableDockedMode.Event -= UpdateDockedModeState;
|
||||||
ConfigurationState.Instance.System.AudioVolume.Event -= UpdateAudioVolumeState;
|
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;
|
_topLevel.PointerMoved -= TopLevel_PointerMoved;
|
||||||
|
|
||||||
@ -516,7 +548,12 @@ namespace Ryujinx.Ava
|
|||||||
{
|
{
|
||||||
Logger.Info?.Print(LogClass.Application, "Loading as Firmware Title (NCA).");
|
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))
|
else if (Directory.Exists(ApplicationPath))
|
||||||
{
|
{
|
||||||
@ -531,13 +568,23 @@ namespace Ryujinx.Ava
|
|||||||
{
|
{
|
||||||
Logger.Info?.Print(LogClass.Application, "Loading as cart with RomFS.");
|
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
|
else
|
||||||
{
|
{
|
||||||
Logger.Info?.Print(LogClass.Application, "Loading as cart WITHOUT RomFS.");
|
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))
|
else if (File.Exists(ApplicationPath))
|
||||||
@ -548,7 +595,12 @@ namespace Ryujinx.Ava
|
|||||||
{
|
{
|
||||||
Logger.Info?.Print(LogClass.Application, "Loading as XCI.");
|
Logger.Info?.Print(LogClass.Application, "Loading as XCI.");
|
||||||
|
|
||||||
Device.LoadXci(ApplicationPath);
|
if (!Device.LoadXci(ApplicationPath))
|
||||||
|
{
|
||||||
|
Device.Dispose();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -556,7 +608,12 @@ namespace Ryujinx.Ava
|
|||||||
{
|
{
|
||||||
Logger.Info?.Print(LogClass.Application, "Loading as NCA.");
|
Logger.Info?.Print(LogClass.Application, "Loading as NCA.");
|
||||||
|
|
||||||
Device.LoadNca(ApplicationPath);
|
if (!Device.LoadNca(ApplicationPath))
|
||||||
|
{
|
||||||
|
Device.Dispose();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -565,7 +622,12 @@ namespace Ryujinx.Ava
|
|||||||
{
|
{
|
||||||
Logger.Info?.Print(LogClass.Application, "Loading as NSP.");
|
Logger.Info?.Print(LogClass.Application, "Loading as NSP.");
|
||||||
|
|
||||||
Device.LoadNsp(ApplicationPath);
|
if (!Device.LoadNsp(ApplicationPath))
|
||||||
|
{
|
||||||
|
Device.Dispose();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -575,13 +637,18 @@ namespace Ryujinx.Ava
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Device.LoadProgram(ApplicationPath);
|
if (!Device.LoadProgram(ApplicationPath))
|
||||||
|
{
|
||||||
|
Device.Dispose();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (ArgumentOutOfRangeException)
|
catch (ArgumentOutOfRangeException)
|
||||||
{
|
{
|
||||||
Logger.Error?.Print(LogClass.Application, "The specified file is not supported by Ryujinx.");
|
Logger.Error?.Print(LogClass.Application, "The specified file is not supported by Ryujinx.");
|
||||||
|
|
||||||
Dispose();
|
Device.Dispose();
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -594,14 +661,14 @@ namespace Ryujinx.Ava
|
|||||||
{
|
{
|
||||||
Logger.Warning?.Print(LogClass.Application, "Please specify a valid XCI/NCA/NSP/PFS0/NRO file.");
|
Logger.Warning?.Print(LogClass.Application, "Please specify a valid XCI/NCA/NSP/PFS0/NRO file.");
|
||||||
|
|
||||||
Dispose();
|
Device.Dispose();
|
||||||
|
|
||||||
return false;
|
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();
|
appMetadata.LastPlayed = DateTime.UtcNow.ToString();
|
||||||
});
|
});
|
||||||
@ -678,7 +745,9 @@ namespace Ryujinx.Ava
|
|||||||
ConfigurationState.Instance.System.MemoryManagerMode,
|
ConfigurationState.Instance.System.MemoryManagerMode,
|
||||||
ConfigurationState.Instance.System.IgnoreMissingServices,
|
ConfigurationState.Instance.System.IgnoreMissingServices,
|
||||||
ConfigurationState.Instance.Graphics.AspectRatio,
|
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);
|
Device = new Switch(configuration);
|
||||||
}
|
}
|
||||||
@ -765,7 +834,7 @@ namespace Ryujinx.Ava
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private unsafe void RenderLoop()
|
private void RenderLoop()
|
||||||
{
|
{
|
||||||
Dispatcher.UIThread.InvokeAsync(() =>
|
Dispatcher.UIThread.InvokeAsync(() =>
|
||||||
{
|
{
|
||||||
@ -788,6 +857,10 @@ namespace Ryujinx.Ava
|
|||||||
|
|
||||||
Device.Gpu.Renderer.Initialize(_glLogLevel);
|
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;
|
Width = (int)_rendererHost.Bounds.Width;
|
||||||
Height = (int)_rendererHost.Bounds.Height;
|
Height = (int)_rendererHost.Bounds.Height;
|
||||||
|
|
||||||
@ -801,6 +874,8 @@ namespace Ryujinx.Ava
|
|||||||
Device.Gpu.InitializeShaderCache(_gpuCancellationTokenSource.Token);
|
Device.Gpu.InitializeShaderCache(_gpuCancellationTokenSource.Token);
|
||||||
Translator.IsReadyForTranslation.Set();
|
Translator.IsReadyForTranslation.Set();
|
||||||
|
|
||||||
|
_renderer.Window.ChangeVSyncMode(Device.EnableDeviceVsync);
|
||||||
|
|
||||||
while (_isActive)
|
while (_isActive)
|
||||||
{
|
{
|
||||||
_ticks += _chrono.ElapsedTicks;
|
_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.
|
// 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];
|
string dockedMode = ConfigurationState.Instance.System.EnableDockedMode ? LocaleManager.Instance[LocaleKeys.Docked] : LocaleManager.Instance[LocaleKeys.Handheld];
|
||||||
|
|
||||||
if (GraphicsConfig.ResScale != 1)
|
if (GraphicsConfig.ResScale != 1)
|
||||||
{
|
{
|
||||||
dockedMode += $" ({GraphicsConfig.ResScale}x)";
|
dockedMode += $" ({GraphicsConfig.ResScale}x)";
|
||||||
@ -920,7 +995,7 @@ namespace Ryujinx.Ava
|
|||||||
{
|
{
|
||||||
if (_keyboardInterface.GetKeyboardStateSnapshot().IsPressed(Key.Delete) && _viewModel.WindowState != WindowState.FullScreen)
|
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;
|
return state;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
"SettingsTabSystemMemoryManagerModeSoftware": "Software",
|
"SettingsTabSystemMemoryManagerModeSoftware": "Software",
|
||||||
"SettingsTabSystemMemoryManagerModeHost": "Host (fast)",
|
"SettingsTabSystemMemoryManagerModeHost": "Host (fast)",
|
||||||
"SettingsTabSystemMemoryManagerModeHostUnchecked": "Host Unchecked (fastest, unsafe)",
|
"SettingsTabSystemMemoryManagerModeHostUnchecked": "Host Unchecked (fastest, unsafe)",
|
||||||
|
"SettingsTabSystemUseHypervisor": "Use Hypervisor",
|
||||||
"MenuBarFile": "_File",
|
"MenuBarFile": "_File",
|
||||||
"MenuBarFileOpenFromFile": "_Load Application From File",
|
"MenuBarFileOpenFromFile": "_Load Application From File",
|
||||||
"MenuBarFileOpenUnpacked": "Load _Unpacked Game",
|
"MenuBarFileOpenUnpacked": "Load _Unpacked Game",
|
||||||
@ -428,6 +429,7 @@
|
|||||||
"DlcManagerEnableAllButton": "Enable All",
|
"DlcManagerEnableAllButton": "Enable All",
|
||||||
"DlcManagerDisableAllButton": "Disable All",
|
"DlcManagerDisableAllButton": "Disable All",
|
||||||
"MenuBarOptionsChangeLanguage": "Change Language",
|
"MenuBarOptionsChangeLanguage": "Change Language",
|
||||||
|
"MenuBarShowFileTypes": "Show File Types",
|
||||||
"CommonSort": "Sort",
|
"CommonSort": "Sort",
|
||||||
"CommonShowNames": "Show Names",
|
"CommonShowNames": "Show Names",
|
||||||
"CommonFavorite": "Favorite",
|
"CommonFavorite": "Favorite",
|
||||||
@ -457,6 +459,7 @@
|
|||||||
"MemoryManagerSoftwareTooltip": "Use a software page table for address translation. Highest accuracy but slowest performance.",
|
"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.",
|
"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.",
|
"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.",
|
"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.",
|
"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.",
|
"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",
|
"SettingsTabNetwork": "Network",
|
||||||
"SettingsTabNetworkConnection": "Network Connection",
|
"SettingsTabNetworkConnection": "Network Connection",
|
||||||
"SettingsTabCpuCache": "CPU Cache",
|
"SettingsTabCpuCache": "CPU Cache",
|
||||||
"SettingsTabCpuMemory": "CPU Memory",
|
"SettingsTabCpuMemory": "CPU Mode",
|
||||||
"DialogUpdaterFlatpakNotSupportedMessage": "Please update Ryujinx via FlatHub.",
|
"DialogUpdaterFlatpakNotSupportedMessage": "Please update Ryujinx via FlatHub.",
|
||||||
"UpdaterDisabledWarningTitle": "Updater Disabled!",
|
"UpdaterDisabledWarningTitle": "Updater Disabled!",
|
||||||
"GameListContextMenuOpenSdModsDirectory": "Open Atmosphere Mods Directory",
|
"GameListContextMenuOpenSdModsDirectory": "Open Atmosphere Mods Directory",
|
||||||
@ -581,10 +584,10 @@
|
|||||||
"SelectUpdateDialogTitle": "Select update files",
|
"SelectUpdateDialogTitle": "Select update files",
|
||||||
"UserProfileWindowTitle": "User Profiles Manager",
|
"UserProfileWindowTitle": "User Profiles Manager",
|
||||||
"CheatWindowTitle": "Cheats Manager",
|
"CheatWindowTitle": "Cheats Manager",
|
||||||
"DlcWindowTitle": "Downloadable Content Manager",
|
"DlcWindowTitle": "Manage Downloadable Content for {0} ({1})",
|
||||||
"UpdateWindowTitle": "Title Update Manager",
|
"UpdateWindowTitle": "Title Update Manager",
|
||||||
"CheatWindowHeading": "Cheats Available for {0} [{1}]",
|
"CheatWindowHeading": "Cheats Available for {0} [{1}]",
|
||||||
"DlcWindowHeading": "{0} Downloadable Content(s) available for {1} ({2})",
|
"DlcWindowHeading": "{0} Downloadable Content(s)",
|
||||||
"UserProfilesEditProfile": "Edit Selected",
|
"UserProfilesEditProfile": "Edit Selected",
|
||||||
"Cancel": "Cancel",
|
"Cancel": "Cancel",
|
||||||
"Save": "Save",
|
"Save": "Save",
|
||||||
@ -624,6 +627,19 @@
|
|||||||
"Recover": "Recover",
|
"Recover": "Recover",
|
||||||
"UserProfilesRecoverHeading" : "Saves were found for the following accounts",
|
"UserProfilesRecoverHeading" : "Saves were found for the following accounts",
|
||||||
"UserProfilesRecoverEmptyList": "No profiles to recover",
|
"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",
|
"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;
|
||||||
using LibHac.Tools.FsSystem.NcaUtils;
|
using LibHac.Tools.FsSystem.NcaUtils;
|
||||||
using Ryujinx.Ava.Common.Locale;
|
using Ryujinx.Ava.Common.Locale;
|
||||||
|
using Ryujinx.Ava.UI.Controls;
|
||||||
using Ryujinx.Ava.UI.Helpers;
|
using Ryujinx.Ava.UI.Helpers;
|
||||||
using Ryujinx.Ava.UI.Windows;
|
using Ryujinx.Ava.UI.Windows;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.HLE.FileSystem;
|
using Ryujinx.HLE.FileSystem;
|
||||||
using Ryujinx.HLE.HOS;
|
using Ryujinx.HLE.HOS;
|
||||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||||
|
using Ryujinx.Ui.App.Common;
|
||||||
using Ryujinx.Ui.Common.Helper;
|
using Ryujinx.Ui.Common.Helper;
|
||||||
using System;
|
using System;
|
||||||
using System.Buffers;
|
using System.Buffers;
|
||||||
@ -151,25 +153,17 @@ namespace Ryujinx.Ava.Common
|
|||||||
string destination = await folderDialog.ShowAsync(_owner);
|
string destination = await folderDialog.ShowAsync(_owner);
|
||||||
var cancellationToken = new CancellationTokenSource();
|
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))
|
if (!string.IsNullOrWhiteSpace(destination))
|
||||||
{
|
{
|
||||||
Thread extractorThread = new(() =>
|
Thread extractorThread = new(() =>
|
||||||
{
|
{
|
||||||
Dispatcher.UIThread.Post(async () =>
|
Dispatcher.UIThread.Post(waitingDialog.Show);
|
||||||
{
|
|
||||||
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(
|
|
||||||
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogNcaExtractionMessage, ncaSectionType, Path.GetFileName(titleFilePath)),
|
|
||||||
"",
|
|
||||||
"",
|
|
||||||
LocaleManager.Instance[LocaleKeys.InputDialogCancel],
|
|
||||||
LocaleManager.Instance[LocaleKeys.DialogNcaExtractionTitle]);
|
|
||||||
|
|
||||||
if (result == UserResult.Cancel)
|
|
||||||
{
|
|
||||||
cancellationToken.Cancel();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
using FileStream file = new(titleFilePath, FileMode.Open, FileAccess.Read);
|
using FileStream file = new(titleFilePath, FileMode.Open, FileAccess.Read);
|
||||||
|
|
||||||
Nca mainNca = null;
|
Nca mainNca = null;
|
||||||
@ -193,7 +187,7 @@ namespace Ryujinx.Ava.Common
|
|||||||
{
|
{
|
||||||
using var ncaFile = new UniqueRef<IFile>();
|
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());
|
Nca nca = new(_virtualFileSystem.KeySet, ncaFile.Get.AsStorage());
|
||||||
if (nca.Header.ContentType == NcaContentType.Program)
|
if (nca.Header.ContentType == NcaContentType.Program)
|
||||||
@ -221,13 +215,15 @@ namespace Ryujinx.Ava.Common
|
|||||||
|
|
||||||
Dispatcher.UIThread.InvokeAsync(async () =>
|
Dispatcher.UIThread.InvokeAsync(async () =>
|
||||||
{
|
{
|
||||||
|
waitingDialog.Close();
|
||||||
|
|
||||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogNcaExtractionMainNcaNotFoundErrorMessage]);
|
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogNcaExtractionMainNcaNotFoundErrorMessage]);
|
||||||
});
|
});
|
||||||
|
|
||||||
return;
|
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)
|
if (updatePatchNca != null)
|
||||||
{
|
{
|
||||||
patchNca = updatePatchNca;
|
patchNca = updatePatchNca;
|
||||||
@ -249,8 +245,8 @@ namespace Ryujinx.Ava.Common
|
|||||||
using var uniqueSourceFs = new UniqueRef<IFileSystem>(ncaFileSystem);
|
using var uniqueSourceFs = new UniqueRef<IFileSystem>(ncaFileSystem);
|
||||||
using var uniqueOutputFs = new UniqueRef<IFileSystem>(new LocalFileSystem(destination));
|
using var uniqueOutputFs = new UniqueRef<IFileSystem>(new LocalFileSystem(destination));
|
||||||
|
|
||||||
fsClient.Register(source.ToU8Span(), ref uniqueSourceFs.Ref());
|
fsClient.Register(source.ToU8Span(), ref uniqueSourceFs.Ref);
|
||||||
fsClient.Register(output.ToU8Span(), ref uniqueOutputFs.Ref());
|
fsClient.Register(output.ToU8Span(), ref uniqueOutputFs.Ref);
|
||||||
|
|
||||||
(Result? resultCode, bool canceled) = CopyDirectory(fsClient, $"{source}:/", $"{output}:/", cancellationToken.Token);
|
(Result? resultCode, bool canceled) = CopyDirectory(fsClient, $"{source}:/", $"{output}:/", cancellationToken.Token);
|
||||||
|
|
||||||
@ -262,11 +258,15 @@ namespace Ryujinx.Ava.Common
|
|||||||
|
|
||||||
Dispatcher.UIThread.InvokeAsync(async () =>
|
Dispatcher.UIThread.InvokeAsync(async () =>
|
||||||
{
|
{
|
||||||
|
waitingDialog.Close();
|
||||||
|
|
||||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogNcaExtractionCheckLogErrorMessage]);
|
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogNcaExtractionCheckLogErrorMessage]);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else if (resultCode.Value.IsSuccess())
|
else if (resultCode.Value.IsSuccess())
|
||||||
{
|
{
|
||||||
|
Dispatcher.UIThread.Post(waitingDialog.Close);
|
||||||
|
|
||||||
NotificationHelper.Show(
|
NotificationHelper.Show(
|
||||||
LocaleManager.Instance[LocaleKeys.DialogNcaExtractionTitle],
|
LocaleManager.Instance[LocaleKeys.DialogNcaExtractionTitle],
|
||||||
$"{titleName}\n\n{LocaleManager.Instance[LocaleKeys.DialogNcaExtractionSuccessMessage]}",
|
$"{titleName}\n\n{LocaleManager.Instance[LocaleKeys.DialogNcaExtractionSuccessMessage]}",
|
||||||
@ -283,6 +283,8 @@ namespace Ryujinx.Ava.Common
|
|||||||
|
|
||||||
Dispatcher.UIThread.InvokeAsync(async () =>
|
Dispatcher.UIThread.InvokeAsync(async () =>
|
||||||
{
|
{
|
||||||
|
waitingDialog.Close();
|
||||||
|
|
||||||
await ContentDialogHelper.CreateErrorDialog(ex.Message);
|
await ContentDialogHelper.CreateErrorDialog(ex.Message);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -130,7 +130,7 @@ namespace Ryujinx.Ava.Common.Locale
|
|||||||
{
|
{
|
||||||
var localeStrings = new Dictionary<LocaleKeys, string>();
|
var localeStrings = new Dictionary<LocaleKeys, string>();
|
||||||
string languageJson = EmbeddedResources.ReadAllText($"Ryujinx.Ava/Assets/Locales/{languageCode}.json");
|
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)
|
foreach (var item in strings)
|
||||||
{
|
{
|
||||||
|
@ -4,13 +4,14 @@ using FluentAvalonia.UI.Controls;
|
|||||||
using ICSharpCode.SharpZipLib.GZip;
|
using ICSharpCode.SharpZipLib.GZip;
|
||||||
using ICSharpCode.SharpZipLib.Tar;
|
using ICSharpCode.SharpZipLib.Tar;
|
||||||
using ICSharpCode.SharpZipLib.Zip;
|
using ICSharpCode.SharpZipLib.Zip;
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
using Ryujinx.Ava;
|
using Ryujinx.Ava;
|
||||||
using Ryujinx.Ava.Common.Locale;
|
using Ryujinx.Ava.Common.Locale;
|
||||||
using Ryujinx.Ava.UI.Helpers;
|
using Ryujinx.Ava.UI.Helpers;
|
||||||
using Ryujinx.Common;
|
using Ryujinx.Common;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
|
using Ryujinx.Common.Utilities;
|
||||||
using Ryujinx.Ui.Common.Helper;
|
using Ryujinx.Ui.Common.Helper;
|
||||||
|
using Ryujinx.Ui.Common.Models.Github;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
@ -21,6 +22,7 @@ using System.Net.Http;
|
|||||||
using System.Net.NetworkInformation;
|
using System.Net.NetworkInformation;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Runtime.Versioning;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@ -30,6 +32,7 @@ namespace Ryujinx.Modules
|
|||||||
internal static class Updater
|
internal static class Updater
|
||||||
{
|
{
|
||||||
private const string GitHubApiURL = "https://api.github.com";
|
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 HomeDir = AppDomain.CurrentDomain.BaseDirectory;
|
||||||
private static readonly string UpdateDir = Path.Combine(Path.GetTempPath(), "Ryujinx", "update");
|
private static readonly string UpdateDir = Path.Combine(Path.GetTempPath(), "Ryujinx", "update");
|
||||||
@ -57,7 +60,7 @@ namespace Ryujinx.Modules
|
|||||||
// Detect current platform
|
// Detect current platform
|
||||||
if (OperatingSystem.IsMacOS())
|
if (OperatingSystem.IsMacOS())
|
||||||
{
|
{
|
||||||
_platformExt = "osx_x64.zip";
|
_platformExt = "macos_universal.app.tar.gz";
|
||||||
}
|
}
|
||||||
else if (OperatingSystem.IsWindows())
|
else if (OperatingSystem.IsWindows())
|
||||||
{
|
{
|
||||||
@ -98,22 +101,16 @@ namespace Ryujinx.Modules
|
|||||||
|
|
||||||
string buildInfoURL = $"{GitHubApiURL}/repos/{ReleaseInformation.ReleaseChannelOwner}/{ReleaseInformation.ReleaseChannelRepo}/releases/latest";
|
string buildInfoURL = $"{GitHubApiURL}/repos/{ReleaseInformation.ReleaseChannelOwner}/{ReleaseInformation.ReleaseChannelRepo}/releases/latest";
|
||||||
string fetchedJson = await jsonClient.GetStringAsync(buildInfoURL);
|
string fetchedJson = await jsonClient.GetStringAsync(buildInfoURL);
|
||||||
JObject jsonRoot = JObject.Parse(fetchedJson);
|
var fetched = JsonHelper.Deserialize(fetchedJson, SerializerContext.GithubReleasesJsonResponse);
|
||||||
JToken assets = jsonRoot["assets"];
|
_buildVer = fetched.Name;
|
||||||
|
|
||||||
_buildVer = (string)jsonRoot["name"];
|
foreach (var asset in fetched.Assets)
|
||||||
|
|
||||||
foreach (JToken asset in assets)
|
|
||||||
{
|
{
|
||||||
string assetName = (string)asset["name"];
|
if (asset.Name.StartsWith("test-ava-ryujinx") && asset.Name.EndsWith(_platformExt))
|
||||||
string assetState = (string)asset["state"];
|
|
||||||
string downloadURL = (string)asset["browser_download_url"];
|
|
||||||
|
|
||||||
if (assetName.StartsWith("test-ava-ryujinx") && assetName.EndsWith(_platformExt))
|
|
||||||
{
|
{
|
||||||
_buildUrl = downloadURL;
|
_buildUrl = asset.BrowserDownloadUrl;
|
||||||
|
|
||||||
if (assetState != "uploaded")
|
if (asset.State != "uploaded")
|
||||||
{
|
{
|
||||||
if (showVersionUpToDate)
|
if (showVersionUpToDate)
|
||||||
{
|
{
|
||||||
@ -132,8 +129,8 @@ namespace Ryujinx.Modules
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If build not done, assume no new update are availaible.
|
// If build not done, assume no new update are available.
|
||||||
if (_buildUrl == null)
|
if (_buildUrl is null)
|
||||||
{
|
{
|
||||||
if (showVersionUpToDate)
|
if (showVersionUpToDate)
|
||||||
{
|
{
|
||||||
@ -240,13 +237,13 @@ namespace Ryujinx.Modules
|
|||||||
{
|
{
|
||||||
HttpClient result = new();
|
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");
|
result.DefaultRequestHeaders.Add("User-Agent", "Ryujinx-Updater/1.0.0");
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async void UpdateRyujinx(Window parent, string downloadUrl)
|
private static async void UpdateRyujinx(Window parent, string downloadUrl)
|
||||||
{
|
{
|
||||||
_updateSuccessful = false;
|
_updateSuccessful = false;
|
||||||
|
|
||||||
@ -286,24 +283,40 @@ namespace Ryujinx.Modules
|
|||||||
|
|
||||||
if (_updateSuccessful)
|
if (_updateSuccessful)
|
||||||
{
|
{
|
||||||
var shouldRestart = await ContentDialogHelper.CreateChoiceDialog(LocaleManager.Instance[LocaleKeys.RyujinxUpdater],
|
bool shouldRestart = true;
|
||||||
LocaleManager.Instance[LocaleKeys.DialogUpdaterCompleteMessage],
|
|
||||||
LocaleManager.Instance[LocaleKeys.DialogUpdaterRestartMessage]);
|
if (!OperatingSystem.IsMacOS())
|
||||||
|
{
|
||||||
|
shouldRestart = await ContentDialogHelper.CreateChoiceDialog(LocaleManager.Instance[LocaleKeys.RyujinxUpdater],
|
||||||
|
LocaleManager.Instance[LocaleKeys.DialogUpdaterCompleteMessage],
|
||||||
|
LocaleManager.Instance[LocaleKeys.DialogUpdaterRestartMessage]);
|
||||||
|
}
|
||||||
|
|
||||||
if (shouldRestart)
|
if (shouldRestart)
|
||||||
{
|
{
|
||||||
|
List<string> arguments = CommandLineState.Arguments.ToList();
|
||||||
string ryuName = Path.GetFileName(Environment.ProcessPath);
|
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);
|
Environment.Exit(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -383,6 +396,15 @@ namespace Ryujinx.Modules
|
|||||||
|
|
||||||
File.WriteAllBytes(updateFile, mergedFileBytes);
|
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
|
try
|
||||||
{
|
{
|
||||||
InstallUpdate(taskDialog, updateFile);
|
InstallUpdate(taskDialog, updateFile);
|
||||||
@ -408,9 +430,9 @@ namespace Ryujinx.Modules
|
|||||||
Logger.Warning?.Print(LogClass.Application, ex.Message);
|
Logger.Warning?.Print(LogClass.Application, ex.Message);
|
||||||
Logger.Warning?.Print(LogClass.Application, "Multi-Threaded update failed, falling back to single-threaded updater.");
|
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);
|
DoUpdateWithSingleThread(taskDialog, downloadUrl, updateFile);
|
||||||
@ -472,19 +494,74 @@ namespace Ryujinx.Modules
|
|||||||
worker.Start();
|
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 |
|
using Stream inStream = File.OpenRead(archivePath);
|
||||||
UnixFileMode.UserWrite |
|
using GZipInputStream gzipStream = new(inStream);
|
||||||
UnixFileMode.UserRead |
|
using TarInputStream tarStream = new(gzipStream, Encoding.ASCII);
|
||||||
UnixFileMode.GroupRead |
|
|
||||||
UnixFileMode.GroupWrite |
|
|
||||||
UnixFileMode.OtherRead |
|
|
||||||
UnixFileMode.OtherWrite;
|
|
||||||
|
|
||||||
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.SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterExtracting];
|
||||||
taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal);
|
taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal);
|
||||||
|
|
||||||
if (OperatingSystem.IsLinux())
|
await Task.Run(() =>
|
||||||
{
|
{
|
||||||
using Stream inStream = File.OpenRead(updateFile);
|
if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
|
||||||
using GZipInputStream gzipStream = new(inStream);
|
|
||||||
using TarInputStream tarStream = new(gzipStream, Encoding.ASCII);
|
|
||||||
|
|
||||||
await Task.Run(() =>
|
|
||||||
{
|
{
|
||||||
TarEntry tarEntry;
|
ExtractTarGzipFile(taskDialog, updateFile, UpdateDir);
|
||||||
while ((tarEntry = tarStream.GetNextEntry()) != null)
|
}
|
||||||
{
|
else if (OperatingSystem.IsWindows())
|
||||||
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(() =>
|
|
||||||
{
|
{
|
||||||
double count = 0;
|
ExtractZipFile(taskDialog, updateFile, UpdateDir);
|
||||||
foreach (ZipEntry zipEntry in zipFile)
|
}
|
||||||
{
|
else
|
||||||
count++;
|
{
|
||||||
if (zipEntry.IsDirectory) continue;
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
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);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete downloaded zip
|
// Delete downloaded zip
|
||||||
File.Delete(updateFile);
|
File.Delete(updateFile);
|
||||||
@ -570,40 +595,42 @@ namespace Ryujinx.Modules
|
|||||||
taskDialog.SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterRenaming];
|
taskDialog.SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterRenaming];
|
||||||
taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal);
|
taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal);
|
||||||
|
|
||||||
// Replace old files
|
// NOTE: On macOS, replacement is delayed to the restart phase.
|
||||||
await Task.Run(() =>
|
if (!OperatingSystem.IsMacOS())
|
||||||
{
|
{
|
||||||
double count = 0;
|
// Replace old files
|
||||||
foreach (string file in allFiles)
|
await Task.Run(() =>
|
||||||
{
|
{
|
||||||
count++;
|
double count = 0;
|
||||||
try
|
foreach (string file in allFiles)
|
||||||
{
|
{
|
||||||
File.Move(file, file + ".ryuold");
|
count++;
|
||||||
|
try
|
||||||
Dispatcher.UIThread.Post(() =>
|
|
||||||
{
|
{
|
||||||
taskDialog.SetProgressBarState(GetPercentage(count, allFiles.Count), TaskDialogProgressState.Normal);
|
File.Move(file, file + ".ryuold");
|
||||||
});
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
Logger.Warning?.Print(LogClass.Application, LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.UpdaterRenameFailed, file));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Dispatcher.UIThread.Post(() =>
|
Dispatcher.UIThread.Post(() =>
|
||||||
{
|
{
|
||||||
taskDialog.SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterAddingFiles];
|
taskDialog.SetProgressBarState(GetPercentage(count, allFiles.Count), TaskDialogProgressState.Normal);
|
||||||
taskDialog.SetProgressBarState(0, 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);
|
||||||
});
|
}
|
||||||
|
|
||||||
Directory.Delete(UpdateDir, true);
|
|
||||||
|
|
||||||
SetFileExecutable(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Ryujinx"));
|
|
||||||
|
|
||||||
_updateSuccessful = true;
|
_updateSuccessful = true;
|
||||||
|
|
||||||
@ -613,7 +640,7 @@ namespace Ryujinx.Modules
|
|||||||
public static bool CanUpdate(bool showWarnings)
|
public static bool CanUpdate(bool showWarnings)
|
||||||
{
|
{
|
||||||
#if !DISABLE_UPDATER
|
#if !DISABLE_UPDATER
|
||||||
if (RuntimeInformation.OSArchitecture != Architecture.X64)
|
if (RuntimeInformation.OSArchitecture != Architecture.X64 && !OperatingSystem.IsMacOS())
|
||||||
{
|
{
|
||||||
if (showWarnings)
|
if (showWarnings)
|
||||||
{
|
{
|
||||||
@ -686,7 +713,7 @@ namespace Ryujinx.Modules
|
|||||||
#endif
|
#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()
|
private static IEnumerable<string> EnumerateFilesToDelete()
|
||||||
{
|
{
|
||||||
var files = Directory.EnumerateFiles(HomeDir); // All files directly in base dir.
|
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)
|
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>
|
<PropertyGroup>
|
||||||
<TargetFramework>net7.0</TargetFramework>
|
<TargetFramework>net7.0</TargetFramework>
|
||||||
<RuntimeIdentifiers>win10-x64;osx-x64;linux-x64</RuntimeIdentifiers>
|
<RuntimeIdentifiers>win10-x64;osx-x64;linux-x64</RuntimeIdentifiers>
|
||||||
@ -6,11 +6,16 @@
|
|||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
<Version>1.0.0-dirty</Version>
|
<Version>1.0.0-dirty</Version>
|
||||||
<DefineConstants Condition=" '$(ExtraDefineConstants)' != '' ">$(DefineConstants);$(ExtraDefineConstants)</DefineConstants>
|
<DefineConstants Condition=" '$(ExtraDefineConstants)' != '' ">$(DefineConstants);$(ExtraDefineConstants)</DefineConstants>
|
||||||
|
<SigningCertificate Condition=" '$(SigningCertificate)' == '' ">-</SigningCertificate>
|
||||||
<RootNamespace>Ryujinx.Ava</RootNamespace>
|
<RootNamespace>Ryujinx.Ava</RootNamespace>
|
||||||
<ApplicationIcon>Ryujinx.ico</ApplicationIcon>
|
<ApplicationIcon>Ryujinx.ico</ApplicationIcon>
|
||||||
<TieredPGO>true</TieredPGO>
|
<TieredPGO>true</TieredPGO>
|
||||||
</PropertyGroup>
|
</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)' != ''">
|
<PropertyGroup Condition="'$(RuntimeIdentifier)' != ''">
|
||||||
<PublishSingleFile>true</PublishSingleFile>
|
<PublishSingleFile>true</PublishSingleFile>
|
||||||
<PublishTrimmed>true</PublishTrimmed>
|
<PublishTrimmed>true</PublishTrimmed>
|
||||||
|
@ -53,6 +53,8 @@ namespace Ryujinx.Ava.UI.Applet
|
|||||||
|
|
||||||
bool opened = false;
|
bool opened = false;
|
||||||
|
|
||||||
|
_parent.Activate();
|
||||||
|
|
||||||
UserResult response = await ContentDialogHelper.ShowDeferredContentDialog(_parent,
|
UserResult response = await ContentDialogHelper.ShowDeferredContentDialog(_parent,
|
||||||
title,
|
title,
|
||||||
message,
|
message,
|
||||||
|
@ -48,6 +48,7 @@
|
|||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
|
Focusable="True"
|
||||||
KeyUp="Message_KeyUp"
|
KeyUp="Message_KeyUp"
|
||||||
Text="{Binding Message}"
|
Text="{Binding Message}"
|
||||||
TextInput="Message_TextInput"
|
TextInput="Message_TextInput"
|
||||||
@ -61,4 +62,4 @@
|
|||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
TextWrapping="Wrap" />
|
TextWrapping="Wrap" />
|
||||||
</Grid>
|
</Grid>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
|
@ -23,10 +23,11 @@ namespace Ryujinx.Ava.UI.Controls
|
|||||||
|
|
||||||
private ContentDialog _host;
|
private ContentDialog _host;
|
||||||
|
|
||||||
public SwkbdAppletDialog(string mainText, string secondaryText, string placeholder)
|
public SwkbdAppletDialog(string mainText, string secondaryText, string placeholder, string message)
|
||||||
{
|
{
|
||||||
MainText = mainText;
|
MainText = mainText;
|
||||||
SecondaryText = secondaryText;
|
SecondaryText = secondaryText;
|
||||||
|
Message = message ?? "";
|
||||||
DataContext = this;
|
DataContext = this;
|
||||||
_placeholder = placeholder;
|
_placeholder = placeholder;
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
@ -44,6 +45,13 @@ namespace Ryujinx.Ava.UI.Controls
|
|||||||
InitializeComponent();
|
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 Message { get; set; } = "";
|
||||||
public string MainText { get; set; } = "";
|
public string MainText { get; set; } = "";
|
||||||
public string SecondaryText { get; set; } = "";
|
public string SecondaryText { get; set; } = "";
|
||||||
@ -54,31 +62,10 @@ namespace Ryujinx.Ava.UI.Controls
|
|||||||
|
|
||||||
UserResult result = UserResult.Cancel;
|
UserResult result = UserResult.Cancel;
|
||||||
|
|
||||||
SwkbdAppletDialog content = new SwkbdAppletDialog(args.HeaderText, args.SubtitleText, args.GuideText)
|
SwkbdAppletDialog content = new SwkbdAppletDialog(args.HeaderText, args.SubtitleText, args.GuideText, args.InitialText);
|
||||||
{
|
|
||||||
Message = args.InitialText ?? ""
|
|
||||||
};
|
|
||||||
|
|
||||||
string input = string.Empty;
|
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.SetInputLengthValidation(args.StringLengthMin, args.StringLengthMax);
|
||||||
|
|
||||||
content._host = contentDialog;
|
content._host = contentDialog;
|
||||||
@ -99,25 +86,7 @@ namespace Ryujinx.Ava.UI.Controls
|
|||||||
};
|
};
|
||||||
contentDialog.Closed += handler;
|
contentDialog.Closed += handler;
|
||||||
|
|
||||||
overlay.Opened += OverlayOnActivated;
|
await ContentDialogHelper.ShowAsync(contentDialog);
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
return (result, input);
|
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.FileSystem;
|
||||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using UserProfile = Ryujinx.Ava.UI.Models.UserProfile;
|
using UserProfile = Ryujinx.Ava.UI.Models.UserProfile;
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.Controls
|
namespace Ryujinx.Ava.UI.Controls
|
||||||
@ -121,7 +121,7 @@ namespace Ryujinx.Ava.UI.Controls
|
|||||||
|
|
||||||
using var saveDataIterator = new UniqueRef<SaveDataIterator>();
|
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];
|
Span<SaveDataInfo> saveDataInfo = stackalloc SaveDataInfo[10];
|
||||||
|
|
||||||
|
@ -1,15 +1,26 @@
|
|||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Ryujinx.Ava.UI.Windows;
|
using Ryujinx.Ava.UI.Windows;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.Controls
|
namespace Ryujinx.Ava.UI.Controls
|
||||||
{
|
{
|
||||||
public partial class UpdateWaitWindow : StyleableWindow
|
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()
|
public UpdateWaitWindow(string primaryText, string secondaryText) : this()
|
||||||
{
|
{
|
||||||
PrimaryText.Text = primaryText;
|
PrimaryText.Text = primaryText;
|
||||||
SecondaryText.Text = secondaryText;
|
SecondaryText.Text = secondaryText;
|
||||||
WindowStartupLocation = WindowStartupLocation.CenterOwner;
|
WindowStartupLocation = WindowStartupLocation.CenterOwner;
|
||||||
|
SystemDecorations = SystemDecorations.BorderOnly;
|
||||||
|
ShowInTaskbar = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public UpdateWaitWindow()
|
public UpdateWaitWindow()
|
||||||
|
@ -27,7 +27,6 @@ namespace Ryujinx.Ava.UI.Helpers
|
|||||||
string closeButton,
|
string closeButton,
|
||||||
UserResult primaryButtonResult = UserResult.Ok,
|
UserResult primaryButtonResult = UserResult.Ok,
|
||||||
ManualResetEvent deferResetEvent = null,
|
ManualResetEvent deferResetEvent = null,
|
||||||
Func<Window, Task> doWhileDeferred = null,
|
|
||||||
TypedEventHandler<ContentDialog, ContentDialogButtonClickEventArgs> deferCloseAction = null)
|
TypedEventHandler<ContentDialog, ContentDialogButtonClickEventArgs> deferCloseAction = null)
|
||||||
{
|
{
|
||||||
UserResult result = UserResult.None;
|
UserResult result = UserResult.None;
|
||||||
@ -78,12 +77,11 @@ namespace Ryujinx.Ava.UI.Helpers
|
|||||||
int iconSymbol,
|
int iconSymbol,
|
||||||
UserResult primaryButtonResult = UserResult.Ok,
|
UserResult primaryButtonResult = UserResult.Ok,
|
||||||
ManualResetEvent deferResetEvent = null,
|
ManualResetEvent deferResetEvent = null,
|
||||||
Func<Window, Task> doWhileDeferred = null,
|
|
||||||
TypedEventHandler<ContentDialog, ContentDialogButtonClickEventArgs> deferCloseAction = null)
|
TypedEventHandler<ContentDialog, ContentDialogButtonClickEventArgs> deferCloseAction = null)
|
||||||
{
|
{
|
||||||
Grid content = CreateTextDialogContent(primaryText, secondaryText, iconSymbol);
|
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(
|
public async static Task<UserResult> ShowDeferredContentDialog(
|
||||||
@ -111,7 +109,6 @@ namespace Ryujinx.Ava.UI.Helpers
|
|||||||
iconSymbol,
|
iconSymbol,
|
||||||
primaryButton == LocaleManager.Instance[LocaleKeys.InputDialogYes] ? UserResult.Yes : UserResult.Ok,
|
primaryButton == LocaleManager.Instance[LocaleKeys.InputDialogYes] ? UserResult.Yes : UserResult.Ok,
|
||||||
deferResetEvent,
|
deferResetEvent,
|
||||||
doWhileDeferred,
|
|
||||||
DeferClose);
|
DeferClose);
|
||||||
|
|
||||||
async void DeferClose(ContentDialog sender, ContentDialogButtonClickEventArgs args)
|
async void DeferClose(ContentDialog sender, ContentDialogButtonClickEventArgs args)
|
||||||
@ -236,11 +233,6 @@ namespace Ryujinx.Ava.UI.Helpers
|
|||||||
primaryButtonResult);
|
primaryButtonResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static UpdateWaitWindow CreateWaitingDialog(string mainText, string secondaryText)
|
|
||||||
{
|
|
||||||
return new(mainText, secondaryText);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static async Task CreateUpdaterInfoDialog(string primary, string secondaryText)
|
internal static async Task CreateUpdaterInfoDialog(string primary, string secondaryText)
|
||||||
{
|
{
|
||||||
await ShowTextDialog(
|
await ShowTextDialog(
|
||||||
@ -319,28 +311,6 @@ namespace Ryujinx.Ava.UI.Helpers
|
|||||||
LocaleManager.Instance[LocaleKeys.DialogExitSubMessage]);
|
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)
|
public static async Task<ContentDialogResult> ShowAsync(ContentDialog contentDialog)
|
||||||
{
|
{
|
||||||
ContentDialogResult result;
|
ContentDialogResult result;
|
||||||
|
@ -6,8 +6,8 @@ namespace Ryujinx.Ava.UI.Helpers
|
|||||||
{
|
{
|
||||||
using AvaLogger = Avalonia.Logging.Logger;
|
using AvaLogger = Avalonia.Logging.Logger;
|
||||||
using AvaLogLevel = Avalonia.Logging.LogEventLevel;
|
using AvaLogLevel = Avalonia.Logging.LogEventLevel;
|
||||||
using RyuLogger = Ryujinx.Common.Logging.Logger;
|
|
||||||
using RyuLogClass = Ryujinx.Common.Logging.LogClass;
|
using RyuLogClass = Ryujinx.Common.Logging.LogClass;
|
||||||
|
using RyuLogger = Ryujinx.Common.Logging.Logger;
|
||||||
|
|
||||||
internal class LoggerAdapter : Avalonia.Logging.ILogSink
|
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 Ryujinx.Ava.UI.ViewModels;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.Models
|
namespace Ryujinx.Ava.UI.Models
|
||||||
{
|
{
|
||||||
@ -21,6 +22,8 @@ namespace Ryujinx.Ava.UI.Models
|
|||||||
public string ContainerPath { get; }
|
public string ContainerPath { get; }
|
||||||
public string FullPath { get; }
|
public string FullPath { get; }
|
||||||
|
|
||||||
|
public string FileName => Path.GetFileName(ContainerPath);
|
||||||
|
|
||||||
public DownloadableContentModel(string titleId, string containerPath, string fullPath, bool enabled)
|
public DownloadableContentModel(string titleId, string containerPath, string fullPath, bool enabled)
|
||||||
{
|
{
|
||||||
TitleId = titleId;
|
TitleId = titleId;
|
||||||
|
@ -2,9 +2,9 @@ using Avalonia;
|
|||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Input;
|
using Avalonia.Input;
|
||||||
using Avalonia.Platform;
|
using Avalonia.Platform;
|
||||||
using Ryujinx.Ava.UI.Helpers;
|
|
||||||
using Ryujinx.Common.Configuration;
|
using Ryujinx.Common.Configuration;
|
||||||
using Ryujinx.Ui.Common.Configuration;
|
using Ryujinx.Ui.Common.Configuration;
|
||||||
|
using Ryujinx.Ui.Common.Helper;
|
||||||
using SPB.Graphics;
|
using SPB.Graphics;
|
||||||
using SPB.Platform;
|
using SPB.Platform;
|
||||||
using SPB.Platform.GLX;
|
using SPB.Platform.GLX;
|
||||||
@ -30,6 +30,7 @@ namespace Ryujinx.Ava.UI.Renderer
|
|||||||
protected IntPtr NsView { get; set; }
|
protected IntPtr NsView { get; set; }
|
||||||
protected IntPtr MetalLayer { get; set; }
|
protected IntPtr MetalLayer { get; set; }
|
||||||
|
|
||||||
|
public delegate void UpdateBoundsCallbackDelegate(Rect rect);
|
||||||
private UpdateBoundsCallbackDelegate _updateBoundsCallback;
|
private UpdateBoundsCallbackDelegate _updateBoundsCallback;
|
||||||
|
|
||||||
public event EventHandler<IntPtr> WindowCreated;
|
public event EventHandler<IntPtr> WindowCreated;
|
||||||
@ -237,8 +238,29 @@ namespace Ryujinx.Ava.UI.Renderer
|
|||||||
[SupportedOSPlatform("macos")]
|
[SupportedOSPlatform("macos")]
|
||||||
IPlatformHandle CreateMacOS()
|
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;
|
NsView = nsView;
|
||||||
|
|
||||||
return new PlatformHandle(nsView, "NSView");
|
return new PlatformHandle(nsView, "NSView");
|
||||||
@ -260,7 +282,7 @@ namespace Ryujinx.Ava.UI.Renderer
|
|||||||
[SupportedOSPlatform("macos")]
|
[SupportedOSPlatform("macos")]
|
||||||
void DestroyMacOS()
|
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/");
|
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
|
catch
|
||||||
{
|
{
|
||||||
|
@ -3,13 +3,12 @@ using Avalonia.Collections;
|
|||||||
using Avalonia.Media.Imaging;
|
using Avalonia.Media.Imaging;
|
||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
using Ryujinx.Ava.Common.Locale;
|
using Ryujinx.Ava.Common.Locale;
|
||||||
using Ryujinx.Ava.UI.Controls;
|
|
||||||
using Ryujinx.Ava.UI.Helpers;
|
using Ryujinx.Ava.UI.Helpers;
|
||||||
using Ryujinx.Ava.UI.Models;
|
|
||||||
using Ryujinx.Ava.UI.Windows;
|
using Ryujinx.Ava.UI.Windows;
|
||||||
using Ryujinx.Common;
|
using Ryujinx.Common;
|
||||||
using Ryujinx.Common.Configuration;
|
using Ryujinx.Common.Configuration;
|
||||||
using Ryujinx.Common.Utilities;
|
using Ryujinx.Common.Utilities;
|
||||||
|
using Ryujinx.Ui.Common.Models.Amiibo;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
@ -17,8 +16,8 @@ using System.IO;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.Json;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using AmiiboJsonSerializerContext = Ryujinx.Ui.Common.Models.Amiibo.AmiiboJsonSerializerContext;
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.ViewModels
|
namespace Ryujinx.Ava.UI.ViewModels
|
||||||
{
|
{
|
||||||
@ -31,10 +30,10 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
private readonly byte[] _amiiboLogoBytes;
|
private readonly byte[] _amiiboLogoBytes;
|
||||||
private readonly HttpClient _httpClient;
|
private readonly HttpClient _httpClient;
|
||||||
private readonly StyleableWindow _owner;
|
private readonly StyleableWindow _owner;
|
||||||
|
|
||||||
private Bitmap _amiiboImage;
|
private Bitmap _amiiboImage;
|
||||||
private List<Amiibo.AmiiboApi> _amiiboList;
|
private List<AmiiboApi> _amiiboList;
|
||||||
private AvaloniaList<Amiibo.AmiiboApi> _amiibos;
|
private AvaloniaList<AmiiboApi> _amiibos;
|
||||||
private ObservableCollection<string> _amiiboSeries;
|
private ObservableCollection<string> _amiiboSeries;
|
||||||
|
|
||||||
private int _amiiboSelectedIndex;
|
private int _amiiboSelectedIndex;
|
||||||
@ -43,6 +42,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
private bool _showAllAmiibo;
|
private bool _showAllAmiibo;
|
||||||
private bool _useRandomUuid;
|
private bool _useRandomUuid;
|
||||||
private string _usage;
|
private string _usage;
|
||||||
|
|
||||||
|
private static readonly AmiiboJsonSerializerContext SerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
||||||
|
|
||||||
public AmiiboWindowViewModel(StyleableWindow owner, string lastScannedAmiiboId, string titleId)
|
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"));
|
Directory.CreateDirectory(Path.Join(AppDataManager.BaseDirPath, "system", "amiibo"));
|
||||||
|
|
||||||
_amiiboJsonPath = Path.Join(AppDataManager.BaseDirPath, "system", "amiibo", "Amiibo.json");
|
_amiiboJsonPath = Path.Join(AppDataManager.BaseDirPath, "system", "amiibo", "Amiibo.json");
|
||||||
_amiiboList = new List<Amiibo.AmiiboApi>();
|
_amiiboList = new List<AmiiboApi>();
|
||||||
_amiiboSeries = new ObservableCollection<string>();
|
_amiiboSeries = new ObservableCollection<string>();
|
||||||
_amiibos = new AvaloniaList<Amiibo.AmiiboApi>();
|
_amiibos = new AvaloniaList<AmiiboApi>();
|
||||||
|
|
||||||
_amiiboLogoBytes = EmbeddedResources.Read("Ryujinx.Ui.Common/Resources/Logo_Amiibo.png");
|
_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;
|
get => _amiibos;
|
||||||
set
|
set
|
||||||
@ -189,9 +190,9 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
if (File.Exists(_amiiboJsonPath))
|
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();
|
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();
|
_amiiboList = _amiiboList.OrderBy(amiibo => amiibo.AmiiboSeries).ToList();
|
||||||
|
|
||||||
ParseAmiiboData();
|
ParseAmiiboData();
|
||||||
@ -225,7 +226,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
{
|
{
|
||||||
if (!ShowAllAmiibo)
|
if (!ShowAllAmiibo)
|
||||||
{
|
{
|
||||||
foreach (Amiibo.AmiiboApiGamesSwitch game in _amiiboList[i].GamesSwitch)
|
foreach (AmiiboApiGamesSwitch game in _amiiboList[i].GamesSwitch)
|
||||||
{
|
{
|
||||||
if (game != null)
|
if (game != null)
|
||||||
{
|
{
|
||||||
@ -257,7 +258,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
private void SelectLastScannedAmiibo()
|
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);
|
SeriesSelectedIndex = AmiiboSeries.IndexOf(scanned.AmiiboSeries);
|
||||||
AmiiboSelectedIndex = AmiiboList.IndexOf(scanned);
|
AmiiboSelectedIndex = AmiiboList.IndexOf(scanned);
|
||||||
@ -272,7 +273,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Amiibo.AmiiboApi> amiiboSortedList = _amiiboList
|
List<AmiiboApi> amiiboSortedList = _amiiboList
|
||||||
.Where(amiibo => amiibo.AmiiboSeries == _amiiboSeries[SeriesSelectedIndex])
|
.Where(amiibo => amiibo.AmiiboSeries == _amiiboSeries[SeriesSelectedIndex])
|
||||||
.OrderBy(amiibo => amiibo.Name).ToList();
|
.OrderBy(amiibo => amiibo.Name).ToList();
|
||||||
|
|
||||||
@ -282,7 +283,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
{
|
{
|
||||||
if (!_showAllAmiibo)
|
if (!_showAllAmiibo)
|
||||||
{
|
{
|
||||||
foreach (Amiibo.AmiiboApiGamesSwitch game in amiiboSortedList[i].GamesSwitch)
|
foreach (AmiiboApiGamesSwitch game in amiiboSortedList[i].GamesSwitch)
|
||||||
{
|
{
|
||||||
if (game != null)
|
if (game != null)
|
||||||
{
|
{
|
||||||
@ -316,7 +317,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Amiibo.AmiiboApi selected = _amiibos[_amiiboSelectedIndex];
|
AmiiboApi selected = _amiibos[_amiiboSelectedIndex];
|
||||||
|
|
||||||
string imageUrl = _amiiboList.FirstOrDefault(amiibo => amiibo.Equals(selected)).Image;
|
string imageUrl = _amiiboList.FirstOrDefault(amiibo => amiibo.Equals(selected)).Image;
|
||||||
|
|
||||||
@ -328,11 +329,11 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
{
|
{
|
||||||
bool writable = false;
|
bool writable = false;
|
||||||
|
|
||||||
foreach (Amiibo.AmiiboApiGamesSwitch item in _amiiboList[i].GamesSwitch)
|
foreach (AmiiboApiGamesSwitch item in _amiiboList[i].GamesSwitch)
|
||||||
{
|
{
|
||||||
if (item.GameId.Contains(TitleId))
|
if (item.GameId.Contains(TitleId))
|
||||||
{
|
{
|
||||||
foreach (Amiibo.AmiiboApiUsage usageItem in item.AmiiboUsage)
|
foreach (AmiiboApiUsage usageItem in item.AmiiboUsage)
|
||||||
{
|
{
|
||||||
usageString += Environment.NewLine +
|
usageString += Environment.NewLine +
|
||||||
$"- {usageItem.Usage.Replace("/", Environment.NewLine + "-")}";
|
$"- {usageItem.Usage.Replace("/", Environment.NewLine + "-")}";
|
||||||
|
@ -246,7 +246,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
{
|
{
|
||||||
using var file = new UniqueRef<IFile>();
|
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();
|
.ThrowIfFailure();
|
||||||
|
|
||||||
using (MemoryStream stream = new())
|
using (MemoryStream stream = new())
|
||||||
|
@ -3,11 +3,8 @@ using Avalonia.Controls;
|
|||||||
using Avalonia.Controls.ApplicationLifetimes;
|
using Avalonia.Controls.ApplicationLifetimes;
|
||||||
using Avalonia.Svg.Skia;
|
using Avalonia.Svg.Skia;
|
||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
using LibHac.Bcat;
|
|
||||||
using LibHac.Tools.Fs;
|
|
||||||
using Ryujinx.Ava.Common.Locale;
|
using Ryujinx.Ava.Common.Locale;
|
||||||
using Ryujinx.Ava.Input;
|
using Ryujinx.Ava.Input;
|
||||||
using Ryujinx.Ava.UI.Controls;
|
|
||||||
using Ryujinx.Ava.UI.Helpers;
|
using Ryujinx.Ava.UI.Helpers;
|
||||||
using Ryujinx.Ava.UI.Models;
|
using Ryujinx.Ava.UI.Models;
|
||||||
using Ryujinx.Ava.UI.Windows;
|
using Ryujinx.Ava.UI.Windows;
|
||||||
@ -54,6 +51,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
private bool _isLoaded;
|
private bool _isLoaded;
|
||||||
private readonly UserControl _owner;
|
private readonly UserControl _owner;
|
||||||
|
|
||||||
|
private static readonly InputConfigJsonSerializerContext SerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
||||||
|
|
||||||
public IGamepadDriver AvaloniaKeyboardDriver { get; }
|
public IGamepadDriver AvaloniaKeyboardDriver { get; }
|
||||||
public IGamepad SelectedGamepad { get; private set; }
|
public IGamepad SelectedGamepad { get; private set; }
|
||||||
|
|
||||||
@ -709,10 +708,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using (Stream stream = File.OpenRead(path))
|
config = JsonHelper.DeserializeFromFile(path, SerializerContext.InputConfig);
|
||||||
{
|
|
||||||
config = JsonHelper.Deserialize<InputConfig>(stream);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (JsonException) { }
|
catch (JsonException) { }
|
||||||
catch (InvalidOperationException)
|
catch (InvalidOperationException)
|
||||||
@ -778,7 +774,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
config.ControllerType = Controllers[_controller].Type;
|
config.ControllerType = Controllers[_controller].Type;
|
||||||
|
|
||||||
string jsonString = JsonHelper.Serialize(config, true);
|
string jsonString = JsonHelper.Serialize(config, SerializerContext.InputConfig);
|
||||||
|
|
||||||
await File.WriteAllTextAsync(path, jsonString);
|
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 DynamicData.Binding;
|
||||||
using LibHac.Common;
|
using LibHac.Common;
|
||||||
using LibHac.Fs;
|
using LibHac.Fs;
|
||||||
using LibHac.FsSystem;
|
using LibHac.Tools.FsSystem.NcaUtils;
|
||||||
using LibHac.Tools.Fs;
|
|
||||||
using Ryujinx.Ava.Common;
|
using Ryujinx.Ava.Common;
|
||||||
using Ryujinx.Ava.Common.Locale;
|
using Ryujinx.Ava.Common.Locale;
|
||||||
using Ryujinx.Ava.Input;
|
using Ryujinx.Ava.Input;
|
||||||
@ -973,7 +972,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
LocaleManager.Instance[LocaleKeys.InputDialogNo],
|
LocaleManager.Instance[LocaleKeys.InputDialogNo],
|
||||||
LocaleManager.Instance[LocaleKeys.RyujinxConfirm]);
|
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)
|
if (result == UserResult.Yes)
|
||||||
{
|
{
|
||||||
@ -1209,10 +1208,10 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
public void SetUIProgressHandlers(Switch emulationContext)
|
public void SetUIProgressHandlers(Switch emulationContext)
|
||||||
{
|
{
|
||||||
if (emulationContext.Application.DiskCacheLoadState != null)
|
if (emulationContext.Processes.ActiveApplication.DiskCacheLoadState != null)
|
||||||
{
|
{
|
||||||
emulationContext.Application.DiskCacheLoadState.StateChanged -= ProgressHandler;
|
emulationContext.Processes.ActiveApplication.DiskCacheLoadState.StateChanged -= ProgressHandler;
|
||||||
emulationContext.Application.DiskCacheLoadState.StateChanged += ProgressHandler;
|
emulationContext.Processes.ActiveApplication.DiskCacheLoadState.StateChanged += ProgressHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
emulationContext.Gpu.ShaderCacheStateChanged -= 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()
|
public async void ManageProfiles()
|
||||||
{
|
{
|
||||||
await NavigationDialogHost.Show(AccountManager, ContentManager, VirtualFileSystem, LibHacHorizonManager.RyujinxClient);
|
await NavigationDialogHost.Show(AccountManager, ContentManager, VirtualFileSystem, LibHacHorizonManager.RyujinxClient);
|
||||||
@ -1565,7 +1581,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
{
|
{
|
||||||
if (SelectedApplication != null)
|
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))
|
if (string.IsNullOrWhiteSpace(titleName))
|
||||||
{
|
{
|
||||||
LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, AppHost.Device.Application.TitleName);
|
LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, AppHost.Device.Processes.ActiveApplication.Name);
|
||||||
TitleName = AppHost.Device.Application.TitleName;
|
TitleName = AppHost.Device.Processes.ActiveApplication.Name;
|
||||||
}
|
}
|
||||||
|
|
||||||
SwitchToRenderer(startFullscreen);
|
SwitchToRenderer(startFullscreen);
|
||||||
|
@ -22,6 +22,8 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Net.NetworkInformation;
|
||||||
using TimeZone = Ryujinx.Ava.UI.Models.TimeZone;
|
using TimeZone = Ryujinx.Ava.UI.Models.TimeZone;
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.ViewModels
|
namespace Ryujinx.Ava.UI.ViewModels
|
||||||
@ -34,6 +36,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
private readonly List<string> _validTzRegions;
|
private readonly List<string> _validTzRegions;
|
||||||
|
|
||||||
|
private readonly Dictionary<string, string> _networkInterfaces;
|
||||||
|
|
||||||
private float _customResolutionScale;
|
private float _customResolutionScale;
|
||||||
private int _resolutionScale;
|
private int _resolutionScale;
|
||||||
private int _graphicsBackendMultithreadingIndex;
|
private int _graphicsBackendMultithreadingIndex;
|
||||||
@ -44,9 +48,12 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
private KeyboardHotkeys _keyboardHotkeys;
|
private KeyboardHotkeys _keyboardHotkeys;
|
||||||
private int _graphicsBackendIndex;
|
private int _graphicsBackendIndex;
|
||||||
private string _customThemePath;
|
private string _customThemePath;
|
||||||
|
private int _scalingFilter;
|
||||||
|
private int _scalingFilterLevel;
|
||||||
|
|
||||||
public event Action CloseWindow;
|
public event Action CloseWindow;
|
||||||
public event Action SaveSettingsEvent;
|
public event Action SaveSettingsEvent;
|
||||||
|
private int _networkInterfaceIndex;
|
||||||
|
|
||||||
public int ResolutionScale
|
public int ResolutionScale
|
||||||
{
|
{
|
||||||
@ -59,6 +66,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
OnPropertyChanged(nameof(IsCustomResolutionScaleActive));
|
OnPropertyChanged(nameof(IsCustomResolutionScaleActive));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public int GraphicsBackendMultithreadingIndex
|
public int GraphicsBackendMultithreadingIndex
|
||||||
{
|
{
|
||||||
get => _graphicsBackendMultithreadingIndex;
|
get => _graphicsBackendMultithreadingIndex;
|
||||||
@ -106,6 +114,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
public bool IsOpenGLAvailable => !OperatingSystem.IsMacOS();
|
public bool IsOpenGLAvailable => !OperatingSystem.IsMacOS();
|
||||||
|
|
||||||
|
public bool IsHypervisorAvailable => OperatingSystem.IsMacOS() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64;
|
||||||
|
|
||||||
public bool DirectoryChanged
|
public bool DirectoryChanged
|
||||||
{
|
{
|
||||||
get => _directoryChanged;
|
get => _directoryChanged;
|
||||||
@ -117,10 +127,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsMacOS
|
public bool IsMacOS => OperatingSystem.IsMacOS();
|
||||||
{
|
|
||||||
get => OperatingSystem.IsMacOS();
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool EnableDiscordIntegration { get; set; }
|
public bool EnableDiscordIntegration { get; set; }
|
||||||
public bool CheckUpdatesOnStart { get; set; }
|
public bool CheckUpdatesOnStart { get; set; }
|
||||||
@ -152,7 +159,10 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
public bool IsSDL2Enabled { get; set; }
|
public bool IsSDL2Enabled { get; set; }
|
||||||
public bool EnableCustomTheme { get; set; }
|
public bool EnableCustomTheme { get; set; }
|
||||||
public bool IsCustomResolutionScaleActive => _resolutionScale == 4;
|
public bool IsCustomResolutionScaleActive => _resolutionScale == 4;
|
||||||
|
public bool IsScalingFilterActive => _scalingFilter == (int)Ryujinx.Common.Configuration.ScalingFilter.Fsr;
|
||||||
|
|
||||||
public bool IsVulkanSelected => GraphicsBackendIndex == 0;
|
public bool IsVulkanSelected => GraphicsBackendIndex == 0;
|
||||||
|
public bool UseHypervisor { get; set; }
|
||||||
|
|
||||||
public string TimeZone { get; set; }
|
public string TimeZone { get; set; }
|
||||||
public string ShaderDumpPath { get; set; }
|
public string ShaderDumpPath { get; set; }
|
||||||
@ -177,6 +187,18 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
public int AudioBackend { get; set; }
|
public int AudioBackend { get; set; }
|
||||||
public int MaxAnisotropy { get; set; }
|
public int MaxAnisotropy { get; set; }
|
||||||
public int AspectRatio { 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 OpenglDebugLevel { get; set; }
|
||||||
public int MemoryMode { get; set; }
|
public int MemoryMode { get; set; }
|
||||||
public int BaseStyleIndex { get; set; }
|
public int BaseStyleIndex { get; set; }
|
||||||
@ -190,6 +212,16 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
OnPropertyChanged(nameof(IsVulkanSelected));
|
OnPropertyChanged(nameof(IsVulkanSelected));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public int ScalingFilter
|
||||||
|
{
|
||||||
|
get => _scalingFilter;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_scalingFilter = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
OnPropertyChanged(nameof(IsScalingFilterActive));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public int PreferredGpuIndex { get; set; }
|
public int PreferredGpuIndex { get; set; }
|
||||||
|
|
||||||
@ -208,10 +240,15 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
public DateTimeOffset DateOffset { get; set; }
|
public DateTimeOffset DateOffset { get; set; }
|
||||||
public TimeSpan TimeOffset { 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 AvaloniaList<string> GameDirectories { get; set; }
|
||||||
public ObservableCollection<ComboBoxItem> AvailableGpus { get; set; }
|
public ObservableCollection<ComboBoxItem> AvailableGpus { get; set; }
|
||||||
|
|
||||||
|
public AvaloniaList<string> NetworkInterfaceList
|
||||||
|
{
|
||||||
|
get => new AvaloniaList<string>(_networkInterfaces.Keys);
|
||||||
|
}
|
||||||
|
|
||||||
public KeyboardHotkeys KeyboardHotkeys
|
public KeyboardHotkeys KeyboardHotkeys
|
||||||
{
|
{
|
||||||
get => _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()
|
public SettingsViewModel(VirtualFileSystem virtualFileSystem, ContentManager contentManager) : this()
|
||||||
{
|
{
|
||||||
_virtualFileSystem = virtualFileSystem;
|
_virtualFileSystem = virtualFileSystem;
|
||||||
@ -239,8 +286,10 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
TimeZones = new AvaloniaList<TimeZone>();
|
TimeZones = new AvaloniaList<TimeZone>();
|
||||||
AvailableGpus = new ObservableCollection<ComboBoxItem>();
|
AvailableGpus = new ObservableCollection<ComboBoxItem>();
|
||||||
_validTzRegions = new List<string>();
|
_validTzRegions = new List<string>();
|
||||||
|
_networkInterfaces = new Dictionary<string, string>();
|
||||||
|
|
||||||
CheckSoundBackends();
|
CheckSoundBackends();
|
||||||
|
PopulateNetworkInterfaces();
|
||||||
|
|
||||||
if (Program.PreviewerDetached)
|
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)
|
public void ValidateAndSetTimeZone(string location)
|
||||||
{
|
{
|
||||||
if (_validTzRegions.Contains(location))
|
if (_validTzRegions.Contains(location))
|
||||||
@ -349,6 +409,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
// CPU
|
// CPU
|
||||||
EnablePptc = config.System.EnablePtc;
|
EnablePptc = config.System.EnablePtc;
|
||||||
MemoryMode = (int)config.System.MemoryManagerMode.Value;
|
MemoryMode = (int)config.System.MemoryManagerMode.Value;
|
||||||
|
UseHypervisor = config.System.UseHypervisor;
|
||||||
|
|
||||||
// Graphics
|
// Graphics
|
||||||
GraphicsBackendIndex = (int)config.Graphics.GraphicsBackend.Value;
|
GraphicsBackendIndex = (int)config.Graphics.GraphicsBackend.Value;
|
||||||
@ -362,6 +423,9 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
AspectRatio = (int)config.Graphics.AspectRatio.Value;
|
AspectRatio = (int)config.Graphics.AspectRatio.Value;
|
||||||
GraphicsBackendMultithreadingIndex = (int)config.Graphics.BackendThreading.Value;
|
GraphicsBackendMultithreadingIndex = (int)config.Graphics.BackendThreading.Value;
|
||||||
ShaderDumpPath = config.Graphics.ShadersDumpPath;
|
ShaderDumpPath = config.Graphics.ShadersDumpPath;
|
||||||
|
AntiAliasingEffect = (int)config.Graphics.AntiAliasing.Value;
|
||||||
|
ScalingFilter = (int)config.Graphics.ScalingFilter.Value;
|
||||||
|
ScalingFilterLevel = config.Graphics.ScalingFilterLevel.Value;
|
||||||
|
|
||||||
// Audio
|
// Audio
|
||||||
AudioBackend = (int)config.System.AudioBackend.Value;
|
AudioBackend = (int)config.System.AudioBackend.Value;
|
||||||
@ -369,7 +433,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
// Network
|
// Network
|
||||||
EnableInternetAccess = config.System.EnableInternetAccess;
|
EnableInternetAccess = config.System.EnableInternetAccess;
|
||||||
|
|
||||||
// Logging
|
// Logging
|
||||||
EnableFileLog = config.Logger.EnableFileLog;
|
EnableFileLog = config.Logger.EnableFileLog;
|
||||||
EnableStub = config.Logger.EnableStub;
|
EnableStub = config.Logger.EnableStub;
|
||||||
@ -382,6 +446,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
EnableFsAccessLog = config.Logger.EnableFsAccessLog;
|
EnableFsAccessLog = config.Logger.EnableFsAccessLog;
|
||||||
FsGlobalAccessLogMode = config.System.FsGlobalAccessLogMode;
|
FsGlobalAccessLogMode = config.System.FsGlobalAccessLogMode;
|
||||||
OpenglDebugLevel = (int)config.Logger.GraphicsDebugLevel.Value;
|
OpenglDebugLevel = (int)config.Logger.GraphicsDebugLevel.Value;
|
||||||
|
|
||||||
|
NetworkInterfaceIndex = _networkInterfaces.Values.ToList().IndexOf(config.Multiplayer.LanInterfaceId.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SaveSettings()
|
public void SaveSettings()
|
||||||
@ -432,6 +498,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
// CPU
|
// CPU
|
||||||
config.System.EnablePtc.Value = EnablePptc;
|
config.System.EnablePtc.Value = EnablePptc;
|
||||||
config.System.MemoryManagerMode.Value = (MemoryManagerMode)MemoryMode;
|
config.System.MemoryManagerMode.Value = (MemoryManagerMode)MemoryMode;
|
||||||
|
config.System.UseHypervisor.Value = UseHypervisor;
|
||||||
|
|
||||||
// Graphics
|
// Graphics
|
||||||
config.Graphics.GraphicsBackend.Value = (GraphicsBackend)GraphicsBackendIndex;
|
config.Graphics.GraphicsBackend.Value = (GraphicsBackend)GraphicsBackendIndex;
|
||||||
@ -443,6 +510,9 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
config.Graphics.ResScaleCustom.Value = CustomResolutionScale;
|
config.Graphics.ResScaleCustom.Value = CustomResolutionScale;
|
||||||
config.Graphics.MaxAnisotropy.Value = MaxAnisotropy == 0 ? -1 : MathF.Pow(2, MaxAnisotropy);
|
config.Graphics.MaxAnisotropy.Value = MaxAnisotropy == 0 ? -1 : MathF.Pow(2, MaxAnisotropy);
|
||||||
config.Graphics.AspectRatio.Value = (AspectRatio)AspectRatio;
|
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)
|
if (ConfigurationState.Instance.Graphics.BackendThreading != (BackendThreading)GraphicsBackendMultithreadingIndex)
|
||||||
{
|
{
|
||||||
@ -479,6 +549,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
config.System.FsGlobalAccessLogMode.Value = FsGlobalAccessLogMode;
|
config.System.FsGlobalAccessLogMode.Value = FsGlobalAccessLogMode;
|
||||||
config.Logger.GraphicsDebugLevel.Value = (GraphicsDebugLevel)OpenglDebugLevel;
|
config.Logger.GraphicsDebugLevel.Value = (GraphicsDebugLevel)OpenglDebugLevel;
|
||||||
|
|
||||||
|
config.Multiplayer.LanInterfaceId.Value = _networkInterfaces[NetworkInterfaceList[NetworkInterfaceIndex]];
|
||||||
|
|
||||||
config.ToFileFormat().SaveConfig(Program.ConfigurationPath);
|
config.ToFileFormat().SaveConfig(Program.ConfigurationPath);
|
||||||
|
|
||||||
MainWindow.UpdateGraphicsConfig();
|
MainWindow.UpdateGraphicsConfig();
|
||||||
|
@ -17,234 +17,236 @@ using Ryujinx.Common.Logging;
|
|||||||
using Ryujinx.Common.Utilities;
|
using Ryujinx.Common.Utilities;
|
||||||
using Ryujinx.HLE.FileSystem;
|
using Ryujinx.HLE.FileSystem;
|
||||||
using Ryujinx.HLE.HOS;
|
using Ryujinx.HLE.HOS;
|
||||||
|
using Ryujinx.Ui.App.Common;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
|
||||||
using Path = System.IO.Path;
|
using Path = System.IO.Path;
|
||||||
using SpanHelpers = LibHac.Common.SpanHelpers;
|
using SpanHelpers = LibHac.Common.SpanHelpers;
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.ViewModels;
|
namespace Ryujinx.Ava.UI.ViewModels
|
||||||
|
|
||||||
public class TitleUpdateViewModel : BaseModel
|
|
||||||
{
|
{
|
||||||
public TitleUpdateMetadata _titleUpdateWindowData;
|
public class TitleUpdateViewModel : BaseModel
|
||||||
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
|
|
||||||
{
|
{
|
||||||
get => _titleUpdates;
|
public TitleUpdateMetadata _titleUpdateWindowData;
|
||||||
set
|
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;
|
get => _titleUpdates;
|
||||||
OnPropertyChanged();
|
set
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
{
|
{
|
||||||
Selected = "",
|
_titleUpdates = value;
|
||||||
Paths = new List<string>()
|
OnPropertyChanged();
|
||||||
};
|
|
||||||
|
|
||||||
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];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private void AddUpdate(string path)
|
public AvaloniaList<object> Views
|
||||||
{
|
|
||||||
if (File.Exists(path) && TitleUpdates.All(x => x.Path != path))
|
|
||||||
{
|
{
|
||||||
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
|
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();
|
LoadUpdates();
|
||||||
nacpFile.Get.Read(out _, 0, SpanHelpers.AsByteSpan(ref controlData), ReadOption.None).ThrowIfFailure();
|
}
|
||||||
|
|
||||||
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
|
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 () =>
|
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)
|
public void RemoveUpdate(TitleUpdateModel update)
|
||||||
{
|
|
||||||
TitleUpdates.Remove(update);
|
|
||||||
|
|
||||||
SortUpdates();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async void Add()
|
|
||||||
{
|
|
||||||
OpenFileDialog dialog = new()
|
|
||||||
{
|
{
|
||||||
Title = LocaleManager.Instance[LocaleKeys.SelectUpdateDialogTitle],
|
TitleUpdates.Remove(update);
|
||||||
AllowMultiple = true
|
|
||||||
};
|
|
||||||
|
|
||||||
dialog.Filters.Add(new FileDialogFilter
|
SortUpdates();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async void Add()
|
||||||
{
|
{
|
||||||
Name = "NSP",
|
OpenFileDialog dialog = new()
|
||||||
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)
|
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()
|
||||||
}
|
|
||||||
|
|
||||||
public void Save()
|
|
||||||
{
|
|
||||||
_titleUpdateWindowData.Paths.Clear();
|
|
||||||
_titleUpdateWindowData.Selected = "";
|
|
||||||
|
|
||||||
foreach (TitleUpdateModel update in TitleUpdates)
|
|
||||||
{
|
{
|
||||||
_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>();
|
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 stream = new())
|
||||||
using (MemoryStream streamPng = new())
|
using (MemoryStream streamPng = new())
|
||||||
|
@ -78,6 +78,7 @@
|
|||||||
</MenuItem>
|
</MenuItem>
|
||||||
<Separator />
|
<Separator />
|
||||||
<MenuItem Name="ChangeLanguageMenuItem" Header="{locale:Locale MenuBarOptionsChangeLanguage}" />
|
<MenuItem Name="ChangeLanguageMenuItem" Header="{locale:Locale MenuBarOptionsChangeLanguage}" />
|
||||||
|
<MenuItem Name="ToggleFileTypesMenuItem" Header="{locale:Locale MenuBarShowFileTypes}" />
|
||||||
<Separator />
|
<Separator />
|
||||||
<MenuItem
|
<MenuItem
|
||||||
Click="OpenSettings"
|
Click="OpenSettings"
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Interactivity;
|
using Avalonia.Interactivity;
|
||||||
using LibHac.FsSystem;
|
|
||||||
using LibHac.Ncm;
|
using LibHac.Ncm;
|
||||||
|
using LibHac.Tools.FsSystem.NcaUtils;
|
||||||
using Ryujinx.Ava.Common.Locale;
|
using Ryujinx.Ava.Common.Locale;
|
||||||
using Ryujinx.Ava.UI.Helpers;
|
using Ryujinx.Ava.UI.Helpers;
|
||||||
using Ryujinx.Ava.UI.ViewModels;
|
using Ryujinx.Ava.UI.ViewModels;
|
||||||
@ -11,6 +11,8 @@ using Ryujinx.Common;
|
|||||||
using Ryujinx.Common.Utilities;
|
using Ryujinx.Common.Utilities;
|
||||||
using Ryujinx.HLE.HOS;
|
using Ryujinx.HLE.HOS;
|
||||||
using Ryujinx.Modules;
|
using Ryujinx.Modules;
|
||||||
|
using Ryujinx.Ui.Common;
|
||||||
|
using Ryujinx.Ui.Common.Configuration;
|
||||||
using Ryujinx.Ui.Common.Helper;
|
using Ryujinx.Ui.Common.Helper;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@ -29,6 +31,30 @@ namespace Ryujinx.Ava.UI.Views.Main
|
|||||||
{
|
{
|
||||||
InitializeComponent();
|
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();
|
List<MenuItem> menuItems = new();
|
||||||
|
|
||||||
string localePath = "Ryujinx.Ava/Assets/Locales";
|
string localePath = "Ryujinx.Ava/Assets/Locales";
|
||||||
@ -42,7 +68,7 @@ namespace Ryujinx.Ava.UI.Views.Main
|
|||||||
{
|
{
|
||||||
string languageCode = Path.GetFileNameWithoutExtension(locale).Split('.').Last();
|
string languageCode = Path.GetFileNameWithoutExtension(locale).Split('.').Last();
|
||||||
string languageJson = EmbeddedResources.ReadAllText($"{localePath}/{languageCode}{localeExt}");
|
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))
|
if (!strings.TryGetValue("Language", out string languageName))
|
||||||
{
|
{
|
||||||
@ -61,7 +87,7 @@ namespace Ryujinx.Ava.UI.Views.Main
|
|||||||
menuItems.Add(menuItem);
|
menuItems.Add(menuItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
ChangeLanguageMenuItem.Items = menuItems.ToArray();
|
return menuItems.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
|
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))
|
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);
|
AmiiboWindow window = new(ViewModel.ShowAll, ViewModel.LastScannedAmiiboId, titleId);
|
||||||
|
|
||||||
await window.ShowDialog(Window);
|
await window.ShowDialog(Window);
|
||||||
@ -148,13 +174,11 @@ namespace Ryujinx.Ava.UI.Views.Main
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ApplicationLoader application = ViewModel.AppHost.Device.Application;
|
string name = ViewModel.AppHost.Device.Processes.ActiveApplication.ApplicationControlProperties.Title[(int)ViewModel.AppHost.Device.System.State.DesiredTitleLanguage].NameString.ToString();
|
||||||
if (application != null)
|
|
||||||
{
|
|
||||||
await new CheatWindow(Window.VirtualFileSystem, application.TitleIdText, application.TitleName).ShowDialog(Window);
|
|
||||||
|
|
||||||
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)
|
private void ScanAmiiboMenuItem_AttachedToVisualTree(object sender, VisualTreeAttachmentEventArgs e)
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<UserControl
|
<UserControl
|
||||||
x:Class="Ryujinx.Ava.UI.Views.Settings.SettingsCPUView"
|
x:Class="Ryujinx.Ava.UI.Views.Settings.SettingsCPUView"
|
||||||
xmlns="https://github.com/avaloniaui"
|
xmlns="https://github.com/avaloniaui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
@ -65,8 +65,14 @@
|
|||||||
</ComboBoxItem>
|
</ComboBoxItem>
|
||||||
</ComboBox>
|
</ComboBox>
|
||||||
</StackPanel>
|
</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>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Border>
|
</Border>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
|
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
|
||||||
|
Design.Width="1000"
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
x:CompileBindings="True"
|
x:CompileBindings="True"
|
||||||
x:DataType="viewModels:SettingsViewModel">
|
x:DataType="viewModels:SettingsViewModel">
|
||||||
@ -111,6 +112,83 @@
|
|||||||
Minimum="0.1"
|
Minimum="0.1"
|
||||||
Value="{Binding CustomResolutionScale}" />
|
Value="{Binding CustomResolutionScale}" />
|
||||||
</StackPanel>
|
</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">
|
<StackPanel Orientation="Horizontal">
|
||||||
<TextBlock VerticalAlignment="Center"
|
<TextBlock VerticalAlignment="Center"
|
||||||
ToolTip.Tip="{locale:Locale AnisotropyTooltip}"
|
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