Compare commits
43 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
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 | ||
|
2fd819613f | ||
|
ad6ff6ce99 | ||
|
dc30d94852 | ||
|
4f293f8cbe | ||
|
32a1cd83fd | ||
|
e3d0ccf8d5 | ||
|
c14844d12c | ||
|
7fea26e97e | ||
|
7b7f62c776 | ||
|
423dbc8888 |
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
|
3
.gitignore
vendored
3
.gitignore
vendored
@@ -170,3 +170,6 @@ launchSettings.json
|
|||||||
|
|
||||||
# NetCore Publishing Profiles
|
# NetCore Publishing Profiles
|
||||||
PublishProfiles/
|
PublishProfiles/
|
||||||
|
|
||||||
|
# Glade backup files
|
||||||
|
*.glade~
|
||||||
|
@@ -16,4 +16,10 @@
|
|||||||
</ContentWithTargetPath>
|
</ContentWithTargetPath>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
|
||||||
|
<_Parameter1>Ryujinx.Tests</_Parameter1>
|
||||||
|
</AssemblyAttribute>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
using ARMeilleure.IntermediateRepresentation;
|
using ARMeilleure.IntermediateRepresentation;
|
||||||
using System;
|
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
|
||||||
namespace ARMeilleure.CodeGen.Arm64
|
namespace ARMeilleure.CodeGen.Arm64
|
||||||
@@ -32,9 +31,12 @@ namespace ARMeilleure.CodeGen.Arm64
|
|||||||
|
|
||||||
public static bool TryEncodeBitMask(Operand operand, out int immN, out int immS, out int immR)
|
public static bool TryEncodeBitMask(Operand operand, out int immN, out int immS, out int immR)
|
||||||
{
|
{
|
||||||
ulong value = operand.Value;
|
return TryEncodeBitMask(operand.Type, operand.Value, out immN, out immS, out immR);
|
||||||
|
}
|
||||||
|
|
||||||
if (operand.Type == OperandType.I32)
|
public static bool TryEncodeBitMask(OperandType type, ulong value, out int immN, out int immS, out int immR)
|
||||||
|
{
|
||||||
|
if (type == OperandType.I32)
|
||||||
{
|
{
|
||||||
value |= value << 32;
|
value |= value << 32;
|
||||||
}
|
}
|
||||||
@@ -50,7 +52,7 @@ namespace ARMeilleure.CodeGen.Arm64
|
|||||||
// Any value AND all ones will be equal itself, so it's effectively a no-op.
|
// Any value AND all ones will be equal itself, so it's effectively a no-op.
|
||||||
// Any value OR all ones will be equal all ones, so one can just use MOV.
|
// Any value OR all ones will be equal all ones, so one can just use MOV.
|
||||||
// Any value XOR all ones will be equal its inverse, so one can just use MVN.
|
// Any value XOR all ones will be equal its inverse, so one can just use MVN.
|
||||||
if (value == ulong.MaxValue)
|
if (value == 0 || value == ulong.MaxValue)
|
||||||
{
|
{
|
||||||
immN = 0;
|
immN = 0;
|
||||||
immS = 0;
|
immS = 0;
|
||||||
@@ -59,79 +61,18 @@ namespace ARMeilleure.CodeGen.Arm64
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
int bitLength = CountSequence(value);
|
// Normalize value, rotating it such that the LSB is 1: Ensures we get a complete element that has not
|
||||||
|
// been cut-in-half across the word boundary.
|
||||||
|
int rotation = BitOperations.TrailingZeroCount(value & (value + 1));
|
||||||
|
ulong rotatedValue = ulong.RotateRight(value, rotation);
|
||||||
|
|
||||||
if ((value >> bitLength) != 0)
|
// Now that we have a complete element in the LSB with the LSB = 1, determine size and number of ones
|
||||||
{
|
// in element.
|
||||||
bitLength += CountSequence(value >> bitLength);
|
int elementSize = BitOperations.TrailingZeroCount(rotatedValue & (rotatedValue + 1));
|
||||||
}
|
int onesInElement = BitOperations.TrailingZeroCount(~rotatedValue);
|
||||||
|
|
||||||
int bitLengthLog2 = BitOperations.Log2((uint)bitLength);
|
// Check the value is repeating; also ensures element size is a power of two.
|
||||||
int bitLengthPow2 = 1 << bitLengthLog2;
|
if (ulong.RotateRight(value, elementSize) != value)
|
||||||
|
|
||||||
if (bitLengthPow2 < bitLength)
|
|
||||||
{
|
|
||||||
bitLengthLog2++;
|
|
||||||
bitLengthPow2 <<= 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int selectedESize = 64;
|
|
||||||
int repetitions = 1;
|
|
||||||
int onesCount = BitOperations.PopCount(value);
|
|
||||||
|
|
||||||
if (bitLengthPow2 < 64 && (value >> bitLengthPow2) != 0)
|
|
||||||
{
|
|
||||||
for (int eSizeLog2 = bitLengthLog2; eSizeLog2 < 6; eSizeLog2++)
|
|
||||||
{
|
|
||||||
bool match = true;
|
|
||||||
int eSize = 1 << eSizeLog2;
|
|
||||||
ulong mask = (1UL << eSize) - 1;
|
|
||||||
ulong eValue = value & mask;
|
|
||||||
|
|
||||||
for (int e = 1; e < 64 / eSize; e++)
|
|
||||||
{
|
|
||||||
if (((value >> (e * eSize)) & mask) != eValue)
|
|
||||||
{
|
|
||||||
match = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (match)
|
|
||||||
{
|
|
||||||
selectedESize = eSize;
|
|
||||||
repetitions = 64 / eSize;
|
|
||||||
onesCount = BitOperations.PopCount(eValue);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find rotation. We have two cases, one where the highest bit is 0
|
|
||||||
// and one where it is 1.
|
|
||||||
// If it's 1, we just need to count the number of 1 bits on the MSB to find the right rotation.
|
|
||||||
// If it's 0, we just need to count the number of 0 bits on the LSB to find the left rotation,
|
|
||||||
// then we can convert it to the right rotation shift by subtracting the value from the element size.
|
|
||||||
int rotation;
|
|
||||||
long vHigh = (long)(value << (64 - selectedESize));
|
|
||||||
if (vHigh < 0)
|
|
||||||
{
|
|
||||||
rotation = BitOperations.LeadingZeroCount(~(ulong)vHigh);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
rotation = (selectedESize - BitOperations.TrailingZeroCount(value)) & (selectedESize - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reconstruct value and see if it matches. If not, we can't encode.
|
|
||||||
ulong reconstructed = onesCount == 64 ? ulong.MaxValue : RotateRight((1UL << onesCount) - 1, rotation, selectedESize);
|
|
||||||
|
|
||||||
for (int bit = 32; bit >= selectedESize; bit >>= 1)
|
|
||||||
{
|
|
||||||
reconstructed |= reconstructed << bit;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (reconstructed != value || onesCount == 0)
|
|
||||||
{
|
{
|
||||||
immN = 0;
|
immN = 0;
|
||||||
immS = 0;
|
immS = 0;
|
||||||
@@ -140,34 +81,11 @@ namespace ARMeilleure.CodeGen.Arm64
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
immR = rotation;
|
immN = (elementSize >> 6) & 1;
|
||||||
|
immS = (((~elementSize + 1) << 1) | (onesInElement - 1)) & 0x3f;
|
||||||
// immN indicates that there are no repetitions.
|
immR = (elementSize - rotation) & (elementSize - 1);
|
||||||
// The MSB of immS indicates the amount of repetitions, and the LSB the number of bits set.
|
|
||||||
if (repetitions == 1)
|
|
||||||
{
|
|
||||||
immN = 1;
|
|
||||||
immS = 0;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
immN = 0;
|
|
||||||
immS = (0xf80 >> BitOperations.Log2((uint)repetitions)) & 0x3f;
|
|
||||||
}
|
|
||||||
|
|
||||||
immS |= onesCount - 1;
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int CountSequence(ulong value)
|
|
||||||
{
|
|
||||||
return BitOperations.TrailingZeroCount(value) + BitOperations.TrailingZeroCount(~value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ulong RotateRight(ulong bits, int shift, int size)
|
|
||||||
{
|
|
||||||
return (bits >> shift) | ((bits << (size - shift)) & (size == 64 ? ulong.MaxValue : (1UL << size) - 1));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1303,7 +1303,15 @@ namespace ARMeilleure.CodeGen.Arm64
|
|||||||
|
|
||||||
private static void GenerateConstantCopy(CodeGenContext context, Operand dest, ulong value)
|
private static void GenerateConstantCopy(CodeGenContext context, Operand dest, ulong value)
|
||||||
{
|
{
|
||||||
if (value != 0)
|
if (value == 0)
|
||||||
|
{
|
||||||
|
context.Assembler.Mov(dest, Register(ZrRegister, dest.Type));
|
||||||
|
}
|
||||||
|
else if (CodeGenCommon.TryEncodeBitMask(dest.Type, value, out _, out _, out _))
|
||||||
|
{
|
||||||
|
context.Assembler.Orr(dest, Register(ZrRegister, dest.Type), Const(dest.Type, (long)value));
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
int hw = 0;
|
int hw = 0;
|
||||||
bool first = true;
|
bool first = true;
|
||||||
@@ -1328,10 +1336,6 @@ namespace ARMeilleure.CodeGen.Arm64
|
|||||||
value >>= 16;
|
value >>= 16;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
context.Assembler.Mov(dest, Register(ZrRegister, dest.Type));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void GenerateAtomicCas(
|
private static void GenerateAtomicCas(
|
||||||
|
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -191,7 +191,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)
|
||||||
|
@@ -29,7 +29,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 = 4272; //! To be incremented manually for each change to the ARMeilleure project.
|
private const uint InternalVersion = 4328; //! 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";
|
||||||
@@ -745,9 +745,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)
|
||||||
|
@@ -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,18 +7,18 @@ 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)
|
||||||
|
@@ -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();
|
||||||
}
|
}
|
||||||
|
@@ -20,7 +20,7 @@
|
|||||||
<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.17.0" />
|
||||||
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.3" />
|
<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.4.0" />
|
||||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
|
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
|
||||||
<PackageVersion Include="MsgPack.Cli" Version="1.0.1" />
|
<PackageVersion Include="MsgPack.Cli" Version="1.0.1" />
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
<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.1-build23" />
|
||||||
<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,12 @@
|
|||||||
<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.26.1" />
|
||||||
<PackageVersion Include="System.IO.FileSystem.Primitives" Version="4.3.0" />
|
<PackageVersion Include="System.IO.FileSystem.Primitives" Version="4.3.0" />
|
||||||
<PackageVersion Include="System.Management" Version="7.0.0" />
|
<PackageVersion Include="System.Management" Version="7.0.0" />
|
||||||
<PackageVersion Include="System.Net.NameResolution" Version="4.3.0" />
|
<PackageVersion Include="System.Net.NameResolution" Version="4.3.0" />
|
||||||
<PackageVersion Include="System.Threading.ThreadPool" Version="4.3.0" />
|
<PackageVersion Include="System.Threading.ThreadPool" Version="4.3.0" />
|
||||||
|
<PackageVersion Include="UnicornEngine.Unicorn" Version="2.0.2-rc1-9c9356d" />
|
||||||
<PackageVersion Include="XamlNameReferenceGenerator" Version="1.5.1" />
|
<PackageVersion Include="XamlNameReferenceGenerator" Version="1.5.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
@@ -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
|
||||||
{
|
{
|
||||||
@@ -231,7 +230,7 @@ namespace Ryujinx.Ava
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private unsafe void Renderer_ScreenCaptured(object sender, ScreenCaptureImageInfo e)
|
private void Renderer_ScreenCaptured(object sender, ScreenCaptureImageInfo e)
|
||||||
{
|
{
|
||||||
if (e.Data.Length > 0 && e.Height > 0 && e.Width > 0)
|
if (e.Data.Length > 0 && e.Height > 0 && e.Width > 0)
|
||||||
{
|
{
|
||||||
@@ -240,8 +239,8 @@ namespace Ryujinx.Ava
|
|||||||
lock (_lockObject)
|
lock (_lockObject)
|
||||||
{
|
{
|
||||||
DateTime currentTime = DateTime.Now;
|
DateTime currentTime = DateTime.Now;
|
||||||
string filename = $"ryujinx_capture_{currentTime}-{currentTime:D2}-{currentTime:D2}_{currentTime:D2}-{currentTime:D2}-{currentTime: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"),
|
||||||
@@ -678,7 +677,8 @@ 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);
|
||||||
|
|
||||||
Device = new Switch(configuration);
|
Device = new Switch(configuration);
|
||||||
}
|
}
|
||||||
@@ -765,7 +765,7 @@ namespace Ryujinx.Ava
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private unsafe void RenderLoop()
|
private void RenderLoop()
|
||||||
{
|
{
|
||||||
Dispatcher.UIThread.InvokeAsync(() =>
|
Dispatcher.UIThread.InvokeAsync(() =>
|
||||||
{
|
{
|
||||||
@@ -801,6 +801,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 +841,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)";
|
||||||
|
@@ -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",
|
||||||
@@ -26,6 +27,9 @@
|
|||||||
"MenuBarToolsInstallFirmware": "Install Firmware",
|
"MenuBarToolsInstallFirmware": "Install Firmware",
|
||||||
"MenuBarFileToolsInstallFirmwareFromFile": "Install a firmware from XCI or ZIP",
|
"MenuBarFileToolsInstallFirmwareFromFile": "Install a firmware from XCI or ZIP",
|
||||||
"MenuBarFileToolsInstallFirmwareFromDirectory": "Install a firmware from a directory",
|
"MenuBarFileToolsInstallFirmwareFromDirectory": "Install a firmware from a directory",
|
||||||
|
"MenuBarToolsManageFileTypes": "Manage file types",
|
||||||
|
"MenuBarToolsInstallFileTypes": "Install file types",
|
||||||
|
"MenuBarToolsUninstallFileTypes": "Uninstall file types",
|
||||||
"MenuBarHelp": "Help",
|
"MenuBarHelp": "Help",
|
||||||
"MenuBarHelpCheckForUpdates": "Check for Updates",
|
"MenuBarHelpCheckForUpdates": "Check for Updates",
|
||||||
"MenuBarHelpAbout": "About",
|
"MenuBarHelpAbout": "About",
|
||||||
@@ -339,6 +343,10 @@
|
|||||||
"DialogFirmwareInstallEmbeddedSuccessMessage": "No installed firmware was found but Ryujinx was able to install firmware {0} from the provided game.\\nThe emulator will now start.",
|
"DialogFirmwareInstallEmbeddedSuccessMessage": "No installed firmware was found but Ryujinx was able to install firmware {0} from the provided game.\\nThe emulator will now start.",
|
||||||
"DialogFirmwareNoFirmwareInstalledMessage": "No Firmware Installed",
|
"DialogFirmwareNoFirmwareInstalledMessage": "No Firmware Installed",
|
||||||
"DialogFirmwareInstalledMessage": "Firmware {0} was installed",
|
"DialogFirmwareInstalledMessage": "Firmware {0} was installed",
|
||||||
|
"DialogInstallFileTypesSuccessMessage": "Successfully installed file types!",
|
||||||
|
"DialogInstallFileTypesErrorMessage": "Failed to install file types.",
|
||||||
|
"DialogUninstallFileTypesSuccessMessage": "Successfully uninstalled file types!",
|
||||||
|
"DialogUninstallFileTypesErrorMessage": "Failed to uninstall file types.",
|
||||||
"DialogOpenSettingsWindowLabel": "Open Settings Window",
|
"DialogOpenSettingsWindowLabel": "Open Settings Window",
|
||||||
"DialogControllerAppletTitle": "Controller Applet",
|
"DialogControllerAppletTitle": "Controller Applet",
|
||||||
"DialogMessageDialogErrorExceptionMessage": "Error displaying Message Dialog: {0}",
|
"DialogMessageDialogErrorExceptionMessage": "Error displaying Message Dialog: {0}",
|
||||||
@@ -450,6 +458,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.",
|
||||||
@@ -500,7 +509,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",
|
||||||
@@ -619,4 +628,4 @@
|
|||||||
"UserProfilesRecoverEmptyList": "No profiles to recover",
|
"UserProfilesRecoverEmptyList": "No profiles to recover",
|
||||||
"UserEditorTitle" : "Edit User",
|
"UserEditorTitle" : "Edit User",
|
||||||
"UserEditorTitleCreate" : "Create User"
|
"UserEditorTitleCreate" : "Create User"
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Input;
|
using Avalonia.Input;
|
||||||
|
using Avalonia.Interactivity;
|
||||||
using Ryujinx.Ava.Common.Locale;
|
using Ryujinx.Ava.Common.Locale;
|
||||||
using Ryujinx.Input;
|
using Ryujinx.Input;
|
||||||
using System;
|
using System;
|
||||||
@@ -30,6 +31,7 @@ namespace Ryujinx.Ava.Input
|
|||||||
_control.KeyDown += OnKeyPress;
|
_control.KeyDown += OnKeyPress;
|
||||||
_control.KeyUp += OnKeyRelease;
|
_control.KeyUp += OnKeyRelease;
|
||||||
_control.TextInput += Control_TextInput;
|
_control.TextInput += Control_TextInput;
|
||||||
|
_control.AddHandler(InputElement.TextInputEvent, Control_LastChanceTextInput, RoutingStrategies.Bubble);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Control_TextInput(object sender, TextInputEventArgs e)
|
private void Control_TextInput(object sender, TextInputEventArgs e)
|
||||||
@@ -37,6 +39,12 @@ namespace Ryujinx.Ava.Input
|
|||||||
TextInput?.Invoke(this, e.Text);
|
TextInput?.Invoke(this, e.Text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void Control_LastChanceTextInput(object sender, TextInputEventArgs e)
|
||||||
|
{
|
||||||
|
// Swallow event
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
|
||||||
public event Action<string> OnGamepadConnected
|
public event Action<string> OnGamepadConnected
|
||||||
{
|
{
|
||||||
add { }
|
add { }
|
||||||
|
@@ -132,8 +132,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 +240,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;
|
||||||
|
|
||||||
@@ -300,8 +300,6 @@ namespace Ryujinx.Modules
|
|||||||
ryuExe = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, OperatingSystem.IsWindows() ? "Ryujinx.exe" : "Ryujinx");
|
ryuExe = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, OperatingSystem.IsWindows() ? "Ryujinx.exe" : "Ryujinx");
|
||||||
}
|
}
|
||||||
|
|
||||||
SetFileExecutable(ryuExe);
|
|
||||||
|
|
||||||
Process.Start(ryuExe, CommandLineState.Arguments);
|
Process.Start(ryuExe, CommandLineState.Arguments);
|
||||||
|
|
||||||
Environment.Exit(0);
|
Environment.Exit(0);
|
||||||
@@ -408,9 +406,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,22 +470,6 @@ namespace Ryujinx.Modules
|
|||||||
worker.Start();
|
worker.Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void SetFileExecutable(string path)
|
|
||||||
{
|
|
||||||
const UnixFileMode ExecutableFileMode = UnixFileMode.UserExecute |
|
|
||||||
UnixFileMode.UserWrite |
|
|
||||||
UnixFileMode.UserRead |
|
|
||||||
UnixFileMode.GroupRead |
|
|
||||||
UnixFileMode.GroupWrite |
|
|
||||||
UnixFileMode.OtherRead |
|
|
||||||
UnixFileMode.OtherWrite;
|
|
||||||
|
|
||||||
if (!OperatingSystem.IsWindows() && File.Exists(path))
|
|
||||||
{
|
|
||||||
File.SetUnixFileMode(path, ExecutableFileMode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async void InstallUpdate(TaskDialog taskDialog, string updateFile)
|
private static async void InstallUpdate(TaskDialog taskDialog, string updateFile)
|
||||||
{
|
{
|
||||||
// Extract Update
|
// Extract Update
|
||||||
@@ -503,27 +485,30 @@ namespace Ryujinx.Modules
|
|||||||
await Task.Run(() =>
|
await Task.Run(() =>
|
||||||
{
|
{
|
||||||
TarEntry tarEntry;
|
TarEntry tarEntry;
|
||||||
while ((tarEntry = tarStream.GetNextEntry()) != null)
|
|
||||||
|
if (!OperatingSystem.IsWindows())
|
||||||
{
|
{
|
||||||
if (tarEntry.IsDirectory) continue;
|
while ((tarEntry = tarStream.GetNextEntry()) is not null)
|
||||||
|
|
||||||
string outPath = Path.Combine(UpdateDir, tarEntry.Name);
|
|
||||||
|
|
||||||
Directory.CreateDirectory(Path.GetDirectoryName(outPath));
|
|
||||||
|
|
||||||
using (FileStream outStream = File.OpenWrite(outPath))
|
|
||||||
{
|
{
|
||||||
tarStream.CopyEntryContents(outStream);
|
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.SetUnixFileMode(outPath, (UnixFileMode)tarEntry.TarHeader.Mode);
|
||||||
|
File.SetLastWriteTime(outPath, DateTime.SpecifyKind(tarEntry.ModTime, DateTimeKind.Utc));
|
||||||
|
|
||||||
|
Dispatcher.UIThread.Post(() =>
|
||||||
|
{
|
||||||
|
taskDialog.SetProgressBarState(GetPercentage(tarEntry.Size, inStream.Length), TaskDialogProgressState.Normal);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
File.SetLastWriteTime(outPath, DateTime.SpecifyKind(tarEntry.ModTime, DateTimeKind.Utc));
|
|
||||||
|
|
||||||
TarEntry entry = tarEntry;
|
|
||||||
|
|
||||||
Dispatcher.UIThread.Post(() =>
|
|
||||||
{
|
|
||||||
taskDialog.SetProgressBarState(GetPercentage(entry.Size, inStream.Length), TaskDialogProgressState.Normal);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -603,8 +588,6 @@ namespace Ryujinx.Modules
|
|||||||
|
|
||||||
Directory.Delete(UpdateDir, true);
|
Directory.Delete(UpdateDir, true);
|
||||||
|
|
||||||
SetFileExecutable(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Ryujinx"));
|
|
||||||
|
|
||||||
_updateSuccessful = true;
|
_updateSuccessful = true;
|
||||||
|
|
||||||
taskDialog.Hide();
|
taskDialog.Hide();
|
||||||
|
@@ -14,10 +14,8 @@ using Ryujinx.Ui.Common;
|
|||||||
using Ryujinx.Ui.Common.Configuration;
|
using Ryujinx.Ui.Common.Configuration;
|
||||||
using Ryujinx.Ui.Common.Helper;
|
using Ryujinx.Ui.Common.Helper;
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Runtime.Versioning;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Ryujinx.Ava
|
namespace Ryujinx.Ava
|
||||||
@@ -35,48 +33,6 @@ namespace Ryujinx.Ava
|
|||||||
|
|
||||||
private const uint MB_ICONWARNING = 0x30;
|
private const uint MB_ICONWARNING = 0x30;
|
||||||
|
|
||||||
[SupportedOSPlatform("linux")]
|
|
||||||
static void RegisterMimeTypes()
|
|
||||||
{
|
|
||||||
if (ReleaseInformation.IsFlatHubBuild())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
string mimeDbPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".local", "share", "mime");
|
|
||||||
|
|
||||||
if (!File.Exists(Path.Combine(mimeDbPath, "packages", "Ryujinx.xml")))
|
|
||||||
{
|
|
||||||
string mimeTypesFile = Path.Combine(ReleaseInformation.GetBaseApplicationDirectory(), "mime", "Ryujinx.xml");
|
|
||||||
using Process mimeProcess = new();
|
|
||||||
|
|
||||||
mimeProcess.StartInfo.FileName = "xdg-mime";
|
|
||||||
mimeProcess.StartInfo.Arguments = $"install --novendor --mode user {mimeTypesFile}";
|
|
||||||
|
|
||||||
mimeProcess.Start();
|
|
||||||
mimeProcess.WaitForExit();
|
|
||||||
|
|
||||||
if (mimeProcess.ExitCode != 0)
|
|
||||||
{
|
|
||||||
Logger.Error?.PrintMsg(LogClass.Application, $"Unable to install mime types. Make sure xdg-utils is installed. Process exited with code: {mimeProcess.ExitCode}");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
using Process updateMimeProcess = new();
|
|
||||||
|
|
||||||
updateMimeProcess.StartInfo.FileName = "update-mime-database";
|
|
||||||
updateMimeProcess.StartInfo.Arguments = mimeDbPath;
|
|
||||||
|
|
||||||
updateMimeProcess.Start();
|
|
||||||
updateMimeProcess.WaitForExit();
|
|
||||||
|
|
||||||
if (updateMimeProcess.ExitCode != 0)
|
|
||||||
{
|
|
||||||
Logger.Error?.PrintMsg(LogClass.Application, $"Could not update local mime database. Process exited with code: {updateMimeProcess.ExitCode}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void Main(string[] args)
|
public static void Main(string[] args)
|
||||||
{
|
{
|
||||||
Version = ReleaseInformation.GetVersion();
|
Version = ReleaseInformation.GetVersion();
|
||||||
@@ -139,12 +95,6 @@ namespace Ryujinx.Ava
|
|||||||
// Initialize the logger system.
|
// Initialize the logger system.
|
||||||
LoggerModule.Initialize();
|
LoggerModule.Initialize();
|
||||||
|
|
||||||
// Register mime types on linux.
|
|
||||||
if (OperatingSystem.IsLinux())
|
|
||||||
{
|
|
||||||
RegisterMimeTypes();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize Discord integration.
|
// Initialize Discord integration.
|
||||||
DiscordIntegrationModule.Initialize();
|
DiscordIntegrationModule.Initialize();
|
||||||
|
|
||||||
|
@@ -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>
|
||||||
|
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -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;
|
||||||
@@ -140,68 +141,75 @@ namespace Ryujinx.Ava.UI.Renderer
|
|||||||
{
|
{
|
||||||
if (VisualRoot != null)
|
if (VisualRoot != null)
|
||||||
{
|
{
|
||||||
Point rootVisualPosition = this.TranslatePoint(new Point((long)lParam & 0xFFFF, (long)lParam >> 16 & 0xFFFF), VisualRoot).Value;
|
if (msg == WindowsMessages.LBUTTONDOWN ||
|
||||||
Pointer pointer = new(0, PointerType.Mouse, true);
|
msg == WindowsMessages.RBUTTONDOWN ||
|
||||||
|
msg == WindowsMessages.LBUTTONUP ||
|
||||||
switch (msg)
|
msg == WindowsMessages.RBUTTONUP ||
|
||||||
|
msg == WindowsMessages.MOUSEMOVE)
|
||||||
{
|
{
|
||||||
case WindowsMessages.LBUTTONDOWN:
|
Point rootVisualPosition = this.TranslatePoint(new Point((long)lParam & 0xFFFF, (long)lParam >> 16 & 0xFFFF), VisualRoot).Value;
|
||||||
case WindowsMessages.RBUTTONDOWN:
|
Pointer pointer = new(0, PointerType.Mouse, true);
|
||||||
{
|
|
||||||
bool isLeft = msg == WindowsMessages.LBUTTONDOWN;
|
|
||||||
RawInputModifiers pointerPointModifier = isLeft ? RawInputModifiers.LeftMouseButton : RawInputModifiers.RightMouseButton;
|
|
||||||
PointerPointProperties properties = new(pointerPointModifier, isLeft ? PointerUpdateKind.LeftButtonPressed : PointerUpdateKind.RightButtonPressed);
|
|
||||||
|
|
||||||
var evnt = new PointerPressedEventArgs(
|
switch (msg)
|
||||||
this,
|
{
|
||||||
pointer,
|
case WindowsMessages.LBUTTONDOWN:
|
||||||
VisualRoot,
|
case WindowsMessages.RBUTTONDOWN:
|
||||||
rootVisualPosition,
|
{
|
||||||
(ulong)Environment.TickCount64,
|
bool isLeft = msg == WindowsMessages.LBUTTONDOWN;
|
||||||
properties,
|
RawInputModifiers pointerPointModifier = isLeft ? RawInputModifiers.LeftMouseButton : RawInputModifiers.RightMouseButton;
|
||||||
KeyModifiers.None);
|
PointerPointProperties properties = new(pointerPointModifier, isLeft ? PointerUpdateKind.LeftButtonPressed : PointerUpdateKind.RightButtonPressed);
|
||||||
|
|
||||||
RaiseEvent(evnt);
|
var evnt = new PointerPressedEventArgs(
|
||||||
|
this,
|
||||||
|
pointer,
|
||||||
|
VisualRoot,
|
||||||
|
rootVisualPosition,
|
||||||
|
(ulong)Environment.TickCount64,
|
||||||
|
properties,
|
||||||
|
KeyModifiers.None);
|
||||||
|
|
||||||
break;
|
RaiseEvent(evnt);
|
||||||
}
|
|
||||||
case WindowsMessages.LBUTTONUP:
|
|
||||||
case WindowsMessages.RBUTTONUP:
|
|
||||||
{
|
|
||||||
bool isLeft = msg == WindowsMessages.LBUTTONUP;
|
|
||||||
RawInputModifiers pointerPointModifier = isLeft ? RawInputModifiers.LeftMouseButton : RawInputModifiers.RightMouseButton;
|
|
||||||
PointerPointProperties properties = new(pointerPointModifier, isLeft ? PointerUpdateKind.LeftButtonReleased : PointerUpdateKind.RightButtonReleased);
|
|
||||||
|
|
||||||
var evnt = new PointerReleasedEventArgs(
|
break;
|
||||||
this,
|
}
|
||||||
pointer,
|
case WindowsMessages.LBUTTONUP:
|
||||||
VisualRoot,
|
case WindowsMessages.RBUTTONUP:
|
||||||
rootVisualPosition,
|
{
|
||||||
(ulong)Environment.TickCount64,
|
bool isLeft = msg == WindowsMessages.LBUTTONUP;
|
||||||
properties,
|
RawInputModifiers pointerPointModifier = isLeft ? RawInputModifiers.LeftMouseButton : RawInputModifiers.RightMouseButton;
|
||||||
KeyModifiers.None,
|
PointerPointProperties properties = new(pointerPointModifier, isLeft ? PointerUpdateKind.LeftButtonReleased : PointerUpdateKind.RightButtonReleased);
|
||||||
isLeft ? MouseButton.Left : MouseButton.Right);
|
|
||||||
|
|
||||||
RaiseEvent(evnt);
|
var evnt = new PointerReleasedEventArgs(
|
||||||
|
this,
|
||||||
|
pointer,
|
||||||
|
VisualRoot,
|
||||||
|
rootVisualPosition,
|
||||||
|
(ulong)Environment.TickCount64,
|
||||||
|
properties,
|
||||||
|
KeyModifiers.None,
|
||||||
|
isLeft ? MouseButton.Left : MouseButton.Right);
|
||||||
|
|
||||||
break;
|
RaiseEvent(evnt);
|
||||||
}
|
|
||||||
case WindowsMessages.MOUSEMOVE:
|
|
||||||
{
|
|
||||||
var evnt = new PointerEventArgs(
|
|
||||||
PointerMovedEvent,
|
|
||||||
this,
|
|
||||||
pointer,
|
|
||||||
VisualRoot,
|
|
||||||
rootVisualPosition,
|
|
||||||
(ulong)Environment.TickCount64,
|
|
||||||
new PointerPointProperties(RawInputModifiers.None, PointerUpdateKind.Other),
|
|
||||||
KeyModifiers.None);
|
|
||||||
|
|
||||||
RaiseEvent(evnt);
|
break;
|
||||||
|
}
|
||||||
|
case WindowsMessages.MOUSEMOVE:
|
||||||
|
{
|
||||||
|
var evnt = new PointerEventArgs(
|
||||||
|
PointerMovedEvent,
|
||||||
|
this,
|
||||||
|
pointer,
|
||||||
|
VisualRoot,
|
||||||
|
rootVisualPosition,
|
||||||
|
(ulong)Environment.TickCount64,
|
||||||
|
new PointerPointProperties(RawInputModifiers.None, PointerUpdateKind.Other),
|
||||||
|
KeyModifiers.None);
|
||||||
|
|
||||||
break;
|
RaiseEvent(evnt);
|
||||||
}
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -230,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");
|
||||||
@@ -253,7 +282,7 @@ namespace Ryujinx.Ava.UI.Renderer
|
|||||||
[SupportedOSPlatform("macos")]
|
[SupportedOSPlatform("macos")]
|
||||||
void DestroyMacOS()
|
void DestroyMacOS()
|
||||||
{
|
{
|
||||||
MetalHelper.DestroyMetalLayer(NsView, MetalLayer);
|
// TODO
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -683,6 +683,11 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
get => ConsoleHelper.SetConsoleWindowStateSupported;
|
get => ConsoleHelper.SetConsoleWindowStateSupported;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool ManageFileTypesVisible
|
||||||
|
{
|
||||||
|
get => FileAssociationHelper.IsTypeAssociationSupported;
|
||||||
|
}
|
||||||
|
|
||||||
public ObservableCollection<ApplicationData> Applications
|
public ObservableCollection<ApplicationData> Applications
|
||||||
{
|
{
|
||||||
get => _applications;
|
get => _applications;
|
||||||
|
@@ -22,6 +22,7 @@ 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 TimeZone = Ryujinx.Ava.UI.Models.TimeZone;
|
using TimeZone = Ryujinx.Ava.UI.Models.TimeZone;
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.ViewModels
|
namespace Ryujinx.Ava.UI.ViewModels
|
||||||
@@ -59,6 +60,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
OnPropertyChanged(nameof(IsCustomResolutionScaleActive));
|
OnPropertyChanged(nameof(IsCustomResolutionScaleActive));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public int GraphicsBackendMultithreadingIndex
|
public int GraphicsBackendMultithreadingIndex
|
||||||
{
|
{
|
||||||
get => _graphicsBackendMultithreadingIndex;
|
get => _graphicsBackendMultithreadingIndex;
|
||||||
@@ -106,6 +108,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 +121,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; }
|
||||||
@@ -153,6 +154,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
public bool EnableCustomTheme { get; set; }
|
public bool EnableCustomTheme { get; set; }
|
||||||
public bool IsCustomResolutionScaleActive => _resolutionScale == 4;
|
public bool IsCustomResolutionScaleActive => _resolutionScale == 4;
|
||||||
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; }
|
||||||
@@ -349,6 +351,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;
|
||||||
@@ -369,7 +372,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;
|
||||||
@@ -432,6 +435,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;
|
||||||
|
@@ -21,8 +21,9 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using SpanHelpers = LibHac.Common.SpanHelpers;
|
using System.Text;
|
||||||
using Path = System.IO.Path;
|
using Path = System.IO.Path;
|
||||||
|
using SpanHelpers = LibHac.Common.SpanHelpers;
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.ViewModels;
|
namespace Ryujinx.Ava.UI.ViewModels;
|
||||||
|
|
||||||
@@ -90,6 +91,8 @@ public class TitleUpdateViewModel : BaseModel
|
|||||||
Selected = "",
|
Selected = "",
|
||||||
Paths = new List<string>()
|
Paths = new List<string>()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Save();
|
||||||
}
|
}
|
||||||
|
|
||||||
LoadUpdates();
|
LoadUpdates();
|
||||||
@@ -102,6 +105,9 @@ public class TitleUpdateViewModel : BaseModel
|
|||||||
AddUpdate(path);
|
AddUpdate(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NOTE: Save the list again to remove leftovers.
|
||||||
|
Save();
|
||||||
|
|
||||||
TitleUpdateModel selected = TitleUpdates.FirstOrDefault(x => x.Path == _titleUpdateWindowData.Selected, null);
|
TitleUpdateModel selected = TitleUpdates.FirstOrDefault(x => x.Path == _titleUpdateWindowData.Selected, null);
|
||||||
|
|
||||||
SelectedUpdate = selected;
|
SelectedUpdate = selected;
|
||||||
@@ -223,4 +229,22 @@ public class TitleUpdateViewModel : BaseModel
|
|||||||
|
|
||||||
SortUpdates();
|
SortUpdates();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Save()
|
||||||
|
{
|
||||||
|
_titleUpdateWindowData.Paths.Clear();
|
||||||
|
_titleUpdateWindowData.Selected = "";
|
||||||
|
|
||||||
|
foreach (TitleUpdateModel update in TitleUpdates)
|
||||||
|
{
|
||||||
|
_titleUpdateWindowData.Paths.Add(update.Path);
|
||||||
|
|
||||||
|
if (update == SelectedUpdate)
|
||||||
|
{
|
||||||
|
_titleUpdateWindowData.Selected = update.Path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
File.WriteAllBytes(_titleUpdateJsonPath, Encoding.UTF8.GetBytes(JsonHelper.Serialize(_titleUpdateWindowData, true)));
|
||||||
|
}
|
||||||
}
|
}
|
@@ -77,8 +77,7 @@
|
|||||||
</MenuItem.Icon>
|
</MenuItem.Icon>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<Separator />
|
<Separator />
|
||||||
<MenuItem Name="ChangeLanguageMenuItem" Header="{locale:Locale MenuBarOptionsChangeLanguage}">
|
<MenuItem Name="ChangeLanguageMenuItem" Header="{locale:Locale MenuBarOptionsChangeLanguage}" />
|
||||||
</MenuItem>
|
|
||||||
<Separator />
|
<Separator />
|
||||||
<MenuItem
|
<MenuItem
|
||||||
Click="OpenSettings"
|
Click="OpenSettings"
|
||||||
@@ -141,6 +140,10 @@
|
|||||||
<MenuItem Command="{ReflectionBinding InstallFirmwareFromFile}" Header="{locale:Locale MenuBarFileToolsInstallFirmwareFromFile}" />
|
<MenuItem Command="{ReflectionBinding InstallFirmwareFromFile}" Header="{locale:Locale MenuBarFileToolsInstallFirmwareFromFile}" />
|
||||||
<MenuItem Command="{ReflectionBinding InstallFirmwareFromFolder}" Header="{locale:Locale MenuBarFileToolsInstallFirmwareFromDirectory}" />
|
<MenuItem Command="{ReflectionBinding InstallFirmwareFromFolder}" Header="{locale:Locale MenuBarFileToolsInstallFirmwareFromDirectory}" />
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
<MenuItem Header="{locale:Locale MenuBarToolsManageFileTypes}" IsVisible="{Binding ManageFileTypesVisible}">
|
||||||
|
<MenuItem Header="{locale:Locale MenuBarToolsInstallFileTypes}" Click="InstallFileTypes_Click"/>
|
||||||
|
<MenuItem Header="{locale:Locale MenuBarToolsUninstallFileTypes}" Click="UninstallFileTypes_Click"/>
|
||||||
|
</MenuItem>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem VerticalAlignment="Center" Header="{locale:Locale MenuBarHelp}">
|
<MenuItem VerticalAlignment="Center" Header="{locale:Locale MenuBarHelp}">
|
||||||
<MenuItem
|
<MenuItem
|
||||||
@@ -157,4 +160,4 @@
|
|||||||
</MenuItem>
|
</MenuItem>
|
||||||
</Menu>
|
</Menu>
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
|
@@ -3,6 +3,7 @@ using Avalonia.Controls;
|
|||||||
using Avalonia.Interactivity;
|
using Avalonia.Interactivity;
|
||||||
using LibHac.FsSystem;
|
using LibHac.FsSystem;
|
||||||
using LibHac.Ncm;
|
using LibHac.Ncm;
|
||||||
|
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;
|
||||||
using Ryujinx.Ava.UI.Windows;
|
using Ryujinx.Ava.UI.Windows;
|
||||||
@@ -10,6 +11,7 @@ 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.Helper;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
@@ -163,6 +165,32 @@ namespace Ryujinx.Ava.UI.Views.Main
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async void InstallFileTypes_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (FileAssociationHelper.Install())
|
||||||
|
{
|
||||||
|
await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.DialogInstallFileTypesSuccessMessage],
|
||||||
|
string.Empty, LocaleManager.Instance[LocaleKeys.InputDialogOk], string.Empty, string.Empty);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogInstallFileTypesErrorMessage]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void UninstallFileTypes_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (FileAssociationHelper.Uninstall())
|
||||||
|
{
|
||||||
|
await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.DialogUninstallFileTypesSuccessMessage],
|
||||||
|
string.Empty, LocaleManager.Instance[LocaleKeys.InputDialogOk], string.Empty, string.Empty);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogUninstallFileTypesErrorMessage]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public async void CheckForUpdates(object sender, RoutedEventArgs e)
|
public async void CheckForUpdates(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (Updater.CanUpdate(true))
|
if (Updater.CanUpdate(true))
|
||||||
|
@@ -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>
|
||||||
|
@@ -60,24 +60,7 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
|
|
||||||
public void Save(object sender, RoutedEventArgs e)
|
public void Save(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
ViewModel._titleUpdateWindowData.Paths.Clear();
|
ViewModel.Save();
|
||||||
|
|
||||||
ViewModel._titleUpdateWindowData.Selected = "";
|
|
||||||
|
|
||||||
foreach (TitleUpdateModel update in ViewModel.TitleUpdates)
|
|
||||||
{
|
|
||||||
ViewModel._titleUpdateWindowData.Paths.Add(update.Path);
|
|
||||||
|
|
||||||
if (update == ViewModel.SelectedUpdate)
|
|
||||||
{
|
|
||||||
ViewModel._titleUpdateWindowData.Selected = update.Path;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
using (FileStream titleUpdateJsonStream = File.Create(ViewModel._titleUpdateJsonPath, 4096, FileOptions.WriteThrough))
|
|
||||||
{
|
|
||||||
titleUpdateJsonStream.Write(Encoding.UTF8.GetBytes(JsonHelper.Serialize(ViewModel._titleUpdateWindowData, true)));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (VisualRoot is MainWindow window)
|
if (VisualRoot is MainWindow window)
|
||||||
{
|
{
|
||||||
|
27
Ryujinx.Cpu/AppleHv/Arm/ApFlags.cs
Normal file
27
Ryujinx.Cpu/AppleHv/Arm/ApFlags.cs
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
namespace Ryujinx.Cpu.AppleHv.Arm
|
||||||
|
{
|
||||||
|
enum ApFlags : ulong
|
||||||
|
{
|
||||||
|
ApShift = 6,
|
||||||
|
PxnShift = 53,
|
||||||
|
UxnShift = 54,
|
||||||
|
|
||||||
|
UserExecuteKernelReadWriteExecute = (0UL << (int)ApShift),
|
||||||
|
UserReadWriteExecuteKernelReadWrite = (1UL << (int)ApShift),
|
||||||
|
UserExecuteKernelReadExecute = (2UL << (int)ApShift),
|
||||||
|
UserReadExecuteKernelReadExecute = (3UL << (int)ApShift),
|
||||||
|
|
||||||
|
UserExecuteKernelReadWrite = (1UL << (int)PxnShift) | (0UL << (int)ApShift),
|
||||||
|
UserExecuteKernelRead = (1UL << (int)PxnShift) | (2UL << (int)ApShift),
|
||||||
|
UserReadExecuteKernelRead = (1UL << (int)PxnShift) | (3UL << (int)ApShift),
|
||||||
|
|
||||||
|
UserNoneKernelReadWriteExecute = (1UL << (int)UxnShift) | (0UL << (int)ApShift),
|
||||||
|
UserReadWriteKernelReadWrite = (1UL << (int)UxnShift) | (1UL << (int)ApShift),
|
||||||
|
UserNoneKernelReadExecute = (1UL << (int)UxnShift) | (2UL << (int)ApShift),
|
||||||
|
UserReadKernelReadExecute = (1UL << (int)UxnShift) | (3UL << (int)ApShift),
|
||||||
|
|
||||||
|
UserNoneKernelReadWrite = (1UL << (int)PxnShift) | (1UL << (int)UxnShift) | (0UL << (int)ApShift),
|
||||||
|
UserNoneKernelRead = (1UL << (int)PxnShift) | (1UL << (int)UxnShift) | (2UL << (int)ApShift),
|
||||||
|
UserReadKernelRead = (1UL << (int)PxnShift) | (1UL << (int)UxnShift) | (3UL << (int)ApShift)
|
||||||
|
}
|
||||||
|
}
|
47
Ryujinx.Cpu/AppleHv/Arm/ExceptionClass.cs
Normal file
47
Ryujinx.Cpu/AppleHv/Arm/ExceptionClass.cs
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
namespace Ryujinx.Cpu.AppleHv.Arm
|
||||||
|
{
|
||||||
|
enum ExceptionClass
|
||||||
|
{
|
||||||
|
Unknown = 0b000000,
|
||||||
|
TrappedWfeWfiWfetWfit = 0b000001,
|
||||||
|
TrappedMcrMrcCp15 = 0b000011,
|
||||||
|
TrappedMcrrMrrcCp15 = 0b000100,
|
||||||
|
TrappedMcrMrcCp14 = 0b000101,
|
||||||
|
TrappedLdcStc = 0b000110,
|
||||||
|
TrappedSveFpSimd = 0b000111,
|
||||||
|
TrappedVmrs = 0b001000,
|
||||||
|
TrappedPAuth = 0b001001,
|
||||||
|
TrappedLd64bSt64bSt64bvSt64bv0 = 0b001010,
|
||||||
|
TrappedMrrcCp14 = 0b001100,
|
||||||
|
IllegalExecutionState = 0b001110,
|
||||||
|
SvcAarch32 = 0b010001,
|
||||||
|
HvcAarch32 = 0b010010,
|
||||||
|
SmcAarch32 = 0b010011,
|
||||||
|
SvcAarch64 = 0b010101,
|
||||||
|
HvcAarch64 = 0b010110,
|
||||||
|
SmcAarch64 = 0b010111,
|
||||||
|
TrappedMsrMrsSystem = 0b011000,
|
||||||
|
TrappedSve = 0b011001,
|
||||||
|
TrappedEretEretaaEretab = 0b011010,
|
||||||
|
PointerAuthenticationFailure = 0b011100,
|
||||||
|
ImplementationDefinedEl3 = 0b011111,
|
||||||
|
InstructionAbortLowerEl = 0b100000,
|
||||||
|
InstructionAbortSameEl = 0b100001,
|
||||||
|
PcAlignmentFault = 0b100010,
|
||||||
|
DataAbortLowerEl = 0b100100,
|
||||||
|
DataAbortSameEl = 0b100101,
|
||||||
|
SpAlignmentFault = 0b100110,
|
||||||
|
TrappedFpExceptionAarch32 = 0b101000,
|
||||||
|
TrappedFpExceptionAarch64 = 0b101100,
|
||||||
|
SErrorInterrupt = 0b101111,
|
||||||
|
BreakpointLowerEl = 0b110000,
|
||||||
|
BreakpointSameEl = 0b110001,
|
||||||
|
SoftwareStepLowerEl = 0b110010,
|
||||||
|
SoftwareStepSameEl = 0b110011,
|
||||||
|
WatchpointLowerEl = 0b110100,
|
||||||
|
WatchpointSameEl = 0b110101,
|
||||||
|
BkptAarch32 = 0b111000,
|
||||||
|
VectorCatchAarch32 = 0b111010,
|
||||||
|
BrkAarch64 = 0b111100
|
||||||
|
}
|
||||||
|
}
|
17
Ryujinx.Cpu/AppleHv/DummyDiskCacheLoadState.cs
Normal file
17
Ryujinx.Cpu/AppleHv/DummyDiskCacheLoadState.cs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Ryujinx.Cpu.AppleHv
|
||||||
|
{
|
||||||
|
public class DummyDiskCacheLoadState : IDiskCacheLoadState
|
||||||
|
{
|
||||||
|
#pragma warning disable CS0067
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public event Action<LoadState, int, int> StateChanged;
|
||||||
|
#pragma warning restore CS0067
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void Cancel()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
129
Ryujinx.Cpu/AppleHv/HvAddressSpace.cs
Normal file
129
Ryujinx.Cpu/AppleHv/HvAddressSpace.cs
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
using Ryujinx.Cpu.AppleHv.Arm;
|
||||||
|
using Ryujinx.Memory;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Ryujinx.Cpu.AppleHv
|
||||||
|
{
|
||||||
|
class HvAddressSpace : IDisposable
|
||||||
|
{
|
||||||
|
private const ulong KernelRegionBase = unchecked((ulong)-(1L << 39));
|
||||||
|
private const ulong KernelRegionCodeOffset = 0UL;
|
||||||
|
private const ulong KernelRegionCodeSize = 0x2000UL;
|
||||||
|
private const ulong KernelRegionTlbiEretOffset = KernelRegionCodeOffset + 0x1000UL;
|
||||||
|
private const ulong KernelRegionEretOffset = KernelRegionTlbiEretOffset + 4UL;
|
||||||
|
|
||||||
|
public const ulong KernelRegionEretAddress = KernelRegionBase + KernelRegionEretOffset;
|
||||||
|
public const ulong KernelRegionTlbiEretAddress = KernelRegionBase + KernelRegionTlbiEretOffset;
|
||||||
|
|
||||||
|
private const ulong AllocationGranule = 1UL << 14;
|
||||||
|
|
||||||
|
private readonly ulong _asBase;
|
||||||
|
private readonly ulong _asSize;
|
||||||
|
private readonly ulong _backingSize;
|
||||||
|
|
||||||
|
private readonly HvAddressSpaceRange _userRange;
|
||||||
|
private readonly HvAddressSpaceRange _kernelRange;
|
||||||
|
|
||||||
|
private MemoryBlock _kernelCodeBlock;
|
||||||
|
|
||||||
|
public HvAddressSpace(MemoryBlock backingMemory, ulong asSize)
|
||||||
|
{
|
||||||
|
(_asBase, var ipaAllocator) = HvVm.CreateAddressSpace(backingMemory);
|
||||||
|
_asSize = asSize;
|
||||||
|
_backingSize = backingMemory.Size;
|
||||||
|
|
||||||
|
_userRange = new HvAddressSpaceRange(ipaAllocator);
|
||||||
|
_kernelRange = new HvAddressSpaceRange(ipaAllocator);
|
||||||
|
|
||||||
|
_kernelCodeBlock = new MemoryBlock(AllocationGranule);
|
||||||
|
|
||||||
|
InitializeKernelCode(ipaAllocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeKernelCode(HvIpaAllocator ipaAllocator)
|
||||||
|
{
|
||||||
|
// Write exception handlers.
|
||||||
|
for (ulong offset = 0; offset < 0x800; offset += 0x80)
|
||||||
|
{
|
||||||
|
// Offsets:
|
||||||
|
// 0x0: Synchronous
|
||||||
|
// 0x80: IRQ
|
||||||
|
// 0x100: FIQ
|
||||||
|
// 0x180: SError
|
||||||
|
_kernelCodeBlock.Write(KernelRegionCodeOffset + offset, 0xD41FFFE2u); // HVC #0xFFFF
|
||||||
|
_kernelCodeBlock.Write(KernelRegionCodeOffset + offset + 4, 0xD69F03E0u); // ERET
|
||||||
|
}
|
||||||
|
|
||||||
|
_kernelCodeBlock.Write(KernelRegionTlbiEretOffset, 0xD508831Fu); // TLBI VMALLE1IS
|
||||||
|
_kernelCodeBlock.Write(KernelRegionEretOffset, 0xD69F03E0u); // ERET
|
||||||
|
|
||||||
|
ulong kernelCodePa = ipaAllocator.Allocate(AllocationGranule);
|
||||||
|
HvApi.hv_vm_map((ulong)_kernelCodeBlock.Pointer, kernelCodePa, AllocationGranule, hv_memory_flags_t.HV_MEMORY_READ | hv_memory_flags_t.HV_MEMORY_EXEC).ThrowOnError();
|
||||||
|
|
||||||
|
_kernelRange.Map(KernelRegionCodeOffset, kernelCodePa, KernelRegionCodeSize, ApFlags.UserNoneKernelReadExecute);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void InitializeMmu(ulong vcpu)
|
||||||
|
{
|
||||||
|
HvApi.hv_vcpu_set_sys_reg(vcpu, hv_sys_reg_t.HV_SYS_REG_VBAR_EL1, KernelRegionBase + KernelRegionCodeOffset);
|
||||||
|
|
||||||
|
HvApi.hv_vcpu_set_sys_reg(vcpu, hv_sys_reg_t.HV_SYS_REG_TTBR0_EL1, _userRange.GetIpaBase());
|
||||||
|
HvApi.hv_vcpu_set_sys_reg(vcpu, hv_sys_reg_t.HV_SYS_REG_TTBR1_EL1, _kernelRange.GetIpaBase());
|
||||||
|
HvApi.hv_vcpu_set_sys_reg(vcpu, hv_sys_reg_t.HV_SYS_REG_MAIR_EL1, 0xffUL);
|
||||||
|
HvApi.hv_vcpu_set_sys_reg(vcpu, hv_sys_reg_t.HV_SYS_REG_TCR_EL1, 0x00000011B5193519UL);
|
||||||
|
HvApi.hv_vcpu_set_sys_reg(vcpu, hv_sys_reg_t.HV_SYS_REG_SCTLR_EL1, 0x0000000034D5D925UL);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool GetAndClearUserTlbInvalidationPending()
|
||||||
|
{
|
||||||
|
return _userRange.GetAndClearTlbInvalidationPending();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void MapUser(ulong va, ulong pa, ulong size, MemoryPermission permission)
|
||||||
|
{
|
||||||
|
pa += _asBase;
|
||||||
|
|
||||||
|
lock (_userRange)
|
||||||
|
{
|
||||||
|
_userRange.Map(va, pa, size, GetApFlags(permission));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UnmapUser(ulong va, ulong size)
|
||||||
|
{
|
||||||
|
lock (_userRange)
|
||||||
|
{
|
||||||
|
_userRange.Unmap(va, size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ReprotectUser(ulong va, ulong size, MemoryPermission permission)
|
||||||
|
{
|
||||||
|
lock (_userRange)
|
||||||
|
{
|
||||||
|
_userRange.Reprotect(va, size, GetApFlags(permission));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ApFlags GetApFlags(MemoryPermission permission)
|
||||||
|
{
|
||||||
|
return permission switch
|
||||||
|
{
|
||||||
|
MemoryPermission.None => ApFlags.UserNoneKernelRead,
|
||||||
|
MemoryPermission.Execute => ApFlags.UserExecuteKernelRead,
|
||||||
|
MemoryPermission.Read => ApFlags.UserReadKernelRead,
|
||||||
|
MemoryPermission.ReadAndWrite => ApFlags.UserReadWriteKernelReadWrite,
|
||||||
|
MemoryPermission.ReadAndExecute => ApFlags.UserReadExecuteKernelRead,
|
||||||
|
MemoryPermission.ReadWriteExecute => ApFlags.UserReadWriteExecuteKernelReadWrite,
|
||||||
|
_ => throw new ArgumentException($"Permission \"{permission}\" is invalid.")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_userRange.Dispose();
|
||||||
|
_kernelRange.Dispose();
|
||||||
|
HvVm.DestroyAddressSpace(_asBase, _backingSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
370
Ryujinx.Cpu/AppleHv/HvAddressSpaceRange.cs
Normal file
370
Ryujinx.Cpu/AppleHv/HvAddressSpaceRange.cs
Normal file
@@ -0,0 +1,370 @@
|
|||||||
|
using Ryujinx.Cpu.AppleHv.Arm;
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
|
namespace Ryujinx.Cpu.AppleHv
|
||||||
|
{
|
||||||
|
class HvAddressSpaceRange : IDisposable
|
||||||
|
{
|
||||||
|
private const ulong AllocationGranule = 1UL << 14;
|
||||||
|
|
||||||
|
private const ulong AttributesMask = (0x3ffUL << 2) | (0x3fffUL << 50);
|
||||||
|
|
||||||
|
private const ulong BaseAttributes = (1UL << 10) | (3UL << 8); // Access flag set, inner shareable.
|
||||||
|
|
||||||
|
private const int LevelBits = 9;
|
||||||
|
private const int LevelCount = 1 << LevelBits;
|
||||||
|
private const int LevelMask = LevelCount - 1;
|
||||||
|
private const int PageBits = 12;
|
||||||
|
private const int PageSize = 1 << PageBits;
|
||||||
|
private const int PageMask = PageSize - 1;
|
||||||
|
private const int AllLevelsMask = PageMask | (LevelMask << PageBits) | (LevelMask << (PageBits + LevelBits));
|
||||||
|
|
||||||
|
private class PtLevel
|
||||||
|
{
|
||||||
|
public ulong Address => Allocation.Ipa + Allocation.Offset;
|
||||||
|
public int EntriesCount;
|
||||||
|
public readonly HvMemoryBlockAllocation Allocation;
|
||||||
|
public readonly PtLevel[] Next;
|
||||||
|
|
||||||
|
public PtLevel(HvMemoryBlockAllocator blockAllocator, int count, bool hasNext)
|
||||||
|
{
|
||||||
|
ulong size = (ulong)count * sizeof(ulong);
|
||||||
|
Allocation = blockAllocator.Allocate(size, PageSize);
|
||||||
|
|
||||||
|
AsSpan().Fill(0UL);
|
||||||
|
|
||||||
|
if (hasNext)
|
||||||
|
{
|
||||||
|
Next = new PtLevel[count];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe Span<ulong> AsSpan()
|
||||||
|
{
|
||||||
|
return MemoryMarshal.Cast<byte, ulong>(Allocation.Memory.GetSpan(Allocation.Offset, (int)Allocation.Size));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private PtLevel _level0;
|
||||||
|
|
||||||
|
private int _tlbInvalidationPending;
|
||||||
|
|
||||||
|
private readonly HvIpaAllocator _ipaAllocator;
|
||||||
|
private readonly HvMemoryBlockAllocator _blockAllocator;
|
||||||
|
|
||||||
|
public HvAddressSpaceRange(HvIpaAllocator ipaAllocator)
|
||||||
|
{
|
||||||
|
_ipaAllocator = ipaAllocator;
|
||||||
|
_blockAllocator = new HvMemoryBlockAllocator(ipaAllocator, (int)AllocationGranule);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ulong GetIpaBase()
|
||||||
|
{
|
||||||
|
return EnsureLevel0().Address;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool GetAndClearTlbInvalidationPending()
|
||||||
|
{
|
||||||
|
return Interlocked.Exchange(ref _tlbInvalidationPending, 0) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Map(ulong va, ulong pa, ulong size, ApFlags accessPermission)
|
||||||
|
{
|
||||||
|
MapImpl(va, pa, size, (ulong)accessPermission | BaseAttributes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Unmap(ulong va, ulong size)
|
||||||
|
{
|
||||||
|
UnmapImpl(EnsureLevel0(), 0, va, size);
|
||||||
|
Interlocked.Exchange(ref _tlbInvalidationPending, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Reprotect(ulong va, ulong size, ApFlags accessPermission)
|
||||||
|
{
|
||||||
|
UpdateAttributes(va, size, (ulong)accessPermission | BaseAttributes);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void MapImpl(ulong va, ulong pa, ulong size, ulong attr)
|
||||||
|
{
|
||||||
|
PtLevel level0 = EnsureLevel0();
|
||||||
|
|
||||||
|
ulong endVa = va + size;
|
||||||
|
|
||||||
|
while (va < endVa)
|
||||||
|
{
|
||||||
|
(ulong mapSize, int depth) = GetMapSizeAndDepth(va, pa, endVa);
|
||||||
|
|
||||||
|
PtLevel currentLevel = level0;
|
||||||
|
|
||||||
|
for (int i = 0; i < depth; i++)
|
||||||
|
{
|
||||||
|
int l = (int)(va >> (PageBits + (2 - i) * LevelBits)) & LevelMask;
|
||||||
|
EnsureTable(currentLevel, l, i == 0);
|
||||||
|
currentLevel = currentLevel.Next[l];
|
||||||
|
}
|
||||||
|
|
||||||
|
(ulong blockSize, int blockShift) = GetBlockSizeAndShift(depth);
|
||||||
|
|
||||||
|
for (ulong i = 0; i < mapSize; i += blockSize)
|
||||||
|
{
|
||||||
|
if ((va >> blockShift) << blockShift != va ||
|
||||||
|
(pa >> blockShift) << blockShift != pa)
|
||||||
|
{
|
||||||
|
Debug.Fail($"Block size 0x{blockSize:X} (log2: {blockShift}) is invalid for VA 0x{va:X} or PA 0x{pa:X}.");
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteBlock(currentLevel, (int)(va >> blockShift) & LevelMask, depth, pa, attr);
|
||||||
|
|
||||||
|
va += blockSize;
|
||||||
|
pa += blockSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UnmapImpl(PtLevel level, int depth, ulong va, ulong size)
|
||||||
|
{
|
||||||
|
ulong endVa = (va + size + PageMask) & ~((ulong)PageMask);
|
||||||
|
va &= ~((ulong)PageMask);
|
||||||
|
|
||||||
|
(ulong blockSize, int blockShift) = GetBlockSizeAndShift(depth);
|
||||||
|
|
||||||
|
while (va < endVa)
|
||||||
|
{
|
||||||
|
ulong nextEntryVa = GetNextAddress(va, blockSize);
|
||||||
|
ulong chunckSize = Math.Min(endVa - va, nextEntryVa - va);
|
||||||
|
|
||||||
|
int l = (int)(va >> (PageBits + (2 - depth) * LevelBits)) & LevelMask;
|
||||||
|
|
||||||
|
PtLevel nextTable = level.Next != null ? level.Next[l] : null;
|
||||||
|
|
||||||
|
if (nextTable != null)
|
||||||
|
{
|
||||||
|
// Entry is a table, visit it and update attributes as required.
|
||||||
|
UnmapImpl(nextTable, depth + 1, va, chunckSize);
|
||||||
|
}
|
||||||
|
else if (chunckSize != blockSize)
|
||||||
|
{
|
||||||
|
// Entry is a block but is not aligned, we need to turn it into a table.
|
||||||
|
ref ulong pte = ref level.AsSpan()[l];
|
||||||
|
nextTable = CreateTable(pte, depth + 1);
|
||||||
|
level.Next[l] = nextTable;
|
||||||
|
|
||||||
|
// Now that we have a table, we can handle it like the first case.
|
||||||
|
UnmapImpl(nextTable, depth + 1, va, chunckSize);
|
||||||
|
|
||||||
|
// Update PTE to point to the new table.
|
||||||
|
pte = (nextTable.Address & ~(ulong)PageMask) | 3UL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If entry is a block, or if entry is a table but it is empty, we can remove it.
|
||||||
|
if (nextTable == null || nextTable.EntriesCount == 0)
|
||||||
|
{
|
||||||
|
// Entry is a block and is fully aligned, so we can just set it to 0.
|
||||||
|
if (nextTable != null)
|
||||||
|
{
|
||||||
|
nextTable.Allocation.Dispose();
|
||||||
|
level.Next[l] = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
level.AsSpan()[l] = 0UL;
|
||||||
|
level.EntriesCount--;
|
||||||
|
ValidateEntriesCount(level.EntriesCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
va += chunckSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateAttributes(ulong va, ulong size, ulong newAttr)
|
||||||
|
{
|
||||||
|
UpdateAttributes(EnsureLevel0(), 0, va, size, newAttr);
|
||||||
|
|
||||||
|
Interlocked.Exchange(ref _tlbInvalidationPending, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateAttributes(PtLevel level, int depth, ulong va, ulong size, ulong newAttr)
|
||||||
|
{
|
||||||
|
ulong endVa = (va + size + PageSize - 1) & ~((ulong)PageSize - 1);
|
||||||
|
va &= ~((ulong)PageSize - 1);
|
||||||
|
|
||||||
|
(ulong blockSize, int blockShift) = GetBlockSizeAndShift(depth);
|
||||||
|
|
||||||
|
while (va < endVa)
|
||||||
|
{
|
||||||
|
ulong nextEntryVa = GetNextAddress(va, blockSize);
|
||||||
|
ulong chunckSize = Math.Min(endVa - va, nextEntryVa - va);
|
||||||
|
|
||||||
|
int l = (int)(va >> (PageBits + (2 - depth) * LevelBits)) & LevelMask;
|
||||||
|
|
||||||
|
ref ulong pte = ref level.AsSpan()[l];
|
||||||
|
|
||||||
|
// First check if the region is mapped.
|
||||||
|
if ((pte & 3) != 0)
|
||||||
|
{
|
||||||
|
PtLevel nextTable = level.Next != null ? level.Next[l] : null;
|
||||||
|
|
||||||
|
if (nextTable != null)
|
||||||
|
{
|
||||||
|
// Entry is a table, visit it and update attributes as required.
|
||||||
|
UpdateAttributes(nextTable, depth + 1, va, chunckSize, newAttr);
|
||||||
|
}
|
||||||
|
else if (chunckSize != blockSize)
|
||||||
|
{
|
||||||
|
// Entry is a block but is not aligned, we need to turn it into a table.
|
||||||
|
nextTable = CreateTable(pte, depth + 1);
|
||||||
|
level.Next[l] = nextTable;
|
||||||
|
|
||||||
|
// Now that we have a table, we can handle it like the first case.
|
||||||
|
UpdateAttributes(nextTable, depth + 1, va, chunckSize, newAttr);
|
||||||
|
|
||||||
|
// Update PTE to point to the new table.
|
||||||
|
pte = (nextTable.Address & ~(ulong)PageMask) | 3UL;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Entry is a block and is fully aligned, so we can just update the attributes.
|
||||||
|
// Update PTE with the new attributes.
|
||||||
|
pte = (pte & ~AttributesMask) | newAttr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
va += chunckSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private PtLevel CreateTable(ulong pte, int depth)
|
||||||
|
{
|
||||||
|
pte &= ~3UL;
|
||||||
|
pte |= (depth == 2 ? 3UL : 1UL);
|
||||||
|
|
||||||
|
PtLevel level = new PtLevel(_blockAllocator, LevelCount, depth < 2);
|
||||||
|
Span<ulong> currentLevel = level.AsSpan();
|
||||||
|
|
||||||
|
(ulong blockSize, int blockShift) = GetBlockSizeAndShift(depth);
|
||||||
|
|
||||||
|
// Fill in the blocks.
|
||||||
|
for (int i = 0; i < LevelCount; i++)
|
||||||
|
{
|
||||||
|
ulong offset = (ulong)i << blockShift;
|
||||||
|
currentLevel[i] = pte + offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
level.EntriesCount = LevelCount;
|
||||||
|
|
||||||
|
return level;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static (ulong, int) GetBlockSizeAndShift(int depth)
|
||||||
|
{
|
||||||
|
int blockShift = PageBits + (2 - depth) * LevelBits;
|
||||||
|
ulong blockSize = 1UL << blockShift;
|
||||||
|
|
||||||
|
return (blockSize, blockShift);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static (ulong, int) GetMapSizeAndDepth(ulong va, ulong pa, ulong endVa)
|
||||||
|
{
|
||||||
|
// Both virtual and physical addresses must be aligned to the block size.
|
||||||
|
ulong combinedAddress = va | pa;
|
||||||
|
|
||||||
|
ulong l0Alignment = 1UL << (PageBits + LevelBits * 2);
|
||||||
|
ulong l1Alignment = 1UL << (PageBits + LevelBits);
|
||||||
|
|
||||||
|
if ((combinedAddress & (l0Alignment - 1)) == 0 && AlignDown(endVa, l0Alignment) > va)
|
||||||
|
{
|
||||||
|
return (AlignDown(endVa, l0Alignment) - va, 0);
|
||||||
|
}
|
||||||
|
else if ((combinedAddress & (l1Alignment - 1)) == 0 && AlignDown(endVa, l1Alignment) > va)
|
||||||
|
{
|
||||||
|
ulong nextOrderVa = GetNextAddress(va, l0Alignment);
|
||||||
|
|
||||||
|
if (nextOrderVa <= endVa)
|
||||||
|
{
|
||||||
|
return (nextOrderVa - va, 1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return (AlignDown(endVa, l1Alignment) - va, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ulong nextOrderVa = GetNextAddress(va, l1Alignment);
|
||||||
|
|
||||||
|
if (nextOrderVa <= endVa)
|
||||||
|
{
|
||||||
|
return (nextOrderVa - va, 2);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return (endVa - va, 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ulong AlignDown(ulong va, ulong alignment)
|
||||||
|
{
|
||||||
|
return va & ~(alignment - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ulong GetNextAddress(ulong va, ulong alignment)
|
||||||
|
{
|
||||||
|
return (va + alignment) & ~(alignment - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private PtLevel EnsureLevel0()
|
||||||
|
{
|
||||||
|
PtLevel level0 = _level0;
|
||||||
|
|
||||||
|
if (level0 == null)
|
||||||
|
{
|
||||||
|
level0 = new PtLevel(_blockAllocator, LevelCount, true);
|
||||||
|
_level0 = level0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return level0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EnsureTable(PtLevel level, int index, bool hasNext)
|
||||||
|
{
|
||||||
|
Span<ulong> currentTable = level.AsSpan();
|
||||||
|
|
||||||
|
if ((currentTable[index] & 1) == 0)
|
||||||
|
{
|
||||||
|
PtLevel nextLevel = new PtLevel(_blockAllocator, LevelCount, hasNext);
|
||||||
|
|
||||||
|
currentTable[index] = (nextLevel.Address & ~(ulong)PageMask) | 3UL;
|
||||||
|
level.Next[index] = nextLevel;
|
||||||
|
level.EntriesCount++;
|
||||||
|
ValidateEntriesCount(level.EntriesCount);
|
||||||
|
}
|
||||||
|
else if (level.Next[index] == null)
|
||||||
|
{
|
||||||
|
Debug.Fail($"Index {index} is block, expected a table.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteBlock(PtLevel level, int index, int depth, ulong pa, ulong attr)
|
||||||
|
{
|
||||||
|
Span<ulong> currentTable = level.AsSpan();
|
||||||
|
|
||||||
|
currentTable[index] = (pa & ~((ulong)AllLevelsMask >> (depth * LevelBits))) | (depth == 2 ? 3UL : 1UL) | attr;
|
||||||
|
|
||||||
|
level.EntriesCount++;
|
||||||
|
ValidateEntriesCount(level.EntriesCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ValidateEntriesCount(int count)
|
||||||
|
{
|
||||||
|
Debug.Assert(count >= 0 && count <= LevelCount, $"Entries count {count} is invalid.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_blockAllocator.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
320
Ryujinx.Cpu/AppleHv/HvApi.cs
Normal file
320
Ryujinx.Cpu/AppleHv/HvApi.cs
Normal file
@@ -0,0 +1,320 @@
|
|||||||
|
using ARMeilleure.State;
|
||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Ryujinx.Cpu.AppleHv
|
||||||
|
{
|
||||||
|
struct hv_vcpu_exit_exception_t
|
||||||
|
{
|
||||||
|
#pragma warning disable CS0649
|
||||||
|
public ulong syndrome;
|
||||||
|
public ulong virtual_address;
|
||||||
|
public ulong physical_address;
|
||||||
|
#pragma warning restore CS0649
|
||||||
|
}
|
||||||
|
|
||||||
|
struct hv_vcpu_exit_t
|
||||||
|
{
|
||||||
|
#pragma warning disable CS0649
|
||||||
|
public uint reason;
|
||||||
|
public hv_vcpu_exit_exception_t exception;
|
||||||
|
#pragma warning restore CS0649
|
||||||
|
}
|
||||||
|
|
||||||
|
enum hv_reg_t : uint
|
||||||
|
{
|
||||||
|
HV_REG_X0,
|
||||||
|
HV_REG_X1,
|
||||||
|
HV_REG_X2,
|
||||||
|
HV_REG_X3,
|
||||||
|
HV_REG_X4,
|
||||||
|
HV_REG_X5,
|
||||||
|
HV_REG_X6,
|
||||||
|
HV_REG_X7,
|
||||||
|
HV_REG_X8,
|
||||||
|
HV_REG_X9,
|
||||||
|
HV_REG_X10,
|
||||||
|
HV_REG_X11,
|
||||||
|
HV_REG_X12,
|
||||||
|
HV_REG_X13,
|
||||||
|
HV_REG_X14,
|
||||||
|
HV_REG_X15,
|
||||||
|
HV_REG_X16,
|
||||||
|
HV_REG_X17,
|
||||||
|
HV_REG_X18,
|
||||||
|
HV_REG_X19,
|
||||||
|
HV_REG_X20,
|
||||||
|
HV_REG_X21,
|
||||||
|
HV_REG_X22,
|
||||||
|
HV_REG_X23,
|
||||||
|
HV_REG_X24,
|
||||||
|
HV_REG_X25,
|
||||||
|
HV_REG_X26,
|
||||||
|
HV_REG_X27,
|
||||||
|
HV_REG_X28,
|
||||||
|
HV_REG_X29,
|
||||||
|
HV_REG_FP = HV_REG_X29,
|
||||||
|
HV_REG_X30,
|
||||||
|
HV_REG_LR = HV_REG_X30,
|
||||||
|
HV_REG_PC,
|
||||||
|
HV_REG_FPCR,
|
||||||
|
HV_REG_FPSR,
|
||||||
|
HV_REG_CPSR,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum hv_simd_fp_reg_t : uint
|
||||||
|
{
|
||||||
|
HV_SIMD_FP_REG_Q0,
|
||||||
|
HV_SIMD_FP_REG_Q1,
|
||||||
|
HV_SIMD_FP_REG_Q2,
|
||||||
|
HV_SIMD_FP_REG_Q3,
|
||||||
|
HV_SIMD_FP_REG_Q4,
|
||||||
|
HV_SIMD_FP_REG_Q5,
|
||||||
|
HV_SIMD_FP_REG_Q6,
|
||||||
|
HV_SIMD_FP_REG_Q7,
|
||||||
|
HV_SIMD_FP_REG_Q8,
|
||||||
|
HV_SIMD_FP_REG_Q9,
|
||||||
|
HV_SIMD_FP_REG_Q10,
|
||||||
|
HV_SIMD_FP_REG_Q11,
|
||||||
|
HV_SIMD_FP_REG_Q12,
|
||||||
|
HV_SIMD_FP_REG_Q13,
|
||||||
|
HV_SIMD_FP_REG_Q14,
|
||||||
|
HV_SIMD_FP_REG_Q15,
|
||||||
|
HV_SIMD_FP_REG_Q16,
|
||||||
|
HV_SIMD_FP_REG_Q17,
|
||||||
|
HV_SIMD_FP_REG_Q18,
|
||||||
|
HV_SIMD_FP_REG_Q19,
|
||||||
|
HV_SIMD_FP_REG_Q20,
|
||||||
|
HV_SIMD_FP_REG_Q21,
|
||||||
|
HV_SIMD_FP_REG_Q22,
|
||||||
|
HV_SIMD_FP_REG_Q23,
|
||||||
|
HV_SIMD_FP_REG_Q24,
|
||||||
|
HV_SIMD_FP_REG_Q25,
|
||||||
|
HV_SIMD_FP_REG_Q26,
|
||||||
|
HV_SIMD_FP_REG_Q27,
|
||||||
|
HV_SIMD_FP_REG_Q28,
|
||||||
|
HV_SIMD_FP_REG_Q29,
|
||||||
|
HV_SIMD_FP_REG_Q30,
|
||||||
|
HV_SIMD_FP_REG_Q31,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum hv_sys_reg_t : ushort
|
||||||
|
{
|
||||||
|
HV_SYS_REG_DBGBVR0_EL1 = 0x8004,
|
||||||
|
HV_SYS_REG_DBGBCR0_EL1 = 0x8005,
|
||||||
|
HV_SYS_REG_DBGWVR0_EL1 = 0x8006,
|
||||||
|
HV_SYS_REG_DBGWCR0_EL1 = 0x8007,
|
||||||
|
HV_SYS_REG_DBGBVR1_EL1 = 0x800c,
|
||||||
|
HV_SYS_REG_DBGBCR1_EL1 = 0x800d,
|
||||||
|
HV_SYS_REG_DBGWVR1_EL1 = 0x800e,
|
||||||
|
HV_SYS_REG_DBGWCR1_EL1 = 0x800f,
|
||||||
|
HV_SYS_REG_MDCCINT_EL1 = 0x8010,
|
||||||
|
HV_SYS_REG_MDSCR_EL1 = 0x8012,
|
||||||
|
HV_SYS_REG_DBGBVR2_EL1 = 0x8014,
|
||||||
|
HV_SYS_REG_DBGBCR2_EL1 = 0x8015,
|
||||||
|
HV_SYS_REG_DBGWVR2_EL1 = 0x8016,
|
||||||
|
HV_SYS_REG_DBGWCR2_EL1 = 0x8017,
|
||||||
|
HV_SYS_REG_DBGBVR3_EL1 = 0x801c,
|
||||||
|
HV_SYS_REG_DBGBCR3_EL1 = 0x801d,
|
||||||
|
HV_SYS_REG_DBGWVR3_EL1 = 0x801e,
|
||||||
|
HV_SYS_REG_DBGWCR3_EL1 = 0x801f,
|
||||||
|
HV_SYS_REG_DBGBVR4_EL1 = 0x8024,
|
||||||
|
HV_SYS_REG_DBGBCR4_EL1 = 0x8025,
|
||||||
|
HV_SYS_REG_DBGWVR4_EL1 = 0x8026,
|
||||||
|
HV_SYS_REG_DBGWCR4_EL1 = 0x8027,
|
||||||
|
HV_SYS_REG_DBGBVR5_EL1 = 0x802c,
|
||||||
|
HV_SYS_REG_DBGBCR5_EL1 = 0x802d,
|
||||||
|
HV_SYS_REG_DBGWVR5_EL1 = 0x802e,
|
||||||
|
HV_SYS_REG_DBGWCR5_EL1 = 0x802f,
|
||||||
|
HV_SYS_REG_DBGBVR6_EL1 = 0x8034,
|
||||||
|
HV_SYS_REG_DBGBCR6_EL1 = 0x8035,
|
||||||
|
HV_SYS_REG_DBGWVR6_EL1 = 0x8036,
|
||||||
|
HV_SYS_REG_DBGWCR6_EL1 = 0x8037,
|
||||||
|
HV_SYS_REG_DBGBVR7_EL1 = 0x803c,
|
||||||
|
HV_SYS_REG_DBGBCR7_EL1 = 0x803d,
|
||||||
|
HV_SYS_REG_DBGWVR7_EL1 = 0x803e,
|
||||||
|
HV_SYS_REG_DBGWCR7_EL1 = 0x803f,
|
||||||
|
HV_SYS_REG_DBGBVR8_EL1 = 0x8044,
|
||||||
|
HV_SYS_REG_DBGBCR8_EL1 = 0x8045,
|
||||||
|
HV_SYS_REG_DBGWVR8_EL1 = 0x8046,
|
||||||
|
HV_SYS_REG_DBGWCR8_EL1 = 0x8047,
|
||||||
|
HV_SYS_REG_DBGBVR9_EL1 = 0x804c,
|
||||||
|
HV_SYS_REG_DBGBCR9_EL1 = 0x804d,
|
||||||
|
HV_SYS_REG_DBGWVR9_EL1 = 0x804e,
|
||||||
|
HV_SYS_REG_DBGWCR9_EL1 = 0x804f,
|
||||||
|
HV_SYS_REG_DBGBVR10_EL1 = 0x8054,
|
||||||
|
HV_SYS_REG_DBGBCR10_EL1 = 0x8055,
|
||||||
|
HV_SYS_REG_DBGWVR10_EL1 = 0x8056,
|
||||||
|
HV_SYS_REG_DBGWCR10_EL1 = 0x8057,
|
||||||
|
HV_SYS_REG_DBGBVR11_EL1 = 0x805c,
|
||||||
|
HV_SYS_REG_DBGBCR11_EL1 = 0x805d,
|
||||||
|
HV_SYS_REG_DBGWVR11_EL1 = 0x805e,
|
||||||
|
HV_SYS_REG_DBGWCR11_EL1 = 0x805f,
|
||||||
|
HV_SYS_REG_DBGBVR12_EL1 = 0x8064,
|
||||||
|
HV_SYS_REG_DBGBCR12_EL1 = 0x8065,
|
||||||
|
HV_SYS_REG_DBGWVR12_EL1 = 0x8066,
|
||||||
|
HV_SYS_REG_DBGWCR12_EL1 = 0x8067,
|
||||||
|
HV_SYS_REG_DBGBVR13_EL1 = 0x806c,
|
||||||
|
HV_SYS_REG_DBGBCR13_EL1 = 0x806d,
|
||||||
|
HV_SYS_REG_DBGWVR13_EL1 = 0x806e,
|
||||||
|
HV_SYS_REG_DBGWCR13_EL1 = 0x806f,
|
||||||
|
HV_SYS_REG_DBGBVR14_EL1 = 0x8074,
|
||||||
|
HV_SYS_REG_DBGBCR14_EL1 = 0x8075,
|
||||||
|
HV_SYS_REG_DBGWVR14_EL1 = 0x8076,
|
||||||
|
HV_SYS_REG_DBGWCR14_EL1 = 0x8077,
|
||||||
|
HV_SYS_REG_DBGBVR15_EL1 = 0x807c,
|
||||||
|
HV_SYS_REG_DBGBCR15_EL1 = 0x807d,
|
||||||
|
HV_SYS_REG_DBGWVR15_EL1 = 0x807e,
|
||||||
|
HV_SYS_REG_DBGWCR15_EL1 = 0x807f,
|
||||||
|
HV_SYS_REG_MIDR_EL1 = 0xc000,
|
||||||
|
HV_SYS_REG_MPIDR_EL1 = 0xc005,
|
||||||
|
HV_SYS_REG_ID_AA64PFR0_EL1 = 0xc020,
|
||||||
|
HV_SYS_REG_ID_AA64PFR1_EL1 = 0xc021,
|
||||||
|
HV_SYS_REG_ID_AA64DFR0_EL1 = 0xc028,
|
||||||
|
HV_SYS_REG_ID_AA64DFR1_EL1 = 0xc029,
|
||||||
|
HV_SYS_REG_ID_AA64ISAR0_EL1 = 0xc030,
|
||||||
|
HV_SYS_REG_ID_AA64ISAR1_EL1 = 0xc031,
|
||||||
|
HV_SYS_REG_ID_AA64MMFR0_EL1 = 0xc038,
|
||||||
|
HV_SYS_REG_ID_AA64MMFR1_EL1 = 0xc039,
|
||||||
|
HV_SYS_REG_ID_AA64MMFR2_EL1 = 0xc03a,
|
||||||
|
HV_SYS_REG_SCTLR_EL1 = 0xc080,
|
||||||
|
HV_SYS_REG_CPACR_EL1 = 0xc082,
|
||||||
|
HV_SYS_REG_TTBR0_EL1 = 0xc100,
|
||||||
|
HV_SYS_REG_TTBR1_EL1 = 0xc101,
|
||||||
|
HV_SYS_REG_TCR_EL1 = 0xc102,
|
||||||
|
HV_SYS_REG_APIAKEYLO_EL1 = 0xc108,
|
||||||
|
HV_SYS_REG_APIAKEYHI_EL1 = 0xc109,
|
||||||
|
HV_SYS_REG_APIBKEYLO_EL1 = 0xc10a,
|
||||||
|
HV_SYS_REG_APIBKEYHI_EL1 = 0xc10b,
|
||||||
|
HV_SYS_REG_APDAKEYLO_EL1 = 0xc110,
|
||||||
|
HV_SYS_REG_APDAKEYHI_EL1 = 0xc111,
|
||||||
|
HV_SYS_REG_APDBKEYLO_EL1 = 0xc112,
|
||||||
|
HV_SYS_REG_APDBKEYHI_EL1 = 0xc113,
|
||||||
|
HV_SYS_REG_APGAKEYLO_EL1 = 0xc118,
|
||||||
|
HV_SYS_REG_APGAKEYHI_EL1 = 0xc119,
|
||||||
|
HV_SYS_REG_SPSR_EL1 = 0xc200,
|
||||||
|
HV_SYS_REG_ELR_EL1 = 0xc201,
|
||||||
|
HV_SYS_REG_SP_EL0 = 0xc208,
|
||||||
|
HV_SYS_REG_AFSR0_EL1 = 0xc288,
|
||||||
|
HV_SYS_REG_AFSR1_EL1 = 0xc289,
|
||||||
|
HV_SYS_REG_ESR_EL1 = 0xc290,
|
||||||
|
HV_SYS_REG_FAR_EL1 = 0xc300,
|
||||||
|
HV_SYS_REG_PAR_EL1 = 0xc3a0,
|
||||||
|
HV_SYS_REG_MAIR_EL1 = 0xc510,
|
||||||
|
HV_SYS_REG_AMAIR_EL1 = 0xc518,
|
||||||
|
HV_SYS_REG_VBAR_EL1 = 0xc600,
|
||||||
|
HV_SYS_REG_CONTEXTIDR_EL1 = 0xc681,
|
||||||
|
HV_SYS_REG_TPIDR_EL1 = 0xc684,
|
||||||
|
HV_SYS_REG_CNTKCTL_EL1 = 0xc708,
|
||||||
|
HV_SYS_REG_CSSELR_EL1 = 0xd000,
|
||||||
|
HV_SYS_REG_TPIDR_EL0 = 0xde82,
|
||||||
|
HV_SYS_REG_TPIDRRO_EL0 = 0xde83,
|
||||||
|
HV_SYS_REG_CNTV_CTL_EL0 = 0xdf19,
|
||||||
|
HV_SYS_REG_CNTV_CVAL_EL0 = 0xdf1a,
|
||||||
|
HV_SYS_REG_SP_EL1 = 0xe208,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum hv_memory_flags_t : ulong
|
||||||
|
{
|
||||||
|
HV_MEMORY_READ = 1UL << 0,
|
||||||
|
HV_MEMORY_WRITE = 1UL << 1,
|
||||||
|
HV_MEMORY_EXEC = 1UL << 2
|
||||||
|
}
|
||||||
|
|
||||||
|
enum hv_result_t : uint
|
||||||
|
{
|
||||||
|
HV_SUCCESS = 0,
|
||||||
|
HV_ERROR = 0xfae94001,
|
||||||
|
HV_BUSY = 0xfae94002,
|
||||||
|
HV_BAD_ARGUMENT = 0xfae94003,
|
||||||
|
HV_NO_RESOURCES = 0xfae94005,
|
||||||
|
HV_NO_DEVICE = 0xfae94006,
|
||||||
|
HV_DENIED = 0xfae94007,
|
||||||
|
HV_UNSUPPORTED = 0xfae9400f
|
||||||
|
}
|
||||||
|
|
||||||
|
enum hv_interrupt_type_t : uint
|
||||||
|
{
|
||||||
|
HV_INTERRUPT_TYPE_IRQ,
|
||||||
|
HV_INTERRUPT_TYPE_FIQ
|
||||||
|
}
|
||||||
|
|
||||||
|
struct hv_simd_fp_uchar16_t
|
||||||
|
{
|
||||||
|
public ulong Low;
|
||||||
|
public ulong High;
|
||||||
|
}
|
||||||
|
|
||||||
|
static class HvResultExtensions
|
||||||
|
{
|
||||||
|
public static void ThrowOnError(this hv_result_t result)
|
||||||
|
{
|
||||||
|
if (result != hv_result_t.HV_SUCCESS)
|
||||||
|
{
|
||||||
|
throw new Exception($"Unexpected result \"{result}\".");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static partial class HvApi
|
||||||
|
{
|
||||||
|
public const string LibraryName = "/System/Library/Frameworks/Hypervisor.framework/Hypervisor";
|
||||||
|
|
||||||
|
[LibraryImport(LibraryName, SetLastError = true)]
|
||||||
|
public static partial hv_result_t hv_vm_get_max_vcpu_count(out uint max_vcpu_count);
|
||||||
|
|
||||||
|
[LibraryImport(LibraryName, SetLastError = true)]
|
||||||
|
public static partial hv_result_t hv_vm_create(IntPtr config);
|
||||||
|
|
||||||
|
[LibraryImport(LibraryName, SetLastError = true)]
|
||||||
|
public static partial hv_result_t hv_vm_destroy();
|
||||||
|
|
||||||
|
[LibraryImport(LibraryName, SetLastError = true)]
|
||||||
|
public static partial hv_result_t hv_vm_map(ulong addr, ulong ipa, ulong size, hv_memory_flags_t flags);
|
||||||
|
|
||||||
|
[LibraryImport(LibraryName, SetLastError = true)]
|
||||||
|
public static partial hv_result_t hv_vm_unmap(ulong ipa, ulong size);
|
||||||
|
|
||||||
|
[LibraryImport(LibraryName, SetLastError = true)]
|
||||||
|
public static partial hv_result_t hv_vm_protect(ulong ipa, ulong size, hv_memory_flags_t flags);
|
||||||
|
|
||||||
|
[LibraryImport(LibraryName, SetLastError = true)]
|
||||||
|
public unsafe static partial hv_result_t hv_vcpu_create(out ulong vcpu, ref hv_vcpu_exit_t* exit, IntPtr config);
|
||||||
|
|
||||||
|
[LibraryImport(LibraryName, SetLastError = true)]
|
||||||
|
public unsafe static partial hv_result_t hv_vcpu_destroy(ulong vcpu);
|
||||||
|
|
||||||
|
[LibraryImport(LibraryName, SetLastError = true)]
|
||||||
|
public static partial hv_result_t hv_vcpu_run(ulong vcpu);
|
||||||
|
|
||||||
|
[LibraryImport(LibraryName, SetLastError = true)]
|
||||||
|
public static partial hv_result_t hv_vcpus_exit(ref ulong vcpus, uint vcpu_count);
|
||||||
|
|
||||||
|
[LibraryImport(LibraryName, SetLastError = true)]
|
||||||
|
public static partial hv_result_t hv_vcpu_set_vtimer_mask(ulong vcpu, [MarshalAs(UnmanagedType.Bool)] bool vtimer_is_masked);
|
||||||
|
|
||||||
|
[LibraryImport(LibraryName, SetLastError = true)]
|
||||||
|
public static partial hv_result_t hv_vcpu_get_reg(ulong vcpu, hv_reg_t reg, out ulong value);
|
||||||
|
|
||||||
|
[LibraryImport(LibraryName, SetLastError = true)]
|
||||||
|
public static partial hv_result_t hv_vcpu_set_reg(ulong vcpu, hv_reg_t reg, ulong value);
|
||||||
|
|
||||||
|
[LibraryImport(LibraryName, SetLastError = true)]
|
||||||
|
public static partial hv_result_t hv_vcpu_get_simd_fp_reg(ulong vcpu, hv_simd_fp_reg_t reg, out hv_simd_fp_uchar16_t value);
|
||||||
|
|
||||||
|
[LibraryImport(LibraryName, SetLastError = true)]
|
||||||
|
public static partial hv_result_t hv_vcpu_set_simd_fp_reg(ulong vcpu, hv_simd_fp_reg_t reg, hv_simd_fp_uchar16_t value); // DO NOT USE DIRECTLY!
|
||||||
|
|
||||||
|
[LibraryImport(LibraryName, SetLastError = true)]
|
||||||
|
public static partial hv_result_t hv_vcpu_get_sys_reg(ulong vcpu, hv_sys_reg_t reg, out ulong value);
|
||||||
|
|
||||||
|
[LibraryImport(LibraryName, SetLastError = true)]
|
||||||
|
public static partial hv_result_t hv_vcpu_set_sys_reg(ulong vcpu, hv_sys_reg_t reg, ulong value);
|
||||||
|
|
||||||
|
[LibraryImport(LibraryName, SetLastError = true)]
|
||||||
|
public static partial hv_result_t hv_vcpu_get_pending_interrupt(ulong vcpu, hv_interrupt_type_t type, [MarshalAs(UnmanagedType.Bool)] out bool pending);
|
||||||
|
|
||||||
|
[LibraryImport(LibraryName, SetLastError = true)]
|
||||||
|
public static partial hv_result_t hv_vcpu_set_pending_interrupt(ulong vcpu, hv_interrupt_type_t type, [MarshalAs(UnmanagedType.Bool)] bool pending);
|
||||||
|
}
|
||||||
|
}
|
47
Ryujinx.Cpu/AppleHv/HvCpuContext.cs
Normal file
47
Ryujinx.Cpu/AppleHv/HvCpuContext.cs
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
using ARMeilleure.Memory;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Ryujinx.Cpu.AppleHv
|
||||||
|
{
|
||||||
|
class HvCpuContext : ICpuContext
|
||||||
|
{
|
||||||
|
private readonly ITickSource _tickSource;
|
||||||
|
private readonly HvMemoryManager _memoryManager;
|
||||||
|
|
||||||
|
public HvCpuContext(ITickSource tickSource, IMemoryManager memory, bool for64Bit)
|
||||||
|
{
|
||||||
|
_tickSource = tickSource;
|
||||||
|
_memoryManager = (HvMemoryManager)memory;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UnmapHandler(ulong address, ulong size)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public IExecutionContext CreateExecutionContext(ExceptionCallbacks exceptionCallbacks)
|
||||||
|
{
|
||||||
|
return new HvExecutionContext(_tickSource, exceptionCallbacks);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void Execute(IExecutionContext context, ulong address)
|
||||||
|
{
|
||||||
|
((HvExecutionContext)context).Execute(_memoryManager, address);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void InvalidateCacheRegion(ulong address, ulong size)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public IDiskCacheLoadState LoadDiskCache(string titleIdText, string displayVersion, bool enabled)
|
||||||
|
{
|
||||||
|
return new DummyDiskCacheLoadState();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PrepareCodeRange(ulong address, ulong size)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
20
Ryujinx.Cpu/AppleHv/HvEngine.cs
Normal file
20
Ryujinx.Cpu/AppleHv/HvEngine.cs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
using ARMeilleure.Memory;
|
||||||
|
|
||||||
|
namespace Ryujinx.Cpu.AppleHv
|
||||||
|
{
|
||||||
|
public class HvEngine : ICpuEngine
|
||||||
|
{
|
||||||
|
private readonly ITickSource _tickSource;
|
||||||
|
|
||||||
|
public HvEngine(ITickSource tickSource)
|
||||||
|
{
|
||||||
|
_tickSource = tickSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public ICpuContext CreateCpuContext(IMemoryManager memoryManager, bool for64Bit)
|
||||||
|
{
|
||||||
|
return new HvCpuContext(_tickSource, memoryManager, for64Bit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
284
Ryujinx.Cpu/AppleHv/HvExecutionContext.cs
Normal file
284
Ryujinx.Cpu/AppleHv/HvExecutionContext.cs
Normal file
@@ -0,0 +1,284 @@
|
|||||||
|
using ARMeilleure.State;
|
||||||
|
using Ryujinx.Cpu.AppleHv.Arm;
|
||||||
|
using Ryujinx.Memory.Tracking;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Ryujinx.Cpu.AppleHv
|
||||||
|
{
|
||||||
|
class HvExecutionContext : IExecutionContext
|
||||||
|
{
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public ulong Pc => _impl.ElrEl1;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public long TpidrEl0
|
||||||
|
{
|
||||||
|
get => _impl.TpidrEl0;
|
||||||
|
set => _impl.TpidrEl0 = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public long TpidrroEl0
|
||||||
|
{
|
||||||
|
get => _impl.TpidrroEl0;
|
||||||
|
set => _impl.TpidrroEl0 = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public uint Pstate
|
||||||
|
{
|
||||||
|
get => _impl.Pstate;
|
||||||
|
set => _impl.Pstate = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public uint Fpcr
|
||||||
|
{
|
||||||
|
get => _impl.Fpcr;
|
||||||
|
set => _impl.Fpcr = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public uint Fpsr
|
||||||
|
{
|
||||||
|
get => _impl.Fpsr;
|
||||||
|
set => _impl.Fpsr = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool IsAarch32
|
||||||
|
{
|
||||||
|
get => false;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool Running { get; private set; }
|
||||||
|
|
||||||
|
private readonly ICounter _counter;
|
||||||
|
private readonly IHvExecutionContext _shadowContext;
|
||||||
|
private IHvExecutionContext _impl;
|
||||||
|
|
||||||
|
private readonly ExceptionCallbacks _exceptionCallbacks;
|
||||||
|
|
||||||
|
public HvExecutionContext(ICounter counter, ExceptionCallbacks exceptionCallbacks)
|
||||||
|
{
|
||||||
|
_counter = counter;
|
||||||
|
_shadowContext = new HvExecutionContextShadow();
|
||||||
|
_impl = _shadowContext;
|
||||||
|
_exceptionCallbacks = exceptionCallbacks;
|
||||||
|
Running = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public ulong GetX(int index) => _impl.GetX(index);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void SetX(int index, ulong value) => _impl.SetX(index, value);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public V128 GetV(int index) => _impl.GetV(index);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void SetV(int index, V128 value) => _impl.SetV(index, value);
|
||||||
|
|
||||||
|
private void InterruptHandler()
|
||||||
|
{
|
||||||
|
_exceptionCallbacks.InterruptCallback?.Invoke(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BreakHandler(ulong address, int imm)
|
||||||
|
{
|
||||||
|
_exceptionCallbacks.BreakCallback?.Invoke(this, address, imm);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SupervisorCallHandler(ulong address, int imm)
|
||||||
|
{
|
||||||
|
_exceptionCallbacks.SupervisorCallback?.Invoke(this, address, imm);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UndefinedHandler(ulong address, int opCode)
|
||||||
|
{
|
||||||
|
_exceptionCallbacks.UndefinedCallback?.Invoke(this, address, opCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void RequestInterrupt()
|
||||||
|
{
|
||||||
|
_impl.RequestInterrupt();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void StopRunning()
|
||||||
|
{
|
||||||
|
Running = false;
|
||||||
|
RequestInterrupt();
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe void Execute(HvMemoryManager memoryManager, ulong address)
|
||||||
|
{
|
||||||
|
HvVcpu vcpu = HvVcpuPool.Instance.Create(memoryManager.AddressSpace, _shadowContext, SwapContext);
|
||||||
|
|
||||||
|
HvApi.hv_vcpu_set_reg(vcpu.Handle, hv_reg_t.HV_REG_PC, address).ThrowOnError();
|
||||||
|
|
||||||
|
while (Running)
|
||||||
|
{
|
||||||
|
HvApi.hv_vcpu_run(vcpu.Handle).ThrowOnError();
|
||||||
|
|
||||||
|
uint reason = vcpu.ExitInfo->reason;
|
||||||
|
|
||||||
|
if (reason == 1)
|
||||||
|
{
|
||||||
|
uint hvEsr = (uint)vcpu.ExitInfo->exception.syndrome;
|
||||||
|
ExceptionClass hvEc = (ExceptionClass)(hvEsr >> 26);
|
||||||
|
|
||||||
|
if (hvEc != ExceptionClass.HvcAarch64)
|
||||||
|
{
|
||||||
|
throw new Exception($"Unhandled exception from guest kernel with ESR 0x{hvEsr:X} ({hvEc}).");
|
||||||
|
}
|
||||||
|
|
||||||
|
address = SynchronousException(memoryManager, ref vcpu);
|
||||||
|
HvApi.hv_vcpu_set_reg(vcpu.Handle, hv_reg_t.HV_REG_PC, address).ThrowOnError();
|
||||||
|
}
|
||||||
|
else if (reason == 0)
|
||||||
|
{
|
||||||
|
if (_impl.GetAndClearInterruptRequested())
|
||||||
|
{
|
||||||
|
ReturnToPool(vcpu);
|
||||||
|
InterruptHandler();
|
||||||
|
vcpu = RentFromPool(memoryManager.AddressSpace, vcpu);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new Exception($"Unhandled exit reason {reason}.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HvVcpuPool.Instance.Destroy(vcpu, SwapContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ulong SynchronousException(HvMemoryManager memoryManager, ref HvVcpu vcpu)
|
||||||
|
{
|
||||||
|
ulong vcpuHandle = vcpu.Handle;
|
||||||
|
|
||||||
|
HvApi.hv_vcpu_get_sys_reg(vcpuHandle, hv_sys_reg_t.HV_SYS_REG_ELR_EL1, out ulong elr).ThrowOnError();
|
||||||
|
HvApi.hv_vcpu_get_sys_reg(vcpuHandle, hv_sys_reg_t.HV_SYS_REG_ESR_EL1, out ulong esr).ThrowOnError();
|
||||||
|
|
||||||
|
ExceptionClass ec = (ExceptionClass)((uint)esr >> 26);
|
||||||
|
|
||||||
|
switch (ec)
|
||||||
|
{
|
||||||
|
case ExceptionClass.DataAbortLowerEl:
|
||||||
|
DataAbort(memoryManager.Tracking, vcpuHandle, (uint)esr);
|
||||||
|
break;
|
||||||
|
case ExceptionClass.TrappedMsrMrsSystem:
|
||||||
|
InstructionTrap((uint)esr);
|
||||||
|
HvApi.hv_vcpu_set_sys_reg(vcpuHandle, hv_sys_reg_t.HV_SYS_REG_ELR_EL1, elr + 4UL).ThrowOnError();
|
||||||
|
break;
|
||||||
|
case ExceptionClass.SvcAarch64:
|
||||||
|
ReturnToPool(vcpu);
|
||||||
|
ushort id = (ushort)esr;
|
||||||
|
SupervisorCallHandler(elr - 4UL, id);
|
||||||
|
vcpu = RentFromPool(memoryManager.AddressSpace, vcpu);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Exception($"Unhandled guest exception {ec}.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure we will continue running at EL0.
|
||||||
|
if (memoryManager.AddressSpace.GetAndClearUserTlbInvalidationPending())
|
||||||
|
{
|
||||||
|
// TODO: Invalidate only the range that was modified?
|
||||||
|
return HvAddressSpace.KernelRegionTlbiEretAddress;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return HvAddressSpace.KernelRegionEretAddress;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DataAbort(MemoryTracking tracking, ulong vcpu, uint esr)
|
||||||
|
{
|
||||||
|
bool write = (esr & (1u << 6)) != 0;
|
||||||
|
bool farValid = (esr & (1u << 10)) == 0;
|
||||||
|
int accessSizeLog2 = (int)((esr >> 22) & 3);
|
||||||
|
|
||||||
|
if (farValid)
|
||||||
|
{
|
||||||
|
HvApi.hv_vcpu_get_sys_reg(vcpu, hv_sys_reg_t.HV_SYS_REG_FAR_EL1, out ulong far).ThrowOnError();
|
||||||
|
|
||||||
|
ulong size = 1UL << accessSizeLog2;
|
||||||
|
|
||||||
|
if (!tracking.VirtualMemoryEvent(far, size, write))
|
||||||
|
{
|
||||||
|
string rw = write ? "write" : "read";
|
||||||
|
throw new Exception($"Unhandled invalid memory access at VA 0x{far:X} with size 0x{size:X} ({rw}).");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new Exception($"Unhandled invalid memory access at unknown VA with ESR 0x{esr:X}.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InstructionTrap(uint esr)
|
||||||
|
{
|
||||||
|
bool read = (esr & 1) != 0;
|
||||||
|
uint rt = (esr >> 5) & 0x1f;
|
||||||
|
|
||||||
|
if (read)
|
||||||
|
{
|
||||||
|
// Op0 Op2 Op1 CRn 00000 CRm
|
||||||
|
switch ((esr >> 1) & 0x1ffe0f)
|
||||||
|
{
|
||||||
|
case 0b11_000_011_1110_00000_0000: // CNTFRQ_EL0
|
||||||
|
WriteRt(rt, _counter.Frequency);
|
||||||
|
break;
|
||||||
|
case 0b11_001_011_1110_00000_0000: // CNTPCT_EL0
|
||||||
|
WriteRt(rt, _counter.Counter);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Exception($"Unhandled system register read with ESR 0x{esr:X}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new Exception($"Unhandled system register write with ESR 0x{esr:X}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteRt(uint rt, ulong value)
|
||||||
|
{
|
||||||
|
if (rt < 31)
|
||||||
|
{
|
||||||
|
SetX((int)rt, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ReturnToPool(HvVcpu vcpu)
|
||||||
|
{
|
||||||
|
HvVcpuPool.Instance.Return(vcpu, SwapContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
private HvVcpu RentFromPool(HvAddressSpace addressSpace, HvVcpu vcpu)
|
||||||
|
{
|
||||||
|
return HvVcpuPool.Instance.Rent(addressSpace, _shadowContext, vcpu, SwapContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SwapContext(IHvExecutionContext newContext)
|
||||||
|
{
|
||||||
|
_impl = newContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
59
Ryujinx.Cpu/AppleHv/HvExecutionContextShadow.cs
Normal file
59
Ryujinx.Cpu/AppleHv/HvExecutionContextShadow.cs
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
using ARMeilleure.State;
|
||||||
|
|
||||||
|
namespace Ryujinx.Cpu.AppleHv
|
||||||
|
{
|
||||||
|
unsafe class HvExecutionContextShadow : IHvExecutionContext
|
||||||
|
{
|
||||||
|
public ulong Pc { get; set; }
|
||||||
|
public ulong ElrEl1 { get; set; }
|
||||||
|
public ulong EsrEl1 { get; set; }
|
||||||
|
|
||||||
|
public long TpidrEl0 { get; set; }
|
||||||
|
public long TpidrroEl0 { get; set; }
|
||||||
|
|
||||||
|
public uint Pstate { get; set; }
|
||||||
|
|
||||||
|
public uint Fpcr { get; set; }
|
||||||
|
public uint Fpsr { get; set; }
|
||||||
|
|
||||||
|
public bool IsAarch32 { get; set; }
|
||||||
|
|
||||||
|
private readonly ulong[] _x;
|
||||||
|
private readonly V128[] _v;
|
||||||
|
|
||||||
|
public HvExecutionContextShadow()
|
||||||
|
{
|
||||||
|
_x = new ulong[32];
|
||||||
|
_v = new V128[32];
|
||||||
|
}
|
||||||
|
|
||||||
|
public ulong GetX(int index)
|
||||||
|
{
|
||||||
|
return _x[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetX(int index, ulong value)
|
||||||
|
{
|
||||||
|
_x[index] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public V128 GetV(int index)
|
||||||
|
{
|
||||||
|
return _v[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetV(int index, V128 value)
|
||||||
|
{
|
||||||
|
_v[index] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RequestInterrupt()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool GetAndClearInterruptRequested()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
196
Ryujinx.Cpu/AppleHv/HvExecutionContextVcpu.cs
Normal file
196
Ryujinx.Cpu/AppleHv/HvExecutionContextVcpu.cs
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
using ARMeilleure.State;
|
||||||
|
using Ryujinx.Memory;
|
||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
|
namespace Ryujinx.Cpu.AppleHv
|
||||||
|
{
|
||||||
|
class HvExecutionContextVcpu : IHvExecutionContext
|
||||||
|
{
|
||||||
|
private static MemoryBlock _setSimdFpRegFuncMem;
|
||||||
|
private delegate hv_result_t SetSimdFpReg(ulong vcpu, hv_simd_fp_reg_t reg, in V128 value, IntPtr funcPtr);
|
||||||
|
private static SetSimdFpReg _setSimdFpReg;
|
||||||
|
private static IntPtr _setSimdFpRegNativePtr;
|
||||||
|
|
||||||
|
static HvExecutionContextVcpu()
|
||||||
|
{
|
||||||
|
// .NET does not support passing vectors by value, so we need to pass a pointer and use a native
|
||||||
|
// function to load the value into a vector register.
|
||||||
|
_setSimdFpRegFuncMem = new MemoryBlock(MemoryBlock.GetPageSize());
|
||||||
|
_setSimdFpRegFuncMem.Write(0, 0x3DC00040u); // LDR Q0, [X2]
|
||||||
|
_setSimdFpRegFuncMem.Write(4, 0xD61F0060u); // BR X3
|
||||||
|
_setSimdFpRegFuncMem.Reprotect(0, _setSimdFpRegFuncMem.Size, MemoryPermission.ReadAndExecute);
|
||||||
|
|
||||||
|
_setSimdFpReg = Marshal.GetDelegateForFunctionPointer<SetSimdFpReg>(_setSimdFpRegFuncMem.Pointer);
|
||||||
|
|
||||||
|
if (NativeLibrary.TryLoad(HvApi.LibraryName, out IntPtr hvLibHandle))
|
||||||
|
{
|
||||||
|
_setSimdFpRegNativePtr = NativeLibrary.GetExport(hvLibHandle, nameof(HvApi.hv_vcpu_set_simd_fp_reg));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ulong Pc
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
HvApi.hv_vcpu_get_reg(_vcpu, hv_reg_t.HV_REG_PC, out ulong pc).ThrowOnError();
|
||||||
|
return pc;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
HvApi.hv_vcpu_set_reg(_vcpu, hv_reg_t.HV_REG_PC, value).ThrowOnError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ulong ElrEl1
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
HvApi.hv_vcpu_get_sys_reg(_vcpu, hv_sys_reg_t.HV_SYS_REG_ELR_EL1, out ulong elr).ThrowOnError();
|
||||||
|
return elr;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
HvApi.hv_vcpu_set_sys_reg(_vcpu, hv_sys_reg_t.HV_SYS_REG_ELR_EL1, value).ThrowOnError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ulong EsrEl1
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
HvApi.hv_vcpu_get_sys_reg(_vcpu, hv_sys_reg_t.HV_SYS_REG_ESR_EL1, out ulong esr).ThrowOnError();
|
||||||
|
return esr;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
HvApi.hv_vcpu_set_sys_reg(_vcpu, hv_sys_reg_t.HV_SYS_REG_ESR_EL1, value).ThrowOnError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public long TpidrEl0
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
HvApi.hv_vcpu_get_sys_reg(_vcpu, hv_sys_reg_t.HV_SYS_REG_TPIDR_EL0, out ulong tpidrEl0).ThrowOnError();
|
||||||
|
return (long)tpidrEl0;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
HvApi.hv_vcpu_set_sys_reg(_vcpu, hv_sys_reg_t.HV_SYS_REG_TPIDR_EL0, (ulong)value).ThrowOnError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public long TpidrroEl0
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
HvApi.hv_vcpu_get_sys_reg(_vcpu, hv_sys_reg_t.HV_SYS_REG_TPIDRRO_EL0, out ulong tpidrroEl0).ThrowOnError();
|
||||||
|
return (long)tpidrroEl0;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
HvApi.hv_vcpu_set_sys_reg(_vcpu, hv_sys_reg_t.HV_SYS_REG_TPIDRRO_EL0, (ulong)value).ThrowOnError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public uint Pstate
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
HvApi.hv_vcpu_get_reg(_vcpu, hv_reg_t.HV_REG_CPSR, out ulong cpsr).ThrowOnError();
|
||||||
|
return (uint)cpsr;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
HvApi.hv_vcpu_set_reg(_vcpu, hv_reg_t.HV_REG_CPSR, (ulong)value).ThrowOnError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public uint Fpcr
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
HvApi.hv_vcpu_get_reg(_vcpu, hv_reg_t.HV_REG_FPCR, out ulong fpcr).ThrowOnError();
|
||||||
|
return (uint)fpcr;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
HvApi.hv_vcpu_set_reg(_vcpu, hv_reg_t.HV_REG_FPCR, (ulong)value).ThrowOnError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public uint Fpsr
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
HvApi.hv_vcpu_get_reg(_vcpu, hv_reg_t.HV_REG_FPSR, out ulong fpsr).ThrowOnError();
|
||||||
|
return (uint)fpsr;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
HvApi.hv_vcpu_set_reg(_vcpu, hv_reg_t.HV_REG_FPSR, (ulong)value).ThrowOnError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ulong _vcpu;
|
||||||
|
private int _interruptRequested;
|
||||||
|
|
||||||
|
public HvExecutionContextVcpu(ulong vcpu)
|
||||||
|
{
|
||||||
|
_vcpu = vcpu;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ulong GetX(int index)
|
||||||
|
{
|
||||||
|
if (index == 31)
|
||||||
|
{
|
||||||
|
HvApi.hv_vcpu_get_sys_reg(_vcpu, hv_sys_reg_t.HV_SYS_REG_SP_EL0, out ulong value).ThrowOnError();
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
HvApi.hv_vcpu_get_reg(_vcpu, hv_reg_t.HV_REG_X0 + (uint)index, out ulong value).ThrowOnError();
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetX(int index, ulong value)
|
||||||
|
{
|
||||||
|
if (index == 31)
|
||||||
|
{
|
||||||
|
HvApi.hv_vcpu_set_sys_reg(_vcpu, hv_sys_reg_t.HV_SYS_REG_SP_EL0, value).ThrowOnError();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
HvApi.hv_vcpu_set_reg(_vcpu, hv_reg_t.HV_REG_X0 + (uint)index, value).ThrowOnError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public V128 GetV(int index)
|
||||||
|
{
|
||||||
|
HvApi.hv_vcpu_get_simd_fp_reg(_vcpu, hv_simd_fp_reg_t.HV_SIMD_FP_REG_Q0 + (uint)index, out hv_simd_fp_uchar16_t value).ThrowOnError();
|
||||||
|
return new V128(value.Low, value.High);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetV(int index, V128 value)
|
||||||
|
{
|
||||||
|
_setSimdFpReg(_vcpu, hv_simd_fp_reg_t.HV_SIMD_FP_REG_Q0 + (uint)index, value, _setSimdFpRegNativePtr).ThrowOnError();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RequestInterrupt()
|
||||||
|
{
|
||||||
|
if (Interlocked.Exchange(ref _interruptRequested, 1) == 0)
|
||||||
|
{
|
||||||
|
ulong vcpu = _vcpu;
|
||||||
|
HvApi.hv_vcpus_exit(ref vcpu, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool GetAndClearInterruptRequested()
|
||||||
|
{
|
||||||
|
return Interlocked.Exchange(ref _interruptRequested, 0) != 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
34
Ryujinx.Cpu/AppleHv/HvIpaAllocator.cs
Normal file
34
Ryujinx.Cpu/AppleHv/HvIpaAllocator.cs
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Ryujinx.Cpu.AppleHv
|
||||||
|
{
|
||||||
|
class HvIpaAllocator
|
||||||
|
{
|
||||||
|
private const ulong AllocationGranule = 1UL << 14;
|
||||||
|
private const ulong IpaRegionSize = 1UL << 35;
|
||||||
|
|
||||||
|
private readonly PrivateMemoryAllocator.Block _block;
|
||||||
|
|
||||||
|
public HvIpaAllocator()
|
||||||
|
{
|
||||||
|
_block = new PrivateMemoryAllocator.Block(null, IpaRegionSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ulong Allocate(ulong size, ulong alignment = AllocationGranule)
|
||||||
|
{
|
||||||
|
ulong offset = _block.Allocate(size, alignment);
|
||||||
|
|
||||||
|
if (offset == PrivateMemoryAllocator.InvalidOffset)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"No enough free IPA memory to allocate 0x{size:X} bytes with alignment 0x{alignment:X}.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Free(ulong offset, ulong size)
|
||||||
|
{
|
||||||
|
_block.Free(offset, size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
34
Ryujinx.Cpu/AppleHv/HvMemoryBlockAllocation.cs
Normal file
34
Ryujinx.Cpu/AppleHv/HvMemoryBlockAllocation.cs
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
using Ryujinx.Memory;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Ryujinx.Cpu.AppleHv
|
||||||
|
{
|
||||||
|
struct HvMemoryBlockAllocation : IDisposable
|
||||||
|
{
|
||||||
|
private readonly HvMemoryBlockAllocator _owner;
|
||||||
|
private readonly HvMemoryBlockAllocator.Block _block;
|
||||||
|
|
||||||
|
public bool IsValid => _owner != null;
|
||||||
|
public MemoryBlock Memory => _block.Memory;
|
||||||
|
public ulong Ipa => _block.Ipa;
|
||||||
|
public ulong Offset { get; }
|
||||||
|
public ulong Size { get; }
|
||||||
|
|
||||||
|
public HvMemoryBlockAllocation(
|
||||||
|
HvMemoryBlockAllocator owner,
|
||||||
|
HvMemoryBlockAllocator.Block block,
|
||||||
|
ulong offset,
|
||||||
|
ulong size)
|
||||||
|
{
|
||||||
|
_owner = owner;
|
||||||
|
_block = block;
|
||||||
|
Offset = offset;
|
||||||
|
Size = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_owner.Free(_block, Offset, Size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
59
Ryujinx.Cpu/AppleHv/HvMemoryBlockAllocator.cs
Normal file
59
Ryujinx.Cpu/AppleHv/HvMemoryBlockAllocator.cs
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
using Ryujinx.Memory;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Ryujinx.Cpu.AppleHv
|
||||||
|
{
|
||||||
|
class HvMemoryBlockAllocator : PrivateMemoryAllocatorImpl<HvMemoryBlockAllocator.Block>
|
||||||
|
{
|
||||||
|
private const ulong InvalidOffset = ulong.MaxValue;
|
||||||
|
|
||||||
|
public class Block : PrivateMemoryAllocator.Block
|
||||||
|
{
|
||||||
|
private readonly HvIpaAllocator _ipaAllocator;
|
||||||
|
public ulong Ipa { get; }
|
||||||
|
|
||||||
|
public Block(HvIpaAllocator ipaAllocator, MemoryBlock memory, ulong size) : base(memory, size)
|
||||||
|
{
|
||||||
|
_ipaAllocator = ipaAllocator;
|
||||||
|
|
||||||
|
lock (ipaAllocator)
|
||||||
|
{
|
||||||
|
Ipa = ipaAllocator.Allocate(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
HvApi.hv_vm_map((ulong)Memory.Pointer, Ipa, size, hv_memory_flags_t.HV_MEMORY_READ | hv_memory_flags_t.HV_MEMORY_WRITE).ThrowOnError();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Destroy()
|
||||||
|
{
|
||||||
|
HvApi.hv_vm_unmap(Ipa, Size).ThrowOnError();
|
||||||
|
|
||||||
|
lock (_ipaAllocator)
|
||||||
|
{
|
||||||
|
_ipaAllocator.Free(Ipa, Size);
|
||||||
|
}
|
||||||
|
|
||||||
|
base.Destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly HvIpaAllocator _ipaAllocator;
|
||||||
|
|
||||||
|
public HvMemoryBlockAllocator(HvIpaAllocator ipaAllocator, int blockAlignment) : base(blockAlignment, MemoryAllocationFlags.None)
|
||||||
|
{
|
||||||
|
_ipaAllocator = ipaAllocator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe HvMemoryBlockAllocation Allocate(ulong size, ulong alignment)
|
||||||
|
{
|
||||||
|
var allocation = Allocate(size, alignment, CreateBlock);
|
||||||
|
|
||||||
|
return new HvMemoryBlockAllocation(this, allocation.Block, allocation.Offset, allocation.Size);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Block CreateBlock(MemoryBlock memory, ulong size)
|
||||||
|
{
|
||||||
|
return new Block(_ipaAllocator, memory, size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
947
Ryujinx.Cpu/AppleHv/HvMemoryManager.cs
Normal file
947
Ryujinx.Cpu/AppleHv/HvMemoryManager.cs
Normal file
@@ -0,0 +1,947 @@
|
|||||||
|
using ARMeilleure.Memory;
|
||||||
|
using Ryujinx.Cpu.Tracking;
|
||||||
|
using Ryujinx.Memory;
|
||||||
|
using Ryujinx.Memory.Range;
|
||||||
|
using Ryujinx.Memory.Tracking;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
|
namespace Ryujinx.Cpu.AppleHv
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a CPU memory manager which maps guest virtual memory directly onto the Hypervisor page table.
|
||||||
|
/// </summary>
|
||||||
|
public class HvMemoryManager : MemoryManagerBase, IMemoryManager, IVirtualMemoryManagerTracked, IWritableBlock
|
||||||
|
{
|
||||||
|
public const int PageBits = 12;
|
||||||
|
public const int PageSize = 1 << PageBits;
|
||||||
|
public const int PageMask = PageSize - 1;
|
||||||
|
|
||||||
|
public const int PageToPteShift = 5; // 32 pages (2 bits each) in one ulong page table entry.
|
||||||
|
public const ulong BlockMappedMask = 0x5555555555555555; // First bit of each table entry set.
|
||||||
|
|
||||||
|
private enum HostMappedPtBits : ulong
|
||||||
|
{
|
||||||
|
Unmapped = 0,
|
||||||
|
Mapped,
|
||||||
|
WriteTracked,
|
||||||
|
ReadWriteTracked,
|
||||||
|
|
||||||
|
MappedReplicated = 0x5555555555555555,
|
||||||
|
WriteTrackedReplicated = 0xaaaaaaaaaaaaaaaa,
|
||||||
|
ReadWriteTrackedReplicated = ulong.MaxValue
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly InvalidAccessHandler _invalidAccessHandler;
|
||||||
|
|
||||||
|
private readonly ulong _addressSpaceSize;
|
||||||
|
|
||||||
|
private readonly HvAddressSpace _addressSpace;
|
||||||
|
|
||||||
|
internal HvAddressSpace AddressSpace => _addressSpace;
|
||||||
|
|
||||||
|
private readonly MemoryBlock _backingMemory;
|
||||||
|
private readonly PageTable<ulong> _pageTable;
|
||||||
|
|
||||||
|
private readonly ulong[] _pageBitmap;
|
||||||
|
|
||||||
|
public bool Supports4KBPages => true;
|
||||||
|
|
||||||
|
public int AddressSpaceBits { get; }
|
||||||
|
|
||||||
|
public IntPtr PageTablePointer => IntPtr.Zero;
|
||||||
|
|
||||||
|
public MemoryManagerType Type => MemoryManagerType.SoftwarePageTable;
|
||||||
|
|
||||||
|
public MemoryTracking Tracking { get; }
|
||||||
|
|
||||||
|
public event Action<ulong, ulong> UnmapEvent;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new instance of the Hypervisor memory manager.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="backingMemory">Physical backing memory where virtual memory will be mapped to</param>
|
||||||
|
/// <param name="addressSpaceSize">Size of the address space</param>
|
||||||
|
/// <param name="invalidAccessHandler">Optional function to handle invalid memory accesses</param>
|
||||||
|
public HvMemoryManager(MemoryBlock backingMemory, ulong addressSpaceSize, InvalidAccessHandler invalidAccessHandler = null)
|
||||||
|
{
|
||||||
|
_backingMemory = backingMemory;
|
||||||
|
_pageTable = new PageTable<ulong>();
|
||||||
|
_invalidAccessHandler = invalidAccessHandler;
|
||||||
|
_addressSpaceSize = addressSpaceSize;
|
||||||
|
|
||||||
|
ulong asSize = PageSize;
|
||||||
|
int asBits = PageBits;
|
||||||
|
|
||||||
|
while (asSize < addressSpaceSize)
|
||||||
|
{
|
||||||
|
asSize <<= 1;
|
||||||
|
asBits++;
|
||||||
|
}
|
||||||
|
|
||||||
|
_addressSpace = new HvAddressSpace(backingMemory, asSize);
|
||||||
|
|
||||||
|
AddressSpaceBits = asBits;
|
||||||
|
|
||||||
|
_pageBitmap = new ulong[1 << (AddressSpaceBits - (PageBits + PageToPteShift))];
|
||||||
|
Tracking = new MemoryTracking(this, PageSize, invalidAccessHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if the virtual address is part of the addressable space.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="va">Virtual address</param>
|
||||||
|
/// <returns>True if the virtual address is part of the addressable space</returns>
|
||||||
|
private bool ValidateAddress(ulong va)
|
||||||
|
{
|
||||||
|
return va < _addressSpaceSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if the combination of virtual address and size is part of the addressable space.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="va">Virtual address of the range</param>
|
||||||
|
/// <param name="size">Size of the range in bytes</param>
|
||||||
|
/// <returns>True if the combination of virtual address and size is part of the addressable space</returns>
|
||||||
|
private bool ValidateAddressAndSize(ulong va, ulong size)
|
||||||
|
{
|
||||||
|
ulong endVa = va + size;
|
||||||
|
return endVa >= va && endVa >= size && endVa <= _addressSpaceSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ensures the combination of virtual address and size is part of the addressable space.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="va">Virtual address of the range</param>
|
||||||
|
/// <param name="size">Size of the range in bytes</param>
|
||||||
|
/// <exception cref="InvalidMemoryRegionException">Throw when the memory region specified outside the addressable space</exception>
|
||||||
|
private void AssertValidAddressAndSize(ulong va, ulong size)
|
||||||
|
{
|
||||||
|
if (!ValidateAddressAndSize(va, size))
|
||||||
|
{
|
||||||
|
throw new InvalidMemoryRegionException($"va=0x{va:X16}, size=0x{size:X16}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ensures the combination of virtual address and size is part of the addressable space and fully mapped.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="va">Virtual address of the range</param>
|
||||||
|
/// <param name="size">Size of the range in bytes</param>
|
||||||
|
private void AssertMapped(ulong va, ulong size)
|
||||||
|
{
|
||||||
|
if (!ValidateAddressAndSize(va, size) || !IsRangeMappedImpl(va, size))
|
||||||
|
{
|
||||||
|
throw new InvalidMemoryRegionException($"Not mapped: va=0x{va:X16}, size=0x{size:X16}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void Map(ulong va, ulong pa, ulong size, MemoryMapFlags flags)
|
||||||
|
{
|
||||||
|
AssertValidAddressAndSize(va, size);
|
||||||
|
|
||||||
|
PtMap(va, pa, size);
|
||||||
|
_addressSpace.MapUser(va, pa, size, MemoryPermission.ReadWriteExecute);
|
||||||
|
AddMapping(va, size);
|
||||||
|
|
||||||
|
Tracking.Map(va, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PtMap(ulong va, ulong pa, ulong size)
|
||||||
|
{
|
||||||
|
while (size != 0)
|
||||||
|
{
|
||||||
|
_pageTable.Map(va, pa);
|
||||||
|
|
||||||
|
va += PageSize;
|
||||||
|
pa += PageSize;
|
||||||
|
size -= PageSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void MapForeign(ulong va, nuint hostPointer, ulong size)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void Unmap(ulong va, ulong size)
|
||||||
|
{
|
||||||
|
AssertValidAddressAndSize(va, size);
|
||||||
|
|
||||||
|
UnmapEvent?.Invoke(va, size);
|
||||||
|
Tracking.Unmap(va, size);
|
||||||
|
|
||||||
|
RemoveMapping(va, size);
|
||||||
|
_addressSpace.UnmapUser(va, size);
|
||||||
|
PtUnmap(va, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PtUnmap(ulong va, ulong size)
|
||||||
|
{
|
||||||
|
while (size != 0)
|
||||||
|
{
|
||||||
|
_pageTable.Unmap(va);
|
||||||
|
|
||||||
|
va += PageSize;
|
||||||
|
size -= PageSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public T Read<T>(ulong va) where T : unmanaged
|
||||||
|
{
|
||||||
|
return MemoryMarshal.Cast<byte, T>(GetSpan(va, Unsafe.SizeOf<T>()))[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public T ReadTracked<T>(ulong va) where T : unmanaged
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
SignalMemoryTracking(va, (ulong)Unsafe.SizeOf<T>(), false);
|
||||||
|
|
||||||
|
return Read<T>(va);
|
||||||
|
}
|
||||||
|
catch (InvalidMemoryRegionException)
|
||||||
|
{
|
||||||
|
if (_invalidAccessHandler == null || !_invalidAccessHandler(va))
|
||||||
|
{
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void Read(ulong va, Span<byte> data)
|
||||||
|
{
|
||||||
|
ReadImpl(va, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void Write<T>(ulong va, T value) where T : unmanaged
|
||||||
|
{
|
||||||
|
Write(va, MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateSpan(ref value, 1)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void Write(ulong va, ReadOnlySpan<byte> data)
|
||||||
|
{
|
||||||
|
if (data.Length == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SignalMemoryTracking(va, (ulong)data.Length, true);
|
||||||
|
|
||||||
|
WriteImpl(va, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void WriteUntracked(ulong va, ReadOnlySpan<byte> data)
|
||||||
|
{
|
||||||
|
if (data.Length == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteImpl(va, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool WriteWithRedundancyCheck(ulong va, ReadOnlySpan<byte> data)
|
||||||
|
{
|
||||||
|
if (data.Length == 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
SignalMemoryTracking(va, (ulong)data.Length, false);
|
||||||
|
|
||||||
|
if (IsContiguousAndMapped(va, data.Length))
|
||||||
|
{
|
||||||
|
var target = _backingMemory.GetSpan(GetPhysicalAddressInternal(va), data.Length);
|
||||||
|
|
||||||
|
bool changed = !data.SequenceEqual(target);
|
||||||
|
|
||||||
|
if (changed)
|
||||||
|
{
|
||||||
|
data.CopyTo(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
WriteImpl(va, data);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteImpl(ulong va, ReadOnlySpan<byte> data)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
AssertValidAddressAndSize(va, (ulong)data.Length);
|
||||||
|
|
||||||
|
if (IsContiguousAndMapped(va, data.Length))
|
||||||
|
{
|
||||||
|
data.CopyTo(_backingMemory.GetSpan(GetPhysicalAddressInternal(va), data.Length));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int offset = 0, size;
|
||||||
|
|
||||||
|
if ((va & PageMask) != 0)
|
||||||
|
{
|
||||||
|
ulong pa = GetPhysicalAddressChecked(va);
|
||||||
|
|
||||||
|
size = Math.Min(data.Length, PageSize - (int)(va & PageMask));
|
||||||
|
|
||||||
|
data.Slice(0, size).CopyTo(_backingMemory.GetSpan(pa, size));
|
||||||
|
|
||||||
|
offset += size;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (; offset < data.Length; offset += size)
|
||||||
|
{
|
||||||
|
ulong pa = GetPhysicalAddressChecked(va + (ulong)offset);
|
||||||
|
|
||||||
|
size = Math.Min(data.Length - offset, PageSize);
|
||||||
|
|
||||||
|
data.Slice(offset, size).CopyTo(_backingMemory.GetSpan(pa, size));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (InvalidMemoryRegionException)
|
||||||
|
{
|
||||||
|
if (_invalidAccessHandler == null || !_invalidAccessHandler(va))
|
||||||
|
{
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public ReadOnlySpan<byte> GetSpan(ulong va, int size, bool tracked = false)
|
||||||
|
{
|
||||||
|
if (size == 0)
|
||||||
|
{
|
||||||
|
return ReadOnlySpan<byte>.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tracked)
|
||||||
|
{
|
||||||
|
SignalMemoryTracking(va, (ulong)size, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsContiguousAndMapped(va, size))
|
||||||
|
{
|
||||||
|
return _backingMemory.GetSpan(GetPhysicalAddressInternal(va), size);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Span<byte> data = new byte[size];
|
||||||
|
|
||||||
|
ReadImpl(va, data);
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public WritableRegion GetWritableRegion(ulong va, int size, bool tracked = false)
|
||||||
|
{
|
||||||
|
if (size == 0)
|
||||||
|
{
|
||||||
|
return new WritableRegion(null, va, Memory<byte>.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tracked)
|
||||||
|
{
|
||||||
|
SignalMemoryTracking(va, (ulong)size, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsContiguousAndMapped(va, size))
|
||||||
|
{
|
||||||
|
return new WritableRegion(null, va, _backingMemory.GetMemory(GetPhysicalAddressInternal(va), size));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Memory<byte> memory = new byte[size];
|
||||||
|
|
||||||
|
ReadImpl(va, memory.Span);
|
||||||
|
|
||||||
|
return new WritableRegion(this, va, memory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public ref T GetRef<T>(ulong va) where T : unmanaged
|
||||||
|
{
|
||||||
|
if (!IsContiguous(va, Unsafe.SizeOf<T>()))
|
||||||
|
{
|
||||||
|
ThrowMemoryNotContiguous();
|
||||||
|
}
|
||||||
|
|
||||||
|
SignalMemoryTracking(va, (ulong)Unsafe.SizeOf<T>(), true);
|
||||||
|
|
||||||
|
return ref _backingMemory.GetRef<T>(GetPhysicalAddressChecked(va));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public bool IsMapped(ulong va)
|
||||||
|
{
|
||||||
|
return ValidateAddress(va) && IsMappedImpl(va);
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private bool IsMappedImpl(ulong va)
|
||||||
|
{
|
||||||
|
ulong page = va >> PageBits;
|
||||||
|
|
||||||
|
int bit = (int)((page & 31) << 1);
|
||||||
|
|
||||||
|
int pageIndex = (int)(page >> PageToPteShift);
|
||||||
|
ref ulong pageRef = ref _pageBitmap[pageIndex];
|
||||||
|
|
||||||
|
ulong pte = Volatile.Read(ref pageRef);
|
||||||
|
|
||||||
|
return ((pte >> bit) & 3) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool IsRangeMapped(ulong va, ulong size)
|
||||||
|
{
|
||||||
|
AssertValidAddressAndSize(va, size);
|
||||||
|
|
||||||
|
return IsRangeMappedImpl(va, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private void GetPageBlockRange(ulong pageStart, ulong pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex)
|
||||||
|
{
|
||||||
|
startMask = ulong.MaxValue << ((int)(pageStart & 31) << 1);
|
||||||
|
endMask = ulong.MaxValue >> (64 - ((int)(pageEnd & 31) << 1));
|
||||||
|
|
||||||
|
pageIndex = (int)(pageStart >> PageToPteShift);
|
||||||
|
pageEndIndex = (int)((pageEnd - 1) >> PageToPteShift);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsRangeMappedImpl(ulong va, ulong size)
|
||||||
|
{
|
||||||
|
int pages = GetPagesCount(va, size, out _);
|
||||||
|
|
||||||
|
if (pages == 1)
|
||||||
|
{
|
||||||
|
return IsMappedImpl(va);
|
||||||
|
}
|
||||||
|
|
||||||
|
ulong pageStart = va >> PageBits;
|
||||||
|
ulong pageEnd = pageStart + (ulong)pages;
|
||||||
|
|
||||||
|
GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex);
|
||||||
|
|
||||||
|
// Check if either bit in each 2 bit page entry is set.
|
||||||
|
// OR the block with itself shifted down by 1, and check the first bit of each entry.
|
||||||
|
|
||||||
|
ulong mask = BlockMappedMask & startMask;
|
||||||
|
|
||||||
|
while (pageIndex <= pageEndIndex)
|
||||||
|
{
|
||||||
|
if (pageIndex == pageEndIndex)
|
||||||
|
{
|
||||||
|
mask &= endMask;
|
||||||
|
}
|
||||||
|
|
||||||
|
ref ulong pageRef = ref _pageBitmap[pageIndex++];
|
||||||
|
ulong pte = Volatile.Read(ref pageRef);
|
||||||
|
|
||||||
|
pte |= pte >> 1;
|
||||||
|
if ((pte & mask) != mask)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
mask = BlockMappedMask;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ThrowMemoryNotContiguous() => throw new MemoryNotContiguousException();
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private bool IsContiguousAndMapped(ulong va, int size) => IsContiguous(va, size) && IsMapped(va);
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private bool IsContiguous(ulong va, int size)
|
||||||
|
{
|
||||||
|
if (!ValidateAddress(va) || !ValidateAddressAndSize(va, (ulong)size))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int pages = GetPagesCount(va, (uint)size, out va);
|
||||||
|
|
||||||
|
for (int page = 0; page < pages - 1; page++)
|
||||||
|
{
|
||||||
|
if (!ValidateAddress(va + PageSize))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GetPhysicalAddressInternal(va) + PageSize != GetPhysicalAddressInternal(va + PageSize))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
va += PageSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public IEnumerable<HostMemoryRange> GetHostRegions(ulong va, ulong size)
|
||||||
|
{
|
||||||
|
if (size == 0)
|
||||||
|
{
|
||||||
|
return Enumerable.Empty<HostMemoryRange>();
|
||||||
|
}
|
||||||
|
|
||||||
|
var guestRegions = GetPhysicalRegionsImpl(va, size);
|
||||||
|
if (guestRegions == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var regions = new HostMemoryRange[guestRegions.Count];
|
||||||
|
|
||||||
|
for (int i = 0; i < regions.Length; i++)
|
||||||
|
{
|
||||||
|
var guestRegion = guestRegions[i];
|
||||||
|
IntPtr pointer = _backingMemory.GetPointer(guestRegion.Address, guestRegion.Size);
|
||||||
|
regions[i] = new HostMemoryRange((nuint)(ulong)pointer, guestRegion.Size);
|
||||||
|
}
|
||||||
|
|
||||||
|
return regions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public IEnumerable<MemoryRange> GetPhysicalRegions(ulong va, ulong size)
|
||||||
|
{
|
||||||
|
if (size == 0)
|
||||||
|
{
|
||||||
|
return Enumerable.Empty<MemoryRange>();
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetPhysicalRegionsImpl(va, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<MemoryRange> GetPhysicalRegionsImpl(ulong va, ulong size)
|
||||||
|
{
|
||||||
|
if (!ValidateAddress(va) || !ValidateAddressAndSize(va, size))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
int pages = GetPagesCount(va, (uint)size, out va);
|
||||||
|
|
||||||
|
var regions = new List<MemoryRange>();
|
||||||
|
|
||||||
|
ulong regionStart = GetPhysicalAddressInternal(va);
|
||||||
|
ulong regionSize = PageSize;
|
||||||
|
|
||||||
|
for (int page = 0; page < pages - 1; page++)
|
||||||
|
{
|
||||||
|
if (!ValidateAddress(va + PageSize))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
ulong newPa = GetPhysicalAddressInternal(va + PageSize);
|
||||||
|
|
||||||
|
if (GetPhysicalAddressInternal(va) + PageSize != newPa)
|
||||||
|
{
|
||||||
|
regions.Add(new MemoryRange(regionStart, regionSize));
|
||||||
|
regionStart = newPa;
|
||||||
|
regionSize = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
va += PageSize;
|
||||||
|
regionSize += PageSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
regions.Add(new MemoryRange(regionStart, regionSize));
|
||||||
|
|
||||||
|
return regions;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ReadImpl(ulong va, Span<byte> data)
|
||||||
|
{
|
||||||
|
if (data.Length == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
AssertValidAddressAndSize(va, (ulong)data.Length);
|
||||||
|
|
||||||
|
int offset = 0, size;
|
||||||
|
|
||||||
|
if ((va & PageMask) != 0)
|
||||||
|
{
|
||||||
|
ulong pa = GetPhysicalAddressChecked(va);
|
||||||
|
|
||||||
|
size = Math.Min(data.Length, PageSize - (int)(va & PageMask));
|
||||||
|
|
||||||
|
_backingMemory.GetSpan(pa, size).CopyTo(data.Slice(0, size));
|
||||||
|
|
||||||
|
offset += size;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (; offset < data.Length; offset += size)
|
||||||
|
{
|
||||||
|
ulong pa = GetPhysicalAddressChecked(va + (ulong)offset);
|
||||||
|
|
||||||
|
size = Math.Min(data.Length - offset, PageSize);
|
||||||
|
|
||||||
|
_backingMemory.GetSpan(pa, size).CopyTo(data.Slice(offset, size));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (InvalidMemoryRegionException)
|
||||||
|
{
|
||||||
|
if (_invalidAccessHandler == null || !_invalidAccessHandler(va))
|
||||||
|
{
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
/// <remarks>
|
||||||
|
/// This function also validates that the given range is both valid and mapped, and will throw if it is not.
|
||||||
|
/// </remarks>
|
||||||
|
public void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false)
|
||||||
|
{
|
||||||
|
AssertValidAddressAndSize(va, size);
|
||||||
|
|
||||||
|
if (precise)
|
||||||
|
{
|
||||||
|
Tracking.VirtualMemoryEvent(va, size, write, precise: true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Software table, used for managed memory tracking.
|
||||||
|
|
||||||
|
int pages = GetPagesCount(va, size, out _);
|
||||||
|
ulong pageStart = va >> PageBits;
|
||||||
|
|
||||||
|
if (pages == 1)
|
||||||
|
{
|
||||||
|
ulong tag = (ulong)(write ? HostMappedPtBits.WriteTracked : HostMappedPtBits.ReadWriteTracked);
|
||||||
|
|
||||||
|
int bit = (int)((pageStart & 31) << 1);
|
||||||
|
|
||||||
|
int pageIndex = (int)(pageStart >> PageToPteShift);
|
||||||
|
ref ulong pageRef = ref _pageBitmap[pageIndex];
|
||||||
|
|
||||||
|
ulong pte = Volatile.Read(ref pageRef);
|
||||||
|
ulong state = ((pte >> bit) & 3);
|
||||||
|
|
||||||
|
if (state >= tag)
|
||||||
|
{
|
||||||
|
Tracking.VirtualMemoryEvent(va, size, write);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (state == 0)
|
||||||
|
{
|
||||||
|
ThrowInvalidMemoryRegionException($"Not mapped: va=0x{va:X16}, size=0x{size:X16}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ulong pageEnd = pageStart + (ulong)pages;
|
||||||
|
|
||||||
|
GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex);
|
||||||
|
|
||||||
|
ulong mask = startMask;
|
||||||
|
|
||||||
|
ulong anyTrackingTag = (ulong)HostMappedPtBits.WriteTrackedReplicated;
|
||||||
|
|
||||||
|
while (pageIndex <= pageEndIndex)
|
||||||
|
{
|
||||||
|
if (pageIndex == pageEndIndex)
|
||||||
|
{
|
||||||
|
mask &= endMask;
|
||||||
|
}
|
||||||
|
|
||||||
|
ref ulong pageRef = ref _pageBitmap[pageIndex++];
|
||||||
|
|
||||||
|
ulong pte = Volatile.Read(ref pageRef);
|
||||||
|
ulong mappedMask = mask & BlockMappedMask;
|
||||||
|
|
||||||
|
ulong mappedPte = pte | (pte >> 1);
|
||||||
|
if ((mappedPte & mappedMask) != mappedMask)
|
||||||
|
{
|
||||||
|
ThrowInvalidMemoryRegionException($"Not mapped: va=0x{va:X16}, size=0x{size:X16}");
|
||||||
|
}
|
||||||
|
|
||||||
|
pte &= mask;
|
||||||
|
if ((pte & anyTrackingTag) != 0) // Search for any tracking.
|
||||||
|
{
|
||||||
|
// Writes trigger any tracking.
|
||||||
|
// Only trigger tracking from reads if both bits are set on any page.
|
||||||
|
if (write || (pte & (pte >> 1) & BlockMappedMask) != 0)
|
||||||
|
{
|
||||||
|
Tracking.VirtualMemoryEvent(va, size, write);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mask = ulong.MaxValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Computes the number of pages in a virtual address range.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="va">Virtual address of the range</param>
|
||||||
|
/// <param name="size">Size of the range</param>
|
||||||
|
/// <param name="startVa">The virtual address of the beginning of the first page</param>
|
||||||
|
/// <remarks>This function does not differentiate between allocated and unallocated pages.</remarks>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private int GetPagesCount(ulong va, ulong size, out ulong startVa)
|
||||||
|
{
|
||||||
|
// WARNING: Always check if ulong does not overflow during the operations.
|
||||||
|
startVa = va & ~(ulong)PageMask;
|
||||||
|
ulong vaSpan = (va - startVa + size + PageMask) & ~(ulong)PageMask;
|
||||||
|
|
||||||
|
return (int)(vaSpan / PageSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection)
|
||||||
|
{
|
||||||
|
// Protection is inverted on software pages, since the default value is 0.
|
||||||
|
protection = (~protection) & MemoryPermission.ReadAndWrite;
|
||||||
|
|
||||||
|
int pages = GetPagesCount(va, size, out va);
|
||||||
|
ulong pageStart = va >> PageBits;
|
||||||
|
|
||||||
|
if (pages == 1)
|
||||||
|
{
|
||||||
|
ulong protTag = protection switch
|
||||||
|
{
|
||||||
|
MemoryPermission.None => (ulong)HostMappedPtBits.Mapped,
|
||||||
|
MemoryPermission.Write => (ulong)HostMappedPtBits.WriteTracked,
|
||||||
|
_ => (ulong)HostMappedPtBits.ReadWriteTracked,
|
||||||
|
};
|
||||||
|
|
||||||
|
int bit = (int)((pageStart & 31) << 1);
|
||||||
|
|
||||||
|
ulong tagMask = 3UL << bit;
|
||||||
|
ulong invTagMask = ~tagMask;
|
||||||
|
|
||||||
|
ulong tag = protTag << bit;
|
||||||
|
|
||||||
|
int pageIndex = (int)(pageStart >> PageToPteShift);
|
||||||
|
ref ulong pageRef = ref _pageBitmap[pageIndex];
|
||||||
|
|
||||||
|
ulong pte;
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
pte = Volatile.Read(ref pageRef);
|
||||||
|
}
|
||||||
|
while ((pte & tagMask) != 0 && Interlocked.CompareExchange(ref pageRef, (pte & invTagMask) | tag, pte) != pte);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ulong pageEnd = pageStart + (ulong)pages;
|
||||||
|
|
||||||
|
GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex);
|
||||||
|
|
||||||
|
ulong mask = startMask;
|
||||||
|
|
||||||
|
ulong protTag = protection switch
|
||||||
|
{
|
||||||
|
MemoryPermission.None => (ulong)HostMappedPtBits.MappedReplicated,
|
||||||
|
MemoryPermission.Write => (ulong)HostMappedPtBits.WriteTrackedReplicated,
|
||||||
|
_ => (ulong)HostMappedPtBits.ReadWriteTrackedReplicated,
|
||||||
|
};
|
||||||
|
|
||||||
|
while (pageIndex <= pageEndIndex)
|
||||||
|
{
|
||||||
|
if (pageIndex == pageEndIndex)
|
||||||
|
{
|
||||||
|
mask &= endMask;
|
||||||
|
}
|
||||||
|
|
||||||
|
ref ulong pageRef = ref _pageBitmap[pageIndex++];
|
||||||
|
|
||||||
|
ulong pte;
|
||||||
|
ulong mappedMask;
|
||||||
|
|
||||||
|
// Change the protection of all 2 bit entries that are mapped.
|
||||||
|
do
|
||||||
|
{
|
||||||
|
pte = Volatile.Read(ref pageRef);
|
||||||
|
|
||||||
|
mappedMask = pte | (pte >> 1);
|
||||||
|
mappedMask |= (mappedMask & BlockMappedMask) << 1;
|
||||||
|
mappedMask &= mask; // Only update mapped pages within the given range.
|
||||||
|
}
|
||||||
|
while (Interlocked.CompareExchange(ref pageRef, (pte & (~mappedMask)) | (protTag & mappedMask), pte) != pte);
|
||||||
|
|
||||||
|
mask = ulong.MaxValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protection = protection switch
|
||||||
|
{
|
||||||
|
MemoryPermission.None => MemoryPermission.ReadAndWrite,
|
||||||
|
MemoryPermission.Write => MemoryPermission.Read,
|
||||||
|
_ => MemoryPermission.None
|
||||||
|
};
|
||||||
|
|
||||||
|
_addressSpace.ReprotectUser(va, size, protection);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public CpuRegionHandle BeginTracking(ulong address, ulong size)
|
||||||
|
{
|
||||||
|
return new CpuRegionHandle(Tracking.BeginTracking(address, size));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public CpuMultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable<IRegionHandle> handles, ulong granularity)
|
||||||
|
{
|
||||||
|
return new CpuMultiRegionHandle(Tracking.BeginGranularTracking(address, size, handles, granularity));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public CpuSmartMultiRegionHandle BeginSmartGranularTracking(ulong address, ulong size, ulong granularity)
|
||||||
|
{
|
||||||
|
return new CpuSmartMultiRegionHandle(Tracking.BeginSmartGranularTracking(address, size, granularity));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds the given address mapping to the page table.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="va">Virtual memory address</param>
|
||||||
|
/// <param name="size">Size to be mapped</param>
|
||||||
|
private void AddMapping(ulong va, ulong size)
|
||||||
|
{
|
||||||
|
int pages = GetPagesCount(va, size, out _);
|
||||||
|
ulong pageStart = va >> PageBits;
|
||||||
|
ulong pageEnd = pageStart + (ulong)pages;
|
||||||
|
|
||||||
|
GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex);
|
||||||
|
|
||||||
|
ulong mask = startMask;
|
||||||
|
|
||||||
|
while (pageIndex <= pageEndIndex)
|
||||||
|
{
|
||||||
|
if (pageIndex == pageEndIndex)
|
||||||
|
{
|
||||||
|
mask &= endMask;
|
||||||
|
}
|
||||||
|
|
||||||
|
ref ulong pageRef = ref _pageBitmap[pageIndex++];
|
||||||
|
|
||||||
|
ulong pte;
|
||||||
|
ulong mappedMask;
|
||||||
|
|
||||||
|
// Map all 2-bit entries that are unmapped.
|
||||||
|
do
|
||||||
|
{
|
||||||
|
pte = Volatile.Read(ref pageRef);
|
||||||
|
|
||||||
|
mappedMask = pte | (pte >> 1);
|
||||||
|
mappedMask |= (mappedMask & BlockMappedMask) << 1;
|
||||||
|
mappedMask |= ~mask; // Treat everything outside the range as mapped, thus unchanged.
|
||||||
|
}
|
||||||
|
while (Interlocked.CompareExchange(ref pageRef, (pte & mappedMask) | (BlockMappedMask & (~mappedMask)), pte) != pte);
|
||||||
|
|
||||||
|
mask = ulong.MaxValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes the given address mapping from the page table.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="va">Virtual memory address</param>
|
||||||
|
/// <param name="size">Size to be unmapped</param>
|
||||||
|
private void RemoveMapping(ulong va, ulong size)
|
||||||
|
{
|
||||||
|
int pages = GetPagesCount(va, size, out _);
|
||||||
|
ulong pageStart = va >> PageBits;
|
||||||
|
ulong pageEnd = pageStart + (ulong)pages;
|
||||||
|
|
||||||
|
GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex);
|
||||||
|
|
||||||
|
startMask = ~startMask;
|
||||||
|
endMask = ~endMask;
|
||||||
|
|
||||||
|
ulong mask = startMask;
|
||||||
|
|
||||||
|
while (pageIndex <= pageEndIndex)
|
||||||
|
{
|
||||||
|
if (pageIndex == pageEndIndex)
|
||||||
|
{
|
||||||
|
mask |= endMask;
|
||||||
|
}
|
||||||
|
|
||||||
|
ref ulong pageRef = ref _pageBitmap[pageIndex++];
|
||||||
|
ulong pte;
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
pte = Volatile.Read(ref pageRef);
|
||||||
|
}
|
||||||
|
while (Interlocked.CompareExchange(ref pageRef, pte & mask, pte) != pte);
|
||||||
|
|
||||||
|
mask = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ulong GetPhysicalAddressChecked(ulong va)
|
||||||
|
{
|
||||||
|
if (!IsMapped(va))
|
||||||
|
{
|
||||||
|
ThrowInvalidMemoryRegionException($"Not mapped: va=0x{va:X16}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetPhysicalAddressInternal(va);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ulong GetPhysicalAddressInternal(ulong va)
|
||||||
|
{
|
||||||
|
return _pageTable.Read(va) + (va & PageMask);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Disposes of resources used by the memory manager.
|
||||||
|
/// </summary>
|
||||||
|
protected override void Destroy()
|
||||||
|
{
|
||||||
|
_addressSpace.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ThrowInvalidMemoryRegionException(string message) => throw new InvalidMemoryRegionException(message);
|
||||||
|
}
|
||||||
|
}
|
25
Ryujinx.Cpu/AppleHv/HvVcpu.cs
Normal file
25
Ryujinx.Cpu/AppleHv/HvVcpu.cs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
namespace Ryujinx.Cpu.AppleHv
|
||||||
|
{
|
||||||
|
unsafe class HvVcpu
|
||||||
|
{
|
||||||
|
public readonly ulong Handle;
|
||||||
|
public readonly hv_vcpu_exit_t* ExitInfo;
|
||||||
|
public readonly IHvExecutionContext ShadowContext;
|
||||||
|
public readonly IHvExecutionContext NativeContext;
|
||||||
|
public readonly bool IsEphemeral;
|
||||||
|
|
||||||
|
public HvVcpu(
|
||||||
|
ulong handle,
|
||||||
|
hv_vcpu_exit_t* exitInfo,
|
||||||
|
IHvExecutionContext shadowContext,
|
||||||
|
IHvExecutionContext nativeContext,
|
||||||
|
bool isEphemeral)
|
||||||
|
{
|
||||||
|
Handle = handle;
|
||||||
|
ExitInfo = exitInfo;
|
||||||
|
ShadowContext = shadowContext;
|
||||||
|
NativeContext = nativeContext;
|
||||||
|
IsEphemeral = isEphemeral;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
103
Ryujinx.Cpu/AppleHv/HvVcpuPool.cs
Normal file
103
Ryujinx.Cpu/AppleHv/HvVcpuPool.cs
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
|
namespace Ryujinx.Cpu.AppleHv
|
||||||
|
{
|
||||||
|
class HvVcpuPool
|
||||||
|
{
|
||||||
|
// Since there's a limit on the number of VCPUs we can create,
|
||||||
|
// and we assign one VCPU per guest thread, we need to ensure
|
||||||
|
// there are enough VCPUs available for at least the maximum number of active guest threads.
|
||||||
|
// To do that, we always destroy and re-create VCPUs that are above a given limit.
|
||||||
|
// Those VCPUs are called "ephemeral" here because they are not kept for long.
|
||||||
|
//
|
||||||
|
// In the future, we might want to consider a smarter approach that only makes
|
||||||
|
// VCPUs for threads that are not running frequently "ephemeral", but this is
|
||||||
|
// complicated because VCPUs can only be destroyed by the same thread that created them.
|
||||||
|
|
||||||
|
private const int MaxActiveVcpus = 4;
|
||||||
|
|
||||||
|
public static readonly HvVcpuPool Instance = new HvVcpuPool();
|
||||||
|
|
||||||
|
private int _totalVcpus;
|
||||||
|
private int _maxVcpus;
|
||||||
|
|
||||||
|
public HvVcpuPool()
|
||||||
|
{
|
||||||
|
HvApi.hv_vm_get_max_vcpu_count(out uint maxVcpuCount).ThrowOnError();
|
||||||
|
_maxVcpus = (int)maxVcpuCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HvVcpu Create(HvAddressSpace addressSpace, IHvExecutionContext shadowContext, Action<IHvExecutionContext> swapContext)
|
||||||
|
{
|
||||||
|
HvVcpu vcpu = CreateNew(addressSpace, shadowContext);
|
||||||
|
vcpu.NativeContext.Load(shadowContext);
|
||||||
|
swapContext(vcpu.NativeContext);
|
||||||
|
return vcpu;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Destroy(HvVcpu vcpu, Action<IHvExecutionContext> swapContext)
|
||||||
|
{
|
||||||
|
vcpu.ShadowContext.Load(vcpu.NativeContext);
|
||||||
|
swapContext(vcpu.ShadowContext);
|
||||||
|
DestroyVcpu(vcpu);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Return(HvVcpu vcpu, Action<IHvExecutionContext> swapContext)
|
||||||
|
{
|
||||||
|
if (vcpu.IsEphemeral)
|
||||||
|
{
|
||||||
|
Destroy(vcpu, swapContext);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public HvVcpu Rent(HvAddressSpace addressSpace, IHvExecutionContext shadowContext, HvVcpu vcpu, Action<IHvExecutionContext> swapContext)
|
||||||
|
{
|
||||||
|
if (vcpu.IsEphemeral)
|
||||||
|
{
|
||||||
|
return Create(addressSpace, shadowContext, swapContext);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return vcpu;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe HvVcpu CreateNew(HvAddressSpace addressSpace, IHvExecutionContext shadowContext)
|
||||||
|
{
|
||||||
|
int newCount = IncrementVcpuCount();
|
||||||
|
bool isEphemeral = newCount > _maxVcpus - MaxActiveVcpus;
|
||||||
|
|
||||||
|
// Create VCPU.
|
||||||
|
hv_vcpu_exit_t* exitInfo = null;
|
||||||
|
HvApi.hv_vcpu_create(out ulong vcpuHandle, ref exitInfo, IntPtr.Zero).ThrowOnError();
|
||||||
|
|
||||||
|
// Enable FP and SIMD instructions.
|
||||||
|
HvApi.hv_vcpu_set_sys_reg(vcpuHandle, hv_sys_reg_t.HV_SYS_REG_CPACR_EL1, 0b11 << 20).ThrowOnError();
|
||||||
|
|
||||||
|
addressSpace.InitializeMmu(vcpuHandle);
|
||||||
|
|
||||||
|
HvExecutionContextVcpu nativeContext = new HvExecutionContextVcpu(vcpuHandle);
|
||||||
|
|
||||||
|
HvVcpu vcpu = new HvVcpu(vcpuHandle, exitInfo, shadowContext, nativeContext, isEphemeral);
|
||||||
|
|
||||||
|
return vcpu;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DestroyVcpu(HvVcpu vcpu)
|
||||||
|
{
|
||||||
|
HvApi.hv_vcpu_destroy(vcpu.Handle).ThrowOnError();
|
||||||
|
DecrementVcpuCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
private int IncrementVcpuCount()
|
||||||
|
{
|
||||||
|
return Interlocked.Increment(ref _totalVcpus);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DecrementVcpuCount()
|
||||||
|
{
|
||||||
|
Interlocked.Decrement(ref _totalVcpus);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
68
Ryujinx.Cpu/AppleHv/HvVm.cs
Normal file
68
Ryujinx.Cpu/AppleHv/HvVm.cs
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
using Ryujinx.Memory;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Ryujinx.Cpu.AppleHv
|
||||||
|
{
|
||||||
|
static class HvVm
|
||||||
|
{
|
||||||
|
// This alignment allows us to use larger blocks on the page table.
|
||||||
|
private const ulong AsIpaAlignment = 1UL << 30;
|
||||||
|
|
||||||
|
private static int _addressSpaces;
|
||||||
|
private static HvIpaAllocator _ipaAllocator;
|
||||||
|
private static object _lock = new object();
|
||||||
|
|
||||||
|
public static (ulong, HvIpaAllocator) CreateAddressSpace(MemoryBlock block)
|
||||||
|
{
|
||||||
|
HvIpaAllocator ipaAllocator;
|
||||||
|
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
if (++_addressSpaces == 1)
|
||||||
|
{
|
||||||
|
HvApi.hv_vm_create(IntPtr.Zero).ThrowOnError();
|
||||||
|
_ipaAllocator = ipaAllocator = new HvIpaAllocator();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ipaAllocator = _ipaAllocator;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ulong baseAddress;
|
||||||
|
|
||||||
|
lock (ipaAllocator)
|
||||||
|
{
|
||||||
|
baseAddress = ipaAllocator.Allocate(block.Size, AsIpaAlignment);
|
||||||
|
}
|
||||||
|
|
||||||
|
var rwx = hv_memory_flags_t.HV_MEMORY_READ | hv_memory_flags_t.HV_MEMORY_WRITE | hv_memory_flags_t.HV_MEMORY_EXEC;
|
||||||
|
|
||||||
|
HvApi.hv_vm_map((ulong)block.Pointer, baseAddress, block.Size, rwx).ThrowOnError();
|
||||||
|
|
||||||
|
return (baseAddress, ipaAllocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void DestroyAddressSpace(ulong address, ulong size)
|
||||||
|
{
|
||||||
|
HvApi.hv_vm_unmap(address, size);
|
||||||
|
|
||||||
|
HvIpaAllocator ipaAllocator;
|
||||||
|
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
if (--_addressSpaces == 0)
|
||||||
|
{
|
||||||
|
HvApi.hv_vm_destroy().ThrowOnError();
|
||||||
|
}
|
||||||
|
|
||||||
|
ipaAllocator = _ipaAllocator;
|
||||||
|
}
|
||||||
|
|
||||||
|
lock (ipaAllocator)
|
||||||
|
{
|
||||||
|
ipaAllocator.Free(address, size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
46
Ryujinx.Cpu/AppleHv/IHvExecutionContext.cs
Normal file
46
Ryujinx.Cpu/AppleHv/IHvExecutionContext.cs
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
using ARMeilleure.State;
|
||||||
|
|
||||||
|
namespace Ryujinx.Cpu.AppleHv
|
||||||
|
{
|
||||||
|
public interface IHvExecutionContext
|
||||||
|
{
|
||||||
|
ulong Pc { get; set; }
|
||||||
|
ulong ElrEl1 { get; set; }
|
||||||
|
ulong EsrEl1 { get; set; }
|
||||||
|
|
||||||
|
long TpidrEl0 { get; set; }
|
||||||
|
long TpidrroEl0 { get; set; }
|
||||||
|
|
||||||
|
uint Pstate { get; set; }
|
||||||
|
|
||||||
|
uint Fpcr { get; set; }
|
||||||
|
uint Fpsr { get; set; }
|
||||||
|
|
||||||
|
ulong GetX(int index);
|
||||||
|
void SetX(int index, ulong value);
|
||||||
|
|
||||||
|
V128 GetV(int index);
|
||||||
|
void SetV(int index, V128 value);
|
||||||
|
|
||||||
|
public void Load(IHvExecutionContext context)
|
||||||
|
{
|
||||||
|
Pc = context.Pc;
|
||||||
|
ElrEl1 = context.ElrEl1;
|
||||||
|
EsrEl1 = context.EsrEl1;
|
||||||
|
TpidrEl0 = context.TpidrEl0;
|
||||||
|
TpidrroEl0 = context.TpidrroEl0;
|
||||||
|
Pstate = context.Pstate;
|
||||||
|
Fpcr = context.Fpcr;
|
||||||
|
Fpsr = context.Fpsr;
|
||||||
|
|
||||||
|
for (int i = 0; i < 32; i++)
|
||||||
|
{
|
||||||
|
SetX(i, context.GetX(i));
|
||||||
|
SetV(i, context.GetV(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RequestInterrupt();
|
||||||
|
bool GetAndClearInterruptRequested();
|
||||||
|
}
|
||||||
|
}
|
@@ -7,9 +7,7 @@ using Ryujinx.Graphics.GAL.Multithreading.Commands.Sampler;
|
|||||||
using Ryujinx.Graphics.GAL.Multithreading.Commands.Texture;
|
using Ryujinx.Graphics.GAL.Multithreading.Commands.Texture;
|
||||||
using Ryujinx.Graphics.GAL.Multithreading.Commands.Window;
|
using Ryujinx.Graphics.GAL.Multithreading.Commands.Window;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
@@ -30,207 +28,115 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
|||||||
|
|
||||||
public static int GetMaxCommandSize()
|
public static int GetMaxCommandSize()
|
||||||
{
|
{
|
||||||
Assembly assembly = typeof(CommandHelper).Assembly;
|
return InitLookup() + 1; // 1 byte reserved for command size.
|
||||||
|
|
||||||
IEnumerable<Type> commands = assembly.GetTypes().Where(type => typeof(IGALCommand).IsAssignableFrom(type) && type.IsValueType);
|
|
||||||
|
|
||||||
int maxSize = commands.Max(command =>
|
|
||||||
{
|
|
||||||
MethodInfo method = typeof(Unsafe).GetMethod(nameof(Unsafe.SizeOf));
|
|
||||||
MethodInfo generic = method.MakeGenericMethod(command);
|
|
||||||
int size = (int)generic.Invoke(null, null);
|
|
||||||
|
|
||||||
return size;
|
|
||||||
});
|
|
||||||
|
|
||||||
InitLookup();
|
|
||||||
|
|
||||||
return maxSize + 1; // 1 byte reserved for command size.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void InitLookup()
|
private static int InitLookup()
|
||||||
{
|
{
|
||||||
_lookup[(int)CommandType.Action] = (memory, threaded, renderer) =>
|
int maxCommandSize = 0;
|
||||||
ActionCommand.Run(ref GetCommand<ActionCommand>(memory), threaded, renderer);
|
|
||||||
_lookup[(int)CommandType.CreateBuffer] = (memory, threaded, renderer) =>
|
|
||||||
CreateBufferCommand.Run(ref GetCommand<CreateBufferCommand>(memory), threaded, renderer);
|
|
||||||
_lookup[(int)CommandType.CreateProgram] = (memory, threaded, renderer) =>
|
|
||||||
CreateProgramCommand.Run(ref GetCommand<CreateProgramCommand>(memory), threaded, renderer);
|
|
||||||
_lookup[(int)CommandType.CreateSampler] = (memory, threaded, renderer) =>
|
|
||||||
CreateSamplerCommand.Run(ref GetCommand<CreateSamplerCommand>(memory), threaded, renderer);
|
|
||||||
_lookup[(int)CommandType.CreateSync] = (memory, threaded, renderer) =>
|
|
||||||
CreateSyncCommand.Run(ref GetCommand<CreateSyncCommand>(memory), threaded, renderer);
|
|
||||||
_lookup[(int)CommandType.CreateTexture] = (memory, threaded, renderer) =>
|
|
||||||
CreateTextureCommand.Run(ref GetCommand<CreateTextureCommand>(memory), threaded, renderer);
|
|
||||||
_lookup[(int)CommandType.GetCapabilities] = (memory, threaded, renderer) =>
|
|
||||||
GetCapabilitiesCommand.Run(ref GetCommand<GetCapabilitiesCommand>(memory), threaded, renderer);
|
|
||||||
_lookup[(int)CommandType.PreFrame] = (memory, threaded, renderer) =>
|
|
||||||
PreFrameCommand.Run(ref GetCommand<PreFrameCommand>(memory), threaded, renderer);
|
|
||||||
_lookup[(int)CommandType.ReportCounter] = (memory, threaded, renderer) =>
|
|
||||||
ReportCounterCommand.Run(ref GetCommand<ReportCounterCommand>(memory), threaded, renderer);
|
|
||||||
_lookup[(int)CommandType.ResetCounter] = (memory, threaded, renderer) =>
|
|
||||||
ResetCounterCommand.Run(ref GetCommand<ResetCounterCommand>(memory), threaded, renderer);
|
|
||||||
_lookup[(int)CommandType.UpdateCounters] = (memory, threaded, renderer) =>
|
|
||||||
UpdateCountersCommand.Run(ref GetCommand<UpdateCountersCommand>(memory), threaded, renderer);
|
|
||||||
|
|
||||||
_lookup[(int)CommandType.BufferDispose] = (memory, threaded, renderer) =>
|
void Register<T>(CommandType commandType) where T : unmanaged, IGALCommand, IGALCommand<T>
|
||||||
BufferDisposeCommand.Run(ref GetCommand<BufferDisposeCommand>(memory), threaded, renderer);
|
{
|
||||||
_lookup[(int)CommandType.BufferGetData] = (memory, threaded, renderer) =>
|
maxCommandSize = Math.Max(maxCommandSize, Unsafe.SizeOf<T>());
|
||||||
BufferGetDataCommand.Run(ref GetCommand<BufferGetDataCommand>(memory), threaded, renderer);
|
_lookup[(int)commandType] = (memory, threaded, renderer) => T.Run(ref GetCommand<T>(memory), threaded, renderer);
|
||||||
_lookup[(int)CommandType.BufferSetData] = (memory, threaded, renderer) =>
|
}
|
||||||
BufferSetDataCommand.Run(ref GetCommand<BufferSetDataCommand>(memory), threaded, renderer);
|
|
||||||
|
|
||||||
_lookup[(int)CommandType.CounterEventDispose] = (memory, threaded, renderer) =>
|
Register<ActionCommand>(CommandType.Action);
|
||||||
CounterEventDisposeCommand.Run(ref GetCommand<CounterEventDisposeCommand>(memory), threaded, renderer);
|
Register<CreateBufferCommand>(CommandType.CreateBuffer);
|
||||||
_lookup[(int)CommandType.CounterEventFlush] = (memory, threaded, renderer) =>
|
Register<CreateProgramCommand>(CommandType.CreateProgram);
|
||||||
CounterEventFlushCommand.Run(ref GetCommand<CounterEventFlushCommand>(memory), threaded, renderer);
|
Register<CreateSamplerCommand>(CommandType.CreateSampler);
|
||||||
|
Register<CreateSyncCommand>(CommandType.CreateSync);
|
||||||
|
Register<CreateTextureCommand>(CommandType.CreateTexture);
|
||||||
|
Register<GetCapabilitiesCommand>(CommandType.GetCapabilities);
|
||||||
|
Register<PreFrameCommand>(CommandType.PreFrame);
|
||||||
|
Register<ReportCounterCommand>(CommandType.ReportCounter);
|
||||||
|
Register<ResetCounterCommand>(CommandType.ResetCounter);
|
||||||
|
Register<UpdateCountersCommand>(CommandType.UpdateCounters);
|
||||||
|
|
||||||
_lookup[(int)CommandType.ProgramDispose] = (memory, threaded, renderer) =>
|
Register<BufferDisposeCommand>(CommandType.BufferDispose);
|
||||||
ProgramDisposeCommand.Run(ref GetCommand<ProgramDisposeCommand>(memory), threaded, renderer);
|
Register<BufferGetDataCommand>(CommandType.BufferGetData);
|
||||||
_lookup[(int)CommandType.ProgramGetBinary] = (memory, threaded, renderer) =>
|
Register<BufferSetDataCommand>(CommandType.BufferSetData);
|
||||||
ProgramGetBinaryCommand.Run(ref GetCommand<ProgramGetBinaryCommand>(memory), threaded, renderer);
|
|
||||||
_lookup[(int)CommandType.ProgramCheckLink] = (memory, threaded, renderer) =>
|
|
||||||
ProgramCheckLinkCommand.Run(ref GetCommand<ProgramCheckLinkCommand>(memory), threaded, renderer);
|
|
||||||
|
|
||||||
_lookup[(int)CommandType.SamplerDispose] = (memory, threaded, renderer) =>
|
Register<CounterEventDisposeCommand>(CommandType.CounterEventDispose);
|
||||||
SamplerDisposeCommand.Run(ref GetCommand<SamplerDisposeCommand>(memory), threaded, renderer);
|
Register<CounterEventFlushCommand>(CommandType.CounterEventFlush);
|
||||||
|
|
||||||
_lookup[(int)CommandType.TextureCopyTo] = (memory, threaded, renderer) =>
|
Register<ProgramDisposeCommand>(CommandType.ProgramDispose);
|
||||||
TextureCopyToCommand.Run(ref GetCommand<TextureCopyToCommand>(memory), threaded, renderer);
|
Register<ProgramGetBinaryCommand>(CommandType.ProgramGetBinary);
|
||||||
_lookup[(int)CommandType.TextureCopyToScaled] = (memory, threaded, renderer) =>
|
Register<ProgramCheckLinkCommand>(CommandType.ProgramCheckLink);
|
||||||
TextureCopyToScaledCommand.Run(ref GetCommand<TextureCopyToScaledCommand>(memory), threaded, renderer);
|
|
||||||
_lookup[(int)CommandType.TextureCopyToSlice] = (memory, threaded, renderer) =>
|
|
||||||
TextureCopyToSliceCommand.Run(ref GetCommand<TextureCopyToSliceCommand>(memory), threaded, renderer);
|
|
||||||
_lookup[(int)CommandType.TextureCreateView] = (memory, threaded, renderer) =>
|
|
||||||
TextureCreateViewCommand.Run(ref GetCommand<TextureCreateViewCommand>(memory), threaded, renderer);
|
|
||||||
_lookup[(int)CommandType.TextureGetData] = (memory, threaded, renderer) =>
|
|
||||||
TextureGetDataCommand.Run(ref GetCommand<TextureGetDataCommand>(memory), threaded, renderer);
|
|
||||||
_lookup[(int)CommandType.TextureGetDataSlice] = (memory, threaded, renderer) =>
|
|
||||||
TextureGetDataSliceCommand.Run(ref GetCommand<TextureGetDataSliceCommand>(memory), threaded, renderer);
|
|
||||||
_lookup[(int)CommandType.TextureRelease] = (memory, threaded, renderer) =>
|
|
||||||
TextureReleaseCommand.Run(ref GetCommand<TextureReleaseCommand>(memory), threaded, renderer);
|
|
||||||
_lookup[(int)CommandType.TextureSetData] = (memory, threaded, renderer) =>
|
|
||||||
TextureSetDataCommand.Run(ref GetCommand<TextureSetDataCommand>(memory), threaded, renderer);
|
|
||||||
_lookup[(int)CommandType.TextureSetDataSlice] = (memory, threaded, renderer) =>
|
|
||||||
TextureSetDataSliceCommand.Run(ref GetCommand<TextureSetDataSliceCommand>(memory), threaded, renderer);
|
|
||||||
_lookup[(int)CommandType.TextureSetDataSliceRegion] = (memory, threaded, renderer) =>
|
|
||||||
TextureSetDataSliceRegionCommand.Run(ref GetCommand<TextureSetDataSliceRegionCommand>(memory), threaded, renderer);
|
|
||||||
_lookup[(int)CommandType.TextureSetStorage] = (memory, threaded, renderer) =>
|
|
||||||
TextureSetStorageCommand.Run(ref GetCommand<TextureSetStorageCommand>(memory), threaded, renderer);
|
|
||||||
|
|
||||||
_lookup[(int)CommandType.WindowPresent] = (memory, threaded, renderer) =>
|
Register<SamplerDisposeCommand>(CommandType.SamplerDispose);
|
||||||
WindowPresentCommand.Run(ref GetCommand<WindowPresentCommand>(memory), threaded, renderer);
|
|
||||||
|
|
||||||
_lookup[(int)CommandType.Barrier] = (memory, threaded, renderer) =>
|
Register<TextureCopyToCommand>(CommandType.TextureCopyTo);
|
||||||
BarrierCommand.Run(ref GetCommand<BarrierCommand>(memory), threaded, renderer);
|
Register<TextureCopyToScaledCommand>(CommandType.TextureCopyToScaled);
|
||||||
_lookup[(int)CommandType.BeginTransformFeedback] = (memory, threaded, renderer) =>
|
Register<TextureCopyToSliceCommand>(CommandType.TextureCopyToSlice);
|
||||||
BeginTransformFeedbackCommand.Run(ref GetCommand<BeginTransformFeedbackCommand>(memory), threaded, renderer);
|
Register<TextureCreateViewCommand>(CommandType.TextureCreateView);
|
||||||
_lookup[(int)CommandType.ClearBuffer] = (memory, threaded, renderer) =>
|
Register<TextureGetDataCommand>(CommandType.TextureGetData);
|
||||||
ClearBufferCommand.Run(ref GetCommand<ClearBufferCommand>(memory), threaded, renderer);
|
Register<TextureGetDataSliceCommand>(CommandType.TextureGetDataSlice);
|
||||||
_lookup[(int)CommandType.ClearRenderTargetColor] = (memory, threaded, renderer) =>
|
Register<TextureReleaseCommand>(CommandType.TextureRelease);
|
||||||
ClearRenderTargetColorCommand.Run(ref GetCommand<ClearRenderTargetColorCommand>(memory), threaded, renderer);
|
Register<TextureSetDataCommand>(CommandType.TextureSetData);
|
||||||
_lookup[(int)CommandType.ClearRenderTargetDepthStencil] = (memory, threaded, renderer) =>
|
Register<TextureSetDataSliceCommand>(CommandType.TextureSetDataSlice);
|
||||||
ClearRenderTargetDepthStencilCommand.Run(ref GetCommand<ClearRenderTargetDepthStencilCommand>(memory), threaded, renderer);
|
Register<TextureSetDataSliceRegionCommand>(CommandType.TextureSetDataSliceRegion);
|
||||||
_lookup[(int)CommandType.CommandBufferBarrier] = (memory, threaded, renderer) =>
|
Register<TextureSetStorageCommand>(CommandType.TextureSetStorage);
|
||||||
CommandBufferBarrierCommand.Run(ref GetCommand<CommandBufferBarrierCommand>(memory), threaded, renderer);
|
|
||||||
_lookup[(int)CommandType.CopyBuffer] = (memory, threaded, renderer) =>
|
Register<WindowPresentCommand>(CommandType.WindowPresent);
|
||||||
CopyBufferCommand.Run(ref GetCommand<CopyBufferCommand>(memory), threaded, renderer);
|
|
||||||
_lookup[(int)CommandType.DispatchCompute] = (memory, threaded, renderer) =>
|
Register<BarrierCommand>(CommandType.Barrier);
|
||||||
DispatchComputeCommand.Run(ref GetCommand<DispatchComputeCommand>(memory), threaded, renderer);
|
Register<BeginTransformFeedbackCommand>(CommandType.BeginTransformFeedback);
|
||||||
_lookup[(int)CommandType.Draw] = (memory, threaded, renderer) =>
|
Register<ClearBufferCommand>(CommandType.ClearBuffer);
|
||||||
DrawCommand.Run(ref GetCommand<DrawCommand>(memory), threaded, renderer);
|
Register<ClearRenderTargetColorCommand>(CommandType.ClearRenderTargetColor);
|
||||||
_lookup[(int)CommandType.DrawIndexed] = (memory, threaded, renderer) =>
|
Register<ClearRenderTargetDepthStencilCommand>(CommandType.ClearRenderTargetDepthStencil);
|
||||||
DrawIndexedCommand.Run(ref GetCommand<DrawIndexedCommand>(memory), threaded, renderer);
|
Register<CommandBufferBarrierCommand>(CommandType.CommandBufferBarrier);
|
||||||
_lookup[(int)CommandType.DrawIndexedIndirect] = (memory, threaded, renderer) =>
|
Register<CopyBufferCommand>(CommandType.CopyBuffer);
|
||||||
DrawIndexedIndirectCommand.Run(ref GetCommand<DrawIndexedIndirectCommand>(memory), threaded, renderer);
|
Register<DispatchComputeCommand>(CommandType.DispatchCompute);
|
||||||
_lookup[(int)CommandType.DrawIndexedIndirectCount] = (memory, threaded, renderer) =>
|
Register<DrawCommand>(CommandType.Draw);
|
||||||
DrawIndexedIndirectCountCommand.Run(ref GetCommand<DrawIndexedIndirectCountCommand>(memory), threaded, renderer);
|
Register<DrawIndexedCommand>(CommandType.DrawIndexed);
|
||||||
_lookup[(int)CommandType.DrawIndirect] = (memory, threaded, renderer) =>
|
Register<DrawIndexedIndirectCommand>(CommandType.DrawIndexedIndirect);
|
||||||
DrawIndirectCommand.Run(ref GetCommand<DrawIndirectCommand>(memory), threaded, renderer);
|
Register<DrawIndexedIndirectCountCommand>(CommandType.DrawIndexedIndirectCount);
|
||||||
_lookup[(int)CommandType.DrawIndirectCount] = (memory, threaded, renderer) =>
|
Register<DrawIndirectCommand>(CommandType.DrawIndirect);
|
||||||
DrawIndirectCountCommand.Run(ref GetCommand<DrawIndirectCountCommand>(memory), threaded, renderer);
|
Register<DrawIndirectCountCommand>(CommandType.DrawIndirectCount);
|
||||||
_lookup[(int)CommandType.DrawTexture] = (memory, threaded, renderer) =>
|
Register<DrawTextureCommand>(CommandType.DrawTexture);
|
||||||
DrawTextureCommand.Run(ref GetCommand<DrawTextureCommand>(memory), threaded, renderer);
|
Register<EndHostConditionalRenderingCommand>(CommandType.EndHostConditionalRendering);
|
||||||
_lookup[(int)CommandType.EndHostConditionalRendering] = (memory, threaded, renderer) =>
|
Register<EndTransformFeedbackCommand>(CommandType.EndTransformFeedback);
|
||||||
EndHostConditionalRenderingCommand.Run(renderer);
|
Register<SetAlphaTestCommand>(CommandType.SetAlphaTest);
|
||||||
_lookup[(int)CommandType.EndTransformFeedback] = (memory, threaded, renderer) =>
|
Register<SetBlendStateCommand>(CommandType.SetBlendState);
|
||||||
EndTransformFeedbackCommand.Run(ref GetCommand<EndTransformFeedbackCommand>(memory), threaded, renderer);
|
Register<SetDepthBiasCommand>(CommandType.SetDepthBias);
|
||||||
_lookup[(int)CommandType.SetAlphaTest] = (memory, threaded, renderer) =>
|
Register<SetDepthClampCommand>(CommandType.SetDepthClamp);
|
||||||
SetAlphaTestCommand.Run(ref GetCommand<SetAlphaTestCommand>(memory), threaded, renderer);
|
Register<SetDepthModeCommand>(CommandType.SetDepthMode);
|
||||||
_lookup[(int)CommandType.SetBlendState] = (memory, threaded, renderer) =>
|
Register<SetDepthTestCommand>(CommandType.SetDepthTest);
|
||||||
SetBlendStateCommand.Run(ref GetCommand<SetBlendStateCommand>(memory), threaded, renderer);
|
Register<SetFaceCullingCommand>(CommandType.SetFaceCulling);
|
||||||
_lookup[(int)CommandType.SetDepthBias] = (memory, threaded, renderer) =>
|
Register<SetFrontFaceCommand>(CommandType.SetFrontFace);
|
||||||
SetDepthBiasCommand.Run(ref GetCommand<SetDepthBiasCommand>(memory), threaded, renderer);
|
Register<SetStorageBuffersCommand>(CommandType.SetStorageBuffers);
|
||||||
_lookup[(int)CommandType.SetDepthClamp] = (memory, threaded, renderer) =>
|
Register<SetTransformFeedbackBuffersCommand>(CommandType.SetTransformFeedbackBuffers);
|
||||||
SetDepthClampCommand.Run(ref GetCommand<SetDepthClampCommand>(memory), threaded, renderer);
|
Register<SetUniformBuffersCommand>(CommandType.SetUniformBuffers);
|
||||||
_lookup[(int)CommandType.SetDepthMode] = (memory, threaded, renderer) =>
|
Register<SetImageCommand>(CommandType.SetImage);
|
||||||
SetDepthModeCommand.Run(ref GetCommand<SetDepthModeCommand>(memory), threaded, renderer);
|
Register<SetIndexBufferCommand>(CommandType.SetIndexBuffer);
|
||||||
_lookup[(int)CommandType.SetDepthTest] = (memory, threaded, renderer) =>
|
Register<SetLineParametersCommand>(CommandType.SetLineParameters);
|
||||||
SetDepthTestCommand.Run(ref GetCommand<SetDepthTestCommand>(memory), threaded, renderer);
|
Register<SetLogicOpStateCommand>(CommandType.SetLogicOpState);
|
||||||
_lookup[(int)CommandType.SetFaceCulling] = (memory, threaded, renderer) =>
|
Register<SetMultisampleStateCommand>(CommandType.SetMultisampleState);
|
||||||
SetFaceCullingCommand.Run(ref GetCommand<SetFaceCullingCommand>(memory), threaded, renderer);
|
Register<SetPatchParametersCommand>(CommandType.SetPatchParameters);
|
||||||
_lookup[(int)CommandType.SetFrontFace] = (memory, threaded, renderer) =>
|
Register<SetPointParametersCommand>(CommandType.SetPointParameters);
|
||||||
SetFrontFaceCommand.Run(ref GetCommand<SetFrontFaceCommand>(memory), threaded, renderer);
|
Register<SetPolygonModeCommand>(CommandType.SetPolygonMode);
|
||||||
_lookup[(int)CommandType.SetStorageBuffers] = (memory, threaded, renderer) =>
|
Register<SetPrimitiveRestartCommand>(CommandType.SetPrimitiveRestart);
|
||||||
SetStorageBuffersCommand.Run(ref GetCommand<SetStorageBuffersCommand>(memory), threaded, renderer);
|
Register<SetPrimitiveTopologyCommand>(CommandType.SetPrimitiveTopology);
|
||||||
_lookup[(int)CommandType.SetTransformFeedbackBuffers] = (memory, threaded, renderer) =>
|
Register<SetProgramCommand>(CommandType.SetProgram);
|
||||||
SetTransformFeedbackBuffersCommand.Run(ref GetCommand<SetTransformFeedbackBuffersCommand>(memory), threaded, renderer);
|
Register<SetRasterizerDiscardCommand>(CommandType.SetRasterizerDiscard);
|
||||||
_lookup[(int)CommandType.SetUniformBuffers] = (memory, threaded, renderer) =>
|
Register<SetRenderTargetColorMasksCommand>(CommandType.SetRenderTargetColorMasks);
|
||||||
SetUniformBuffersCommand.Run(ref GetCommand<SetUniformBuffersCommand>(memory), threaded, renderer);
|
Register<SetRenderTargetScaleCommand>(CommandType.SetRenderTargetScale);
|
||||||
_lookup[(int)CommandType.SetImage] = (memory, threaded, renderer) =>
|
Register<SetRenderTargetsCommand>(CommandType.SetRenderTargets);
|
||||||
SetImageCommand.Run(ref GetCommand<SetImageCommand>(memory), threaded, renderer);
|
Register<SetScissorsCommand>(CommandType.SetScissor);
|
||||||
_lookup[(int)CommandType.SetIndexBuffer] = (memory, threaded, renderer) =>
|
Register<SetStencilTestCommand>(CommandType.SetStencilTest);
|
||||||
SetIndexBufferCommand.Run(ref GetCommand<SetIndexBufferCommand>(memory), threaded, renderer);
|
Register<SetTextureAndSamplerCommand>(CommandType.SetTextureAndSampler);
|
||||||
_lookup[(int)CommandType.SetLineParameters] = (memory, threaded, renderer) =>
|
Register<SetUserClipDistanceCommand>(CommandType.SetUserClipDistance);
|
||||||
SetLineParametersCommand.Run(ref GetCommand<SetLineParametersCommand>(memory), threaded, renderer);
|
Register<SetVertexAttribsCommand>(CommandType.SetVertexAttribs);
|
||||||
_lookup[(int)CommandType.SetLogicOpState] = (memory, threaded, renderer) =>
|
Register<SetVertexBuffersCommand>(CommandType.SetVertexBuffers);
|
||||||
SetLogicOpStateCommand.Run(ref GetCommand<SetLogicOpStateCommand>(memory), threaded, renderer);
|
Register<SetViewportsCommand>(CommandType.SetViewports);
|
||||||
_lookup[(int)CommandType.SetMultisampleState] = (memory, threaded, renderer) =>
|
Register<TextureBarrierCommand>(CommandType.TextureBarrier);
|
||||||
SetMultisampleStateCommand.Run(ref GetCommand<SetMultisampleStateCommand>(memory), threaded, renderer);
|
Register<TextureBarrierTiledCommand>(CommandType.TextureBarrierTiled);
|
||||||
_lookup[(int)CommandType.SetPatchParameters] = (memory, threaded, renderer) =>
|
Register<TryHostConditionalRenderingCommand>(CommandType.TryHostConditionalRendering);
|
||||||
SetPatchParametersCommand.Run(ref GetCommand<SetPatchParametersCommand>(memory), threaded, renderer);
|
Register<TryHostConditionalRenderingFlushCommand>(CommandType.TryHostConditionalRenderingFlush);
|
||||||
_lookup[(int)CommandType.SetPointParameters] = (memory, threaded, renderer) =>
|
Register<UpdateRenderScaleCommand>(CommandType.UpdateRenderScale);
|
||||||
SetPointParametersCommand.Run(ref GetCommand<SetPointParametersCommand>(memory), threaded, renderer);
|
|
||||||
_lookup[(int)CommandType.SetPolygonMode] = (memory, threaded, renderer) =>
|
return maxCommandSize;
|
||||||
SetPolygonModeCommand.Run(ref GetCommand<SetPolygonModeCommand>(memory), threaded, renderer);
|
|
||||||
_lookup[(int)CommandType.SetPrimitiveRestart] = (memory, threaded, renderer) =>
|
|
||||||
SetPrimitiveRestartCommand.Run(ref GetCommand<SetPrimitiveRestartCommand>(memory), threaded, renderer);
|
|
||||||
_lookup[(int)CommandType.SetPrimitiveTopology] = (memory, threaded, renderer) =>
|
|
||||||
SetPrimitiveTopologyCommand.Run(ref GetCommand<SetPrimitiveTopologyCommand>(memory), threaded, renderer);
|
|
||||||
_lookup[(int)CommandType.SetProgram] = (memory, threaded, renderer) =>
|
|
||||||
SetProgramCommand.Run(ref GetCommand<SetProgramCommand>(memory), threaded, renderer);
|
|
||||||
_lookup[(int)CommandType.SetRasterizerDiscard] = (memory, threaded, renderer) =>
|
|
||||||
SetRasterizerDiscardCommand.Run(ref GetCommand<SetRasterizerDiscardCommand>(memory), threaded, renderer);
|
|
||||||
_lookup[(int)CommandType.SetRenderTargetColorMasks] = (memory, threaded, renderer) =>
|
|
||||||
SetRenderTargetColorMasksCommand.Run(ref GetCommand<SetRenderTargetColorMasksCommand>(memory), threaded, renderer);
|
|
||||||
_lookup[(int)CommandType.SetRenderTargetScale] = (memory, threaded, renderer) =>
|
|
||||||
SetRenderTargetScaleCommand.Run(ref GetCommand<SetRenderTargetScaleCommand>(memory), threaded, renderer);
|
|
||||||
_lookup[(int)CommandType.SetRenderTargets] = (memory, threaded, renderer) =>
|
|
||||||
SetRenderTargetsCommand.Run(ref GetCommand<SetRenderTargetsCommand>(memory), threaded, renderer);
|
|
||||||
_lookup[(int)CommandType.SetScissor] = (memory, threaded, renderer) =>
|
|
||||||
SetScissorsCommand.Run(ref GetCommand<SetScissorsCommand>(memory), threaded, renderer);
|
|
||||||
_lookup[(int)CommandType.SetStencilTest] = (memory, threaded, renderer) =>
|
|
||||||
SetStencilTestCommand.Run(ref GetCommand<SetStencilTestCommand>(memory), threaded, renderer);
|
|
||||||
_lookup[(int)CommandType.SetTextureAndSampler] = (memory, threaded, renderer) =>
|
|
||||||
SetTextureAndSamplerCommand.Run(ref GetCommand<SetTextureAndSamplerCommand>(memory), threaded, renderer);
|
|
||||||
_lookup[(int)CommandType.SetUserClipDistance] = (memory, threaded, renderer) =>
|
|
||||||
SetUserClipDistanceCommand.Run(ref GetCommand<SetUserClipDistanceCommand>(memory), threaded, renderer);
|
|
||||||
_lookup[(int)CommandType.SetVertexAttribs] = (memory, threaded, renderer) =>
|
|
||||||
SetVertexAttribsCommand.Run(ref GetCommand<SetVertexAttribsCommand>(memory), threaded, renderer);
|
|
||||||
_lookup[(int)CommandType.SetVertexBuffers] = (memory, threaded, renderer) =>
|
|
||||||
SetVertexBuffersCommand.Run(ref GetCommand<SetVertexBuffersCommand>(memory), threaded, renderer);
|
|
||||||
_lookup[(int)CommandType.SetViewports] = (memory, threaded, renderer) =>
|
|
||||||
SetViewportsCommand.Run(ref GetCommand<SetViewportsCommand>(memory), threaded, renderer);
|
|
||||||
_lookup[(int)CommandType.TextureBarrier] = (memory, threaded, renderer) =>
|
|
||||||
TextureBarrierCommand.Run(ref GetCommand<TextureBarrierCommand>(memory), threaded, renderer);
|
|
||||||
_lookup[(int)CommandType.TextureBarrierTiled] = (memory, threaded, renderer) =>
|
|
||||||
TextureBarrierTiledCommand.Run(ref GetCommand<TextureBarrierTiledCommand>(memory), threaded, renderer);
|
|
||||||
_lookup[(int)CommandType.TryHostConditionalRendering] = (memory, threaded, renderer) =>
|
|
||||||
TryHostConditionalRenderingCommand.Run(ref GetCommand<TryHostConditionalRenderingCommand>(memory), threaded, renderer);
|
|
||||||
_lookup[(int)CommandType.TryHostConditionalRenderingFlush] = (memory, threaded, renderer) =>
|
|
||||||
TryHostConditionalRenderingFlushCommand.Run(ref GetCommand<TryHostConditionalRenderingFlushCommand>(memory), threaded, renderer);
|
|
||||||
_lookup[(int)CommandType.UpdateRenderScale] = (memory, threaded, renderer) =>
|
|
||||||
UpdateRenderScaleCommand.Run(ref GetCommand<UpdateRenderScaleCommand>(memory), threaded, renderer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
||||||
{
|
{
|
||||||
struct BarrierCommand : IGALCommand
|
struct BarrierCommand : IGALCommand, IGALCommand<BarrierCommand>
|
||||||
{
|
{
|
||||||
public CommandType CommandType => CommandType.Barrier;
|
public CommandType CommandType => CommandType.Barrier;
|
||||||
|
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
||||||
{
|
{
|
||||||
struct BeginTransformFeedbackCommand : IGALCommand
|
struct BeginTransformFeedbackCommand : IGALCommand, IGALCommand<BeginTransformFeedbackCommand>
|
||||||
{
|
{
|
||||||
public CommandType CommandType => CommandType.BeginTransformFeedback;
|
public CommandType CommandType => CommandType.BeginTransformFeedback;
|
||||||
private PrimitiveTopology _topology;
|
private PrimitiveTopology _topology;
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Buffer
|
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Buffer
|
||||||
{
|
{
|
||||||
struct BufferDisposeCommand : IGALCommand
|
struct BufferDisposeCommand : IGALCommand, IGALCommand<BufferDisposeCommand>
|
||||||
{
|
{
|
||||||
public CommandType CommandType => CommandType.BufferDispose;
|
public CommandType CommandType => CommandType.BufferDispose;
|
||||||
private BufferHandle _buffer;
|
private BufferHandle _buffer;
|
||||||
|
@@ -3,7 +3,7 @@ using System;
|
|||||||
|
|
||||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Buffer
|
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Buffer
|
||||||
{
|
{
|
||||||
struct BufferGetDataCommand : IGALCommand
|
struct BufferGetDataCommand : IGALCommand, IGALCommand<BufferGetDataCommand>
|
||||||
{
|
{
|
||||||
public CommandType CommandType => CommandType.BufferGetData;
|
public CommandType CommandType => CommandType.BufferGetData;
|
||||||
private BufferHandle _buffer;
|
private BufferHandle _buffer;
|
||||||
|
@@ -3,7 +3,7 @@ using System;
|
|||||||
|
|
||||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Buffer
|
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Buffer
|
||||||
{
|
{
|
||||||
struct BufferSetDataCommand : IGALCommand
|
struct BufferSetDataCommand : IGALCommand, IGALCommand<BufferSetDataCommand>
|
||||||
{
|
{
|
||||||
public CommandType CommandType => CommandType.BufferSetData;
|
public CommandType CommandType => CommandType.BufferSetData;
|
||||||
private BufferHandle _buffer;
|
private BufferHandle _buffer;
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
||||||
{
|
{
|
||||||
struct ClearBufferCommand : IGALCommand
|
struct ClearBufferCommand : IGALCommand, IGALCommand<ClearBufferCommand>
|
||||||
{
|
{
|
||||||
public CommandType CommandType => CommandType.ClearBuffer;
|
public CommandType CommandType => CommandType.ClearBuffer;
|
||||||
private BufferHandle _destination;
|
private BufferHandle _destination;
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
||||||
{
|
{
|
||||||
struct ClearRenderTargetColorCommand : IGALCommand
|
struct ClearRenderTargetColorCommand : IGALCommand, IGALCommand<ClearRenderTargetColorCommand>
|
||||||
{
|
{
|
||||||
public CommandType CommandType => CommandType.ClearRenderTargetColor;
|
public CommandType CommandType => CommandType.ClearRenderTargetColor;
|
||||||
private int _index;
|
private int _index;
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
||||||
{
|
{
|
||||||
struct ClearRenderTargetDepthStencilCommand : IGALCommand
|
struct ClearRenderTargetDepthStencilCommand : IGALCommand, IGALCommand<ClearRenderTargetDepthStencilCommand>
|
||||||
{
|
{
|
||||||
public CommandType CommandType => CommandType.ClearRenderTargetDepthStencil;
|
public CommandType CommandType => CommandType.ClearRenderTargetDepthStencil;
|
||||||
private int _layer;
|
private int _layer;
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
||||||
{
|
{
|
||||||
struct CommandBufferBarrierCommand : IGALCommand
|
struct CommandBufferBarrierCommand : IGALCommand, IGALCommand<CommandBufferBarrierCommand>
|
||||||
{
|
{
|
||||||
public CommandType CommandType => CommandType.CommandBufferBarrier;
|
public CommandType CommandType => CommandType.CommandBufferBarrier;
|
||||||
|
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
||||||
{
|
{
|
||||||
struct CopyBufferCommand : IGALCommand
|
struct CopyBufferCommand : IGALCommand, IGALCommand<CopyBufferCommand>
|
||||||
{
|
{
|
||||||
public CommandType CommandType => CommandType.CopyBuffer;
|
public CommandType CommandType => CommandType.CopyBuffer;
|
||||||
private BufferHandle _source;
|
private BufferHandle _source;
|
||||||
|
@@ -3,7 +3,7 @@ using Ryujinx.Graphics.GAL.Multithreading.Resources;
|
|||||||
|
|
||||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.CounterEvent
|
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.CounterEvent
|
||||||
{
|
{
|
||||||
struct CounterEventDisposeCommand : IGALCommand
|
struct CounterEventDisposeCommand : IGALCommand, IGALCommand<CounterEventDisposeCommand>
|
||||||
{
|
{
|
||||||
public CommandType CommandType => CommandType.CounterEventDispose;
|
public CommandType CommandType => CommandType.CounterEventDispose;
|
||||||
private TableRef<ThreadedCounterEvent> _event;
|
private TableRef<ThreadedCounterEvent> _event;
|
||||||
|
@@ -3,7 +3,7 @@ using Ryujinx.Graphics.GAL.Multithreading.Resources;
|
|||||||
|
|
||||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.CounterEvent
|
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.CounterEvent
|
||||||
{
|
{
|
||||||
struct CounterEventFlushCommand : IGALCommand
|
struct CounterEventFlushCommand : IGALCommand, IGALCommand<CounterEventFlushCommand>
|
||||||
{
|
{
|
||||||
public CommandType CommandType => CommandType.CounterEventFlush;
|
public CommandType CommandType => CommandType.CounterEventFlush;
|
||||||
private TableRef<ThreadedCounterEvent> _event;
|
private TableRef<ThreadedCounterEvent> _event;
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
||||||
{
|
{
|
||||||
struct DispatchComputeCommand : IGALCommand
|
struct DispatchComputeCommand : IGALCommand, IGALCommand<DispatchComputeCommand>
|
||||||
{
|
{
|
||||||
public CommandType CommandType => CommandType.DispatchCompute;
|
public CommandType CommandType => CommandType.DispatchCompute;
|
||||||
private int _groupsX;
|
private int _groupsX;
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
||||||
{
|
{
|
||||||
struct DrawIndexedCommand : IGALCommand
|
struct DrawIndexedCommand : IGALCommand, IGALCommand<DrawIndexedCommand>
|
||||||
{
|
{
|
||||||
public CommandType CommandType => CommandType.DrawIndexed;
|
public CommandType CommandType => CommandType.DrawIndexed;
|
||||||
private int _indexCount;
|
private int _indexCount;
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
||||||
{
|
{
|
||||||
struct DrawCommand : IGALCommand
|
struct DrawCommand : IGALCommand, IGALCommand<DrawCommand>
|
||||||
{
|
{
|
||||||
public CommandType CommandType => CommandType.Draw;
|
public CommandType CommandType => CommandType.Draw;
|
||||||
private int _vertexCount;
|
private int _vertexCount;
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
||||||
{
|
{
|
||||||
struct DrawIndexedIndirectCommand : IGALCommand
|
struct DrawIndexedIndirectCommand : IGALCommand, IGALCommand<DrawIndexedIndirectCommand>
|
||||||
{
|
{
|
||||||
public CommandType CommandType => CommandType.DrawIndexedIndirect;
|
public CommandType CommandType => CommandType.DrawIndexedIndirect;
|
||||||
private BufferRange _indirectBuffer;
|
private BufferRange _indirectBuffer;
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
||||||
{
|
{
|
||||||
struct DrawIndexedIndirectCountCommand : IGALCommand
|
struct DrawIndexedIndirectCountCommand : IGALCommand, IGALCommand<DrawIndexedIndirectCountCommand>
|
||||||
{
|
{
|
||||||
public CommandType CommandType => CommandType.DrawIndexedIndirectCount;
|
public CommandType CommandType => CommandType.DrawIndexedIndirectCount;
|
||||||
private BufferRange _indirectBuffer;
|
private BufferRange _indirectBuffer;
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
||||||
{
|
{
|
||||||
struct DrawIndirectCommand : IGALCommand
|
struct DrawIndirectCommand : IGALCommand, IGALCommand<DrawIndirectCommand>
|
||||||
{
|
{
|
||||||
public CommandType CommandType => CommandType.DrawIndirect;
|
public CommandType CommandType => CommandType.DrawIndirect;
|
||||||
private BufferRange _indirectBuffer;
|
private BufferRange _indirectBuffer;
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
||||||
{
|
{
|
||||||
struct DrawIndirectCountCommand : IGALCommand
|
struct DrawIndirectCountCommand : IGALCommand, IGALCommand<DrawIndirectCountCommand>
|
||||||
{
|
{
|
||||||
public CommandType CommandType => CommandType.DrawIndirectCount;
|
public CommandType CommandType => CommandType.DrawIndirectCount;
|
||||||
private BufferRange _indirectBuffer;
|
private BufferRange _indirectBuffer;
|
||||||
|
@@ -3,7 +3,7 @@ using Ryujinx.Graphics.GAL.Multithreading.Resources;
|
|||||||
|
|
||||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
||||||
{
|
{
|
||||||
struct DrawTextureCommand : IGALCommand
|
struct DrawTextureCommand : IGALCommand, IGALCommand<DrawTextureCommand>
|
||||||
{
|
{
|
||||||
public CommandType CommandType => CommandType.DrawTexture;
|
public CommandType CommandType => CommandType.DrawTexture;
|
||||||
private TableRef<ITexture> _texture;
|
private TableRef<ITexture> _texture;
|
||||||
|
@@ -1,10 +1,10 @@
|
|||||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
||||||
{
|
{
|
||||||
struct EndHostConditionalRenderingCommand : IGALCommand
|
struct EndHostConditionalRenderingCommand : IGALCommand, IGALCommand<EndHostConditionalRenderingCommand>
|
||||||
{
|
{
|
||||||
public CommandType CommandType => CommandType.EndHostConditionalRendering;
|
public CommandType CommandType => CommandType.EndHostConditionalRendering;
|
||||||
|
|
||||||
public static void Run(IRenderer renderer)
|
public static void Run(ref EndHostConditionalRenderingCommand command, ThreadedRenderer threaded, IRenderer renderer)
|
||||||
{
|
{
|
||||||
renderer.Pipeline.EndHostConditionalRendering();
|
renderer.Pipeline.EndHostConditionalRendering();
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
||||||
{
|
{
|
||||||
struct EndTransformFeedbackCommand : IGALCommand
|
struct EndTransformFeedbackCommand : IGALCommand, IGALCommand<EndTransformFeedbackCommand>
|
||||||
{
|
{
|
||||||
public CommandType CommandType => CommandType.EndTransformFeedback;
|
public CommandType CommandType => CommandType.EndTransformFeedback;
|
||||||
|
|
||||||
|
@@ -4,4 +4,9 @@
|
|||||||
{
|
{
|
||||||
CommandType CommandType { get; }
|
CommandType CommandType { get; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface IGALCommand<T> where T : IGALCommand
|
||||||
|
{
|
||||||
|
abstract static void Run(ref T command, ThreadedRenderer threaded, IRenderer renderer);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -3,7 +3,7 @@ using Ryujinx.Graphics.GAL.Multithreading.Resources;
|
|||||||
|
|
||||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Program
|
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Program
|
||||||
{
|
{
|
||||||
struct ProgramCheckLinkCommand : IGALCommand
|
struct ProgramCheckLinkCommand : IGALCommand, IGALCommand<ProgramCheckLinkCommand>
|
||||||
{
|
{
|
||||||
public CommandType CommandType => CommandType.ProgramCheckLink;
|
public CommandType CommandType => CommandType.ProgramCheckLink;
|
||||||
private TableRef<ThreadedProgram> _program;
|
private TableRef<ThreadedProgram> _program;
|
||||||
|
@@ -3,7 +3,7 @@ using Ryujinx.Graphics.GAL.Multithreading.Resources;
|
|||||||
|
|
||||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Program
|
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Program
|
||||||
{
|
{
|
||||||
struct ProgramDisposeCommand : IGALCommand
|
struct ProgramDisposeCommand : IGALCommand, IGALCommand<ProgramDisposeCommand>
|
||||||
{
|
{
|
||||||
public CommandType CommandType => CommandType.ProgramDispose;
|
public CommandType CommandType => CommandType.ProgramDispose;
|
||||||
private TableRef<ThreadedProgram> _program;
|
private TableRef<ThreadedProgram> _program;
|
||||||
|
@@ -3,7 +3,7 @@ using Ryujinx.Graphics.GAL.Multithreading.Resources;
|
|||||||
|
|
||||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Program
|
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Program
|
||||||
{
|
{
|
||||||
struct ProgramGetBinaryCommand : IGALCommand
|
struct ProgramGetBinaryCommand : IGALCommand, IGALCommand<ProgramGetBinaryCommand>
|
||||||
{
|
{
|
||||||
public CommandType CommandType => CommandType.ProgramGetBinary;
|
public CommandType CommandType => CommandType.ProgramGetBinary;
|
||||||
private TableRef<ThreadedProgram> _program;
|
private TableRef<ThreadedProgram> _program;
|
||||||
|
@@ -3,7 +3,7 @@ using System;
|
|||||||
|
|
||||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer
|
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer
|
||||||
{
|
{
|
||||||
struct ActionCommand : IGALCommand
|
struct ActionCommand : IGALCommand, IGALCommand<ActionCommand>
|
||||||
{
|
{
|
||||||
public CommandType CommandType => CommandType.Action;
|
public CommandType CommandType => CommandType.Action;
|
||||||
private TableRef<Action> _action;
|
private TableRef<Action> _action;
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer
|
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer
|
||||||
{
|
{
|
||||||
struct CreateBufferCommand : IGALCommand
|
struct CreateBufferCommand : IGALCommand, IGALCommand<CreateBufferCommand>
|
||||||
{
|
{
|
||||||
public CommandType CommandType => CommandType.CreateBuffer;
|
public CommandType CommandType => CommandType.CreateBuffer;
|
||||||
private BufferHandle _threadedHandle;
|
private BufferHandle _threadedHandle;
|
||||||
|
@@ -3,7 +3,7 @@ using Ryujinx.Graphics.GAL.Multithreading.Resources.Programs;
|
|||||||
|
|
||||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer
|
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer
|
||||||
{
|
{
|
||||||
struct CreateProgramCommand : IGALCommand
|
struct CreateProgramCommand : IGALCommand, IGALCommand<CreateProgramCommand>
|
||||||
{
|
{
|
||||||
public CommandType CommandType => CommandType.CreateProgram;
|
public CommandType CommandType => CommandType.CreateProgram;
|
||||||
private TableRef<IProgramRequest> _request;
|
private TableRef<IProgramRequest> _request;
|
||||||
|
@@ -3,7 +3,7 @@ using Ryujinx.Graphics.GAL.Multithreading.Resources;
|
|||||||
|
|
||||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer
|
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer
|
||||||
{
|
{
|
||||||
struct CreateSamplerCommand : IGALCommand
|
struct CreateSamplerCommand : IGALCommand, IGALCommand<CreateSamplerCommand>
|
||||||
{
|
{
|
||||||
public CommandType CommandType => CommandType.CreateSampler;
|
public CommandType CommandType => CommandType.CreateSampler;
|
||||||
private TableRef<ThreadedSampler> _sampler;
|
private TableRef<ThreadedSampler> _sampler;
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer
|
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer
|
||||||
{
|
{
|
||||||
struct CreateSyncCommand : IGALCommand
|
struct CreateSyncCommand : IGALCommand, IGALCommand<CreateSyncCommand>
|
||||||
{
|
{
|
||||||
public CommandType CommandType => CommandType.CreateSync;
|
public CommandType CommandType => CommandType.CreateSync;
|
||||||
private ulong _id;
|
private ulong _id;
|
||||||
|
@@ -3,7 +3,7 @@ using Ryujinx.Graphics.GAL.Multithreading.Resources;
|
|||||||
|
|
||||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer
|
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer
|
||||||
{
|
{
|
||||||
struct CreateTextureCommand : IGALCommand
|
struct CreateTextureCommand : IGALCommand, IGALCommand<CreateTextureCommand>
|
||||||
{
|
{
|
||||||
public CommandType CommandType => CommandType.CreateTexture;
|
public CommandType CommandType => CommandType.CreateTexture;
|
||||||
private TableRef<ThreadedTexture> _texture;
|
private TableRef<ThreadedTexture> _texture;
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer
|
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer
|
||||||
{
|
{
|
||||||
struct GetCapabilitiesCommand : IGALCommand
|
struct GetCapabilitiesCommand : IGALCommand, IGALCommand<GetCapabilitiesCommand>
|
||||||
{
|
{
|
||||||
public CommandType CommandType => CommandType.GetCapabilities;
|
public CommandType CommandType => CommandType.GetCapabilities;
|
||||||
private TableRef<ResultBox<Capabilities>> _result;
|
private TableRef<ResultBox<Capabilities>> _result;
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer
|
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer
|
||||||
{
|
{
|
||||||
struct PreFrameCommand : IGALCommand
|
struct PreFrameCommand : IGALCommand, IGALCommand<PreFrameCommand>
|
||||||
{
|
{
|
||||||
public CommandType CommandType => CommandType.PreFrame;
|
public CommandType CommandType => CommandType.PreFrame;
|
||||||
|
|
||||||
|
@@ -4,7 +4,7 @@ using System;
|
|||||||
|
|
||||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer
|
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer
|
||||||
{
|
{
|
||||||
struct ReportCounterCommand : IGALCommand
|
struct ReportCounterCommand : IGALCommand, IGALCommand<ReportCounterCommand>
|
||||||
{
|
{
|
||||||
public CommandType CommandType => CommandType.ReportCounter;
|
public CommandType CommandType => CommandType.ReportCounter;
|
||||||
private TableRef<ThreadedCounterEvent> _event;
|
private TableRef<ThreadedCounterEvent> _event;
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer
|
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer
|
||||||
{
|
{
|
||||||
struct ResetCounterCommand : IGALCommand
|
struct ResetCounterCommand : IGALCommand, IGALCommand<ResetCounterCommand>
|
||||||
{
|
{
|
||||||
public CommandType CommandType => CommandType.ResetCounter;
|
public CommandType CommandType => CommandType.ResetCounter;
|
||||||
private CounterType _type;
|
private CounterType _type;
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer
|
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer
|
||||||
{
|
{
|
||||||
struct UpdateCountersCommand : IGALCommand
|
struct UpdateCountersCommand : IGALCommand, IGALCommand<UpdateCountersCommand>
|
||||||
{
|
{
|
||||||
public CommandType CommandType => CommandType.UpdateCounters;
|
public CommandType CommandType => CommandType.UpdateCounters;
|
||||||
|
|
||||||
|
@@ -3,7 +3,7 @@ using Ryujinx.Graphics.GAL.Multithreading.Resources;
|
|||||||
|
|
||||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Sampler
|
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Sampler
|
||||||
{
|
{
|
||||||
struct SamplerDisposeCommand : IGALCommand
|
struct SamplerDisposeCommand : IGALCommand, IGALCommand<SamplerDisposeCommand>
|
||||||
{
|
{
|
||||||
public CommandType CommandType => CommandType.SamplerDispose;
|
public CommandType CommandType => CommandType.SamplerDispose;
|
||||||
private TableRef<ThreadedSampler> _sampler;
|
private TableRef<ThreadedSampler> _sampler;
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
||||||
{
|
{
|
||||||
struct SetAlphaTestCommand : IGALCommand
|
struct SetAlphaTestCommand : IGALCommand, IGALCommand<SetAlphaTestCommand>
|
||||||
{
|
{
|
||||||
public CommandType CommandType => CommandType.SetAlphaTest;
|
public CommandType CommandType => CommandType.SetAlphaTest;
|
||||||
private bool _enable;
|
private bool _enable;
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
||||||
{
|
{
|
||||||
struct SetBlendStateCommand : IGALCommand
|
struct SetBlendStateCommand : IGALCommand, IGALCommand<SetBlendStateCommand>
|
||||||
{
|
{
|
||||||
public CommandType CommandType => CommandType.SetBlendState;
|
public CommandType CommandType => CommandType.SetBlendState;
|
||||||
private int _index;
|
private int _index;
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
||||||
{
|
{
|
||||||
struct SetDepthBiasCommand : IGALCommand
|
struct SetDepthBiasCommand : IGALCommand, IGALCommand<SetDepthBiasCommand>
|
||||||
{
|
{
|
||||||
public CommandType CommandType => CommandType.SetDepthBias;
|
public CommandType CommandType => CommandType.SetDepthBias;
|
||||||
private PolygonModeMask _enables;
|
private PolygonModeMask _enables;
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
||||||
{
|
{
|
||||||
struct SetDepthClampCommand : IGALCommand
|
struct SetDepthClampCommand : IGALCommand, IGALCommand<SetDepthClampCommand>
|
||||||
{
|
{
|
||||||
public CommandType CommandType => CommandType.SetDepthClamp;
|
public CommandType CommandType => CommandType.SetDepthClamp;
|
||||||
private bool _clamp;
|
private bool _clamp;
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
||||||
{
|
{
|
||||||
struct SetDepthModeCommand : IGALCommand
|
struct SetDepthModeCommand : IGALCommand, IGALCommand<SetDepthModeCommand>
|
||||||
{
|
{
|
||||||
public CommandType CommandType => CommandType.SetDepthMode;
|
public CommandType CommandType => CommandType.SetDepthMode;
|
||||||
private DepthMode _mode;
|
private DepthMode _mode;
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
||||||
{
|
{
|
||||||
struct SetDepthTestCommand : IGALCommand
|
struct SetDepthTestCommand : IGALCommand, IGALCommand<SetDepthTestCommand>
|
||||||
{
|
{
|
||||||
public CommandType CommandType => CommandType.SetDepthTest;
|
public CommandType CommandType => CommandType.SetDepthTest;
|
||||||
private DepthTestDescriptor _depthTest;
|
private DepthTestDescriptor _depthTest;
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
||||||
{
|
{
|
||||||
struct SetFaceCullingCommand : IGALCommand
|
struct SetFaceCullingCommand : IGALCommand, IGALCommand<SetFaceCullingCommand>
|
||||||
{
|
{
|
||||||
public CommandType CommandType => CommandType.SetFaceCulling;
|
public CommandType CommandType => CommandType.SetFaceCulling;
|
||||||
private bool _enable;
|
private bool _enable;
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
||||||
{
|
{
|
||||||
struct SetFrontFaceCommand : IGALCommand
|
struct SetFrontFaceCommand : IGALCommand, IGALCommand<SetFrontFaceCommand>
|
||||||
{
|
{
|
||||||
public CommandType CommandType => CommandType.SetFrontFace;
|
public CommandType CommandType => CommandType.SetFrontFace;
|
||||||
private FrontFace _frontFace;
|
private FrontFace _frontFace;
|
||||||
|
@@ -3,7 +3,7 @@ using Ryujinx.Graphics.GAL.Multithreading.Resources;
|
|||||||
|
|
||||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
||||||
{
|
{
|
||||||
struct SetImageCommand : IGALCommand
|
struct SetImageCommand : IGALCommand, IGALCommand<SetImageCommand>
|
||||||
{
|
{
|
||||||
public CommandType CommandType => CommandType.SetImage;
|
public CommandType CommandType => CommandType.SetImage;
|
||||||
private int _binding;
|
private int _binding;
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user