Compare commits
44 Commits
Author | SHA1 | Date | |
---|---|---|---|
1f8d66db7c | |||
c3a5716a95 | |||
1f1e2a7f03 | |||
e54f9dc4b4 | |||
edfd4d70c0 | |||
fc43aecbbd | |||
58d7a1fe97 | |||
7aa430f1a5 | |||
6bf460e104 | |||
efb135b74c | |||
a707842e14 | |||
a5a9b9bc8b | |||
17078ad929 | |||
32450d45de | |||
ed7a0474c6 | |||
fe9c49949a | |||
052b23c83c | |||
e4f68592c3 | |||
1dcd44b94f | |||
61b1ce252f | |||
5f38086f94 | |||
7bae440d3a | |||
f1943fd0b6 | |||
ec8d4f3af5 | |||
b3f0978869 | |||
f614d2c435 | |||
40c9416097 | |||
618c8edc79 | |||
99fc4fa61b | |||
f6d5499a16 | |||
26bf13a65d | |||
96cf242bcf | |||
59755818ef | |||
f8beeeb7d3 | |||
cb250162cb | |||
7528f94536 | |||
43081c16c4 | |||
780627e7b0 | |||
9044cb38d1 | |||
a53cfdab78 | |||
c7f9962dde | |||
296c4a3d01 | |||
e7cf4e6eaf | |||
a1a4771ac1 |
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
|
@ -48,9 +48,21 @@ namespace ARMeilleure.CodeGen
|
||||
/// <returns>A delegate of type <typeparamref name="T"/> pointing to the mapped function</returns>
|
||||
public T Map<T>()
|
||||
{
|
||||
IntPtr codePtr = JitCache.Map(this);
|
||||
return MapWithPointer<T>(out _);
|
||||
}
|
||||
|
||||
return Marshal.GetDelegateForFunctionPointer<T>(codePtr);
|
||||
/// <summary>
|
||||
/// Maps the <see cref="CompiledFunction"/> onto the <see cref="JitCache"/> and returns a delegate of type
|
||||
/// <typeparamref name="T"/> pointing to the mapped function.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of delegate</typeparam>
|
||||
/// <param name="codePointer">Pointer to the function code in memory</param>
|
||||
/// <returns>A delegate of type <typeparamref name="T"/> pointing to the mapped function</returns>
|
||||
public T MapWithPointer<T>(out IntPtr codePointer)
|
||||
{
|
||||
codePointer = JitCache.Map(this);
|
||||
|
||||
return Marshal.GetDelegateForFunctionPointer<T>(codePointer);
|
||||
}
|
||||
}
|
||||
}
|
@ -191,7 +191,7 @@ namespace ARMeilleure.Instructions
|
||||
{
|
||||
TranslatedFunction function = Context.Translator.GetOrTranslate(address, GetContext().ExecutionMode);
|
||||
|
||||
return (ulong)function.FuncPtr.ToInt64();
|
||||
return (ulong)function.FuncPointer.ToInt64();
|
||||
}
|
||||
|
||||
public static void InvalidateCacheLine(ulong address)
|
||||
|
@ -71,6 +71,7 @@ namespace ARMeilleure.Memory
|
||||
/// <param name="size">Size of the region</param>
|
||||
/// <param name="write">True if the region was written, false if read</param>
|
||||
/// <param name="precise">True if the access is precise, false otherwise</param>
|
||||
void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false);
|
||||
/// <param name="exemptId">Optional ID of the handles that should not be signalled</param>
|
||||
void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false, int? exemptId = null);
|
||||
}
|
||||
}
|
@ -222,7 +222,7 @@ namespace ARMeilleure.Signal
|
||||
|
||||
// Tracking action should be non-null to call it, otherwise assume false return.
|
||||
context.BranchIfFalse(skipActionLabel, trackingActionPtr);
|
||||
Operand result = context.Call(trackingActionPtr, OperandType.I32, offset, Const(_pageSize), isWrite, Const(0));
|
||||
Operand result = context.Call(trackingActionPtr, OperandType.I32, offset, Const(_pageSize), isWrite);
|
||||
context.Copy(inRegionLocal, result);
|
||||
|
||||
context.MarkLabel(skipActionLabel);
|
||||
|
@ -745,9 +745,9 @@ namespace ARMeilleure.Translation.PTC
|
||||
bool highCq)
|
||||
{
|
||||
var cFunc = new CompiledFunction(code, unwindInfo, RelocInfo.Empty);
|
||||
var gFunc = cFunc.Map<GuestFunction>();
|
||||
var gFunc = cFunc.MapWithPointer<GuestFunction>(out IntPtr gFuncPointer);
|
||||
|
||||
return new TranslatedFunction(gFunc, callCounter, guestSize, highCq);
|
||||
return new TranslatedFunction(gFunc, gFuncPointer, callCounter, guestSize, highCq);
|
||||
}
|
||||
|
||||
private void UpdateInfo(InfoEntry infoEntry)
|
||||
|
@ -1,6 +1,5 @@
|
||||
using ARMeilleure.Common;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace ARMeilleure.Translation
|
||||
{
|
||||
@ -8,18 +7,18 @@ namespace ARMeilleure.Translation
|
||||
{
|
||||
private readonly GuestFunction _func; // Ensure that this delegate will not be garbage collected.
|
||||
|
||||
public IntPtr FuncPointer { get; }
|
||||
public Counter<uint> CallCounter { get; }
|
||||
public ulong GuestSize { get; }
|
||||
public bool HighCq { get; }
|
||||
public IntPtr FuncPtr { get; }
|
||||
|
||||
public TranslatedFunction(GuestFunction func, Counter<uint> callCounter, ulong guestSize, bool highCq)
|
||||
public TranslatedFunction(GuestFunction func, IntPtr funcPointer, Counter<uint> callCounter, ulong guestSize, bool highCq)
|
||||
{
|
||||
_func = func;
|
||||
FuncPointer = funcPointer;
|
||||
CallCounter = callCounter;
|
||||
GuestSize = guestSize;
|
||||
HighCq = highCq;
|
||||
FuncPtr = Marshal.GetFunctionPointerForDelegate(func);
|
||||
}
|
||||
|
||||
public ulong Execute(State.ExecutionContext context)
|
||||
|
@ -211,7 +211,7 @@ namespace ARMeilleure.Translation
|
||||
|
||||
if (oldFunc != func)
|
||||
{
|
||||
JitCache.Unmap(func.FuncPtr);
|
||||
JitCache.Unmap(func.FuncPointer);
|
||||
func = oldFunc;
|
||||
}
|
||||
|
||||
@ -230,7 +230,7 @@ namespace ARMeilleure.Translation
|
||||
{
|
||||
if (FunctionTable.IsValid(guestAddress) && (Optimizations.AllowLcqInFunctionTable || func.HighCq))
|
||||
{
|
||||
Volatile.Write(ref FunctionTable.GetValue(guestAddress), (ulong)func.FuncPtr);
|
||||
Volatile.Write(ref FunctionTable.GetValue(guestAddress), (ulong)func.FuncPointer);
|
||||
}
|
||||
}
|
||||
|
||||
@ -292,11 +292,11 @@ namespace ARMeilleure.Translation
|
||||
_ptc.WriteCompiledFunction(address, funcSize, hash, highCq, compiledFunc);
|
||||
}
|
||||
|
||||
GuestFunction func = compiledFunc.Map<GuestFunction>();
|
||||
GuestFunction func = compiledFunc.MapWithPointer<GuestFunction>(out IntPtr funcPointer);
|
||||
|
||||
Allocators.ResetAll();
|
||||
|
||||
return new TranslatedFunction(func, counter, funcSize, highCq);
|
||||
return new TranslatedFunction(func, funcPointer, counter, funcSize, highCq);
|
||||
}
|
||||
|
||||
private void BackgroundTranslate()
|
||||
@ -537,7 +537,7 @@ namespace ARMeilleure.Translation
|
||||
|
||||
foreach (var func in functions)
|
||||
{
|
||||
JitCache.Unmap(func.FuncPtr);
|
||||
JitCache.Unmap(func.FuncPointer);
|
||||
|
||||
func.CallCounter?.Dispose();
|
||||
}
|
||||
@ -546,7 +546,7 @@ namespace ARMeilleure.Translation
|
||||
|
||||
while (_oldFuncs.TryDequeue(out var kv))
|
||||
{
|
||||
JitCache.Unmap(kv.Value.FuncPtr);
|
||||
JitCache.Unmap(kv.Value.FuncPointer);
|
||||
|
||||
kv.Value.CallCounter?.Dispose();
|
||||
}
|
||||
|
@ -36,7 +36,7 @@
|
||||
<PackageVersion Include="Ryujinx.GtkSharp" Version="3.24.24.59-ryujinx" />
|
||||
<PackageVersion Include="Ryujinx.SDL2-CS" Version="2.26.1-build23" />
|
||||
<PackageVersion Include="shaderc.net" Version="0.1.0" />
|
||||
<PackageVersion Include="SharpZipLib" Version="1.4.1" />
|
||||
<PackageVersion Include="SharpZipLib" Version="1.4.2" />
|
||||
<PackageVersion Include="Silk.NET.Vulkan" Version="2.16.0" />
|
||||
<PackageVersion Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.16.0" />
|
||||
<PackageVersion Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.16.0" />
|
||||
@ -44,11 +44,12 @@
|
||||
<PackageVersion Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta11" />
|
||||
<PackageVersion Include="SPB" Version="0.0.4-build28" />
|
||||
<PackageVersion Include="System.Drawing.Common" Version="7.0.0" />
|
||||
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="6.26.0" />
|
||||
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="6.27.0" />
|
||||
<PackageVersion Include="System.IO.FileSystem.Primitives" Version="4.3.0" />
|
||||
<PackageVersion Include="System.Management" Version="7.0.0" />
|
||||
<PackageVersion Include="System.Net.NameResolution" Version="4.3.0" />
|
||||
<PackageVersion Include="System.Threading.ThreadPool" Version="4.3.0" />
|
||||
<PackageVersion Include="UnicornEngine.Unicorn" Version="2.0.2-rc1-9c9356d" />
|
||||
<PackageVersion Include="XamlNameReferenceGenerator" Version="1.5.1" />
|
||||
</ItemGroup>
|
||||
</Project>
|
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.Intrinsics;
|
||||
using System.Runtime.Intrinsics.X86;
|
||||
@ -380,7 +381,6 @@ namespace Ryujinx.Audio.Renderer.Dsp
|
||||
return _normalCurveLut2F;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private unsafe static void ResampleDefaultQuality(Span<float> outputBuffer, ReadOnlySpan<short> inputBuffer, float ratio, ref float fraction, int sampleCount, bool needPitch)
|
||||
{
|
||||
ReadOnlySpan<float> parameters = GetDefaultParameter(ratio);
|
||||
@ -394,35 +394,33 @@ namespace Ryujinx.Audio.Renderer.Dsp
|
||||
if (ratio == 1f)
|
||||
{
|
||||
fixed (short* pInput = inputBuffer)
|
||||
fixed (float* pOutput = outputBuffer, pParameters = parameters)
|
||||
{
|
||||
fixed (float* pOutput = outputBuffer, pParameters = parameters)
|
||||
Vector128<float> parameter = Sse.LoadVector128(pParameters);
|
||||
|
||||
for (; i < (sampleCount & ~3); i += 4)
|
||||
{
|
||||
Vector128<float> parameter = Sse.LoadVector128(pParameters);
|
||||
Vector128<int> intInput0 = Sse41.ConvertToVector128Int32(pInput + (uint)i);
|
||||
Vector128<int> intInput1 = Sse41.ConvertToVector128Int32(pInput + (uint)i + 1);
|
||||
Vector128<int> intInput2 = Sse41.ConvertToVector128Int32(pInput + (uint)i + 2);
|
||||
Vector128<int> intInput3 = Sse41.ConvertToVector128Int32(pInput + (uint)i + 3);
|
||||
|
||||
for (; i < (sampleCount & ~3); i += 4)
|
||||
{
|
||||
Vector128<int> intInput0 = Sse41.ConvertToVector128Int32(pInput + (uint)i);
|
||||
Vector128<int> intInput1 = Sse41.ConvertToVector128Int32(pInput + (uint)i + 1);
|
||||
Vector128<int> intInput2 = Sse41.ConvertToVector128Int32(pInput + (uint)i + 2);
|
||||
Vector128<int> intInput3 = Sse41.ConvertToVector128Int32(pInput + (uint)i + 3);
|
||||
Vector128<float> input0 = Sse2.ConvertToVector128Single(intInput0);
|
||||
Vector128<float> input1 = Sse2.ConvertToVector128Single(intInput1);
|
||||
Vector128<float> input2 = Sse2.ConvertToVector128Single(intInput2);
|
||||
Vector128<float> input3 = Sse2.ConvertToVector128Single(intInput3);
|
||||
|
||||
Vector128<float> input0 = Sse2.ConvertToVector128Single(intInput0);
|
||||
Vector128<float> input1 = Sse2.ConvertToVector128Single(intInput1);
|
||||
Vector128<float> input2 = Sse2.ConvertToVector128Single(intInput2);
|
||||
Vector128<float> input3 = Sse2.ConvertToVector128Single(intInput3);
|
||||
Vector128<float> mix0 = Sse.Multiply(input0, parameter);
|
||||
Vector128<float> mix1 = Sse.Multiply(input1, parameter);
|
||||
Vector128<float> mix2 = Sse.Multiply(input2, parameter);
|
||||
Vector128<float> mix3 = Sse.Multiply(input3, parameter);
|
||||
|
||||
Vector128<float> mix0 = Sse.Multiply(input0, parameter);
|
||||
Vector128<float> mix1 = Sse.Multiply(input1, parameter);
|
||||
Vector128<float> mix2 = Sse.Multiply(input2, parameter);
|
||||
Vector128<float> mix3 = Sse.Multiply(input3, parameter);
|
||||
Vector128<float> mix01 = Sse3.HorizontalAdd(mix0, mix1);
|
||||
Vector128<float> mix23 = Sse3.HorizontalAdd(mix2, mix3);
|
||||
|
||||
Vector128<float> mix01 = Sse3.HorizontalAdd(mix0, mix1);
|
||||
Vector128<float> mix23 = Sse3.HorizontalAdd(mix2, mix3);
|
||||
Vector128<float> mix0123 = Sse3.HorizontalAdd(mix01, mix23);
|
||||
|
||||
Vector128<float> mix0123 = Sse3.HorizontalAdd(mix01, mix23);
|
||||
|
||||
Sse.Store(pOutput + (uint)i, Sse41.RoundToNearestInteger(mix0123));
|
||||
}
|
||||
Sse.Store(pOutput + (uint)i, Sse41.RoundToNearestInteger(mix0123));
|
||||
}
|
||||
}
|
||||
|
||||
@ -431,62 +429,60 @@ namespace Ryujinx.Audio.Renderer.Dsp
|
||||
else
|
||||
{
|
||||
fixed (short* pInput = inputBuffer)
|
||||
fixed (float* pOutput = outputBuffer, pParameters = parameters)
|
||||
{
|
||||
fixed (float* pOutput = outputBuffer, pParameters = parameters)
|
||||
for (; i < (sampleCount & ~3); i += 4)
|
||||
{
|
||||
for (; i < (sampleCount & ~3); i += 4)
|
||||
{
|
||||
uint baseIndex0 = (uint)(fraction * 128) * 4;
|
||||
uint inputIndex0 = (uint)inputBufferIndex;
|
||||
uint baseIndex0 = (uint)(fraction * 128) * 4;
|
||||
uint inputIndex0 = (uint)inputBufferIndex;
|
||||
|
||||
fraction += ratio;
|
||||
fraction += ratio;
|
||||
|
||||
uint baseIndex1 = ((uint)(fraction * 128) & 127) * 4;
|
||||
uint inputIndex1 = (uint)inputBufferIndex + (uint)fraction;
|
||||
uint baseIndex1 = ((uint)(fraction * 128) & 127) * 4;
|
||||
uint inputIndex1 = (uint)inputBufferIndex + (uint)fraction;
|
||||
|
||||
fraction += ratio;
|
||||
fraction += ratio;
|
||||
|
||||
uint baseIndex2 = ((uint)(fraction * 128) & 127) * 4;
|
||||
uint inputIndex2 = (uint)inputBufferIndex + (uint)fraction;
|
||||
uint baseIndex2 = ((uint)(fraction * 128) & 127) * 4;
|
||||
uint inputIndex2 = (uint)inputBufferIndex + (uint)fraction;
|
||||
|
||||
fraction += ratio;
|
||||
fraction += ratio;
|
||||
|
||||
uint baseIndex3 = ((uint)(fraction * 128) & 127) * 4;
|
||||
uint inputIndex3 = (uint)inputBufferIndex + (uint)fraction;
|
||||
uint baseIndex3 = ((uint)(fraction * 128) & 127) * 4;
|
||||
uint inputIndex3 = (uint)inputBufferIndex + (uint)fraction;
|
||||
|
||||
fraction += ratio;
|
||||
inputBufferIndex += (int)fraction;
|
||||
fraction += ratio;
|
||||
inputBufferIndex += (int)fraction;
|
||||
|
||||
// Only keep lower part (safe as fraction isn't supposed to be negative)
|
||||
fraction -= (int)fraction;
|
||||
// Only keep lower part (safe as fraction isn't supposed to be negative)
|
||||
fraction -= (int)fraction;
|
||||
|
||||
Vector128<float> parameter0 = Sse.LoadVector128(pParameters + baseIndex0);
|
||||
Vector128<float> parameter1 = Sse.LoadVector128(pParameters + baseIndex1);
|
||||
Vector128<float> parameter2 = Sse.LoadVector128(pParameters + baseIndex2);
|
||||
Vector128<float> parameter3 = Sse.LoadVector128(pParameters + baseIndex3);
|
||||
Vector128<float> parameter0 = Sse.LoadVector128(pParameters + baseIndex0);
|
||||
Vector128<float> parameter1 = Sse.LoadVector128(pParameters + baseIndex1);
|
||||
Vector128<float> parameter2 = Sse.LoadVector128(pParameters + baseIndex2);
|
||||
Vector128<float> parameter3 = Sse.LoadVector128(pParameters + baseIndex3);
|
||||
|
||||
Vector128<int> intInput0 = Sse41.ConvertToVector128Int32(pInput + inputIndex0);
|
||||
Vector128<int> intInput1 = Sse41.ConvertToVector128Int32(pInput + inputIndex1);
|
||||
Vector128<int> intInput2 = Sse41.ConvertToVector128Int32(pInput + inputIndex2);
|
||||
Vector128<int> intInput3 = Sse41.ConvertToVector128Int32(pInput + inputIndex3);
|
||||
Vector128<int> intInput0 = Sse41.ConvertToVector128Int32(pInput + inputIndex0);
|
||||
Vector128<int> intInput1 = Sse41.ConvertToVector128Int32(pInput + inputIndex1);
|
||||
Vector128<int> intInput2 = Sse41.ConvertToVector128Int32(pInput + inputIndex2);
|
||||
Vector128<int> intInput3 = Sse41.ConvertToVector128Int32(pInput + inputIndex3);
|
||||
|
||||
Vector128<float> input0 = Sse2.ConvertToVector128Single(intInput0);
|
||||
Vector128<float> input1 = Sse2.ConvertToVector128Single(intInput1);
|
||||
Vector128<float> input2 = Sse2.ConvertToVector128Single(intInput2);
|
||||
Vector128<float> input3 = Sse2.ConvertToVector128Single(intInput3);
|
||||
Vector128<float> input0 = Sse2.ConvertToVector128Single(intInput0);
|
||||
Vector128<float> input1 = Sse2.ConvertToVector128Single(intInput1);
|
||||
Vector128<float> input2 = Sse2.ConvertToVector128Single(intInput2);
|
||||
Vector128<float> input3 = Sse2.ConvertToVector128Single(intInput3);
|
||||
|
||||
Vector128<float> mix0 = Sse.Multiply(input0, parameter0);
|
||||
Vector128<float> mix1 = Sse.Multiply(input1, parameter1);
|
||||
Vector128<float> mix2 = Sse.Multiply(input2, parameter2);
|
||||
Vector128<float> mix3 = Sse.Multiply(input3, parameter3);
|
||||
Vector128<float> mix0 = Sse.Multiply(input0, parameter0);
|
||||
Vector128<float> mix1 = Sse.Multiply(input1, parameter1);
|
||||
Vector128<float> mix2 = Sse.Multiply(input2, parameter2);
|
||||
Vector128<float> mix3 = Sse.Multiply(input3, parameter3);
|
||||
|
||||
Vector128<float> mix01 = Sse3.HorizontalAdd(mix0, mix1);
|
||||
Vector128<float> mix23 = Sse3.HorizontalAdd(mix2, mix3);
|
||||
Vector128<float> mix01 = Sse3.HorizontalAdd(mix0, mix1);
|
||||
Vector128<float> mix23 = Sse3.HorizontalAdd(mix2, mix3);
|
||||
|
||||
Vector128<float> mix0123 = Sse3.HorizontalAdd(mix01, mix23);
|
||||
Vector128<float> mix0123 = Sse3.HorizontalAdd(mix01, mix23);
|
||||
|
||||
Sse.Store(pOutput + (uint)i, Sse41.RoundToNearestInteger(mix0123));
|
||||
}
|
||||
Sse.Store(pOutput + (uint)i, Sse41.RoundToNearestInteger(mix0123));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -526,34 +522,59 @@ namespace Ryujinx.Audio.Renderer.Dsp
|
||||
return _highCurveLut2F;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void ResampleHighQuality(Span<float> outputBuffer, ReadOnlySpan<short> inputBuffer, float ratio, ref float fraction, int sampleCount)
|
||||
private static unsafe void ResampleHighQuality(Span<float> outputBuffer, ReadOnlySpan<short> inputBuffer, float ratio, ref float fraction, int sampleCount)
|
||||
{
|
||||
ReadOnlySpan<float> parameters = GetHighParameter(ratio);
|
||||
|
||||
int inputBufferIndex = 0;
|
||||
|
||||
// TODO: fast path
|
||||
|
||||
for (int i = 0; i < sampleCount; i++)
|
||||
if (Avx2.IsSupported)
|
||||
{
|
||||
int baseIndex = (int)(fraction * 128) * 8;
|
||||
ReadOnlySpan<float> parameter = parameters.Slice(baseIndex, 8);
|
||||
ReadOnlySpan<short> currentInput = inputBuffer.Slice(inputBufferIndex, 8);
|
||||
// Fast path; assumes 256-bit vectors for simplicity because the filter is 8 taps
|
||||
fixed (short* pInput = inputBuffer)
|
||||
fixed (float* pParameters = parameters)
|
||||
{
|
||||
for (int i = 0; i < sampleCount; i++)
|
||||
{
|
||||
int baseIndex = (int)(fraction * 128) * 8;
|
||||
|
||||
outputBuffer[i] = (float)Math.Round(currentInput[0] * parameter[0] +
|
||||
currentInput[1] * parameter[1] +
|
||||
currentInput[2] * parameter[2] +
|
||||
currentInput[3] * parameter[3] +
|
||||
currentInput[4] * parameter[4] +
|
||||
currentInput[5] * parameter[5] +
|
||||
currentInput[6] * parameter[6] +
|
||||
currentInput[7] * parameter[7]);
|
||||
Vector256<int> intInput = Avx2.ConvertToVector256Int32(pInput + inputBufferIndex);
|
||||
Vector256<float> floatInput = Avx.ConvertToVector256Single(intInput);
|
||||
Vector256<float> parameter = Avx.LoadVector256(pParameters + baseIndex);
|
||||
Vector256<float> dp = Avx.DotProduct(floatInput, parameter, control: 0xFF);
|
||||
|
||||
fraction += ratio;
|
||||
inputBufferIndex += (int)MathF.Truncate(fraction);
|
||||
// avx2 does an 8-element dot product piecewise so we have to sum up 2 intermediate results
|
||||
outputBuffer[i] = (float)Math.Round(dp[0] + dp[4]);
|
||||
|
||||
fraction -= (int)fraction;
|
||||
fraction += ratio;
|
||||
inputBufferIndex += (int)MathF.Truncate(fraction);
|
||||
|
||||
fraction -= (int)fraction;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < sampleCount; i++)
|
||||
{
|
||||
int baseIndex = (int)(fraction * 128) * 8;
|
||||
ReadOnlySpan<float> parameter = parameters.Slice(baseIndex, 8);
|
||||
ReadOnlySpan<short> currentInput = inputBuffer.Slice(inputBufferIndex, 8);
|
||||
|
||||
outputBuffer[i] = (float)Math.Round(currentInput[0] * parameter[0] +
|
||||
currentInput[1] * parameter[1] +
|
||||
currentInput[2] * parameter[2] +
|
||||
currentInput[3] * parameter[3] +
|
||||
currentInput[4] * parameter[4] +
|
||||
currentInput[5] * parameter[5] +
|
||||
currentInput[6] * parameter[6] +
|
||||
currentInput[7] * parameter[7]);
|
||||
|
||||
fraction += ratio;
|
||||
inputBufferIndex += (int)MathF.Truncate(fraction);
|
||||
|
||||
fraction -= (int)fraction;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,7 @@ using Ryujinx.Audio.Renderer.Server.Upsampler;
|
||||
using Ryujinx.Common.Memory;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp
|
||||
@ -70,16 +71,32 @@ namespace Ryujinx.Audio.Renderer.Dsp
|
||||
return;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
float DoFilterBank(ref UpsamplerBufferState state, in Array20<float> bank)
|
||||
{
|
||||
float result = 0.0f;
|
||||
|
||||
Debug.Assert(state.History.Length == HistoryLength);
|
||||
Debug.Assert(bank.Length == FilterBankLength);
|
||||
for (int j = 0; j < FilterBankLength; j++)
|
||||
|
||||
int curIdx = 0;
|
||||
if (Vector.IsHardwareAccelerated)
|
||||
{
|
||||
result += bank[j] * state.History[j];
|
||||
// Do SIMD-accelerated block operations where possible.
|
||||
// Only about a 2x speedup since filter bank length is short
|
||||
int stopIdx = FilterBankLength - (FilterBankLength % Vector<float>.Count);
|
||||
while (curIdx < stopIdx)
|
||||
{
|
||||
result += Vector.Dot(
|
||||
new Vector<float>(bank.AsSpan().Slice(curIdx, Vector<float>.Count)),
|
||||
new Vector<float>(state.History.AsSpan().Slice(curIdx, Vector<float>.Count)));
|
||||
curIdx += Vector<float>.Count;
|
||||
}
|
||||
}
|
||||
|
||||
while (curIdx < FilterBankLength)
|
||||
{
|
||||
result += bank[curIdx] * state.History[curIdx];
|
||||
curIdx++;
|
||||
}
|
||||
|
||||
return result;
|
||||
|
@ -53,7 +53,6 @@ using Key = Ryujinx.Input.Key;
|
||||
using MouseButton = Ryujinx.Input.MouseButton;
|
||||
using Size = Avalonia.Size;
|
||||
using Switch = Ryujinx.HLE.Switch;
|
||||
using WindowState = Avalonia.Controls.WindowState;
|
||||
|
||||
namespace Ryujinx.Ava
|
||||
{
|
||||
@ -241,7 +240,7 @@ namespace Ryujinx.Ava
|
||||
{
|
||||
DateTime currentTime = DateTime.Now;
|
||||
string filename = $"ryujinx_capture_{currentTime.Year}-{currentTime.Month:D2}-{currentTime.Day:D2}_{currentTime.Hour:D2}-{currentTime.Minute:D2}-{currentTime.Second:D2}.png";
|
||||
|
||||
|
||||
string directory = AppDataManager.Mode switch
|
||||
{
|
||||
AppDataManager.LaunchMode.Portable => Path.Combine(AppDataManager.BaseDirPath, "screenshots"),
|
||||
@ -678,7 +677,8 @@ namespace Ryujinx.Ava
|
||||
ConfigurationState.Instance.System.MemoryManagerMode,
|
||||
ConfigurationState.Instance.System.IgnoreMissingServices,
|
||||
ConfigurationState.Instance.Graphics.AspectRatio,
|
||||
ConfigurationState.Instance.System.AudioVolume);
|
||||
ConfigurationState.Instance.System.AudioVolume,
|
||||
ConfigurationState.Instance.System.UseHypervisor);
|
||||
|
||||
Device = new Switch(configuration);
|
||||
}
|
||||
@ -765,7 +765,7 @@ namespace Ryujinx.Ava
|
||||
}
|
||||
}
|
||||
|
||||
private unsafe void RenderLoop()
|
||||
private void RenderLoop()
|
||||
{
|
||||
Dispatcher.UIThread.InvokeAsync(() =>
|
||||
{
|
||||
@ -801,6 +801,8 @@ namespace Ryujinx.Ava
|
||||
Device.Gpu.InitializeShaderCache(_gpuCancellationTokenSource.Token);
|
||||
Translator.IsReadyForTranslation.Set();
|
||||
|
||||
_renderer.Window.ChangeVSyncMode(Device.EnableDeviceVsync);
|
||||
|
||||
while (_isActive)
|
||||
{
|
||||
_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.
|
||||
string dockedMode = ConfigurationState.Instance.System.EnableDockedMode ? LocaleManager.Instance[LocaleKeys.Docked] : LocaleManager.Instance[LocaleKeys.Handheld];
|
||||
|
||||
|
||||
if (GraphicsConfig.ResScale != 1)
|
||||
{
|
||||
dockedMode += $" ({GraphicsConfig.ResScale}x)";
|
||||
|
@ -7,6 +7,7 @@
|
||||
"SettingsTabSystemMemoryManagerModeSoftware": "Software",
|
||||
"SettingsTabSystemMemoryManagerModeHost": "Host (fast)",
|
||||
"SettingsTabSystemMemoryManagerModeHostUnchecked": "Host Unchecked (fastest, unsafe)",
|
||||
"SettingsTabSystemUseHypervisor": "Use Hypervisor",
|
||||
"MenuBarFile": "_File",
|
||||
"MenuBarFileOpenFromFile": "_Load Application From File",
|
||||
"MenuBarFileOpenUnpacked": "Load _Unpacked Game",
|
||||
@ -457,6 +458,7 @@
|
||||
"MemoryManagerSoftwareTooltip": "Use a software page table for address translation. Highest accuracy but slowest performance.",
|
||||
"MemoryManagerHostTooltip": "Directly map memory in the host address space. Much faster JIT compilation and execution.",
|
||||
"MemoryManagerUnsafeTooltip": "Directly map memory, but do not mask the address within the guest address space before access. Faster, but at the cost of safety. The guest application can access memory from anywhere in Ryujinx, so only run programs you trust with this mode.",
|
||||
"UseHypervisorTooltip": "Use Hypervisor instead of JIT. Greatly improves performance when available, but can be unstable in its current state.",
|
||||
"DRamTooltip": "Utilizes an alternative MemoryMode layout to mimic a Switch development model.\n\nThis is only useful for higher-resolution texture packs or 4k resolution mods. Does NOT improve performance.\n\nLeave OFF if unsure.",
|
||||
"IgnoreMissingServicesTooltip": "Ignores unimplemented Horizon OS services. This may help in bypassing crashes when booting certain games.\n\nLeave OFF if unsure.",
|
||||
"GraphicsBackendThreadingTooltip": "Executes graphics backend commands on a second thread.\n\nSpeeds up shader compilation, reduces stuttering, and improves performance on GPU drivers without multithreading support of their own. Slightly better performance on drivers with multithreading.\n\nSet to AUTO if unsure.",
|
||||
@ -507,7 +509,7 @@
|
||||
"SettingsTabNetwork": "Network",
|
||||
"SettingsTabNetworkConnection": "Network Connection",
|
||||
"SettingsTabCpuCache": "CPU Cache",
|
||||
"SettingsTabCpuMemory": "CPU Memory",
|
||||
"SettingsTabCpuMemory": "CPU Mode",
|
||||
"DialogUpdaterFlatpakNotSupportedMessage": "Please update Ryujinx via FlatHub.",
|
||||
"UpdaterDisabledWarningTitle": "Updater Disabled!",
|
||||
"GameListContextMenuOpenSdModsDirectory": "Open Atmosphere Mods Directory",
|
||||
|
@ -132,8 +132,8 @@ namespace Ryujinx.Modules
|
||||
}
|
||||
}
|
||||
|
||||
// If build not done, assume no new update are availaible.
|
||||
if (_buildUrl == null)
|
||||
// If build not done, assume no new update are available.
|
||||
if (_buildUrl is null)
|
||||
{
|
||||
if (showVersionUpToDate)
|
||||
{
|
||||
@ -240,13 +240,13 @@ namespace Ryujinx.Modules
|
||||
{
|
||||
HttpClient result = new();
|
||||
|
||||
// Required by GitHub to interract with APIs.
|
||||
// Required by GitHub to interact with APIs.
|
||||
result.DefaultRequestHeaders.Add("User-Agent", "Ryujinx-Updater/1.0.0");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static async void UpdateRyujinx(Window parent, string downloadUrl)
|
||||
private static async void UpdateRyujinx(Window parent, string downloadUrl)
|
||||
{
|
||||
_updateSuccessful = false;
|
||||
|
||||
@ -300,8 +300,6 @@ namespace Ryujinx.Modules
|
||||
ryuExe = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, OperatingSystem.IsWindows() ? "Ryujinx.exe" : "Ryujinx");
|
||||
}
|
||||
|
||||
SetFileExecutable(ryuExe);
|
||||
|
||||
Process.Start(ryuExe, CommandLineState.Arguments);
|
||||
|
||||
Environment.Exit(0);
|
||||
@ -408,9 +406,9 @@ namespace Ryujinx.Modules
|
||||
Logger.Warning?.Print(LogClass.Application, ex.Message);
|
||||
Logger.Warning?.Print(LogClass.Application, "Multi-Threaded update failed, falling back to single-threaded updater.");
|
||||
|
||||
for (int j = 0; j < webClients.Count; j++)
|
||||
foreach (WebClient webClient in webClients)
|
||||
{
|
||||
webClients[j].CancelAsync();
|
||||
webClient.CancelAsync();
|
||||
}
|
||||
|
||||
DoUpdateWithSingleThread(taskDialog, downloadUrl, updateFile);
|
||||
@ -472,22 +470,6 @@ namespace Ryujinx.Modules
|
||||
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)
|
||||
{
|
||||
// Extract Update
|
||||
@ -503,27 +485,35 @@ namespace Ryujinx.Modules
|
||||
await Task.Run(() =>
|
||||
{
|
||||
TarEntry tarEntry;
|
||||
while ((tarEntry = tarStream.GetNextEntry()) != null)
|
||||
|
||||
if (!OperatingSystem.IsWindows())
|
||||
{
|
||||
if (tarEntry.IsDirectory) continue;
|
||||
|
||||
string outPath = Path.Combine(UpdateDir, tarEntry.Name);
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(outPath));
|
||||
|
||||
using (FileStream outStream = File.OpenWrite(outPath))
|
||||
while ((tarEntry = tarStream.GetNextEntry()) is not null)
|
||||
{
|
||||
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(() =>
|
||||
{
|
||||
if (tarEntry is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
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 +593,6 @@ namespace Ryujinx.Modules
|
||||
|
||||
Directory.Delete(UpdateDir, true);
|
||||
|
||||
SetFileExecutable(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Ryujinx"));
|
||||
|
||||
_updateSuccessful = true;
|
||||
|
||||
taskDialog.Hide();
|
||||
|
@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<RuntimeIdentifiers>win10-x64;osx-x64;linux-x64</RuntimeIdentifiers>
|
||||
@ -6,11 +6,16 @@
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<Version>1.0.0-dirty</Version>
|
||||
<DefineConstants Condition=" '$(ExtraDefineConstants)' != '' ">$(DefineConstants);$(ExtraDefineConstants)</DefineConstants>
|
||||
<SigningCertificate Condition=" '$(SigningCertificate)' == '' ">-</SigningCertificate>
|
||||
<RootNamespace>Ryujinx.Ava</RootNamespace>
|
||||
<ApplicationIcon>Ryujinx.ico</ApplicationIcon>
|
||||
<TieredPGO>true</TieredPGO>
|
||||
</PropertyGroup>
|
||||
|
||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent" Condition="$([MSBuild]::IsOSPlatform('OSX'))">
|
||||
<Exec Command="codesign --entitlements $(ProjectDir)..\distribution\macos\entitlements.xml -f --deep -s $(SigningCertificate) $(TargetDir)$(TargetName)" />
|
||||
</Target>
|
||||
|
||||
<PropertyGroup Condition="'$(RuntimeIdentifier)' != ''">
|
||||
<PublishSingleFile>true</PublishSingleFile>
|
||||
<PublishTrimmed>true</PublishTrimmed>
|
||||
|
@ -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.Input;
|
||||
using Avalonia.Platform;
|
||||
using Ryujinx.Ava.UI.Helpers;
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Ui.Common.Configuration;
|
||||
using Ryujinx.Ui.Common.Helper;
|
||||
using SPB.Graphics;
|
||||
using SPB.Platform;
|
||||
using SPB.Platform.GLX;
|
||||
@ -30,6 +30,7 @@ namespace Ryujinx.Ava.UI.Renderer
|
||||
protected IntPtr NsView { get; set; }
|
||||
protected IntPtr MetalLayer { get; set; }
|
||||
|
||||
public delegate void UpdateBoundsCallbackDelegate(Rect rect);
|
||||
private UpdateBoundsCallbackDelegate _updateBoundsCallback;
|
||||
|
||||
public event EventHandler<IntPtr> WindowCreated;
|
||||
@ -237,8 +238,29 @@ namespace Ryujinx.Ava.UI.Renderer
|
||||
[SupportedOSPlatform("macos")]
|
||||
IPlatformHandle CreateMacOS()
|
||||
{
|
||||
MetalLayer = MetalHelper.GetMetalLayer(out IntPtr nsView, out _updateBoundsCallback);
|
||||
// Create a new CAMetalLayer.
|
||||
IntPtr layerClass = ObjectiveC.objc_getClass("CAMetalLayer");
|
||||
IntPtr metalLayer = ObjectiveC.IntPtr_objc_msgSend(layerClass, "alloc");
|
||||
ObjectiveC.objc_msgSend(metalLayer, "init");
|
||||
|
||||
// Create a child NSView to render into.
|
||||
IntPtr nsViewClass = ObjectiveC.objc_getClass("NSView");
|
||||
IntPtr child = ObjectiveC.IntPtr_objc_msgSend(nsViewClass, "alloc");
|
||||
ObjectiveC.objc_msgSend(child, "init", new ObjectiveC.NSRect(0, 0, 0, 0));
|
||||
|
||||
// Make its renderer our metal layer.
|
||||
ObjectiveC.objc_msgSend(child, "setWantsLayer:", 1);
|
||||
ObjectiveC.objc_msgSend(child, "setLayer:", metalLayer);
|
||||
ObjectiveC.objc_msgSend(metalLayer, "setContentsScale:", Program.DesktopScaleFactor);
|
||||
|
||||
// Ensure the scale factor is up to date.
|
||||
_updateBoundsCallback = rect =>
|
||||
{
|
||||
ObjectiveC.objc_msgSend(metalLayer, "setContentsScale:", Program.DesktopScaleFactor);
|
||||
};
|
||||
|
||||
IntPtr nsView = child;
|
||||
MetalLayer = metalLayer;
|
||||
NsView = nsView;
|
||||
|
||||
return new PlatformHandle(nsView, "NSView");
|
||||
@ -260,7 +282,7 @@ namespace Ryujinx.Ava.UI.Renderer
|
||||
[SupportedOSPlatform("macos")]
|
||||
void DestroyMacOS()
|
||||
{
|
||||
MetalHelper.DestroyMetalLayer(NsView, MetalLayer);
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
}
|
@ -22,6 +22,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using TimeZone = Ryujinx.Ava.UI.Models.TimeZone;
|
||||
|
||||
namespace Ryujinx.Ava.UI.ViewModels
|
||||
@ -59,6 +60,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
OnPropertyChanged(nameof(IsCustomResolutionScaleActive));
|
||||
}
|
||||
}
|
||||
|
||||
public int GraphicsBackendMultithreadingIndex
|
||||
{
|
||||
get => _graphicsBackendMultithreadingIndex;
|
||||
@ -106,6 +108,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
|
||||
public bool IsOpenGLAvailable => !OperatingSystem.IsMacOS();
|
||||
|
||||
public bool IsHypervisorAvailable => OperatingSystem.IsMacOS() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64;
|
||||
|
||||
public bool DirectoryChanged
|
||||
{
|
||||
get => _directoryChanged;
|
||||
@ -117,10 +121,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsMacOS
|
||||
{
|
||||
get => OperatingSystem.IsMacOS();
|
||||
}
|
||||
public bool IsMacOS => OperatingSystem.IsMacOS();
|
||||
|
||||
public bool EnableDiscordIntegration { get; set; }
|
||||
public bool CheckUpdatesOnStart { get; set; }
|
||||
@ -153,6 +154,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
public bool EnableCustomTheme { get; set; }
|
||||
public bool IsCustomResolutionScaleActive => _resolutionScale == 4;
|
||||
public bool IsVulkanSelected => GraphicsBackendIndex == 0;
|
||||
public bool UseHypervisor { get; set; }
|
||||
|
||||
public string TimeZone { get; set; }
|
||||
public string ShaderDumpPath { get; set; }
|
||||
@ -349,6 +351,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
// CPU
|
||||
EnablePptc = config.System.EnablePtc;
|
||||
MemoryMode = (int)config.System.MemoryManagerMode.Value;
|
||||
UseHypervisor = config.System.UseHypervisor;
|
||||
|
||||
// Graphics
|
||||
GraphicsBackendIndex = (int)config.Graphics.GraphicsBackend.Value;
|
||||
@ -369,7 +372,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
|
||||
// Network
|
||||
EnableInternetAccess = config.System.EnableInternetAccess;
|
||||
|
||||
|
||||
// Logging
|
||||
EnableFileLog = config.Logger.EnableFileLog;
|
||||
EnableStub = config.Logger.EnableStub;
|
||||
@ -432,6 +435,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
// CPU
|
||||
config.System.EnablePtc.Value = EnablePptc;
|
||||
config.System.MemoryManagerMode.Value = (MemoryManagerMode)MemoryMode;
|
||||
config.System.UseHypervisor.Value = UseHypervisor;
|
||||
|
||||
// Graphics
|
||||
config.Graphics.GraphicsBackend.Value = (GraphicsBackend)GraphicsBackendIndex;
|
||||
|
@ -1,4 +1,4 @@
|
||||
<UserControl
|
||||
<UserControl
|
||||
x:Class="Ryujinx.Ava.UI.Views.Settings.SettingsCPUView"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
@ -65,8 +65,14 @@
|
||||
</ComboBoxItem>
|
||||
</ComboBox>
|
||||
</StackPanel>
|
||||
<CheckBox IsChecked="{Binding UseHypervisor}"
|
||||
IsVisible="{Binding IsHypervisorAvailable}"
|
||||
ToolTip.Tip="{locale:Locale UseHypervisorTooltip}">
|
||||
<TextBlock Text="{locale:Locale SettingsTabSystemUseHypervisor}"
|
||||
ToolTip.Tip="{locale:Locale UseHypervisorTooltip}" />
|
||||
</CheckBox>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</ScrollViewer>
|
||||
</UserControl>
|
||||
</UserControl>
|
||||
|
@ -45,7 +45,15 @@ namespace Ryujinx.Common.Configuration
|
||||
|
||||
public static void Initialize(string baseDirPath)
|
||||
{
|
||||
string appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
|
||||
string appDataPath;
|
||||
if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
appDataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), "Library", "Application Support");
|
||||
}
|
||||
else
|
||||
{
|
||||
appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
|
||||
}
|
||||
|
||||
if (appDataPath.Length == 0)
|
||||
{
|
||||
@ -81,6 +89,21 @@ namespace Ryujinx.Common.Configuration
|
||||
|
||||
BaseDirPath = Path.GetFullPath(BaseDirPath); // convert relative paths
|
||||
|
||||
// NOTE: Moves the Ryujinx folder in `~/.config` to `~/Library/Application Support` if one is found
|
||||
// and a Ryujinx folder does not already exist in Application Support.
|
||||
// Also creates a symlink from `~/.config/Ryujinx` to `~/Library/Application Support/Ryujinx` to preserve backwards compatibility.
|
||||
// This should be removed in the future.
|
||||
if (OperatingSystem.IsMacOS() && Mode == LaunchMode.UserProfile)
|
||||
{
|
||||
string oldConfigPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), DefaultBaseDir);
|
||||
if (Path.Exists(oldConfigPath) && !Path.Exists(BaseDirPath))
|
||||
{
|
||||
CopyDirectory(oldConfigPath, BaseDirPath);
|
||||
Directory.Delete(oldConfigPath, true);
|
||||
Directory.CreateSymbolicLink(oldConfigPath, BaseDirPath);
|
||||
}
|
||||
}
|
||||
|
||||
SetupBasePaths();
|
||||
}
|
||||
|
||||
@ -92,6 +115,34 @@ namespace Ryujinx.Common.Configuration
|
||||
Directory.CreateDirectory(KeysDirPath = Path.Combine(BaseDirPath, KeysDir));
|
||||
}
|
||||
|
||||
private static void CopyDirectory(string sourceDir, string destinationDir)
|
||||
{
|
||||
var dir = new DirectoryInfo(sourceDir);
|
||||
|
||||
if (!dir.Exists)
|
||||
{
|
||||
throw new DirectoryNotFoundException($"Source directory not found: {dir.FullName}");
|
||||
}
|
||||
|
||||
DirectoryInfo[] subDirs = dir.GetDirectories();
|
||||
Directory.CreateDirectory(destinationDir);
|
||||
|
||||
foreach (FileInfo file in dir.GetFiles())
|
||||
{
|
||||
if (file.Name == ".DS_Store")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
file.CopyTo(Path.Combine(destinationDir, file.Name));
|
||||
}
|
||||
|
||||
foreach (DirectoryInfo subDir in subDirs)
|
||||
{
|
||||
CopyDirectory(subDir.FullName, Path.Combine(destinationDir, subDir.Name));
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetModsPath() => CustomModsPath ?? Directory.CreateDirectory(Path.Combine(BaseDirPath, DefaultModsDir)).FullName;
|
||||
public static string GetSdModsPath() => CustomSdModsPath ?? Directory.CreateDirectory(Path.Combine(BaseDirPath, DefaultSdcardDir, "atmosphere")).FullName;
|
||||
}
|
||||
|
@ -40,14 +40,21 @@ namespace Ryujinx.Common
|
||||
}
|
||||
}
|
||||
|
||||
#if FORCE_EXTERNAL_BASE_DIR
|
||||
public static string GetBaseApplicationDirectory()
|
||||
{
|
||||
if (IsFlatHubBuild())
|
||||
return AppDataManager.BaseDirPath;
|
||||
}
|
||||
#else
|
||||
public static string GetBaseApplicationDirectory()
|
||||
{
|
||||
if (IsFlatHubBuild() || OperatingSystem.IsMacOS())
|
||||
{
|
||||
return AppDataManager.BaseDirPath;
|
||||
}
|
||||
|
||||
return AppDomain.CurrentDomain.BaseDirectory;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
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, int? exemptId = null)
|
||||
{
|
||||
AssertValidAddressAndSize(va, size);
|
||||
|
||||
if (precise)
|
||||
{
|
||||
Tracking.VirtualMemoryEvent(va, size, write, precise: true, exemptId);
|
||||
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, precise: false, exemptId);
|
||||
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, precise: false, exemptId);
|
||||
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, int id)
|
||||
{
|
||||
return new CpuRegionHandle(Tracking.BeginTracking(address, size, id));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public CpuMultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable<IRegionHandle> handles, ulong granularity, int id)
|
||||
{
|
||||
return new CpuMultiRegionHandle(Tracking.BeginGranularTracking(address, size, handles, granularity, id));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public CpuSmartMultiRegionHandle BeginSmartGranularTracking(ulong address, ulong size, ulong granularity, int id)
|
||||
{
|
||||
return new CpuSmartMultiRegionHandle(Tracking.BeginSmartGranularTracking(address, size, granularity, id));
|
||||
}
|
||||
|
||||
/// <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();
|
||||
}
|
||||
}
|
@ -28,8 +28,9 @@ namespace Ryujinx.Cpu
|
||||
/// </summary>
|
||||
/// <param name="address">CPU virtual address of the region</param>
|
||||
/// <param name="size">Size of the region</param>
|
||||
/// <param name="id">Handle ID</param>
|
||||
/// <returns>The memory tracking handle</returns>
|
||||
CpuRegionHandle BeginTracking(ulong address, ulong size);
|
||||
CpuRegionHandle BeginTracking(ulong address, ulong size, int id);
|
||||
|
||||
/// <summary>
|
||||
/// Obtains a memory tracking handle for the given virtual region, with a specified granularity. This should be disposed when finished with.
|
||||
@ -38,8 +39,9 @@ namespace Ryujinx.Cpu
|
||||
/// <param name="size">Size of the region</param>
|
||||
/// <param name="handles">Handles to inherit state from or reuse. When none are present, provide null</param>
|
||||
/// <param name="granularity">Desired granularity of write tracking</param>
|
||||
/// <param name="id">Handle ID</param>
|
||||
/// <returns>The memory tracking handle</returns>
|
||||
CpuMultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable<IRegionHandle> handles, ulong granularity);
|
||||
CpuMultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable<IRegionHandle> handles, ulong granularity, int id);
|
||||
|
||||
/// <summary>
|
||||
/// Obtains a smart memory tracking handle for the given virtual region, with a specified granularity. This should be disposed when finished with.
|
||||
@ -47,7 +49,8 @@ namespace Ryujinx.Cpu
|
||||
/// <param name="address">CPU virtual address of the region</param>
|
||||
/// <param name="size">Size of the region</param>
|
||||
/// <param name="granularity">Desired granularity of write tracking</param>
|
||||
/// <param name="id">Handle ID</param>
|
||||
/// <returns>The memory tracking handle</returns>
|
||||
CpuSmartMultiRegionHandle BeginSmartGranularTracking(ulong address, ulong size, ulong granularity);
|
||||
CpuSmartMultiRegionHandle BeginSmartGranularTracking(ulong address, ulong size, ulong granularity, int id);
|
||||
}
|
||||
}
|
||||
|
@ -629,31 +629,31 @@ namespace Ryujinx.Cpu.Jit
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public CpuRegionHandle BeginTracking(ulong address, ulong size)
|
||||
public CpuRegionHandle BeginTracking(ulong address, ulong size, int id)
|
||||
{
|
||||
return new CpuRegionHandle(Tracking.BeginTracking(address, size));
|
||||
return new CpuRegionHandle(Tracking.BeginTracking(address, size, id));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public CpuMultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable<IRegionHandle> handles, ulong granularity)
|
||||
public CpuMultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable<IRegionHandle> handles, ulong granularity, int id)
|
||||
{
|
||||
return new CpuMultiRegionHandle(Tracking.BeginGranularTracking(address, size, handles, granularity));
|
||||
return new CpuMultiRegionHandle(Tracking.BeginGranularTracking(address, size, handles, granularity, id));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public CpuSmartMultiRegionHandle BeginSmartGranularTracking(ulong address, ulong size, ulong granularity)
|
||||
public CpuSmartMultiRegionHandle BeginSmartGranularTracking(ulong address, ulong size, ulong granularity, int id)
|
||||
{
|
||||
return new CpuSmartMultiRegionHandle(Tracking.BeginSmartGranularTracking(address, size, granularity));
|
||||
return new CpuSmartMultiRegionHandle(Tracking.BeginSmartGranularTracking(address, size, granularity, id));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false)
|
||||
public void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false, int? exemptId = null)
|
||||
{
|
||||
AssertValidAddressAndSize(va, size);
|
||||
|
||||
if (precise)
|
||||
{
|
||||
Tracking.VirtualMemoryEvent(va, size, write, precise: true);
|
||||
Tracking.VirtualMemoryEvent(va, size, write, precise: true, exemptId);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -676,7 +676,7 @@ namespace Ryujinx.Cpu.Jit
|
||||
|
||||
if ((pte & tag) != 0)
|
||||
{
|
||||
Tracking.VirtualMemoryEvent(va, size, write);
|
||||
Tracking.VirtualMemoryEvent(va, size, write, precise: false, exemptId);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -518,13 +518,13 @@ namespace Ryujinx.Cpu.Jit
|
||||
/// <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)
|
||||
public void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false, int? exemptId = null)
|
||||
{
|
||||
AssertValidAddressAndSize(va, size);
|
||||
|
||||
if (precise)
|
||||
{
|
||||
Tracking.VirtualMemoryEvent(va, size, write, precise: true);
|
||||
Tracking.VirtualMemoryEvent(va, size, write, precise: true, exemptId);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -547,7 +547,7 @@ namespace Ryujinx.Cpu.Jit
|
||||
|
||||
if (state >= tag)
|
||||
{
|
||||
Tracking.VirtualMemoryEvent(va, size, write);
|
||||
Tracking.VirtualMemoryEvent(va, size, write, precise: false, exemptId);
|
||||
return;
|
||||
}
|
||||
else if (state == 0)
|
||||
@ -590,7 +590,7 @@ namespace Ryujinx.Cpu.Jit
|
||||
// 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);
|
||||
Tracking.VirtualMemoryEvent(va, size, write, precise: false, exemptId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -706,21 +706,21 @@ namespace Ryujinx.Cpu.Jit
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public CpuRegionHandle BeginTracking(ulong address, ulong size)
|
||||
public CpuRegionHandle BeginTracking(ulong address, ulong size, int id)
|
||||
{
|
||||
return new CpuRegionHandle(Tracking.BeginTracking(address, size));
|
||||
return new CpuRegionHandle(Tracking.BeginTracking(address, size, id));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public CpuMultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable<IRegionHandle> handles, ulong granularity)
|
||||
public CpuMultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable<IRegionHandle> handles, ulong granularity, int id)
|
||||
{
|
||||
return new CpuMultiRegionHandle(Tracking.BeginGranularTracking(address, size, handles, granularity));
|
||||
return new CpuMultiRegionHandle(Tracking.BeginGranularTracking(address, size, handles, granularity, id));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public CpuSmartMultiRegionHandle BeginSmartGranularTracking(ulong address, ulong size, ulong granularity)
|
||||
public CpuSmartMultiRegionHandle BeginSmartGranularTracking(ulong address, ulong size, ulong granularity, int id)
|
||||
{
|
||||
return new CpuSmartMultiRegionHandle(Tracking.BeginSmartGranularTracking(address, size, granularity));
|
||||
return new CpuSmartMultiRegionHandle(Tracking.BeginSmartGranularTracking(address, size, granularity, id));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -8,7 +8,7 @@ namespace Ryujinx.Cpu
|
||||
{
|
||||
public class MemoryEhMeilleure : IDisposable
|
||||
{
|
||||
private delegate bool TrackingEventDelegate(ulong address, ulong size, bool write, bool precise = false);
|
||||
private delegate bool TrackingEventDelegate(ulong address, ulong size, bool write);
|
||||
|
||||
private readonly MemoryTracking _tracking;
|
||||
private readonly TrackingEventDelegate _trackingEvent;
|
||||
|
16
Ryujinx.Graphics.GAL/AdvancedBlendDescriptor.cs
Normal file
16
Ryujinx.Graphics.GAL/AdvancedBlendDescriptor.cs
Normal file
@ -0,0 +1,16 @@
|
||||
namespace Ryujinx.Graphics.GAL
|
||||
{
|
||||
public struct AdvancedBlendDescriptor
|
||||
{
|
||||
public AdvancedBlendOp Op { get; }
|
||||
public AdvancedBlendOverlap Overlap { get; }
|
||||
public bool SrcPreMultiplied { get; }
|
||||
|
||||
public AdvancedBlendDescriptor(AdvancedBlendOp op, AdvancedBlendOverlap overlap, bool srcPreMultiplied)
|
||||
{
|
||||
Op = op;
|
||||
Overlap = overlap;
|
||||
SrcPreMultiplied = srcPreMultiplied;
|
||||
}
|
||||
}
|
||||
}
|
52
Ryujinx.Graphics.GAL/AdvancedBlendOp.cs
Normal file
52
Ryujinx.Graphics.GAL/AdvancedBlendOp.cs
Normal file
@ -0,0 +1,52 @@
|
||||
namespace Ryujinx.Graphics.GAL
|
||||
{
|
||||
public enum AdvancedBlendOp
|
||||
{
|
||||
Zero,
|
||||
Src,
|
||||
Dst,
|
||||
SrcOver,
|
||||
DstOver,
|
||||
SrcIn,
|
||||
DstIn,
|
||||
SrcOut,
|
||||
DstOut,
|
||||
SrcAtop,
|
||||
DstAtop,
|
||||
Xor,
|
||||
Plus,
|
||||
PlusClamped,
|
||||
PlusClampedAlpha,
|
||||
PlusDarker,
|
||||
Multiply,
|
||||
Screen,
|
||||
Overlay,
|
||||
Darken,
|
||||
Lighten,
|
||||
ColorDodge,
|
||||
ColorBurn,
|
||||
HardLight,
|
||||
SoftLight,
|
||||
Difference,
|
||||
Minus,
|
||||
MinusClamped,
|
||||
Exclusion,
|
||||
Contrast,
|
||||
Invert,
|
||||
InvertRGB,
|
||||
InvertOvg,
|
||||
LinearDodge,
|
||||
LinearBurn,
|
||||
VividLight,
|
||||
LinearLight,
|
||||
PinLight,
|
||||
HardMix,
|
||||
Red,
|
||||
Green,
|
||||
Blue,
|
||||
HslHue,
|
||||
HslSaturation,
|
||||
HslColor,
|
||||
HslLuminosity
|
||||
}
|
||||
}
|
9
Ryujinx.Graphics.GAL/AdvancedBlendOverlap.cs
Normal file
9
Ryujinx.Graphics.GAL/AdvancedBlendOverlap.cs
Normal file
@ -0,0 +1,9 @@
|
||||
namespace Ryujinx.Graphics.GAL
|
||||
{
|
||||
public enum AdvancedBlendOverlap
|
||||
{
|
||||
Uncorrelated,
|
||||
Disjoint,
|
||||
Conjoint
|
||||
}
|
||||
}
|
@ -23,6 +23,7 @@ namespace Ryujinx.Graphics.GAL
|
||||
public readonly bool SupportsR4G4B4A4Format;
|
||||
public readonly bool SupportsSnormBufferTextureFormat;
|
||||
public readonly bool Supports5BitComponentFormat;
|
||||
public readonly bool SupportsBlendEquationAdvanced;
|
||||
public readonly bool SupportsFragmentShaderInterlock;
|
||||
public readonly bool SupportsFragmentShaderOrderingIntel;
|
||||
public readonly bool SupportsGeometryShaderPassthrough;
|
||||
@ -64,6 +65,7 @@ namespace Ryujinx.Graphics.GAL
|
||||
bool supportsR4G4B4A4Format,
|
||||
bool supportsSnormBufferTextureFormat,
|
||||
bool supports5BitComponentFormat,
|
||||
bool supportsBlendEquationAdvanced,
|
||||
bool supportsFragmentShaderInterlock,
|
||||
bool supportsFragmentShaderOrderingIntel,
|
||||
bool supportsGeometryShaderPassthrough,
|
||||
@ -102,6 +104,7 @@ namespace Ryujinx.Graphics.GAL
|
||||
SupportsR4G4B4A4Format = supportsR4G4B4A4Format;
|
||||
SupportsSnormBufferTextureFormat = supportsSnormBufferTextureFormat;
|
||||
Supports5BitComponentFormat = supports5BitComponentFormat;
|
||||
SupportsBlendEquationAdvanced = supportsBlendEquationAdvanced;
|
||||
SupportsFragmentShaderInterlock = supportsFragmentShaderInterlock;
|
||||
SupportsFragmentShaderOrderingIntel = supportsFragmentShaderOrderingIntel;
|
||||
SupportsGeometryShaderPassthrough = supportsGeometryShaderPassthrough;
|
||||
|
@ -44,6 +44,7 @@ namespace Ryujinx.Graphics.GAL
|
||||
|
||||
void SetAlphaTest(bool enable, float reference, CompareOp op);
|
||||
|
||||
void SetBlendState(AdvancedBlendDescriptor blend);
|
||||
void SetBlendState(int index, BlendDescriptor blend);
|
||||
|
||||
void SetDepthBias(PolygonModeMask enables, float factor, float units, float clamp);
|
||||
|
@ -98,6 +98,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
||||
Register<EndHostConditionalRenderingCommand>(CommandType.EndHostConditionalRendering);
|
||||
Register<EndTransformFeedbackCommand>(CommandType.EndTransformFeedback);
|
||||
Register<SetAlphaTestCommand>(CommandType.SetAlphaTest);
|
||||
Register<SetBlendStateAdvancedCommand>(CommandType.SetBlendStateAdvanced);
|
||||
Register<SetBlendStateCommand>(CommandType.SetBlendState);
|
||||
Register<SetDepthBiasCommand>(CommandType.SetDepthBias);
|
||||
Register<SetDepthClampCommand>(CommandType.SetDepthClamp);
|
||||
|
@ -60,6 +60,7 @@
|
||||
EndHostConditionalRendering,
|
||||
EndTransformFeedback,
|
||||
SetAlphaTest,
|
||||
SetBlendStateAdvanced,
|
||||
SetBlendState,
|
||||
SetDepthBias,
|
||||
SetDepthClamp,
|
||||
|
@ -0,0 +1,18 @@
|
||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
||||
{
|
||||
struct SetBlendStateAdvancedCommand : IGALCommand, IGALCommand<SetBlendStateAdvancedCommand>
|
||||
{
|
||||
public CommandType CommandType => CommandType.SetBlendStateAdvanced;
|
||||
private AdvancedBlendDescriptor _blend;
|
||||
|
||||
public void Set(AdvancedBlendDescriptor blend)
|
||||
{
|
||||
_blend = blend;
|
||||
}
|
||||
|
||||
public static void Run(ref SetBlendStateAdvancedCommand command, ThreadedRenderer threaded, IRenderer renderer)
|
||||
{
|
||||
renderer.Pipeline.SetBlendState(command._blend);
|
||||
}
|
||||
}
|
||||
}
|
@ -131,6 +131,12 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
||||
_renderer.QueueCommand();
|
||||
}
|
||||
|
||||
public void SetBlendState(AdvancedBlendDescriptor blend)
|
||||
{
|
||||
_renderer.New<SetBlendStateAdvancedCommand>().Set(blend);
|
||||
_renderer.QueueCommand();
|
||||
}
|
||||
|
||||
public void SetBlendState(int index, BlendDescriptor blend)
|
||||
{
|
||||
_renderer.New<SetBlendStateCommand>().Set(index, blend);
|
||||
|
@ -197,7 +197,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.InlineToMemory
|
||||
|
||||
if (target != null)
|
||||
{
|
||||
target.SynchronizeMemory();
|
||||
target.SetData(data, 0, 0, new GAL.Rectangle<int>(_dstX, _dstY, _lineLengthIn / target.Info.FormatInfo.BytesPerPixel, _lineCount));
|
||||
target.SignalModified();
|
||||
|
||||
return;
|
||||
}
|
||||
|
4226
Ryujinx.Graphics.Gpu/Engine/Threed/Blender/AdvancedBlendFunctions.cs
Normal file
4226
Ryujinx.Graphics.Gpu/Engine/Threed/Blender/AdvancedBlendFunctions.cs
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,115 @@
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Engine.Threed.Blender
|
||||
{
|
||||
/// <summary>
|
||||
/// Advanced blend manager.
|
||||
/// </summary>
|
||||
class AdvancedBlendManager
|
||||
{
|
||||
private const int InstructionRamSize = 128;
|
||||
private const int InstructionRamSizeMask = InstructionRamSize - 1;
|
||||
|
||||
private readonly DeviceStateWithShadow<ThreedClassState> _state;
|
||||
|
||||
private readonly uint[] _code;
|
||||
private int _ip;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the advanced blend manager.
|
||||
/// </summary>
|
||||
/// <param name="state">GPU state of the channel owning this manager</param>
|
||||
public AdvancedBlendManager(DeviceStateWithShadow<ThreedClassState> state)
|
||||
{
|
||||
_state = state;
|
||||
_code = new uint[InstructionRamSize];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the start offset of the blend microcode in memory.
|
||||
/// </summary>
|
||||
/// <param name="argument">Method call argument</param>
|
||||
public void LoadBlendUcodeStart(int argument)
|
||||
{
|
||||
_ip = argument;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pushes one word of blend microcode.
|
||||
/// </summary>
|
||||
/// <param name="argument">Method call argument</param>
|
||||
public void LoadBlendUcodeInstruction(int argument)
|
||||
{
|
||||
_code[_ip++ & InstructionRamSizeMask] = (uint)argument;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to identify the current advanced blend function being used,
|
||||
/// given the current state and microcode that was uploaded.
|
||||
/// </summary>
|
||||
/// <param name="descriptor">Advanced blend descriptor</param>
|
||||
/// <returns>True if the function was found, false otherwise</returns>
|
||||
public bool TryGetAdvancedBlend(out AdvancedBlendDescriptor descriptor)
|
||||
{
|
||||
Span<uint> currentCode = new Span<uint>(_code);
|
||||
byte codeLength = (byte)_state.State.BlendUcodeSize;
|
||||
|
||||
if (currentCode.Length > codeLength)
|
||||
{
|
||||
currentCode = currentCode.Slice(0, codeLength);
|
||||
}
|
||||
|
||||
Hash128 hash = XXHash128.ComputeHash(MemoryMarshal.Cast<uint, byte>(currentCode));
|
||||
|
||||
descriptor = default;
|
||||
|
||||
if (!AdvancedBlendPreGenTable.Entries.TryGetValue(hash, out var entry))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (entry.Constants != null)
|
||||
{
|
||||
bool constantsMatch = true;
|
||||
|
||||
for (int i = 0; i < entry.Constants.Length; i++)
|
||||
{
|
||||
RgbFloat constant = entry.Constants[i];
|
||||
RgbHalf constant2 = _state.State.BlendUcodeConstants[i];
|
||||
|
||||
if ((Half)constant.R != constant2.UnpackR() ||
|
||||
(Half)constant.G != constant2.UnpackG() ||
|
||||
(Half)constant.B != constant2.UnpackB())
|
||||
{
|
||||
constantsMatch = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!constantsMatch)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (entry.Alpha.Enable != _state.State.BlendUcodeEnable)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (entry.Alpha.Enable == BlendUcodeEnable.EnableRGBA &&
|
||||
(entry.Alpha.AlphaOp != _state.State.BlendStateCommon.AlphaOp ||
|
||||
entry.Alpha.AlphaSrcFactor != _state.State.BlendStateCommon.AlphaSrcFactor ||
|
||||
entry.Alpha.AlphaDstFactor != _state.State.BlendStateCommon.AlphaDstFactor))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
descriptor = new AdvancedBlendDescriptor(entry.Op, entry.Overlap, entry.SrcPreMultiplied);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,273 @@
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Engine.Threed.Blender
|
||||
{
|
||||
/// <summary>
|
||||
/// Advanced blend function entry.
|
||||
/// </summary>
|
||||
struct AdvancedBlendEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// Advanced blend operation.
|
||||
/// </summary>
|
||||
public AdvancedBlendOp Op { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Advanced blend overlap mode.
|
||||
/// </summary>
|
||||
public AdvancedBlendOverlap Overlap { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Whenever the source input is pre-multiplied.
|
||||
/// </summary>
|
||||
public bool SrcPreMultiplied { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Constants used by the microcode.
|
||||
/// </summary>
|
||||
public RgbFloat[] Constants { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Fixed function alpha state.
|
||||
/// </summary>
|
||||
public FixedFunctionAlpha Alpha { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new advanced blend function entry.
|
||||
/// </summary>
|
||||
/// <param name="op">Advanced blend operation</param>
|
||||
/// <param name="overlap">Advanced blend overlap mode</param>
|
||||
/// <param name="srcPreMultiplied">Whenever the source input is pre-multiplied</param>
|
||||
/// <param name="constants">Constants used by the microcode</param>
|
||||
/// <param name="alpha">Fixed function alpha state</param>
|
||||
public AdvancedBlendEntry(
|
||||
AdvancedBlendOp op,
|
||||
AdvancedBlendOverlap overlap,
|
||||
bool srcPreMultiplied,
|
||||
RgbFloat[] constants,
|
||||
FixedFunctionAlpha alpha)
|
||||
{
|
||||
Op = op;
|
||||
Overlap = overlap;
|
||||
SrcPreMultiplied = srcPreMultiplied;
|
||||
Constants = constants;
|
||||
Alpha = alpha;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pre-generated hash table with advanced blend functions used by the driver.
|
||||
/// </summary>
|
||||
static class AdvancedBlendPreGenTable
|
||||
{
|
||||
/// <summary>
|
||||
/// Advanced blend functions dictionary.
|
||||
/// </summary>
|
||||
public static readonly IReadOnlyDictionary<Hash128, AdvancedBlendEntry> Entries = new Dictionary<Hash128, AdvancedBlendEntry>()
|
||||
{
|
||||
{ new Hash128(0x19ECF57B83DE31F7, 0x5BAE759246F264C0), new AdvancedBlendEntry(AdvancedBlendOp.PlusClamped, AdvancedBlendOverlap.Uncorrelated, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0xDE1B14A356A1A9ED, 0x59D803593C607C1D), new AdvancedBlendEntry(AdvancedBlendOp.PlusClampedAlpha, AdvancedBlendOverlap.Uncorrelated, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x1A3C3A6D32DEC368, 0xBCAE519EC6AAA045), new AdvancedBlendEntry(AdvancedBlendOp.PlusDarker, AdvancedBlendOverlap.Uncorrelated, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x6FD380261A63B240, 0x17C3B335DBB9E3DB), new AdvancedBlendEntry(AdvancedBlendOp.Multiply, AdvancedBlendOverlap.Uncorrelated, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0x1D39164823D3A2D1, 0xC45350959CE1C8FB), new AdvancedBlendEntry(AdvancedBlendOp.Screen, AdvancedBlendOverlap.Uncorrelated, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0x18DF09FF53B129FE, 0xC02EDA33C36019F6), new AdvancedBlendEntry(AdvancedBlendOp.Overlay, AdvancedBlendOverlap.Uncorrelated, true, new[] { new RgbFloat(0.5f, 0.5f, 0.5f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0x5973E583271EBF06, 0x711497D75D1272E0), new AdvancedBlendEntry(AdvancedBlendOp.Darken, AdvancedBlendOverlap.Uncorrelated, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0x4759E0E5DA54D5E8, 0x1FDD57C0C38AFA1F), new AdvancedBlendEntry(AdvancedBlendOp.Lighten, AdvancedBlendOverlap.Uncorrelated, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0x337684D43CCE97FA, 0x0139E30CC529E1C9), new AdvancedBlendEntry(AdvancedBlendOp.ColorDodge, AdvancedBlendOverlap.Uncorrelated, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0xDA59E85D8428992D, 0x1D3D7C64C9EF0132), new AdvancedBlendEntry(AdvancedBlendOp.ColorBurn, AdvancedBlendOverlap.Uncorrelated, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0x9455B949298CE805, 0xE73D3301518BE98A), new AdvancedBlendEntry(AdvancedBlendOp.HardLight, AdvancedBlendOverlap.Uncorrelated, true, new[] { new RgbFloat(0.5f, 0.5f, 0.5f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0xBDD3B4DEDBE336AA, 0xBFA4DCD50D535DEE), new AdvancedBlendEntry(AdvancedBlendOp.SoftLight, AdvancedBlendOverlap.Uncorrelated, true, new[] { new RgbFloat(0.2605f, 0.2605f, 0.2605f), new RgbFloat(-0.7817f, -0.7817f, -0.7817f), new RgbFloat(0.3022f, 0.3022f, 0.3022f), new RgbFloat(0.2192f, 0.2192f, 0.2192f), new RgbFloat(0.25f, 0.25f, 0.25f), new RgbFloat(16f, 16f, 16f), new RgbFloat(12f, 12f, 12f), new RgbFloat(3f, 3f, 3f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0x22D4E970A028649A, 0x4F3FCB055FCED965), new AdvancedBlendEntry(AdvancedBlendOp.Difference, AdvancedBlendOverlap.Uncorrelated, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0xA346A91311D72114, 0x151A27A3FB0A1904), new AdvancedBlendEntry(AdvancedBlendOp.Minus, AdvancedBlendOverlap.Uncorrelated, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.ReverseSubtractGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x8A307241061FACD6, 0xA39D1826440B8EE7), new AdvancedBlendEntry(AdvancedBlendOp.MinusClamped, AdvancedBlendOverlap.Uncorrelated, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0xB3BE569485EFFFE0, 0x0BA4E269B3CFB165), new AdvancedBlendEntry(AdvancedBlendOp.Exclusion, AdvancedBlendOverlap.Uncorrelated, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0x36FCA3277DC11822, 0x2BC0F6CAC2029672), new AdvancedBlendEntry(AdvancedBlendOp.Contrast, AdvancedBlendOverlap.Uncorrelated, true, new[] { new RgbFloat(2f, 2f, 2f), new RgbFloat(0.5f, 0.5f, 0.5f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x4A6226AF2DE9BD7F, 0xEB890D7DA716F73A), new AdvancedBlendEntry(AdvancedBlendOp.Invert, AdvancedBlendOverlap.Uncorrelated, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0xF364CAA94E160FEB, 0xBF364512C72A3797), new AdvancedBlendEntry(AdvancedBlendOp.InvertRGB, AdvancedBlendOverlap.Uncorrelated, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x6BF791AB4AC19C87, 0x6FA17A994EA0FCDE), new AdvancedBlendEntry(AdvancedBlendOp.InvertOvg, AdvancedBlendOverlap.Uncorrelated, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0x053C75A0AE0BB222, 0x03C791FEEB59754C), new AdvancedBlendEntry(AdvancedBlendOp.LinearDodge, AdvancedBlendOverlap.Uncorrelated, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0x25762AB40B6CBDE9, 0x595E9A968AC4F01C), new AdvancedBlendEntry(AdvancedBlendOp.LinearBurn, AdvancedBlendOverlap.Uncorrelated, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0xC2D05E2DBE16955D, 0xB8659C7A3FCFA7CE), new AdvancedBlendEntry(AdvancedBlendOp.VividLight, AdvancedBlendOverlap.Uncorrelated, true, new[] { new RgbFloat(0.5f, 0.5f, 0.5f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0x223F220B8F74CBFB, 0xD3DD19D7C39209A5), new AdvancedBlendEntry(AdvancedBlendOp.LinearLight, AdvancedBlendOverlap.Uncorrelated, true, new[] { new RgbFloat(2f, 2f, 2f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0xD0DAE57A9F1FE78A, 0x353796BCFB8CE30B), new AdvancedBlendEntry(AdvancedBlendOp.PinLight, AdvancedBlendOverlap.Uncorrelated, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0x601C8CBEC07FF8FF, 0xB8E22882360E8695), new AdvancedBlendEntry(AdvancedBlendOp.HardMix, AdvancedBlendOverlap.Uncorrelated, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0x3A55B7B78C76A7A8, 0x206F503B2D9FFEAA), new AdvancedBlendEntry(AdvancedBlendOp.Red, AdvancedBlendOverlap.Uncorrelated, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x80BC65C7831388E5, 0xC652457B2C766AEC), new AdvancedBlendEntry(AdvancedBlendOp.Green, AdvancedBlendOverlap.Uncorrelated, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x3D3A912E5833EE13, 0x307895951349EE33), new AdvancedBlendEntry(AdvancedBlendOp.Blue, AdvancedBlendOverlap.Uncorrelated, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x289105BE92E81803, 0xFD8F1F03D15C53B4), new AdvancedBlendEntry(AdvancedBlendOp.HslHue, AdvancedBlendOverlap.Uncorrelated, true, new[] { new RgbFloat(0.3f, 0.59f, 0.11f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0x007AE3BD140764EB, 0x0EE05A0D2E80BBAE), new AdvancedBlendEntry(AdvancedBlendOp.HslSaturation, AdvancedBlendOverlap.Uncorrelated, true, new[] { new RgbFloat(0.3f, 0.59f, 0.11f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0x77F7EE0DB3FDDB96, 0xDEA47C881306DB3E), new AdvancedBlendEntry(AdvancedBlendOp.HslColor, AdvancedBlendOverlap.Uncorrelated, true, new[] { new RgbFloat(0.3f, 0.59f, 0.11f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0x66F4E9A7D73CA157, 0x1486058A177DB11C), new AdvancedBlendEntry(AdvancedBlendOp.HslLuminosity, AdvancedBlendOverlap.Uncorrelated, true, new[] { new RgbFloat(0.3f, 0.59f, 0.11f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0x593E9F331612D618, 0x9D217BEFA4EB919A), new AdvancedBlendEntry(AdvancedBlendOp.Src, AdvancedBlendOverlap.Disjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.ZeroGl)) },
|
||||
{ new Hash128(0x0A5194C5E6891106, 0xDD8EC6586106557C), new AdvancedBlendEntry(AdvancedBlendOp.Dst, AdvancedBlendOverlap.Disjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x8D77173D5E06E916, 0x06AB190E7D10F4D4), new AdvancedBlendEntry(AdvancedBlendOp.SrcOver, AdvancedBlendOverlap.Disjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x655B4EBC148981DA, 0x455999EF2B9BD28A), new AdvancedBlendEntry(AdvancedBlendOp.DstOver, AdvancedBlendOverlap.Disjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x98F5437D5F518929, 0xBFF4A6E83183DB63), new AdvancedBlendEntry(AdvancedBlendOp.SrcIn, AdvancedBlendOverlap.Disjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x6ADDEFE3B9CEF2FD, 0xB6F6272AFECB1AAB), new AdvancedBlendEntry(AdvancedBlendOp.DstIn, AdvancedBlendOverlap.Disjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x80953F0953BF05B1, 0xD59ABFAA34F8196F), new AdvancedBlendEntry(AdvancedBlendOp.SrcOut, AdvancedBlendOverlap.Disjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0xA401D9AA2A39C121, 0xFC0C8005C22AD7E3), new AdvancedBlendEntry(AdvancedBlendOp.DstOut, AdvancedBlendOverlap.Disjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x06274FB7CA9CDD22, 0x6CE8188B1A9AB6EF), new AdvancedBlendEntry(AdvancedBlendOp.SrcAtop, AdvancedBlendOverlap.Disjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x0B079BE7F7F70817, 0xB72E7736CA51E321), new AdvancedBlendEntry(AdvancedBlendOp.DstAtop, AdvancedBlendOverlap.Disjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.ZeroGl)) },
|
||||
{ new Hash128(0x66215C99403CEDDE, 0x900B733D62204C48), new AdvancedBlendEntry(AdvancedBlendOp.Xor, AdvancedBlendOverlap.Disjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x12DEF2AD900CAD6C, 0x58CF5CC3004910DF), new AdvancedBlendEntry(AdvancedBlendOp.Plus, AdvancedBlendOverlap.Disjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x272BA3A49F64DAE4, 0xAC70B96C00A99EAF), new AdvancedBlendEntry(AdvancedBlendOp.Multiply, AdvancedBlendOverlap.Disjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x206C34AAA7D3F545, 0xDA4B30CACAA483A0), new AdvancedBlendEntry(AdvancedBlendOp.Screen, AdvancedBlendOverlap.Disjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x3D93494920D257BE, 0xDCC573BE1F5F4449), new AdvancedBlendEntry(AdvancedBlendOp.Overlay, AdvancedBlendOverlap.Disjoint, true, new[] { new RgbFloat(0.5f, 0.5f, 0.5f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x0D7417D80191107B, 0xEAF40547827E005F), new AdvancedBlendEntry(AdvancedBlendOp.Darken, AdvancedBlendOverlap.Disjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0xEC1B03E8C883F9C9, 0x2D3CA044C58C01B4), new AdvancedBlendEntry(AdvancedBlendOp.Lighten, AdvancedBlendOverlap.Disjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x58A19A0135D68B31, 0x82F35B97AED068E5), new AdvancedBlendEntry(AdvancedBlendOp.ColorDodge, AdvancedBlendOverlap.Disjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x20489F9AB36CC0E3, 0x20499874219E35EE), new AdvancedBlendEntry(AdvancedBlendOp.ColorBurn, AdvancedBlendOverlap.Disjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0xBB176935E5EE05BF, 0x95B26D4D30EA7A14), new AdvancedBlendEntry(AdvancedBlendOp.HardLight, AdvancedBlendOverlap.Disjoint, true, new[] { new RgbFloat(0.5f, 0.5f, 0.5f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x5FF9393C908ACFED, 0x068B0BD875773ABF), new AdvancedBlendEntry(AdvancedBlendOp.SoftLight, AdvancedBlendOverlap.Disjoint, true, new[] { new RgbFloat(0.2605f, 0.2605f, 0.2605f), new RgbFloat(-0.7817f, -0.7817f, -0.7817f), new RgbFloat(0.3022f, 0.3022f, 0.3022f), new RgbFloat(0.2192f, 0.2192f, 0.2192f), new RgbFloat(0.25f, 0.25f, 0.25f), new RgbFloat(16f, 16f, 16f), new RgbFloat(12f, 12f, 12f), new RgbFloat(3f, 3f, 3f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x03181F8711C9802C, 0x6B02C7C6B224FE7B), new AdvancedBlendEntry(AdvancedBlendOp.Difference, AdvancedBlendOverlap.Disjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x2EE2209021F6B977, 0xF3AFA1491B8B89FC), new AdvancedBlendEntry(AdvancedBlendOp.Exclusion, AdvancedBlendOverlap.Disjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0xD8BA4DD2EDE4DC9E, 0x01006114977CF715), new AdvancedBlendEntry(AdvancedBlendOp.Invert, AdvancedBlendOverlap.Disjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0xD156B99835A2D8ED, 0x2D0BEE9E135EA7A7), new AdvancedBlendEntry(AdvancedBlendOp.InvertRGB, AdvancedBlendOverlap.Disjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x20CE8C898ED4BE27, 0x1514900B6F5E8F66), new AdvancedBlendEntry(AdvancedBlendOp.LinearDodge, AdvancedBlendOverlap.Disjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0xCDE5F743820BA2D9, 0x917845FE2ECB083D), new AdvancedBlendEntry(AdvancedBlendOp.LinearBurn, AdvancedBlendOverlap.Disjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0xEB03DF4A0C1D14CD, 0xBAE2E831C6E8FFE4), new AdvancedBlendEntry(AdvancedBlendOp.VividLight, AdvancedBlendOverlap.Disjoint, true, new[] { new RgbFloat(0.5f, 0.5f, 0.5f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x1DC9E49AABC779AC, 0x4053A1441EB713D3), new AdvancedBlendEntry(AdvancedBlendOp.LinearLight, AdvancedBlendOverlap.Disjoint, true, new[] { new RgbFloat(2f, 2f, 2f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0xFBDEF776248F7B3E, 0xE05EEFD65AC47CB7), new AdvancedBlendEntry(AdvancedBlendOp.PinLight, AdvancedBlendOverlap.Disjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x415A1A48E03AA6E7, 0x046D7EE33CA46B9A), new AdvancedBlendEntry(AdvancedBlendOp.HardMix, AdvancedBlendOverlap.Disjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x59A6901EC9BB2041, 0x2F3E19CE5EEC3EBE), new AdvancedBlendEntry(AdvancedBlendOp.HslHue, AdvancedBlendOverlap.Disjoint, true, new[] { new RgbFloat(0.3f, 0.59f, 0.11f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x044B2B6E105221DA, 0x3089BBC033F994AF), new AdvancedBlendEntry(AdvancedBlendOp.HslSaturation, AdvancedBlendOverlap.Disjoint, true, new[] { new RgbFloat(0.3f, 0.59f, 0.11f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x374A5A24AA8E6CC5, 0x29930FAA6215FA2B), new AdvancedBlendEntry(AdvancedBlendOp.HslColor, AdvancedBlendOverlap.Disjoint, true, new[] { new RgbFloat(0.3f, 0.59f, 0.11f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x30CD0F7AF0CF26F9, 0x06CCA6744DE7DCF5), new AdvancedBlendEntry(AdvancedBlendOp.HslLuminosity, AdvancedBlendOverlap.Disjoint, true, new[] { new RgbFloat(0.3f, 0.59f, 0.11f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x1A6C9A1F6FE494A5, 0xA0CFAF77617E54DD), new AdvancedBlendEntry(AdvancedBlendOp.Src, AdvancedBlendOverlap.Conjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.ZeroGl)) },
|
||||
{ new Hash128(0x081AF6DAAB1C8717, 0xBFEDCE59AE3DC9AC), new AdvancedBlendEntry(AdvancedBlendOp.Dst, AdvancedBlendOverlap.Conjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x3518E44573AB68BA, 0xC96EE71AF9F8F546), new AdvancedBlendEntry(AdvancedBlendOp.SrcOver, AdvancedBlendOverlap.Conjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0xF89E81FE8D73C96F, 0x4583A04577A0F21C), new AdvancedBlendEntry(AdvancedBlendOp.DstOver, AdvancedBlendOverlap.Conjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0xDF4026421CB61119, 0x14115A1F5139AFC7), new AdvancedBlendEntry(AdvancedBlendOp.SrcIn, AdvancedBlendOverlap.Conjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MinimumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x91A20262C3E3A695, 0x0B3A102BFCDC6B1C), new AdvancedBlendEntry(AdvancedBlendOp.DstIn, AdvancedBlendOverlap.Conjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MinimumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x44F4C7CCFEB9EBFA, 0xF68394E6D56E5C2F), new AdvancedBlendEntry(AdvancedBlendOp.SrcOut, AdvancedBlendOverlap.Conjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0xB89F17C7021E9760, 0x430357EE0F7188EF), new AdvancedBlendEntry(AdvancedBlendOp.DstOut, AdvancedBlendOverlap.Conjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0xDA2D20EA4242B8A0, 0x0D1EC05B72E3838F), new AdvancedBlendEntry(AdvancedBlendOp.SrcAtop, AdvancedBlendOverlap.Conjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x855DFEE1208D11B9, 0x77C6E3DDCFE30B85), new AdvancedBlendEntry(AdvancedBlendOp.DstAtop, AdvancedBlendOverlap.Conjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.ZeroGl)) },
|
||||
{ new Hash128(0x9B3808439683FD58, 0x123DCBE4705AB25E), new AdvancedBlendEntry(AdvancedBlendOp.Xor, AdvancedBlendOverlap.Conjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0xA42CF045C248A00A, 0x0C6C63C24EA0B0C1), new AdvancedBlendEntry(AdvancedBlendOp.Multiply, AdvancedBlendOverlap.Conjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x320A83B6D00C8059, 0x796EDAB3EB7314BC), new AdvancedBlendEntry(AdvancedBlendOp.Screen, AdvancedBlendOverlap.Conjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x45253AC9ABFFC613, 0x8F92EA70195FB573), new AdvancedBlendEntry(AdvancedBlendOp.Overlay, AdvancedBlendOverlap.Conjoint, true, new[] { new RgbFloat(0.5f, 0.5f, 0.5f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x1A5D263B588274B6, 0x167D305F6C794179), new AdvancedBlendEntry(AdvancedBlendOp.Darken, AdvancedBlendOverlap.Conjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x709C1A837FE966AC, 0x75D8CE49E8A78EDB), new AdvancedBlendEntry(AdvancedBlendOp.Lighten, AdvancedBlendOverlap.Conjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x8265C26F85E4145F, 0x932E6CCBF37CB600), new AdvancedBlendEntry(AdvancedBlendOp.ColorDodge, AdvancedBlendOverlap.Conjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x3F252B3FEF983F27, 0x9370D7EEFEFA1A9E), new AdvancedBlendEntry(AdvancedBlendOp.ColorBurn, AdvancedBlendOverlap.Conjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x66A334A4AEA41078, 0xCB52254E1E395231), new AdvancedBlendEntry(AdvancedBlendOp.HardLight, AdvancedBlendOverlap.Conjoint, true, new[] { new RgbFloat(0.5f, 0.5f, 0.5f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0xFDD05C53B25F0035, 0xB7E3ECEE166C222F), new AdvancedBlendEntry(AdvancedBlendOp.SoftLight, AdvancedBlendOverlap.Conjoint, true, new[] { new RgbFloat(0.2605f, 0.2605f, 0.2605f), new RgbFloat(-0.7817f, -0.7817f, -0.7817f), new RgbFloat(0.3022f, 0.3022f, 0.3022f), new RgbFloat(0.2192f, 0.2192f, 0.2192f), new RgbFloat(0.25f, 0.25f, 0.25f), new RgbFloat(16f, 16f, 16f), new RgbFloat(12f, 12f, 12f), new RgbFloat(3f, 3f, 3f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x25D932A77FFED81A, 0xA50D797B0FCA94E8), new AdvancedBlendEntry(AdvancedBlendOp.Difference, AdvancedBlendOverlap.Conjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x4A953B6F5F7D341C, 0xDC05CFB50DDB5DC1), new AdvancedBlendEntry(AdvancedBlendOp.Exclusion, AdvancedBlendOverlap.Conjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x838CB660C4F41F6D, 0x9E7D958697543495), new AdvancedBlendEntry(AdvancedBlendOp.Invert, AdvancedBlendOverlap.Conjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x4DF6EC1348A8F797, 0xA128E0CD69DB5A64), new AdvancedBlendEntry(AdvancedBlendOp.InvertRGB, AdvancedBlendOverlap.Conjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x178CDFAB9A015295, 0x2BF40EA72E596D57), new AdvancedBlendEntry(AdvancedBlendOp.LinearDodge, AdvancedBlendOverlap.Conjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x338FC99050E56AFD, 0x2AF41CF82BE602BF), new AdvancedBlendEntry(AdvancedBlendOp.LinearBurn, AdvancedBlendOverlap.Conjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x62E02ED60D1E978E, 0xBF726B3E68C11E4D), new AdvancedBlendEntry(AdvancedBlendOp.VividLight, AdvancedBlendOverlap.Conjoint, true, new[] { new RgbFloat(0.5f, 0.5f, 0.5f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0xFBAF92DD4C101502, 0x7AF2EDA6596B819D), new AdvancedBlendEntry(AdvancedBlendOp.LinearLight, AdvancedBlendOverlap.Conjoint, true, new[] { new RgbFloat(2f, 2f, 2f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x0EF1241F65D4B50A, 0xE8D85DFA6AEDDB84), new AdvancedBlendEntry(AdvancedBlendOp.PinLight, AdvancedBlendOverlap.Conjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x77FE024B5C9D4A18, 0xF19D48A932F6860F), new AdvancedBlendEntry(AdvancedBlendOp.HardMix, AdvancedBlendOverlap.Conjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x9C88CBFA2E09D857, 0x0A0361704CBEEE1D), new AdvancedBlendEntry(AdvancedBlendOp.HslHue, AdvancedBlendOverlap.Conjoint, true, new[] { new RgbFloat(0.3f, 0.59f, 0.11f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x5B94127FA190E640, 0x8D1FEFF837A91268), new AdvancedBlendEntry(AdvancedBlendOp.HslSaturation, AdvancedBlendOverlap.Conjoint, true, new[] { new RgbFloat(0.3f, 0.59f, 0.11f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0xB9C9105B7E063DDB, 0xF6A70E1D511B96FD), new AdvancedBlendEntry(AdvancedBlendOp.HslColor, AdvancedBlendOverlap.Conjoint, true, new[] { new RgbFloat(0.3f, 0.59f, 0.11f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0xF0751AAE332B3ED1, 0xC40146F5C83C2533), new AdvancedBlendEntry(AdvancedBlendOp.HslLuminosity, AdvancedBlendOverlap.Conjoint, true, new[] { new RgbFloat(0.3f, 0.59f, 0.11f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x579EB12F595F75AD, 0x151BF0504703B81B), new AdvancedBlendEntry(AdvancedBlendOp.DstOver, AdvancedBlendOverlap.Uncorrelated, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0xF9CA152C03AC8C62, 0x1581336205E5CF47), new AdvancedBlendEntry(AdvancedBlendOp.SrcIn, AdvancedBlendOverlap.Uncorrelated, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.DstAlphaGl, BlendFactor.ZeroGl)) },
|
||||
{ new Hash128(0x98ACD8BB5E195D0F, 0x91F937672BE899F0), new AdvancedBlendEntry(AdvancedBlendOp.SrcOut, AdvancedBlendOverlap.Uncorrelated, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneMinusDstAlphaGl, BlendFactor.ZeroGl)) },
|
||||
{ new Hash128(0xBF97F10FC301F44C, 0x75721789F0D48548), new AdvancedBlendEntry(AdvancedBlendOp.SrcAtop, AdvancedBlendOverlap.Uncorrelated, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x1B982263B8B08A10, 0x3350C76E2E1B27DF), new AdvancedBlendEntry(AdvancedBlendOp.DstAtop, AdvancedBlendOverlap.Uncorrelated, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.ZeroGl)) },
|
||||
{ new Hash128(0xFF20AC79F64EDED8, 0xAF9025B2D97B9273), new AdvancedBlendEntry(AdvancedBlendOp.Xor, AdvancedBlendOverlap.Uncorrelated, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneMinusDstAlphaGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0x9FFD986600FB112F, 0x384FDDF4E060139A), new AdvancedBlendEntry(AdvancedBlendOp.PlusClamped, AdvancedBlendOverlap.Uncorrelated, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x0425E40B5B8B3B52, 0x5880CBED7CAB631C), new AdvancedBlendEntry(AdvancedBlendOp.PlusClampedAlpha, AdvancedBlendOverlap.Uncorrelated, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x16DAC8593F28623A, 0x233DBC82325B8AED), new AdvancedBlendEntry(AdvancedBlendOp.PlusDarker, AdvancedBlendOverlap.Uncorrelated, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0xB37E5F234B9F0948, 0xD5F957A2ECD98FD6), new AdvancedBlendEntry(AdvancedBlendOp.Multiply, AdvancedBlendOverlap.Uncorrelated, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0xCA0FDADD1D20DBE3, 0x1A5C15CCBF1AC538), new AdvancedBlendEntry(AdvancedBlendOp.Screen, AdvancedBlendOverlap.Uncorrelated, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0x1C48304D73A9DF3A, 0x891DB93FA36E3450), new AdvancedBlendEntry(AdvancedBlendOp.Overlay, AdvancedBlendOverlap.Uncorrelated, false, new[] { new RgbFloat(0.5f, 0.5f, 0.5f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0x53200F2279B7FA39, 0x051C2462EBF6789C), new AdvancedBlendEntry(AdvancedBlendOp.Darken, AdvancedBlendOverlap.Uncorrelated, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0xB88BFB80714DCD5C, 0xEBD6938D744E6A41), new AdvancedBlendEntry(AdvancedBlendOp.Lighten, AdvancedBlendOverlap.Uncorrelated, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0xE33DC2A25FC1A976, 0x08B3DBB1F3027D45), new AdvancedBlendEntry(AdvancedBlendOp.ColorDodge, AdvancedBlendOverlap.Uncorrelated, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0xCE97E71615370316, 0xE131AE49D3A4D62B), new AdvancedBlendEntry(AdvancedBlendOp.ColorBurn, AdvancedBlendOverlap.Uncorrelated, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0xE059FD265149B256, 0x94AF817AC348F61F), new AdvancedBlendEntry(AdvancedBlendOp.HardLight, AdvancedBlendOverlap.Uncorrelated, false, new[] { new RgbFloat(0.5f, 0.5f, 0.5f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0x16D31333D477E231, 0x9A98AAC84F72CC62), new AdvancedBlendEntry(AdvancedBlendOp.SoftLight, AdvancedBlendOverlap.Uncorrelated, false, new[] { new RgbFloat(0.2605f, 0.2605f, 0.2605f), new RgbFloat(-0.7817f, -0.7817f, -0.7817f), new RgbFloat(0.3022f, 0.3022f, 0.3022f), new RgbFloat(0.2192f, 0.2192f, 0.2192f), new RgbFloat(0.25f, 0.25f, 0.25f), new RgbFloat(16f, 16f, 16f), new RgbFloat(12f, 12f, 12f), new RgbFloat(3f, 3f, 3f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0x47FC3B0776366D3C, 0xE96D9BD83B277874), new AdvancedBlendEntry(AdvancedBlendOp.Difference, AdvancedBlendOverlap.Uncorrelated, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0x7230401E3FEA1F3B, 0xF0D15F05D3D1E309), new AdvancedBlendEntry(AdvancedBlendOp.Minus, AdvancedBlendOverlap.Uncorrelated, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.ReverseSubtractGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x188212F9303742F5, 0x100C51CB96E03591), new AdvancedBlendEntry(AdvancedBlendOp.MinusClamped, AdvancedBlendOverlap.Uncorrelated, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x52B755D296B44DC5, 0x4003B87275625973), new AdvancedBlendEntry(AdvancedBlendOp.Exclusion, AdvancedBlendOverlap.Uncorrelated, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0xD873ED973ADF7EAD, 0x73E68B57D92034E7), new AdvancedBlendEntry(AdvancedBlendOp.Contrast, AdvancedBlendOverlap.Uncorrelated, false, new[] { new RgbFloat(2f, 2f, 2f), new RgbFloat(0.5f, 0.5f, 0.5f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x471F9FA34B945ACB, 0x10524D1410B3C402), new AdvancedBlendEntry(AdvancedBlendOp.InvertRGB, AdvancedBlendOverlap.Uncorrelated, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x99F569454EA0EF32, 0x6FC70A8B3A07DC8B), new AdvancedBlendEntry(AdvancedBlendOp.LinearDodge, AdvancedBlendOverlap.Uncorrelated, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0x5AD55F950067AC7E, 0x4BA60A4FBABDD0AC), new AdvancedBlendEntry(AdvancedBlendOp.LinearBurn, AdvancedBlendOverlap.Uncorrelated, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0x03FF2C858C9C4C5B, 0xE95AE7F561FB60E9), new AdvancedBlendEntry(AdvancedBlendOp.VividLight, AdvancedBlendOverlap.Uncorrelated, false, new[] { new RgbFloat(0.5f, 0.5f, 0.5f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0x6DC0E510C7BCF9D2, 0xAE805D7CECDCB5C1), new AdvancedBlendEntry(AdvancedBlendOp.LinearLight, AdvancedBlendOverlap.Uncorrelated, false, new[] { new RgbFloat(2f, 2f, 2f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0x44832332CED5C054, 0x2F8D5536C085B30A), new AdvancedBlendEntry(AdvancedBlendOp.PinLight, AdvancedBlendOverlap.Uncorrelated, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0x4AB4D387618AC51F, 0x495B46E0555F4B32), new AdvancedBlendEntry(AdvancedBlendOp.HardMix, AdvancedBlendOverlap.Uncorrelated, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0x99282B49405A01A8, 0xD6FA93F864F24A8E), new AdvancedBlendEntry(AdvancedBlendOp.Red, AdvancedBlendOverlap.Uncorrelated, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x37B30C1064FBD23E, 0x5D068366F42317C2), new AdvancedBlendEntry(AdvancedBlendOp.Green, AdvancedBlendOverlap.Uncorrelated, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x760FAE9D59E04BC2, 0xA40AD483EA01435E), new AdvancedBlendEntry(AdvancedBlendOp.Blue, AdvancedBlendOverlap.Uncorrelated, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0xE786950FD9D1C6EF, 0xF9FDD5AF6451D239), new AdvancedBlendEntry(AdvancedBlendOp.HslHue, AdvancedBlendOverlap.Uncorrelated, false, new[] { new RgbFloat(0.3f, 0.59f, 0.11f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0x052458BB4788B0CA, 0x8AC58FDCA1F45EF5), new AdvancedBlendEntry(AdvancedBlendOp.HslSaturation, AdvancedBlendOverlap.Uncorrelated, false, new[] { new RgbFloat(0.3f, 0.59f, 0.11f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0x6AFC3837D1D31920, 0xB9D49C2FE49642C6), new AdvancedBlendEntry(AdvancedBlendOp.HslColor, AdvancedBlendOverlap.Uncorrelated, false, new[] { new RgbFloat(0.3f, 0.59f, 0.11f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0xAFC2911949317E01, 0xD5B63636F5CB3422), new AdvancedBlendEntry(AdvancedBlendOp.HslLuminosity, AdvancedBlendOverlap.Uncorrelated, false, new[] { new RgbFloat(0.3f, 0.59f, 0.11f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0x13B46DF507CC2C53, 0x86DE26517E6BF0A7), new AdvancedBlendEntry(AdvancedBlendOp.Src, AdvancedBlendOverlap.Disjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.ZeroGl)) },
|
||||
{ new Hash128(0x5C372442474BE410, 0x79ECD3C0C496EF2E), new AdvancedBlendEntry(AdvancedBlendOp.SrcOver, AdvancedBlendOverlap.Disjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x74AAB45DBF5336E9, 0x01BFC4E181DAD442), new AdvancedBlendEntry(AdvancedBlendOp.DstOver, AdvancedBlendOverlap.Disjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x43239E282A36C85C, 0x36FB65560E46AD0F), new AdvancedBlendEntry(AdvancedBlendOp.SrcIn, AdvancedBlendOverlap.Disjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x1A3BA8A7583B8F7A, 0xE64E41D548033180), new AdvancedBlendEntry(AdvancedBlendOp.SrcOut, AdvancedBlendOverlap.Disjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x32BBB9859E9B565D, 0x3D5CE94FE55F18B5), new AdvancedBlendEntry(AdvancedBlendOp.SrcAtop, AdvancedBlendOverlap.Disjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0xD947A0766AE3C0FC, 0x391E5D53E86F4ED6), new AdvancedBlendEntry(AdvancedBlendOp.DstAtop, AdvancedBlendOverlap.Disjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.ZeroGl)) },
|
||||
{ new Hash128(0xBD9A7C08BDFD8CE6, 0x905407634901355E), new AdvancedBlendEntry(AdvancedBlendOp.Xor, AdvancedBlendOverlap.Disjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x8395475BCB0D7A8C, 0x48AF5DD501D44A70), new AdvancedBlendEntry(AdvancedBlendOp.Plus, AdvancedBlendOverlap.Disjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x80AAC23FEBD4A3E5, 0xEA8C70F0B4DE52DE), new AdvancedBlendEntry(AdvancedBlendOp.Multiply, AdvancedBlendOverlap.Disjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x2F3AD1B0F1B3FD09, 0xC0EBC784BFAB8EA3), new AdvancedBlendEntry(AdvancedBlendOp.Screen, AdvancedBlendOverlap.Disjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x52B54032F2F70BFF, 0xC941D6FDED674765), new AdvancedBlendEntry(AdvancedBlendOp.Overlay, AdvancedBlendOverlap.Disjoint, false, new[] { new RgbFloat(0.5f, 0.5f, 0.5f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0xCA7B86F72EC6A99B, 0x55868A131AFE359E), new AdvancedBlendEntry(AdvancedBlendOp.Darken, AdvancedBlendOverlap.Disjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x377919B60BD133CA, 0x0FD611627664EF40), new AdvancedBlendEntry(AdvancedBlendOp.Lighten, AdvancedBlendOverlap.Disjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x9D4A0C5EE1153887, 0x7B869EBA218C589B), new AdvancedBlendEntry(AdvancedBlendOp.ColorDodge, AdvancedBlendOverlap.Disjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x311F2A858545D123, 0xB4D09C802480AD62), new AdvancedBlendEntry(AdvancedBlendOp.ColorBurn, AdvancedBlendOverlap.Disjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0xCF78AA6A83AFA689, 0x9DC48B0C2182A3E1), new AdvancedBlendEntry(AdvancedBlendOp.HardLight, AdvancedBlendOverlap.Disjoint, false, new[] { new RgbFloat(0.5f, 0.5f, 0.5f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0xC3018CD6F1CF62D1, 0x016E32DD9087B1BB), new AdvancedBlendEntry(AdvancedBlendOp.SoftLight, AdvancedBlendOverlap.Disjoint, false, new[] { new RgbFloat(0.2605f, 0.2605f, 0.2605f), new RgbFloat(-0.7817f, -0.7817f, -0.7817f), new RgbFloat(0.3022f, 0.3022f, 0.3022f), new RgbFloat(0.2192f, 0.2192f, 0.2192f), new RgbFloat(0.25f, 0.25f, 0.25f), new RgbFloat(16f, 16f, 16f), new RgbFloat(12f, 12f, 12f), new RgbFloat(3f, 3f, 3f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x9CB62CE0E956EE29, 0x0FB67F503E60B3AD), new AdvancedBlendEntry(AdvancedBlendOp.Difference, AdvancedBlendOverlap.Disjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x3589A13C16EF3BFA, 0x15B29BFC91F3BDFB), new AdvancedBlendEntry(AdvancedBlendOp.Exclusion, AdvancedBlendOverlap.Disjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x3502CA5FB7529917, 0xFA51BFD0D1688071), new AdvancedBlendEntry(AdvancedBlendOp.InvertRGB, AdvancedBlendOverlap.Disjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x62ADC25AD6D0A923, 0x76CB6D238276D3A3), new AdvancedBlendEntry(AdvancedBlendOp.LinearDodge, AdvancedBlendOverlap.Disjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x09FDEB1116A9D52C, 0x85BB8627CD5C2733), new AdvancedBlendEntry(AdvancedBlendOp.LinearBurn, AdvancedBlendOverlap.Disjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x0709FED1B65E18EB, 0x5BC3AA4D99EC19CF), new AdvancedBlendEntry(AdvancedBlendOp.VividLight, AdvancedBlendOverlap.Disjoint, false, new[] { new RgbFloat(0.5f, 0.5f, 0.5f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0xB18D28AE5DE4C723, 0xE820AA2B75C9C02E), new AdvancedBlendEntry(AdvancedBlendOp.LinearLight, AdvancedBlendOverlap.Disjoint, false, new[] { new RgbFloat(2f, 2f, 2f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x6743C51621497480, 0x4B164E40858834AE), new AdvancedBlendEntry(AdvancedBlendOp.PinLight, AdvancedBlendOverlap.Disjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x63D1E181E34A2944, 0x1AE292C9D9F12819), new AdvancedBlendEntry(AdvancedBlendOp.HardMix, AdvancedBlendOverlap.Disjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x079523298250BFF6, 0xC0C793510603CDB5), new AdvancedBlendEntry(AdvancedBlendOp.HslHue, AdvancedBlendOverlap.Disjoint, false, new[] { new RgbFloat(0.3f, 0.59f, 0.11f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x4C9D0A973C805EA6, 0xD1FF59AD5156B93C), new AdvancedBlendEntry(AdvancedBlendOp.HslSaturation, AdvancedBlendOverlap.Disjoint, false, new[] { new RgbFloat(0.3f, 0.59f, 0.11f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x1E914678F3057BCD, 0xD503AE389C12D229), new AdvancedBlendEntry(AdvancedBlendOp.HslColor, AdvancedBlendOverlap.Disjoint, false, new[] { new RgbFloat(0.3f, 0.59f, 0.11f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x9FDBADE5556C5311, 0x03F0CBC798FC5C94), new AdvancedBlendEntry(AdvancedBlendOp.HslLuminosity, AdvancedBlendOverlap.Disjoint, false, new[] { new RgbFloat(0.3f, 0.59f, 0.11f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0xE39451534635403C, 0x606CC1CA1F452388), new AdvancedBlendEntry(AdvancedBlendOp.Src, AdvancedBlendOverlap.Conjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.ZeroGl)) },
|
||||
{ new Hash128(0x1D39F0F0A1008AA6, 0xBFDF2B97E6C3F125), new AdvancedBlendEntry(AdvancedBlendOp.SrcOver, AdvancedBlendOverlap.Conjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0xDB81BED30D5BDBEA, 0xAF0B2856EB93AD2C), new AdvancedBlendEntry(AdvancedBlendOp.DstOver, AdvancedBlendOverlap.Conjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x83F69CCF1D0A79B6, 0x70D31332797430AC), new AdvancedBlendEntry(AdvancedBlendOp.SrcIn, AdvancedBlendOverlap.Conjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MinimumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x7B87F807AB7A8F5C, 0x1241A2A01FB31771), new AdvancedBlendEntry(AdvancedBlendOp.SrcOut, AdvancedBlendOverlap.Conjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0xF557172E20D5272D, 0xC1961F8C7A5D2820), new AdvancedBlendEntry(AdvancedBlendOp.SrcAtop, AdvancedBlendOverlap.Conjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0xA8476B3944DBBC9B, 0x84A2F6AF97B15FDF), new AdvancedBlendEntry(AdvancedBlendOp.DstAtop, AdvancedBlendOverlap.Conjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.ZeroGl)) },
|
||||
{ new Hash128(0x3259602B55414DA3, 0x72AACCC00B5A9D10), new AdvancedBlendEntry(AdvancedBlendOp.Xor, AdvancedBlendOverlap.Conjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0xC0CB8C10F36EDCD6, 0x8C2D088AD8191E1C), new AdvancedBlendEntry(AdvancedBlendOp.Multiply, AdvancedBlendOverlap.Conjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x81806C451C6255EF, 0x5AA8AC9A08941A15), new AdvancedBlendEntry(AdvancedBlendOp.Screen, AdvancedBlendOverlap.Conjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0xE55A6537F4568198, 0xCA8735390B799B19), new AdvancedBlendEntry(AdvancedBlendOp.Overlay, AdvancedBlendOverlap.Conjoint, false, new[] { new RgbFloat(0.5f, 0.5f, 0.5f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x5C044BA14536DDA3, 0xBCE0123ED7D510EC), new AdvancedBlendEntry(AdvancedBlendOp.Darken, AdvancedBlendOverlap.Conjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x6788346C405BE130, 0x372A4BB199C01F9F), new AdvancedBlendEntry(AdvancedBlendOp.Lighten, AdvancedBlendOverlap.Conjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x510EDC2A34E2856B, 0xE1727A407E294254), new AdvancedBlendEntry(AdvancedBlendOp.ColorDodge, AdvancedBlendOverlap.Conjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x4B7BE01BD398C7A8, 0x5BFF79BC00672C18), new AdvancedBlendEntry(AdvancedBlendOp.ColorBurn, AdvancedBlendOverlap.Conjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x213B43845540CFEC, 0xDA857411CF1CCFCE), new AdvancedBlendEntry(AdvancedBlendOp.HardLight, AdvancedBlendOverlap.Conjoint, false, new[] { new RgbFloat(0.5f, 0.5f, 0.5f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x765AFA6732E783F1, 0x8F1CABF1BC78A014), new AdvancedBlendEntry(AdvancedBlendOp.SoftLight, AdvancedBlendOverlap.Conjoint, false, new[] { new RgbFloat(0.2605f, 0.2605f, 0.2605f), new RgbFloat(-0.7817f, -0.7817f, -0.7817f), new RgbFloat(0.3022f, 0.3022f, 0.3022f), new RgbFloat(0.2192f, 0.2192f, 0.2192f), new RgbFloat(0.25f, 0.25f, 0.25f), new RgbFloat(16f, 16f, 16f), new RgbFloat(12f, 12f, 12f), new RgbFloat(3f, 3f, 3f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0xA4A5DE1CC06F6CB1, 0xA0634A0011001709), new AdvancedBlendEntry(AdvancedBlendOp.Difference, AdvancedBlendOverlap.Conjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x81F32BD8816EA796, 0x697EE86683165170), new AdvancedBlendEntry(AdvancedBlendOp.Exclusion, AdvancedBlendOverlap.Conjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0xB870C209EAA5F092, 0xAF5FD923909CAA1F), new AdvancedBlendEntry(AdvancedBlendOp.InvertRGB, AdvancedBlendOverlap.Conjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x3649A9F5C936FB83, 0xDD7C834897AA182A), new AdvancedBlendEntry(AdvancedBlendOp.LinearDodge, AdvancedBlendOverlap.Conjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0xD72A2B1097A5995C, 0x3D41B2763A913654), new AdvancedBlendEntry(AdvancedBlendOp.LinearBurn, AdvancedBlendOverlap.Conjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x551E212B9F6C454A, 0xB0DFA05BEB3C37FA), new AdvancedBlendEntry(AdvancedBlendOp.VividLight, AdvancedBlendOverlap.Conjoint, false, new[] { new RgbFloat(0.5f, 0.5f, 0.5f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x681B5A313B7416BF, 0xCB1CBAEEB4D81500), new AdvancedBlendEntry(AdvancedBlendOp.LinearLight, AdvancedBlendOverlap.Conjoint, false, new[] { new RgbFloat(2f, 2f, 2f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x9343A18BD4B16777, 0xEDB4AC1C8972C3A4), new AdvancedBlendEntry(AdvancedBlendOp.PinLight, AdvancedBlendOverlap.Conjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0xC960BF6D8519DE28, 0x78D8557FD405D119), new AdvancedBlendEntry(AdvancedBlendOp.HardMix, AdvancedBlendOverlap.Conjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x65A7B01FDC73A46C, 0x297E096ED5CC4D8A), new AdvancedBlendEntry(AdvancedBlendOp.HslHue, AdvancedBlendOverlap.Conjoint, false, new[] { new RgbFloat(0.3f, 0.59f, 0.11f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0xD9C99BA4A6CDC13B, 0x3CFF0ACEDC2EE150), new AdvancedBlendEntry(AdvancedBlendOp.HslSaturation, AdvancedBlendOverlap.Conjoint, false, new[] { new RgbFloat(0.3f, 0.59f, 0.11f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x6BC00DA6EB922BD1, 0x5FD4C11F2A685234), new AdvancedBlendEntry(AdvancedBlendOp.HslColor, AdvancedBlendOverlap.Conjoint, false, new[] { new RgbFloat(0.3f, 0.59f, 0.11f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x8652300E32D93050, 0x9460E7B449132371), new AdvancedBlendEntry(AdvancedBlendOp.HslLuminosity, AdvancedBlendOverlap.Conjoint, false, new[] { new RgbFloat(0.3f, 0.59f, 0.11f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
};
|
||||
}
|
||||
}
|
126
Ryujinx.Graphics.Gpu/Engine/Threed/Blender/AdvancedBlendUcode.cs
Normal file
126
Ryujinx.Graphics.Gpu/Engine/Threed/Blender/AdvancedBlendUcode.cs
Normal file
@ -0,0 +1,126 @@
|
||||
using Ryujinx.Graphics.GAL;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Engine.Threed.Blender
|
||||
{
|
||||
/// <summary>
|
||||
/// Fixed function alpha state used for a advanced blend function.
|
||||
/// </summary>
|
||||
struct FixedFunctionAlpha
|
||||
{
|
||||
/// <summary>
|
||||
/// Fixed function alpha state with alpha blending disabled.
|
||||
/// </summary>
|
||||
public static FixedFunctionAlpha Disabled => new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, default, default, default);
|
||||
|
||||
/// <summary>
|
||||
/// Individual enable bits for the RGB and alpha components.
|
||||
/// </summary>
|
||||
public BlendUcodeEnable Enable { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Alpha blend operation.
|
||||
/// </summary>
|
||||
public BlendOp AlphaOp { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Value multiplied with the blend source operand.
|
||||
/// </summary>
|
||||
public BlendFactor AlphaSrcFactor { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Value multiplied with the blend destination operand.
|
||||
/// </summary>
|
||||
public BlendFactor AlphaDstFactor { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new blend fixed function alpha state.
|
||||
/// </summary>
|
||||
/// <param name="enable">Individual enable bits for the RGB and alpha components</param>
|
||||
/// <param name="alphaOp">Alpha blend operation</param>
|
||||
/// <param name="alphaSrc">Value multiplied with the blend source operand</param>
|
||||
/// <param name="alphaDst">Value multiplied with the blend destination operand</param>
|
||||
public FixedFunctionAlpha(BlendUcodeEnable enable, BlendOp alphaOp, BlendFactor alphaSrc, BlendFactor alphaDst)
|
||||
{
|
||||
Enable = enable;
|
||||
AlphaOp = alphaOp;
|
||||
AlphaSrcFactor = alphaSrc;
|
||||
AlphaDstFactor = alphaDst;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new blend fixed function alpha state.
|
||||
/// </summary>
|
||||
/// <param name="alphaOp">Alpha blend operation</param>
|
||||
/// <param name="alphaSrc">Value multiplied with the blend source operand</param>
|
||||
/// <param name="alphaDst">Value multiplied with the blend destination operand</param>
|
||||
public FixedFunctionAlpha(BlendOp alphaOp, BlendFactor alphaSrc, BlendFactor alphaDst) : this(BlendUcodeEnable.EnableRGB, alphaOp, alphaSrc, alphaDst)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Blend microcode assembly function delegate.
|
||||
/// </summary>
|
||||
/// <param name="asm">Assembler</param>
|
||||
/// <returns>Fixed function alpha state for the microcode</returns>
|
||||
delegate FixedFunctionAlpha GenUcodeFunc(ref UcodeAssembler asm);
|
||||
|
||||
/// <summary>
|
||||
/// Advanced blend microcode state.
|
||||
/// </summary>
|
||||
struct AdvancedBlendUcode
|
||||
{
|
||||
/// <summary>
|
||||
/// Advanced blend operation.
|
||||
/// </summary>
|
||||
public AdvancedBlendOp Op { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Advanced blend overlap mode.
|
||||
/// </summary>
|
||||
public AdvancedBlendOverlap Overlap { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Whenever the source input is pre-multiplied.
|
||||
/// </summary>
|
||||
public bool SrcPreMultiplied { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Fixed function alpha state.
|
||||
/// </summary>
|
||||
public FixedFunctionAlpha Alpha { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Microcode.
|
||||
/// </summary>
|
||||
public uint[] Code { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Constants used by the microcode.
|
||||
/// </summary>
|
||||
public RgbFloat[] Constants { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new advanced blend state.
|
||||
/// </summary>
|
||||
/// <param name="op">Advanced blend operation</param>
|
||||
/// <param name="overlap">Advanced blend overlap mode</param>
|
||||
/// <param name="srcPreMultiplied">Whenever the source input is pre-multiplied</param>
|
||||
/// <param name="genFunc">Function that will generate the advanced blend microcode</param>
|
||||
public AdvancedBlendUcode(
|
||||
AdvancedBlendOp op,
|
||||
AdvancedBlendOverlap overlap,
|
||||
bool srcPreMultiplied,
|
||||
GenUcodeFunc genFunc)
|
||||
{
|
||||
Op = op;
|
||||
Overlap = overlap;
|
||||
SrcPreMultiplied = srcPreMultiplied;
|
||||
|
||||
UcodeAssembler asm = new UcodeAssembler();
|
||||
Alpha = genFunc(ref asm);
|
||||
Code = asm.GetCode();
|
||||
Constants = asm.GetConstants();
|
||||
}
|
||||
}
|
||||
}
|
305
Ryujinx.Graphics.Gpu/Engine/Threed/Blender/UcodeAssembler.cs
Normal file
305
Ryujinx.Graphics.Gpu/Engine/Threed/Blender/UcodeAssembler.cs
Normal file
@ -0,0 +1,305 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Engine.Threed.Blender
|
||||
{
|
||||
/// <summary>
|
||||
/// Blend microcode instruction.
|
||||
/// </summary>
|
||||
enum Instruction
|
||||
{
|
||||
Mmadd = 0,
|
||||
Mmsub = 1,
|
||||
Min = 2,
|
||||
Max = 3,
|
||||
Rcp = 4,
|
||||
Add = 5,
|
||||
Sub = 6
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Blend microcode condition code.
|
||||
/// </summary>
|
||||
enum CC
|
||||
{
|
||||
F = 0,
|
||||
T = 1,
|
||||
EQ = 2,
|
||||
NE = 3,
|
||||
LT = 4,
|
||||
LE = 5,
|
||||
GT = 6,
|
||||
GE = 7
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Blend microcode opend B or D value.
|
||||
/// </summary>
|
||||
enum OpBD
|
||||
{
|
||||
ConstantZero = 0x0,
|
||||
ConstantOne = 0x1,
|
||||
SrcRGB = 0x2,
|
||||
SrcAAA = 0x3,
|
||||
OneMinusSrcAAA = 0x4,
|
||||
DstRGB = 0x5,
|
||||
DstAAA = 0x6,
|
||||
OneMinusDstAAA = 0x7,
|
||||
Temp0 = 0x9,
|
||||
Temp1 = 0xa,
|
||||
Temp2 = 0xb,
|
||||
PBR = 0xc,
|
||||
ConstantRGB = 0xd
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Blend microcode operand A or C value.
|
||||
/// </summary>
|
||||
enum OpAC
|
||||
{
|
||||
SrcRGB = 0,
|
||||
DstRGB = 1,
|
||||
SrcAAA = 2,
|
||||
DstAAA = 3,
|
||||
Temp0 = 4,
|
||||
Temp1 = 5,
|
||||
Temp2 = 6,
|
||||
PBR = 7
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Blend microcode destination operand.
|
||||
/// </summary>
|
||||
enum OpDst
|
||||
{
|
||||
Temp0 = 0,
|
||||
Temp1 = 1,
|
||||
Temp2 = 2,
|
||||
PBR = 3
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Blend microcode input swizzle.
|
||||
/// </summary>
|
||||
enum Swizzle
|
||||
{
|
||||
RGB = 0,
|
||||
GBR = 1,
|
||||
RRR = 2,
|
||||
GGG = 3,
|
||||
BBB = 4,
|
||||
RToA = 5
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Blend microcode output components.
|
||||
/// </summary>
|
||||
enum WriteMask
|
||||
{
|
||||
RGB = 0,
|
||||
R = 1,
|
||||
G = 2,
|
||||
B = 3
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Floating-point RGB color values.
|
||||
/// </summary>
|
||||
struct RgbFloat
|
||||
{
|
||||
/// <summary>
|
||||
/// Red component value.
|
||||
/// </summary>
|
||||
public float R { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Green component value.
|
||||
/// </summary>
|
||||
public float G { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Blue component value.
|
||||
/// </summary>
|
||||
public float B { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new floating-point RGB value.
|
||||
/// </summary>
|
||||
/// <param name="r">Red component value</param>
|
||||
/// <param name="g">Green component value</param>
|
||||
/// <param name="b">Blue component value</param>
|
||||
public RgbFloat(float r, float g, float b)
|
||||
{
|
||||
R = r;
|
||||
G = g;
|
||||
B = b;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Blend microcode destination operand, including swizzle, write mask and condition code update flag.
|
||||
/// </summary>
|
||||
struct Dest
|
||||
{
|
||||
public static Dest Temp0 => new Dest(OpDst.Temp0, Swizzle.RGB, WriteMask.RGB, false);
|
||||
public static Dest Temp1 => new Dest(OpDst.Temp1, Swizzle.RGB, WriteMask.RGB, false);
|
||||
public static Dest Temp2 => new Dest(OpDst.Temp2, Swizzle.RGB, WriteMask.RGB, false);
|
||||
public static Dest PBR => new Dest(OpDst.PBR, Swizzle.RGB, WriteMask.RGB, false);
|
||||
|
||||
public Dest GBR => new Dest(Dst, Swizzle.GBR, WriteMask, WriteCC);
|
||||
public Dest RRR => new Dest(Dst, Swizzle.RRR, WriteMask, WriteCC);
|
||||
public Dest GGG => new Dest(Dst, Swizzle.GGG, WriteMask, WriteCC);
|
||||
public Dest BBB => new Dest(Dst, Swizzle.BBB, WriteMask, WriteCC);
|
||||
public Dest RToA => new Dest(Dst, Swizzle.RToA, WriteMask, WriteCC);
|
||||
|
||||
public Dest R => new Dest(Dst, Swizzle, WriteMask.R, WriteCC);
|
||||
public Dest G => new Dest(Dst, Swizzle, WriteMask.G, WriteCC);
|
||||
public Dest B => new Dest(Dst, Swizzle, WriteMask.B, WriteCC);
|
||||
|
||||
public Dest CC => new Dest(Dst, Swizzle, WriteMask, true);
|
||||
|
||||
public OpDst Dst { get; }
|
||||
public Swizzle Swizzle { get; }
|
||||
public WriteMask WriteMask { get; }
|
||||
public bool WriteCC { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new blend microcode destination operand.
|
||||
/// </summary>
|
||||
/// <param name="dst">Operand</param>
|
||||
/// <param name="swizzle">Swizzle</param>
|
||||
/// <param name="writeMask">Write maks</param>
|
||||
/// <param name="writeCC">Indicates if condition codes should be updated</param>
|
||||
public Dest(OpDst dst, Swizzle swizzle, WriteMask writeMask, bool writeCC)
|
||||
{
|
||||
Dst = dst;
|
||||
Swizzle = swizzle;
|
||||
WriteMask = writeMask;
|
||||
WriteCC = writeCC;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Blend microcode operaiton.
|
||||
/// </summary>
|
||||
struct UcodeOp
|
||||
{
|
||||
public readonly uint Word;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new blend microcode operation.
|
||||
/// </summary>
|
||||
/// <param name="cc">Condition code that controls whenever the operation is executed or not</param>
|
||||
/// <param name="inst">Instruction</param>
|
||||
/// <param name="constIndex">Index on the constant table of the constant used by any constant operand</param>
|
||||
/// <param name="dest">Destination operand</param>
|
||||
/// <param name="srcA">First input operand</param>
|
||||
/// <param name="srcB">Second input operand</param>
|
||||
/// <param name="srcC">Third input operand</param>
|
||||
/// <param name="srcD">Fourth input operand</param>
|
||||
public UcodeOp(CC cc, Instruction inst, int constIndex, Dest dest, OpAC srcA, OpBD srcB, OpAC srcC, OpBD srcD)
|
||||
{
|
||||
Word = (uint)cc |
|
||||
((uint)inst << 3) |
|
||||
((uint)constIndex << 6) |
|
||||
((uint)srcA << 9) |
|
||||
((uint)srcB << 12) |
|
||||
((uint)srcC << 16) |
|
||||
((uint)srcD << 19) |
|
||||
((uint)dest.Swizzle << 23) |
|
||||
((uint)dest.WriteMask << 26) |
|
||||
((uint)dest.Dst << 28) |
|
||||
(dest.WriteCC ? (1u << 31) : 0);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Blend microcode assembler.
|
||||
/// </summary>
|
||||
struct UcodeAssembler
|
||||
{
|
||||
private List<uint> _code;
|
||||
private RgbFloat[] _constants;
|
||||
private int _constantIndex;
|
||||
|
||||
public void Mul(CC cc, Dest dest, OpAC srcA, OpBD srcB)
|
||||
{
|
||||
Assemble(cc, Instruction.Mmadd, dest, srcA, srcB, OpAC.SrcRGB, OpBD.ConstantZero);
|
||||
}
|
||||
|
||||
public void Madd(CC cc, Dest dest, OpAC srcA, OpBD srcB, OpAC srcC)
|
||||
{
|
||||
Assemble(cc, Instruction.Mmadd, dest, srcA, srcB, srcC, OpBD.ConstantOne);
|
||||
}
|
||||
|
||||
public void Mmadd(CC cc, Dest dest, OpAC srcA, OpBD srcB, OpAC srcC, OpBD srcD)
|
||||
{
|
||||
Assemble(cc, Instruction.Mmadd, dest, srcA, srcB, srcC, srcD);
|
||||
}
|
||||
|
||||
public void Mmsub(CC cc, Dest dest, OpAC srcA, OpBD srcB, OpAC srcC, OpBD srcD)
|
||||
{
|
||||
Assemble(cc, Instruction.Mmsub, dest, srcA, srcB, srcC, srcD);
|
||||
}
|
||||
|
||||
public void Min(CC cc, Dest dest, OpAC srcA, OpBD srcB)
|
||||
{
|
||||
Assemble(cc, Instruction.Min, dest, srcA, srcB, OpAC.SrcRGB, OpBD.ConstantZero);
|
||||
}
|
||||
|
||||
public void Max(CC cc, Dest dest, OpAC srcA, OpBD srcB)
|
||||
{
|
||||
Assemble(cc, Instruction.Max, dest, srcA, srcB, OpAC.SrcRGB, OpBD.ConstantZero);
|
||||
}
|
||||
|
||||
public void Rcp(CC cc, Dest dest, OpAC srcA)
|
||||
{
|
||||
Assemble(cc, Instruction.Rcp, dest, srcA, OpBD.ConstantZero, OpAC.SrcRGB, OpBD.ConstantZero);
|
||||
}
|
||||
|
||||
public void Mov(CC cc, Dest dest, OpBD srcB)
|
||||
{
|
||||
Assemble(cc, Instruction.Add, dest, OpAC.SrcRGB, srcB, OpAC.SrcRGB, OpBD.ConstantZero);
|
||||
}
|
||||
|
||||
public void Add(CC cc, Dest dest, OpBD srcB, OpBD srcD)
|
||||
{
|
||||
Assemble(cc, Instruction.Add, dest, OpAC.SrcRGB, srcB, OpAC.SrcRGB, srcD);
|
||||
}
|
||||
|
||||
public void Sub(CC cc, Dest dest, OpBD srcB, OpBD srcD)
|
||||
{
|
||||
Assemble(cc, Instruction.Sub, dest, OpAC.SrcRGB, srcB, OpAC.SrcRGB, srcD);
|
||||
}
|
||||
|
||||
private void Assemble(CC cc, Instruction inst, Dest dest, OpAC srcA, OpBD srcB, OpAC srcC, OpBD srcD)
|
||||
{
|
||||
(_code ??= new List<uint>()).Add(new UcodeOp(cc, inst, _constantIndex, dest, srcA, srcB, srcC, srcD).Word);
|
||||
}
|
||||
|
||||
public void SetConstant(int index, float r, float g, float b)
|
||||
{
|
||||
if (_constants == null)
|
||||
{
|
||||
_constants = new RgbFloat[index + 1];
|
||||
}
|
||||
else if (_constants.Length <= index)
|
||||
{
|
||||
Array.Resize(ref _constants, index + 1);
|
||||
}
|
||||
|
||||
_constants[index] = new RgbFloat(r, g, b);
|
||||
_constantIndex = index;
|
||||
}
|
||||
|
||||
public uint[] GetCode()
|
||||
{
|
||||
return _code?.ToArray();
|
||||
}
|
||||
|
||||
public RgbFloat[] GetConstants()
|
||||
{
|
||||
return _constants;
|
||||
}
|
||||
}
|
||||
}
|
@ -725,10 +725,25 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
return;
|
||||
}
|
||||
|
||||
bool clearDepth = (argument & 1) != 0;
|
||||
bool clearStencil = (argument & 2) != 0;
|
||||
uint componentMask = (uint)((argument >> 2) & 0xf);
|
||||
int index = (argument >> 6) & 0xf;
|
||||
int layer = (argument >> 10) & 0x3ff;
|
||||
|
||||
engine.UpdateRenderTargetState(useControl: false, layered: layer != 0 || layerCount > 1, singleUse: index);
|
||||
RenderTargetUpdateFlags updateFlags = RenderTargetUpdateFlags.SingleColor;
|
||||
|
||||
if (layer != 0 || layerCount > 1)
|
||||
{
|
||||
updateFlags |= RenderTargetUpdateFlags.Layered;
|
||||
}
|
||||
|
||||
if (clearDepth || clearStencil)
|
||||
{
|
||||
updateFlags |= RenderTargetUpdateFlags.UpdateDepthStencil;
|
||||
}
|
||||
|
||||
engine.UpdateRenderTargetState(updateFlags, singleUse: componentMask != 0 ? index : -1);
|
||||
|
||||
// If there is a mismatch on the host clip region and the one explicitly defined by the guest
|
||||
// on the screen scissor state, then we need to force only one texture to be bound to avoid
|
||||
@ -788,18 +803,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
_context.Renderer.Pipeline.SetScissors(scissors);
|
||||
}
|
||||
|
||||
if (clipMismatch)
|
||||
{
|
||||
_channel.TextureManager.UpdateRenderTarget(index);
|
||||
}
|
||||
else
|
||||
{
|
||||
_channel.TextureManager.UpdateRenderTargets();
|
||||
}
|
||||
|
||||
bool clearDepth = (argument & 1) != 0;
|
||||
bool clearStencil = (argument & 2) != 0;
|
||||
uint componentMask = (uint)((argument >> 2) & 0xf);
|
||||
_channel.TextureManager.UpdateRenderTargets();
|
||||
|
||||
if (componentMask != 0)
|
||||
{
|
||||
@ -841,7 +845,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
engine.UpdateScissorState();
|
||||
}
|
||||
|
||||
engine.UpdateRenderTargetState(useControl: true);
|
||||
engine.UpdateRenderTargetState(RenderTargetUpdateFlags.UpdateAll);
|
||||
|
||||
if (renderEnable == ConditionalRenderEnabled.Host)
|
||||
{
|
||||
|
@ -0,0 +1,41 @@
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
{
|
||||
/// <summary>
|
||||
/// Flags indicating how the render targets should be updated.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
enum RenderTargetUpdateFlags
|
||||
{
|
||||
/// <summary>
|
||||
/// No flags.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Get render target index from the control register.
|
||||
/// </summary>
|
||||
UseControl = 1 << 0,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that all render targets are 2D array textures.
|
||||
/// </summary>
|
||||
Layered = 1 << 1,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that only a single color target will be used.
|
||||
/// </summary>
|
||||
SingleColor = 1 << 2,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that the depth-stencil target will be used.
|
||||
/// </summary>
|
||||
UpdateDepthStencil = 1 << 3,
|
||||
|
||||
/// <summary>
|
||||
/// Default update flags for draw.
|
||||
/// </summary>
|
||||
UpdateAll = UseControl | UpdateDepthStencil
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Gpu.Engine.Threed.Blender;
|
||||
using Ryujinx.Graphics.Gpu.Engine.Types;
|
||||
using Ryujinx.Graphics.Gpu.Image;
|
||||
using Ryujinx.Graphics.Gpu.Shader;
|
||||
@ -26,6 +27,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
private readonly GpuChannel _channel;
|
||||
private readonly DeviceStateWithShadow<ThreedClassState> _state;
|
||||
private readonly DrawState _drawState;
|
||||
private readonly AdvancedBlendManager _blendManager;
|
||||
|
||||
private readonly StateUpdateTracker<ThreedClassState> _updateTracker;
|
||||
|
||||
@ -55,13 +57,21 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
/// <param name="channel">GPU channel</param>
|
||||
/// <param name="state">3D engine state</param>
|
||||
/// <param name="drawState">Draw state</param>
|
||||
/// <param name="blendManager">Advanced blend manager</param>
|
||||
/// <param name="spec">Specialization state updater</param>
|
||||
public StateUpdater(GpuContext context, GpuChannel channel, DeviceStateWithShadow<ThreedClassState> state, DrawState drawState, SpecializationStateUpdater spec)
|
||||
public StateUpdater(
|
||||
GpuContext context,
|
||||
GpuChannel channel,
|
||||
DeviceStateWithShadow<ThreedClassState> state,
|
||||
DrawState drawState,
|
||||
AdvancedBlendManager blendManager,
|
||||
SpecializationStateUpdater spec)
|
||||
{
|
||||
_context = context;
|
||||
_channel = channel;
|
||||
_state = state;
|
||||
_drawState = drawState;
|
||||
_blendManager = blendManager;
|
||||
_currentProgramInfo = new ShaderProgramInfo[Constants.ShaderStages];
|
||||
_currentSpecState = spec;
|
||||
|
||||
@ -84,6 +94,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
new StateUpdateCallbackEntry(UpdateVertexAttribState, nameof(ThreedClassState.VertexAttribState)),
|
||||
|
||||
new StateUpdateCallbackEntry(UpdateBlendState,
|
||||
nameof(ThreedClassState.BlendUcodeEnable),
|
||||
nameof(ThreedClassState.BlendUcodeSize),
|
||||
nameof(ThreedClassState.BlendIndependent),
|
||||
nameof(ThreedClassState.BlendConstant),
|
||||
nameof(ThreedClassState.BlendStateCommon),
|
||||
@ -402,20 +414,23 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
/// </summary>
|
||||
private void UpdateRenderTargetState()
|
||||
{
|
||||
UpdateRenderTargetState(true);
|
||||
UpdateRenderTargetState(RenderTargetUpdateFlags.UpdateAll);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates render targets (color and depth-stencil buffers) based on current render target state.
|
||||
/// </summary>
|
||||
/// <param name="useControl">Use draw buffers information from render target control register</param>
|
||||
/// <param name="layered">Indicates if the texture is layered</param>
|
||||
/// <param name="updateFlags">Flags indicating which render targets should be updated and how</param>
|
||||
/// <param name="singleUse">If this is not -1, it indicates that only the given indexed target will be used.</param>
|
||||
public void UpdateRenderTargetState(bool useControl, bool layered = false, int singleUse = -1)
|
||||
public void UpdateRenderTargetState(RenderTargetUpdateFlags updateFlags, int singleUse = -1)
|
||||
{
|
||||
var memoryManager = _channel.MemoryManager;
|
||||
var rtControl = _state.State.RtControl;
|
||||
|
||||
bool useControl = updateFlags.HasFlag(RenderTargetUpdateFlags.UseControl);
|
||||
bool layered = updateFlags.HasFlag(RenderTargetUpdateFlags.Layered);
|
||||
bool singleColor = updateFlags.HasFlag(RenderTargetUpdateFlags.SingleColor);
|
||||
|
||||
int count = useControl ? rtControl.UnpackCount() : Constants.TotalRenderTargets;
|
||||
|
||||
var msaaMode = _state.State.RtMsaaMode;
|
||||
@ -438,7 +453,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
|
||||
var colorState = _state.State.RtColorState[rtIndex];
|
||||
|
||||
if (index >= count || !IsRtEnabled(colorState))
|
||||
if (index >= count || !IsRtEnabled(colorState) || (singleColor && index != singleUse))
|
||||
{
|
||||
changedScale |= _channel.TextureManager.SetRenderTargetColor(index, null);
|
||||
|
||||
@ -478,7 +493,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
|
||||
Image.Texture depthStencil = null;
|
||||
|
||||
if (dsEnable)
|
||||
if (dsEnable && updateFlags.HasFlag(RenderTargetUpdateFlags.UpdateDepthStencil))
|
||||
{
|
||||
var dsState = _state.State.RtDepthStencilState;
|
||||
var dsSize = _state.State.RtDepthStencilSize;
|
||||
@ -1151,6 +1166,20 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
/// </summary>
|
||||
private void UpdateBlendState()
|
||||
{
|
||||
if (_state.State.BlendUcodeEnable != BlendUcodeEnable.Disabled)
|
||||
{
|
||||
if (_context.Capabilities.SupportsBlendEquationAdvanced && _blendManager.TryGetAdvancedBlend(out var blendDescriptor))
|
||||
{
|
||||
// Try to HLE it using advanced blend on the host if we can.
|
||||
_context.Renderer.Pipeline.SetBlendState(blendDescriptor);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: Blend emulation fallback.
|
||||
}
|
||||
}
|
||||
|
||||
bool blendIndependent = _state.State.BlendIndependent;
|
||||
ColorF blendConstant = _state.State.BlendConstant;
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Gpu.Engine.GPFifo;
|
||||
using Ryujinx.Graphics.Gpu.Engine.InlineToMemory;
|
||||
using Ryujinx.Graphics.Gpu.Engine.Threed.Blender;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
@ -18,6 +19,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
private readonly DeviceStateWithShadow<ThreedClassState> _state;
|
||||
|
||||
private readonly InlineToMemoryClass _i2mClass;
|
||||
private readonly AdvancedBlendManager _blendManager;
|
||||
private readonly DrawManager _drawManager;
|
||||
private readonly SemaphoreUpdater _semaphoreUpdater;
|
||||
private readonly ConstantBufferUpdater _cbUpdater;
|
||||
@ -40,6 +42,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
{ nameof(ThreedClassState.InvalidateSamplerCacheNoWfi), new RwCallback(InvalidateSamplerCacheNoWfi, null) },
|
||||
{ nameof(ThreedClassState.InvalidateTextureHeaderCacheNoWfi), new RwCallback(InvalidateTextureHeaderCacheNoWfi, null) },
|
||||
{ nameof(ThreedClassState.TextureBarrier), new RwCallback(TextureBarrier, null) },
|
||||
{ nameof(ThreedClassState.LoadBlendUcodeStart), new RwCallback(LoadBlendUcodeStart, null) },
|
||||
{ nameof(ThreedClassState.LoadBlendUcodeInstruction), new RwCallback(LoadBlendUcodeInstruction, null) },
|
||||
{ nameof(ThreedClassState.TextureBarrierTiled), new RwCallback(TextureBarrierTiled, null) },
|
||||
{ nameof(ThreedClassState.DrawTextureSrcY), new RwCallback(DrawTexture, null) },
|
||||
{ nameof(ThreedClassState.DrawVertexArrayBeginEndInstanceFirst), new RwCallback(DrawVertexArrayBeginEndInstanceFirst, null) },
|
||||
@ -75,9 +79,10 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
var drawState = new DrawState();
|
||||
|
||||
_drawManager = new DrawManager(context, channel, _state, drawState, spec);
|
||||
_blendManager = new AdvancedBlendManager(_state);
|
||||
_semaphoreUpdater = new SemaphoreUpdater(context, channel, _state);
|
||||
_cbUpdater = new ConstantBufferUpdater(channel, _state);
|
||||
_stateUpdater = new StateUpdater(context, channel, _state, drawState, spec);
|
||||
_stateUpdater = new StateUpdater(context, channel, _state, drawState, _blendManager, spec);
|
||||
|
||||
// This defaults to "always", even without any register write.
|
||||
// Reads just return 0, regardless of what was set there.
|
||||
@ -139,12 +144,11 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
/// <summary>
|
||||
/// Updates render targets (color and depth-stencil buffers) based on current render target state.
|
||||
/// </summary>
|
||||
/// <param name="useControl">Use draw buffers information from render target control register</param>
|
||||
/// <param name="layered">Indicates if the texture is layered</param>
|
||||
/// <param name="updateFlags">Flags indicating which render targets should be updated and how</param>
|
||||
/// <param name="singleUse">If this is not -1, it indicates that only the given indexed target will be used.</param>
|
||||
public void UpdateRenderTargetState(bool useControl, bool layered = false, int singleUse = -1)
|
||||
public void UpdateRenderTargetState(RenderTargetUpdateFlags updateFlags, int singleUse = -1)
|
||||
{
|
||||
_stateUpdater.UpdateRenderTargetState(useControl, layered, singleUse);
|
||||
_stateUpdater.UpdateRenderTargetState(updateFlags, singleUse);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -284,6 +288,24 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
_context.Renderer.Pipeline.TextureBarrier();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the start offset of the blend microcode in memory.
|
||||
/// </summary>
|
||||
/// <param name="argument">Method call argument</param>
|
||||
private void LoadBlendUcodeStart(int argument)
|
||||
{
|
||||
_blendManager.LoadBlendUcodeStart(argument);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pushes one word of blend microcode.
|
||||
/// </summary>
|
||||
/// <param name="argument">Method call argument</param>
|
||||
private void LoadBlendUcodeInstruction(int argument)
|
||||
{
|
||||
_blendManager.LoadBlendUcodeInstruction(argument);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Issues a texture barrier.
|
||||
/// This waits until previous texture writes from the GPU to finish, before
|
||||
|
@ -5,6 +5,7 @@ using Ryujinx.Graphics.Gpu.Engine.Types;
|
||||
using Ryujinx.Graphics.Gpu.Image;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
{
|
||||
@ -214,6 +215,17 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
#pragma warning restore CS0649
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whenever the blend microcode processes RGB and alpha components.
|
||||
/// </summary>
|
||||
enum BlendUcodeEnable
|
||||
{
|
||||
Disabled = 0,
|
||||
EnableRGB = 1,
|
||||
EnableAlpha = 2,
|
||||
EnableRGBA = 3
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scissor state.
|
||||
/// </summary>
|
||||
@ -434,6 +446,49 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
TriangleRastFlip = 1 << 4
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// RGB color components packed as 16-bit float values.
|
||||
/// </summary>
|
||||
struct RgbHalf
|
||||
{
|
||||
#pragma warning disable CS0649
|
||||
public uint R;
|
||||
public uint G;
|
||||
public uint B;
|
||||
public uint Padding;
|
||||
#pragma warning restore CS0649
|
||||
|
||||
/// <summary>
|
||||
/// Unpacks the red color component as a 16-bit float value.
|
||||
/// </summary>
|
||||
/// <returns>The component value</returns>
|
||||
public Half UnpackR()
|
||||
{
|
||||
ushort value = (ushort)R;
|
||||
return Unsafe.As<ushort, Half>(ref value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unpacks the green color component as a 16-bit float value.
|
||||
/// </summary>
|
||||
/// <returns>The component value</returns>
|
||||
public Half UnpackG()
|
||||
{
|
||||
ushort value = (ushort)G;
|
||||
return Unsafe.As<ushort, Half>(ref value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unpacks the blue color component as a 16-bit float value.
|
||||
/// </summary>
|
||||
/// <returns>The component value</returns>
|
||||
public Half UnpackB()
|
||||
{
|
||||
ushort value = (ushort)B;
|
||||
return Unsafe.As<ushort, Half>(ref value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Condition for conditional rendering.
|
||||
/// </summary>
|
||||
@ -752,7 +807,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
public Boolean32 EarlyZForce;
|
||||
public fixed uint Reserved214[45];
|
||||
public uint SyncpointAction;
|
||||
public fixed uint Reserved2CC[21];
|
||||
public fixed uint Reserved2CC[10];
|
||||
public uint BlendUcodeNormalizedDst;
|
||||
public fixed uint Reserved2F8[10];
|
||||
public TessMode TessMode;
|
||||
public Array4<float> TessOuterLevel;
|
||||
public Array2<float> TessInnerLevel;
|
||||
@ -781,11 +838,16 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
public fixed uint ReservedDB8[2];
|
||||
public DepthBiasState DepthBiasState;
|
||||
public int PatchVertices;
|
||||
public fixed uint ReservedDD0[4];
|
||||
public BlendUcodeEnable BlendUcodeEnable;
|
||||
public uint BlendUcodeSize;
|
||||
public fixed uint ReservedDD8[2];
|
||||
public uint TextureBarrier;
|
||||
public uint WatchdogTimer;
|
||||
public Boolean32 PrimitiveRestartDrawArrays;
|
||||
public fixed uint ReservedDEC[5];
|
||||
public uint ReservedDEC;
|
||||
public uint LoadBlendUcodeStart;
|
||||
public uint LoadBlendUcodeInstruction;
|
||||
public fixed uint ReservedDF8[2];
|
||||
public Array16<ScissorState> ScissorState;
|
||||
public fixed uint ReservedF00[21];
|
||||
public StencilBackMasks StencilBackMasks;
|
||||
@ -850,7 +912,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
public fixed uint Reserved142C[2];
|
||||
public uint FirstVertex;
|
||||
public uint FirstInstance;
|
||||
public fixed uint Reserved143C[53];
|
||||
public fixed uint Reserved143C[17];
|
||||
public Array8<RgbHalf> BlendUcodeConstants;
|
||||
public fixed uint Reserved1500[4];
|
||||
public uint ClipDistanceEnable;
|
||||
public uint Reserved1514;
|
||||
public float PointSize;
|
||||
|
@ -33,10 +33,12 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// </summary>
|
||||
class AutoDeleteCache : IEnumerable<Texture>
|
||||
{
|
||||
private const int MinCountForDeletion = 32;
|
||||
private const int MaxCapacity = 2048;
|
||||
private const ulong MaxTextureSizeCapacity = 512 * 1024 * 1024; // MB;
|
||||
|
||||
private readonly LinkedList<Texture> _textures;
|
||||
private readonly ConcurrentQueue<Texture> _deferredRemovals;
|
||||
private ulong _totalSize;
|
||||
|
||||
private HashSet<ShortTextureCacheEntry> _shortCacheBuilder;
|
||||
private HashSet<ShortTextureCacheEntry> _shortCache;
|
||||
@ -49,7 +51,6 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
public AutoDeleteCache()
|
||||
{
|
||||
_textures = new LinkedList<Texture>();
|
||||
_deferredRemovals = new ConcurrentQueue<Texture>();
|
||||
|
||||
_shortCacheBuilder = new HashSet<ShortTextureCacheEntry>();
|
||||
_shortCache = new HashSet<ShortTextureCacheEntry>();
|
||||
@ -67,37 +68,15 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// <param name="texture">The texture to be added to the cache</param>
|
||||
public void Add(Texture texture)
|
||||
{
|
||||
texture.IncrementReferenceCount();
|
||||
_totalSize += texture.Size;
|
||||
|
||||
texture.IncrementReferenceCount();
|
||||
texture.CacheNode = _textures.AddLast(texture);
|
||||
|
||||
if (_textures.Count > MaxCapacity)
|
||||
if (_textures.Count > MaxCapacity ||
|
||||
(_totalSize > MaxTextureSizeCapacity && _textures.Count >= MinCountForDeletion))
|
||||
{
|
||||
Texture oldestTexture = _textures.First.Value;
|
||||
|
||||
if (!oldestTexture.CheckModified(false))
|
||||
{
|
||||
// The texture must be flushed if it falls out of the auto delete cache.
|
||||
// Flushes out of the auto delete cache do not trigger write tracking,
|
||||
// as it is expected that other overlapping textures exist that have more up-to-date contents.
|
||||
|
||||
oldestTexture.Group.SynchronizeDependents(oldestTexture);
|
||||
oldestTexture.FlushModified(false);
|
||||
}
|
||||
|
||||
_textures.RemoveFirst();
|
||||
|
||||
oldestTexture.DecrementReferenceCount();
|
||||
|
||||
oldestTexture.CacheNode = null;
|
||||
}
|
||||
|
||||
if (_deferredRemovals.Count > 0)
|
||||
{
|
||||
while (_deferredRemovals.TryDequeue(out Texture textureToRemove))
|
||||
{
|
||||
Remove(textureToRemove, false);
|
||||
}
|
||||
RemoveLeastUsedTexture();
|
||||
}
|
||||
}
|
||||
|
||||
@ -120,6 +99,11 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
|
||||
texture.CacheNode = _textures.AddLast(texture);
|
||||
}
|
||||
|
||||
if (_totalSize > MaxTextureSizeCapacity && _textures.Count >= MinCountForDeletion)
|
||||
{
|
||||
RemoveLeastUsedTexture();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -127,6 +111,31 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the least used texture from the cache.
|
||||
/// </summary>
|
||||
private void RemoveLeastUsedTexture()
|
||||
{
|
||||
Texture oldestTexture = _textures.First.Value;
|
||||
|
||||
_totalSize -= oldestTexture.Size;
|
||||
|
||||
if (!oldestTexture.CheckModified(false))
|
||||
{
|
||||
// The texture must be flushed if it falls out of the auto delete cache.
|
||||
// Flushes out of the auto delete cache do not trigger write tracking,
|
||||
// as it is expected that other overlapping textures exist that have more up-to-date contents.
|
||||
|
||||
oldestTexture.Group.SynchronizeDependents(oldestTexture);
|
||||
oldestTexture.FlushModified(false);
|
||||
}
|
||||
|
||||
_textures.RemoveFirst();
|
||||
|
||||
oldestTexture.DecrementReferenceCount();
|
||||
oldestTexture.CacheNode = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a texture from the cache.
|
||||
/// </summary>
|
||||
@ -148,20 +157,13 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
|
||||
_textures.Remove(texture.CacheNode);
|
||||
|
||||
_totalSize -= texture.Size;
|
||||
|
||||
texture.CacheNode = null;
|
||||
|
||||
return texture.DecrementReferenceCount();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queues removal of a texture from the cache in a thread safe way.
|
||||
/// </summary>
|
||||
/// <param name="texture">The texture to be removed from the cache</param>
|
||||
public void RemoveDeferred(Texture texture)
|
||||
{
|
||||
_deferredRemovals.Enqueue(texture);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempt to find a texture on the short duration cache.
|
||||
/// </summary>
|
||||
|
@ -69,7 +69,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
Address = address;
|
||||
Size = size;
|
||||
|
||||
_memoryTracking = physicalMemory.BeginGranularTracking(address, size);
|
||||
_memoryTracking = physicalMemory.BeginGranularTracking(address, size, ResourceKind.Pool);
|
||||
_memoryTracking.RegisterPreciseAction(address, size, PreciseAction);
|
||||
_modifiedDelegate = RegionModified;
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
@ -89,12 +88,6 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// </summary>
|
||||
public TextureGroup Group { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Set when a texture has been changed size. This indicates that it may need to be
|
||||
/// changed again when obtained as a sampler.
|
||||
/// </summary>
|
||||
public bool ChangedSize { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Set when a texture's GPU VA has ever been partially or fully unmapped.
|
||||
/// This indicates that the range must be fully checked when matching the texture.
|
||||
@ -410,122 +403,6 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
Group.CreateCopyDependency(contained, FirstLayer + layer, FirstLevel + level, copyTo);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes the texture size.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This operation may also change the size of all mipmap levels, including from the parent
|
||||
/// and other possible child textures, to ensure that all sizes are consistent.
|
||||
/// </remarks>
|
||||
/// <param name="width">The new texture width</param>
|
||||
/// <param name="height">The new texture height</param>
|
||||
/// <param name="depthOrLayers">The new texture depth (for 3D textures) or layers (for layered textures)</param>
|
||||
public void ChangeSize(int width, int height, int depthOrLayers)
|
||||
{
|
||||
int blockWidth = Info.FormatInfo.BlockWidth;
|
||||
int blockHeight = Info.FormatInfo.BlockHeight;
|
||||
|
||||
width <<= FirstLevel;
|
||||
height <<= FirstLevel;
|
||||
|
||||
if (Target == Target.Texture3D)
|
||||
{
|
||||
depthOrLayers <<= FirstLevel;
|
||||
}
|
||||
else
|
||||
{
|
||||
depthOrLayers = _viewStorage.Info.DepthOrLayers;
|
||||
}
|
||||
|
||||
_viewStorage.RecreateStorageOrView(width, height, blockWidth, blockHeight, depthOrLayers);
|
||||
|
||||
foreach (Texture view in _viewStorage._views)
|
||||
{
|
||||
int viewWidth = Math.Max(1, width >> view.FirstLevel);
|
||||
int viewHeight = Math.Max(1, height >> view.FirstLevel);
|
||||
|
||||
int viewDepthOrLayers;
|
||||
|
||||
if (view.Info.Target == Target.Texture3D)
|
||||
{
|
||||
viewDepthOrLayers = Math.Max(1, depthOrLayers >> view.FirstLevel);
|
||||
}
|
||||
else
|
||||
{
|
||||
viewDepthOrLayers = view.Info.DepthOrLayers;
|
||||
}
|
||||
|
||||
view.RecreateStorageOrView(viewWidth, viewHeight, blockWidth, blockHeight, viewDepthOrLayers);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recreates the texture storage (or view, in the case of child textures) of this texture.
|
||||
/// This allows recreating the texture with a new size.
|
||||
/// A copy is automatically performed from the old to the new texture.
|
||||
/// </summary>
|
||||
/// <param name="width">The new texture width</param>
|
||||
/// <param name="height">The new texture height</param>
|
||||
/// <param name="width">The block width related to the given width</param>
|
||||
/// <param name="height">The block height related to the given height</param>
|
||||
/// <param name="depthOrLayers">The new texture depth (for 3D textures) or layers (for layered textures)</param>
|
||||
private void RecreateStorageOrView(int width, int height, int blockWidth, int blockHeight, int depthOrLayers)
|
||||
{
|
||||
RecreateStorageOrView(
|
||||
BitUtils.DivRoundUp(width * Info.FormatInfo.BlockWidth, blockWidth),
|
||||
BitUtils.DivRoundUp(height * Info.FormatInfo.BlockHeight, blockHeight),
|
||||
depthOrLayers);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recreates the texture storage (or view, in the case of child textures) of this texture.
|
||||
/// This allows recreating the texture with a new size.
|
||||
/// A copy is automatically performed from the old to the new texture.
|
||||
/// </summary>
|
||||
/// <param name="width">The new texture width</param>
|
||||
/// <param name="height">The new texture height</param>
|
||||
/// <param name="depthOrLayers">The new texture depth (for 3D textures) or layers (for layered textures)</param>
|
||||
private void RecreateStorageOrView(int width, int height, int depthOrLayers)
|
||||
{
|
||||
ChangedSize = true;
|
||||
|
||||
SetInfo(new TextureInfo(
|
||||
Info.GpuAddress,
|
||||
width,
|
||||
height,
|
||||
depthOrLayers,
|
||||
Info.Levels,
|
||||
Info.SamplesInX,
|
||||
Info.SamplesInY,
|
||||
Info.Stride,
|
||||
Info.IsLinear,
|
||||
Info.GobBlocksInY,
|
||||
Info.GobBlocksInZ,
|
||||
Info.GobBlocksInTileX,
|
||||
Info.Target,
|
||||
Info.FormatInfo,
|
||||
Info.DepthStencilMode,
|
||||
Info.SwizzleR,
|
||||
Info.SwizzleG,
|
||||
Info.SwizzleB,
|
||||
Info.SwizzleA));
|
||||
|
||||
TextureCreateInfo createInfo = TextureCache.GetCreateInfo(Info, _context.Capabilities, ScaleFactor);
|
||||
|
||||
if (_viewStorage != this)
|
||||
{
|
||||
ReplaceStorage(_viewStorage.HostTexture.CreateView(createInfo, FirstLayer, FirstLevel));
|
||||
}
|
||||
else
|
||||
{
|
||||
ITexture newStorage = _context.Renderer.CreateTexture(createInfo, ScaleFactor);
|
||||
|
||||
HostTexture.CopyTo(newStorage, 0, 0);
|
||||
|
||||
ReplaceStorage(newStorage);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers when a texture has had its data set after being scaled, and
|
||||
/// determines if it should be blacklisted from scaling to improve performance.
|
||||
@ -1215,7 +1092,9 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// <returns>A value indicating how well this texture matches the given info</returns>
|
||||
public TextureMatchQuality IsExactMatch(TextureInfo info, TextureSearchFlags flags)
|
||||
{
|
||||
TextureMatchQuality matchQuality = TextureCompatibility.FormatMatches(Info, info, (flags & TextureSearchFlags.ForSampler) != 0, (flags & TextureSearchFlags.ForCopy) != 0);
|
||||
bool forSampler = (flags & TextureSearchFlags.ForSampler) != 0;
|
||||
|
||||
TextureMatchQuality matchQuality = TextureCompatibility.FormatMatches(Info, info, forSampler, (flags & TextureSearchFlags.ForCopy) != 0);
|
||||
|
||||
if (matchQuality == TextureMatchQuality.NoMatch)
|
||||
{
|
||||
@ -1227,12 +1106,12 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
return TextureMatchQuality.NoMatch;
|
||||
}
|
||||
|
||||
if (!TextureCompatibility.SizeMatches(Info, info, (flags & TextureSearchFlags.Strict) == 0, FirstLevel))
|
||||
if (!TextureCompatibility.SizeMatches(Info, info, forSampler))
|
||||
{
|
||||
return TextureMatchQuality.NoMatch;
|
||||
}
|
||||
|
||||
if ((flags & TextureSearchFlags.ForSampler) != 0 || (flags & TextureSearchFlags.Strict) != 0)
|
||||
if ((flags & TextureSearchFlags.ForSampler) != 0)
|
||||
{
|
||||
if (!TextureCompatibility.SamplerParamsMatches(Info, info))
|
||||
{
|
||||
@ -1262,12 +1141,20 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// </summary>
|
||||
/// <param name="info">Texture view information</param>
|
||||
/// <param name="range">Texture view physical memory ranges</param>
|
||||
/// <param name="exactSize">Indicates if the texture sizes must be exactly equal, or width is allowed to differ</param>
|
||||
/// <param name="layerSize">Layer size on the given texture</param>
|
||||
/// <param name="caps">Host GPU capabilities</param>
|
||||
/// <param name="firstLayer">Texture view initial layer on this texture</param>
|
||||
/// <param name="firstLevel">Texture view first mipmap level on this texture</param>
|
||||
/// <returns>The level of compatiblilty a view with the given parameters created from this texture has</returns>
|
||||
public TextureViewCompatibility IsViewCompatible(TextureInfo info, MultiRange range, int layerSize, Capabilities caps, out int firstLayer, out int firstLevel)
|
||||
public TextureViewCompatibility IsViewCompatible(
|
||||
TextureInfo info,
|
||||
MultiRange range,
|
||||
bool exactSize,
|
||||
int layerSize,
|
||||
Capabilities caps,
|
||||
out int firstLayer,
|
||||
out int firstLevel)
|
||||
{
|
||||
TextureViewCompatibility result = TextureViewCompatibility.Full;
|
||||
|
||||
@ -1317,7 +1204,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
return TextureViewCompatibility.LayoutIncompatible;
|
||||
}
|
||||
|
||||
result = TextureCompatibility.PropagateViewCompatibility(result, TextureCompatibility.ViewSizeMatches(Info, info, firstLevel));
|
||||
result = TextureCompatibility.PropagateViewCompatibility(result, TextureCompatibility.ViewSizeMatches(Info, info, exactSize, firstLevel));
|
||||
result = TextureCompatibility.PropagateViewCompatibility(result, TextureCompatibility.ViewSubImagesInBounds(Info, info, firstLayer, firstLevel));
|
||||
|
||||
return result;
|
||||
@ -1750,13 +1637,6 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
}
|
||||
|
||||
RemoveFromPools(true);
|
||||
|
||||
// We only want to remove if there's no mapped region of the texture that was modified by the GPU,
|
||||
// otherwise we could lose data.
|
||||
if (!Group.AnyModified(this))
|
||||
{
|
||||
_physicalMemory.TextureCache.QueueAutoDeleteCacheRemoval(this);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -210,8 +210,8 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
ulong offset,
|
||||
FormatInfo formatInfo,
|
||||
bool shouldCreate,
|
||||
bool preferScaling = true,
|
||||
Size? sizeHint = null)
|
||||
bool preferScaling,
|
||||
Size sizeHint)
|
||||
{
|
||||
int gobBlocksInY = copyTexture.MemoryLayout.UnpackGobBlocksInY();
|
||||
int gobBlocksInZ = copyTexture.MemoryLayout.UnpackGobBlocksInZ();
|
||||
@ -229,7 +229,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
|
||||
TextureInfo info = new TextureInfo(
|
||||
copyTexture.Address.Pack() + offset,
|
||||
width,
|
||||
GetMinimumWidthInGob(width, sizeHint.Width, formatInfo.BytesPerPixel, copyTexture.LinearLayout),
|
||||
copyTexture.Height,
|
||||
copyTexture.Depth,
|
||||
1,
|
||||
@ -255,7 +255,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
flags |= TextureSearchFlags.NoCreate;
|
||||
}
|
||||
|
||||
Texture texture = FindOrCreateTexture(memoryManager, flags, info, 0, sizeHint);
|
||||
Texture texture = FindOrCreateTexture(memoryManager, flags, info, 0);
|
||||
|
||||
texture?.SynchronizeMemory();
|
||||
|
||||
@ -326,7 +326,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
|
||||
TextureInfo info = new TextureInfo(
|
||||
colorState.Address.Pack(),
|
||||
width,
|
||||
GetMinimumWidthInGob(width, sizeHint.Width, formatInfo.BytesPerPixel, isLinear),
|
||||
colorState.Height,
|
||||
colorState.Depth,
|
||||
1,
|
||||
@ -342,7 +342,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
|
||||
int layerSize = !isLinear ? colorState.LayerSize * 4 : 0;
|
||||
|
||||
Texture texture = FindOrCreateTexture(memoryManager, TextureSearchFlags.WithUpscale, info, layerSize, sizeHint);
|
||||
Texture texture = FindOrCreateTexture(memoryManager, TextureSearchFlags.WithUpscale, info, layerSize);
|
||||
|
||||
texture?.SynchronizeMemory();
|
||||
|
||||
@ -395,7 +395,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
|
||||
TextureInfo info = new TextureInfo(
|
||||
dsState.Address.Pack(),
|
||||
size.Width,
|
||||
GetMinimumWidthInGob(size.Width, sizeHint.Width, formatInfo.BytesPerPixel, false),
|
||||
size.Height,
|
||||
size.Depth,
|
||||
1,
|
||||
@ -409,13 +409,41 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
target,
|
||||
formatInfo);
|
||||
|
||||
Texture texture = FindOrCreateTexture(memoryManager, TextureSearchFlags.WithUpscale, info, dsState.LayerSize * 4, sizeHint);
|
||||
Texture texture = FindOrCreateTexture(memoryManager, TextureSearchFlags.WithUpscale, info, dsState.LayerSize * 4);
|
||||
|
||||
texture?.SynchronizeMemory();
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For block linear textures, gets the minimum width of the texture
|
||||
/// that would still have the same number of GOBs per row as the original width.
|
||||
/// </summary>
|
||||
/// <param name="width">The possibly aligned texture width</param>
|
||||
/// <param name="minimumWidth">The minimum width that the texture may have without losing data</param>
|
||||
/// <param name="bytesPerPixel">Bytes per pixel of the texture format</param>
|
||||
/// <param name="isLinear">True if the texture is linear, false for block linear</param>
|
||||
/// <returns>The minimum width of the texture with the same amount of GOBs per row</returns>
|
||||
private static int GetMinimumWidthInGob(int width, int minimumWidth, int bytesPerPixel, bool isLinear)
|
||||
{
|
||||
if (isLinear || (uint)minimumWidth >= (uint)width)
|
||||
{
|
||||
return width;
|
||||
}
|
||||
|
||||
// Calculate the minimum possible that would not cause data loss
|
||||
// and would be still within the same GOB (aligned size would be the same).
|
||||
// This is useful for render and copy operations, where we don't know the
|
||||
// exact width of the texture, but it doesn't matter, as long the texture is
|
||||
// at least as large as the region being rendered or copied.
|
||||
|
||||
int alignment = 64 / bytesPerPixel;
|
||||
int widthAligned = BitUtils.AlignUp(width, alignment);
|
||||
|
||||
return Math.Clamp(widthAligned - alignment + 1, minimumWidth, widthAligned);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to find an existing texture, or create a new one if not found.
|
||||
/// </summary>
|
||||
@ -423,7 +451,6 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// <param name="flags">The texture search flags, defines texture comparison rules</param>
|
||||
/// <param name="info">Texture information of the texture to be found or created</param>
|
||||
/// <param name="layerSize">Size in bytes of a single texture layer</param>
|
||||
/// <param name="sizeHint">A hint indicating the minimum used size for the texture</param>
|
||||
/// <param name="range">Optional ranges of physical memory where the texture data is located</param>
|
||||
/// <returns>The texture</returns>
|
||||
public Texture FindOrCreateTexture(
|
||||
@ -431,7 +458,6 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
TextureSearchFlags flags,
|
||||
TextureInfo info,
|
||||
int layerSize = 0,
|
||||
Size? sizeHint = null,
|
||||
MultiRange? range = null)
|
||||
{
|
||||
bool isSamplerTexture = (flags & TextureSearchFlags.ForSampler) != 0;
|
||||
@ -448,6 +474,29 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
address = memoryManager.Translate(info.GpuAddress);
|
||||
|
||||
// If the start address is unmapped, let's try to find a page of memory that is mapped.
|
||||
if (address == MemoryManager.PteUnmapped)
|
||||
{
|
||||
// Make sure that the dimensions are valid before calculating the texture size.
|
||||
if (info.Width < 1 || info.Height < 1 || info.Levels < 1)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if ((info.Target == Target.Texture3D ||
|
||||
info.Target == Target.Texture2DArray ||
|
||||
info.Target == Target.Texture2DMultisampleArray ||
|
||||
info.Target == Target.CubemapArray) && info.DepthOrLayers < 1)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
ulong dataSize = (ulong)info.CalculateSizeInfo(layerSize).TotalSize;
|
||||
|
||||
address = memoryManager.TranslateFirstMapped(info.GpuAddress, dataSize);
|
||||
}
|
||||
|
||||
// If address is still invalid, the texture is fully unmapped, so it has no data, just return null.
|
||||
if (address == MemoryManager.PteUnmapped)
|
||||
{
|
||||
return null;
|
||||
@ -512,8 +561,6 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
|
||||
if (texture != null)
|
||||
{
|
||||
ChangeSizeIfNeeded(info, texture, isSamplerTexture, sizeHint);
|
||||
|
||||
texture.SynchronizeMemory();
|
||||
|
||||
return texture;
|
||||
@ -568,6 +615,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
TextureViewCompatibility overlapCompatibility = overlap.IsViewCompatible(
|
||||
info,
|
||||
range.Value,
|
||||
isSamplerTexture,
|
||||
sizeInfo.LayerSize,
|
||||
_context.Capabilities,
|
||||
out int firstLayer,
|
||||
@ -598,17 +646,15 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
|
||||
if (oInfo.Compatibility == TextureViewCompatibility.Full)
|
||||
{
|
||||
TextureInfo adjInfo = AdjustSizes(overlap, info, oInfo.FirstLevel);
|
||||
|
||||
if (!isSamplerTexture)
|
||||
{
|
||||
info = adjInfo;
|
||||
// If this is not a sampler texture, the size might be different from the requested size,
|
||||
// so we need to make sure the texture information has the correct size for this base texture,
|
||||
// before creating the view.
|
||||
info = info.CreateInfoForLevelView(overlap, oInfo.FirstLevel);
|
||||
}
|
||||
|
||||
texture = overlap.CreateView(adjInfo, sizeInfo, range.Value, oInfo.FirstLayer, oInfo.FirstLevel);
|
||||
|
||||
ChangeSizeIfNeeded(info, texture, isSamplerTexture, sizeHint);
|
||||
|
||||
texture = overlap.CreateView(info, sizeInfo, range.Value, oInfo.FirstLayer, oInfo.FirstLevel);
|
||||
texture.SynchronizeMemory();
|
||||
break;
|
||||
}
|
||||
@ -682,6 +728,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
TextureViewCompatibility compatibility = texture.IsViewCompatible(
|
||||
overlap.Info,
|
||||
overlap.Range,
|
||||
exactSize: true,
|
||||
overlap.LayerSize,
|
||||
_context.Capabilities,
|
||||
out int firstLayer,
|
||||
@ -792,7 +839,11 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
continue;
|
||||
}
|
||||
|
||||
TextureInfo overlapInfo = AdjustSizes(texture, overlap.Info, oInfo.FirstLevel);
|
||||
// Note: If we allow different sizes for those overlaps,
|
||||
// we need to make sure that the "info" has the correct size for the parent texture here.
|
||||
// Since this is not allowed right now, we don't need to do it.
|
||||
|
||||
TextureInfo overlapInfo = overlap.Info;
|
||||
|
||||
if (texture.ScaleFactor != overlap.ScaleFactor)
|
||||
{
|
||||
@ -856,44 +907,6 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
return texture;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes a texture's size to match the desired size for samplers,
|
||||
/// or increases a texture's size to fit the region indicated by a size hint.
|
||||
/// </summary>
|
||||
/// <param name="info">The desired texture info</param>
|
||||
/// <param name="texture">The texture to resize</param>
|
||||
/// <param name="isSamplerTexture">True if the texture will be used for a sampler, false otherwise</param>
|
||||
/// <param name="sizeHint">A hint indicating the minimum used size for the texture</param>
|
||||
private void ChangeSizeIfNeeded(TextureInfo info, Texture texture, bool isSamplerTexture, Size? sizeHint)
|
||||
{
|
||||
if (isSamplerTexture)
|
||||
{
|
||||
// If this is used for sampling, the size must match,
|
||||
// otherwise the shader would sample garbage data.
|
||||
// To fix that, we create a new texture with the correct
|
||||
// size, and copy the data from the old one to the new one.
|
||||
|
||||
if (!TextureCompatibility.SizeMatches(texture.Info, info))
|
||||
{
|
||||
texture.ChangeSize(info.Width, info.Height, info.DepthOrLayers);
|
||||
}
|
||||
}
|
||||
else if (sizeHint != null)
|
||||
{
|
||||
// A size hint indicates that data will be used within that range, at least.
|
||||
// If the texture is smaller than the size hint, it must be enlarged to meet it.
|
||||
// The maximum size is provided by the requested info, which generally has an aligned size.
|
||||
|
||||
int width = Math.Max(texture.Info.Width, Math.Min(sizeHint.Value.Width, info.Width));
|
||||
int height = Math.Max(texture.Info.Height, Math.Min(sizeHint.Value.Height, info.Height));
|
||||
|
||||
if (texture.Info.Width != width || texture.Info.Height != height)
|
||||
{
|
||||
texture.ChangeSize(width, height, info.DepthOrLayers);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempt to find a texture on the short duration cache.
|
||||
/// </summary>
|
||||
@ -1000,92 +1013,6 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adjusts the size of the texture information for a given mipmap level,
|
||||
/// based on the size of a parent texture.
|
||||
/// </summary>
|
||||
/// <param name="parent">The parent texture</param>
|
||||
/// <param name="info">The texture information to be adjusted</param>
|
||||
/// <param name="firstLevel">The first level of the texture view</param>
|
||||
/// <returns>The adjusted texture information with the new size</returns>
|
||||
private static TextureInfo AdjustSizes(Texture parent, TextureInfo info, int firstLevel)
|
||||
{
|
||||
// When the texture is used as view of another texture, we must
|
||||
// ensure that the sizes are valid, otherwise data uploads would fail
|
||||
// (and the size wouldn't match the real size used on the host API).
|
||||
// Given a parent texture from where the view is created, we have the
|
||||
// following rules:
|
||||
// - The view size must be equal to the parent size, divided by (2 ^ l),
|
||||
// where l is the first mipmap level of the view. The division result must
|
||||
// be rounded down, and the result must be clamped to 1.
|
||||
// - If the parent format is compressed, and the view format isn't, the
|
||||
// view size is calculated as above, but the width and height of the
|
||||
// view must be also divided by the compressed format block width and height.
|
||||
// - If the parent format is not compressed, and the view is, the view
|
||||
// size is calculated as described on the first point, but the width and height
|
||||
// of the view must be also multiplied by the block width and height.
|
||||
int width = Math.Max(1, parent.Info.Width >> firstLevel);
|
||||
int height = Math.Max(1, parent.Info.Height >> firstLevel);
|
||||
|
||||
if (parent.Info.FormatInfo.IsCompressed && !info.FormatInfo.IsCompressed)
|
||||
{
|
||||
width = BitUtils.DivRoundUp(width, parent.Info.FormatInfo.BlockWidth);
|
||||
height = BitUtils.DivRoundUp(height, parent.Info.FormatInfo.BlockHeight);
|
||||
}
|
||||
else if (!parent.Info.FormatInfo.IsCompressed && info.FormatInfo.IsCompressed)
|
||||
{
|
||||
width *= info.FormatInfo.BlockWidth;
|
||||
height *= info.FormatInfo.BlockHeight;
|
||||
}
|
||||
|
||||
int depthOrLayers;
|
||||
|
||||
if (info.Target == Target.Texture3D)
|
||||
{
|
||||
depthOrLayers = Math.Max(1, parent.Info.DepthOrLayers >> firstLevel);
|
||||
}
|
||||
else
|
||||
{
|
||||
depthOrLayers = info.DepthOrLayers;
|
||||
}
|
||||
|
||||
// 2D and 2D multisample textures are not considered compatible.
|
||||
// This specific case is required for copies, where the source texture might be multisample.
|
||||
// In this case, we inherit the parent texture multisample state.
|
||||
Target target = info.Target;
|
||||
int samplesInX = info.SamplesInX;
|
||||
int samplesInY = info.SamplesInY;
|
||||
|
||||
if (target == Target.Texture2D && parent.Target == Target.Texture2DMultisample)
|
||||
{
|
||||
target = Target.Texture2DMultisample;
|
||||
samplesInX = parent.Info.SamplesInX;
|
||||
samplesInY = parent.Info.SamplesInY;
|
||||
}
|
||||
|
||||
return new TextureInfo(
|
||||
info.GpuAddress,
|
||||
width,
|
||||
height,
|
||||
depthOrLayers,
|
||||
info.Levels,
|
||||
samplesInX,
|
||||
samplesInY,
|
||||
info.Stride,
|
||||
info.IsLinear,
|
||||
info.GobBlocksInY,
|
||||
info.GobBlocksInZ,
|
||||
info.GobBlocksInTileX,
|
||||
target,
|
||||
info.FormatInfo,
|
||||
info.DepthStencilMode,
|
||||
info.SwizzleR,
|
||||
info.SwizzleG,
|
||||
info.SwizzleB,
|
||||
info.SwizzleA);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets a texture creation information from texture information.
|
||||
/// This can be used to create new host textures.
|
||||
@ -1175,19 +1102,6 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queues the removal of a texture from the auto delete cache.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This function is thread safe and can be called from any thread.
|
||||
/// The texture will be deleted on the next time the cache is used.
|
||||
/// </remarks>
|
||||
/// <param name="texture">The texture to be removed</param>
|
||||
public void QueueAutoDeleteCacheRemoval(Texture texture)
|
||||
{
|
||||
_cache.RemoveDeferred(texture);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a texture to the short duration cache. This typically keeps it alive for two ticks.
|
||||
/// </summary>
|
||||
|
@ -214,41 +214,6 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if two formats are compatible, according to the host API copy format compatibility rules.
|
||||
/// </summary>
|
||||
/// <param name="lhsFormat">First comparand</param>
|
||||
/// <param name="rhsFormat">Second comparand</param>
|
||||
/// <param name="caps">Host GPU capabilities</param>
|
||||
/// <returns>True if the formats are compatible, false otherwise</returns>
|
||||
public static bool FormatCompatible(TextureInfo lhs, TextureInfo rhs, Capabilities caps)
|
||||
{
|
||||
FormatInfo lhsFormat = lhs.FormatInfo;
|
||||
FormatInfo rhsFormat = rhs.FormatInfo;
|
||||
|
||||
if (lhsFormat.Format.IsDepthOrStencil() || rhsFormat.Format.IsDepthOrStencil())
|
||||
{
|
||||
return lhsFormat.Format == rhsFormat.Format;
|
||||
}
|
||||
|
||||
if (IsFormatHostIncompatible(lhs, caps) || IsFormatHostIncompatible(rhs, caps))
|
||||
{
|
||||
return lhsFormat.Format == rhsFormat.Format;
|
||||
}
|
||||
|
||||
if (lhsFormat.IsCompressed && rhsFormat.IsCompressed)
|
||||
{
|
||||
FormatClass lhsClass = GetFormatClass(lhsFormat.Format);
|
||||
FormatClass rhsClass = GetFormatClass(rhsFormat.Format);
|
||||
|
||||
return lhsClass == rhsClass;
|
||||
}
|
||||
else
|
||||
{
|
||||
return lhsFormat.BytesPerPixel == rhsFormat.BytesPerPixel;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the texture format matches with the specified texture information.
|
||||
/// </summary>
|
||||
@ -380,42 +345,44 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// </summary>
|
||||
/// <param name="lhs">Texture information of the texture view</param>
|
||||
/// <param name="rhs">Texture information of the texture view to match against</param>
|
||||
/// <param name="exact">Indicates if the sizes must be exactly equal</param>
|
||||
/// <param name="level">Mipmap level of the texture view in relation to this texture</param>
|
||||
/// <returns>The view compatibility level of the view sizes</returns>
|
||||
public static TextureViewCompatibility ViewSizeMatches(TextureInfo lhs, TextureInfo rhs, int level)
|
||||
public static TextureViewCompatibility ViewSizeMatches(TextureInfo lhs, TextureInfo rhs, bool exact, int level)
|
||||
{
|
||||
Size size = GetAlignedSize(lhs, level);
|
||||
Size lhsAlignedSize = GetAlignedSize(lhs, level);
|
||||
Size rhsAlignedSize = GetAlignedSize(rhs);
|
||||
|
||||
Size otherSize = GetAlignedSize(rhs);
|
||||
Size lhsSize = GetSizeInBlocks(lhs, level);
|
||||
Size rhsSize = GetSizeInBlocks(rhs);
|
||||
|
||||
bool alignedWidthMatches = lhsAlignedSize.Width == rhsAlignedSize.Width;
|
||||
|
||||
if (lhs.FormatInfo.BytesPerPixel != rhs.FormatInfo.BytesPerPixel && IsIncompatibleFormatAliasingAllowed(lhs.FormatInfo, rhs.FormatInfo))
|
||||
{
|
||||
alignedWidthMatches = lhsSize.Width * lhs.FormatInfo.BytesPerPixel == rhsSize.Width * rhs.FormatInfo.BytesPerPixel;
|
||||
}
|
||||
|
||||
TextureViewCompatibility result = TextureViewCompatibility.Full;
|
||||
|
||||
// For copies, we can copy a subset of the 3D texture slices,
|
||||
// so the depth may be different in this case.
|
||||
if (rhs.Target == Target.Texture3D && size.Depth != otherSize.Depth)
|
||||
if (rhs.Target == Target.Texture3D && lhsSize.Depth != rhsSize.Depth)
|
||||
{
|
||||
result = TextureViewCompatibility.CopyOnly;
|
||||
}
|
||||
|
||||
if (size.Width == otherSize.Width && size.Height == otherSize.Height)
|
||||
// Some APIs align the width for copy and render target textures,
|
||||
// so the width may not match in this case for different uses of the same texture.
|
||||
// To account for this, we compare the aligned width here.
|
||||
// We expect height to always match exactly, if the texture is the same.
|
||||
if (alignedWidthMatches && lhsSize.Height == rhsSize.Height)
|
||||
{
|
||||
if (level > 0 && result == TextureViewCompatibility.Full)
|
||||
{
|
||||
// A resize should not change the aligned size of the largest mip.
|
||||
// If it would, then create a copy dependency rather than a full view.
|
||||
|
||||
Size mip0SizeLhs = GetAlignedSize(lhs);
|
||||
Size mip0SizeRhs = GetLargestAlignedSize(rhs, level);
|
||||
|
||||
if (mip0SizeLhs.Width != mip0SizeRhs.Width || mip0SizeLhs.Height != mip0SizeRhs.Height)
|
||||
{
|
||||
result = TextureViewCompatibility.CopyOnly;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
return (exact && lhsSize.Width != rhsSize.Width) || lhsSize.Width < rhsSize.Width
|
||||
? TextureViewCompatibility.CopyOnly
|
||||
: result;
|
||||
}
|
||||
else if (lhs.IsLinear && rhs.IsLinear)
|
||||
else if (lhs.IsLinear && rhs.IsLinear && lhsSize.Height == rhsSize.Height)
|
||||
{
|
||||
// Copy between linear textures with matching stride.
|
||||
int stride = BitUtils.AlignUp(Math.Max(1, lhs.Stride >> level), Constants.StrideAlignment);
|
||||
@ -454,57 +421,33 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// </summary>
|
||||
/// <param name="lhs">Texture information to compare</param>
|
||||
/// <param name="rhs">Texture information to compare with</param>
|
||||
/// <returns>True if the size matches, false otherwise</returns>
|
||||
public static bool SizeMatches(TextureInfo lhs, TextureInfo rhs)
|
||||
{
|
||||
return SizeMatches(lhs, rhs, alignSizes: false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the texture sizes of the supplied texture informations match the given level
|
||||
/// </summary>
|
||||
/// <param name="lhs">Texture information to compare</param>
|
||||
/// <param name="rhs">Texture information to compare with</param>
|
||||
/// <param name="level">Mipmap level of this texture to compare with</param>
|
||||
/// <returns>True if the size matches with the level, false otherwise</returns>
|
||||
public static bool SizeMatches(TextureInfo lhs, TextureInfo rhs, int level)
|
||||
{
|
||||
return Math.Max(1, lhs.Width >> level) == rhs.Width &&
|
||||
Math.Max(1, lhs.Height >> level) == rhs.Height &&
|
||||
Math.Max(1, lhs.GetDepth() >> level) == rhs.GetDepth();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the texture sizes of the supplied texture informations match.
|
||||
/// </summary>
|
||||
/// <param name="lhs">Texture information to compare</param>
|
||||
/// <param name="rhs">Texture information to compare with</param>
|
||||
/// <param name="alignSizes">True to align the sizes according to the texture layout for comparison</param>
|
||||
/// <param name="lhsLevel">Mip level of the lhs texture. Aligned sizes are compared for the largest mip</param>
|
||||
/// <param name="exact">Indicates if the size must be exactly equal between the textures, or if <paramref name="rhs"/> is allowed to be larger</param>
|
||||
/// <returns>True if the sizes matches, false otherwise</returns>
|
||||
public static bool SizeMatches(TextureInfo lhs, TextureInfo rhs, bool alignSizes, int lhsLevel = 0)
|
||||
public static bool SizeMatches(TextureInfo lhs, TextureInfo rhs, bool exact)
|
||||
{
|
||||
if (lhs.GetLayers() != rhs.GetLayers())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool isTextureBuffer = lhs.Target == Target.TextureBuffer || rhs.Target == Target.TextureBuffer;
|
||||
Size lhsSize = GetSizeInBlocks(lhs);
|
||||
Size rhsSize = GetSizeInBlocks(rhs);
|
||||
|
||||
if (alignSizes && !isTextureBuffer)
|
||||
if (exact || lhs.IsLinear || rhs.IsLinear)
|
||||
{
|
||||
Size size0 = GetLargestAlignedSize(lhs, lhsLevel);
|
||||
Size size1 = GetLargestAlignedSize(rhs, lhsLevel);
|
||||
|
||||
return size0.Width == size1.Width &&
|
||||
size0.Height == size1.Height &&
|
||||
size0.Depth == size1.Depth;
|
||||
return lhsSize.Width == rhsSize.Width &&
|
||||
lhsSize.Height == rhsSize.Height &&
|
||||
lhsSize.Depth == rhsSize.Depth;
|
||||
}
|
||||
else
|
||||
{
|
||||
return lhs.Width == rhs.Width &&
|
||||
lhs.Height == rhs.Height &&
|
||||
lhs.GetDepth() == rhs.GetDepth();
|
||||
Size lhsAlignedSize = GetAlignedSize(lhs);
|
||||
Size rhsAlignedSize = GetAlignedSize(rhs);
|
||||
|
||||
return lhsAlignedSize.Width == rhsAlignedSize.Width &&
|
||||
lhsSize.Width >= rhsSize.Width &&
|
||||
lhsSize.Height == rhsSize.Height &&
|
||||
lhsSize.Depth == rhsSize.Depth;
|
||||
}
|
||||
}
|
||||
|
||||
@ -543,22 +486,6 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the aligned sizes of the specified texture information, shifted to the largest mip from a given level.
|
||||
/// The alignment depends on the texture layout and format bytes per pixel.
|
||||
/// </summary>
|
||||
/// <param name="info">Texture information to calculate the aligned size from</param>
|
||||
/// <param name="level">Mipmap level for texture views. Shifts the aligned size to represent the largest mip level</param>
|
||||
/// <returns>The aligned texture size of the largest mip level</returns>
|
||||
public static Size GetLargestAlignedSize(TextureInfo info, int level)
|
||||
{
|
||||
int width = info.Width << level;
|
||||
int height = info.Height << level;
|
||||
int depth = info.GetDepth() << level;
|
||||
|
||||
return GetAlignedSize(info, width, height, depth);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the aligned sizes of the specified texture information.
|
||||
/// The alignment depends on the texture layout and format bytes per pixel.
|
||||
@ -575,6 +502,25 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
return GetAlignedSize(info, width, height, depth);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the size in blocks for the given texture information.
|
||||
/// For non-compressed formats, that's the same as the regular size.
|
||||
/// </summary>
|
||||
/// <param name="info">Texture information to calculate the aligned size from</param>
|
||||
/// <param name="level">Mipmap level for texture views</param>
|
||||
/// <returns>The texture size in blocks</returns>
|
||||
public static Size GetSizeInBlocks(TextureInfo info, int level = 0)
|
||||
{
|
||||
int width = Math.Max(1, info.Width >> level);
|
||||
int height = Math.Max(1, info.Height >> level);
|
||||
int depth = Math.Max(1, info.GetDepth() >> level);
|
||||
|
||||
return new Size(
|
||||
BitUtils.DivRoundUp(width, info.FormatInfo.BlockWidth),
|
||||
BitUtils.DivRoundUp(height, info.FormatInfo.BlockHeight),
|
||||
depth);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if it's possible to create a view with the layout of the second texture information from the first.
|
||||
/// The layout information is composed of the Stride for linear textures, or GOB block size
|
||||
@ -685,21 +631,62 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// <returns>The view compatibility level of the texture formats</returns>
|
||||
public static TextureViewCompatibility ViewFormatCompatible(TextureInfo lhs, TextureInfo rhs, Capabilities caps)
|
||||
{
|
||||
if (FormatCompatible(lhs, rhs, caps))
|
||||
FormatInfo lhsFormat = lhs.FormatInfo;
|
||||
FormatInfo rhsFormat = rhs.FormatInfo;
|
||||
|
||||
if (lhsFormat.Format.IsDepthOrStencil() || rhsFormat.Format.IsDepthOrStencil())
|
||||
{
|
||||
if (lhs.FormatInfo.IsCompressed != rhs.FormatInfo.IsCompressed)
|
||||
{
|
||||
return TextureViewCompatibility.CopyOnly;
|
||||
}
|
||||
else
|
||||
{
|
||||
return TextureViewCompatibility.Full;
|
||||
}
|
||||
return lhsFormat.Format == rhsFormat.Format ? TextureViewCompatibility.Full : TextureViewCompatibility.Incompatible;
|
||||
}
|
||||
|
||||
if (IsFormatHostIncompatible(lhs, caps) || IsFormatHostIncompatible(rhs, caps))
|
||||
{
|
||||
return lhsFormat.Format == rhsFormat.Format ? TextureViewCompatibility.Full : TextureViewCompatibility.Incompatible;
|
||||
}
|
||||
|
||||
if (lhsFormat.IsCompressed && rhsFormat.IsCompressed)
|
||||
{
|
||||
FormatClass lhsClass = GetFormatClass(lhsFormat.Format);
|
||||
FormatClass rhsClass = GetFormatClass(rhsFormat.Format);
|
||||
|
||||
return lhsClass == rhsClass ? TextureViewCompatibility.Full : TextureViewCompatibility.Incompatible;
|
||||
}
|
||||
else if (lhsFormat.BytesPerPixel == rhsFormat.BytesPerPixel)
|
||||
{
|
||||
return lhs.FormatInfo.IsCompressed == rhs.FormatInfo.IsCompressed
|
||||
? TextureViewCompatibility.Full
|
||||
: TextureViewCompatibility.CopyOnly;
|
||||
}
|
||||
else if (IsIncompatibleFormatAliasingAllowed(lhsFormat, rhsFormat))
|
||||
{
|
||||
return TextureViewCompatibility.CopyOnly;
|
||||
}
|
||||
|
||||
return TextureViewCompatibility.Incompatible;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if aliasing of two formats that would normally be considered incompatible be allowed,
|
||||
/// using copy dependencies.
|
||||
/// </summary>
|
||||
/// <param name="lhsFormat">Format information of the first texture</param
|
||||
/// <param name="rhsFormat">Format information of the second texture</param>
|
||||
/// <returns>True if aliasing should be allowed, false otherwise</returns>
|
||||
private static bool IsIncompatibleFormatAliasingAllowed(FormatInfo lhsFormat, FormatInfo rhsFormat)
|
||||
{
|
||||
// Some games will try to alias textures with incompatible foramts, with different BPP (bytes per pixel).
|
||||
// We allow that in some cases as long Width * BPP is equal on both textures.
|
||||
// This is very conservative right now as we want to avoid copies as much as possible,
|
||||
// so we only consider the formats we have seen being aliased.
|
||||
|
||||
if (rhsFormat.BytesPerPixel < lhsFormat.BytesPerPixel)
|
||||
{
|
||||
(lhsFormat, rhsFormat) = (rhsFormat, lhsFormat);
|
||||
}
|
||||
|
||||
return lhsFormat.Format == Format.R8Unorm && rhsFormat.Format == Format.R8G8B8A8Unorm;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the target of the first texture view information is compatible with the target of the second texture view information.
|
||||
/// This follows the host API target compatibility rules.
|
||||
|
@ -336,24 +336,23 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
if (_loadNeeded[baseHandle + i])
|
||||
{
|
||||
var info = GetHandleInformation(baseHandle + i);
|
||||
int offsetIndex = info.Index;
|
||||
|
||||
// Only one of these will be greater than 1, as partial sync is only called when there are sub-image views.
|
||||
for (int layer = 0; layer < info.Layers; layer++)
|
||||
{
|
||||
for (int level = 0; level < info.Levels; level++)
|
||||
{
|
||||
int offsetIndex = GetOffsetIndex(info.BaseLayer + layer, info.BaseLevel + level);
|
||||
|
||||
int offset = _allOffsets[offsetIndex];
|
||||
int endOffset = Math.Min(offset + _sliceSizes[info.BaseLevel + level], (int)Storage.Size);
|
||||
int size = endOffset - offset;
|
||||
|
||||
ReadOnlySpan<byte> data = _physicalMemory.GetSpan(Storage.Range.GetSlice((ulong)offset, (ulong)size));
|
||||
|
||||
SpanOrArray<byte> result = Storage.ConvertToHostCompatibleFormat(data, info.BaseLevel, true);
|
||||
SpanOrArray<byte> result = Storage.ConvertToHostCompatibleFormat(data, info.BaseLevel + level, true);
|
||||
|
||||
Storage.SetData(result, info.BaseLayer, info.BaseLevel);
|
||||
|
||||
offsetIndex++;
|
||||
Storage.SetData(result, info.BaseLayer + layer, info.BaseLevel + level);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -434,32 +433,6 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a texture was modified by the GPU.
|
||||
/// </summary>
|
||||
/// <param name="texture">The texture to be checked</param>
|
||||
/// <returns>True if any region of the texture was modified by the GPU, false otherwise</returns>
|
||||
public bool AnyModified(Texture texture)
|
||||
{
|
||||
bool anyModified = false;
|
||||
|
||||
EvaluateRelevantHandles(texture, (baseHandle, regionCount, split) =>
|
||||
{
|
||||
for (int i = 0; i < regionCount; i++)
|
||||
{
|
||||
TextureGroupHandle group = _handles[baseHandle + i];
|
||||
|
||||
if (group.Modified)
|
||||
{
|
||||
anyModified = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return anyModified;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Flush modified ranges for a given texture.
|
||||
/// </summary>
|
||||
@ -881,7 +854,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// <returns>A CpuRegionHandle covering the given range</returns>
|
||||
private CpuRegionHandle GenerateHandle(ulong address, ulong size)
|
||||
{
|
||||
return _physicalMemory.BeginTracking(address, size);
|
||||
return _physicalMemory.BeginTracking(address, size, ResourceKind.Texture);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -1,5 +1,7 @@
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Texture;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
@ -292,5 +294,88 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
layerSize);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates texture information for a given mipmap level of the specified parent texture and this information.
|
||||
/// </summary>
|
||||
/// <param name="parent">The parent texture</param>
|
||||
/// <param name="firstLevel">The first level of the texture view</param>
|
||||
/// <returns>The adjusted texture information with the new size</returns>
|
||||
public TextureInfo CreateInfoForLevelView(Texture parent, int firstLevel)
|
||||
{
|
||||
// When the texture is used as view of another texture, we must
|
||||
// ensure that the sizes are valid, otherwise data uploads would fail
|
||||
// (and the size wouldn't match the real size used on the host API).
|
||||
// Given a parent texture from where the view is created, we have the
|
||||
// following rules:
|
||||
// - The view size must be equal to the parent size, divided by (2 ^ l),
|
||||
// where l is the first mipmap level of the view. The division result must
|
||||
// be rounded down, and the result must be clamped to 1.
|
||||
// - If the parent format is compressed, and the view format isn't, the
|
||||
// view size is calculated as above, but the width and height of the
|
||||
// view must be also divided by the compressed format block width and height.
|
||||
// - If the parent format is not compressed, and the view is, the view
|
||||
// size is calculated as described on the first point, but the width and height
|
||||
// of the view must be also multiplied by the block width and height.
|
||||
int width = Math.Max(1, parent.Info.Width >> firstLevel);
|
||||
int height = Math.Max(1, parent.Info.Height >> firstLevel);
|
||||
|
||||
if (parent.Info.FormatInfo.IsCompressed && !FormatInfo.IsCompressed)
|
||||
{
|
||||
width = BitUtils.DivRoundUp(width, parent.Info.FormatInfo.BlockWidth);
|
||||
height = BitUtils.DivRoundUp(height, parent.Info.FormatInfo.BlockHeight);
|
||||
}
|
||||
else if (!parent.Info.FormatInfo.IsCompressed && FormatInfo.IsCompressed)
|
||||
{
|
||||
width *= FormatInfo.BlockWidth;
|
||||
height *= FormatInfo.BlockHeight;
|
||||
}
|
||||
|
||||
int depthOrLayers;
|
||||
|
||||
if (Target == Target.Texture3D)
|
||||
{
|
||||
depthOrLayers = Math.Max(1, parent.Info.DepthOrLayers >> firstLevel);
|
||||
}
|
||||
else
|
||||
{
|
||||
depthOrLayers = DepthOrLayers;
|
||||
}
|
||||
|
||||
// 2D and 2D multisample textures are not considered compatible.
|
||||
// This specific case is required for copies, where the source texture might be multisample.
|
||||
// In this case, we inherit the parent texture multisample state.
|
||||
Target target = Target;
|
||||
int samplesInX = SamplesInX;
|
||||
int samplesInY = SamplesInY;
|
||||
|
||||
if (target == Target.Texture2D && parent.Target == Target.Texture2DMultisample)
|
||||
{
|
||||
target = Target.Texture2DMultisample;
|
||||
samplesInX = parent.Info.SamplesInX;
|
||||
samplesInY = parent.Info.SamplesInY;
|
||||
}
|
||||
|
||||
return new TextureInfo(
|
||||
GpuAddress,
|
||||
width,
|
||||
height,
|
||||
depthOrLayers,
|
||||
Levels,
|
||||
samplesInX,
|
||||
samplesInY,
|
||||
Stride,
|
||||
IsLinear,
|
||||
GobBlocksInY,
|
||||
GobBlocksInZ,
|
||||
GobBlocksInTileX,
|
||||
target,
|
||||
FormatInfo,
|
||||
DepthStencilMode,
|
||||
SwizzleR,
|
||||
SwizzleG,
|
||||
SwizzleB,
|
||||
SwizzleA);
|
||||
}
|
||||
}
|
||||
}
|
@ -437,22 +437,6 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update host framebuffer attachments based on currently bound render target buffers.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// All attachments other than <paramref name="index"/> will be unbound.
|
||||
/// </remarks>
|
||||
/// <param name="index">Index of the render target color to be updated</param>
|
||||
public void UpdateRenderTarget(int index)
|
||||
{
|
||||
new Span<ITexture>(_rtHostColors).Fill(null);
|
||||
_rtHostColors[index] = _rtColors[index]?.HostTexture;
|
||||
_rtHostDs = null;
|
||||
|
||||
_context.Renderer.Pipeline.SetRenderTargets(_rtHostColors, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update host framebuffer attachments based on currently bound render target buffers.
|
||||
/// </summary>
|
||||
|
@ -77,22 +77,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
}
|
||||
else
|
||||
{
|
||||
if (texture.ChangedSize)
|
||||
{
|
||||
// Texture changed size at one point - it may be a different size than the sampler expects.
|
||||
// This can be triggered when the size is changed by a size hint on copy or draw, but the texture has been sampled before.
|
||||
|
||||
int baseLevel = descriptor.UnpackBaseLevel();
|
||||
int width = Math.Max(1, descriptor.UnpackWidth() >> baseLevel);
|
||||
int height = Math.Max(1, descriptor.UnpackHeight() >> baseLevel);
|
||||
|
||||
if (texture.Info.Width != width || texture.Info.Height != height)
|
||||
{
|
||||
texture.ChangeSize(width, height, texture.Info.DepthOrLayers);
|
||||
}
|
||||
}
|
||||
|
||||
// Memory is automatically synchronized on texture creation.
|
||||
// On the path above (texture not yet in the pool), memory is automatically synchronized on texture creation.
|
||||
texture.SynchronizeMemory();
|
||||
}
|
||||
|
||||
|
@ -9,7 +9,6 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
enum TextureSearchFlags
|
||||
{
|
||||
None = 0,
|
||||
Strict = 1 << 0,
|
||||
ForSampler = 1 << 1,
|
||||
ForCopy = 1 << 2,
|
||||
WithUpscale = 1 << 3,
|
||||
|
@ -105,13 +105,13 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
|
||||
if (_useGranular)
|
||||
{
|
||||
_memoryTrackingGranular = physicalMemory.BeginGranularTracking(address, size, baseHandles);
|
||||
_memoryTrackingGranular = physicalMemory.BeginGranularTracking(address, size, ResourceKind.Buffer, baseHandles);
|
||||
|
||||
_memoryTrackingGranular.RegisterPreciseAction(address, size, PreciseAction);
|
||||
}
|
||||
else
|
||||
{
|
||||
_memoryTracking = physicalMemory.BeginTracking(address, size);
|
||||
_memoryTracking = physicalMemory.BeginTracking(address, size, ResourceKind.Buffer);
|
||||
|
||||
if (baseHandles != null)
|
||||
{
|
||||
|
@ -368,7 +368,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
|
||||
_context.Renderer.Pipeline.ClearBuffer(buffer.Handle, offset, (int)size, value);
|
||||
|
||||
buffer.SignalModified(address, size);
|
||||
memoryManager.Physical.FillTrackedResource(address, size, value, ResourceKind.Buffer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -583,6 +583,38 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
return UnpackPaFromPte(pte) + (va & PageMask);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Translates a GPU virtual address to a CPU virtual address on the first mapped page of memory
|
||||
/// on the specified region.
|
||||
/// If no page is mapped on the specified region, <see cref="PteUnmapped"/> is returned.
|
||||
/// </summary>
|
||||
/// <param name="va">GPU virtual address to be translated</param>
|
||||
/// <param name="size">Size of the range to be translated</param>
|
||||
/// <returns>CPU virtual address, or <see cref="PteUnmapped"/> if unmapped</returns>
|
||||
public ulong TranslateFirstMapped(ulong va, ulong size)
|
||||
{
|
||||
if (!ValidateAddress(va))
|
||||
{
|
||||
return PteUnmapped;
|
||||
}
|
||||
|
||||
ulong endVa = va + size;
|
||||
|
||||
ulong pte = GetPte(va);
|
||||
|
||||
for (; va < endVa && pte == PteUnmapped; va += PageSize - (va & PageMask))
|
||||
{
|
||||
pte = GetPte(va);
|
||||
}
|
||||
|
||||
if (pte == PteUnmapped)
|
||||
{
|
||||
return PteUnmapped;
|
||||
}
|
||||
|
||||
return UnpackPaFromPte(pte) + (va & PageMask);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the kind of a given memory page.
|
||||
/// This might indicate the type of resource that can be allocated on the page, and also texture tiling.
|
||||
|
@ -7,6 +7,7 @@ using Ryujinx.Memory.Range;
|
||||
using Ryujinx.Memory.Tracking;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Memory
|
||||
@ -295,23 +296,41 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fills the specified memory region with a 32-bit integer value.
|
||||
/// </summary>
|
||||
/// <param name="address">CPU virtual address of the region</param>
|
||||
/// <param name="size">Size of the region</param>
|
||||
/// <param name="value">Value to fill the region with</param>
|
||||
/// <param name="kind">Kind of the resource being filled, which will not be signalled as CPU modified</param>
|
||||
public void FillTrackedResource(ulong address, ulong size, uint value, ResourceKind kind)
|
||||
{
|
||||
_cpuMemory.SignalMemoryTracking(address, size, write: true, precise: true, (int)kind);
|
||||
|
||||
using WritableRegion region = _cpuMemory.GetWritableRegion(address, (int)size);
|
||||
|
||||
MemoryMarshal.Cast<byte, uint>(region.Memory.Span).Fill(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Obtains a memory tracking handle for the given virtual region. This should be disposed when finished with.
|
||||
/// </summary>
|
||||
/// <param name="address">CPU virtual address of the region</param>
|
||||
/// <param name="size">Size of the region</param>
|
||||
/// <param name="kind">Kind of the resource being tracked</param>
|
||||
/// <returns>The memory tracking handle</returns>
|
||||
public CpuRegionHandle BeginTracking(ulong address, ulong size)
|
||||
public CpuRegionHandle BeginTracking(ulong address, ulong size, ResourceKind kind)
|
||||
{
|
||||
return _cpuMemory.BeginTracking(address, size);
|
||||
return _cpuMemory.BeginTracking(address, size, (int)kind);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Obtains a memory tracking handle for the given virtual region. This should be disposed when finished with.
|
||||
/// </summary>
|
||||
/// <param name="range">Ranges of physical memory where the data is located</param>
|
||||
/// <param name="kind">Kind of the resource being tracked</param>
|
||||
/// <returns>The memory tracking handle</returns>
|
||||
public GpuRegionHandle BeginTracking(MultiRange range)
|
||||
public GpuRegionHandle BeginTracking(MultiRange range, ResourceKind kind)
|
||||
{
|
||||
var cpuRegionHandles = new CpuRegionHandle[range.Count];
|
||||
int count = 0;
|
||||
@ -321,7 +340,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
var currentRange = range.GetSubRange(i);
|
||||
if (currentRange.Address != MemoryManager.PteUnmapped)
|
||||
{
|
||||
cpuRegionHandles[count++] = _cpuMemory.BeginTracking(currentRange.Address, currentRange.Size);
|
||||
cpuRegionHandles[count++] = _cpuMemory.BeginTracking(currentRange.Address, currentRange.Size, (int)kind);
|
||||
}
|
||||
}
|
||||
|
||||
@ -338,12 +357,13 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// </summary>
|
||||
/// <param name="address">CPU virtual address of the region</param>
|
||||
/// <param name="size">Size of the region</param>
|
||||
/// <param name="kind">Kind of the resource being tracked</param>
|
||||
/// <param name="handles">Handles to inherit state from or reuse</param>
|
||||
/// <param name="granularity">Desired granularity of write tracking</param>
|
||||
/// <returns>The memory tracking handle</returns>
|
||||
public CpuMultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable<IRegionHandle> handles = null, ulong granularity = 4096)
|
||||
public CpuMultiRegionHandle BeginGranularTracking(ulong address, ulong size, ResourceKind kind, IEnumerable<IRegionHandle> handles = null, ulong granularity = 4096)
|
||||
{
|
||||
return _cpuMemory.BeginGranularTracking(address, size, handles, granularity);
|
||||
return _cpuMemory.BeginGranularTracking(address, size, handles, granularity, (int)kind);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -351,11 +371,12 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// </summary>
|
||||
/// <param name="address">CPU virtual address of the region</param>
|
||||
/// <param name="size">Size of the region</param>
|
||||
/// <param name="kind">Kind of the resource being tracked</param>
|
||||
/// <param name="granularity">Desired granularity of write tracking</param>
|
||||
/// <returns>The memory tracking handle</returns>
|
||||
public CpuSmartMultiRegionHandle BeginSmartGranularTracking(ulong address, ulong size, ulong granularity = 4096)
|
||||
public CpuSmartMultiRegionHandle BeginSmartGranularTracking(ulong address, ulong size, ResourceKind kind, ulong granularity = 4096)
|
||||
{
|
||||
return _cpuMemory.BeginSmartGranularTracking(address, size, granularity);
|
||||
return _cpuMemory.BeginSmartGranularTracking(address, size, granularity, (int)kind);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
13
Ryujinx.Graphics.Gpu/Memory/ResourceKind.cs
Normal file
13
Ryujinx.Graphics.Gpu/Memory/ResourceKind.cs
Normal file
@ -0,0 +1,13 @@
|
||||
namespace Ryujinx.Graphics.Gpu.Memory
|
||||
{
|
||||
/// <summary>
|
||||
/// Kind of a GPU resource.
|
||||
/// </summary>
|
||||
enum ResourceKind
|
||||
{
|
||||
None,
|
||||
Buffer,
|
||||
Texture,
|
||||
Pool
|
||||
}
|
||||
}
|
@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
private const ushort FileFormatVersionMajor = 1;
|
||||
private const ushort FileFormatVersionMinor = 2;
|
||||
private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor;
|
||||
private const uint CodeGenVersion = 4336;
|
||||
private const uint CodeGenVersion = 4369;
|
||||
|
||||
private const string SharedTocFileName = "shared.toc";
|
||||
private const string SharedDataFileName = "shared.data";
|
||||
|
@ -202,7 +202,7 @@ namespace Ryujinx.Graphics.Gpu
|
||||
{
|
||||
pt.AcquireCallback(_context, pt.UserObj);
|
||||
|
||||
Texture texture = pt.Cache.FindOrCreateTexture(null, TextureSearchFlags.WithUpscale, pt.Info, 0, null, pt.Range);
|
||||
Texture texture = pt.Cache.FindOrCreateTexture(null, TextureSearchFlags.WithUpscale, pt.Info, 0, pt.Range);
|
||||
|
||||
pt.Cache.Tick();
|
||||
|
||||
|
@ -34,6 +34,126 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
return TextureWrapMode.Clamp;
|
||||
}
|
||||
|
||||
public static NvBlendEquationAdvanced Convert(this AdvancedBlendOp op)
|
||||
{
|
||||
switch (op)
|
||||
{
|
||||
case AdvancedBlendOp.Zero:
|
||||
return NvBlendEquationAdvanced.Zero;
|
||||
case AdvancedBlendOp.Src:
|
||||
return NvBlendEquationAdvanced.SrcNv;
|
||||
case AdvancedBlendOp.Dst:
|
||||
return NvBlendEquationAdvanced.DstNv;
|
||||
case AdvancedBlendOp.SrcOver:
|
||||
return NvBlendEquationAdvanced.SrcOverNv;
|
||||
case AdvancedBlendOp.DstOver:
|
||||
return NvBlendEquationAdvanced.DstOverNv;
|
||||
case AdvancedBlendOp.SrcIn:
|
||||
return NvBlendEquationAdvanced.SrcInNv;
|
||||
case AdvancedBlendOp.DstIn:
|
||||
return NvBlendEquationAdvanced.DstInNv;
|
||||
case AdvancedBlendOp.SrcOut:
|
||||
return NvBlendEquationAdvanced.SrcOutNv;
|
||||
case AdvancedBlendOp.DstOut:
|
||||
return NvBlendEquationAdvanced.DstOutNv;
|
||||
case AdvancedBlendOp.SrcAtop:
|
||||
return NvBlendEquationAdvanced.SrcAtopNv;
|
||||
case AdvancedBlendOp.DstAtop:
|
||||
return NvBlendEquationAdvanced.DstAtopNv;
|
||||
case AdvancedBlendOp.Xor:
|
||||
return NvBlendEquationAdvanced.XorNv;
|
||||
case AdvancedBlendOp.Plus:
|
||||
return NvBlendEquationAdvanced.PlusNv;
|
||||
case AdvancedBlendOp.PlusClamped:
|
||||
return NvBlendEquationAdvanced.PlusClampedNv;
|
||||
case AdvancedBlendOp.PlusClampedAlpha:
|
||||
return NvBlendEquationAdvanced.PlusClampedAlphaNv;
|
||||
case AdvancedBlendOp.PlusDarker:
|
||||
return NvBlendEquationAdvanced.PlusDarkerNv;
|
||||
case AdvancedBlendOp.Multiply:
|
||||
return NvBlendEquationAdvanced.MultiplyNv;
|
||||
case AdvancedBlendOp.Screen:
|
||||
return NvBlendEquationAdvanced.ScreenNv;
|
||||
case AdvancedBlendOp.Overlay:
|
||||
return NvBlendEquationAdvanced.OverlayNv;
|
||||
case AdvancedBlendOp.Darken:
|
||||
return NvBlendEquationAdvanced.DarkenNv;
|
||||
case AdvancedBlendOp.Lighten:
|
||||
return NvBlendEquationAdvanced.LightenNv;
|
||||
case AdvancedBlendOp.ColorDodge:
|
||||
return NvBlendEquationAdvanced.ColordodgeNv;
|
||||
case AdvancedBlendOp.ColorBurn:
|
||||
return NvBlendEquationAdvanced.ColorburnNv;
|
||||
case AdvancedBlendOp.HardLight:
|
||||
return NvBlendEquationAdvanced.HardlightNv;
|
||||
case AdvancedBlendOp.SoftLight:
|
||||
return NvBlendEquationAdvanced.SoftlightNv;
|
||||
case AdvancedBlendOp.Difference:
|
||||
return NvBlendEquationAdvanced.DifferenceNv;
|
||||
case AdvancedBlendOp.Minus:
|
||||
return NvBlendEquationAdvanced.MinusNv;
|
||||
case AdvancedBlendOp.MinusClamped:
|
||||
return NvBlendEquationAdvanced.MinusClampedNv;
|
||||
case AdvancedBlendOp.Exclusion:
|
||||
return NvBlendEquationAdvanced.ExclusionNv;
|
||||
case AdvancedBlendOp.Contrast:
|
||||
return NvBlendEquationAdvanced.ContrastNv;
|
||||
case AdvancedBlendOp.Invert:
|
||||
return NvBlendEquationAdvanced.Invert;
|
||||
case AdvancedBlendOp.InvertRGB:
|
||||
return NvBlendEquationAdvanced.InvertRgbNv;
|
||||
case AdvancedBlendOp.InvertOvg:
|
||||
return NvBlendEquationAdvanced.InvertOvgNv;
|
||||
case AdvancedBlendOp.LinearDodge:
|
||||
return NvBlendEquationAdvanced.LineardodgeNv;
|
||||
case AdvancedBlendOp.LinearBurn:
|
||||
return NvBlendEquationAdvanced.LinearburnNv;
|
||||
case AdvancedBlendOp.VividLight:
|
||||
return NvBlendEquationAdvanced.VividlightNv;
|
||||
case AdvancedBlendOp.LinearLight:
|
||||
return NvBlendEquationAdvanced.LinearlightNv;
|
||||
case AdvancedBlendOp.PinLight:
|
||||
return NvBlendEquationAdvanced.PinlightNv;
|
||||
case AdvancedBlendOp.HardMix:
|
||||
return NvBlendEquationAdvanced.HardmixNv;
|
||||
case AdvancedBlendOp.Red:
|
||||
return NvBlendEquationAdvanced.RedNv;
|
||||
case AdvancedBlendOp.Green:
|
||||
return NvBlendEquationAdvanced.GreenNv;
|
||||
case AdvancedBlendOp.Blue:
|
||||
return NvBlendEquationAdvanced.BlueNv;
|
||||
case AdvancedBlendOp.HslHue:
|
||||
return NvBlendEquationAdvanced.HslHueNv;
|
||||
case AdvancedBlendOp.HslSaturation:
|
||||
return NvBlendEquationAdvanced.HslSaturationNv;
|
||||
case AdvancedBlendOp.HslColor:
|
||||
return NvBlendEquationAdvanced.HslColorNv;
|
||||
case AdvancedBlendOp.HslLuminosity:
|
||||
return NvBlendEquationAdvanced.HslLuminosityNv;
|
||||
}
|
||||
|
||||
Logger.Debug?.Print(LogClass.Gpu, $"Invalid {nameof(AdvancedBlendOp)} enum value: {op}.");
|
||||
|
||||
return NvBlendEquationAdvanced.Zero;
|
||||
}
|
||||
|
||||
public static All Convert(this AdvancedBlendOverlap overlap)
|
||||
{
|
||||
switch (overlap)
|
||||
{
|
||||
case AdvancedBlendOverlap.Uncorrelated:
|
||||
return All.UncorrelatedNv;
|
||||
case AdvancedBlendOverlap.Disjoint:
|
||||
return All.DisjointNv;
|
||||
case AdvancedBlendOverlap.Conjoint:
|
||||
return All.ConjointNv;
|
||||
}
|
||||
|
||||
Logger.Debug?.Print(LogClass.Gpu, $"Invalid {nameof(AdvancedBlendOverlap)} enum value: {overlap}.");
|
||||
|
||||
return All.UncorrelatedNv;
|
||||
}
|
||||
|
||||
public static All Convert(this BlendFactor factor)
|
||||
{
|
||||
switch (factor)
|
||||
|
@ -7,6 +7,7 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
{
|
||||
private static readonly Lazy<bool> _supportsAlphaToCoverageDitherControl = new Lazy<bool>(() => HasExtension("GL_NV_alpha_to_coverage_dither_control"));
|
||||
private static readonly Lazy<bool> _supportsAstcCompression = new Lazy<bool>(() => HasExtension("GL_KHR_texture_compression_astc_ldr"));
|
||||
private static readonly Lazy<bool> _supportsBlendEquationAdvanced = new Lazy<bool>(() => HasExtension("GL_NV_blend_equation_advanced"));
|
||||
private static readonly Lazy<bool> _supportsDrawTexture = new Lazy<bool>(() => HasExtension("GL_NV_draw_texture"));
|
||||
private static readonly Lazy<bool> _supportsFragmentShaderInterlock = new Lazy<bool>(() => HasExtension("GL_ARB_fragment_shader_interlock"));
|
||||
private static readonly Lazy<bool> _supportsFragmentShaderOrdering = new Lazy<bool>(() => HasExtension("GL_INTEL_fragment_shader_ordering"));
|
||||
@ -51,6 +52,7 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
|
||||
public static bool SupportsAlphaToCoverageDitherControl => _supportsAlphaToCoverageDitherControl.Value;
|
||||
public static bool SupportsAstcCompression => _supportsAstcCompression.Value;
|
||||
public static bool SupportsBlendEquationAdvanced => _supportsBlendEquationAdvanced.Value;
|
||||
public static bool SupportsDrawTexture => _supportsDrawTexture.Value;
|
||||
public static bool SupportsFragmentShaderInterlock => _supportsFragmentShaderInterlock.Value;
|
||||
public static bool SupportsFragmentShaderOrdering => _supportsFragmentShaderOrdering.Value;
|
||||
|
252
Ryujinx.Graphics.OpenGL/Image/TextureCopyIncompatible.cs
Normal file
252
Ryujinx.Graphics.OpenGL/Image/TextureCopyIncompatible.cs
Normal file
@ -0,0 +1,252 @@
|
||||
using OpenTK.Graphics.OpenGL;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Ryujinx.Graphics.OpenGL.Image
|
||||
{
|
||||
class TextureCopyIncompatible
|
||||
{
|
||||
private const string ComputeShaderShortening = @"#version 450 core
|
||||
|
||||
layout (binding = 0, $SRC_FORMAT$) uniform uimage2D src;
|
||||
layout (binding = 1, $DST_FORMAT$) uniform uimage2D dst;
|
||||
|
||||
layout (local_size_x = 32, local_size_y = 32, local_size_z = 1) in;
|
||||
|
||||
void main()
|
||||
{
|
||||
uvec2 coords = gl_GlobalInvocationID.xy;
|
||||
ivec2 imageSz = imageSize(src);
|
||||
|
||||
if (int(coords.x) >= imageSz.x || int(coords.y) >= imageSz.y)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
uint coordsShifted = coords.x << $RATIO_LOG2$;
|
||||
|
||||
uvec2 dstCoords0 = uvec2(coordsShifted, coords.y);
|
||||
uvec2 dstCoords1 = uvec2(coordsShifted + 1, coords.y);
|
||||
uvec2 dstCoords2 = uvec2(coordsShifted + 2, coords.y);
|
||||
uvec2 dstCoords3 = uvec2(coordsShifted + 3, coords.y);
|
||||
|
||||
uvec4 rgba = imageLoad(src, ivec2(coords));
|
||||
|
||||
imageStore(dst, ivec2(dstCoords0), rgba.rrrr);
|
||||
imageStore(dst, ivec2(dstCoords1), rgba.gggg);
|
||||
imageStore(dst, ivec2(dstCoords2), rgba.bbbb);
|
||||
imageStore(dst, ivec2(dstCoords3), rgba.aaaa);
|
||||
}";
|
||||
|
||||
private const string ComputeShaderWidening = @"#version 450 core
|
||||
|
||||
layout (binding = 0, $SRC_FORMAT$) uniform uimage2D src;
|
||||
layout (binding = 1, $DST_FORMAT$) uniform uimage2D dst;
|
||||
|
||||
layout (local_size_x = 32, local_size_y = 32, local_size_z = 1) in;
|
||||
|
||||
void main()
|
||||
{
|
||||
uvec2 coords = gl_GlobalInvocationID.xy;
|
||||
ivec2 imageSz = imageSize(dst);
|
||||
|
||||
if (int(coords.x) >= imageSz.x || int(coords.y) >= imageSz.y)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
uvec2 srcCoords = uvec2(coords.x << $RATIO_LOG2$, coords.y);
|
||||
|
||||
uint r = imageLoad(src, ivec2(srcCoords) + ivec2(0, 0)).r;
|
||||
uint g = imageLoad(src, ivec2(srcCoords) + ivec2(1, 0)).r;
|
||||
uint b = imageLoad(src, ivec2(srcCoords) + ivec2(2, 0)).r;
|
||||
uint a = imageLoad(src, ivec2(srcCoords) + ivec2(3, 0)).r;
|
||||
|
||||
imageStore(dst, ivec2(coords), uvec4(r, g, b, a));
|
||||
}";
|
||||
|
||||
private readonly OpenGLRenderer _renderer;
|
||||
private readonly Dictionary<int, int> _shorteningProgramHandles;
|
||||
private readonly Dictionary<int, int> _wideningProgramHandles;
|
||||
|
||||
public TextureCopyIncompatible(OpenGLRenderer renderer)
|
||||
{
|
||||
_renderer = renderer;
|
||||
_shorteningProgramHandles = new Dictionary<int, int>();
|
||||
_wideningProgramHandles = new Dictionary<int, int>();
|
||||
}
|
||||
|
||||
public void CopyIncompatibleFormats(ITextureInfo src, ITextureInfo dst, int srcLayer, int dstLayer, int srcLevel, int dstLevel, int depth, int levels)
|
||||
{
|
||||
TextureCreateInfo srcInfo = src.Info;
|
||||
TextureCreateInfo dstInfo = dst.Info;
|
||||
|
||||
int srcBpp = src.Info.BytesPerPixel;
|
||||
int dstBpp = dst.Info.BytesPerPixel;
|
||||
|
||||
// Calculate ideal component size, given our constraints:
|
||||
// - Component size must not exceed bytes per pixel of source and destination image formats.
|
||||
// - Maximum component size is 4 (R32).
|
||||
int componentSize = Math.Min(Math.Min(srcBpp, dstBpp), 4);
|
||||
|
||||
int srcComponentsCount = srcBpp / componentSize;
|
||||
int dstComponentsCount = dstBpp / componentSize;
|
||||
|
||||
var srcFormat = GetFormat(componentSize, srcComponentsCount);
|
||||
var dstFormat = GetFormat(componentSize, dstComponentsCount);
|
||||
|
||||
GL.UseProgram(srcBpp < dstBpp
|
||||
? GetWideningShader(componentSize, srcComponentsCount, dstComponentsCount)
|
||||
: GetShorteningShader(componentSize, srcComponentsCount, dstComponentsCount));
|
||||
|
||||
for (int l = 0; l < levels; l++)
|
||||
{
|
||||
int srcWidth = Math.Max(1, src.Info.Width >> l);
|
||||
int srcHeight = Math.Max(1, src.Info.Height >> l);
|
||||
|
||||
int dstWidth = Math.Max(1, dst.Info.Width >> l);
|
||||
int dstHeight = Math.Max(1, dst.Info.Height >> l);
|
||||
|
||||
int width = Math.Min(srcWidth, dstWidth);
|
||||
int height = Math.Min(srcHeight, dstHeight);
|
||||
|
||||
for (int z = 0; z < depth; z++)
|
||||
{
|
||||
GL.BindImageTexture(0, src.Handle, srcLevel + l, false, srcLayer + z, TextureAccess.ReadOnly, srcFormat);
|
||||
GL.BindImageTexture(1, dst.Handle, dstLevel + l, false, dstLayer + z, TextureAccess.WriteOnly, dstFormat);
|
||||
|
||||
GL.DispatchCompute((width + 31) / 32, (height + 31) / 32, 1);
|
||||
}
|
||||
}
|
||||
|
||||
Pipeline pipeline = (Pipeline)_renderer.Pipeline;
|
||||
|
||||
pipeline.RestoreProgram();
|
||||
pipeline.RestoreImages1And2();
|
||||
}
|
||||
|
||||
private static SizedInternalFormat GetFormat(int componentSize, int componentsCount)
|
||||
{
|
||||
if (componentSize == 1)
|
||||
{
|
||||
return componentsCount switch
|
||||
{
|
||||
1 => SizedInternalFormat.R8ui,
|
||||
2 => SizedInternalFormat.Rg8ui,
|
||||
4 => SizedInternalFormat.Rgba8ui,
|
||||
_ => throw new ArgumentException($"Invalid components count {componentsCount}.")
|
||||
};
|
||||
}
|
||||
else if (componentSize == 2)
|
||||
{
|
||||
return componentsCount switch
|
||||
{
|
||||
1 => SizedInternalFormat.R16ui,
|
||||
2 => SizedInternalFormat.Rg16ui,
|
||||
4 => SizedInternalFormat.Rgba16ui,
|
||||
_ => throw new ArgumentException($"Invalid components count {componentsCount}.")
|
||||
};
|
||||
}
|
||||
else if (componentSize == 4)
|
||||
{
|
||||
return componentsCount switch
|
||||
{
|
||||
1 => SizedInternalFormat.R32ui,
|
||||
2 => SizedInternalFormat.Rg32ui,
|
||||
4 => SizedInternalFormat.Rgba32ui,
|
||||
_ => throw new ArgumentException($"Invalid components count {componentsCount}.")
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException($"Invalid component size {componentSize}.");
|
||||
}
|
||||
}
|
||||
|
||||
private int GetShorteningShader(int componentSize, int srcComponentsCount, int dstComponentsCount)
|
||||
{
|
||||
return GetShader(ComputeShaderShortening, _shorteningProgramHandles, componentSize, srcComponentsCount, dstComponentsCount);
|
||||
}
|
||||
|
||||
private int GetWideningShader(int componentSize, int srcComponentsCount, int dstComponentsCount)
|
||||
{
|
||||
return GetShader(ComputeShaderWidening, _wideningProgramHandles, componentSize, srcComponentsCount, dstComponentsCount);
|
||||
}
|
||||
|
||||
private int GetShader(
|
||||
string code,
|
||||
Dictionary<int, int> programHandles,
|
||||
int componentSize,
|
||||
int srcComponentsCount,
|
||||
int dstComponentsCount)
|
||||
{
|
||||
int componentSizeLog2 = BitOperations.Log2((uint)componentSize);
|
||||
|
||||
int srcIndex = componentSizeLog2 + BitOperations.Log2((uint)srcComponentsCount) * 3;
|
||||
int dstIndex = componentSizeLog2 + BitOperations.Log2((uint)dstComponentsCount) * 3;
|
||||
|
||||
int key = srcIndex | (dstIndex << 8);
|
||||
|
||||
if (!programHandles.TryGetValue(key, out int programHandle))
|
||||
{
|
||||
int csHandle = GL.CreateShader(ShaderType.ComputeShader);
|
||||
|
||||
string[] formatTable = new[] { "r8ui", "r16ui", "r32ui", "rg8ui", "rg16ui", "rg32ui", "rgba8ui", "rgba16ui", "rgba32ui" };
|
||||
|
||||
string srcFormat = formatTable[srcIndex];
|
||||
string dstFormat = formatTable[dstIndex];
|
||||
|
||||
int srcBpp = srcComponentsCount * componentSize;
|
||||
int dstBpp = dstComponentsCount * componentSize;
|
||||
|
||||
int ratio = srcBpp < dstBpp ? dstBpp / srcBpp : srcBpp / dstBpp;
|
||||
int ratioLog2 = BitOperations.Log2((uint)ratio);
|
||||
|
||||
GL.ShaderSource(csHandle, code
|
||||
.Replace("$SRC_FORMAT$", srcFormat)
|
||||
.Replace("$DST_FORMAT$", dstFormat)
|
||||
.Replace("$RATIO_LOG2$", ratioLog2.ToString(CultureInfo.InvariantCulture)));
|
||||
|
||||
GL.CompileShader(csHandle);
|
||||
|
||||
programHandle = GL.CreateProgram();
|
||||
|
||||
GL.AttachShader(programHandle, csHandle);
|
||||
GL.LinkProgram(programHandle);
|
||||
GL.DetachShader(programHandle, csHandle);
|
||||
GL.DeleteShader(csHandle);
|
||||
|
||||
GL.GetProgram(programHandle, GetProgramParameterName.LinkStatus, out int status);
|
||||
|
||||
if (status == 0)
|
||||
{
|
||||
throw new Exception(GL.GetProgramInfoLog(programHandle));
|
||||
}
|
||||
|
||||
programHandles.Add(key, programHandle);
|
||||
}
|
||||
|
||||
return programHandle;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (int handle in _shorteningProgramHandles.Values)
|
||||
{
|
||||
GL.DeleteProgram(handle);
|
||||
}
|
||||
|
||||
_shorteningProgramHandles.Clear();
|
||||
|
||||
foreach (int handle in _wideningProgramHandles.Values)
|
||||
{
|
||||
GL.DeleteProgram(handle);
|
||||
}
|
||||
|
||||
_wideningProgramHandles.Clear();
|
||||
}
|
||||
}
|
||||
}
|
@ -127,6 +127,12 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
||||
int layers = Math.Min(Info.GetLayers(), destinationView.Info.GetLayers() - firstLayer);
|
||||
_renderer.TextureCopyMS.CopyNonMSToMS(this, destinationView, 0, firstLayer, layers);
|
||||
}
|
||||
else if (destinationView.Info.BytesPerPixel != Info.BytesPerPixel)
|
||||
{
|
||||
int layers = Math.Min(Info.GetLayers(), destinationView.Info.GetLayers() - firstLayer);
|
||||
int levels = Math.Min(Info.Levels, destinationView.Info.Levels - firstLevel);
|
||||
_renderer.TextureCopyIncompatible.CopyIncompatibleFormats(this, destinationView, 0, firstLayer, 0, firstLevel, layers, levels);
|
||||
}
|
||||
else
|
||||
{
|
||||
_renderer.TextureCopy.CopyUnscaled(this, destinationView, 0, firstLayer, 0, firstLevel);
|
||||
@ -145,6 +151,10 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
||||
{
|
||||
_renderer.TextureCopyMS.CopyNonMSToMS(this, destinationView, srcLayer, dstLayer, 1);
|
||||
}
|
||||
else if (destinationView.Info.BytesPerPixel != Info.BytesPerPixel)
|
||||
{
|
||||
_renderer.TextureCopyIncompatible.CopyIncompatibleFormats(this, destinationView, srcLayer, dstLayer, srcLevel, dstLevel, 1, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
_renderer.TextureCopy.CopyUnscaled(this, destinationView, srcLayer, dstLayer, srcLevel, dstLevel, 1, 1);
|
||||
|
@ -24,6 +24,7 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
private TextureCopy _textureCopy;
|
||||
private TextureCopy _backgroundTextureCopy;
|
||||
internal TextureCopy TextureCopy => BackgroundContextWorker.InBackground ? _backgroundTextureCopy : _textureCopy;
|
||||
internal TextureCopyIncompatible TextureCopyIncompatible { get; }
|
||||
internal TextureCopyMS TextureCopyMS { get; }
|
||||
|
||||
private Sync _sync;
|
||||
@ -49,6 +50,7 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
_window = new Window(this);
|
||||
_textureCopy = new TextureCopy(this);
|
||||
_backgroundTextureCopy = new TextureCopy(this);
|
||||
TextureCopyIncompatible = new TextureCopyIncompatible(this);
|
||||
TextureCopyMS = new TextureCopyMS(this);
|
||||
_sync = new Sync();
|
||||
PersistentBuffers = new PersistentBuffers();
|
||||
@ -119,6 +121,7 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
supportsR4G4B4A4Format: true,
|
||||
supportsSnormBufferTextureFormat: false,
|
||||
supports5BitComponentFormat: true,
|
||||
supportsBlendEquationAdvanced: HwCapabilities.SupportsBlendEquationAdvanced,
|
||||
supportsFragmentShaderInterlock: HwCapabilities.SupportsFragmentShaderInterlock,
|
||||
supportsFragmentShaderOrderingIntel: HwCapabilities.SupportsFragmentShaderOrdering,
|
||||
supportsGeometryShaderPassthrough: HwCapabilities.SupportsGeometryShaderPassthrough,
|
||||
|
@ -59,6 +59,7 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
private uint _fragmentOutputMap;
|
||||
private uint _componentMasks;
|
||||
private uint _currentComponentMasks;
|
||||
private bool _advancedBlendEnable;
|
||||
|
||||
private uint _scissorEnables;
|
||||
|
||||
@ -784,8 +785,26 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
GL.Enable(EnableCap.AlphaTest);
|
||||
}
|
||||
|
||||
public void SetBlendState(AdvancedBlendDescriptor blend)
|
||||
{
|
||||
if (HwCapabilities.SupportsBlendEquationAdvanced)
|
||||
{
|
||||
GL.BlendEquation((BlendEquationMode)blend.Op.Convert());
|
||||
GL.NV.BlendParameter(NvBlendEquationAdvanced.BlendOverlapNv, (int)blend.Overlap.Convert());
|
||||
GL.NV.BlendParameter(NvBlendEquationAdvanced.BlendPremultipliedSrcNv, blend.SrcPreMultiplied ? 1 : 0);
|
||||
GL.Enable(EnableCap.Blend);
|
||||
_advancedBlendEnable = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetBlendState(int index, BlendDescriptor blend)
|
||||
{
|
||||
if (_advancedBlendEnable)
|
||||
{
|
||||
GL.Disable(EnableCap.Blend);
|
||||
_advancedBlendEnable = false;
|
||||
}
|
||||
|
||||
if (!blend.Enable)
|
||||
{
|
||||
GL.Disable(IndexedEnableCap.Blend, index);
|
||||
|
@ -10,6 +10,8 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
{
|
||||
class Program : IProgram
|
||||
{
|
||||
private const int MaxShaderLogLength = 2048;
|
||||
|
||||
public int Handle { get; private set; }
|
||||
|
||||
public bool IsLinked
|
||||
@ -115,9 +117,16 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
|
||||
if (status == 0)
|
||||
{
|
||||
// Use GL.GetProgramInfoLog(Handle), it may be too long to print on the log.
|
||||
_status = ProgramLinkStatus.Failure;
|
||||
Logger.Debug?.Print(LogClass.Gpu, "Shader linking failed.");
|
||||
|
||||
string log = GL.GetProgramInfoLog(Handle);
|
||||
|
||||
if (log.Length > MaxShaderLogLength)
|
||||
{
|
||||
log = log.Substring(0, MaxShaderLogLength) + "...";
|
||||
}
|
||||
|
||||
Logger.Warning?.Print(LogClass.Gpu, $"Shader linking failed: \n{log}");
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -485,6 +485,16 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
||||
|
||||
AttributeType type = context.Config.GpuAccessor.QueryAttributeType(location);
|
||||
|
||||
return type.ToAggregateType();
|
||||
}
|
||||
else if (context.Config.Stage == ShaderStage.Fragment && isAsgDest &&
|
||||
operand.Value >= AttributeConsts.FragmentOutputColorBase &&
|
||||
operand.Value < AttributeConsts.FragmentOutputColorEnd)
|
||||
{
|
||||
int location = (operand.Value - AttributeConsts.FragmentOutputColorBase) / 16;
|
||||
|
||||
AttributeType type = context.Config.GpuAccessor.QueryFragmentOutputType(location);
|
||||
|
||||
return type.ToAggregateType();
|
||||
}
|
||||
}
|
||||
|
@ -397,6 +397,31 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
private static void DeclareInputAttributes(CodeGenContext context, StructuredProgramInfo info, bool perPatch)
|
||||
{
|
||||
bool iaIndexing = context.Config.UsedFeatures.HasFlag(FeatureFlags.IaIndexing);
|
||||
|
||||
if (iaIndexing && !perPatch)
|
||||
{
|
||||
var attrType = context.TypeVector(context.TypeFP32(), (LiteralInteger)4);
|
||||
attrType = context.TypeArray(attrType, context.Constant(context.TypeU32(), (LiteralInteger)MaxAttributes));
|
||||
|
||||
if (context.Config.Stage == ShaderStage.Geometry)
|
||||
{
|
||||
attrType = context.TypeArray(attrType, context.Constant(context.TypeU32(), (LiteralInteger)context.InputVertices));
|
||||
}
|
||||
|
||||
var spvType = context.TypePointer(StorageClass.Input, attrType);
|
||||
var spvVar = context.Variable(spvType, StorageClass.Input);
|
||||
|
||||
if (context.Config.PassthroughAttributes != 0 && context.Config.GpuAccessor.QueryHostSupportsGeometryShaderPassthrough())
|
||||
{
|
||||
context.Decorate(spvVar, Decoration.PassthroughNV);
|
||||
}
|
||||
|
||||
context.Decorate(spvVar, Decoration.Location, (LiteralInteger)0);
|
||||
|
||||
context.AddGlobalVariable(spvVar);
|
||||
context.InputsArray = spvVar;
|
||||
}
|
||||
|
||||
var inputs = perPatch ? info.InputsPerPatch : info.Inputs;
|
||||
|
||||
foreach (int attr in inputs)
|
||||
@ -410,60 +435,56 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
|
||||
if (iaIndexing && isUserAttr && !perPatch)
|
||||
{
|
||||
if (context.InputsArray == null)
|
||||
{
|
||||
var attrType = context.TypeVector(context.TypeFP32(), (LiteralInteger)4);
|
||||
attrType = context.TypeArray(attrType, context.Constant(context.TypeU32(), (LiteralInteger)MaxAttributes));
|
||||
|
||||
if (context.Config.Stage == ShaderStage.Geometry)
|
||||
{
|
||||
attrType = context.TypeArray(attrType, context.Constant(context.TypeU32(), (LiteralInteger)context.InputVertices));
|
||||
}
|
||||
|
||||
var spvType = context.TypePointer(StorageClass.Input, attrType);
|
||||
var spvVar = context.Variable(spvType, StorageClass.Input);
|
||||
|
||||
if (context.Config.PassthroughAttributes != 0 && context.Config.GpuAccessor.QueryHostSupportsGeometryShaderPassthrough())
|
||||
{
|
||||
context.Decorate(spvVar, Decoration.PassthroughNV);
|
||||
}
|
||||
|
||||
context.Decorate(spvVar, Decoration.Location, (LiteralInteger)0);
|
||||
|
||||
context.AddGlobalVariable(spvVar);
|
||||
context.InputsArray = spvVar;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
else
|
||||
|
||||
PixelImap iq = PixelImap.Unused;
|
||||
|
||||
if (context.Config.Stage == ShaderStage.Fragment)
|
||||
{
|
||||
PixelImap iq = PixelImap.Unused;
|
||||
|
||||
if (context.Config.Stage == ShaderStage.Fragment)
|
||||
if (attr >= AttributeConsts.UserAttributeBase && attr < AttributeConsts.UserAttributeEnd)
|
||||
{
|
||||
if (attr >= AttributeConsts.UserAttributeBase && attr < AttributeConsts.UserAttributeEnd)
|
||||
{
|
||||
iq = context.Config.ImapTypes[(attr - AttributeConsts.UserAttributeBase) / 16].GetFirstUsedType();
|
||||
}
|
||||
else
|
||||
{
|
||||
AttributeInfo attrInfo = AttributeInfo.From(context.Config, attr, isOutAttr: false);
|
||||
AggregateType elemType = attrInfo.Type & AggregateType.ElementTypeMask;
|
||||
iq = context.Config.ImapTypes[(attr - AttributeConsts.UserAttributeBase) / 16].GetFirstUsedType();
|
||||
}
|
||||
else
|
||||
{
|
||||
AttributeInfo attrInfo = AttributeInfo.From(context.Config, attr, isOutAttr: false);
|
||||
AggregateType elemType = attrInfo.Type & AggregateType.ElementTypeMask;
|
||||
|
||||
if (attrInfo.IsBuiltin && (elemType == AggregateType.S32 || elemType == AggregateType.U32))
|
||||
{
|
||||
iq = PixelImap.Constant;
|
||||
}
|
||||
if (attrInfo.IsBuiltin && (elemType == AggregateType.S32 || elemType == AggregateType.U32))
|
||||
{
|
||||
iq = PixelImap.Constant;
|
||||
}
|
||||
}
|
||||
|
||||
DeclareInputOrOutput(context, attr, perPatch, isOutAttr: false, iq);
|
||||
}
|
||||
|
||||
DeclareInputOrOutput(context, attr, perPatch, isOutAttr: false, iq);
|
||||
}
|
||||
}
|
||||
|
||||
private static void DeclareOutputAttributes(CodeGenContext context, StructuredProgramInfo info, bool perPatch)
|
||||
{
|
||||
bool oaIndexing = context.Config.UsedFeatures.HasFlag(FeatureFlags.OaIndexing);
|
||||
|
||||
if (oaIndexing && !perPatch)
|
||||
{
|
||||
var attrType = context.TypeVector(context.TypeFP32(), (LiteralInteger)4);
|
||||
attrType = context.TypeArray(attrType, context.Constant(context.TypeU32(), (LiteralInteger)MaxAttributes));
|
||||
|
||||
if (context.Config.Stage == ShaderStage.TessellationControl)
|
||||
{
|
||||
attrType = context.TypeArray(attrType, context.Constant(context.TypeU32(), context.Config.ThreadsPerInputPrimitive));
|
||||
}
|
||||
|
||||
var spvType = context.TypePointer(StorageClass.Output, attrType);
|
||||
var spvVar = context.Variable(spvType, StorageClass.Output);
|
||||
|
||||
context.Decorate(spvVar, Decoration.Location, (LiteralInteger)0);
|
||||
|
||||
context.AddGlobalVariable(spvVar);
|
||||
context.OutputsArray = spvVar;
|
||||
}
|
||||
|
||||
var outputs = perPatch ? info.OutputsPerPatch : info.Outputs;
|
||||
|
||||
foreach (int attr in outputs)
|
||||
@ -477,29 +498,10 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
|
||||
if (oaIndexing && isUserAttr && !perPatch)
|
||||
{
|
||||
if (context.OutputsArray == null)
|
||||
{
|
||||
var attrType = context.TypeVector(context.TypeFP32(), (LiteralInteger)4);
|
||||
attrType = context.TypeArray(attrType, context.Constant(context.TypeU32(), (LiteralInteger)MaxAttributes));
|
||||
|
||||
if (context.Config.Stage == ShaderStage.TessellationControl)
|
||||
{
|
||||
attrType = context.TypeArray(attrType, context.Constant(context.TypeU32(), context.Config.ThreadsPerInputPrimitive));
|
||||
}
|
||||
|
||||
var spvType = context.TypePointer(StorageClass.Output, attrType);
|
||||
var spvVar = context.Variable(spvType, StorageClass.Output);
|
||||
|
||||
context.Decorate(spvVar, Decoration.Location, (LiteralInteger)0);
|
||||
|
||||
context.AddGlobalVariable(spvVar);
|
||||
context.OutputsArray = spvVar;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DeclareOutputAttribute(context, attr, perPatch);
|
||||
continue;
|
||||
}
|
||||
|
||||
DeclareOutputAttribute(context, attr, perPatch);
|
||||
}
|
||||
|
||||
if (context.Config.Stage == ShaderStage.Vertex)
|
||||
|
@ -4,6 +4,7 @@ using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.Intrinsics;
|
||||
using System.Runtime.Intrinsics.Arm;
|
||||
using System.Runtime.Intrinsics.X86;
|
||||
|
||||
namespace Ryujinx.Graphics.Vic
|
||||
@ -17,10 +18,18 @@ namespace Ryujinx.Graphics.Vic
|
||||
int x2 = Math.Min(src.Width, x1 + targetRect.Width);
|
||||
int y2 = Math.Min(src.Height, y1 + targetRect.Height);
|
||||
|
||||
if (Sse41.IsSupported && ((x1 | x2) & 3) == 0)
|
||||
if (((x1 | x2) & 3) == 0)
|
||||
{
|
||||
BlendOneSse41(dst, src, ref slot, x1, y1, x2, y2);
|
||||
return;
|
||||
if (Sse41.IsSupported)
|
||||
{
|
||||
BlendOneSse41(dst, src, ref slot, x1, y1, x2, y2);
|
||||
return;
|
||||
}
|
||||
else if (AdvSimd.IsSupported)
|
||||
{
|
||||
BlendOneAdvSimd(dst, src, ref slot, x1, y1, x2, y2);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
for (int y = y1; y < y2; y++)
|
||||
@ -105,6 +114,84 @@ namespace Ryujinx.Graphics.Vic
|
||||
}
|
||||
}
|
||||
|
||||
private unsafe static void BlendOneAdvSimd(Surface dst, Surface src, ref SlotStruct slot, int x1, int y1, int x2, int y2)
|
||||
{
|
||||
Debug.Assert(((x1 | x2) & 3) == 0);
|
||||
|
||||
ref MatrixStruct mtx = ref slot.ColorMatrixStruct;
|
||||
|
||||
Vector128<int> col1 = Vector128.Create(mtx.MatrixCoeff00, mtx.MatrixCoeff10, mtx.MatrixCoeff20, 0);
|
||||
Vector128<int> col2 = Vector128.Create(mtx.MatrixCoeff01, mtx.MatrixCoeff11, mtx.MatrixCoeff21, 0);
|
||||
Vector128<int> col3 = Vector128.Create(mtx.MatrixCoeff02, mtx.MatrixCoeff12, mtx.MatrixCoeff22, 0);
|
||||
Vector128<int> col4 = Vector128.Create(mtx.MatrixCoeff03, mtx.MatrixCoeff13, mtx.MatrixCoeff23, 0);
|
||||
|
||||
Vector128<int> rShift = Vector128.Create(-mtx.MatrixRShift);
|
||||
Vector128<int> selMask = Vector128.Create(0, 0, 0, -1);
|
||||
Vector128<ushort> clMin = Vector128.Create((ushort)slot.SlotConfig.SoftClampLow);
|
||||
Vector128<ushort> clMax = Vector128.Create((ushort)slot.SlotConfig.SoftClampHigh);
|
||||
|
||||
fixed (Pixel* srcPtr = src.Data, dstPtr = dst.Data)
|
||||
{
|
||||
Pixel* ip = srcPtr;
|
||||
Pixel* op = dstPtr;
|
||||
|
||||
if (mtx.MatrixEnable)
|
||||
{
|
||||
for (int y = y1; y < y2; y++, ip += src.Width, op += dst.Width)
|
||||
{
|
||||
for (int x = x1; x < x2; x += 4)
|
||||
{
|
||||
Vector128<ushort> pixel12 = AdvSimd.LoadVector128((ushort*)(ip + (uint)x));
|
||||
Vector128<ushort> pixel34 = AdvSimd.LoadVector128((ushort*)(ip + (uint)x + 2));
|
||||
|
||||
Vector128<uint> pixel1 = AdvSimd.ZeroExtendWideningLower(pixel12.GetLower());
|
||||
Vector128<uint> pixel2 = AdvSimd.ZeroExtendWideningUpper(pixel12);
|
||||
Vector128<uint> pixel3 = AdvSimd.ZeroExtendWideningLower(pixel34.GetLower());
|
||||
Vector128<uint> pixel4 = AdvSimd.ZeroExtendWideningUpper(pixel34);
|
||||
|
||||
Vector128<int> t1 = MatrixMultiplyAdvSimd(pixel1.AsInt32(), col1, col2, col3, col4, rShift, selMask);
|
||||
Vector128<int> t2 = MatrixMultiplyAdvSimd(pixel2.AsInt32(), col1, col2, col3, col4, rShift, selMask);
|
||||
Vector128<int> t3 = MatrixMultiplyAdvSimd(pixel3.AsInt32(), col1, col2, col3, col4, rShift, selMask);
|
||||
Vector128<int> t4 = MatrixMultiplyAdvSimd(pixel4.AsInt32(), col1, col2, col3, col4, rShift, selMask);
|
||||
|
||||
Vector64<ushort> lower1 = AdvSimd.ExtractNarrowingSaturateUnsignedLower(t1);
|
||||
Vector64<ushort> lower3 = AdvSimd.ExtractNarrowingSaturateUnsignedLower(t3);
|
||||
|
||||
pixel12 = AdvSimd.ExtractNarrowingSaturateUnsignedUpper(lower1, t2);
|
||||
pixel34 = AdvSimd.ExtractNarrowingSaturateUnsignedUpper(lower3, t4);
|
||||
|
||||
pixel12 = AdvSimd.Min(pixel12, clMax);
|
||||
pixel34 = AdvSimd.Min(pixel34, clMax);
|
||||
pixel12 = AdvSimd.Max(pixel12, clMin);
|
||||
pixel34 = AdvSimd.Max(pixel34, clMin);
|
||||
|
||||
AdvSimd.Store((ushort*)(op + (uint)x + 0), pixel12);
|
||||
AdvSimd.Store((ushort*)(op + (uint)x + 2), pixel34);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int y = y1; y < y2; y++, ip += src.Width, op += dst.Width)
|
||||
{
|
||||
for (int x = x1; x < x2; x += 4)
|
||||
{
|
||||
Vector128<ushort> pixel12 = AdvSimd.LoadVector128((ushort*)(ip + (uint)x));
|
||||
Vector128<ushort> pixel34 = AdvSimd.LoadVector128((ushort*)(ip + (uint)x + 2));
|
||||
|
||||
pixel12 = AdvSimd.Min(pixel12, clMax);
|
||||
pixel34 = AdvSimd.Min(pixel34, clMax);
|
||||
pixel12 = AdvSimd.Max(pixel12, clMin);
|
||||
pixel34 = AdvSimd.Max(pixel34, clMin);
|
||||
|
||||
AdvSimd.Store((ushort*)(op + (uint)x + 0), pixel12);
|
||||
AdvSimd.Store((ushort*)(op + (uint)x + 2), pixel34);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void MatrixMultiply(ref MatrixStruct mtx, int x, int y, int z, out int r, out int g, out int b)
|
||||
{
|
||||
@ -159,5 +246,33 @@ namespace Ryujinx.Graphics.Vic
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static Vector128<int> MatrixMultiplyAdvSimd(
|
||||
Vector128<int> pixel,
|
||||
Vector128<int> col1,
|
||||
Vector128<int> col2,
|
||||
Vector128<int> col3,
|
||||
Vector128<int> col4,
|
||||
Vector128<int> rShift,
|
||||
Vector128<int> selectMask)
|
||||
{
|
||||
Vector128<int> x = AdvSimd.DuplicateSelectedScalarToVector128(pixel, 0);
|
||||
Vector128<int> y = AdvSimd.DuplicateSelectedScalarToVector128(pixel, 1);
|
||||
Vector128<int> z = AdvSimd.DuplicateSelectedScalarToVector128(pixel, 2);
|
||||
|
||||
col1 = AdvSimd.Multiply(col1, x);
|
||||
col2 = AdvSimd.Multiply(col2, y);
|
||||
col3 = AdvSimd.Multiply(col3, z);
|
||||
|
||||
Vector128<int> res = AdvSimd.Add(col3, AdvSimd.Add(col1, col2));
|
||||
|
||||
res = AdvSimd.ShiftArithmetic(res, rShift);
|
||||
res = AdvSimd.Add(res, col4);
|
||||
res = AdvSimd.ShiftRightArithmetic(res, 8);
|
||||
res = AdvSimd.BitwiseSelect(selectMask, pixel, res);
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ using Ryujinx.Graphics.Vic.Types;
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.Intrinsics;
|
||||
using System.Runtime.Intrinsics.Arm;
|
||||
using System.Runtime.Intrinsics.X86;
|
||||
using static Ryujinx.Graphics.Vic.Image.SurfaceCommon;
|
||||
|
||||
@ -54,7 +55,7 @@ namespace Ryujinx.Graphics.Vic.Image
|
||||
(byte)4, (byte)6, (byte)7, (byte)5,
|
||||
(byte)8, (byte)10, (byte)11, (byte)9,
|
||||
(byte)12, (byte)14, (byte)15, (byte)13);
|
||||
Vector128<short> alphaMask = Vector128.Create(0xffUL << 48).AsInt16();
|
||||
Vector128<short> alphaMask = Vector128.Create(0xff << 24).AsInt16();
|
||||
|
||||
int yStrideGap = yStride - width;
|
||||
int uvStrideGap = uvStride - input.UvWidth;
|
||||
@ -95,6 +96,11 @@ namespace Ryujinx.Graphics.Vic.Image
|
||||
rgba2 = Ssse3.Shuffle(rgba2.AsByte(), shufMask).AsInt16();
|
||||
rgba3 = Ssse3.Shuffle(rgba3.AsByte(), shufMask).AsInt16();
|
||||
|
||||
rgba0 = Sse2.Or(rgba0, alphaMask);
|
||||
rgba1 = Sse2.Or(rgba1, alphaMask);
|
||||
rgba2 = Sse2.Or(rgba2, alphaMask);
|
||||
rgba3 = Sse2.Or(rgba3, alphaMask);
|
||||
|
||||
Vector128<short> rgba16_0 = Sse41.ConvertToVector128Int16(rgba0.AsByte());
|
||||
Vector128<short> rgba16_1 = Sse41.ConvertToVector128Int16(HighToLow(rgba0.AsByte()));
|
||||
Vector128<short> rgba16_2 = Sse41.ConvertToVector128Int16(rgba1.AsByte());
|
||||
@ -104,15 +110,6 @@ namespace Ryujinx.Graphics.Vic.Image
|
||||
Vector128<short> rgba16_6 = Sse41.ConvertToVector128Int16(rgba3.AsByte());
|
||||
Vector128<short> rgba16_7 = Sse41.ConvertToVector128Int16(HighToLow(rgba3.AsByte()));
|
||||
|
||||
rgba16_0 = Sse2.Or(rgba16_0, alphaMask);
|
||||
rgba16_1 = Sse2.Or(rgba16_1, alphaMask);
|
||||
rgba16_2 = Sse2.Or(rgba16_2, alphaMask);
|
||||
rgba16_3 = Sse2.Or(rgba16_3, alphaMask);
|
||||
rgba16_4 = Sse2.Or(rgba16_4, alphaMask);
|
||||
rgba16_5 = Sse2.Or(rgba16_5, alphaMask);
|
||||
rgba16_6 = Sse2.Or(rgba16_6, alphaMask);
|
||||
rgba16_7 = Sse2.Or(rgba16_7, alphaMask);
|
||||
|
||||
rgba16_0 = Sse2.ShiftLeftLogical(rgba16_0, 2);
|
||||
rgba16_1 = Sse2.ShiftLeftLogical(rgba16_1, 2);
|
||||
rgba16_2 = Sse2.ShiftLeftLogical(rgba16_2, 2);
|
||||
@ -149,6 +146,98 @@ namespace Ryujinx.Graphics.Vic.Image
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (AdvSimd.Arm64.IsSupported)
|
||||
{
|
||||
Vector128<int> alphaMask = Vector128.Create(0xffu << 24).AsInt32();
|
||||
|
||||
int yStrideGap = yStride - width;
|
||||
int uvStrideGap = uvStride - input.UvWidth;
|
||||
|
||||
int widthTrunc = width & ~0xf;
|
||||
|
||||
fixed (Pixel* dstPtr = output.Data)
|
||||
{
|
||||
Pixel* op = dstPtr;
|
||||
|
||||
fixed (byte* src0Ptr = input.Buffer0, src1Ptr = input.Buffer1)
|
||||
{
|
||||
byte* i0p = src0Ptr;
|
||||
|
||||
for (int y = 0; y < height; y++)
|
||||
{
|
||||
byte* i1p = src1Ptr + (y >> 1) * uvStride;
|
||||
|
||||
int x = 0;
|
||||
|
||||
for (; x < widthTrunc; x += 16, i0p += 16, i1p += 16)
|
||||
{
|
||||
Vector128<byte> ya = AdvSimd.LoadVector128(i0p);
|
||||
Vector128<byte> uv = AdvSimd.LoadVector128(i1p);
|
||||
|
||||
Vector128<short> ya0 = AdvSimd.ZeroExtendWideningLower(ya.GetLower()).AsInt16();
|
||||
Vector128<short> ya1 = AdvSimd.ZeroExtendWideningUpper(ya).AsInt16();
|
||||
|
||||
Vector128<short> uv0 = AdvSimd.Arm64.ZipLow(uv.AsInt16(), uv.AsInt16());
|
||||
Vector128<short> uv1 = AdvSimd.Arm64.ZipHigh(uv.AsInt16(), uv.AsInt16());
|
||||
|
||||
ya0 = AdvSimd.ShiftLeftLogical(ya0, 8);
|
||||
ya1 = AdvSimd.ShiftLeftLogical(ya1, 8);
|
||||
|
||||
Vector128<short> rgba0 = AdvSimd.Arm64.ZipLow(ya0, uv0);
|
||||
Vector128<short> rgba1 = AdvSimd.Arm64.ZipHigh(ya0, uv0);
|
||||
Vector128<short> rgba2 = AdvSimd.Arm64.ZipLow(ya1, uv1);
|
||||
Vector128<short> rgba3 = AdvSimd.Arm64.ZipHigh(ya1, uv1);
|
||||
|
||||
rgba0 = AdvSimd.ShiftRightLogicalAdd(alphaMask, rgba0.AsInt32(), 8).AsInt16();
|
||||
rgba1 = AdvSimd.ShiftRightLogicalAdd(alphaMask, rgba1.AsInt32(), 8).AsInt16();
|
||||
rgba2 = AdvSimd.ShiftRightLogicalAdd(alphaMask, rgba2.AsInt32(), 8).AsInt16();
|
||||
rgba3 = AdvSimd.ShiftRightLogicalAdd(alphaMask, rgba3.AsInt32(), 8).AsInt16();
|
||||
|
||||
Vector128<short> rgba16_0 = AdvSimd.ZeroExtendWideningLower(rgba0.AsByte().GetLower()).AsInt16();
|
||||
Vector128<short> rgba16_1 = AdvSimd.ZeroExtendWideningUpper(rgba0.AsByte()).AsInt16();
|
||||
Vector128<short> rgba16_2 = AdvSimd.ZeroExtendWideningLower(rgba1.AsByte().GetLower()).AsInt16();
|
||||
Vector128<short> rgba16_3 = AdvSimd.ZeroExtendWideningUpper(rgba1.AsByte()).AsInt16();
|
||||
Vector128<short> rgba16_4 = AdvSimd.ZeroExtendWideningLower(rgba2.AsByte().GetLower()).AsInt16();
|
||||
Vector128<short> rgba16_5 = AdvSimd.ZeroExtendWideningUpper(rgba2.AsByte()).AsInt16();
|
||||
Vector128<short> rgba16_6 = AdvSimd.ZeroExtendWideningLower(rgba3.AsByte().GetLower()).AsInt16();
|
||||
Vector128<short> rgba16_7 = AdvSimd.ZeroExtendWideningUpper(rgba3.AsByte()).AsInt16();
|
||||
|
||||
rgba16_0 = AdvSimd.ShiftLeftLogical(rgba16_0, 2);
|
||||
rgba16_1 = AdvSimd.ShiftLeftLogical(rgba16_1, 2);
|
||||
rgba16_2 = AdvSimd.ShiftLeftLogical(rgba16_2, 2);
|
||||
rgba16_3 = AdvSimd.ShiftLeftLogical(rgba16_3, 2);
|
||||
rgba16_4 = AdvSimd.ShiftLeftLogical(rgba16_4, 2);
|
||||
rgba16_5 = AdvSimd.ShiftLeftLogical(rgba16_5, 2);
|
||||
rgba16_6 = AdvSimd.ShiftLeftLogical(rgba16_6, 2);
|
||||
rgba16_7 = AdvSimd.ShiftLeftLogical(rgba16_7, 2);
|
||||
|
||||
AdvSimd.Store((short*)(op + (uint)x + 0), rgba16_0);
|
||||
AdvSimd.Store((short*)(op + (uint)x + 2), rgba16_1);
|
||||
AdvSimd.Store((short*)(op + (uint)x + 4), rgba16_2);
|
||||
AdvSimd.Store((short*)(op + (uint)x + 6), rgba16_3);
|
||||
AdvSimd.Store((short*)(op + (uint)x + 8), rgba16_4);
|
||||
AdvSimd.Store((short*)(op + (uint)x + 10), rgba16_5);
|
||||
AdvSimd.Store((short*)(op + (uint)x + 12), rgba16_6);
|
||||
AdvSimd.Store((short*)(op + (uint)x + 14), rgba16_7);
|
||||
}
|
||||
|
||||
for (; x < width; x++, i1p += (x & 1) * 2)
|
||||
{
|
||||
Pixel* px = op + (uint)x;
|
||||
|
||||
px->R = Upsample(*i0p++);
|
||||
px->G = Upsample(*i1p);
|
||||
px->B = Upsample(*(i1p + 1));
|
||||
px->A = 0x3ff;
|
||||
}
|
||||
|
||||
op += width;
|
||||
i0p += yStrideGap;
|
||||
i1p += uvStrideGap;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int y = 0; y < height; y++)
|
||||
|
@ -3,6 +3,7 @@ using Ryujinx.Graphics.Texture;
|
||||
using Ryujinx.Graphics.Vic.Types;
|
||||
using System;
|
||||
using System.Runtime.Intrinsics;
|
||||
using System.Runtime.Intrinsics.Arm;
|
||||
using System.Runtime.Intrinsics.X86;
|
||||
using static Ryujinx.Graphics.Vic.Image.SurfaceCommon;
|
||||
|
||||
@ -93,6 +94,64 @@ namespace Ryujinx.Graphics.Vic.Image
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (AdvSimd.IsSupported)
|
||||
{
|
||||
int widthTrunc = width & ~7;
|
||||
int strideGap = stride - width * 4;
|
||||
|
||||
fixed (Pixel* srcPtr = input.Data)
|
||||
{
|
||||
Pixel* ip = srcPtr;
|
||||
|
||||
fixed (byte* dstPtr = dst)
|
||||
{
|
||||
byte* op = dstPtr;
|
||||
|
||||
for (int y = 0; y < height; y++, ip += input.Width)
|
||||
{
|
||||
int x = 0;
|
||||
|
||||
for (; x < widthTrunc; x += 8)
|
||||
{
|
||||
Vector128<ushort> pixel12 = AdvSimd.LoadVector128((ushort*)(ip + (uint)x));
|
||||
Vector128<ushort> pixel34 = AdvSimd.LoadVector128((ushort*)(ip + (uint)x + 2));
|
||||
Vector128<ushort> pixel56 = AdvSimd.LoadVector128((ushort*)(ip + (uint)x + 4));
|
||||
Vector128<ushort> pixel78 = AdvSimd.LoadVector128((ushort*)(ip + (uint)x + 6));
|
||||
|
||||
pixel12 = AdvSimd.ShiftRightLogical(pixel12, 2);
|
||||
pixel34 = AdvSimd.ShiftRightLogical(pixel34, 2);
|
||||
pixel56 = AdvSimd.ShiftRightLogical(pixel56, 2);
|
||||
pixel78 = AdvSimd.ShiftRightLogical(pixel78, 2);
|
||||
|
||||
Vector64<byte> lower12 = AdvSimd.ExtractNarrowingLower(pixel12.AsUInt16());
|
||||
Vector64<byte> lower56 = AdvSimd.ExtractNarrowingLower(pixel56.AsUInt16());
|
||||
|
||||
Vector128<byte> pixel1234 = AdvSimd.ExtractNarrowingUpper(lower12, pixel34.AsUInt16());
|
||||
Vector128<byte> pixel5678 = AdvSimd.ExtractNarrowingUpper(lower56, pixel78.AsUInt16());
|
||||
|
||||
AdvSimd.Store(op + 0x00, pixel1234);
|
||||
AdvSimd.Store(op + 0x10, pixel5678);
|
||||
|
||||
op += 0x20;
|
||||
}
|
||||
|
||||
for (; x < width; x++)
|
||||
{
|
||||
Pixel* px = ip + (uint)x;
|
||||
|
||||
*(op + 0) = Downsample(px->R);
|
||||
*(op + 1) = Downsample(px->G);
|
||||
*(op + 2) = Downsample(px->B);
|
||||
*(op + 3) = Downsample(px->A);
|
||||
|
||||
op += 4;
|
||||
}
|
||||
|
||||
op += strideGap;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int y = 0; y < height; y++)
|
||||
@ -302,6 +361,87 @@ namespace Ryujinx.Graphics.Vic.Image
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (AdvSimd.IsSupported)
|
||||
{
|
||||
Vector128<ushort> mask = Vector128.Create(0xffffUL).AsUInt16();
|
||||
|
||||
int widthTrunc = width & ~0xf;
|
||||
int strideGap = yStride - width;
|
||||
|
||||
fixed (Pixel* srcPtr = input.Data)
|
||||
{
|
||||
Pixel* ip = srcPtr;
|
||||
|
||||
fixed (byte* dstPtr = dstY)
|
||||
{
|
||||
byte* op = dstPtr;
|
||||
|
||||
for (int y = 0; y < height; y++, ip += input.Width)
|
||||
{
|
||||
int x = 0;
|
||||
|
||||
for (; x < widthTrunc; x += 16)
|
||||
{
|
||||
byte* baseOffset = (byte*)(ip + (ulong)(uint)x);
|
||||
|
||||
Vector128<ushort> pixelp1 = AdvSimd.LoadVector128((ushort*)baseOffset);
|
||||
Vector128<ushort> pixelp2 = AdvSimd.LoadVector128((ushort*)(baseOffset + 0x10));
|
||||
Vector128<ushort> pixelp3 = AdvSimd.LoadVector128((ushort*)(baseOffset + 0x20));
|
||||
Vector128<ushort> pixelp4 = AdvSimd.LoadVector128((ushort*)(baseOffset + 0x30));
|
||||
Vector128<ushort> pixelp5 = AdvSimd.LoadVector128((ushort*)(baseOffset + 0x40));
|
||||
Vector128<ushort> pixelp6 = AdvSimd.LoadVector128((ushort*)(baseOffset + 0x50));
|
||||
Vector128<ushort> pixelp7 = AdvSimd.LoadVector128((ushort*)(baseOffset + 0x60));
|
||||
Vector128<ushort> pixelp8 = AdvSimd.LoadVector128((ushort*)(baseOffset + 0x70));
|
||||
|
||||
pixelp1 = AdvSimd.And(pixelp1, mask);
|
||||
pixelp2 = AdvSimd.And(pixelp2, mask);
|
||||
pixelp3 = AdvSimd.And(pixelp3, mask);
|
||||
pixelp4 = AdvSimd.And(pixelp4, mask);
|
||||
pixelp5 = AdvSimd.And(pixelp5, mask);
|
||||
pixelp6 = AdvSimd.And(pixelp6, mask);
|
||||
pixelp7 = AdvSimd.And(pixelp7, mask);
|
||||
pixelp8 = AdvSimd.And(pixelp8, mask);
|
||||
|
||||
Vector64<ushort> lowerp1 = AdvSimd.ExtractNarrowingLower(pixelp1.AsUInt32());
|
||||
Vector64<ushort> lowerp3 = AdvSimd.ExtractNarrowingLower(pixelp3.AsUInt32());
|
||||
Vector64<ushort> lowerp5 = AdvSimd.ExtractNarrowingLower(pixelp5.AsUInt32());
|
||||
Vector64<ushort> lowerp7 = AdvSimd.ExtractNarrowingLower(pixelp7.AsUInt32());
|
||||
|
||||
Vector128<ushort> pixelq1 = AdvSimd.ExtractNarrowingUpper(lowerp1, pixelp2.AsUInt32());
|
||||
Vector128<ushort> pixelq2 = AdvSimd.ExtractNarrowingUpper(lowerp3, pixelp4.AsUInt32());
|
||||
Vector128<ushort> pixelq3 = AdvSimd.ExtractNarrowingUpper(lowerp5, pixelp6.AsUInt32());
|
||||
Vector128<ushort> pixelq4 = AdvSimd.ExtractNarrowingUpper(lowerp7, pixelp8.AsUInt32());
|
||||
|
||||
Vector64<ushort> lowerq1 = AdvSimd.ExtractNarrowingLower(pixelq1.AsUInt32());
|
||||
Vector64<ushort> lowerq3 = AdvSimd.ExtractNarrowingLower(pixelq3.AsUInt32());
|
||||
|
||||
pixelq1 = AdvSimd.ExtractNarrowingUpper(lowerq1, pixelq2.AsUInt32());
|
||||
pixelq2 = AdvSimd.ExtractNarrowingUpper(lowerq3, pixelq4.AsUInt32());
|
||||
|
||||
pixelq1 = AdvSimd.ShiftRightLogical(pixelq1, 2);
|
||||
pixelq2 = AdvSimd.ShiftRightLogical(pixelq2, 2);
|
||||
|
||||
Vector64<byte> pixelLower = AdvSimd.ExtractNarrowingLower(pixelq1.AsUInt16());
|
||||
|
||||
Vector128<byte> pixel = AdvSimd.ExtractNarrowingUpper(pixelLower, pixelq2.AsUInt16());
|
||||
|
||||
AdvSimd.Store(op, pixel);
|
||||
|
||||
op += 0x10;
|
||||
}
|
||||
|
||||
for (; x < width; x++)
|
||||
{
|
||||
Pixel* px = ip + (uint)x;
|
||||
|
||||
*op++ = Downsample(px->R);
|
||||
}
|
||||
|
||||
op += strideGap;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int y = 0; y < height; y++)
|
||||
@ -392,6 +532,69 @@ namespace Ryujinx.Graphics.Vic.Image
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (AdvSimd.Arm64.IsSupported)
|
||||
{
|
||||
int widthTrunc = uvWidth & ~7;
|
||||
int strideGap = uvStride - uvWidth * 2;
|
||||
|
||||
fixed (Pixel* srcPtr = input.Data)
|
||||
{
|
||||
Pixel* ip = srcPtr;
|
||||
|
||||
fixed (byte* dstPtr = dstUv)
|
||||
{
|
||||
byte* op = dstPtr;
|
||||
|
||||
for (int y = 0; y < uvHeight; y++, ip += input.Width * 2)
|
||||
{
|
||||
int x = 0;
|
||||
|
||||
for (; x < widthTrunc; x += 8)
|
||||
{
|
||||
byte* baseOffset = (byte*)ip + (ulong)(uint)x * 16;
|
||||
|
||||
Vector128<uint> pixel1 = AdvSimd.LoadAndReplicateToVector128((uint*)(baseOffset + 0x02));
|
||||
Vector128<uint> pixel2 = AdvSimd.LoadAndReplicateToVector128((uint*)(baseOffset + 0x12));
|
||||
Vector128<uint> pixel3 = AdvSimd.LoadAndReplicateToVector128((uint*)(baseOffset + 0x22));
|
||||
Vector128<uint> pixel4 = AdvSimd.LoadAndReplicateToVector128((uint*)(baseOffset + 0x32));
|
||||
Vector128<uint> pixel5 = AdvSimd.LoadAndReplicateToVector128((uint*)(baseOffset + 0x42));
|
||||
Vector128<uint> pixel6 = AdvSimd.LoadAndReplicateToVector128((uint*)(baseOffset + 0x52));
|
||||
Vector128<uint> pixel7 = AdvSimd.LoadAndReplicateToVector128((uint*)(baseOffset + 0x62));
|
||||
Vector128<uint> pixel8 = AdvSimd.LoadAndReplicateToVector128((uint*)(baseOffset + 0x72));
|
||||
|
||||
Vector128<uint> pixel12 = AdvSimd.Arm64.ZipLow(pixel1, pixel2);
|
||||
Vector128<uint> pixel34 = AdvSimd.Arm64.ZipLow(pixel3, pixel4);
|
||||
Vector128<uint> pixel56 = AdvSimd.Arm64.ZipLow(pixel5, pixel6);
|
||||
Vector128<uint> pixel78 = AdvSimd.Arm64.ZipLow(pixel7, pixel8);
|
||||
|
||||
Vector128<ulong> pixel1234 = AdvSimd.Arm64.ZipLow(pixel12.AsUInt64(), pixel34.AsUInt64());
|
||||
Vector128<ulong> pixel5678 = AdvSimd.Arm64.ZipLow(pixel56.AsUInt64(), pixel78.AsUInt64());
|
||||
|
||||
pixel1234 = AdvSimd.ShiftRightLogical(pixel1234, 2);
|
||||
pixel5678 = AdvSimd.ShiftRightLogical(pixel5678, 2);
|
||||
|
||||
Vector64<byte> pixelLower = AdvSimd.ExtractNarrowingLower(pixel1234.AsUInt16());
|
||||
|
||||
Vector128<byte> pixel = AdvSimd.ExtractNarrowingUpper(pixelLower, pixel5678.AsUInt16());
|
||||
|
||||
AdvSimd.Store(op, pixel);
|
||||
|
||||
op += 0x10;
|
||||
}
|
||||
|
||||
for (; x < uvWidth; x++)
|
||||
{
|
||||
Pixel* px = ip + (uint)(x << 1);
|
||||
|
||||
*op++ = Downsample(px->G);
|
||||
*op++ = Downsample(px->B);
|
||||
}
|
||||
|
||||
op += strideGap;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int y = 0; y < uvHeight; y++)
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user