Compare commits
61 Commits
Author | SHA1 | Date | |
---|---|---|---|
256514c7c9 | |||
556be08c4e | |||
1cbca5eecb | |||
95017b8c66 | |||
4a892fbdc9 | |||
9eb5b7a10d | |||
d64594ec74 | |||
6a1a03566a | |||
13f5294aa3 | |||
9444b4a647 | |||
610fc84f3e | |||
247d26b4b5 | |||
43ebd7a9bb | |||
26a881176e | |||
e44a43c7e1 | |||
3139a85a2b | |||
a4e8bea866 | |||
6a9e9b5360 | |||
952f6f8a65 | |||
d04ba51bb0 | |||
55ee261363 | |||
4e3a34412e | |||
3f4fb8f73a | |||
56c56aa34d | |||
d4b960d348 | |||
b2a225558d | |||
0ef0fc044a | |||
04bd87ed5a | |||
5158cdb308 | |||
1402d8391d | |||
e3b36db71c | |||
ba0171d054 | |||
d1146a5af2 | |||
79408b68c3 | |||
d461d4f68b | |||
b45d30acf8 | |||
df70442c46 | |||
e2ffa5a125 | |||
73feac5819 | |||
e5ad1dfa48 | |||
79becc4b78 | |||
223172ac0b | |||
8c9633d72f | |||
1f93fd52d9 | |||
aac7bbd378 | |||
bed516bfda | |||
69b05f9918 | |||
fb7c80e928 | |||
bb2f9df0a1 | |||
54bfaa125d | |||
7af9fcbc06 | |||
ee174be57c | |||
0bcbe32367 | |||
b97ff4da5e | |||
747081d2c7 | |||
497199bb50 | |||
bd9ac0fdaa | |||
ac21abbb9d | |||
a3dd04deef | |||
3705c20668 | |||
7b35ebc64a |
11
.github/workflows/build.yml
vendored
11
.github/workflows/build.yml
vendored
@ -46,6 +46,7 @@ jobs:
|
||||
env:
|
||||
POWERSHELL_TELEMETRY_OPTOUT: 1
|
||||
DOTNET_CLI_TELEMETRY_OPTOUT: 1
|
||||
RYUJINX_BASE_VERSION: "1.1.0"
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-dotnet@v1
|
||||
@ -59,24 +60,24 @@ jobs:
|
||||
- name: Clear
|
||||
run: dotnet clean && dotnet nuget locals all --clear
|
||||
- name: Build
|
||||
run: dotnet build -c "${{ matrix.configuration }}" /p:Version="1.1.0" /p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" /p:ExtraDefineConstants=DISABLE_UPDATER
|
||||
run: dotnet build -c "${{ matrix.configuration }}" /p:Version="${{ env.RYUJINX_BASE_VERSION }}" /p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" /p:ExtraDefineConstants=DISABLE_UPDATER
|
||||
- name: Test
|
||||
run: dotnet test -c "${{ matrix.configuration }}"
|
||||
- name: Publish Ryujinx
|
||||
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish /p:Version="1.1.0" /p:DebugType=embedded /p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" /p:ExtraDefineConstants=DISABLE_UPDATER Ryujinx --self-contained
|
||||
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish /p:Version="${{ env.RYUJINX_BASE_VERSION }}" /p:DebugType=embedded /p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" /p:ExtraDefineConstants=DISABLE_UPDATER Ryujinx --self-contained
|
||||
if: github.event_name == 'pull_request'
|
||||
- name: Publish Ryujinx.Headless.SDL2
|
||||
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish_sdl2_headless /p:Version="1.1.0" /p:DebugType=embedded /p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" /p:ExtraDefineConstants=DISABLE_UPDATER Ryujinx.Headless.SDL2 --self-contained
|
||||
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish_sdl2_headless /p:Version="${{ env.RYUJINX_BASE_VERSION }}" /p:DebugType=embedded /p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" /p:ExtraDefineConstants=DISABLE_UPDATER Ryujinx.Headless.SDL2 --self-contained
|
||||
if: github.event_name == 'pull_request'
|
||||
- name: Upload Ryujinx artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: ryujinx-${{ matrix.configuration }}-1.0.0+${{ steps.git_short_hash.outputs.result }}-${{ matrix.RELEASE_ZIP_OS_NAME }}
|
||||
name: ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.RELEASE_ZIP_OS_NAME }}
|
||||
path: publish
|
||||
if: github.event_name == 'pull_request'
|
||||
- name: Upload Ryujinx.Headless.SDL2 artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: ryujinx-headless-sdl2-${{ matrix.configuration }}-1.0.0+${{ steps.git_short_hash.outputs.result }}-${{ matrix.RELEASE_ZIP_OS_NAME }}
|
||||
name: ryujinx-headless-sdl2-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.RELEASE_ZIP_OS_NAME }}
|
||||
path: publish_sdl2_headless
|
||||
if: github.event_name == 'pull_request'
|
||||
|
9
.github/workflows/nightly_pr_comment.yml
vendored
9
.github/workflows/nightly_pr_comment.yml
vendored
@ -36,15 +36,20 @@ jobs:
|
||||
return core.error(`No artifacts found`);
|
||||
}
|
||||
let body = `Download the artifacts for this pull request:\n`;
|
||||
let hidden_headless_artifacts = `\n\n <details><summary>GUI-less (SDL2)</summary>\n`;
|
||||
let hidden_debug_artifacts = `\n\n <details><summary>Only for Developers</summary>\n`;
|
||||
for (const art of artifacts) {
|
||||
if(art.name.includes('Debug')){
|
||||
if(art.name.includes('Debug')) {
|
||||
hidden_debug_artifacts += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
|
||||
}else{
|
||||
} else if(art.name.includes('headless-sdl2')) {
|
||||
hidden_headless_artifacts += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
|
||||
} else {
|
||||
body += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
|
||||
}
|
||||
}
|
||||
hidden_headless_artifacts += `\n</details>`;
|
||||
hidden_debug_artifacts += `\n</details>`;
|
||||
body += hidden_headless_artifacts;
|
||||
body += hidden_debug_artifacts;
|
||||
|
||||
const {data: comments} = await github.issues.listComments({repo, owner, issue_number});
|
||||
|
@ -59,7 +59,7 @@ namespace ARMeilleure.CodeGen.Optimizations
|
||||
BasicBlock fromPred = from.Predecessors.Count == 1 ? from.Predecessors[0] : null;
|
||||
|
||||
// If the block is empty, we can try to append to the predecessor and avoid unnecessary jumps.
|
||||
if (from.Operations.Count == 0 && fromPred != null)
|
||||
if (from.Operations.Count == 0 && fromPred != null && fromPred.SuccessorsCount == 1)
|
||||
{
|
||||
for (int i = 0; i < fromPred.SuccessorsCount; i++)
|
||||
{
|
||||
|
@ -121,7 +121,7 @@ namespace ARMeilleure.Decoders
|
||||
currBlock.Branch = GetBlock((ulong)op.Immediate);
|
||||
}
|
||||
|
||||
if (!IsUnconditionalBranch(lastOp) || isCall)
|
||||
if (isCall || !(IsUnconditionalBranch(lastOp) || IsTrap(lastOp)))
|
||||
{
|
||||
currBlock.Next = GetBlock(currBlock.EndAddress);
|
||||
}
|
||||
@ -263,6 +263,11 @@ namespace ARMeilleure.Decoders
|
||||
// so we must consider such operations as a branch in potential aswell.
|
||||
if (opCode is IOpCode32Alu opAlu && opAlu.Rd == RegisterAlias.Aarch32Pc)
|
||||
{
|
||||
if (opCode is OpCodeT32)
|
||||
{
|
||||
return opCode.Instruction.Name != InstName.Tst && opCode.Instruction.Name != InstName.Teq &&
|
||||
opCode.Instruction.Name != InstName.Cmp && opCode.Instruction.Name != InstName.Cmn;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -324,9 +329,13 @@ namespace ARMeilleure.Decoders
|
||||
}
|
||||
|
||||
private static bool IsException(OpCode opCode)
|
||||
{
|
||||
return IsTrap(opCode) || opCode.Instruction.Name == InstName.Svc;
|
||||
}
|
||||
|
||||
private static bool IsTrap(OpCode opCode)
|
||||
{
|
||||
return opCode.Instruction.Name == InstName.Brk ||
|
||||
opCode.Instruction.Name == InstName.Svc ||
|
||||
opCode.Instruction.Name == InstName.Trap ||
|
||||
opCode.Instruction.Name == InstName.Und;
|
||||
}
|
||||
|
@ -13,11 +13,25 @@ namespace ARMeilleure.Decoders
|
||||
Cond = (Condition)((uint)opCode >> 28);
|
||||
}
|
||||
|
||||
public bool IsThumb()
|
||||
{
|
||||
return this is OpCodeT16 || this is OpCodeT32;
|
||||
}
|
||||
|
||||
public uint GetPc()
|
||||
{
|
||||
// Due to backwards compatibility and legacy behavior of ARMv4 CPUs pipeline,
|
||||
// the PC actually points 2 instructions ahead.
|
||||
return (uint)Address + (uint)OpCodeSizeInBytes * 2;
|
||||
if (IsThumb())
|
||||
{
|
||||
// PC is ahead by 4 in thumb mode whether or not the current instruction
|
||||
// is 16 or 32 bit.
|
||||
return (uint)Address + 4u;
|
||||
}
|
||||
else
|
||||
{
|
||||
return (uint)Address + 8u;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
namespace ARMeilleure.Decoders
|
||||
{
|
||||
class OpCodeT16BImm11 : OpCode32, IOpCode32BImm
|
||||
class OpCodeT16BImm11 : OpCodeT16, IOpCode32BImm
|
||||
{
|
||||
public long Immediate { get; }
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
namespace ARMeilleure.Decoders
|
||||
{
|
||||
class OpCodeT16BImm8 : OpCode32, IOpCode32BImm
|
||||
class OpCodeT16BImm8 : OpCodeT16, IOpCode32BImm
|
||||
{
|
||||
public long Immediate { get; }
|
||||
|
||||
|
14
ARMeilleure/Decoders/OpCodeT32.cs
Normal file
14
ARMeilleure/Decoders/OpCodeT32.cs
Normal file
@ -0,0 +1,14 @@
|
||||
namespace ARMeilleure.Decoders
|
||||
{
|
||||
class OpCodeT32 : OpCode32
|
||||
{
|
||||
public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32(inst, address, opCode);
|
||||
|
||||
public OpCodeT32(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode)
|
||||
{
|
||||
Cond = Condition.Al;
|
||||
|
||||
OpCodeSizeInBytes = 4;
|
||||
}
|
||||
}
|
||||
}
|
20
ARMeilleure/Decoders/OpCodeT32Alu.cs
Normal file
20
ARMeilleure/Decoders/OpCodeT32Alu.cs
Normal file
@ -0,0 +1,20 @@
|
||||
namespace ARMeilleure.Decoders
|
||||
{
|
||||
class OpCodeT32Alu : OpCodeT32, IOpCode32Alu
|
||||
{
|
||||
public int Rd { get; }
|
||||
public int Rn { get; }
|
||||
|
||||
public bool? SetFlags { get; }
|
||||
|
||||
public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32Alu(inst, address, opCode);
|
||||
|
||||
public OpCodeT32Alu(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode)
|
||||
{
|
||||
Rd = (opCode >> 8) & 0xf;
|
||||
Rn = (opCode >> 16) & 0xf;
|
||||
|
||||
SetFlags = ((opCode >> 20) & 1) != 0;
|
||||
}
|
||||
}
|
||||
}
|
38
ARMeilleure/Decoders/OpCodeT32AluImm.cs
Normal file
38
ARMeilleure/Decoders/OpCodeT32AluImm.cs
Normal file
@ -0,0 +1,38 @@
|
||||
using ARMeilleure.Common;
|
||||
using System.Runtime.Intrinsics;
|
||||
|
||||
namespace ARMeilleure.Decoders
|
||||
{
|
||||
class OpCodeT32AluImm : OpCodeT32Alu, IOpCode32AluImm
|
||||
{
|
||||
public int Immediate { get; }
|
||||
|
||||
public bool IsRotated { get; }
|
||||
|
||||
public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32AluImm(inst, address, opCode);
|
||||
|
||||
private static readonly Vector128<int> _factor = Vector128.Create(1, 0x00010001, 0x01000100, 0x01010101);
|
||||
|
||||
public OpCodeT32AluImm(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode)
|
||||
{
|
||||
int imm8 = (opCode >> 0) & 0xff;
|
||||
int imm3 = (opCode >> 12) & 7;
|
||||
int imm1 = (opCode >> 26) & 1;
|
||||
|
||||
int imm12 = imm8 | (imm3 << 8) | (imm1 << 11);
|
||||
|
||||
if ((imm12 >> 10) == 0)
|
||||
{
|
||||
Immediate = imm8 * _factor.GetElement((imm12 >> 8) & 3);
|
||||
IsRotated = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
int shift = imm12 >> 7;
|
||||
|
||||
Immediate = BitUtils.RotateRight(0x80 | (imm12 & 0x7f), shift, 32);
|
||||
IsRotated = shift != 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
20
ARMeilleure/Decoders/OpCodeT32AluRsImm.cs
Normal file
20
ARMeilleure/Decoders/OpCodeT32AluRsImm.cs
Normal file
@ -0,0 +1,20 @@
|
||||
namespace ARMeilleure.Decoders
|
||||
{
|
||||
class OpCodeT32AluRsImm : OpCodeT32Alu, IOpCode32AluRsImm
|
||||
{
|
||||
public int Rm { get; }
|
||||
public int Immediate { get; }
|
||||
|
||||
public ShiftType ShiftType { get; }
|
||||
|
||||
public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32AluRsImm(inst, address, opCode);
|
||||
|
||||
public OpCodeT32AluRsImm(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode)
|
||||
{
|
||||
Rm = (opCode >> 0) & 0xf;
|
||||
Immediate = ((opCode >> 6) & 3) | ((opCode >> 10) & 0x1c);
|
||||
|
||||
ShiftType = (ShiftType)((opCode >> 4) & 3);
|
||||
}
|
||||
}
|
||||
}
|
29
ARMeilleure/Decoders/OpCodeT32BImm20.cs
Normal file
29
ARMeilleure/Decoders/OpCodeT32BImm20.cs
Normal file
@ -0,0 +1,29 @@
|
||||
using ARMeilleure.Instructions;
|
||||
|
||||
namespace ARMeilleure.Decoders
|
||||
{
|
||||
class OpCodeT32BImm20 : OpCodeT32, IOpCode32BImm
|
||||
{
|
||||
public long Immediate { get; }
|
||||
|
||||
public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32BImm20(inst, address, opCode);
|
||||
|
||||
public OpCodeT32BImm20(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode)
|
||||
{
|
||||
uint pc = GetPc();
|
||||
|
||||
int imm11 = (opCode >> 0) & 0x7ff;
|
||||
int j2 = (opCode >> 11) & 1;
|
||||
int j1 = (opCode >> 13) & 1;
|
||||
int imm6 = (opCode >> 16) & 0x3f;
|
||||
int s = (opCode >> 26) & 1;
|
||||
|
||||
int imm32 = imm11 | (imm6 << 11) | (j1 << 17) | (j2 << 18) | (s << 19);
|
||||
imm32 = (imm32 << 13) >> 12;
|
||||
|
||||
Immediate = pc + imm32;
|
||||
|
||||
Cond = (Condition)((opCode >> 22) & 0xf);
|
||||
}
|
||||
}
|
||||
}
|
35
ARMeilleure/Decoders/OpCodeT32BImm24.cs
Normal file
35
ARMeilleure/Decoders/OpCodeT32BImm24.cs
Normal file
@ -0,0 +1,35 @@
|
||||
using ARMeilleure.Instructions;
|
||||
|
||||
namespace ARMeilleure.Decoders
|
||||
{
|
||||
class OpCodeT32BImm24 : OpCodeT32, IOpCode32BImm
|
||||
{
|
||||
public long Immediate { get; }
|
||||
|
||||
public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32BImm24(inst, address, opCode);
|
||||
|
||||
public OpCodeT32BImm24(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode)
|
||||
{
|
||||
uint pc = GetPc();
|
||||
|
||||
if (inst.Name == InstName.Blx)
|
||||
{
|
||||
pc &= ~3u;
|
||||
}
|
||||
|
||||
int imm11 = (opCode >> 0) & 0x7ff;
|
||||
int j2 = (opCode >> 11) & 1;
|
||||
int j1 = (opCode >> 13) & 1;
|
||||
int imm10 = (opCode >> 16) & 0x3ff;
|
||||
int s = (opCode >> 26) & 1;
|
||||
|
||||
int i1 = j1 ^ s ^ 1;
|
||||
int i2 = j2 ^ s ^ 1;
|
||||
|
||||
int imm32 = imm11 | (imm10 << 11) | (i2 << 21) | (i1 << 22) | (s << 23);
|
||||
imm32 = (imm32 << 9) >> 8;
|
||||
|
||||
Immediate = pc + imm32;
|
||||
}
|
||||
}
|
||||
}
|
25
ARMeilleure/Decoders/OpCodeT32MemImm12.cs
Normal file
25
ARMeilleure/Decoders/OpCodeT32MemImm12.cs
Normal file
@ -0,0 +1,25 @@
|
||||
namespace ARMeilleure.Decoders
|
||||
{
|
||||
class OpCodeT32MemImm12 : OpCodeT32, IOpCode32Mem
|
||||
{
|
||||
public int Rt { get; }
|
||||
public int Rn { get; }
|
||||
public bool WBack => false;
|
||||
public bool IsLoad { get; }
|
||||
public bool Index => true;
|
||||
public bool Add => true;
|
||||
public int Immediate { get; }
|
||||
|
||||
public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32MemImm12(inst, address, opCode);
|
||||
|
||||
public OpCodeT32MemImm12(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode)
|
||||
{
|
||||
Rt = (opCode >> 12) & 0xf;
|
||||
Rn = (opCode >> 16) & 0xf;
|
||||
|
||||
Immediate = opCode & 0xfff;
|
||||
|
||||
IsLoad = ((opCode >> 20) & 1) != 0;
|
||||
}
|
||||
}
|
||||
}
|
29
ARMeilleure/Decoders/OpCodeT32MemImm8.cs
Normal file
29
ARMeilleure/Decoders/OpCodeT32MemImm8.cs
Normal file
@ -0,0 +1,29 @@
|
||||
namespace ARMeilleure.Decoders
|
||||
{
|
||||
class OpCodeT32MemImm8 : OpCodeT32, IOpCode32Mem
|
||||
{
|
||||
public int Rt { get; }
|
||||
public int Rn { get; }
|
||||
public bool WBack { get; }
|
||||
public bool IsLoad { get; }
|
||||
public bool Index { get; }
|
||||
public bool Add { get; }
|
||||
public int Immediate { get; }
|
||||
|
||||
public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32MemImm8(inst, address, opCode);
|
||||
|
||||
public OpCodeT32MemImm8(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode)
|
||||
{
|
||||
Rt = (opCode >> 12) & 0xf;
|
||||
Rn = (opCode >> 16) & 0xf;
|
||||
|
||||
Index = ((opCode >> 10) & 1) != 0;
|
||||
Add = ((opCode >> 9) & 1) != 0;
|
||||
WBack = ((opCode >> 8) & 1) != 0;
|
||||
|
||||
Immediate = opCode & 0xff;
|
||||
|
||||
IsLoad = ((opCode >> 20) & 1) != 0;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
using ARMeilleure.Instructions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
|
||||
namespace ARMeilleure.Decoders
|
||||
{
|
||||
@ -972,8 +973,7 @@ namespace ARMeilleure.Decoders
|
||||
SetA32("111100111x11<<10xxxx00011xx0xxxx", InstName.Vzip, InstEmit32.Vzip, OpCode32SimdCmpZ.Create);
|
||||
#endregion
|
||||
|
||||
#region "OpCode Table (AArch32, T16/T32)"
|
||||
// T16
|
||||
#region "OpCode Table (AArch32, T16)"
|
||||
SetT16("000<<xxxxxxxxxxx", InstName.Mov, InstEmit32.Mov, OpCodeT16ShiftImm.Create);
|
||||
SetT16("0001100xxxxxxxxx", InstName.Add, InstEmit32.Add, OpCodeT16AddSubReg.Create);
|
||||
SetT16("0001101xxxxxxxxx", InstName.Sub, InstEmit32.Sub, OpCodeT16AddSubReg.Create);
|
||||
@ -1045,6 +1045,72 @@ namespace ARMeilleure.Decoders
|
||||
SetT16("11100xxxxxxxxxxx", InstName.B, InstEmit32.B, OpCodeT16BImm11.Create);
|
||||
#endregion
|
||||
|
||||
#region "OpCode Table (AArch32, T32)"
|
||||
// Base
|
||||
SetT32("11101011010xxxxx0xxxxxxxxxxxxxxx", InstName.Adc, InstEmit32.Adc, OpCodeT32AluRsImm.Create);
|
||||
SetT32("11110x01010xxxxx0xxxxxxxxxxxxxxx", InstName.Adc, InstEmit32.Adc, OpCodeT32AluImm.Create);
|
||||
SetT32("11101011000<xxxx0xxx<<<<xxxxxxxx", InstName.Add, InstEmit32.Add, OpCodeT32AluRsImm.Create);
|
||||
SetT32("11110x01000<xxxx0xxx<<<<xxxxxxxx", InstName.Add, InstEmit32.Add, OpCodeT32AluImm.Create);
|
||||
SetT32("11101010000<xxxx0xxx<<<<xxxxxxxx", InstName.And, InstEmit32.And, OpCodeT32AluRsImm.Create);
|
||||
SetT32("11110x00000<xxxx0xxx<<<<xxxxxxxx", InstName.And, InstEmit32.And, OpCodeT32AluImm.Create);
|
||||
SetT32("11110x<<<xxxxxxx10x0xxxxxxxxxxxx", InstName.B, InstEmit32.B, OpCodeT32BImm20.Create);
|
||||
SetT32("11110xxxxxxxxxxx10x1xxxxxxxxxxxx", InstName.B, InstEmit32.B, OpCodeT32BImm24.Create);
|
||||
SetT32("11101010001xxxxx0xxxxxxxxxxxxxxx", InstName.Bic, InstEmit32.Bic, OpCodeT32AluRsImm.Create);
|
||||
SetT32("11110x00001xxxxx0xxxxxxxxxxxxxxx", InstName.Bic, InstEmit32.Bic, OpCodeT32AluImm.Create);
|
||||
SetT32("11110xxxxxxxxxxx11x1xxxxxxxxxxxx", InstName.Bl, InstEmit32.Bl, OpCodeT32BImm24.Create);
|
||||
SetT32("11110xxxxxxxxxxx11x0xxxxxxxxxxx0", InstName.Blx, InstEmit32.Blx, OpCodeT32BImm24.Create);
|
||||
SetT32("111010110001xxxx0xxx1111xxxxxxxx", InstName.Cmn, InstEmit32.Cmn, OpCodeT32AluRsImm.Create);
|
||||
SetT32("11110x010001xxxx0xxx1111xxxxxxxx", InstName.Cmn, InstEmit32.Cmn, OpCodeT32AluImm.Create);
|
||||
SetT32("111010111011xxxx0xxx1111xxxxxxxx", InstName.Cmp, InstEmit32.Cmp, OpCodeT32AluRsImm.Create);
|
||||
SetT32("11110x011011xxxx0xxx1111xxxxxxxx", InstName.Cmp, InstEmit32.Cmp, OpCodeT32AluImm.Create);
|
||||
SetT32("11101010100<xxxx0xxx<<<<xxxxxxxx", InstName.Eor, InstEmit32.Eor, OpCodeT32AluRsImm.Create);
|
||||
SetT32("11110x00100<xxxx0xxx<<<<xxxxxxxx", InstName.Eor, InstEmit32.Eor, OpCodeT32AluImm.Create);
|
||||
SetT32("111110000101xxxx<<<<10x1xxxxxxxx", InstName.Ldr, InstEmit32.Ldr, OpCodeT32MemImm8.Create);
|
||||
SetT32("111110000101xxxx<<<<1100xxxxxxxx", InstName.Ldr, InstEmit32.Ldr, OpCodeT32MemImm8.Create);
|
||||
SetT32("111110000101xxxx<<<<11x1xxxxxxxx", InstName.Ldr, InstEmit32.Ldr, OpCodeT32MemImm8.Create);
|
||||
SetT32("111110001101xxxxxxxxxxxxxxxxxxxx", InstName.Ldr, InstEmit32.Ldr, OpCodeT32MemImm12.Create);
|
||||
SetT32("111110000001xxxx<<<<10x1xxxxxxxx", InstName.Ldrb, InstEmit32.Ldrb, OpCodeT32MemImm8.Create);
|
||||
SetT32("111110000001xxxx<<<<1100xxxxxxxx", InstName.Ldrb, InstEmit32.Ldrb, OpCodeT32MemImm8.Create);
|
||||
SetT32("111110000001xxxx<<<<11x1xxxxxxxx", InstName.Ldrb, InstEmit32.Ldrb, OpCodeT32MemImm8.Create);
|
||||
SetT32("111110001001xxxxxxxxxxxxxxxxxxxx", InstName.Ldrb, InstEmit32.Ldrb, OpCodeT32MemImm12.Create);
|
||||
SetT32("111110000011xxxx<<<<10x1xxxxxxxx", InstName.Ldrh, InstEmit32.Ldrh, OpCodeT32MemImm8.Create);
|
||||
SetT32("111110000011xxxx<<<<1100xxxxxxxx", InstName.Ldrh, InstEmit32.Ldrh, OpCodeT32MemImm8.Create);
|
||||
SetT32("111110000011xxxx<<<<11x1xxxxxxxx", InstName.Ldrh, InstEmit32.Ldrh, OpCodeT32MemImm8.Create);
|
||||
SetT32("111110001011xxxxxxxxxxxxxxxxxxxx", InstName.Ldrh, InstEmit32.Ldrh, OpCodeT32MemImm12.Create);
|
||||
SetT32("111110010001xxxx<<<<10x1xxxxxxxx", InstName.Ldrsb, InstEmit32.Ldrsb, OpCodeT32MemImm8.Create);
|
||||
SetT32("111110010001xxxx<<<<1100xxxxxxxx", InstName.Ldrsb, InstEmit32.Ldrsb, OpCodeT32MemImm8.Create);
|
||||
SetT32("111110010001xxxx<<<<11x1xxxxxxxx", InstName.Ldrsb, InstEmit32.Ldrsb, OpCodeT32MemImm8.Create);
|
||||
SetT32("111110011001xxxxxxxxxxxxxxxxxxxx", InstName.Ldrsb, InstEmit32.Ldrsb, OpCodeT32MemImm12.Create);
|
||||
SetT32("111110010011xxxx<<<<10x1xxxxxxxx", InstName.Ldrsh, InstEmit32.Ldrsh, OpCodeT32MemImm8.Create);
|
||||
SetT32("111110010011xxxx<<<<1100xxxxxxxx", InstName.Ldrsh, InstEmit32.Ldrsh, OpCodeT32MemImm8.Create);
|
||||
SetT32("111110010011xxxx<<<<11x1xxxxxxxx", InstName.Ldrsh, InstEmit32.Ldrsh, OpCodeT32MemImm8.Create);
|
||||
SetT32("111110011011xxxxxxxxxxxxxxxxxxxx", InstName.Ldrsh, InstEmit32.Ldrsh, OpCodeT32MemImm12.Create);
|
||||
SetT32("11101010010x11110xxxxxxxxxxxxxxx", InstName.Mov, InstEmit32.Mov, OpCodeT32AluRsImm.Create);
|
||||
SetT32("11110x00010x11110xxxxxxxxxxxxxxx", InstName.Mov, InstEmit32.Mov, OpCodeT32AluImm.Create);
|
||||
SetT32("11101010011x11110xxxxxxxxxxxxxxx", InstName.Mvn, InstEmit32.Mvn, OpCodeT32AluRsImm.Create);
|
||||
SetT32("11110x00011x11110xxxxxxxxxxxxxxx", InstName.Mvn, InstEmit32.Mvn, OpCodeT32AluImm.Create);
|
||||
SetT32("11101010011x<<<<0xxxxxxxxxxxxxxx", InstName.Orn, InstEmit32.Orn, OpCodeT32AluRsImm.Create);
|
||||
SetT32("11110x00011x<<<<0xxxxxxxxxxxxxxx", InstName.Orn, InstEmit32.Orn, OpCodeT32AluImm.Create);
|
||||
SetT32("11101010010x<<<<0xxxxxxxxxxxxxxx", InstName.Orr, InstEmit32.Orr, OpCodeT32AluRsImm.Create);
|
||||
SetT32("11110x00010x<<<<0xxxxxxxxxxxxxxx", InstName.Orr, InstEmit32.Orr, OpCodeT32AluImm.Create);
|
||||
SetT32("11101011110xxxxx0xxxxxxxxxxxxxxx", InstName.Rsb, InstEmit32.Rsb, OpCodeT32AluRsImm.Create);
|
||||
SetT32("11110x01110xxxxx0xxxxxxxxxxxxxxx", InstName.Rsb, InstEmit32.Rsb, OpCodeT32AluImm.Create);
|
||||
SetT32("11101011011xxxxx0xxxxxxxxxxxxxxx", InstName.Sbc, InstEmit32.Sbc, OpCodeT32AluRsImm.Create);
|
||||
SetT32("11110x01011xxxxx0xxxxxxxxxxxxxxx", InstName.Sbc, InstEmit32.Sbc, OpCodeT32AluImm.Create);
|
||||
SetT32("111110000100xxxxxxxx1<<>xxxxxxxx", InstName.Str, InstEmit32.Str, OpCodeT32MemImm8.Create);
|
||||
SetT32("111110001100xxxxxxxxxxxxxxxxxxxx", InstName.Str, InstEmit32.Str, OpCodeT32MemImm12.Create);
|
||||
SetT32("111110000000xxxxxxxx1<<>xxxxxxxx", InstName.Strb, InstEmit32.Strb, OpCodeT32MemImm8.Create);
|
||||
SetT32("111110001000xxxxxxxxxxxxxxxxxxxx", InstName.Strb, InstEmit32.Strb, OpCodeT32MemImm12.Create);
|
||||
SetT32("111110000010xxxxxxxx1<<>xxxxxxxx", InstName.Strh, InstEmit32.Strh, OpCodeT32MemImm8.Create);
|
||||
SetT32("111110001010xxxxxxxxxxxxxxxxxxxx", InstName.Strh, InstEmit32.Strh, OpCodeT32MemImm12.Create);
|
||||
SetT32("11101011101<xxxx0xxx<<<<xxxxxxxx", InstName.Sub, InstEmit32.Sub, OpCodeT32AluRsImm.Create);
|
||||
SetT32("11110x01101<xxxx0xxx<<<<xxxxxxxx", InstName.Sub, InstEmit32.Sub, OpCodeT32AluImm.Create);
|
||||
SetT32("111010101001xxxx0xxx1111xxxxxxxx", InstName.Teq, InstEmit32.Teq, OpCodeT32AluRsImm.Create);
|
||||
SetT32("11110x001001xxxx0xxx1111xxxxxxxx", InstName.Teq, InstEmit32.Teq, OpCodeT32AluImm.Create);
|
||||
SetT32("111010100001xxxx0xxx1111xxxxxxxx", InstName.Tst, InstEmit32.Tst, OpCodeT32AluRsImm.Create);
|
||||
SetT32("11110x000001xxxx0xxx1111xxxxxxxx", InstName.Tst, InstEmit32.Tst, OpCodeT32AluImm.Create);
|
||||
#endregion
|
||||
|
||||
FillFastLookupTable(InstA32FastLookup, AllInstA32, ToFastLookupIndexA);
|
||||
FillFastLookupTable(InstT32FastLookup, AllInstT32, ToFastLookupIndexT);
|
||||
FillFastLookupTable(InstA64FastLookup, AllInstA64, ToFastLookupIndexA);
|
||||
@ -1092,8 +1158,11 @@ namespace ARMeilleure.Decoders
|
||||
|
||||
private static void SetT32(string encoding, InstName name, InstEmitter emitter, MakeOp makeOp)
|
||||
{
|
||||
encoding = encoding.Substring(16) + encoding.Substring(0, 16);
|
||||
Set(encoding, AllInstT32, new InstDescriptor(name, emitter), makeOp);
|
||||
string reversedEncoding = encoding.Substring(16) + encoding.Substring(0, 16);
|
||||
MakeOp reversedMakeOp =
|
||||
(InstDescriptor inst, ulong address, int opCode)
|
||||
=> makeOp(inst, address, (int)BitOperations.RotateRight((uint)opCode, 16));
|
||||
Set(reversedEncoding, AllInstT32, new InstDescriptor(name, emitter), reversedMakeOp);
|
||||
}
|
||||
|
||||
private static void SetA64(string encoding, InstName name, InstEmitter emitter, MakeOp makeOp)
|
||||
|
@ -244,6 +244,23 @@ namespace ARMeilleure.Instructions
|
||||
EmitAluStore(context, res);
|
||||
}
|
||||
|
||||
public static void Orn(ArmEmitterContext context)
|
||||
{
|
||||
IOpCode32Alu op = (IOpCode32Alu)context.CurrOp;
|
||||
|
||||
Operand n = GetAluN(context);
|
||||
Operand m = GetAluM(context);
|
||||
|
||||
Operand res = context.BitwiseOr(n, context.BitwiseNot(m));
|
||||
|
||||
if (ShouldSetFlags(context))
|
||||
{
|
||||
EmitNZFlagsCheck(context, res);
|
||||
}
|
||||
|
||||
EmitAluStore(context, res);
|
||||
}
|
||||
|
||||
public static void Pkh(ArmEmitterContext context)
|
||||
{
|
||||
OpCode32AluRsImm op = (OpCode32AluRsImm)context.CurrOp;
|
||||
|
@ -128,7 +128,7 @@ namespace ARMeilleure.Instructions
|
||||
{
|
||||
Debug.Assert(value.Type == OperandType.I32);
|
||||
|
||||
if (IsThumb(context.CurrOp))
|
||||
if (((OpCode32)context.CurrOp).IsThumb())
|
||||
{
|
||||
bool isReturn = IsA32Return(context);
|
||||
if (!isReturn)
|
||||
@ -197,7 +197,7 @@ namespace ARMeilleure.Instructions
|
||||
// ARM32.
|
||||
case IOpCode32AluImm op:
|
||||
{
|
||||
if (ShouldSetFlags(context) && op.IsRotated)
|
||||
if (ShouldSetFlags(context) && op.IsRotated && setCarry)
|
||||
{
|
||||
SetFlag(context, PState.CFlag, Const((uint)op.Immediate >> 31));
|
||||
}
|
||||
|
@ -9,18 +9,25 @@ namespace ARMeilleure.Instructions
|
||||
{
|
||||
public static void Brk(ArmEmitterContext context)
|
||||
{
|
||||
EmitExceptionCall(context, nameof(NativeInterface.Break));
|
||||
OpCodeException op = (OpCodeException)context.CurrOp;
|
||||
|
||||
string name = nameof(NativeInterface.Break);
|
||||
|
||||
context.StoreToContext();
|
||||
|
||||
context.Call(typeof(NativeInterface).GetMethod(name), Const(op.Address), Const(op.Id));
|
||||
|
||||
context.LoadFromContext();
|
||||
|
||||
context.Return(Const(op.Address));
|
||||
}
|
||||
|
||||
public static void Svc(ArmEmitterContext context)
|
||||
{
|
||||
EmitExceptionCall(context, nameof(NativeInterface.SupervisorCall));
|
||||
}
|
||||
|
||||
private static void EmitExceptionCall(ArmEmitterContext context, string name)
|
||||
{
|
||||
OpCodeException op = (OpCodeException)context.CurrOp;
|
||||
|
||||
string name = nameof(NativeInterface.SupervisorCall);
|
||||
|
||||
context.StoreToContext();
|
||||
|
||||
context.Call(typeof(NativeInterface).GetMethod(name), Const(op.Address), Const(op.Id));
|
||||
@ -41,6 +48,8 @@ namespace ARMeilleure.Instructions
|
||||
context.Call(typeof(NativeInterface).GetMethod(name), Const(op.Address), Const(op.RawOpCode));
|
||||
|
||||
context.LoadFromContext();
|
||||
|
||||
context.Return(Const(op.Address));
|
||||
}
|
||||
}
|
||||
}
|
@ -9,19 +9,11 @@ namespace ARMeilleure.Instructions
|
||||
static partial class InstEmit32
|
||||
{
|
||||
public static void Svc(ArmEmitterContext context)
|
||||
{
|
||||
EmitExceptionCall(context, nameof(NativeInterface.SupervisorCall));
|
||||
}
|
||||
|
||||
public static void Trap(ArmEmitterContext context)
|
||||
{
|
||||
EmitExceptionCall(context, nameof(NativeInterface.Break));
|
||||
}
|
||||
|
||||
private static void EmitExceptionCall(ArmEmitterContext context, string name)
|
||||
{
|
||||
IOpCode32Exception op = (IOpCode32Exception)context.CurrOp;
|
||||
|
||||
string name = nameof(NativeInterface.SupervisorCall);
|
||||
|
||||
context.StoreToContext();
|
||||
|
||||
context.Call(typeof(NativeInterface).GetMethod(name), Const(((IOpCode)op).Address), Const(op.Id));
|
||||
@ -30,5 +22,20 @@ namespace ARMeilleure.Instructions
|
||||
|
||||
Translator.EmitSynchronization(context);
|
||||
}
|
||||
|
||||
public static void Trap(ArmEmitterContext context)
|
||||
{
|
||||
IOpCode32Exception op = (IOpCode32Exception)context.CurrOp;
|
||||
|
||||
string name = nameof(NativeInterface.Break);
|
||||
|
||||
context.StoreToContext();
|
||||
|
||||
context.Call(typeof(NativeInterface).GetMethod(name), Const(((IOpCode)op).Address), Const(op.Id));
|
||||
|
||||
context.LoadFromContext();
|
||||
|
||||
context.Return(Const(context.CurrOp.Address));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ namespace ARMeilleure.Instructions
|
||||
|
||||
uint pc = op.GetPc();
|
||||
|
||||
bool isThumb = IsThumb(context.CurrOp);
|
||||
bool isThumb = ((OpCode32)context.CurrOp).IsThumb();
|
||||
|
||||
uint currentPc = isThumb
|
||||
? pc | 1
|
||||
@ -61,7 +61,7 @@ namespace ARMeilleure.Instructions
|
||||
Operand addr = context.Copy(GetIntA32(context, op.Rm));
|
||||
Operand bitOne = context.BitwiseAnd(addr, Const(1));
|
||||
|
||||
bool isThumb = IsThumb(context.CurrOp);
|
||||
bool isThumb = ((OpCode32)context.CurrOp).IsThumb();
|
||||
|
||||
uint currentPc = isThumb
|
||||
? (pc - 2) | 1
|
||||
|
@ -10,11 +10,6 @@ namespace ARMeilleure.Instructions
|
||||
{
|
||||
static class InstEmitHelper
|
||||
{
|
||||
public static bool IsThumb(OpCode op)
|
||||
{
|
||||
return op is OpCodeT16;
|
||||
}
|
||||
|
||||
public static Operand GetExtendedM(ArmEmitterContext context, int rm, IntType type)
|
||||
{
|
||||
Operand value = GetIntOrZR(context, rm);
|
||||
|
@ -130,11 +130,6 @@ namespace ARMeilleure.Instructions
|
||||
bool ordered = (accType & AccessType.Ordered) != 0;
|
||||
bool exclusive = (accType & AccessType.Exclusive) != 0;
|
||||
|
||||
if (ordered)
|
||||
{
|
||||
EmitBarrier(context);
|
||||
}
|
||||
|
||||
Operand address = context.Copy(GetIntOrSP(context, op.Rn));
|
||||
|
||||
Operand t = GetIntOrZR(context, op.Rt);
|
||||
@ -163,6 +158,11 @@ namespace ARMeilleure.Instructions
|
||||
{
|
||||
EmitStoreExclusive(context, address, t, exclusive, op.Size, op.Rs, a32: false);
|
||||
}
|
||||
|
||||
if (ordered)
|
||||
{
|
||||
EmitBarrier(context);
|
||||
}
|
||||
}
|
||||
|
||||
private static void EmitBarrier(ArmEmitterContext context)
|
||||
|
@ -146,13 +146,13 @@ namespace ARMeilleure.Instructions
|
||||
var exclusive = (accType & AccessType.Exclusive) != 0;
|
||||
var ordered = (accType & AccessType.Ordered) != 0;
|
||||
|
||||
if (ordered)
|
||||
{
|
||||
EmitBarrier(context);
|
||||
}
|
||||
|
||||
if ((accType & AccessType.Load) != 0)
|
||||
{
|
||||
if (ordered)
|
||||
{
|
||||
EmitBarrier(context);
|
||||
}
|
||||
|
||||
if (size == DWordSizeLog2)
|
||||
{
|
||||
// Keep loads atomic - make the call to get the whole region and then decompose it into parts
|
||||
@ -219,6 +219,11 @@ namespace ARMeilleure.Instructions
|
||||
Operand value = context.ZeroExtend32(OperandType.I64, GetIntA32(context, op.Rt));
|
||||
EmitStoreExclusive(context, address, value, exclusive, size, op.Rd, a32: true);
|
||||
}
|
||||
|
||||
if (ordered)
|
||||
{
|
||||
EmitBarrier(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -103,7 +103,7 @@ namespace ARMeilleure.Signal
|
||||
// Unix siginfo struct locations.
|
||||
// NOTE: These are incredibly likely to be different between kernel version and architectures.
|
||||
|
||||
config.StructAddressOffset = 16; // si_addr
|
||||
config.StructAddressOffset = OperatingSystem.IsMacOS() ? 24 : 16; // si_addr
|
||||
config.StructWriteOffset = 8; // si_code
|
||||
|
||||
_signalHandlerPtr = Marshal.GetFunctionPointerForDelegate(GenerateUnixSignalHandler(_handlerConfig));
|
||||
@ -191,7 +191,7 @@ namespace ARMeilleure.Signal
|
||||
// Is the fault address within this tracked region?
|
||||
Operand inRange = context.BitwiseAnd(
|
||||
context.ICompare(faultAddress, rangeAddress, Comparison.GreaterOrEqualUI),
|
||||
context.ICompare(faultAddress, rangeEndAddress, Comparison.Less)
|
||||
context.ICompare(faultAddress, rangeEndAddress, Comparison.LessUI)
|
||||
);
|
||||
|
||||
// Only call tracking if in range.
|
||||
|
@ -21,6 +21,7 @@ namespace ARMeilleure.Signal
|
||||
static class UnixSignalHandlerRegistration
|
||||
{
|
||||
private const int SIGSEGV = 11;
|
||||
private const int SIGBUS = 10;
|
||||
private const int SA_SIGINFO = 0x00000004;
|
||||
|
||||
[DllImport("libc", SetLastError = true)]
|
||||
@ -43,7 +44,17 @@ namespace ARMeilleure.Signal
|
||||
|
||||
if (result != 0)
|
||||
{
|
||||
throw new InvalidOperationException($"Could not register sigaction. Error: {result}");
|
||||
throw new InvalidOperationException($"Could not register SIGSEGV sigaction. Error: {result}");
|
||||
}
|
||||
|
||||
if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
result = sigaction(SIGBUS, ref sig, out SigAction oldb);
|
||||
|
||||
if (result != 0)
|
||||
{
|
||||
throw new InvalidOperationException($"Could not register SIGBUS sigaction. Error: {result}");
|
||||
}
|
||||
}
|
||||
|
||||
return old;
|
||||
@ -51,7 +62,7 @@ namespace ARMeilleure.Signal
|
||||
|
||||
public static bool RestoreExceptionHandler(SigAction oldAction)
|
||||
{
|
||||
return sigaction(SIGSEGV, ref oldAction, out SigAction _) == 0;
|
||||
return sigaction(SIGSEGV, ref oldAction, out SigAction _) == 0 && (!OperatingSystem.IsMacOS() || sigaction(SIGBUS, ref oldAction, out SigAction _) == 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -43,6 +43,12 @@ namespace ARMeilleure.State
|
||||
public long TpidrEl0 { get; set; }
|
||||
public long Tpidr { get; set; }
|
||||
|
||||
public uint Pstate
|
||||
{
|
||||
get => _nativeContext.GetPstate();
|
||||
set => _nativeContext.SetPstate(value);
|
||||
}
|
||||
|
||||
public FPCR Fpcr { get; set; }
|
||||
public FPSR Fpsr { get; set; }
|
||||
public FPCR StandardFpcrValue => (Fpcr & (FPCR.Ahp)) | FPCR.Dn | FPCR.Fz;
|
||||
|
@ -95,6 +95,25 @@ namespace ARMeilleure.State
|
||||
GetStorage().Flags[(int)flag] = value ? 1u : 0u;
|
||||
}
|
||||
|
||||
public unsafe uint GetPstate()
|
||||
{
|
||||
uint value = 0;
|
||||
for (int flag = 0; flag < RegisterConsts.FlagsCount; flag++)
|
||||
{
|
||||
value |= GetStorage().Flags[flag] != 0 ? 1u << flag : 0u;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public unsafe void SetPstate(uint value)
|
||||
{
|
||||
for (int flag = 0; flag < RegisterConsts.FlagsCount; flag++)
|
||||
{
|
||||
uint bit = 1u << flag;
|
||||
GetStorage().Flags[flag] = (value & bit) == bit ? 1u : 0u;
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe bool GetFPStateFlag(FPState flag)
|
||||
{
|
||||
if ((uint)flag >= RegisterConsts.FpFlagsCount)
|
||||
|
@ -8,7 +8,7 @@ namespace ARMeilleure.Translation
|
||||
/// </summary>
|
||||
/// <typeparam name="K">Key</typeparam>
|
||||
/// <typeparam name="V">Value</typeparam>
|
||||
public class IntervalTree<K, V> where K : IComparable<K>
|
||||
class IntervalTree<K, V> where K : IComparable<K>
|
||||
{
|
||||
private const int ArrayGrowthSize = 32;
|
||||
|
||||
@ -53,7 +53,7 @@ namespace ARMeilleure.Translation
|
||||
/// <returns>Number of intervals found</returns>
|
||||
public int Get(K start, K end, ref K[] overlaps, int overlapCount = 0)
|
||||
{
|
||||
GetValues(_root, start, end, ref overlaps, ref overlapCount);
|
||||
GetKeys(_root, start, end, ref overlaps, ref overlapCount);
|
||||
|
||||
return overlapCount;
|
||||
}
|
||||
@ -180,20 +180,20 @@ namespace ARMeilleure.Translation
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve all values that overlap the given start and end keys.
|
||||
/// Retrieve all keys that overlap the given start and end keys.
|
||||
/// </summary>
|
||||
/// <param name="start">Start of the range</param>
|
||||
/// <param name="end">End of the range</param>
|
||||
/// <param name="overlaps">Overlaps array to place results in</param>
|
||||
/// <param name="overlapCount">Overlaps count to update</param>
|
||||
private void GetValues(IntervalTreeNode<K, V> node, K start, K end, ref K[] overlaps, ref int overlapCount)
|
||||
private void GetKeys(IntervalTreeNode<K, V> node, K start, K end, ref K[] overlaps, ref int overlapCount)
|
||||
{
|
||||
if (node == null || start.CompareTo(node.Max) >= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GetValues(node.Left, start, end, ref overlaps, ref overlapCount);
|
||||
GetKeys(node.Left, start, end, ref overlaps, ref overlapCount);
|
||||
|
||||
bool endsOnRight = end.CompareTo(node.Start) > 0;
|
||||
if (endsOnRight)
|
||||
@ -208,7 +208,7 @@ namespace ARMeilleure.Translation
|
||||
overlaps[overlapCount++] = node.Start;
|
||||
}
|
||||
|
||||
GetValues(node.Right, start, end, ref overlaps, ref overlapCount);
|
||||
GetKeys(node.Right, start, end, ref overlaps, ref overlapCount);
|
||||
}
|
||||
}
|
||||
|
||||
@ -717,40 +717,40 @@ namespace ARMeilleure.Translation
|
||||
/// </summary>
|
||||
/// <typeparam name="K">Key type of the node</typeparam>
|
||||
/// <typeparam name="V">Value type of the node</typeparam>
|
||||
internal class IntervalTreeNode<K, V>
|
||||
class IntervalTreeNode<K, V>
|
||||
{
|
||||
internal bool Color = true;
|
||||
internal IntervalTreeNode<K, V> Left = null;
|
||||
internal IntervalTreeNode<K, V> Right = null;
|
||||
internal IntervalTreeNode<K, V> Parent = null;
|
||||
public bool Color = true;
|
||||
public IntervalTreeNode<K, V> Left = null;
|
||||
public IntervalTreeNode<K, V> Right = null;
|
||||
public IntervalTreeNode<K, V> Parent = null;
|
||||
|
||||
/// <summary>
|
||||
/// The start of the range.
|
||||
/// </summary>
|
||||
internal K Start;
|
||||
public K Start;
|
||||
|
||||
/// <summary>
|
||||
/// The end of the range.
|
||||
/// </summary>
|
||||
internal K End;
|
||||
public K End;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum end value of this node and all its children.
|
||||
/// </summary>
|
||||
internal K Max;
|
||||
public K Max;
|
||||
|
||||
/// <summary>
|
||||
/// Value stored on this node.
|
||||
/// </summary>
|
||||
internal V Value;
|
||||
public V Value;
|
||||
|
||||
public IntervalTreeNode(K start, K end, V value, IntervalTreeNode<K, V> parent)
|
||||
{
|
||||
this.Start = start;
|
||||
this.End = end;
|
||||
this.Max = end;
|
||||
this.Value = value;
|
||||
this.Parent = parent;
|
||||
Start = start;
|
||||
End = end;
|
||||
Max = end;
|
||||
Value = value;
|
||||
Parent = parent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ namespace ARMeilleure.Translation.PTC
|
||||
private const string OuterHeaderMagicString = "PTCohd\0\0";
|
||||
private const string InnerHeaderMagicString = "PTCihd\0\0";
|
||||
|
||||
private const uint InternalVersion = 3138; //! To be incremented manually for each change to the ARMeilleure project.
|
||||
private const uint InternalVersion = 3267; //! To be incremented manually for each change to the ARMeilleure project.
|
||||
|
||||
private const string ActualDir = "0";
|
||||
private const string BackupDir = "1";
|
||||
|
@ -18,8 +18,10 @@
|
||||
using Ryujinx.Audio.Renderer.Dsp.State;
|
||||
using Ryujinx.Audio.Renderer.Parameter.Effect;
|
||||
using Ryujinx.Audio.Renderer.Server.Effect;
|
||||
using Ryujinx.Audio.Renderer.Utils.Math;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
@ -45,7 +47,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
|
||||
private const int FixedPointPrecision = 14;
|
||||
|
||||
public DelayCommand(uint bufferOffset, DelayParameter parameter, Memory<DelayState> state, bool isEnabled, ulong workBuffer, int nodeId)
|
||||
public DelayCommand(uint bufferOffset, DelayParameter parameter, Memory<DelayState> state, bool isEnabled, ulong workBuffer, int nodeId, bool newEffectChannelMappingSupported)
|
||||
{
|
||||
Enabled = true;
|
||||
NodeId = nodeId;
|
||||
@ -63,9 +65,14 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
InputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Input[i]);
|
||||
OutputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Output[i]);
|
||||
}
|
||||
|
||||
// NOTE: We do the opposite as Nintendo here for now to restore previous behaviour
|
||||
// TODO: Update delay processing and remove this to use RemapLegacyChannelEffectMappingToChannelResourceMapping.
|
||||
DataSourceHelper.RemapChannelResourceMappingToLegacy(newEffectChannelMappingSupported, InputBufferIndices);
|
||||
DataSourceHelper.RemapChannelResourceMappingToLegacy(newEffectChannelMappingSupported, OutputBufferIndices);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private unsafe void ProcessDelayMono(ref DelayState state, float* outputBuffer, float* inputBuffer, uint sampleCount)
|
||||
{
|
||||
float feedbackGain = FixedPointHelper.ToFloat(Parameter.FeedbackGain, FixedPointPrecision);
|
||||
@ -78,133 +85,148 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
float input = inputBuffer[i] * 64;
|
||||
float delayLineValue = state.DelayLines[0].Read();
|
||||
|
||||
float lowPassResult = (input * inGain + delayLineValue * feedbackGain) * state.LowPassBaseGain + state.LowPassZ[0] * state.LowPassFeedbackGain;
|
||||
float temp = input * inGain + delayLineValue * feedbackGain;
|
||||
|
||||
state.LowPassZ[0] = lowPassResult;
|
||||
|
||||
state.DelayLines[0].Update(lowPassResult);
|
||||
state.UpdateLowPassFilter(ref temp, 1);
|
||||
|
||||
outputBuffer[i] = (input * dryGain + delayLineValue * outGain) / 64;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private unsafe void ProcessDelayStereo(ref DelayState state, Span<IntPtr> outputBuffers, ReadOnlySpan<IntPtr> inputBuffers, uint sampleCount)
|
||||
{
|
||||
const ushort channelCount = 2;
|
||||
|
||||
Span<float> channelInput = stackalloc float[channelCount];
|
||||
Span<float> delayLineValues = stackalloc float[channelCount];
|
||||
Span<float> temp = stackalloc float[channelCount];
|
||||
|
||||
float delayFeedbackBaseGain = state.DelayFeedbackBaseGain;
|
||||
float delayFeedbackCrossGain = state.DelayFeedbackCrossGain;
|
||||
float inGain = FixedPointHelper.ToFloat(Parameter.InGain, FixedPointPrecision);
|
||||
float dryGain = FixedPointHelper.ToFloat(Parameter.DryGain, FixedPointPrecision);
|
||||
float outGain = FixedPointHelper.ToFloat(Parameter.OutGain, FixedPointPrecision);
|
||||
|
||||
Matrix2x2 delayFeedback = new Matrix2x2(delayFeedbackBaseGain , delayFeedbackCrossGain,
|
||||
delayFeedbackCrossGain, delayFeedbackBaseGain);
|
||||
|
||||
for (int i = 0; i < sampleCount; i++)
|
||||
{
|
||||
for (int j = 0; j < channelCount; j++)
|
||||
Vector2 channelInput = new Vector2
|
||||
{
|
||||
channelInput[j] = *((float*)inputBuffers[j] + i) * 64;
|
||||
delayLineValues[j] = state.DelayLines[j].Read();
|
||||
}
|
||||
X = *((float*)inputBuffers[0] + i) * 64,
|
||||
Y = *((float*)inputBuffers[1] + i) * 64,
|
||||
};
|
||||
|
||||
temp[0] = channelInput[0] * inGain + delayLineValues[1] * delayFeedbackCrossGain + delayLineValues[0] * delayFeedbackBaseGain;
|
||||
temp[1] = channelInput[1] * inGain + delayLineValues[0] * delayFeedbackCrossGain + delayLineValues[1] * delayFeedbackBaseGain;
|
||||
|
||||
for (int j = 0; j < channelCount; j++)
|
||||
Vector2 delayLineValues = new Vector2()
|
||||
{
|
||||
float lowPassResult = state.LowPassFeedbackGain * state.LowPassZ[j] + temp[j] * state.LowPassBaseGain;
|
||||
X = state.DelayLines[0].Read(),
|
||||
Y = state.DelayLines[1].Read(),
|
||||
};
|
||||
|
||||
state.LowPassZ[j] = lowPassResult;
|
||||
state.DelayLines[j].Update(lowPassResult);
|
||||
Vector2 temp = MatrixHelper.Transform(ref channelInput, ref delayFeedback) + channelInput * inGain;
|
||||
|
||||
*((float*)outputBuffers[j] + i) = (channelInput[j] * dryGain + delayLineValues[j] * outGain) / 64;
|
||||
}
|
||||
state.UpdateLowPassFilter(ref Unsafe.As<Vector2, float>(ref temp), channelCount);
|
||||
|
||||
*((float*)outputBuffers[0] + i) = (channelInput.X * dryGain + delayLineValues.X * outGain) / 64;
|
||||
*((float*)outputBuffers[1] + i) = (channelInput.Y * dryGain + delayLineValues.Y * outGain) / 64;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private unsafe void ProcessDelayQuadraphonic(ref DelayState state, Span<IntPtr> outputBuffers, ReadOnlySpan<IntPtr> inputBuffers, uint sampleCount)
|
||||
{
|
||||
const ushort channelCount = 4;
|
||||
|
||||
Span<float> channelInput = stackalloc float[channelCount];
|
||||
Span<float> delayLineValues = stackalloc float[channelCount];
|
||||
Span<float> temp = stackalloc float[channelCount];
|
||||
|
||||
float delayFeedbackBaseGain = state.DelayFeedbackBaseGain;
|
||||
float delayFeedbackCrossGain = state.DelayFeedbackCrossGain;
|
||||
float inGain = FixedPointHelper.ToFloat(Parameter.InGain, FixedPointPrecision);
|
||||
float dryGain = FixedPointHelper.ToFloat(Parameter.DryGain, FixedPointPrecision);
|
||||
float outGain = FixedPointHelper.ToFloat(Parameter.OutGain, FixedPointPrecision);
|
||||
|
||||
Matrix4x4 delayFeedback = new Matrix4x4(delayFeedbackBaseGain , delayFeedbackCrossGain, delayFeedbackCrossGain, 0.0f,
|
||||
delayFeedbackCrossGain, delayFeedbackBaseGain , 0.0f , delayFeedbackCrossGain,
|
||||
delayFeedbackCrossGain, 0.0f , delayFeedbackBaseGain , delayFeedbackCrossGain,
|
||||
0.0f , delayFeedbackCrossGain, delayFeedbackCrossGain, delayFeedbackBaseGain);
|
||||
|
||||
|
||||
for (int i = 0; i < sampleCount; i++)
|
||||
{
|
||||
for (int j = 0; j < channelCount; j++)
|
||||
Vector4 channelInput = new Vector4
|
||||
{
|
||||
channelInput[j] = *((float*)inputBuffers[j] + i) * 64;
|
||||
delayLineValues[j] = state.DelayLines[j].Read();
|
||||
}
|
||||
X = *((float*)inputBuffers[0] + i) * 64,
|
||||
Y = *((float*)inputBuffers[1] + i) * 64,
|
||||
Z = *((float*)inputBuffers[2] + i) * 64,
|
||||
W = *((float*)inputBuffers[3] + i) * 64
|
||||
};
|
||||
|
||||
temp[0] = channelInput[0] * inGain + (delayLineValues[2] + delayLineValues[1]) * delayFeedbackCrossGain + delayLineValues[0] * delayFeedbackBaseGain;
|
||||
temp[1] = channelInput[1] * inGain + (delayLineValues[0] + delayLineValues[3]) * delayFeedbackCrossGain + delayLineValues[1] * delayFeedbackBaseGain;
|
||||
temp[2] = channelInput[2] * inGain + (delayLineValues[3] + delayLineValues[0]) * delayFeedbackCrossGain + delayLineValues[2] * delayFeedbackBaseGain;
|
||||
temp[3] = channelInput[3] * inGain + (delayLineValues[1] + delayLineValues[2]) * delayFeedbackCrossGain + delayLineValues[3] * delayFeedbackBaseGain;
|
||||
|
||||
for (int j = 0; j < channelCount; j++)
|
||||
Vector4 delayLineValues = new Vector4()
|
||||
{
|
||||
float lowPassResult = state.LowPassFeedbackGain * state.LowPassZ[j] + temp[j] * state.LowPassBaseGain;
|
||||
X = state.DelayLines[0].Read(),
|
||||
Y = state.DelayLines[1].Read(),
|
||||
Z = state.DelayLines[2].Read(),
|
||||
W = state.DelayLines[3].Read()
|
||||
};
|
||||
|
||||
state.LowPassZ[j] = lowPassResult;
|
||||
state.DelayLines[j].Update(lowPassResult);
|
||||
Vector4 temp = MatrixHelper.Transform(ref channelInput, ref delayFeedback) + channelInput * inGain;
|
||||
|
||||
state.UpdateLowPassFilter(ref Unsafe.As<Vector4, float>(ref temp), channelCount);
|
||||
|
||||
*((float*)outputBuffers[j] + i) = (channelInput[j] * dryGain + delayLineValues[j] * outGain) / 64;
|
||||
}
|
||||
*((float*)outputBuffers[0] + i) = (channelInput.X * dryGain + delayLineValues.X * outGain) / 64;
|
||||
*((float*)outputBuffers[1] + i) = (channelInput.Y * dryGain + delayLineValues.Y * outGain) / 64;
|
||||
*((float*)outputBuffers[2] + i) = (channelInput.Z * dryGain + delayLineValues.Z * outGain) / 64;
|
||||
*((float*)outputBuffers[3] + i) = (channelInput.W * dryGain + delayLineValues.W * outGain) / 64;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private unsafe void ProcessDelaySurround(ref DelayState state, Span<IntPtr> outputBuffers, ReadOnlySpan<IntPtr> inputBuffers, uint sampleCount)
|
||||
{
|
||||
const ushort channelCount = 6;
|
||||
|
||||
Span<float> channelInput = stackalloc float[channelCount];
|
||||
Span<float> delayLineValues = stackalloc float[channelCount];
|
||||
Span<float> temp = stackalloc float[channelCount];
|
||||
|
||||
float feedbackGain = FixedPointHelper.ToFloat(Parameter.FeedbackGain, FixedPointPrecision);
|
||||
float delayFeedbackBaseGain = state.DelayFeedbackBaseGain;
|
||||
float delayFeedbackCrossGain = state.DelayFeedbackCrossGain;
|
||||
float inGain = FixedPointHelper.ToFloat(Parameter.InGain, FixedPointPrecision);
|
||||
float dryGain = FixedPointHelper.ToFloat(Parameter.DryGain, FixedPointPrecision);
|
||||
float outGain = FixedPointHelper.ToFloat(Parameter.OutGain, FixedPointPrecision);
|
||||
|
||||
Matrix6x6 delayFeedback = new Matrix6x6(delayFeedbackBaseGain , 0.0f , 0.0f , 0.0f , delayFeedbackCrossGain, delayFeedbackCrossGain,
|
||||
0.0f , delayFeedbackBaseGain , 0.0f , delayFeedbackCrossGain, delayFeedbackCrossGain, 0.0f ,
|
||||
delayFeedbackCrossGain, 0.0f , delayFeedbackBaseGain , delayFeedbackCrossGain, 0.0f , 0.0f ,
|
||||
0.0f , delayFeedbackCrossGain, delayFeedbackCrossGain, delayFeedbackBaseGain , 0.0f , 0.0f ,
|
||||
delayFeedbackCrossGain, delayFeedbackCrossGain, 0.0f , 0.0f , delayFeedbackBaseGain , 0.0f ,
|
||||
0.0f , 0.0f , 0.0f , 0.0f , 0.0f , feedbackGain);
|
||||
|
||||
for (int i = 0; i < sampleCount; i++)
|
||||
{
|
||||
for (int j = 0; j < channelCount; j++)
|
||||
Vector6 channelInput = new Vector6
|
||||
{
|
||||
channelInput[j] = *((float*)inputBuffers[j] + i) * 64;
|
||||
delayLineValues[j] = state.DelayLines[j].Read();
|
||||
}
|
||||
X = *((float*)inputBuffers[0] + i) * 64,
|
||||
Y = *((float*)inputBuffers[1] + i) * 64,
|
||||
Z = *((float*)inputBuffers[2] + i) * 64,
|
||||
W = *((float*)inputBuffers[3] + i) * 64,
|
||||
V = *((float*)inputBuffers[4] + i) * 64,
|
||||
U = *((float*)inputBuffers[5] + i) * 64
|
||||
};
|
||||
|
||||
temp[0] = channelInput[0] * inGain + (delayLineValues[2] + delayLineValues[4]) * delayFeedbackCrossGain + delayLineValues[0] * delayFeedbackBaseGain;
|
||||
temp[1] = channelInput[1] * inGain + (delayLineValues[4] + delayLineValues[3]) * delayFeedbackCrossGain + delayLineValues[1] * delayFeedbackBaseGain;
|
||||
temp[2] = channelInput[2] * inGain + (delayLineValues[3] + delayLineValues[0]) * delayFeedbackCrossGain + delayLineValues[2] * delayFeedbackBaseGain;
|
||||
temp[3] = channelInput[3] * inGain + (delayLineValues[1] + delayLineValues[2]) * delayFeedbackCrossGain + delayLineValues[3] * delayFeedbackBaseGain;
|
||||
temp[4] = channelInput[4] * inGain + (delayLineValues[0] + delayLineValues[1]) * delayFeedbackCrossGain + delayLineValues[4] * delayFeedbackBaseGain;
|
||||
temp[5] = channelInput[5] * inGain + delayLineValues[5] * delayFeedbackBaseGain;
|
||||
|
||||
for (int j = 0; j < channelCount; j++)
|
||||
Vector6 delayLineValues = new Vector6
|
||||
{
|
||||
float lowPassResult = state.LowPassFeedbackGain * state.LowPassZ[j] + temp[j] * state.LowPassBaseGain;
|
||||
X = state.DelayLines[0].Read(),
|
||||
Y = state.DelayLines[1].Read(),
|
||||
Z = state.DelayLines[2].Read(),
|
||||
W = state.DelayLines[3].Read(),
|
||||
V = state.DelayLines[4].Read(),
|
||||
U = state.DelayLines[5].Read()
|
||||
};
|
||||
|
||||
state.LowPassZ[j] = lowPassResult;
|
||||
state.DelayLines[j].Update(lowPassResult);
|
||||
Vector6 temp = MatrixHelper.Transform(ref channelInput, ref delayFeedback) + channelInput * inGain;
|
||||
|
||||
*((float*)outputBuffers[j] + i) = (channelInput[j] * dryGain + delayLineValues[j] * outGain) / 64;
|
||||
}
|
||||
state.UpdateLowPassFilter(ref Unsafe.As<Vector6, float>(ref temp), channelCount);
|
||||
|
||||
*((float*)outputBuffers[0] + i) = (channelInput.X * dryGain + delayLineValues.X * outGain) / 64;
|
||||
*((float*)outputBuffers[1] + i) = (channelInput.Y * dryGain + delayLineValues.Y * outGain) / 64;
|
||||
*((float*)outputBuffers[2] + i) = (channelInput.Z * dryGain + delayLineValues.Z * outGain) / 64;
|
||||
*((float*)outputBuffers[3] + i) = (channelInput.W * dryGain + delayLineValues.W * outGain) / 64;
|
||||
*((float*)outputBuffers[4] + i) = (channelInput.V * dryGain + delayLineValues.V * outGain) / 64;
|
||||
*((float*)outputBuffers[5] + i) = (channelInput.U * dryGain + delayLineValues.U * outGain) / 64;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -63,7 +63,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
|
||||
private Reverb3dParameter _parameter;
|
||||
|
||||
public Reverb3dCommand(uint bufferOffset, Reverb3dParameter parameter, Memory<Reverb3dState> state, bool isEnabled, ulong workBuffer, int nodeId)
|
||||
public Reverb3dCommand(uint bufferOffset, Reverb3dParameter parameter, Memory<Reverb3dState> state, bool isEnabled, ulong workBuffer, int nodeId, bool newEffectChannelMappingSupported)
|
||||
{
|
||||
Enabled = true;
|
||||
IsEffectEnabled = isEnabled;
|
||||
@ -80,6 +80,11 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
InputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Input[i]);
|
||||
OutputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Output[i]);
|
||||
}
|
||||
|
||||
// NOTE: We do the opposite as Nintendo here for now to restore previous behaviour
|
||||
// TODO: Update reverb 3d processing and remove this to use RemapLegacyChannelEffectMappingToChannelResourceMapping.
|
||||
DataSourceHelper.RemapChannelResourceMappingToLegacy(newEffectChannelMappingSupported, InputBufferIndices);
|
||||
DataSourceHelper.RemapChannelResourceMappingToLegacy(newEffectChannelMappingSupported, OutputBufferIndices);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
@ -194,7 +199,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
|
||||
if (isSurround)
|
||||
{
|
||||
*((float*)outputBuffers[4] + sampleIndex) += (outputValues[4] + state.BackLeftDelayLine.Update((values[2] - values[3]) * 0.5f) + channelInput[4] * state.DryGain);
|
||||
*((float*)outputBuffers[4] + sampleIndex) += (outputValues[4] + state.FrontCenterDelayLine.Update((values[2] - values[3]) * 0.5f) + channelInput[4] * state.DryGain);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -66,7 +66,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
|
||||
private const int FixedPointPrecision = 14;
|
||||
|
||||
public ReverbCommand(uint bufferOffset, ReverbParameter parameter, Memory<ReverbState> state, bool isEnabled, ulong workBuffer, int nodeId, bool isLongSizePreDelaySupported)
|
||||
public ReverbCommand(uint bufferOffset, ReverbParameter parameter, Memory<ReverbState> state, bool isEnabled, ulong workBuffer, int nodeId, bool isLongSizePreDelaySupported, bool newEffectChannelMappingSupported)
|
||||
{
|
||||
Enabled = true;
|
||||
IsEffectEnabled = isEnabled;
|
||||
@ -85,6 +85,11 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
}
|
||||
|
||||
IsLongSizePreDelaySupported = isLongSizePreDelaySupported;
|
||||
|
||||
// NOTE: We do the opposite as Nintendo here for now to restore previous behaviour
|
||||
// TODO: Update reverb processing and remove this to use RemapLegacyChannelEffectMappingToChannelResourceMapping.
|
||||
DataSourceHelper.RemapChannelResourceMappingToLegacy(newEffectChannelMappingSupported, InputBufferIndices);
|
||||
DataSourceHelper.RemapChannelResourceMappingToLegacy(newEffectChannelMappingSupported, OutputBufferIndices);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
@ -214,7 +219,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
|
||||
if (isSurround)
|
||||
{
|
||||
outputValues[4] += state.BackLeftDelayLine.Update((feedbackOutputValues[2] - feedbackOutputValues[3]) * 0.5f);
|
||||
outputValues[4] += state.FrontCenterDelayLine.Update((feedbackOutputValues[2] - feedbackOutputValues[3]) * 0.5f);
|
||||
}
|
||||
|
||||
for (int channelIndex = 0; channelIndex < Parameter.ChannelCount; channelIndex++)
|
||||
|
@ -445,5 +445,39 @@ namespace Ryujinx.Audio.Renderer.Dsp
|
||||
ToIntSlow(output, input, sampleCount);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void RemapLegacyChannelEffectMappingToChannelResourceMapping(bool isSupported, Span<ushort> bufferIndices)
|
||||
{
|
||||
if (!isSupported && bufferIndices.Length == 6)
|
||||
{
|
||||
ushort backLeft = bufferIndices[2];
|
||||
ushort backRight = bufferIndices[3];
|
||||
ushort frontCenter = bufferIndices[4];
|
||||
ushort lowFrequency = bufferIndices[5];
|
||||
|
||||
bufferIndices[2] = frontCenter;
|
||||
bufferIndices[3] = lowFrequency;
|
||||
bufferIndices[4] = backLeft;
|
||||
bufferIndices[5] = backRight;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void RemapChannelResourceMappingToLegacy(bool isSupported, Span<ushort> bufferIndices)
|
||||
{
|
||||
if (isSupported && bufferIndices.Length == 6)
|
||||
{
|
||||
ushort frontCenter = bufferIndices[2];
|
||||
ushort lowFrequency = bufferIndices[3];
|
||||
ushort backLeft = bufferIndices[4];
|
||||
ushort backRight = bufferIndices[5];
|
||||
|
||||
bufferIndices[2] = backLeft;
|
||||
bufferIndices[3] = backRight;
|
||||
bufferIndices[4] = frontCenter;
|
||||
bufferIndices[5] = lowFrequency;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,7 @@
|
||||
|
||||
using Ryujinx.Audio.Renderer.Dsp.Effect;
|
||||
using Ryujinx.Audio.Renderer.Parameter.Effect;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp.State
|
||||
{
|
||||
@ -43,7 +44,6 @@ namespace Ryujinx.Audio.Renderer.Dsp.State
|
||||
{
|
||||
DelayLines[i] = new DelayLine(sampleRate, parameter.DelayTimeMax);
|
||||
DelayLines[i].SetDelay(parameter.DelayTime);
|
||||
LowPassZ[0] = 0;
|
||||
}
|
||||
|
||||
UpdateParameter(ref parameter);
|
||||
@ -69,5 +69,16 @@ namespace Ryujinx.Audio.Renderer.Dsp.State
|
||||
LowPassFeedbackGain = 0.95f * FixedPointHelper.ToFloat(parameter.LowPassAmount, FixedPointPrecision);
|
||||
LowPassBaseGain = 1.0f - LowPassFeedbackGain;
|
||||
}
|
||||
|
||||
public void UpdateLowPassFilter(ref float tempRawRef, uint channelCount)
|
||||
{
|
||||
for (int i = 0; i < channelCount; i++)
|
||||
{
|
||||
float lowPassResult = LowPassFeedbackGain * LowPassZ[i] + Unsafe.Add(ref tempRawRef, i) * LowPassBaseGain;
|
||||
|
||||
LowPassZ[i] = lowPassResult;
|
||||
DelayLines[i].Update(lowPassResult);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.State
|
||||
public DecayDelay[] DecayDelays1 { get; }
|
||||
public DecayDelay[] DecayDelays2 { get; }
|
||||
public IDelayLine PreDelayLine { get; }
|
||||
public IDelayLine BackLeftDelayLine { get; }
|
||||
public IDelayLine FrontCenterDelayLine { get; }
|
||||
public float DryGain { get; private set; }
|
||||
public uint[] EarlyDelayTime { get; private set; }
|
||||
public float PreviousPreDelayValue { get; set; }
|
||||
@ -69,7 +69,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.State
|
||||
}
|
||||
|
||||
PreDelayLine = new DelayLine3d(sampleRate, 400);
|
||||
BackLeftDelayLine = new DelayLine3d(sampleRate, 5);
|
||||
FrontCenterDelayLine = new DelayLine3d(sampleRate, 5);
|
||||
|
||||
UpdateParameter(ref parameter);
|
||||
}
|
||||
|
@ -97,7 +97,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.State
|
||||
public DelayLine[] FdnDelayLines { get; }
|
||||
public DecayDelay[] DecayDelays { get; }
|
||||
public DelayLine PreDelayLine { get; }
|
||||
public DelayLine BackLeftDelayLine { get; }
|
||||
public DelayLine FrontCenterDelayLine { get; }
|
||||
public uint[] EarlyDelayTime { get; }
|
||||
public float[] EarlyGain { get; }
|
||||
public uint PreDelayLineDelayTime { get; private set; }
|
||||
@ -149,7 +149,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.State
|
||||
}
|
||||
|
||||
PreDelayLine = new DelayLine(sampleRate, preDelayTimeMax);
|
||||
BackLeftDelayLine = new DelayLine(sampleRate, 5.0f);
|
||||
FrontCenterDelayLine = new DelayLine(sampleRate, 5.0f);
|
||||
|
||||
UpdateParameter(ref parameter);
|
||||
}
|
||||
|
@ -363,6 +363,9 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||
case 4:
|
||||
_commandProcessingTimeEstimator = new CommandProcessingTimeEstimatorVersion4(_sampleCount, _mixBufferCount);
|
||||
break;
|
||||
case 5:
|
||||
_commandProcessingTimeEstimator = new CommandProcessingTimeEstimatorVersion5(_sampleCount, _mixBufferCount);
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException($"Unsupported processing time estimator version {_behaviourContext.GetCommandProcessingTimeEstimatorVersion()}.");
|
||||
}
|
||||
|
@ -107,10 +107,18 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||
/// <remarks>This was added in system update 13.0.0</remarks>
|
||||
public const int Revision10 = 10 << 24;
|
||||
|
||||
/// <summary>
|
||||
/// REV11:
|
||||
/// The "legacy" effects (Delay, Reverb and Reverb 3D) were updated to match the standard channel mapping used by the audio renderer.
|
||||
/// A new version of the command estimator was added to address timing changes caused by the legacy effects changes.
|
||||
/// </summary>
|
||||
/// <remarks>This was added in system update 14.0.0</remarks>
|
||||
public const int Revision11 = 11 << 24;
|
||||
|
||||
/// <summary>
|
||||
/// Last revision supported by the implementation.
|
||||
/// </summary>
|
||||
public const int LastRevision = Revision10;
|
||||
public const int LastRevision = Revision11;
|
||||
|
||||
/// <summary>
|
||||
/// Target revision magic supported by the implementation.
|
||||
@ -366,12 +374,26 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||
return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision10);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the audio renderer should support new channel resource mapping for 5.1 on Delay, Reverb and Reverb 3D effects.
|
||||
/// </summary>
|
||||
/// <returns>True if the audio renderer support new channel resource mapping for 5.1.</returns>
|
||||
public bool IsNewEffectChannelMappingSupported()
|
||||
{
|
||||
return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision11);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the version of the <see cref="ICommandProcessingTimeEstimator"/>.
|
||||
/// </summary>
|
||||
/// <returns>The version of the <see cref="ICommandProcessingTimeEstimator"/>.</returns>
|
||||
public int GetCommandProcessingTimeEstimatorVersion()
|
||||
{
|
||||
if (CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision11))
|
||||
{
|
||||
return 5;
|
||||
}
|
||||
|
||||
if (CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision10))
|
||||
{
|
||||
return 4;
|
||||
|
@ -336,11 +336,12 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||
/// <param name="workBuffer">The work buffer to use for processing.</param>
|
||||
/// <param name="nodeId">The node id associated to this command.</param>
|
||||
/// <param name="isLongSizePreDelaySupported">If set to true, the long size pre-delay is supported.</param>
|
||||
public void GenerateReverbEffect(uint bufferOffset, ReverbParameter parameter, Memory<ReverbState> state, bool isEnabled, CpuAddress workBuffer, int nodeId, bool isLongSizePreDelaySupported)
|
||||
/// <param name="newEffectChannelMappingSupported">If set to true, the new effect channel mapping for 5.1 is supported.</param>
|
||||
public void GenerateReverbEffect(uint bufferOffset, ReverbParameter parameter, Memory<ReverbState> state, bool isEnabled, CpuAddress workBuffer, int nodeId, bool isLongSizePreDelaySupported, bool newEffectChannelMappingSupported)
|
||||
{
|
||||
if (parameter.IsChannelCountValid())
|
||||
{
|
||||
ReverbCommand command = new ReverbCommand(bufferOffset, parameter, state, isEnabled, workBuffer, nodeId, isLongSizePreDelaySupported);
|
||||
ReverbCommand command = new ReverbCommand(bufferOffset, parameter, state, isEnabled, workBuffer, nodeId, isLongSizePreDelaySupported, newEffectChannelMappingSupported);
|
||||
|
||||
command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
|
||||
|
||||
@ -357,11 +358,12 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||
/// <param name="isEnabled">Set to true if the effect should be active.</param>
|
||||
/// <param name="workBuffer">The work buffer to use for processing.</param>
|
||||
/// <param name="nodeId">The node id associated to this command.</param>
|
||||
public void GenerateReverb3dEffect(uint bufferOffset, Reverb3dParameter parameter, Memory<Reverb3dState> state, bool isEnabled, CpuAddress workBuffer, int nodeId)
|
||||
/// <param name="newEffectChannelMappingSupported">If set to true, the new effect channel mapping for 5.1 is supported.</param>
|
||||
public void GenerateReverb3dEffect(uint bufferOffset, Reverb3dParameter parameter, Memory<Reverb3dState> state, bool isEnabled, CpuAddress workBuffer, int nodeId, bool newEffectChannelMappingSupported)
|
||||
{
|
||||
if (parameter.IsChannelCountValid())
|
||||
{
|
||||
Reverb3dCommand command = new Reverb3dCommand(bufferOffset, parameter, state, isEnabled, workBuffer, nodeId);
|
||||
Reverb3dCommand command = new Reverb3dCommand(bufferOffset, parameter, state, isEnabled, workBuffer, nodeId, newEffectChannelMappingSupported);
|
||||
|
||||
command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
|
||||
|
||||
@ -379,11 +381,12 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||
/// <param name="isEnabled">Set to true if the effect should be active.</param>
|
||||
/// <param name="workBuffer">The work buffer to use for processing.</param>
|
||||
/// <param name="nodeId">The node id associated to this command.</param>
|
||||
public void GenerateDelayEffect(uint bufferOffset, DelayParameter parameter, Memory<DelayState> state, bool isEnabled, CpuAddress workBuffer, int nodeId)
|
||||
/// <param name="newEffectChannelMappingSupported">If set to true, the new effect channel mapping for 5.1 is supported.</param>
|
||||
public void GenerateDelayEffect(uint bufferOffset, DelayParameter parameter, Memory<DelayState> state, bool isEnabled, CpuAddress workBuffer, int nodeId, bool newEffectChannelMappingSupported)
|
||||
{
|
||||
if (parameter.IsChannelCountValid())
|
||||
{
|
||||
DelayCommand command = new DelayCommand(bufferOffset, parameter, state, isEnabled, workBuffer, nodeId);
|
||||
DelayCommand command = new DelayCommand(bufferOffset, parameter, state, isEnabled, workBuffer, nodeId, newEffectChannelMappingSupported);
|
||||
|
||||
command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
|
||||
|
||||
|
@ -483,31 +483,31 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||
}
|
||||
}
|
||||
|
||||
private void GenerateDelayEffect(uint bufferOffset, DelayEffect effect, int nodeId)
|
||||
private void GenerateDelayEffect(uint bufferOffset, DelayEffect effect, int nodeId, bool newEffectChannelMappingSupported)
|
||||
{
|
||||
Debug.Assert(effect.Type == EffectType.Delay);
|
||||
|
||||
ulong workBuffer = effect.GetWorkBuffer(-1);
|
||||
|
||||
_commandBuffer.GenerateDelayEffect(bufferOffset, effect.Parameter, effect.State, effect.IsEnabled, workBuffer, nodeId);
|
||||
_commandBuffer.GenerateDelayEffect(bufferOffset, effect.Parameter, effect.State, effect.IsEnabled, workBuffer, nodeId, newEffectChannelMappingSupported);
|
||||
}
|
||||
|
||||
private void GenerateReverbEffect(uint bufferOffset, ReverbEffect effect, int nodeId, bool isLongSizePreDelaySupported)
|
||||
private void GenerateReverbEffect(uint bufferOffset, ReverbEffect effect, int nodeId, bool isLongSizePreDelaySupported, bool newEffectChannelMappingSupported)
|
||||
{
|
||||
Debug.Assert(effect.Type == EffectType.Reverb);
|
||||
|
||||
ulong workBuffer = effect.GetWorkBuffer(-1);
|
||||
|
||||
_commandBuffer.GenerateReverbEffect(bufferOffset, effect.Parameter, effect.State, effect.IsEnabled, workBuffer, nodeId, isLongSizePreDelaySupported);
|
||||
_commandBuffer.GenerateReverbEffect(bufferOffset, effect.Parameter, effect.State, effect.IsEnabled, workBuffer, nodeId, isLongSizePreDelaySupported, newEffectChannelMappingSupported);
|
||||
}
|
||||
|
||||
private void GenerateReverb3dEffect(uint bufferOffset, Reverb3dEffect effect, int nodeId)
|
||||
private void GenerateReverb3dEffect(uint bufferOffset, Reverb3dEffect effect, int nodeId, bool newEffectChannelMappingSupported)
|
||||
{
|
||||
Debug.Assert(effect.Type == EffectType.Reverb3d);
|
||||
|
||||
ulong workBuffer = effect.GetWorkBuffer(-1);
|
||||
|
||||
_commandBuffer.GenerateReverb3dEffect(bufferOffset, effect.Parameter, effect.State, effect.IsEnabled, workBuffer, nodeId);
|
||||
_commandBuffer.GenerateReverb3dEffect(bufferOffset, effect.Parameter, effect.State, effect.IsEnabled, workBuffer, nodeId, newEffectChannelMappingSupported);
|
||||
}
|
||||
|
||||
private void GenerateBiquadFilterEffect(uint bufferOffset, BiquadFilterEffect effect, int nodeId)
|
||||
@ -650,13 +650,13 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||
GenerateAuxEffect(mix.BufferOffset, (AuxiliaryBufferEffect)effect, nodeId);
|
||||
break;
|
||||
case EffectType.Delay:
|
||||
GenerateDelayEffect(mix.BufferOffset, (DelayEffect)effect, nodeId);
|
||||
GenerateDelayEffect(mix.BufferOffset, (DelayEffect)effect, nodeId, _rendererContext.BehaviourContext.IsNewEffectChannelMappingSupported());
|
||||
break;
|
||||
case EffectType.Reverb:
|
||||
GenerateReverbEffect(mix.BufferOffset, (ReverbEffect)effect, nodeId, mix.IsLongSizePreDelaySupported);
|
||||
GenerateReverbEffect(mix.BufferOffset, (ReverbEffect)effect, nodeId, mix.IsLongSizePreDelaySupported, _rendererContext.BehaviourContext.IsNewEffectChannelMappingSupported());
|
||||
break;
|
||||
case EffectType.Reverb3d:
|
||||
GenerateReverb3dEffect(mix.BufferOffset, (Reverb3dEffect)effect, nodeId);
|
||||
GenerateReverb3dEffect(mix.BufferOffset, (Reverb3dEffect)effect, nodeId, _rendererContext.BehaviourContext.IsNewEffectChannelMappingSupported());
|
||||
break;
|
||||
case EffectType.BiquadFilter:
|
||||
GenerateBiquadFilterEffect(mix.BufferOffset, (BiquadFilterEffect)effect, nodeId);
|
||||
|
@ -198,7 +198,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||
return (uint)1853.2f;
|
||||
}
|
||||
|
||||
public uint Estimate(DelayCommand command)
|
||||
public virtual uint Estimate(DelayCommand command)
|
||||
{
|
||||
Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
|
||||
|
||||
@ -272,7 +272,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||
}
|
||||
}
|
||||
|
||||
public uint Estimate(ReverbCommand command)
|
||||
public virtual uint Estimate(ReverbCommand command)
|
||||
{
|
||||
Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
|
||||
|
||||
@ -346,7 +346,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||
}
|
||||
}
|
||||
|
||||
public uint Estimate(Reverb3dCommand command)
|
||||
public virtual uint Estimate(Reverb3dCommand command)
|
||||
{
|
||||
Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
|
||||
|
||||
|
@ -0,0 +1,253 @@
|
||||
//
|
||||
// Copyright (c) 2019-2022 Ryujinx
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
using Ryujinx.Audio.Renderer.Dsp.Command;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Server
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="ICommandProcessingTimeEstimator"/> version 5. (added with REV11)
|
||||
/// </summary>
|
||||
public class CommandProcessingTimeEstimatorVersion5 : CommandProcessingTimeEstimatorVersion4
|
||||
{
|
||||
public CommandProcessingTimeEstimatorVersion5(uint sampleCount, uint bufferCount) : base(sampleCount, bufferCount) { }
|
||||
|
||||
public override uint Estimate(DelayCommand command)
|
||||
{
|
||||
Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
|
||||
|
||||
if (_sampleCount == 160)
|
||||
{
|
||||
if (command.Enabled)
|
||||
{
|
||||
switch (command.Parameter.ChannelCount)
|
||||
{
|
||||
case 1:
|
||||
return 8929;
|
||||
case 2:
|
||||
return 25501;
|
||||
case 4:
|
||||
return 47760;
|
||||
case 6:
|
||||
return 82203;
|
||||
default:
|
||||
throw new NotImplementedException($"{command.Parameter.ChannelCount}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (command.Parameter.ChannelCount)
|
||||
{
|
||||
case 1:
|
||||
return (uint)1295.20f;
|
||||
case 2:
|
||||
return (uint)1213.60f;
|
||||
case 4:
|
||||
return (uint)942.03f;
|
||||
case 6:
|
||||
return (uint)1001.6f;
|
||||
default:
|
||||
throw new NotImplementedException($"{command.Parameter.ChannelCount}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (command.Enabled)
|
||||
{
|
||||
switch (command.Parameter.ChannelCount)
|
||||
{
|
||||
case 1:
|
||||
return 11941;
|
||||
case 2:
|
||||
return 37197;
|
||||
case 4:
|
||||
return 69750;
|
||||
case 6:
|
||||
return 12004;
|
||||
default:
|
||||
throw new NotImplementedException($"{command.Parameter.ChannelCount}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (command.Parameter.ChannelCount)
|
||||
{
|
||||
case 1:
|
||||
return (uint)997.67f;
|
||||
case 2:
|
||||
return (uint)977.63f;
|
||||
case 4:
|
||||
return (uint)792.31f;
|
||||
case 6:
|
||||
return (uint)875.43f;
|
||||
default:
|
||||
throw new NotImplementedException($"{command.Parameter.ChannelCount}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override uint Estimate(ReverbCommand command)
|
||||
{
|
||||
Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
|
||||
|
||||
if (_sampleCount == 160)
|
||||
{
|
||||
if (command.Enabled)
|
||||
{
|
||||
switch (command.Parameter.ChannelCount)
|
||||
{
|
||||
case 1:
|
||||
return 81475;
|
||||
case 2:
|
||||
return 84975;
|
||||
case 4:
|
||||
return 91625;
|
||||
case 6:
|
||||
return 95332;
|
||||
default:
|
||||
throw new NotImplementedException($"{command.Parameter.ChannelCount}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (command.Parameter.ChannelCount)
|
||||
{
|
||||
case 1:
|
||||
return (uint)536.30f;
|
||||
case 2:
|
||||
return (uint)588.80f;
|
||||
case 4:
|
||||
return (uint)643.70f;
|
||||
case 6:
|
||||
return (uint)706.0f;
|
||||
default:
|
||||
throw new NotImplementedException($"{command.Parameter.ChannelCount}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (command.Enabled)
|
||||
{
|
||||
switch (command.Parameter.ChannelCount)
|
||||
{
|
||||
case 1:
|
||||
return 120170;
|
||||
case 2:
|
||||
return 125260;
|
||||
case 4:
|
||||
return 135750;
|
||||
case 6:
|
||||
return 141130;
|
||||
default:
|
||||
throw new NotImplementedException($"{command.Parameter.ChannelCount}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (command.Parameter.ChannelCount)
|
||||
{
|
||||
case 1:
|
||||
return (uint)617.64f;
|
||||
case 2:
|
||||
return (uint)659.54f;
|
||||
case 4:
|
||||
return (uint)711.44f;
|
||||
case 6:
|
||||
return (uint)778.07f;
|
||||
default:
|
||||
throw new NotImplementedException($"{command.Parameter.ChannelCount}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override uint Estimate(Reverb3dCommand command)
|
||||
{
|
||||
Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
|
||||
|
||||
if (_sampleCount == 160)
|
||||
{
|
||||
if (command.Enabled)
|
||||
{
|
||||
switch (command.Parameter.ChannelCount)
|
||||
{
|
||||
case 1:
|
||||
return 116750;
|
||||
case 2:
|
||||
return 125910;
|
||||
case 4:
|
||||
return 146340;
|
||||
case 6:
|
||||
return 165810;
|
||||
default:
|
||||
throw new NotImplementedException($"{command.Parameter.ChannelCount}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (command.Parameter.ChannelCount)
|
||||
{
|
||||
case 1:
|
||||
return 735;
|
||||
case 2:
|
||||
return (uint)766.62f;
|
||||
case 4:
|
||||
return (uint)834.07f;
|
||||
case 6:
|
||||
return (uint)875.44f;
|
||||
default:
|
||||
throw new NotImplementedException($"{command.Parameter.ChannelCount}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (command.Enabled)
|
||||
{
|
||||
switch (command.Parameter.ChannelCount)
|
||||
{
|
||||
case 1:
|
||||
return 170290;
|
||||
case 2:
|
||||
return 183880;
|
||||
case 4:
|
||||
return 214700;
|
||||
case 6:
|
||||
return 243850;
|
||||
default:
|
||||
throw new NotImplementedException($"{command.Parameter.ChannelCount}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (command.Parameter.ChannelCount)
|
||||
{
|
||||
case 1:
|
||||
return (uint)508.47f;
|
||||
case 2:
|
||||
return (uint)582.45f;
|
||||
case 4:
|
||||
return (uint)626.42f;
|
||||
case 6:
|
||||
return (uint)682.47f;
|
||||
default:
|
||||
throw new NotImplementedException($"{command.Parameter.ChannelCount}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -149,11 +149,21 @@ namespace Ryujinx.Audio.Renderer.Server.Performance
|
||||
|
||||
Span<byte> targetSpan = performanceOutput.Slice(nextOffset);
|
||||
|
||||
// NOTE: We check for the space for two headers for the final blank header.
|
||||
int requiredSpace = Unsafe.SizeOf<THeader>() + Unsafe.SizeOf<TEntry>() * inputHeader.GetEntryCount()
|
||||
+ Unsafe.SizeOf<TEntryDetail>() * inputHeader.GetEntryDetailCount()
|
||||
+ Unsafe.SizeOf<THeader>();
|
||||
|
||||
if (targetSpan.Length < requiredSpace)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
ref THeader outputHeader = ref MemoryMarshal.Cast<byte, THeader>(targetSpan)[0];
|
||||
|
||||
nextOffset += Unsafe.SizeOf<THeader>();
|
||||
|
||||
Span<TEntry> outputEntries = MemoryMarshal.Cast<byte, TEntry>(targetSpan.Slice(nextOffset));
|
||||
Span<TEntry> outputEntries = MemoryMarshal.Cast<byte, TEntry>(performanceOutput.Slice(nextOffset));
|
||||
|
||||
int totalProcessingTime = 0;
|
||||
|
||||
@ -175,7 +185,7 @@ namespace Ryujinx.Audio.Renderer.Server.Performance
|
||||
}
|
||||
}
|
||||
|
||||
Span<TEntryDetail> outputEntriesDetail = MemoryMarshal.Cast<byte, TEntryDetail>(targetSpan.Slice(nextOffset));
|
||||
Span<TEntryDetail> outputEntriesDetail = MemoryMarshal.Cast<byte, TEntryDetail>(performanceOutput.Slice(nextOffset));
|
||||
|
||||
int effectiveEntryDetailCount = 0;
|
||||
|
||||
|
71
Ryujinx.Audio/Renderer/Utils/Math/Matrix2x2.cs
Normal file
71
Ryujinx.Audio/Renderer/Utils/Math/Matrix2x2.cs
Normal file
@ -0,0 +1,71 @@
|
||||
namespace Ryujinx.Audio.Renderer.Utils.Math
|
||||
{
|
||||
record struct Matrix2x2
|
||||
{
|
||||
public float M11;
|
||||
public float M12;
|
||||
public float M21;
|
||||
public float M22;
|
||||
|
||||
public Matrix2x2(float m11, float m12,
|
||||
float m21, float m22)
|
||||
{
|
||||
M11 = m11;
|
||||
M12 = m12;
|
||||
|
||||
M21 = m21;
|
||||
M22 = m22;
|
||||
}
|
||||
|
||||
public static Matrix2x2 operator +(Matrix2x2 value1, Matrix2x2 value2)
|
||||
{
|
||||
Matrix2x2 m;
|
||||
|
||||
m.M11 = value1.M11 + value2.M11;
|
||||
m.M12 = value1.M12 + value2.M12;
|
||||
m.M21 = value1.M21 + value2.M21;
|
||||
m.M22 = value1.M22 + value2.M22;
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
public static Matrix2x2 operator -(Matrix2x2 value1, float value2)
|
||||
{
|
||||
Matrix2x2 m;
|
||||
|
||||
m.M11 = value1.M11 - value2;
|
||||
m.M12 = value1.M12 - value2;
|
||||
m.M21 = value1.M21 - value2;
|
||||
m.M22 = value1.M22 - value2;
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
public static Matrix2x2 operator *(Matrix2x2 value1, float value2)
|
||||
{
|
||||
Matrix2x2 m;
|
||||
|
||||
m.M11 = value1.M11 * value2;
|
||||
m.M12 = value1.M12 * value2;
|
||||
m.M21 = value1.M21 * value2;
|
||||
m.M22 = value1.M22 * value2;
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
public static Matrix2x2 operator *(Matrix2x2 value1, Matrix2x2 value2)
|
||||
{
|
||||
Matrix2x2 m;
|
||||
|
||||
// First row
|
||||
m.M11 = value1.M11 * value2.M11 + value1.M12 * value2.M21;
|
||||
m.M12 = value1.M11 * value2.M12 + value1.M12 * value2.M22;
|
||||
|
||||
// Second row
|
||||
m.M21 = value1.M21 * value2.M11 + value1.M22 * value2.M21;
|
||||
m.M22 = value1.M21 * value2.M12 + value1.M22 * value2.M22;
|
||||
|
||||
return m;
|
||||
}
|
||||
}
|
||||
}
|
97
Ryujinx.Audio/Renderer/Utils/Math/Matrix6x6.cs
Normal file
97
Ryujinx.Audio/Renderer/Utils/Math/Matrix6x6.cs
Normal file
@ -0,0 +1,97 @@
|
||||
namespace Ryujinx.Audio.Renderer.Utils.Math
|
||||
{
|
||||
record struct Matrix6x6
|
||||
{
|
||||
public float M11;
|
||||
public float M12;
|
||||
public float M13;
|
||||
public float M14;
|
||||
public float M15;
|
||||
public float M16;
|
||||
|
||||
public float M21;
|
||||
public float M22;
|
||||
public float M23;
|
||||
public float M24;
|
||||
public float M25;
|
||||
public float M26;
|
||||
|
||||
public float M31;
|
||||
public float M32;
|
||||
public float M33;
|
||||
public float M34;
|
||||
public float M35;
|
||||
public float M36;
|
||||
|
||||
public float M41;
|
||||
public float M42;
|
||||
public float M43;
|
||||
public float M44;
|
||||
public float M45;
|
||||
public float M46;
|
||||
|
||||
public float M51;
|
||||
public float M52;
|
||||
public float M53;
|
||||
public float M54;
|
||||
public float M55;
|
||||
public float M56;
|
||||
|
||||
public float M61;
|
||||
public float M62;
|
||||
public float M63;
|
||||
public float M64;
|
||||
public float M65;
|
||||
public float M66;
|
||||
|
||||
public Matrix6x6(float m11, float m12, float m13, float m14, float m15, float m16,
|
||||
float m21, float m22, float m23, float m24, float m25, float m26,
|
||||
float m31, float m32, float m33, float m34, float m35, float m36,
|
||||
float m41, float m42, float m43, float m44, float m45, float m46,
|
||||
float m51, float m52, float m53, float m54, float m55, float m56,
|
||||
float m61, float m62, float m63, float m64, float m65, float m66)
|
||||
{
|
||||
M11 = m11;
|
||||
M12 = m12;
|
||||
M13 = m13;
|
||||
M14 = m14;
|
||||
M15 = m15;
|
||||
M16 = m16;
|
||||
|
||||
M21 = m21;
|
||||
M22 = m22;
|
||||
M23 = m23;
|
||||
M24 = m24;
|
||||
M25 = m25;
|
||||
M26 = m26;
|
||||
|
||||
M31 = m31;
|
||||
M32 = m32;
|
||||
M33 = m33;
|
||||
M34 = m34;
|
||||
M35 = m35;
|
||||
M36 = m36;
|
||||
|
||||
M41 = m41;
|
||||
M42 = m42;
|
||||
M43 = m43;
|
||||
M44 = m44;
|
||||
M45 = m45;
|
||||
M46 = m46;
|
||||
|
||||
M51 = m51;
|
||||
M52 = m52;
|
||||
M53 = m53;
|
||||
M54 = m54;
|
||||
M55 = m55;
|
||||
M56 = m56;
|
||||
|
||||
M61 = m61;
|
||||
M62 = m62;
|
||||
M63 = m63;
|
||||
M64 = m64;
|
||||
M65 = m65;
|
||||
M66 = m66;
|
||||
}
|
||||
}
|
||||
}
|
45
Ryujinx.Audio/Renderer/Utils/Math/MatrixHelper.cs
Normal file
45
Ryujinx.Audio/Renderer/Utils/Math/MatrixHelper.cs
Normal file
@ -0,0 +1,45 @@
|
||||
using Ryujinx.Audio.Renderer.Utils.Math;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp
|
||||
{
|
||||
static class MatrixHelper
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Vector6 Transform(ref Vector6 value1, ref Matrix6x6 value2)
|
||||
{
|
||||
return new Vector6
|
||||
{
|
||||
X = value2.M11 * value1.X + value2.M12 * value1.Y + value2.M13 * value1.Z + value2.M14 * value1.W + value2.M15 * value1.V + value2.M16 * value1.U,
|
||||
Y = value2.M21 * value1.X + value2.M22 * value1.Y + value2.M23 * value1.Z + value2.M24 * value1.W + value2.M25 * value1.V + value2.M26 * value1.U,
|
||||
Z = value2.M31 * value1.X + value2.M32 * value1.Y + value2.M33 * value1.Z + value2.M34 * value1.W + value2.M35 * value1.V + value2.M36 * value1.U,
|
||||
W = value2.M41 * value1.X + value2.M42 * value1.Y + value2.M43 * value1.Z + value2.M44 * value1.W + value2.M45 * value1.V + value2.M46 * value1.U,
|
||||
V = value2.M51 * value1.X + value2.M52 * value1.Y + value2.M53 * value1.Z + value2.M54 * value1.W + value2.M55 * value1.V + value2.M56 * value1.U,
|
||||
U = value2.M61 * value1.X + value2.M62 * value1.Y + value2.M63 * value1.Z + value2.M64 * value1.W + value2.M65 * value1.V + value2.M66 * value1.U,
|
||||
};
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Vector4 Transform(ref Vector4 value1, ref Matrix4x4 value2)
|
||||
{
|
||||
return new Vector4
|
||||
{
|
||||
X = value2.M11 * value1.X + value2.M12 * value1.Y + value2.M13 * value1.Z + value2.M14 * value1.W,
|
||||
Y = value2.M21 * value1.X + value2.M22 * value1.Y + value2.M23 * value1.Z + value2.M24 * value1.W,
|
||||
Z = value2.M31 * value1.X + value2.M32 * value1.Y + value2.M33 * value1.Z + value2.M34 * value1.W,
|
||||
W = value2.M41 * value1.X + value2.M42 * value1.Y + value2.M43 * value1.Z + value2.M44 * value1.W
|
||||
};
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Vector2 Transform(ref Vector2 value1, ref Matrix2x2 value2)
|
||||
{
|
||||
return new Vector2
|
||||
{
|
||||
X = value2.M11 * value1.X + value2.M12 * value1.Y,
|
||||
Y = value2.M21 * value1.X + value2.M22 * value1.Y,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
56
Ryujinx.Audio/Renderer/Utils/Math/Vector6.cs
Normal file
56
Ryujinx.Audio/Renderer/Utils/Math/Vector6.cs
Normal file
@ -0,0 +1,56 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Utils.Math
|
||||
{
|
||||
record struct Vector6
|
||||
{
|
||||
public float X;
|
||||
public float Y;
|
||||
public float Z;
|
||||
public float W;
|
||||
public float V;
|
||||
public float U;
|
||||
|
||||
public Vector6(float value) : this(value, value, value, value, value, value)
|
||||
{
|
||||
}
|
||||
|
||||
public Vector6(float x, float y, float z, float w, float v, float u)
|
||||
{
|
||||
X = x;
|
||||
Y = y;
|
||||
Z = z;
|
||||
W = w;
|
||||
V = v;
|
||||
U = u;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Vector6 operator +(Vector6 left, Vector6 right)
|
||||
{
|
||||
return new Vector6(left.X + right.X,
|
||||
left.Y + right.Y,
|
||||
left.Z + right.Z,
|
||||
left.W + right.W,
|
||||
left.V + right.V,
|
||||
left.U + right.U);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Vector6 operator *(Vector6 left, Vector6 right)
|
||||
{
|
||||
return new Vector6(left.X * right.X,
|
||||
left.Y * right.Y,
|
||||
left.Z * right.Z,
|
||||
left.W * right.W,
|
||||
left.V * right.V,
|
||||
left.U * right.U);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Vector6 operator *(Vector6 left, float right)
|
||||
{
|
||||
return left * new Vector6(right);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,5 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
|
||||
namespace Ryujinx.Common.Collections
|
||||
@ -212,7 +210,7 @@ namespace Ryujinx.Common.Collections
|
||||
/// <param name="overlaps">Overlaps array to place results in</param>
|
||||
/// <param name="overlapCount">Overlaps count to update</param>
|
||||
private void GetValues(IntervalTreeNode<K, V> node, K start, K end, ref V[] overlaps, ref int overlapCount)
|
||||
{
|
||||
{
|
||||
if (node == null || start.CompareTo(node.Max) >= 0)
|
||||
{
|
||||
return;
|
||||
@ -624,7 +622,7 @@ namespace Ryujinx.Common.Collections
|
||||
node.Right = LeftOf(right);
|
||||
if (node.Right != null)
|
||||
{
|
||||
node.Right.Parent = node;
|
||||
node.Right.Parent = node;
|
||||
}
|
||||
IntervalTreeNode<K, V> nodeParent = ParentOf(node);
|
||||
right.Parent = nodeParent;
|
||||
@ -638,7 +636,7 @@ namespace Ryujinx.Common.Collections
|
||||
}
|
||||
else
|
||||
{
|
||||
nodeParent.Right = right;
|
||||
nodeParent.Right = right;
|
||||
}
|
||||
right.Left = node;
|
||||
node.Parent = right;
|
||||
@ -779,37 +777,37 @@ namespace Ryujinx.Common.Collections
|
||||
/// </summary>
|
||||
/// <typeparam name="K">Key type of the node</typeparam>
|
||||
/// <typeparam name="V">Value type of the node</typeparam>
|
||||
internal class IntervalTreeNode<K, V>
|
||||
class IntervalTreeNode<K, V>
|
||||
{
|
||||
internal bool Color = true;
|
||||
internal IntervalTreeNode<K, V> Left = null;
|
||||
internal IntervalTreeNode<K, V> Right = null;
|
||||
internal IntervalTreeNode<K, V> Parent = null;
|
||||
public bool Color = true;
|
||||
public IntervalTreeNode<K, V> Left = null;
|
||||
public IntervalTreeNode<K, V> Right = null;
|
||||
public IntervalTreeNode<K, V> Parent = null;
|
||||
|
||||
/// <summary>
|
||||
/// The start of the range.
|
||||
/// </summary>
|
||||
internal K Start;
|
||||
public K Start;
|
||||
|
||||
/// <summary>
|
||||
/// The end of the range - maximum of all in the Values list.
|
||||
/// </summary>
|
||||
internal K End;
|
||||
public K End;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum end value of this node and all its children.
|
||||
/// </summary>
|
||||
internal K Max;
|
||||
public K Max;
|
||||
|
||||
internal List<RangeNode<K, V>> Values;
|
||||
public List<RangeNode<K, V>> Values;
|
||||
|
||||
public IntervalTreeNode(K start, K end, V value, IntervalTreeNode<K, V> parent)
|
||||
{
|
||||
this.Start = start;
|
||||
this.End = end;
|
||||
this.Max = end;
|
||||
this.Values = new List<RangeNode<K, V>> { new RangeNode<K, V>(start, end, value) };
|
||||
this.Parent = parent;
|
||||
Start = start;
|
||||
End = end;
|
||||
Max = end;
|
||||
Values = new List<RangeNode<K, V>> { new RangeNode<K, V>(start, end, value) };
|
||||
Parent = parent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -921,10 +921,10 @@ namespace Ryujinx.Common.Collections
|
||||
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
public V this[K key]
|
||||
{
|
||||
public V this[K key]
|
||||
{
|
||||
get => Get(key);
|
||||
set => Add(key, value);
|
||||
set => Add(key, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@ -967,20 +967,20 @@ namespace Ryujinx.Common.Collections
|
||||
/// </summary>
|
||||
/// <typeparam name="K">Key of the node</typeparam>
|
||||
/// <typeparam name="V">Value of the node</typeparam>
|
||||
internal class Node<K, V>
|
||||
class Node<K, V>
|
||||
{
|
||||
internal bool Color = true;
|
||||
internal Node<K, V> Left = null;
|
||||
internal Node<K, V> Right = null;
|
||||
internal Node<K, V> Parent = null;
|
||||
internal K Key;
|
||||
internal V Value;
|
||||
public bool Color = true;
|
||||
public Node<K, V> Left = null;
|
||||
public Node<K, V> Right = null;
|
||||
public Node<K, V> Parent = null;
|
||||
public K Key;
|
||||
public V Value;
|
||||
|
||||
public Node(K key, V value, Node<K, V> parent)
|
||||
{
|
||||
this.Key = key;
|
||||
this.Value = value;
|
||||
this.Parent = parent;
|
||||
Key = key;
|
||||
Value = value;
|
||||
Parent = parent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -34,6 +34,7 @@ namespace Ryujinx.Common.Configuration
|
||||
private const string DefaultModsDir = "mods";
|
||||
|
||||
public static string CustomModsPath { get; set; }
|
||||
public static string CustomSdModsPath {get; set; }
|
||||
public static string CustomNandPath { get; set; } // TODO: Actually implement this into VFS
|
||||
public static string CustomSdCardPath { get; set; } // TODO: Actually implement this into VFS
|
||||
|
||||
@ -84,6 +85,7 @@ namespace Ryujinx.Common.Configuration
|
||||
Directory.CreateDirectory(KeysDirPath = Path.Combine(BaseDirPath, KeysDir));
|
||||
}
|
||||
|
||||
public static string GetModsPath() => CustomModsPath ?? Directory.CreateDirectory(Path.Combine(BaseDirPath, DefaultModsDir)).FullName;
|
||||
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;
|
||||
}
|
||||
}
|
@ -5,6 +5,7 @@
|
||||
public Stick Joystick { get; set; }
|
||||
public bool InvertStickX { get; set; }
|
||||
public bool InvertStickY { get; set; }
|
||||
public bool Rotate90CW { get; set; }
|
||||
public Button StickButton { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -47,6 +47,7 @@ namespace Ryujinx.Common.Logging
|
||||
ServiceNim,
|
||||
ServiceNs,
|
||||
ServiceNsd,
|
||||
ServiceNtc,
|
||||
ServiceNv,
|
||||
ServiceOlsc,
|
||||
ServicePctl,
|
||||
|
@ -34,7 +34,7 @@ namespace Ryujinx.Common
|
||||
|
||||
_readerWriterLock.ReleaseWriterLock();
|
||||
|
||||
if (!oldIsInitialized || !oldValue.Equals(_value))
|
||||
if (!oldIsInitialized || oldValue == null || !oldValue.Equals(_value))
|
||||
{
|
||||
Event?.Invoke(this, new ReactiveEventArgs<T>(oldValue, value));
|
||||
}
|
||||
|
@ -1,10 +1,14 @@
|
||||
using System.Reflection;
|
||||
using Ryujinx.Common.Configuration;
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Ryujinx.Common
|
||||
{
|
||||
// DO NOT EDIT, filled by CI
|
||||
public static class ReleaseInformations
|
||||
{
|
||||
private const string FlatHubChannelOwner = "flathub";
|
||||
|
||||
public static string BuildVersion = "%%RYUJINX_BUILD_VERSION%%";
|
||||
public static string BuildGitHash = "%%RYUJINX_BUILD_GIT_HASH%%";
|
||||
public static string ReleaseChannelName = "%%RYUJINX_TARGET_RELEASE_CHANNEL_NAME%%";
|
||||
@ -19,6 +23,11 @@ namespace Ryujinx.Common
|
||||
!ReleaseChannelRepo.StartsWith("%%");
|
||||
}
|
||||
|
||||
public static bool IsFlatHubBuild()
|
||||
{
|
||||
return IsValid() && ReleaseChannelOwner.Equals(FlatHubChannelOwner);
|
||||
}
|
||||
|
||||
public static string GetVersion()
|
||||
{
|
||||
if (IsValid())
|
||||
@ -30,5 +39,15 @@ namespace Ryujinx.Common
|
||||
return Assembly.GetEntryAssembly().GetCustomAttribute<AssemblyInformationalVersionAttribute>().InformationalVersion;
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetBaseApplicationDirectory()
|
||||
{
|
||||
if (IsFlatHubBuild())
|
||||
{
|
||||
return AppDataManager.BaseDirPath;
|
||||
}
|
||||
|
||||
return AppDomain.CurrentDomain.BaseDirectory;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
using Ryujinx.Common.Logging;
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Globalization;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Versioning;
|
||||
|
||||
@ -11,6 +12,23 @@ namespace Ryujinx.Common.System
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool SetProcessDPIAware();
|
||||
|
||||
private const string X11LibraryName = "libX11.so.6";
|
||||
|
||||
[DllImport(X11LibraryName)]
|
||||
private static extern IntPtr XOpenDisplay(string display);
|
||||
|
||||
[DllImport(X11LibraryName)]
|
||||
private static extern IntPtr XGetDefault(IntPtr display, string program, string option);
|
||||
|
||||
[DllImport(X11LibraryName)]
|
||||
private static extern int XDisplayWidth(IntPtr display, int screenNumber);
|
||||
|
||||
[DllImport(X11LibraryName)]
|
||||
private static extern int XDisplayWidthMM(IntPtr display, int screenNumber);
|
||||
|
||||
[DllImport(X11LibraryName)]
|
||||
private static extern int XCloseDisplay(IntPtr display);
|
||||
|
||||
private static readonly double _standardDpiScale = 96.0;
|
||||
private static readonly double _maxScaleFactor = 1.25;
|
||||
|
||||
@ -36,9 +54,29 @@ namespace Ryujinx.Common.System
|
||||
{
|
||||
userDpiScale = Graphics.FromHwnd(IntPtr.Zero).DpiX;
|
||||
}
|
||||
else
|
||||
else if (OperatingSystem.IsLinux())
|
||||
{
|
||||
// TODO: Linux support
|
||||
string xdgSessionType = Environment.GetEnvironmentVariable("XDG_SESSION_TYPE")?.ToLower();
|
||||
|
||||
if (xdgSessionType == null || xdgSessionType == "x11")
|
||||
{
|
||||
IntPtr display = XOpenDisplay(null);
|
||||
string dpiString = Marshal.PtrToStringAnsi(XGetDefault(display, "Xft", "dpi"));
|
||||
if (dpiString == null || !double.TryParse(dpiString, NumberStyles.Any, CultureInfo.InvariantCulture, out userDpiScale))
|
||||
{
|
||||
userDpiScale = (double)XDisplayWidth(display, 0) * 25.4 / (double)XDisplayWidthMM(display, 0);
|
||||
}
|
||||
XCloseDisplay(display);
|
||||
}
|
||||
else if (xdgSessionType == "wayland")
|
||||
{
|
||||
// TODO
|
||||
Logger.Warning?.Print(LogClass.Application, $"Couldn't determine monitor DPI: Wayland not yet supported");
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application, $"Couldn't determine monitor DPI: Unrecognised XDG_SESSION_TYPE: {xdgSessionType}");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
|
@ -24,7 +24,7 @@ namespace Ryujinx.Cpu
|
||||
_baseAddress = (ulong)_addressSpace.Pointer;
|
||||
ulong endAddress = _baseAddress + addressSpace.Size;
|
||||
|
||||
_trackingEvent = new TrackingEventDelegate(tracking.VirtualMemoryEvent);
|
||||
_trackingEvent = new TrackingEventDelegate(tracking.VirtualMemoryEventEh);
|
||||
bool added = NativeSignalHandler.AddTrackedRegion((nuint)_baseAddress, (nuint)endAddress, Marshal.GetFunctionPointerForDelegate(_trackingEvent));
|
||||
|
||||
if (!added)
|
||||
|
@ -25,6 +25,7 @@ namespace Ryujinx.Cpu
|
||||
|
||||
private const int PointerTagBit = 62;
|
||||
|
||||
private readonly MemoryBlock _backingMemory;
|
||||
private readonly InvalidAccessHandler _invalidAccessHandler;
|
||||
|
||||
/// <summary>
|
||||
@ -50,10 +51,12 @@ namespace Ryujinx.Cpu
|
||||
/// <summary>
|
||||
/// Creates a new instance of the 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 MemoryManager(ulong addressSpaceSize, InvalidAccessHandler invalidAccessHandler = null)
|
||||
public MemoryManager(MemoryBlock backingMemory, ulong addressSpaceSize, InvalidAccessHandler invalidAccessHandler = null)
|
||||
{
|
||||
_backingMemory = backingMemory;
|
||||
_invalidAccessHandler = invalidAccessHandler;
|
||||
|
||||
ulong asSize = PageSize;
|
||||
@ -73,18 +76,19 @@ namespace Ryujinx.Cpu
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Map(ulong va, nuint hostAddress, ulong size)
|
||||
public void Map(ulong va, ulong pa, ulong size)
|
||||
{
|
||||
AssertValidAddressAndSize(va, size);
|
||||
|
||||
ulong remainingSize = size;
|
||||
ulong oVa = va;
|
||||
ulong oPa = pa;
|
||||
while (remainingSize != 0)
|
||||
{
|
||||
_pageTable.Write((va / PageSize) * PteSize, hostAddress);
|
||||
_pageTable.Write((va / PageSize) * PteSize, PaToPte(pa));
|
||||
|
||||
va += PageSize;
|
||||
hostAddress += PageSize;
|
||||
pa += PageSize;
|
||||
remainingSize -= PageSize;
|
||||
}
|
||||
Tracking.Map(oVa, size);
|
||||
@ -107,7 +111,7 @@ namespace Ryujinx.Cpu
|
||||
ulong remainingSize = size;
|
||||
while (remainingSize != 0)
|
||||
{
|
||||
_pageTable.Write((va / PageSize) * PteSize, (nuint)0);
|
||||
_pageTable.Write((va / PageSize) * PteSize, 0UL);
|
||||
|
||||
va += PageSize;
|
||||
remainingSize -= PageSize;
|
||||
@ -123,8 +127,21 @@ namespace Ryujinx.Cpu
|
||||
/// <inheritdoc/>
|
||||
public T ReadTracked<T>(ulong va) where T : unmanaged
|
||||
{
|
||||
SignalMemoryTracking(va, (ulong)Unsafe.SizeOf<T>(), false);
|
||||
return MemoryMarshal.Cast<byte, T>(GetSpan(va, Unsafe.SizeOf<T>()))[0];
|
||||
try
|
||||
{
|
||||
SignalMemoryTracking(va, (ulong)Unsafe.SizeOf<T>(), false);
|
||||
|
||||
return Read<T>(va);
|
||||
}
|
||||
catch (InvalidMemoryRegionException)
|
||||
{
|
||||
if (_invalidAccessHandler == null || !_invalidAccessHandler(va))
|
||||
{
|
||||
throw;
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@ -177,7 +194,7 @@ namespace Ryujinx.Cpu
|
||||
|
||||
if (IsContiguousAndMapped(va, data.Length))
|
||||
{
|
||||
data.CopyTo(GetHostSpanContiguous(va, data.Length));
|
||||
data.CopyTo(_backingMemory.GetSpan(GetPhysicalAddressInternal(va), data.Length));
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -185,18 +202,22 @@ namespace Ryujinx.Cpu
|
||||
|
||||
if ((va & PageMask) != 0)
|
||||
{
|
||||
ulong pa = GetPhysicalAddressInternal(va);
|
||||
|
||||
size = Math.Min(data.Length, PageSize - (int)(va & PageMask));
|
||||
|
||||
data.Slice(0, size).CopyTo(GetHostSpanContiguous(va, size));
|
||||
data.Slice(0, size).CopyTo(_backingMemory.GetSpan(pa, size));
|
||||
|
||||
offset += size;
|
||||
}
|
||||
|
||||
for (; offset < data.Length; offset += size)
|
||||
{
|
||||
ulong pa = GetPhysicalAddressInternal(va + (ulong)offset);
|
||||
|
||||
size = Math.Min(data.Length - offset, PageSize);
|
||||
|
||||
data.Slice(offset, size).CopyTo(GetHostSpanContiguous(va + (ulong)offset, size));
|
||||
data.Slice(offset, size).CopyTo(_backingMemory.GetSpan(pa, size));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -224,7 +245,7 @@ namespace Ryujinx.Cpu
|
||||
|
||||
if (IsContiguousAndMapped(va, size))
|
||||
{
|
||||
return GetHostSpanContiguous(va, size);
|
||||
return _backingMemory.GetSpan(GetPhysicalAddressInternal(va), size);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -251,7 +272,7 @@ namespace Ryujinx.Cpu
|
||||
SignalMemoryTracking(va, (ulong)size, true);
|
||||
}
|
||||
|
||||
return new WritableRegion(null, va, new NativeMemoryManager<byte>((byte*)GetHostAddress(va), size).Memory);
|
||||
return new WritableRegion(null, va, _backingMemory.GetMemory(GetPhysicalAddressInternal(va), size));
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -264,7 +285,7 @@ namespace Ryujinx.Cpu
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public unsafe ref T GetRef<T>(ulong va) where T : unmanaged
|
||||
public ref T GetRef<T>(ulong va) where T : unmanaged
|
||||
{
|
||||
if (!IsContiguous(va, Unsafe.SizeOf<T>()))
|
||||
{
|
||||
@ -273,7 +294,7 @@ namespace Ryujinx.Cpu
|
||||
|
||||
SignalMemoryTracking(va, (ulong)Unsafe.SizeOf<T>(), true);
|
||||
|
||||
return ref *(T*)GetHostAddress(va);
|
||||
return ref _backingMemory.GetRef<T>(GetPhysicalAddressInternal(va));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -293,7 +314,7 @@ namespace Ryujinx.Cpu
|
||||
return (int)(vaSpan / PageSize);
|
||||
}
|
||||
|
||||
private void ThrowMemoryNotContiguous() => throw new MemoryNotContiguousException();
|
||||
private static void ThrowMemoryNotContiguous() => throw new MemoryNotContiguousException();
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private bool IsContiguousAndMapped(ulong va, int size) => IsContiguous(va, size) && IsMapped(va);
|
||||
@ -315,7 +336,7 @@ namespace Ryujinx.Cpu
|
||||
return false;
|
||||
}
|
||||
|
||||
if (GetHostAddress(va) + PageSize != GetHostAddress(va + PageSize))
|
||||
if (GetPhysicalAddressInternal(va) + PageSize != GetPhysicalAddressInternal(va + PageSize))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@ -327,11 +348,11 @@ namespace Ryujinx.Cpu
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<HostMemoryRange> GetPhysicalRegions(ulong va, ulong size)
|
||||
public IEnumerable<MemoryRange> GetPhysicalRegions(ulong va, ulong size)
|
||||
{
|
||||
if (size == 0)
|
||||
{
|
||||
return Enumerable.Empty<HostMemoryRange>();
|
||||
return Enumerable.Empty<MemoryRange>();
|
||||
}
|
||||
|
||||
if (!ValidateAddress(va) || !ValidateAddressAndSize(va, size))
|
||||
@ -341,9 +362,9 @@ namespace Ryujinx.Cpu
|
||||
|
||||
int pages = GetPagesCount(va, (uint)size, out va);
|
||||
|
||||
var regions = new List<HostMemoryRange>();
|
||||
var regions = new List<MemoryRange>();
|
||||
|
||||
nuint regionStart = GetHostAddress(va);
|
||||
ulong regionStart = GetPhysicalAddressInternal(va);
|
||||
ulong regionSize = PageSize;
|
||||
|
||||
for (int page = 0; page < pages - 1; page++)
|
||||
@ -353,12 +374,12 @@ namespace Ryujinx.Cpu
|
||||
return null;
|
||||
}
|
||||
|
||||
nuint newHostAddress = GetHostAddress(va + PageSize);
|
||||
ulong newPa = GetPhysicalAddressInternal(va + PageSize);
|
||||
|
||||
if (GetHostAddress(va) + PageSize != newHostAddress)
|
||||
if (GetPhysicalAddressInternal(va) + PageSize != newPa)
|
||||
{
|
||||
regions.Add(new HostMemoryRange(regionStart, regionSize));
|
||||
regionStart = newHostAddress;
|
||||
regions.Add(new MemoryRange(regionStart, regionSize));
|
||||
regionStart = newPa;
|
||||
regionSize = 0;
|
||||
}
|
||||
|
||||
@ -366,7 +387,7 @@ namespace Ryujinx.Cpu
|
||||
regionSize += PageSize;
|
||||
}
|
||||
|
||||
regions.Add(new HostMemoryRange(regionStart, regionSize));
|
||||
regions.Add(new MemoryRange(regionStart, regionSize));
|
||||
|
||||
return regions;
|
||||
}
|
||||
@ -386,18 +407,22 @@ namespace Ryujinx.Cpu
|
||||
|
||||
if ((va & PageMask) != 0)
|
||||
{
|
||||
ulong pa = GetPhysicalAddressInternal(va);
|
||||
|
||||
size = Math.Min(data.Length, PageSize - (int)(va & PageMask));
|
||||
|
||||
GetHostSpanContiguous(va, size).CopyTo(data.Slice(0, size));
|
||||
_backingMemory.GetSpan(pa, size).CopyTo(data.Slice(0, size));
|
||||
|
||||
offset += size;
|
||||
}
|
||||
|
||||
for (; offset < data.Length; offset += size)
|
||||
{
|
||||
ulong pa = GetPhysicalAddressInternal(va + (ulong)offset);
|
||||
|
||||
size = Math.Min(data.Length - offset, PageSize);
|
||||
|
||||
GetHostSpanContiguous(va + (ulong)offset, size).CopyTo(data.Slice(offset, size));
|
||||
_backingMemory.GetSpan(pa, size).CopyTo(data.Slice(offset, size));
|
||||
}
|
||||
}
|
||||
catch (InvalidMemoryRegionException)
|
||||
@ -446,7 +471,7 @@ namespace Ryujinx.Cpu
|
||||
return false;
|
||||
}
|
||||
|
||||
return _pageTable.Read<nuint>((va / PageSize) * PteSize) != 0;
|
||||
return _pageTable.Read<ulong>((va / PageSize) * PteSize) != 0;
|
||||
}
|
||||
|
||||
private bool ValidateAddress(ulong va)
|
||||
@ -480,37 +505,20 @@ namespace Ryujinx.Cpu
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a span representing the given virtual address and size range in host memory.
|
||||
/// This function assumes that the requested virtual memory region is contiguous.
|
||||
/// </summary>
|
||||
/// <param name="va">Virtual address of the range</param>
|
||||
/// <param name="size">Size of the range in bytes</param>
|
||||
/// <returns>A span representing the given virtual range in host memory</returns>
|
||||
/// <exception cref="InvalidMemoryRegionException">Throw when the base virtual address is not mapped</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private unsafe Span<byte> GetHostSpanContiguous(ulong va, int size)
|
||||
private ulong GetPhysicalAddress(ulong va)
|
||||
{
|
||||
return new Span<byte>((void*)GetHostAddress(va), size);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the host address for a given virtual address, using the page table.
|
||||
/// </summary>
|
||||
/// <param name="va">Virtual address</param>
|
||||
/// <returns>The corresponding host address for the given virtual address</returns>
|
||||
/// <exception cref="InvalidMemoryRegionException">Throw when the virtual address is not mapped</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private nuint GetHostAddress(ulong va)
|
||||
{
|
||||
nuint pageBase = _pageTable.Read<nuint>((va / PageSize) * PteSize) & unchecked((nuint)0xffff_ffff_ffffUL);
|
||||
|
||||
if (pageBase == 0)
|
||||
// We return -1L if the virtual address is invalid or unmapped.
|
||||
if (!ValidateAddress(va) || !IsMapped(va))
|
||||
{
|
||||
ThrowInvalidMemoryRegionException($"Not mapped: va=0x{va:X16}");
|
||||
return ulong.MaxValue;
|
||||
}
|
||||
|
||||
return pageBase + (nuint)(va & PageMask);
|
||||
return GetPhysicalAddressInternal(va);
|
||||
}
|
||||
|
||||
private ulong GetPhysicalAddressInternal(ulong va)
|
||||
{
|
||||
return PteToPa(_pageTable.Read<ulong>((va / PageSize) * PteSize) & ~(0xffffUL << 48)) + (va & PageMask);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@ -604,6 +612,16 @@ namespace Ryujinx.Cpu
|
||||
}
|
||||
}
|
||||
|
||||
private ulong PaToPte(ulong pa)
|
||||
{
|
||||
return (ulong)_backingMemory.GetPointer(pa, PageSize);
|
||||
}
|
||||
|
||||
private ulong PteToPa(ulong pte)
|
||||
{
|
||||
return (ulong)((long)pte - _backingMemory.Pointer.ToInt64());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes of resources used by the memory manager.
|
||||
/// </summary>
|
||||
|
@ -5,7 +5,6 @@ using Ryujinx.Memory.Range;
|
||||
using Ryujinx.Memory.Tracking;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
|
||||
@ -14,7 +13,7 @@ namespace Ryujinx.Cpu
|
||||
/// <summary>
|
||||
/// Represents a CPU memory manager which maps guest virtual memory directly onto a host virtual region.
|
||||
/// </summary>
|
||||
public class MemoryManagerHostMapped : MemoryManagerBase, IMemoryManager, IVirtualMemoryManagerTracked
|
||||
public class MemoryManagerHostMapped : MemoryManagerBase, IMemoryManager, IVirtualMemoryManagerTracked, IWritableBlock
|
||||
{
|
||||
public const int PageBits = 12;
|
||||
public const int PageSize = 1 << PageBits;
|
||||
@ -42,9 +41,12 @@ namespace Ryujinx.Cpu
|
||||
private readonly MemoryBlock _addressSpaceMirror;
|
||||
private readonly ulong _addressSpaceSize;
|
||||
|
||||
private readonly MemoryBlock _backingMemory;
|
||||
private readonly PageTable<ulong> _pageTable;
|
||||
|
||||
private readonly MemoryEhMeilleure _memoryEh;
|
||||
|
||||
private ulong[] _pageTable;
|
||||
private readonly ulong[] _pageBitmap;
|
||||
|
||||
public int AddressSpaceBits { get; }
|
||||
|
||||
@ -59,11 +61,14 @@ namespace Ryujinx.Cpu
|
||||
/// <summary>
|
||||
/// Creates a new instance of the host mapped 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="unsafeMode">True if unmanaged access should not be masked (unsafe), false otherwise.</param>
|
||||
/// <param name="invalidAccessHandler">Optional function to handle invalid memory accesses</param>
|
||||
public MemoryManagerHostMapped(ulong addressSpaceSize, bool unsafeMode, InvalidAccessHandler invalidAccessHandler = null)
|
||||
public MemoryManagerHostMapped(MemoryBlock backingMemory, ulong addressSpaceSize, bool unsafeMode, InvalidAccessHandler invalidAccessHandler = null)
|
||||
{
|
||||
_backingMemory = backingMemory;
|
||||
_pageTable = new PageTable<ulong>();
|
||||
_invalidAccessHandler = invalidAccessHandler;
|
||||
_unsafeMode = unsafeMode;
|
||||
_addressSpaceSize = addressSpaceSize;
|
||||
@ -79,9 +84,13 @@ namespace Ryujinx.Cpu
|
||||
|
||||
AddressSpaceBits = asBits;
|
||||
|
||||
_pageTable = new ulong[1 << (AddressSpaceBits - (PageBits + PageToPteShift))];
|
||||
_addressSpace = new MemoryBlock(asSize, MemoryAllocationFlags.Reserve | MemoryAllocationFlags.Mirrorable);
|
||||
_addressSpaceMirror = _addressSpace.CreateMirror();
|
||||
_pageBitmap = new ulong[1 << (AddressSpaceBits - (PageBits + PageToPteShift))];
|
||||
|
||||
MemoryAllocationFlags asFlags = MemoryAllocationFlags.Reserve | MemoryAllocationFlags.ViewCompatible;
|
||||
|
||||
_addressSpace = new MemoryBlock(asSize, asFlags);
|
||||
_addressSpaceMirror = new MemoryBlock(asSize, asFlags | MemoryAllocationFlags.ForceWindows4KBViewMapping);
|
||||
|
||||
Tracking = new MemoryTracking(this, PageSize, invalidAccessHandler);
|
||||
_memoryEh = new MemoryEhMeilleure(_addressSpace, Tracking);
|
||||
}
|
||||
@ -136,12 +145,14 @@ namespace Ryujinx.Cpu
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Map(ulong va, nuint hostAddress, ulong size)
|
||||
public void Map(ulong va, ulong pa, ulong size)
|
||||
{
|
||||
AssertValidAddressAndSize(va, size);
|
||||
|
||||
_addressSpace.Commit(va, size);
|
||||
_addressSpace.MapView(_backingMemory, pa, va, size);
|
||||
_addressSpaceMirror.MapView(_backingMemory, pa, va, size);
|
||||
AddMapping(va, size);
|
||||
PtMap(va, pa, size);
|
||||
|
||||
Tracking.Map(va, size);
|
||||
}
|
||||
@ -155,7 +166,32 @@ namespace Ryujinx.Cpu
|
||||
Tracking.Unmap(va, size);
|
||||
|
||||
RemoveMapping(va, size);
|
||||
_addressSpace.Decommit(va, size);
|
||||
PtUnmap(va, size);
|
||||
_addressSpace.UnmapView(_backingMemory, va, size);
|
||||
_addressSpaceMirror.UnmapView(_backingMemory, va, size);
|
||||
}
|
||||
|
||||
private void PtMap(ulong va, ulong pa, ulong size)
|
||||
{
|
||||
while (size != 0)
|
||||
{
|
||||
_pageTable.Map(va, pa);
|
||||
|
||||
va += PageSize;
|
||||
pa += PageSize;
|
||||
size -= PageSize;
|
||||
}
|
||||
}
|
||||
|
||||
private void PtUnmap(ulong va, ulong size)
|
||||
{
|
||||
while (size != 0)
|
||||
{
|
||||
_pageTable.Unmap(va);
|
||||
|
||||
va += PageSize;
|
||||
size -= PageSize;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@ -216,6 +252,7 @@ namespace Ryujinx.Cpu
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Write<T>(ulong va, T value) where T : unmanaged
|
||||
{
|
||||
@ -267,7 +304,7 @@ namespace Ryujinx.Cpu
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ReadOnlySpan<byte> GetSpan(ulong va, int size, bool tracked = false)
|
||||
@ -322,7 +359,7 @@ namespace Ryujinx.Cpu
|
||||
int bit = (int)((page & 31) << 1);
|
||||
|
||||
int pageIndex = (int)(page >> PageToPteShift);
|
||||
ref ulong pageRef = ref _pageTable[pageIndex];
|
||||
ref ulong pageRef = ref _pageBitmap[pageIndex];
|
||||
|
||||
ulong pte = Volatile.Read(ref pageRef);
|
||||
|
||||
@ -373,7 +410,7 @@ namespace Ryujinx.Cpu
|
||||
mask &= endMask;
|
||||
}
|
||||
|
||||
ref ulong pageRef = ref _pageTable[pageIndex++];
|
||||
ref ulong pageRef = ref _pageBitmap[pageIndex++];
|
||||
ulong pte = Volatile.Read(ref pageRef);
|
||||
|
||||
pte |= pte >> 1;
|
||||
@ -389,16 +426,53 @@ namespace Ryujinx.Cpu
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<HostMemoryRange> GetPhysicalRegions(ulong va, ulong size)
|
||||
public IEnumerable<MemoryRange> GetPhysicalRegions(ulong va, ulong size)
|
||||
{
|
||||
if (size == 0)
|
||||
int pages = GetPagesCount(va, (uint)size, out va);
|
||||
|
||||
var regions = new List<MemoryRange>();
|
||||
|
||||
ulong regionStart = GetPhysicalAddressChecked(va);
|
||||
ulong regionSize = PageSize;
|
||||
|
||||
for (int page = 0; page < pages - 1; page++)
|
||||
{
|
||||
return Enumerable.Empty<HostMemoryRange>();
|
||||
if (!ValidateAddress(va + PageSize))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
ulong newPa = GetPhysicalAddressChecked(va + PageSize);
|
||||
|
||||
if (GetPhysicalAddressChecked(va) + PageSize != newPa)
|
||||
{
|
||||
regions.Add(new MemoryRange(regionStart, regionSize));
|
||||
regionStart = newPa;
|
||||
regionSize = 0;
|
||||
}
|
||||
|
||||
va += PageSize;
|
||||
regionSize += PageSize;
|
||||
}
|
||||
|
||||
AssertMapped(va, size);
|
||||
regions.Add(new MemoryRange(regionStart, regionSize));
|
||||
|
||||
return new HostMemoryRange[] { new HostMemoryRange(_addressSpaceMirror.GetPointer(va, size), size) };
|
||||
return regions;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@ -427,7 +501,7 @@ namespace Ryujinx.Cpu
|
||||
int bit = (int)((pageStart & 31) << 1);
|
||||
|
||||
int pageIndex = (int)(pageStart >> PageToPteShift);
|
||||
ref ulong pageRef = ref _pageTable[pageIndex];
|
||||
ref ulong pageRef = ref _pageBitmap[pageIndex];
|
||||
|
||||
ulong pte = Volatile.Read(ref pageRef);
|
||||
ulong state = ((pte >> bit) & 3);
|
||||
@ -459,7 +533,7 @@ namespace Ryujinx.Cpu
|
||||
mask &= endMask;
|
||||
}
|
||||
|
||||
ref ulong pageRef = ref _pageTable[pageIndex++];
|
||||
ref ulong pageRef = ref _pageBitmap[pageIndex++];
|
||||
|
||||
ulong pte = Volatile.Read(ref pageRef);
|
||||
ulong mappedMask = mask & BlockMappedMask;
|
||||
@ -530,7 +604,7 @@ namespace Ryujinx.Cpu
|
||||
ulong tag = protTag << bit;
|
||||
|
||||
int pageIndex = (int)(pageStart >> PageToPteShift);
|
||||
ref ulong pageRef = ref _pageTable[pageIndex];
|
||||
ref ulong pageRef = ref _pageBitmap[pageIndex];
|
||||
|
||||
ulong pte;
|
||||
|
||||
@ -562,7 +636,7 @@ namespace Ryujinx.Cpu
|
||||
mask &= endMask;
|
||||
}
|
||||
|
||||
ref ulong pageRef = ref _pageTable[pageIndex++];
|
||||
ref ulong pageRef = ref _pageBitmap[pageIndex++];
|
||||
|
||||
ulong pte;
|
||||
ulong mappedMask;
|
||||
@ -632,7 +706,7 @@ namespace Ryujinx.Cpu
|
||||
mask &= endMask;
|
||||
}
|
||||
|
||||
ref ulong pageRef = ref _pageTable[pageIndex++];
|
||||
ref ulong pageRef = ref _pageBitmap[pageIndex++];
|
||||
|
||||
ulong pte;
|
||||
ulong mappedMask;
|
||||
@ -677,7 +751,7 @@ namespace Ryujinx.Cpu
|
||||
mask |= endMask;
|
||||
}
|
||||
|
||||
ref ulong pageRef = ref _pageTable[pageIndex++];
|
||||
ref ulong pageRef = ref _pageBitmap[pageIndex++];
|
||||
ulong pte;
|
||||
|
||||
do
|
||||
@ -695,11 +769,11 @@ namespace Ryujinx.Cpu
|
||||
/// </summary>
|
||||
protected override void Destroy()
|
||||
{
|
||||
_addressSpaceMirror.Dispose();
|
||||
_addressSpace.Dispose();
|
||||
_addressSpaceMirror.Dispose();
|
||||
_memoryEh.Dispose();
|
||||
}
|
||||
|
||||
private void ThrowInvalidMemoryRegionException(string message) => throw new InvalidMemoryRegionException(message);
|
||||
private static void ThrowInvalidMemoryRegionException(string message) => throw new InvalidMemoryRegionException(message);
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,12 @@
|
||||
using Ryujinx.Graphics.Shader.Translation;
|
||||
|
||||
namespace Ryujinx.Graphics.GAL
|
||||
{
|
||||
public struct Capabilities
|
||||
{
|
||||
public readonly TargetApi Api;
|
||||
public readonly string VendorName;
|
||||
|
||||
public readonly bool HasFrontFacingBug;
|
||||
public readonly bool HasVectorIndexingBug;
|
||||
|
||||
@ -24,6 +29,8 @@ namespace Ryujinx.Graphics.GAL
|
||||
public readonly int StorageBufferOffsetAlignment;
|
||||
|
||||
public Capabilities(
|
||||
TargetApi api,
|
||||
string vendorName,
|
||||
bool hasFrontFacingBug,
|
||||
bool hasVectorIndexingBug,
|
||||
bool supportsAstcCompression,
|
||||
@ -43,6 +50,8 @@ namespace Ryujinx.Graphics.GAL
|
||||
float maximumSupportedAnisotropy,
|
||||
int storageBufferOffsetAlignment)
|
||||
{
|
||||
Api = api;
|
||||
VendorName = vendorName;
|
||||
HasFrontFacingBug = hasFrontFacingBug;
|
||||
HasVectorIndexingBug = hasVectorIndexingBug;
|
||||
SupportsAstcCompression = supportsAstcCompression;
|
||||
|
@ -1,47 +0,0 @@
|
||||
namespace Ryujinx.Graphics.GAL
|
||||
{
|
||||
public struct DepthStencilState
|
||||
{
|
||||
public bool DepthTestEnable { get; }
|
||||
public bool DepthWriteEnable { get; }
|
||||
public bool StencilTestEnable { get; }
|
||||
|
||||
public CompareOp DepthFunc { get; }
|
||||
public CompareOp StencilFrontFunc { get; }
|
||||
public StencilOp StencilFrontSFail { get; }
|
||||
public StencilOp StencilFrontDpPass { get; }
|
||||
public StencilOp StencilFrontDpFail { get; }
|
||||
public CompareOp StencilBackFunc { get; }
|
||||
public StencilOp StencilBackSFail { get; }
|
||||
public StencilOp StencilBackDpPass { get; }
|
||||
public StencilOp StencilBackDpFail { get; }
|
||||
|
||||
public DepthStencilState(
|
||||
bool depthTestEnable,
|
||||
bool depthWriteEnable,
|
||||
bool stencilTestEnable,
|
||||
CompareOp depthFunc,
|
||||
CompareOp stencilFrontFunc,
|
||||
StencilOp stencilFrontSFail,
|
||||
StencilOp stencilFrontDpPass,
|
||||
StencilOp stencilFrontDpFail,
|
||||
CompareOp stencilBackFunc,
|
||||
StencilOp stencilBackSFail,
|
||||
StencilOp stencilBackDpPass,
|
||||
StencilOp stencilBackDpFail)
|
||||
{
|
||||
DepthTestEnable = depthTestEnable;
|
||||
DepthWriteEnable = depthWriteEnable;
|
||||
StencilTestEnable = stencilTestEnable;
|
||||
DepthFunc = depthFunc;
|
||||
StencilFrontFunc = stencilFrontFunc;
|
||||
StencilFrontSFail = stencilFrontSFail;
|
||||
StencilFrontDpPass = stencilFrontDpPass;
|
||||
StencilFrontDpFail = stencilFrontDpFail;
|
||||
StencilBackFunc = stencilBackFunc;
|
||||
StencilBackSFail = stencilBackSFail;
|
||||
StencilBackDpPass = stencilBackDpPass;
|
||||
StencilBackDpFail = stencilBackDpFail;
|
||||
}
|
||||
}
|
||||
}
|
@ -52,7 +52,7 @@ namespace Ryujinx.Graphics.GAL
|
||||
R32G32B32A32Sint,
|
||||
S8Uint,
|
||||
D16Unorm,
|
||||
D24X8Unorm,
|
||||
S8UintD24Unorm,
|
||||
D32Float,
|
||||
D24UnormS8Uint,
|
||||
D32FloatS8Uint,
|
||||
@ -266,7 +266,7 @@ namespace Ryujinx.Graphics.GAL
|
||||
{
|
||||
case Format.D16Unorm:
|
||||
case Format.D24UnormS8Uint:
|
||||
case Format.D24X8Unorm:
|
||||
case Format.S8UintD24Unorm:
|
||||
case Format.D32Float:
|
||||
case Format.D32FloatS8Uint:
|
||||
case Format.S8Uint:
|
||||
|
@ -16,11 +16,9 @@ namespace Ryujinx.Graphics.GAL
|
||||
|
||||
void BackgroundContextAction(Action action, bool alwaysBackground = false);
|
||||
|
||||
IShader CompileShader(ShaderStage stage, string code);
|
||||
|
||||
BufferHandle CreateBuffer(int size);
|
||||
|
||||
IProgram CreateProgram(IShader[] shaders, ShaderInfo info);
|
||||
IProgram CreateProgram(ShaderSource[] shaders, ShaderInfo info);
|
||||
|
||||
ISampler CreateSampler(SamplerCreateInfo info);
|
||||
ITexture CreateTexture(TextureCreateInfo info, float scale);
|
||||
|
@ -4,7 +4,6 @@ using Ryujinx.Graphics.GAL.Multithreading.Commands.CounterEvent;
|
||||
using Ryujinx.Graphics.GAL.Multithreading.Commands.Program;
|
||||
using Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer;
|
||||
using Ryujinx.Graphics.GAL.Multithreading.Commands.Sampler;
|
||||
using Ryujinx.Graphics.GAL.Multithreading.Commands.Shader;
|
||||
using Ryujinx.Graphics.GAL.Multithreading.Commands.Texture;
|
||||
using Ryujinx.Graphics.GAL.Multithreading.Commands.Window;
|
||||
using System;
|
||||
@ -53,8 +52,6 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
||||
{
|
||||
_lookup[(int)CommandType.Action] = (Span<byte> memory, ThreadedRenderer threaded, IRenderer renderer) =>
|
||||
ActionCommand.Run(ref GetCommand<ActionCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.CompileShader] = (Span<byte> memory, ThreadedRenderer threaded, IRenderer renderer) =>
|
||||
CompileShaderCommand.Run(ref GetCommand<CompileShaderCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.CreateBuffer] = (Span<byte> memory, ThreadedRenderer threaded, IRenderer renderer) =>
|
||||
CreateBufferCommand.Run(ref GetCommand<CreateBufferCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.CreateProgram] = (Span<byte> memory, ThreadedRenderer threaded, IRenderer renderer) =>
|
||||
@ -98,9 +95,6 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
||||
_lookup[(int)CommandType.SamplerDispose] = (Span<byte> memory, ThreadedRenderer threaded, IRenderer renderer) =>
|
||||
SamplerDisposeCommand.Run(ref GetCommand<SamplerDisposeCommand>(memory), threaded, renderer);
|
||||
|
||||
_lookup[(int)CommandType.ShaderDispose] = (Span<byte> memory, ThreadedRenderer threaded, IRenderer renderer) =>
|
||||
ShaderDisposeCommand.Run(ref GetCommand<ShaderDisposeCommand>(memory), threaded, renderer);
|
||||
|
||||
_lookup[(int)CommandType.TextureCopyTo] = (Span<byte> memory, ThreadedRenderer threaded, IRenderer renderer) =>
|
||||
TextureCopyToCommand.Run(ref GetCommand<TextureCopyToCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.TextureCopyToScaled] = (Span<byte> memory, ThreadedRenderer threaded, IRenderer renderer) =>
|
||||
|
@ -3,7 +3,6 @@
|
||||
enum CommandType : byte
|
||||
{
|
||||
Action,
|
||||
CompileShader,
|
||||
CreateBuffer,
|
||||
CreateProgram,
|
||||
CreateSampler,
|
||||
@ -29,8 +28,6 @@
|
||||
|
||||
SamplerDispose,
|
||||
|
||||
ShaderDispose,
|
||||
|
||||
TextureCopyTo,
|
||||
TextureCopyToScaled,
|
||||
TextureCopyToSlice,
|
||||
|
@ -1,22 +0,0 @@
|
||||
using Ryujinx.Graphics.GAL.Multithreading.Model;
|
||||
using Ryujinx.Graphics.GAL.Multithreading.Resources;
|
||||
|
||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer
|
||||
{
|
||||
struct CompileShaderCommand : IGALCommand
|
||||
{
|
||||
public CommandType CommandType => CommandType.CompileShader;
|
||||
private TableRef<ThreadedShader> _shader;
|
||||
|
||||
public void Set(TableRef<ThreadedShader> shader)
|
||||
{
|
||||
_shader = shader;
|
||||
}
|
||||
|
||||
public static void Run(ref CompileShaderCommand command, ThreadedRenderer threaded, IRenderer renderer)
|
||||
{
|
||||
ThreadedShader shader = command._shader.Get(threaded);
|
||||
shader.EnsureCreated();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,4 @@
|
||||
using Ryujinx.Graphics.GAL.Multithreading.Resources;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
|
||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer
|
||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer
|
||||
{
|
||||
struct CreateBufferCommand : IGALCommand
|
||||
{
|
||||
|
@ -1,6 +1,4 @@
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer
|
||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer
|
||||
{
|
||||
struct PreFrameCommand : IGALCommand
|
||||
{
|
||||
|
@ -1,21 +0,0 @@
|
||||
using Ryujinx.Graphics.GAL.Multithreading.Model;
|
||||
using Ryujinx.Graphics.GAL.Multithreading.Resources;
|
||||
|
||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Shader
|
||||
{
|
||||
struct ShaderDisposeCommand : IGALCommand
|
||||
{
|
||||
public CommandType CommandType => CommandType.ShaderDispose;
|
||||
private TableRef<ThreadedShader> _shader;
|
||||
|
||||
public void Set(TableRef<ThreadedShader> shader)
|
||||
{
|
||||
_shader = shader;
|
||||
}
|
||||
|
||||
public static void Run(ref ShaderDisposeCommand command, ThreadedRenderer threaded, IRenderer renderer)
|
||||
{
|
||||
command._shader.Get(threaded).Base.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
@ -6,10 +6,10 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Resources.Programs
|
||||
{
|
||||
public ThreadedProgram Threaded { get; set; }
|
||||
|
||||
private IShader[] _shaders;
|
||||
private ShaderSource[] _shaders;
|
||||
private ShaderInfo _info;
|
||||
|
||||
public SourceProgramRequest(ThreadedProgram program, IShader[] shaders, ShaderInfo info)
|
||||
public SourceProgramRequest(ThreadedProgram program, ShaderSource[] shaders, ShaderInfo info)
|
||||
{
|
||||
Threaded = program;
|
||||
|
||||
@ -19,14 +19,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Resources.Programs
|
||||
|
||||
public IProgram Create(IRenderer renderer)
|
||||
{
|
||||
IShader[] shaders = _shaders.Select(shader =>
|
||||
{
|
||||
var threaded = (ThreadedShader)shader;
|
||||
threaded?.EnsureCreated();
|
||||
return threaded?.Base;
|
||||
}).ToArray();
|
||||
|
||||
return renderer.CreateProgram(shaders, _info);
|
||||
return renderer.CreateProgram(_shaders, _info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,38 +0,0 @@
|
||||
using Ryujinx.Graphics.GAL.Multithreading.Commands.Shader;
|
||||
using Ryujinx.Graphics.GAL.Multithreading.Model;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
|
||||
namespace Ryujinx.Graphics.GAL.Multithreading.Resources
|
||||
{
|
||||
class ThreadedShader : IShader
|
||||
{
|
||||
private ThreadedRenderer _renderer;
|
||||
private ShaderStage _stage;
|
||||
private string _code;
|
||||
|
||||
public IShader Base;
|
||||
|
||||
public ThreadedShader(ThreadedRenderer renderer, ShaderStage stage, string code)
|
||||
{
|
||||
_renderer = renderer;
|
||||
|
||||
_stage = stage;
|
||||
_code = code;
|
||||
}
|
||||
|
||||
internal void EnsureCreated()
|
||||
{
|
||||
if (_code != null && Base == null)
|
||||
{
|
||||
Base = _renderer.BaseRenderer.CompileShader(_stage, _code);
|
||||
_code = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_renderer.New<ShaderDisposeCommand>().Set(new TableRef<ThreadedShader>(_renderer, this));
|
||||
_renderer.QueueCommand();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
using Ryujinx.Graphics.GAL.Multithreading.Commands;
|
||||
using Ryujinx.Graphics.GAL.Multithreading.Model;
|
||||
using Ryujinx.Graphics.GAL.Multithreading.Resources;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
|
@ -250,15 +250,6 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
||||
}
|
||||
}
|
||||
|
||||
public IShader CompileShader(ShaderStage stage, string code)
|
||||
{
|
||||
var shader = new ThreadedShader(this, stage, code);
|
||||
New<CompileShaderCommand>().Set(Ref(shader));
|
||||
QueueCommand();
|
||||
|
||||
return shader;
|
||||
}
|
||||
|
||||
public BufferHandle CreateBuffer(int size)
|
||||
{
|
||||
BufferHandle handle = Buffers.CreateBufferHandle();
|
||||
@ -268,7 +259,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
||||
return handle;
|
||||
}
|
||||
|
||||
public IProgram CreateProgram(IShader[] shaders, ShaderInfo info)
|
||||
public IProgram CreateProgram(ShaderSource[] shaders, ShaderInfo info)
|
||||
{
|
||||
var program = new ThreadedProgram(this);
|
||||
SourceProgramRequest request = new SourceProgramRequest(program, shaders, info);
|
||||
|
29
Ryujinx.Graphics.GAL/ShaderSource.cs
Normal file
29
Ryujinx.Graphics.GAL/ShaderSource.cs
Normal file
@ -0,0 +1,29 @@
|
||||
using Ryujinx.Graphics.Shader;
|
||||
using Ryujinx.Graphics.Shader.Translation;
|
||||
|
||||
namespace Ryujinx.Graphics.GAL
|
||||
{
|
||||
public struct ShaderSource
|
||||
{
|
||||
public string Code { get; }
|
||||
public byte[] BinaryCode { get; }
|
||||
public ShaderStage Stage { get; }
|
||||
public TargetLanguage Language { get; }
|
||||
|
||||
public ShaderSource(string code, byte[] binaryCode, ShaderStage stage, TargetLanguage language)
|
||||
{
|
||||
Code = code;
|
||||
BinaryCode = binaryCode;
|
||||
Stage = stage;
|
||||
Language = language;
|
||||
}
|
||||
|
||||
public ShaderSource(string code, ShaderStage stage, TargetLanguage language) : this(code, null, stage, language)
|
||||
{
|
||||
}
|
||||
|
||||
public ShaderSource(byte[] binaryCode, ShaderStage stage, TargetLanguage language) : this(null, binaryCode, stage, language)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@ -9,7 +9,6 @@ namespace Ryujinx.Graphics.GAL
|
||||
Texture2DArray,
|
||||
Texture2DMultisample,
|
||||
Texture2DMultisampleArray,
|
||||
Rectangle,
|
||||
Cubemap,
|
||||
CubemapArray,
|
||||
TextureBuffer
|
||||
|
@ -124,24 +124,20 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute
|
||||
ulong samplerPoolGpuVa = ((ulong)_state.State.SetTexSamplerPoolAOffsetUpper << 32) | _state.State.SetTexSamplerPoolB;
|
||||
ulong texturePoolGpuVa = ((ulong)_state.State.SetTexHeaderPoolAOffsetUpper << 32) | _state.State.SetTexHeaderPoolB;
|
||||
|
||||
GpuAccessorState gas = new GpuAccessorState(
|
||||
GpuChannelPoolState poolState = new GpuChannelPoolState(
|
||||
texturePoolGpuVa,
|
||||
_state.State.SetTexHeaderPoolCMaximumIndex,
|
||||
_state.State.SetBindlessTextureConstantBufferSlotSelect,
|
||||
false,
|
||||
PrimitiveTopology.Points,
|
||||
default);
|
||||
_state.State.SetBindlessTextureConstantBufferSlotSelect);
|
||||
|
||||
ShaderBundle cs = memoryManager.Physical.ShaderCache.GetComputeShader(
|
||||
_channel,
|
||||
gas,
|
||||
shaderGpuVa,
|
||||
GpuChannelComputeState computeState = new GpuChannelComputeState(
|
||||
qmd.CtaThreadDimension0,
|
||||
qmd.CtaThreadDimension1,
|
||||
qmd.CtaThreadDimension2,
|
||||
localMemorySize,
|
||||
sharedMemorySize);
|
||||
|
||||
CachedShaderProgram cs = memoryManager.Physical.ShaderCache.GetComputeShader(_channel, poolState, computeState, shaderGpuVa);
|
||||
|
||||
_context.Renderer.Pipeline.SetProgram(cs.HostProgram);
|
||||
|
||||
_channel.TextureManager.SetComputeSamplerPool(samplerPoolGpuVa, _state.State.SetTexSamplerPoolCMaximumIndex, qmd.SamplerIndex);
|
||||
|
@ -1,7 +1,7 @@
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Graphics.Device;
|
||||
using Ryujinx.Graphics.Gpu.Engine.Threed;
|
||||
using Ryujinx.Graphics.Gpu.Memory;
|
||||
using Ryujinx.Graphics.Texture;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@ -330,11 +330,95 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma
|
||||
{
|
||||
// TODO: Implement remap functionality.
|
||||
// Buffer to buffer copy.
|
||||
memoryManager.Physical.BufferCache.CopyBuffer(memoryManager, srcGpuVa, dstGpuVa, size);
|
||||
|
||||
bool srcIsPitchKind = memoryManager.GetKind(srcGpuVa).IsPitch();
|
||||
bool dstIsPitchKind = memoryManager.GetKind(dstGpuVa).IsPitch();
|
||||
|
||||
if (!srcIsPitchKind && dstIsPitchKind)
|
||||
{
|
||||
CopyGobBlockLinearToLinear(memoryManager, srcGpuVa, dstGpuVa, size);
|
||||
}
|
||||
else if (srcIsPitchKind && !dstIsPitchKind)
|
||||
{
|
||||
CopyGobLinearToBlockLinear(memoryManager, srcGpuVa, dstGpuVa, size);
|
||||
}
|
||||
else
|
||||
{
|
||||
memoryManager.Physical.BufferCache.CopyBuffer(memoryManager, srcGpuVa, dstGpuVa, size);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies block linear data with block linear GOBs to a block linear destination with linear GOBs.
|
||||
/// </summary>
|
||||
/// <param name="memoryManager">GPU memory manager</param>
|
||||
/// <param name="srcGpuVa">Source GPU virtual address</param>
|
||||
/// <param name="dstGpuVa">Destination GPU virtual address</param>
|
||||
/// <param name="size">Size in bytes of the copy</param>
|
||||
private static void CopyGobBlockLinearToLinear(MemoryManager memoryManager, ulong srcGpuVa, ulong dstGpuVa, ulong size)
|
||||
{
|
||||
if (((srcGpuVa | dstGpuVa | size) & 0xf) == 0)
|
||||
{
|
||||
for (ulong offset = 0; offset < size; offset += 16)
|
||||
{
|
||||
Vector128<byte> data = memoryManager.Read<Vector128<byte>>(ConvertGobLinearToBlockLinearAddress(srcGpuVa + offset), true);
|
||||
memoryManager.Write(dstGpuVa + offset, data);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (ulong offset = 0; offset < size; offset++)
|
||||
{
|
||||
byte data = memoryManager.Read<byte>(ConvertGobLinearToBlockLinearAddress(srcGpuVa + offset), true);
|
||||
memoryManager.Write(dstGpuVa + offset, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies block linear data with linear GOBs to a block linear destination with block linear GOBs.
|
||||
/// </summary>
|
||||
/// <param name="memoryManager">GPU memory manager</param>
|
||||
/// <param name="srcGpuVa">Source GPU virtual address</param>
|
||||
/// <param name="dstGpuVa">Destination GPU virtual address</param>
|
||||
/// <param name="size">Size in bytes of the copy</param>
|
||||
private static void CopyGobLinearToBlockLinear(MemoryManager memoryManager, ulong srcGpuVa, ulong dstGpuVa, ulong size)
|
||||
{
|
||||
if (((srcGpuVa | dstGpuVa | size) & 0xf) == 0)
|
||||
{
|
||||
for (ulong offset = 0; offset < size; offset += 16)
|
||||
{
|
||||
Vector128<byte> data = memoryManager.Read<Vector128<byte>>(srcGpuVa + offset, true);
|
||||
memoryManager.Write(ConvertGobLinearToBlockLinearAddress(dstGpuVa + offset), data);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (ulong offset = 0; offset < size; offset++)
|
||||
{
|
||||
byte data = memoryManager.Read<byte>(srcGpuVa + offset, true);
|
||||
memoryManager.Write(ConvertGobLinearToBlockLinearAddress(dstGpuVa + offset), data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the GOB block linear address from a linear address.
|
||||
/// </summary>
|
||||
/// <param name="address">Linear address</param>
|
||||
/// <returns>Block linear address</returns>
|
||||
private static ulong ConvertGobLinearToBlockLinearAddress(ulong address)
|
||||
{
|
||||
// y2 y1 y0 x5 x4 x3 x2 x1 x0 -> x5 y2 y1 x4 y0 x3 x2 x1 x0
|
||||
return (address & ~0x1f0UL) |
|
||||
((address & 0x40) >> 2) |
|
||||
((address & 0x10) << 1) |
|
||||
((address & 0x180) >> 1) |
|
||||
((address & 0x20) << 3);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a buffer to buffer, or buffer to texture copy, then optionally releases a semaphore.
|
||||
/// </summary>
|
||||
|
@ -525,7 +525,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
int scissorW = screenScissorState.Width;
|
||||
int scissorH = screenScissorState.Height;
|
||||
|
||||
if (clearAffectedByScissor)
|
||||
if (clearAffectedByScissor && _state.State.ScissorState[0].Enable)
|
||||
{
|
||||
ref var scissorState = ref _state.State.ScissorState[0];
|
||||
|
||||
|
@ -7,7 +7,6 @@ using Ryujinx.Graphics.Shader;
|
||||
using Ryujinx.Graphics.Texture;
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
{
|
||||
@ -20,6 +19,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
public const int RasterizerStateIndex = 1;
|
||||
public const int ScissorStateIndex = 2;
|
||||
public const int VertexBufferStateIndex = 3;
|
||||
public const int PrimitiveRestartStateIndex = 4;
|
||||
|
||||
private readonly GpuContext _context;
|
||||
private readonly GpuChannel _channel;
|
||||
@ -29,11 +29,14 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
private readonly StateUpdateTracker<ThreedClassState> _updateTracker;
|
||||
|
||||
private readonly ShaderProgramInfo[] _currentProgramInfo;
|
||||
private ShaderSpecializationState _shaderSpecState;
|
||||
|
||||
private bool _vtgWritesRtLayer;
|
||||
private byte _vsClipDistancesWritten;
|
||||
|
||||
private bool _prevDrawIndexed;
|
||||
private IndexType _prevIndexType;
|
||||
private uint _prevFirstVertex;
|
||||
private bool _prevTfEnable;
|
||||
|
||||
/// <summary>
|
||||
@ -75,6 +78,10 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
nameof(ThreedClassState.VertexBufferState),
|
||||
nameof(ThreedClassState.VertexBufferEndAddress)),
|
||||
|
||||
new StateUpdateCallbackEntry(UpdatePrimitiveRestartState,
|
||||
nameof(ThreedClassState.PrimitiveRestartDrawArrays),
|
||||
nameof(ThreedClassState.PrimitiveRestartState)),
|
||||
|
||||
new StateUpdateCallbackEntry(UpdateTessellationState,
|
||||
nameof(ThreedClassState.TessOuterLevel),
|
||||
nameof(ThreedClassState.TessInnerLevel),
|
||||
@ -140,8 +147,6 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
nameof(ThreedClassState.PointSpriteEnable),
|
||||
nameof(ThreedClassState.PointCoordReplace)),
|
||||
|
||||
new StateUpdateCallbackEntry(UpdatePrimitiveRestartState, nameof(ThreedClassState.PrimitiveRestartState)),
|
||||
|
||||
new StateUpdateCallbackEntry(UpdateIndexBufferState,
|
||||
nameof(ThreedClassState.IndexBufferState),
|
||||
nameof(ThreedClassState.IndexBufferCount)),
|
||||
@ -190,6 +195,17 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Update()
|
||||
{
|
||||
// If any state that the shader depends on changed,
|
||||
// then we may need to compile/bind a different version
|
||||
// of the shader for the new state.
|
||||
if (_shaderSpecState != null)
|
||||
{
|
||||
if (!_shaderSpecState.MatchesGraphics(_channel, GetPoolState()))
|
||||
{
|
||||
ForceShaderUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
// The vertex buffer size is calculated using a different
|
||||
// method when doing indexed draws, so we need to make sure
|
||||
// to update the vertex buffers if we are doing a regular
|
||||
@ -197,9 +213,31 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
if (_drawState.DrawIndexed != _prevDrawIndexed)
|
||||
{
|
||||
_updateTracker.ForceDirty(VertexBufferStateIndex);
|
||||
|
||||
// If PrimitiveRestartDrawArrays is false and this is a non-indexed draw, we need to ensure primitive restart is disabled.
|
||||
// If PrimitiveRestartDrawArrays is false and this is a indexed draw, we need to ensure primitive restart enable matches GPU state.
|
||||
// If PrimitiveRestartDrawArrays is true, then primitive restart enable should always match GPU state.
|
||||
// That is because "PrimitiveRestartDrawArrays" is not configurable on the backend, it is always
|
||||
// true on OpenGL and always false on Vulkan.
|
||||
if (!_state.State.PrimitiveRestartDrawArrays && _state.State.PrimitiveRestartState.Enable)
|
||||
{
|
||||
_updateTracker.ForceDirty(PrimitiveRestartStateIndex);
|
||||
}
|
||||
|
||||
_prevDrawIndexed = _drawState.DrawIndexed;
|
||||
}
|
||||
|
||||
// In some cases, the index type is also used to guess the
|
||||
// vertex buffer size, so we must update it if the type changed too.
|
||||
if (_drawState.DrawIndexed &&
|
||||
(_prevIndexType != _state.State.IndexBufferState.Type ||
|
||||
_prevFirstVertex != _state.State.FirstVertex))
|
||||
{
|
||||
_updateTracker.ForceDirty(VertexBufferStateIndex);
|
||||
_prevIndexType = _state.State.IndexBufferState.Type;
|
||||
_prevFirstVertex = _state.State.FirstVertex;
|
||||
}
|
||||
|
||||
bool tfEnable = _state.State.TfEnable;
|
||||
|
||||
if (!tfEnable && _prevTfEnable)
|
||||
@ -816,8 +854,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
private void UpdatePrimitiveRestartState()
|
||||
{
|
||||
PrimitiveRestartState primitiveRestart = _state.State.PrimitiveRestartState;
|
||||
bool enable = primitiveRestart.Enable && (_drawState.DrawIndexed || _state.State.PrimitiveRestartDrawArrays);
|
||||
|
||||
_context.Renderer.Pipeline.SetPrimitiveRestart(primitiveRestart.Enable, primitiveRestart.Index);
|
||||
_context.Renderer.Pipeline.SetPrimitiveRestart(enable, primitiveRestart.Index);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -852,6 +891,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
/// </summary>
|
||||
private void UpdateVertexBufferState()
|
||||
{
|
||||
IndexType indexType = _state.State.IndexBufferState.Type;
|
||||
bool indexTypeSmall = indexType == IndexType.UByte || indexType == IndexType.UShort;
|
||||
|
||||
_drawState.IsAnyVbInstanced = false;
|
||||
|
||||
for (int index = 0; index < Constants.TotalVertexBuffers; index++)
|
||||
@ -883,12 +925,27 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
{
|
||||
// This size may be (much) larger than the real vertex buffer size.
|
||||
// Avoid calculating it this way, unless we don't have any other option.
|
||||
|
||||
size = endAddress.Pack() - address + 1;
|
||||
|
||||
if (stride > 0 && indexTypeSmall && _drawState.DrawIndexed && !instanced)
|
||||
{
|
||||
// If the index type is a small integer type, then we might be still able
|
||||
// to reduce the vertex buffer size based on the maximum possible index value.
|
||||
|
||||
ulong maxVertexBufferSize = indexType == IndexType.UByte ? 0x100UL : 0x10000UL;
|
||||
|
||||
maxVertexBufferSize += _state.State.FirstVertex;
|
||||
maxVertexBufferSize *= (uint)stride;
|
||||
|
||||
size = Math.Min(size, maxVertexBufferSize);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// For non-indexed draws, we can guess the size from the vertex count
|
||||
// and stride.
|
||||
|
||||
int firstInstance = (int)_state.State.FirstInstance;
|
||||
|
||||
var drawState = _state.State.VertexBufferDrawState;
|
||||
@ -1019,108 +1076,127 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
/// </summary>
|
||||
private void UpdateShaderState()
|
||||
{
|
||||
var shaderCache = _channel.MemoryManager.Physical.ShaderCache;
|
||||
|
||||
_vtgWritesRtLayer = false;
|
||||
|
||||
ShaderAddresses addresses = new ShaderAddresses();
|
||||
|
||||
Span<ShaderAddresses> addressesSpan = MemoryMarshal.CreateSpan(ref addresses, 1);
|
||||
|
||||
Span<ulong> addressesArray = MemoryMarshal.Cast<ShaderAddresses, ulong>(addressesSpan);
|
||||
Span<ulong> addressesSpan = addresses.AsSpan();
|
||||
|
||||
ulong baseAddress = _state.State.ShaderBaseAddress.Pack();
|
||||
|
||||
for (int index = 0; index < 6; index++)
|
||||
{
|
||||
var shader = _state.State.ShaderState[index];
|
||||
|
||||
if (!shader.UnpackEnable() && index != 1)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
addressesArray[index] = baseAddress + shader.Offset;
|
||||
addressesSpan[index] = baseAddress + shader.Offset;
|
||||
}
|
||||
|
||||
GpuAccessorState gas = new GpuAccessorState(
|
||||
_state.State.TexturePoolState.Address.Pack(),
|
||||
_state.State.TexturePoolState.MaximumId,
|
||||
(int)_state.State.TextureBufferIndex,
|
||||
_state.State.EarlyZForce,
|
||||
_drawState.Topology,
|
||||
_state.State.TessMode);
|
||||
GpuChannelPoolState poolState = GetPoolState();
|
||||
GpuChannelGraphicsState graphicsState = GetGraphicsState();
|
||||
|
||||
ShaderBundle gs = _channel.MemoryManager.Physical.ShaderCache.GetGraphicsShader(ref _state.State, _channel, gas, addresses);
|
||||
CachedShaderProgram gs = shaderCache.GetGraphicsShader(ref _state.State, _channel, poolState, graphicsState, addresses);
|
||||
|
||||
_shaderSpecState = gs.SpecializationState;
|
||||
|
||||
byte oldVsClipDistancesWritten = _vsClipDistancesWritten;
|
||||
|
||||
_drawState.VsUsesInstanceId = gs.Shaders[0]?.Info.UsesInstanceId ?? false;
|
||||
_vsClipDistancesWritten = gs.Shaders[0]?.Info.ClipDistancesWritten ?? 0;
|
||||
_vtgWritesRtLayer = false;
|
||||
_drawState.VsUsesInstanceId = gs.Shaders[1]?.Info.UsesInstanceId ?? false;
|
||||
_vsClipDistancesWritten = gs.Shaders[1]?.Info.ClipDistancesWritten ?? 0;
|
||||
|
||||
if (oldVsClipDistancesWritten != _vsClipDistancesWritten)
|
||||
{
|
||||
UpdateUserClipState();
|
||||
}
|
||||
|
||||
for (int stage = 0; stage < Constants.ShaderStages; stage++)
|
||||
for (int stageIndex = 0; stageIndex < Constants.ShaderStages; stageIndex++)
|
||||
{
|
||||
ShaderProgramInfo info = gs.Shaders[stage]?.Info;
|
||||
|
||||
_currentProgramInfo[stage] = info;
|
||||
|
||||
if (info == null)
|
||||
{
|
||||
_channel.TextureManager.RentGraphicsTextureBindings(stage, 0);
|
||||
_channel.TextureManager.RentGraphicsImageBindings(stage, 0);
|
||||
_channel.BufferManager.SetGraphicsStorageBufferBindings(stage, null);
|
||||
_channel.BufferManager.SetGraphicsUniformBufferBindings(stage, null);
|
||||
continue;
|
||||
}
|
||||
|
||||
Span<TextureBindingInfo> textureBindings = _channel.TextureManager.RentGraphicsTextureBindings(stage, info.Textures.Count);
|
||||
|
||||
if (info.UsesRtLayer)
|
||||
{
|
||||
_vtgWritesRtLayer = true;
|
||||
}
|
||||
|
||||
for (int index = 0; index < info.Textures.Count; index++)
|
||||
{
|
||||
var descriptor = info.Textures[index];
|
||||
|
||||
Target target = ShaderTexture.GetTarget(descriptor.Type);
|
||||
|
||||
textureBindings[index] = new TextureBindingInfo(
|
||||
target,
|
||||
descriptor.Binding,
|
||||
descriptor.CbufSlot,
|
||||
descriptor.HandleIndex,
|
||||
descriptor.Flags);
|
||||
}
|
||||
|
||||
TextureBindingInfo[] imageBindings = _channel.TextureManager.RentGraphicsImageBindings(stage, info.Images.Count);
|
||||
|
||||
for (int index = 0; index < info.Images.Count; index++)
|
||||
{
|
||||
var descriptor = info.Images[index];
|
||||
|
||||
Target target = ShaderTexture.GetTarget(descriptor.Type);
|
||||
Format format = ShaderTexture.GetFormat(descriptor.Format);
|
||||
|
||||
imageBindings[index] = new TextureBindingInfo(
|
||||
target,
|
||||
format,
|
||||
descriptor.Binding,
|
||||
descriptor.CbufSlot,
|
||||
descriptor.HandleIndex,
|
||||
descriptor.Flags);
|
||||
}
|
||||
|
||||
_channel.BufferManager.SetGraphicsStorageBufferBindings(stage, info.SBuffers);
|
||||
_channel.BufferManager.SetGraphicsUniformBufferBindings(stage, info.CBuffers);
|
||||
UpdateStageBindings(stageIndex, gs.Shaders[stageIndex + 1]?.Info);
|
||||
}
|
||||
|
||||
_context.Renderer.Pipeline.SetProgram(gs.HostProgram);
|
||||
}
|
||||
|
||||
private void UpdateStageBindings(int stage, ShaderProgramInfo info)
|
||||
{
|
||||
_currentProgramInfo[stage] = info;
|
||||
|
||||
if (info == null)
|
||||
{
|
||||
_channel.TextureManager.RentGraphicsTextureBindings(stage, 0);
|
||||
_channel.TextureManager.RentGraphicsImageBindings(stage, 0);
|
||||
_channel.BufferManager.SetGraphicsStorageBufferBindings(stage, null);
|
||||
_channel.BufferManager.SetGraphicsUniformBufferBindings(stage, null);
|
||||
return;
|
||||
}
|
||||
|
||||
Span<TextureBindingInfo> textureBindings = _channel.TextureManager.RentGraphicsTextureBindings(stage, info.Textures.Count);
|
||||
|
||||
if (info.UsesRtLayer)
|
||||
{
|
||||
_vtgWritesRtLayer = true;
|
||||
}
|
||||
|
||||
for (int index = 0; index < info.Textures.Count; index++)
|
||||
{
|
||||
var descriptor = info.Textures[index];
|
||||
|
||||
Target target = ShaderTexture.GetTarget(descriptor.Type);
|
||||
|
||||
textureBindings[index] = new TextureBindingInfo(
|
||||
target,
|
||||
descriptor.Binding,
|
||||
descriptor.CbufSlot,
|
||||
descriptor.HandleIndex,
|
||||
descriptor.Flags);
|
||||
}
|
||||
|
||||
TextureBindingInfo[] imageBindings = _channel.TextureManager.RentGraphicsImageBindings(stage, info.Images.Count);
|
||||
|
||||
for (int index = 0; index < info.Images.Count; index++)
|
||||
{
|
||||
var descriptor = info.Images[index];
|
||||
|
||||
Target target = ShaderTexture.GetTarget(descriptor.Type);
|
||||
Format format = ShaderTexture.GetFormat(descriptor.Format);
|
||||
|
||||
imageBindings[index] = new TextureBindingInfo(
|
||||
target,
|
||||
format,
|
||||
descriptor.Binding,
|
||||
descriptor.CbufSlot,
|
||||
descriptor.HandleIndex,
|
||||
descriptor.Flags);
|
||||
}
|
||||
|
||||
_channel.BufferManager.SetGraphicsStorageBufferBindings(stage, info.SBuffers);
|
||||
_channel.BufferManager.SetGraphicsUniformBufferBindings(stage, info.CBuffers);
|
||||
}
|
||||
|
||||
private GpuChannelPoolState GetPoolState()
|
||||
{
|
||||
return new GpuChannelPoolState(
|
||||
_state.State.TexturePoolState.Address.Pack(),
|
||||
_state.State.TexturePoolState.MaximumId,
|
||||
(int)_state.State.TextureBufferIndex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current GPU channel state for shader creation or compatibility verification.
|
||||
/// </summary>
|
||||
/// <returns>Current GPU channel state</returns>
|
||||
private GpuChannelGraphicsState GetGraphicsState()
|
||||
{
|
||||
return new GpuChannelGraphicsState(
|
||||
_state.State.EarlyZForce,
|
||||
_drawState.Topology,
|
||||
_state.State.TessMode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Forces the shaders to be rebound on the next draw.
|
||||
/// </summary>
|
||||
|
@ -730,7 +730,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
public int PatchVertices;
|
||||
public fixed uint ReservedDD0[4];
|
||||
public uint TextureBarrier;
|
||||
public fixed uint ReservedDE4[7];
|
||||
public uint WatchdogTimer;
|
||||
public Boolean32 PrimitiveRestartDrawArrays;
|
||||
public fixed uint ReservedDEC[5];
|
||||
public Array16<ScissorState> ScissorState;
|
||||
public fixed uint ReservedF00[21];
|
||||
public StencilBackMasks StencilBackMasks;
|
||||
|
@ -28,13 +28,13 @@ namespace Ryujinx.Graphics.Gpu.Engine.Types
|
||||
{
|
||||
return format switch
|
||||
{
|
||||
ZetaFormat.D32Float => new FormatInfo(Format.D32Float, 1, 1, 4, 1),
|
||||
ZetaFormat.D16Unorm => new FormatInfo(Format.D16Unorm, 1, 1, 2, 1),
|
||||
ZetaFormat.D24UnormS8Uint => new FormatInfo(Format.D24UnormS8Uint, 1, 1, 4, 2),
|
||||
ZetaFormat.D24Unorm => new FormatInfo(Format.D24UnormS8Uint, 1, 1, 4, 1),
|
||||
ZetaFormat.S8UintD24Unorm => new FormatInfo(Format.D24UnormS8Uint, 1, 1, 4, 2),
|
||||
ZetaFormat.S8Uint => new FormatInfo(Format.S8Uint, 1, 1, 1, 1),
|
||||
ZetaFormat.D32FloatS8Uint => new FormatInfo(Format.D32FloatS8Uint, 1, 1, 8, 2),
|
||||
ZetaFormat.D32Float => new FormatInfo(Format.D32Float, 1, 1, 4, 1),
|
||||
ZetaFormat.D16Unorm => new FormatInfo(Format.D16Unorm, 1, 1, 2, 1),
|
||||
ZetaFormat.D24UnormS8Uint => new FormatInfo(Format.D24UnormS8Uint, 1, 1, 4, 2),
|
||||
ZetaFormat.D24Unorm => new FormatInfo(Format.D24UnormS8Uint, 1, 1, 4, 1),
|
||||
ZetaFormat.S8UintD24Unorm => new FormatInfo(Format.S8UintD24Unorm, 1, 1, 4, 2),
|
||||
ZetaFormat.S8Uint => new FormatInfo(Format.S8Uint, 1, 1, 1, 1),
|
||||
ZetaFormat.D32FloatS8Uint => new FormatInfo(Format.D32FloatS8Uint, 1, 1, 8, 2),
|
||||
_ => FormatInfo.Default
|
||||
};
|
||||
}
|
||||
|
@ -238,13 +238,13 @@ namespace Ryujinx.Graphics.Gpu
|
||||
/// <summary>
|
||||
/// Initialize the GPU shader cache.
|
||||
/// </summary>
|
||||
public void InitializeShaderCache()
|
||||
public void InitializeShaderCache(CancellationToken cancellationToken)
|
||||
{
|
||||
HostInitalized.WaitOne();
|
||||
|
||||
foreach (var physicalMemory in PhysicalMemoryRegistry.Values)
|
||||
{
|
||||
physicalMemory.ShaderCache.Initialize();
|
||||
physicalMemory.ShaderCache.Initialize(cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -55,6 +55,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
{ 0x24a0e, new FormatInfo(Format.D24UnormS8Uint, 1, 1, 4, 2) },
|
||||
{ 0x24a29, new FormatInfo(Format.D24UnormS8Uint, 1, 1, 4, 2) },
|
||||
{ 0x48a29, new FormatInfo(Format.D24UnormS8Uint, 1, 1, 4, 2) },
|
||||
{ 0x4912b, new FormatInfo(Format.S8UintD24Unorm, 1, 1, 4, 2) },
|
||||
{ 0x25385, new FormatInfo(Format.D32FloatS8Uint, 1, 1, 8, 2) },
|
||||
{ 0x253b0, new FormatInfo(Format.D32FloatS8Uint, 1, 1, 8, 2) },
|
||||
{ 0xa4908, new FormatInfo(Format.R8G8B8A8Srgb, 1, 1, 4, 4) },
|
||||
|
@ -1136,17 +1136,33 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// <param name="range">Texture view physical memory ranges</param>
|
||||
/// <param name="layerSize">Layer size on the given texture</param>
|
||||
/// <param name="caps">Host GPU capabilities</param>
|
||||
/// <param name="allowMs">Indicates that multisample textures are allowed to match non-multisample requested textures</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, int layerSize, Capabilities caps, bool allowMs, out int firstLayer, out int firstLevel)
|
||||
{
|
||||
TextureViewCompatibility result = TextureViewCompatibility.Full;
|
||||
|
||||
result = TextureCompatibility.PropagateViewCompatibility(result, TextureCompatibility.ViewFormatCompatible(Info, info, caps));
|
||||
if (result != TextureViewCompatibility.Incompatible)
|
||||
{
|
||||
result = TextureCompatibility.PropagateViewCompatibility(result, TextureCompatibility.ViewTargetCompatible(Info, info));
|
||||
bool msTargetCompatible = false;
|
||||
|
||||
if (allowMs)
|
||||
{
|
||||
msTargetCompatible = Info.Target == Target.Texture2DMultisample && info.Target == Target.Texture2D;
|
||||
}
|
||||
|
||||
if (!msTargetCompatible)
|
||||
{
|
||||
result = TextureCompatibility.PropagateViewCompatibility(result, TextureCompatibility.ViewTargetCompatible(Info, info));
|
||||
|
||||
if (Info.SamplesInX != info.SamplesInX || Info.SamplesInY != info.SamplesInY)
|
||||
{
|
||||
result = TextureViewCompatibility.Incompatible;
|
||||
}
|
||||
}
|
||||
|
||||
if (result == TextureViewCompatibility.Full && Info.FormatInfo.Format != info.FormatInfo.Format && !_context.Capabilities.SupportsMismatchingViewFormat)
|
||||
{
|
||||
@ -1156,11 +1172,6 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
|
||||
result = TextureViewCompatibility.CopyOnly;
|
||||
}
|
||||
|
||||
if (Info.SamplesInX != info.SamplesInX || Info.SamplesInY != info.SamplesInY)
|
||||
{
|
||||
result = TextureViewCompatibility.Incompatible;
|
||||
}
|
||||
}
|
||||
|
||||
firstLayer = 0;
|
||||
|
@ -7,7 +7,6 @@ using Ryujinx.Graphics.Gpu.Engine.Types;
|
||||
using Ryujinx.Graphics.Gpu.Image;
|
||||
using Ryujinx.Graphics.Gpu.Memory;
|
||||
using Ryujinx.Graphics.Texture;
|
||||
using Ryujinx.Memory;
|
||||
using Ryujinx.Memory.Range;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@ -543,7 +542,14 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
for (int index = 0; index < overlapsCount; index++)
|
||||
{
|
||||
Texture overlap = _textureOverlaps[index];
|
||||
TextureViewCompatibility overlapCompatibility = overlap.IsViewCompatible(info, range.Value, sizeInfo.LayerSize, _context.Capabilities, out int firstLayer, out int firstLevel);
|
||||
TextureViewCompatibility overlapCompatibility = overlap.IsViewCompatible(
|
||||
info,
|
||||
range.Value,
|
||||
sizeInfo.LayerSize,
|
||||
_context.Capabilities,
|
||||
flags.HasFlag(TextureSearchFlags.ForCopy),
|
||||
out int firstLayer,
|
||||
out int firstLevel);
|
||||
|
||||
if (overlapCompatibility == TextureViewCompatibility.Full)
|
||||
{
|
||||
@ -651,7 +657,14 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
Texture overlap = _textureOverlaps[index];
|
||||
bool overlapInCache = overlap.CacheNode != null;
|
||||
|
||||
TextureViewCompatibility compatibility = texture.IsViewCompatible(overlap.Info, overlap.Range, overlap.LayerSize, _context.Capabilities, out int firstLayer, out int firstLevel);
|
||||
TextureViewCompatibility compatibility = texture.IsViewCompatible(
|
||||
overlap.Info,
|
||||
overlap.Range,
|
||||
overlap.LayerSize,
|
||||
_context.Capabilities,
|
||||
false,
|
||||
out int firstLayer,
|
||||
out int firstLevel);
|
||||
|
||||
if (overlap.IsView && compatibility == TextureViewCompatibility.Full)
|
||||
{
|
||||
@ -1001,20 +1014,34 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
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,
|
||||
info.SamplesInX,
|
||||
info.SamplesInY,
|
||||
samplesInX,
|
||||
samplesInY,
|
||||
info.Stride,
|
||||
info.IsLinear,
|
||||
info.GobBlocksInY,
|
||||
info.GobBlocksInZ,
|
||||
info.GobBlocksInTileX,
|
||||
info.Target,
|
||||
target,
|
||||
info.FormatInfo,
|
||||
info.DepthStencilMode,
|
||||
info.SwizzleR,
|
||||
|
@ -203,7 +203,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
}
|
||||
|
||||
if ((lhs.FormatInfo.Format == Format.D24UnormS8Uint ||
|
||||
lhs.FormatInfo.Format == Format.D24X8Unorm) && rhs.FormatInfo.Format == Format.B8G8R8A8Unorm)
|
||||
lhs.FormatInfo.Format == Format.S8UintD24Unorm) && rhs.FormatInfo.Format == Format.B8G8R8A8Unorm)
|
||||
{
|
||||
return TextureMatchQuality.FormatAlias;
|
||||
}
|
||||
|
@ -1393,6 +1393,12 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// <param name="size">The size of the flushing memory access</param>
|
||||
public void FlushAction(TextureGroupHandle handle, ulong address, ulong size)
|
||||
{
|
||||
// There is a small gap here where the action is removed but _actionRegistered is still 1.
|
||||
// In this case it will skip registering the action, but here we are already handling it,
|
||||
// so there shouldn't be any issue as it's the same handler for all actions.
|
||||
|
||||
handle.ClearActionRegistered();
|
||||
|
||||
if (!handle.Modified)
|
||||
{
|
||||
return;
|
||||
|
@ -2,6 +2,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
@ -32,9 +33,9 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
private ulong _modifiedSync;
|
||||
|
||||
/// <summary>
|
||||
/// Whether a tracking action is currently registered or not.
|
||||
/// Whether a tracking action is currently registered or not. (0/1)
|
||||
/// </summary>
|
||||
private bool _actionRegistered;
|
||||
private int _actionRegistered;
|
||||
|
||||
/// <summary>
|
||||
/// Whether a sync action is currently registered or not.
|
||||
@ -171,11 +172,9 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
_syncActionRegistered = true;
|
||||
}
|
||||
|
||||
if (!_actionRegistered)
|
||||
if (Interlocked.Exchange(ref _actionRegistered, 1) == 0)
|
||||
{
|
||||
_group.RegisterAction(this);
|
||||
|
||||
_actionRegistered = true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -233,8 +232,6 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// <param name="context">The GPU context used to wait for sync</param>
|
||||
public void Sync(GpuContext context)
|
||||
{
|
||||
_actionRegistered = false;
|
||||
|
||||
bool needsSync = !context.IsGpuThread();
|
||||
|
||||
if (needsSync)
|
||||
@ -263,21 +260,39 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the action registered variable, indicating that the tracking action should be
|
||||
/// re-registered on the next modification.
|
||||
/// </summary>
|
||||
public void ClearActionRegistered()
|
||||
{
|
||||
Interlocked.Exchange(ref _actionRegistered, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Action to perform when a sync number is registered after modification.
|
||||
/// This action will register a read tracking action on the memory tracking handle so that a flush from CPU can happen.
|
||||
/// </summary>
|
||||
private void SyncAction()
|
||||
{
|
||||
// The storage will need to signal modified again to update the sync number in future.
|
||||
_group.Storage.SignalModifiedDirty();
|
||||
|
||||
lock (Overlaps)
|
||||
{
|
||||
foreach (Texture texture in Overlaps)
|
||||
{
|
||||
texture.SignalModifiedDirty();
|
||||
}
|
||||
}
|
||||
|
||||
// Register region tracking for CPU? (again)
|
||||
_registeredSync = _modifiedSync;
|
||||
_syncActionRegistered = false;
|
||||
|
||||
if (!_actionRegistered)
|
||||
if (Interlocked.Exchange(ref _actionRegistered, 1) == 0)
|
||||
{
|
||||
_group.RegisterAction(this);
|
||||
|
||||
_actionRegistered = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -362,7 +362,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
return DepthStencilMode.Depth;
|
||||
}
|
||||
|
||||
if (format == Format.D24X8Unorm || format == Format.D24UnormS8Uint)
|
||||
if (format == Format.D24UnormS8Uint)
|
||||
{
|
||||
return component == SwizzleComponent.Red
|
||||
? DepthStencilMode.Stencil
|
||||
|
@ -17,6 +17,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
private const ulong BufferAlignmentSize = 0x1000;
|
||||
private const ulong BufferAlignmentMask = BufferAlignmentSize - 1;
|
||||
|
||||
private const ulong MaxDynamicGrowthSize = 0x100000;
|
||||
|
||||
private readonly GpuContext _context;
|
||||
private readonly PhysicalMemory _physicalMemory;
|
||||
|
||||
@ -166,10 +168,35 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
// Otherwise, we must delete the overlapping buffers and create a bigger buffer
|
||||
// that fits all the data we need. We also need to copy the contents from the
|
||||
// old buffer(s) to the new buffer.
|
||||
|
||||
ulong endAddress = address + size;
|
||||
|
||||
if (_bufferOverlaps[0].Address > address || _bufferOverlaps[0].EndAddress < endAddress)
|
||||
{
|
||||
// Check if the following conditions are met:
|
||||
// - We have a single overlap.
|
||||
// - The overlap starts at or before the requested range. That is, the overlap happens at the end.
|
||||
// - The size delta between the new, merged buffer and the old one is of at most 2 pages.
|
||||
// In this case, we attempt to extend the buffer further than the requested range,
|
||||
// this can potentially avoid future resizes if the application keeps using overlapping
|
||||
// sequential memory.
|
||||
// Allowing for 2 pages (rather than just one) is necessary to catch cases where the
|
||||
// range crosses a page, and after alignment, ends having a size of 2 pages.
|
||||
if (overlapsCount == 1 &&
|
||||
address >= _bufferOverlaps[0].Address &&
|
||||
endAddress - _bufferOverlaps[0].EndAddress <= BufferAlignmentSize * 2)
|
||||
{
|
||||
// Try to grow the buffer by 1.5x of its current size.
|
||||
// This improves performance in the cases where the buffer is resized often by small amounts.
|
||||
ulong existingSize = _bufferOverlaps[0].Size;
|
||||
ulong growthSize = (existingSize + Math.Min(existingSize >> 1, MaxDynamicGrowthSize)) & ~BufferAlignmentMask;
|
||||
|
||||
size = Math.Max(size, growthSize);
|
||||
endAddress = address + size;
|
||||
|
||||
overlapsCount = _buffers.FindOverlapsNonOverlapping(address, size, ref _bufferOverlaps);
|
||||
}
|
||||
|
||||
for (int index = 0; index < overlapsCount; index++)
|
||||
{
|
||||
Buffer buffer = _bufferOverlaps[index];
|
||||
@ -183,7 +210,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
}
|
||||
}
|
||||
|
||||
Buffer newBuffer = new Buffer(_context, _physicalMemory, address, endAddress - address, _bufferOverlaps.Take(overlapsCount));
|
||||
ulong newSize = endAddress - address;
|
||||
|
||||
Buffer newBuffer = new Buffer(_context, _physicalMemory, address, newSize, _bufferOverlaps.Take(overlapsCount));
|
||||
|
||||
lock (_buffers)
|
||||
{
|
||||
@ -202,7 +231,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
buffer.DisposeData();
|
||||
}
|
||||
|
||||
newBuffer.SynchronizeMemory(address, endAddress - address);
|
||||
newBuffer.SynchronizeMemory(address, newSize);
|
||||
|
||||
// Existing buffers were modified, we need to rebind everything.
|
||||
NotifyBuffersModified?.Invoke();
|
||||
|
@ -115,6 +115,73 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a read-only span of data from GPU mapped memory, up to the entire range specified,
|
||||
/// or the last mapped page if the range is not fully mapped.
|
||||
/// </summary>
|
||||
/// <param name="va">GPU virtual address where the data is located</param>
|
||||
/// <param name="size">Size of the data</param>
|
||||
/// <param name="tracked">True if read tracking is triggered on the span</param>
|
||||
/// <returns>The span of the data at the specified memory location</returns>
|
||||
public ReadOnlySpan<byte> GetSpanMapped(ulong va, int size, bool tracked = false)
|
||||
{
|
||||
bool isContiguous = true;
|
||||
int mappedSize;
|
||||
|
||||
if (ValidateAddress(va) && GetPte(va) != PteUnmapped && Physical.IsMapped(Translate(va)))
|
||||
{
|
||||
ulong endVa = va + (ulong)size;
|
||||
ulong endVaAligned = (endVa + PageMask) & ~PageMask;
|
||||
ulong currentVa = va & ~PageMask;
|
||||
|
||||
int pages = (int)((endVaAligned - currentVa) / PageSize);
|
||||
|
||||
for (int page = 0; page < pages - 1; page++)
|
||||
{
|
||||
ulong nextVa = currentVa + PageSize;
|
||||
ulong nextPa = Translate(nextVa);
|
||||
|
||||
if (!ValidateAddress(nextVa) || GetPte(nextVa) == PteUnmapped || !Physical.IsMapped(nextPa))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (Translate(currentVa) + PageSize != nextPa)
|
||||
{
|
||||
isContiguous = false;
|
||||
}
|
||||
|
||||
currentVa += PageSize;
|
||||
}
|
||||
|
||||
currentVa += PageSize;
|
||||
|
||||
if (currentVa > endVa)
|
||||
{
|
||||
currentVa = endVa;
|
||||
}
|
||||
|
||||
mappedSize = (int)(currentVa - va);
|
||||
}
|
||||
else
|
||||
{
|
||||
return ReadOnlySpan<byte>.Empty;
|
||||
}
|
||||
|
||||
if (isContiguous)
|
||||
{
|
||||
return Physical.GetSpan(Translate(va), mappedSize, tracked);
|
||||
}
|
||||
else
|
||||
{
|
||||
Span<byte> data = new byte[mappedSize];
|
||||
|
||||
ReadImpl(va, data, tracked);
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads data from a possibly non-contiguous region of GPU mapped memory.
|
||||
/// </summary>
|
||||
@ -255,6 +322,49 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes data to GPU mapped memory, stopping at the first unmapped page at the memory region, if any.
|
||||
/// </summary>
|
||||
/// <param name="va">GPU virtual address to write the data into</param>
|
||||
/// <param name="data">The data to be written</param>
|
||||
public void WriteMapped(ulong va, ReadOnlySpan<byte> data)
|
||||
{
|
||||
if (IsContiguous(va, data.Length))
|
||||
{
|
||||
Physical.Write(Translate(va), data);
|
||||
}
|
||||
else
|
||||
{
|
||||
int offset = 0, size;
|
||||
|
||||
if ((va & PageMask) != 0)
|
||||
{
|
||||
ulong pa = Translate(va);
|
||||
|
||||
size = Math.Min(data.Length, (int)PageSize - (int)(va & PageMask));
|
||||
|
||||
if (pa != PteUnmapped && Physical.IsMapped(pa))
|
||||
{
|
||||
Physical.Write(pa, data.Slice(0, size));
|
||||
}
|
||||
|
||||
offset += size;
|
||||
}
|
||||
|
||||
for (; offset < data.Length; offset += size)
|
||||
{
|
||||
ulong pa = Translate(va + (ulong)offset);
|
||||
|
||||
size = Math.Min(data.Length - offset, (int)PageSize);
|
||||
|
||||
if (pa != PteUnmapped && Physical.IsMapped(pa))
|
||||
{
|
||||
Physical.Write(pa, data.Slice(offset, size));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maps a given range of pages to the specified CPU virtual address.
|
||||
/// </summary>
|
||||
@ -264,7 +374,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// <param name="pa">CPU virtual address to map into</param>
|
||||
/// <param name="va">GPU virtual address to be mapped</param>
|
||||
/// <param name="size">Size in bytes of the mapping</param>
|
||||
public void Map(ulong pa, ulong va, ulong size)
|
||||
/// <param name="kind">Kind of the resource located at the mapping</param>
|
||||
public void Map(ulong pa, ulong va, ulong size, PteKind kind)
|
||||
{
|
||||
lock (_pageTable)
|
||||
{
|
||||
@ -272,7 +383,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
|
||||
for (ulong offset = 0; offset < size; offset += PageSize)
|
||||
{
|
||||
SetPte(va + offset, pa + offset);
|
||||
SetPte(va + offset, PackPte(pa + offset, kind));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -462,14 +573,37 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
return PteUnmapped;
|
||||
}
|
||||
|
||||
ulong baseAddress = GetPte(va);
|
||||
ulong pte = GetPte(va);
|
||||
|
||||
if (baseAddress == PteUnmapped)
|
||||
if (pte == PteUnmapped)
|
||||
{
|
||||
return PteUnmapped;
|
||||
}
|
||||
|
||||
return baseAddress + (va & PageMask);
|
||||
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.
|
||||
/// </summary>
|
||||
/// <param name="va">GPU virtual address</param>
|
||||
/// <returns>Kind of the memory page</returns>
|
||||
public PteKind GetKind(ulong va)
|
||||
{
|
||||
if (!ValidateAddress(va))
|
||||
{
|
||||
return PteKind.Invalid;
|
||||
}
|
||||
|
||||
ulong pte = GetPte(va);
|
||||
|
||||
if (pte == PteUnmapped)
|
||||
{
|
||||
return PteKind.Invalid;
|
||||
}
|
||||
|
||||
return UnpackKindFromPte(pte);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -512,5 +646,36 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
|
||||
_pageTable[l0][l1] = pte;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a page table entry from a physical address and kind.
|
||||
/// </summary>
|
||||
/// <param name="pa">Physical address</param>
|
||||
/// <param name="kind">Kind</param>
|
||||
/// <returns>Page table entry</returns>
|
||||
private static ulong PackPte(ulong pa, PteKind kind)
|
||||
{
|
||||
return pa | ((ulong)kind << 56);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unpacks kind from a page table entry.
|
||||
/// </summary>
|
||||
/// <param name="pte">Page table entry</param>
|
||||
/// <returns>Kind</returns>
|
||||
private static PteKind UnpackKindFromPte(ulong pte)
|
||||
{
|
||||
return (PteKind)(pte >> 56);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unpacks physical address from a page table entry.
|
||||
/// </summary>
|
||||
/// <param name="pte">Page table entry</param>
|
||||
/// <returns>Physical address</returns>
|
||||
private static ulong UnpackPaFromPte(ulong pte)
|
||||
{
|
||||
return pte & 0xffffffffffffffUL;
|
||||
}
|
||||
}
|
||||
}
|
@ -340,6 +340,16 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
return _cpuMemory.BeginSmartGranularTracking(address, size, granularity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a given memory page is mapped.
|
||||
/// </summary>
|
||||
/// <param name="address">CPU virtual address of the page</param>
|
||||
/// <returns>True if mapped, false otherwise</returns>
|
||||
public bool IsMapped(ulong address)
|
||||
{
|
||||
return _cpuMemory.IsMapped(address);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Release our reference to the CPU memory manager.
|
||||
/// </summary>
|
||||
|
268
Ryujinx.Graphics.Gpu/Memory/PteKind.cs
Normal file
268
Ryujinx.Graphics.Gpu/Memory/PteKind.cs
Normal file
@ -0,0 +1,268 @@
|
||||
namespace Ryujinx.Graphics.Gpu.Memory
|
||||
{
|
||||
/// <summary>
|
||||
/// Kind of the resource at the given memory mapping.
|
||||
/// </summary>
|
||||
public enum PteKind : byte
|
||||
{
|
||||
Invalid = 0xff,
|
||||
Pitch = 0x00,
|
||||
Z16 = 0x01,
|
||||
Z162C = 0x02,
|
||||
Z16MS22C = 0x03,
|
||||
Z16MS42C = 0x04,
|
||||
Z16MS82C = 0x05,
|
||||
Z16MS162C = 0x06,
|
||||
Z162Z = 0x07,
|
||||
Z16MS22Z = 0x08,
|
||||
Z16MS42Z = 0x09,
|
||||
Z16MS82Z = 0x0a,
|
||||
Z16MS162Z = 0x0b,
|
||||
Z162CZ = 0x36,
|
||||
Z16MS22CZ = 0x37,
|
||||
Z16MS42CZ = 0x38,
|
||||
Z16MS82CZ = 0x39,
|
||||
Z16MS162CZ = 0x5f,
|
||||
Z164CZ = 0x0c,
|
||||
Z16MS24CZ = 0x0d,
|
||||
Z16MS44CZ = 0x0e,
|
||||
Z16MS84CZ = 0x0f,
|
||||
Z16MS164CZ = 0x10,
|
||||
S8Z24 = 0x11,
|
||||
S8Z241Z = 0x12,
|
||||
S8Z24MS21Z = 0x13,
|
||||
S8Z24MS41Z = 0x14,
|
||||
S8Z24MS81Z = 0x15,
|
||||
S8Z24MS161Z = 0x16,
|
||||
S8Z242CZ = 0x17,
|
||||
S8Z24MS22CZ = 0x18,
|
||||
S8Z24MS42CZ = 0x19,
|
||||
S8Z24MS82CZ = 0x1a,
|
||||
S8Z24MS162CZ = 0x1b,
|
||||
S8Z242CS = 0x1c,
|
||||
S8Z24MS22CS = 0x1d,
|
||||
S8Z24MS42CS = 0x1e,
|
||||
S8Z24MS82CS = 0x1f,
|
||||
S8Z24MS162CS = 0x20,
|
||||
S8Z244CSZV = 0x21,
|
||||
S8Z24MS24CSZV = 0x22,
|
||||
S8Z24MS44CSZV = 0x23,
|
||||
S8Z24MS84CSZV = 0x24,
|
||||
S8Z24MS164CSZV = 0x25,
|
||||
V8Z24MS4VC12 = 0x26,
|
||||
V8Z24MS4VC4 = 0x27,
|
||||
V8Z24MS8VC8 = 0x28,
|
||||
V8Z24MS8VC24 = 0x29,
|
||||
V8Z24MS4VC121ZV = 0x2e,
|
||||
V8Z24MS4VC41ZV = 0x2f,
|
||||
V8Z24MS8VC81ZV = 0x30,
|
||||
V8Z24MS8VC241ZV = 0x31,
|
||||
V8Z24MS4VC122CS = 0x32,
|
||||
V8Z24MS4VC42CS = 0x33,
|
||||
V8Z24MS8VC82CS = 0x34,
|
||||
V8Z24MS8VC242CS = 0x35,
|
||||
V8Z24MS4VC122CZV = 0x3a,
|
||||
V8Z24MS4VC42CZV = 0x3b,
|
||||
V8Z24MS8VC82CZV = 0x3c,
|
||||
V8Z24MS8VC242CZV = 0x3d,
|
||||
V8Z24MS4VC122ZV = 0x3e,
|
||||
V8Z24MS4VC42ZV = 0x3f,
|
||||
V8Z24MS8VC82ZV = 0x40,
|
||||
V8Z24MS8VC242ZV = 0x41,
|
||||
V8Z24MS4VC124CSZV = 0x42,
|
||||
V8Z24MS4VC44CSZV = 0x43,
|
||||
V8Z24MS8VC84CSZV = 0x44,
|
||||
V8Z24MS8VC244CSZV = 0x45,
|
||||
Z24S8 = 0x46,
|
||||
Z24S81Z = 0x47,
|
||||
Z24S8MS21Z = 0x48,
|
||||
Z24S8MS41Z = 0x49,
|
||||
Z24S8MS81Z = 0x4a,
|
||||
Z24S8MS161Z = 0x4b,
|
||||
Z24S82CS = 0x4c,
|
||||
Z24S8MS22CS = 0x4d,
|
||||
Z24S8MS42CS = 0x4e,
|
||||
Z24S8MS82CS = 0x4f,
|
||||
Z24S8MS162CS = 0x50,
|
||||
Z24S82CZ = 0x51,
|
||||
Z24S8MS22CZ = 0x52,
|
||||
Z24S8MS42CZ = 0x53,
|
||||
Z24S8MS82CZ = 0x54,
|
||||
Z24S8MS162CZ = 0x55,
|
||||
Z24S84CSZV = 0x56,
|
||||
Z24S8MS24CSZV = 0x57,
|
||||
Z24S8MS44CSZV = 0x58,
|
||||
Z24S8MS84CSZV = 0x59,
|
||||
Z24S8MS164CSZV = 0x5a,
|
||||
Z24V8MS4VC12 = 0x5b,
|
||||
Z24V8MS4VC4 = 0x5c,
|
||||
Z24V8MS8VC8 = 0x5d,
|
||||
Z24V8MS8VC24 = 0x5e,
|
||||
YUVB8C12Y = 0x60,
|
||||
YUVB8C22Y = 0x61,
|
||||
YUVB10C12Y = 0x62,
|
||||
YUVB10C22Y = 0x6b,
|
||||
YUVB12C12Y = 0x6c,
|
||||
YUVB12C22Y = 0x6d,
|
||||
Z24V8MS4VC121ZV = 0x63,
|
||||
Z24V8MS4VC41ZV = 0x64,
|
||||
Z24V8MS8VC81ZV = 0x65,
|
||||
Z24V8MS8VC241ZV = 0x66,
|
||||
Z24V8MS4VC122CS = 0x67,
|
||||
Z24V8MS4VC42CS = 0x68,
|
||||
Z24V8MS8VC82CS = 0x69,
|
||||
Z24V8MS8VC242CS = 0x6a,
|
||||
Z24V8MS4VC122CZV = 0x6f,
|
||||
Z24V8MS4VC42CZV = 0x70,
|
||||
Z24V8MS8VC82CZV = 0x71,
|
||||
Z24V8MS8VC242CZV = 0x72,
|
||||
Z24V8MS4VC122ZV = 0x73,
|
||||
Z24V8MS4VC42ZV = 0x74,
|
||||
Z24V8MS8VC82ZV = 0x75,
|
||||
Z24V8MS8VC242ZV = 0x76,
|
||||
Z24V8MS4VC124CSZV = 0x77,
|
||||
Z24V8MS4VC44CSZV = 0x78,
|
||||
Z24V8MS8VC84CSZV = 0x79,
|
||||
Z24V8MS8VC244CSZV = 0x7a,
|
||||
ZF32 = 0x7b,
|
||||
ZF321Z = 0x7c,
|
||||
ZF32MS21Z = 0x7d,
|
||||
ZF32MS41Z = 0x7e,
|
||||
ZF32MS81Z = 0x7f,
|
||||
ZF32MS161Z = 0x80,
|
||||
ZF322CS = 0x81,
|
||||
ZF32MS22CS = 0x82,
|
||||
ZF32MS42CS = 0x83,
|
||||
ZF32MS82CS = 0x84,
|
||||
ZF32MS162CS = 0x85,
|
||||
ZF322CZ = 0x86,
|
||||
ZF32MS22CZ = 0x87,
|
||||
ZF32MS42CZ = 0x88,
|
||||
ZF32MS82CZ = 0x89,
|
||||
ZF32MS162CZ = 0x8a,
|
||||
X8Z24X16V8S8MS4VC12 = 0x8b,
|
||||
X8Z24X16V8S8MS4VC4 = 0x8c,
|
||||
X8Z24X16V8S8MS8VC8 = 0x8d,
|
||||
X8Z24X16V8S8MS8VC24 = 0x8e,
|
||||
X8Z24X16V8S8MS4VC121CS = 0x8f,
|
||||
X8Z24X16V8S8MS4VC41CS = 0x90,
|
||||
X8Z24X16V8S8MS8VC81CS = 0x91,
|
||||
X8Z24X16V8S8MS8VC241CS = 0x92,
|
||||
X8Z24X16V8S8MS4VC121ZV = 0x97,
|
||||
X8Z24X16V8S8MS4VC41ZV = 0x98,
|
||||
X8Z24X16V8S8MS8VC81ZV = 0x99,
|
||||
X8Z24X16V8S8MS8VC241ZV = 0x9a,
|
||||
X8Z24X16V8S8MS4VC121CZV = 0x9b,
|
||||
X8Z24X16V8S8MS4VC41CZV = 0x9c,
|
||||
X8Z24X16V8S8MS8VC81CZV = 0x9d,
|
||||
X8Z24X16V8S8MS8VC241CZV = 0x9e,
|
||||
X8Z24X16V8S8MS4VC122CS = 0x9f,
|
||||
X8Z24X16V8S8MS4VC42CS = 0xa0,
|
||||
X8Z24X16V8S8MS8VC82CS = 0xa1,
|
||||
X8Z24X16V8S8MS8VC242CS = 0xa2,
|
||||
X8Z24X16V8S8MS4VC122CSZV = 0xa3,
|
||||
X8Z24X16V8S8MS4VC42CSZV = 0xa4,
|
||||
X8Z24X16V8S8MS8VC82CSZV = 0xa5,
|
||||
X8Z24X16V8S8MS8VC242CSZV = 0xa6,
|
||||
ZF32X16V8S8MS4VC12 = 0xa7,
|
||||
ZF32X16V8S8MS4VC4 = 0xa8,
|
||||
ZF32X16V8S8MS8VC8 = 0xa9,
|
||||
ZF32X16V8S8MS8VC24 = 0xaa,
|
||||
ZF32X16V8S8MS4VC121CS = 0xab,
|
||||
ZF32X16V8S8MS4VC41CS = 0xac,
|
||||
ZF32X16V8S8MS8VC81CS = 0xad,
|
||||
ZF32X16V8S8MS8VC241CS = 0xae,
|
||||
ZF32X16V8S8MS4VC121ZV = 0xb3,
|
||||
ZF32X16V8S8MS4VC41ZV = 0xb4,
|
||||
ZF32X16V8S8MS8VC81ZV = 0xb5,
|
||||
ZF32X16V8S8MS8VC241ZV = 0xb6,
|
||||
ZF32X16V8S8MS4VC121CZV = 0xb7,
|
||||
ZF32X16V8S8MS4VC41CZV = 0xb8,
|
||||
ZF32X16V8S8MS8VC81CZV = 0xb9,
|
||||
ZF32X16V8S8MS8VC241CZV = 0xba,
|
||||
ZF32X16V8S8MS4VC122CS = 0xbb,
|
||||
ZF32X16V8S8MS4VC42CS = 0xbc,
|
||||
ZF32X16V8S8MS8VC82CS = 0xbd,
|
||||
ZF32X16V8S8MS8VC242CS = 0xbe,
|
||||
ZF32X16V8S8MS4VC122CSZV = 0xbf,
|
||||
ZF32X16V8S8MS4VC42CSZV = 0xc0,
|
||||
ZF32X16V8S8MS8VC82CSZV = 0xc1,
|
||||
ZF32X16V8S8MS8VC242CSZV = 0xc2,
|
||||
ZF32X24S8 = 0xc3,
|
||||
ZF32X24S81CS = 0xc4,
|
||||
ZF32X24S8MS21CS = 0xc5,
|
||||
ZF32X24S8MS41CS = 0xc6,
|
||||
ZF32X24S8MS81CS = 0xc7,
|
||||
ZF32X24S8MS161CS = 0xc8,
|
||||
ZF32X24S82CSZV = 0xce,
|
||||
ZF32X24S8MS22CSZV = 0xcf,
|
||||
ZF32X24S8MS42CSZV = 0xd0,
|
||||
ZF32X24S8MS82CSZV = 0xd1,
|
||||
ZF32X24S8MS162CSZV = 0xd2,
|
||||
ZF32X24S82CS = 0xd3,
|
||||
ZF32X24S8MS22CS = 0xd4,
|
||||
ZF32X24S8MS42CS = 0xd5,
|
||||
ZF32X24S8MS82CS = 0xd6,
|
||||
ZF32X24S8MS162CS = 0xd7,
|
||||
S8 = 0x2a,
|
||||
S82S = 0x2b,
|
||||
Generic16Bx2 = 0xfe,
|
||||
C322C = 0xd8,
|
||||
C322CBR = 0xd9,
|
||||
C322CBA = 0xda,
|
||||
C322CRA = 0xdb,
|
||||
C322BRA = 0xdc,
|
||||
C32MS22C = 0xdd,
|
||||
C32MS22CBR = 0xde,
|
||||
C32MS24CBRA = 0xcc,
|
||||
C32MS42C = 0xdf,
|
||||
C32MS42CBR = 0xe0,
|
||||
C32MS42CBA = 0xe1,
|
||||
C32MS42CRA = 0xe2,
|
||||
C32MS42BRA = 0xe3,
|
||||
C32MS44CBRA = 0x2c,
|
||||
C32MS8MS162C = 0xe4,
|
||||
C32MS8MS162CRA = 0xe5,
|
||||
C642C = 0xe6,
|
||||
C642CBR = 0xe7,
|
||||
C642CBA = 0xe8,
|
||||
C642CRA = 0xe9,
|
||||
C642BRA = 0xea,
|
||||
C64MS22C = 0xeb,
|
||||
C64MS22CBR = 0xec,
|
||||
C64MS24CBRA = 0xcd,
|
||||
C64MS42C = 0xed,
|
||||
C64MS42CBR = 0xee,
|
||||
C64MS42CBA = 0xef,
|
||||
C64MS42CRA = 0xf0,
|
||||
C64MS42BRA = 0xf1,
|
||||
C64MS44CBRA = 0x2d,
|
||||
C64MS8MS162C = 0xf2,
|
||||
C64MS8MS162CRA = 0xf3,
|
||||
C1282C = 0xf4,
|
||||
C1282CR = 0xf5,
|
||||
C128MS22C = 0xf6,
|
||||
C128MS22CR = 0xf7,
|
||||
C128MS42C = 0xf8,
|
||||
C128MS42CR = 0xf9,
|
||||
C128MS8MS162C = 0xfa,
|
||||
C128MS8MS162CR = 0xfb,
|
||||
X8C24 = 0xfc,
|
||||
PitchNoSwizzle = 0xfd,
|
||||
SmSkedMessage = 0xca,
|
||||
SmHostMessage = 0xcb
|
||||
}
|
||||
|
||||
static class PteKindExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Checks if the kind is pitch.
|
||||
/// </summary>
|
||||
/// <param name="kind">Kind to check</param>
|
||||
/// <returns>True if pitch, false otherwise</returns>
|
||||
public static bool IsPitch(this PteKind kind)
|
||||
{
|
||||
return kind == PteKind.Pitch || kind == PteKind.PitchNoSwizzle;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
namespace Ryujinx.Graphics.Gpu.Memory
|
||||
{
|
||||
/// <summary>
|
||||
/// Name of a GPU resource.
|
||||
/// </summary>
|
||||
public enum ResourceName
|
||||
{
|
||||
Buffer,
|
||||
Texture,
|
||||
TexturePool,
|
||||
SamplerPool
|
||||
}
|
||||
}
|
@ -2,11 +2,8 @@
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Gpu.Memory;
|
||||
using Ryujinx.Graphics.Gpu.Shader.Cache.Definition;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
using Ryujinx.Graphics.Shader.Translation;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
@ -20,70 +17,6 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
||||
/// </summary>
|
||||
static class CacheHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Try to read the manifest header from a given file path.
|
||||
/// </summary>
|
||||
/// <param name="manifestPath">The path to the manifest file</param>
|
||||
/// <param name="header">The manifest header read</param>
|
||||
/// <returns>Return true if the manifest header was read</returns>
|
||||
public static bool TryReadManifestHeader(string manifestPath, out CacheManifestHeader header)
|
||||
{
|
||||
header = default;
|
||||
|
||||
if (File.Exists(manifestPath))
|
||||
{
|
||||
Memory<byte> rawManifest = File.ReadAllBytes(manifestPath);
|
||||
|
||||
if (MemoryMarshal.TryRead(rawManifest.Span, out header))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to read the manifest from a given file path.
|
||||
/// </summary>
|
||||
/// <param name="manifestPath">The path to the manifest file</param>
|
||||
/// <param name="graphicsApi">The graphics api used by the cache</param>
|
||||
/// <param name="hashType">The hash type of the cache</param>
|
||||
/// <param name="header">The manifest header read</param>
|
||||
/// <param name="entries">The entries read from the cache manifest</param>
|
||||
/// <returns>Return true if the manifest was read</returns>
|
||||
public static bool TryReadManifestFile(string manifestPath, CacheGraphicsApi graphicsApi, CacheHashType hashType, out CacheManifestHeader header, out HashSet<Hash128> entries)
|
||||
{
|
||||
header = default;
|
||||
entries = new HashSet<Hash128>();
|
||||
|
||||
if (File.Exists(manifestPath))
|
||||
{
|
||||
Memory<byte> rawManifest = File.ReadAllBytes(manifestPath);
|
||||
|
||||
if (MemoryMarshal.TryRead(rawManifest.Span, out header))
|
||||
{
|
||||
Memory<byte> hashTableRaw = rawManifest.Slice(Unsafe.SizeOf<CacheManifestHeader>());
|
||||
|
||||
bool isValid = header.IsValid(graphicsApi, hashType, hashTableRaw.Span);
|
||||
|
||||
if (isValid)
|
||||
{
|
||||
ReadOnlySpan<Hash128> hashTable = MemoryMarshal.Cast<byte, Hash128>(hashTableRaw.Span);
|
||||
|
||||
foreach (Hash128 hash in hashTable)
|
||||
{
|
||||
entries.Add(hash);
|
||||
}
|
||||
}
|
||||
|
||||
return isValid;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compute a cache manifest from runtime data.
|
||||
/// </summary>
|
||||
@ -246,82 +179,23 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compute the guest program code for usage while dumping to disk or hash.
|
||||
/// </summary>
|
||||
/// <param name="cachedShaderEntries">The guest shader entries to use</param>
|
||||
/// <param name="tfd">The transform feedback descriptors</param>
|
||||
/// <param name="forHashCompute">Used to determine if the guest program code is generated for hashing</param>
|
||||
/// <returns>The guest program code for usage while dumping to disk or hash</returns>
|
||||
private static byte[] ComputeGuestProgramCode(ReadOnlySpan<GuestShaderCacheEntry> cachedShaderEntries, TransformFeedbackDescriptor[] tfd, bool forHashCompute = false)
|
||||
{
|
||||
using (MemoryStream stream = new MemoryStream())
|
||||
{
|
||||
BinaryWriter writer = new BinaryWriter(stream);
|
||||
|
||||
foreach (GuestShaderCacheEntry cachedShaderEntry in cachedShaderEntries)
|
||||
{
|
||||
if (cachedShaderEntry != null)
|
||||
{
|
||||
// Code (and Code A if present)
|
||||
stream.Write(cachedShaderEntry.Code);
|
||||
|
||||
if (forHashCompute)
|
||||
{
|
||||
// Guest GPU accessor header (only write this for hashes, already present in the header for dumps)
|
||||
writer.WriteStruct(cachedShaderEntry.Header.GpuAccessorHeader);
|
||||
}
|
||||
|
||||
// Texture descriptors
|
||||
foreach (GuestTextureDescriptor textureDescriptor in cachedShaderEntry.TextureDescriptors.Values)
|
||||
{
|
||||
writer.WriteStruct(textureDescriptor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Transform feedback
|
||||
if (tfd != null)
|
||||
{
|
||||
foreach (TransformFeedbackDescriptor transform in tfd)
|
||||
{
|
||||
writer.WriteStruct(new GuestShaderCacheTransformFeedbackHeader(transform.BufferIndex, transform.Stride, transform.VaryingLocations.Length));
|
||||
writer.Write(transform.VaryingLocations);
|
||||
}
|
||||
}
|
||||
|
||||
return stream.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compute a guest hash from shader entries.
|
||||
/// </summary>
|
||||
/// <param name="cachedShaderEntries">The guest shader entries to use</param>
|
||||
/// <param name="tfd">The optional transform feedback descriptors</param>
|
||||
/// <returns>A guest hash from shader entries</returns>
|
||||
public static Hash128 ComputeGuestHashFromCache(ReadOnlySpan<GuestShaderCacheEntry> cachedShaderEntries, TransformFeedbackDescriptor[] tfd = null)
|
||||
{
|
||||
return XXHash128.ComputeHash(ComputeGuestProgramCode(cachedShaderEntries, tfd, true));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read transform feedback descriptors from guest.
|
||||
/// </summary>
|
||||
/// <param name="data">The raw guest transform feedback descriptors</param>
|
||||
/// <param name="header">The guest shader program header</param>
|
||||
/// <returns>The transform feedback descriptors read from guest</returns>
|
||||
public static TransformFeedbackDescriptor[] ReadTransformFeedbackInformation(ref ReadOnlySpan<byte> data, GuestShaderCacheHeader header)
|
||||
public static TransformFeedbackDescriptorOld[] ReadTransformFeedbackInformation(ref ReadOnlySpan<byte> data, GuestShaderCacheHeader header)
|
||||
{
|
||||
if (header.TransformFeedbackCount != 0)
|
||||
{
|
||||
TransformFeedbackDescriptor[] result = new TransformFeedbackDescriptor[header.TransformFeedbackCount];
|
||||
TransformFeedbackDescriptorOld[] result = new TransformFeedbackDescriptorOld[header.TransformFeedbackCount];
|
||||
|
||||
for (int i = 0; i < result.Length; i++)
|
||||
{
|
||||
GuestShaderCacheTransformFeedbackHeader feedbackHeader = MemoryMarshal.Read<GuestShaderCacheTransformFeedbackHeader>(data);
|
||||
|
||||
result[i] = new TransformFeedbackDescriptor(feedbackHeader.BufferIndex, feedbackHeader.Stride, data.Slice(Unsafe.SizeOf<GuestShaderCacheTransformFeedbackHeader>(), feedbackHeader.VaryingLocationsLength).ToArray());
|
||||
result[i] = new TransformFeedbackDescriptorOld(feedbackHeader.BufferIndex, feedbackHeader.Stride, data.Slice(Unsafe.SizeOf<GuestShaderCacheTransformFeedbackHeader>(), feedbackHeader.VaryingLocationsLength).ToArray());
|
||||
|
||||
data = data.Slice(Unsafe.SizeOf<GuestShaderCacheTransformFeedbackHeader>() + feedbackHeader.VaryingLocationsLength);
|
||||
}
|
||||
@ -332,205 +206,6 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds gpu state flags using information from the given gpu accessor.
|
||||
/// </summary>
|
||||
/// <param name="gpuAccessor">The gpu accessor</param>
|
||||
/// <returns>The gpu state flags</returns>
|
||||
private static GuestGpuStateFlags GetGpuStateFlags(IGpuAccessor gpuAccessor)
|
||||
{
|
||||
GuestGpuStateFlags flags = 0;
|
||||
|
||||
if (gpuAccessor.QueryEarlyZForce())
|
||||
{
|
||||
flags |= GuestGpuStateFlags.EarlyZForce;
|
||||
}
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Packs the tessellation parameters from the gpu accessor.
|
||||
/// </summary>
|
||||
/// <param name="gpuAccessor">The gpu accessor</param>
|
||||
/// <returns>The packed tessellation parameters</returns>
|
||||
private static byte GetTessellationModePacked(IGpuAccessor gpuAccessor)
|
||||
{
|
||||
byte value;
|
||||
|
||||
value = (byte)((int)gpuAccessor.QueryTessPatchType() & 3);
|
||||
value |= (byte)(((int)gpuAccessor.QueryTessSpacing() & 3) << 2);
|
||||
|
||||
if (gpuAccessor.QueryTessCw())
|
||||
{
|
||||
value |= 0x10;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new instance of <see cref="GuestGpuAccessorHeader"/> from an gpu accessor.
|
||||
/// </summary>
|
||||
/// <param name="gpuAccessor">The gpu accessor</param>
|
||||
/// <returns>A new instance of <see cref="GuestGpuAccessorHeader"/></returns>
|
||||
public static GuestGpuAccessorHeader CreateGuestGpuAccessorCache(IGpuAccessor gpuAccessor)
|
||||
{
|
||||
return new GuestGpuAccessorHeader
|
||||
{
|
||||
ComputeLocalSizeX = gpuAccessor.QueryComputeLocalSizeX(),
|
||||
ComputeLocalSizeY = gpuAccessor.QueryComputeLocalSizeY(),
|
||||
ComputeLocalSizeZ = gpuAccessor.QueryComputeLocalSizeZ(),
|
||||
ComputeLocalMemorySize = gpuAccessor.QueryComputeLocalMemorySize(),
|
||||
ComputeSharedMemorySize = gpuAccessor.QueryComputeSharedMemorySize(),
|
||||
PrimitiveTopology = gpuAccessor.QueryPrimitiveTopology(),
|
||||
TessellationModePacked = GetTessellationModePacked(gpuAccessor),
|
||||
StateFlags = GetGpuStateFlags(gpuAccessor)
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create guest shader cache entries from the runtime contexts.
|
||||
/// </summary>
|
||||
/// <param name="channel">The GPU channel in use</param>
|
||||
/// <param name="shaderContexts">The runtime contexts</param>
|
||||
/// <returns>Guest shader cahe entries from the runtime contexts</returns>
|
||||
public static GuestShaderCacheEntry[] CreateShaderCacheEntries(GpuChannel channel, ReadOnlySpan<TranslatorContext> shaderContexts)
|
||||
{
|
||||
MemoryManager memoryManager = channel.MemoryManager;
|
||||
|
||||
int startIndex = shaderContexts.Length > 1 ? 1 : 0;
|
||||
|
||||
GuestShaderCacheEntry[] entries = new GuestShaderCacheEntry[shaderContexts.Length - startIndex];
|
||||
|
||||
for (int i = startIndex; i < shaderContexts.Length; i++)
|
||||
{
|
||||
TranslatorContext context = shaderContexts[i];
|
||||
|
||||
if (context == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
GpuAccessor gpuAccessor = context.GpuAccessor as GpuAccessor;
|
||||
|
||||
ulong cb1DataAddress;
|
||||
int cb1DataSize = gpuAccessor?.Cb1DataSize ?? 0;
|
||||
|
||||
if (context.Stage == ShaderStage.Compute)
|
||||
{
|
||||
cb1DataAddress = channel.BufferManager.GetComputeUniformBufferAddress(1);
|
||||
}
|
||||
else
|
||||
{
|
||||
int stageIndex = context.Stage switch
|
||||
{
|
||||
ShaderStage.TessellationControl => 1,
|
||||
ShaderStage.TessellationEvaluation => 2,
|
||||
ShaderStage.Geometry => 3,
|
||||
ShaderStage.Fragment => 4,
|
||||
_ => 0
|
||||
};
|
||||
|
||||
cb1DataAddress = channel.BufferManager.GetGraphicsUniformBufferAddress(stageIndex, 1);
|
||||
}
|
||||
|
||||
int size = context.Size;
|
||||
|
||||
TranslatorContext translatorContext2 = i == 1 ? shaderContexts[0] : null;
|
||||
|
||||
int sizeA = translatorContext2 != null ? translatorContext2.Size : 0;
|
||||
|
||||
byte[] code = new byte[size + cb1DataSize + sizeA];
|
||||
|
||||
memoryManager.GetSpan(context.Address, size).CopyTo(code);
|
||||
|
||||
if (cb1DataAddress != 0 && cb1DataSize != 0)
|
||||
{
|
||||
memoryManager.Physical.GetSpan(cb1DataAddress, cb1DataSize).CopyTo(code.AsSpan(size, cb1DataSize));
|
||||
}
|
||||
|
||||
if (translatorContext2 != null)
|
||||
{
|
||||
memoryManager.GetSpan(translatorContext2.Address, sizeA).CopyTo(code.AsSpan(size + cb1DataSize, sizeA));
|
||||
}
|
||||
|
||||
GuestGpuAccessorHeader gpuAccessorHeader = CreateGuestGpuAccessorCache(context.GpuAccessor);
|
||||
|
||||
if (gpuAccessor != null)
|
||||
{
|
||||
gpuAccessorHeader.TextureDescriptorCount = context.TextureHandlesForCache.Count;
|
||||
}
|
||||
|
||||
GuestShaderCacheEntryHeader header = new GuestShaderCacheEntryHeader(
|
||||
context.Stage,
|
||||
size + cb1DataSize,
|
||||
sizeA,
|
||||
cb1DataSize,
|
||||
gpuAccessorHeader);
|
||||
|
||||
GuestShaderCacheEntry entry = new GuestShaderCacheEntry(header, code);
|
||||
|
||||
if (gpuAccessor != null)
|
||||
{
|
||||
foreach (int textureHandle in context.TextureHandlesForCache)
|
||||
{
|
||||
GuestTextureDescriptor textureDescriptor = ((Image.TextureDescriptor)gpuAccessor.GetTextureDescriptor(textureHandle, -1)).ToCache();
|
||||
|
||||
textureDescriptor.Handle = (uint)textureHandle;
|
||||
|
||||
entry.TextureDescriptors.Add(textureHandle, textureDescriptor);
|
||||
}
|
||||
}
|
||||
|
||||
entries[i - startIndex] = entry;
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a guest shader program.
|
||||
/// </summary>
|
||||
/// <param name="shaderCacheEntries">The entries composing the guest program dump</param>
|
||||
/// <param name="tfd">The transform feedback descriptors in use</param>
|
||||
/// <returns>The resulting guest shader program</returns>
|
||||
public static byte[] CreateGuestProgramDump(GuestShaderCacheEntry[] shaderCacheEntries, TransformFeedbackDescriptor[] tfd = null)
|
||||
{
|
||||
using (MemoryStream resultStream = new MemoryStream())
|
||||
{
|
||||
BinaryWriter resultStreamWriter = new BinaryWriter(resultStream);
|
||||
|
||||
byte transformFeedbackCount = 0;
|
||||
|
||||
if (tfd != null)
|
||||
{
|
||||
transformFeedbackCount = (byte)tfd.Length;
|
||||
}
|
||||
|
||||
// Header
|
||||
resultStreamWriter.WriteStruct(new GuestShaderCacheHeader((byte)shaderCacheEntries.Length, transformFeedbackCount));
|
||||
|
||||
// Write all entries header
|
||||
foreach (GuestShaderCacheEntry entry in shaderCacheEntries)
|
||||
{
|
||||
if (entry == null)
|
||||
{
|
||||
resultStreamWriter.WriteStruct(new GuestShaderCacheEntryHeader());
|
||||
}
|
||||
else
|
||||
{
|
||||
resultStreamWriter.WriteStruct(entry.Header);
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, write all program code and all transform feedback information.
|
||||
resultStreamWriter.Write(ComputeGuestProgramCode(shaderCacheEntries, tfd));
|
||||
|
||||
return resultStream.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save temporary files not in archive.
|
||||
/// </summary>
|
||||
|
@ -47,8 +47,6 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
||||
|
||||
string baseCacheDirectory = CacheHelper.GetBaseCacheDirectory(titleId);
|
||||
|
||||
CacheMigration.Run(baseCacheDirectory, graphicsApi, hashType, shaderProvider);
|
||||
|
||||
_guestProgramCache = new CacheCollection(baseCacheDirectory, _hashType, CacheGraphicsApi.Guest, "", "program", GuestCacheVersion);
|
||||
_hostProgramCache = new CacheCollection(baseCacheDirectory, _hashType, _graphicsApi, _shaderProvider, "host", shaderCodeGenVersion);
|
||||
}
|
||||
|
@ -1,175 +0,0 @@
|
||||
using ICSharpCode.SharpZipLib.Zip;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Gpu.Shader.Cache.Definition;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
||||
{
|
||||
/// <summary>
|
||||
/// Class handling shader cache migrations.
|
||||
/// </summary>
|
||||
static class CacheMigration
|
||||
{
|
||||
/// <summary>
|
||||
/// Check if the given cache version need to recompute its hash.
|
||||
/// </summary>
|
||||
/// <param name="version">The version in use</param>
|
||||
/// <param name="newVersion">The new version after migration</param>
|
||||
/// <returns>True if a hash recompute is needed</returns>
|
||||
public static bool NeedHashRecompute(ulong version, out ulong newVersion)
|
||||
{
|
||||
const ulong TargetBrokenVersion = 1717;
|
||||
const ulong TargetFixedVersion = 1759;
|
||||
|
||||
newVersion = TargetFixedVersion;
|
||||
|
||||
if (version == TargetBrokenVersion)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private class StreamZipEntryDataSource : IStaticDataSource
|
||||
{
|
||||
private readonly ZipFile Archive;
|
||||
private readonly ZipEntry Entry;
|
||||
public StreamZipEntryDataSource(ZipFile archive, ZipEntry entry)
|
||||
{
|
||||
Archive = archive;
|
||||
Entry = entry;
|
||||
}
|
||||
|
||||
public Stream GetSource()
|
||||
{
|
||||
return Archive.GetInputStream(Entry);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Move a file with the name of a given hash to another in the cache archive.
|
||||
/// </summary>
|
||||
/// <param name="archive">The archive in use</param>
|
||||
/// <param name="oldKey">The old key</param>
|
||||
/// <param name="newKey">The new key</param>
|
||||
private static void MoveEntry(ZipFile archive, Hash128 oldKey, Hash128 newKey)
|
||||
{
|
||||
ZipEntry oldGuestEntry = archive.GetEntry($"{oldKey}");
|
||||
|
||||
if (oldGuestEntry != null)
|
||||
{
|
||||
archive.Add(new StreamZipEntryDataSource(archive, oldGuestEntry), $"{newKey}", CompressionMethod.Deflated);
|
||||
archive.Delete(oldGuestEntry);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recompute all the hashes of a given cache.
|
||||
/// </summary>
|
||||
/// <param name="guestBaseCacheDirectory">The guest cache directory path</param>
|
||||
/// <param name="hostBaseCacheDirectory">The host cache directory path</param>
|
||||
/// <param name="graphicsApi">The graphics api in use</param>
|
||||
/// <param name="hashType">The hash type in use</param>
|
||||
/// <param name="newVersion">The version to write in the host and guest manifest after migration</param>
|
||||
private static void RecomputeHashes(string guestBaseCacheDirectory, string hostBaseCacheDirectory, CacheGraphicsApi graphicsApi, CacheHashType hashType, ulong newVersion)
|
||||
{
|
||||
string guestManifestPath = CacheHelper.GetManifestPath(guestBaseCacheDirectory);
|
||||
string hostManifestPath = CacheHelper.GetManifestPath(hostBaseCacheDirectory);
|
||||
|
||||
if (CacheHelper.TryReadManifestFile(guestManifestPath, CacheGraphicsApi.Guest, hashType, out _, out HashSet<Hash128> guestEntries))
|
||||
{
|
||||
CacheHelper.TryReadManifestFile(hostManifestPath, graphicsApi, hashType, out _, out HashSet<Hash128> hostEntries);
|
||||
|
||||
Logger.Info?.Print(LogClass.Gpu, "Shader cache hashes need to be recomputed, performing migration...");
|
||||
|
||||
string guestArchivePath = CacheHelper.GetArchivePath(guestBaseCacheDirectory);
|
||||
string hostArchivePath = CacheHelper.GetArchivePath(hostBaseCacheDirectory);
|
||||
|
||||
ZipFile guestArchive = new ZipFile(File.Open(guestArchivePath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None));
|
||||
ZipFile hostArchive = new ZipFile(File.Open(hostArchivePath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None));
|
||||
|
||||
CacheHelper.EnsureArchiveUpToDate(guestBaseCacheDirectory, guestArchive, guestEntries);
|
||||
CacheHelper.EnsureArchiveUpToDate(hostBaseCacheDirectory, hostArchive, hostEntries);
|
||||
|
||||
int programIndex = 0;
|
||||
|
||||
HashSet<Hash128> newEntries = new HashSet<Hash128>();
|
||||
|
||||
foreach (Hash128 oldHash in guestEntries)
|
||||
{
|
||||
byte[] guestProgram = CacheHelper.ReadFromArchive(guestArchive, oldHash);
|
||||
|
||||
Logger.Info?.Print(LogClass.Gpu, $"Migrating shader {oldHash} ({programIndex + 1} / {guestEntries.Count})");
|
||||
|
||||
if (guestProgram != null)
|
||||
{
|
||||
ReadOnlySpan<byte> guestProgramReadOnlySpan = guestProgram;
|
||||
|
||||
ReadOnlySpan<GuestShaderCacheEntry> cachedShaderEntries = GuestShaderCacheEntry.Parse(ref guestProgramReadOnlySpan, out GuestShaderCacheHeader fileHeader);
|
||||
|
||||
TransformFeedbackDescriptor[] tfd = CacheHelper.ReadTransformFeedbackInformation(ref guestProgramReadOnlySpan, fileHeader);
|
||||
|
||||
Hash128 newHash = CacheHelper.ComputeGuestHashFromCache(cachedShaderEntries, tfd);
|
||||
|
||||
if (newHash != oldHash)
|
||||
{
|
||||
MoveEntry(guestArchive, oldHash, newHash);
|
||||
MoveEntry(hostArchive, oldHash, newHash);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Gpu, $"Same hashes for shader {oldHash}");
|
||||
}
|
||||
|
||||
newEntries.Add(newHash);
|
||||
}
|
||||
|
||||
programIndex++;
|
||||
}
|
||||
|
||||
byte[] newGuestManifestContent = CacheHelper.ComputeManifest(newVersion, CacheGraphicsApi.Guest, hashType, newEntries);
|
||||
byte[] newHostManifestContent = CacheHelper.ComputeManifest(newVersion, graphicsApi, hashType, newEntries);
|
||||
|
||||
File.WriteAllBytes(guestManifestPath, newGuestManifestContent);
|
||||
File.WriteAllBytes(hostManifestPath, newHostManifestContent);
|
||||
|
||||
guestArchive.CommitUpdate();
|
||||
hostArchive.CommitUpdate();
|
||||
|
||||
guestArchive.Close();
|
||||
hostArchive.Close();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check and run cache migration if needed.
|
||||
/// </summary>
|
||||
/// <param name="baseCacheDirectory">The base path of the cache</param>
|
||||
/// <param name="graphicsApi">The graphics api in use</param>
|
||||
/// <param name="hashType">The hash type in use</param>
|
||||
/// <param name="shaderProvider">The shader provider name of the cache</param>
|
||||
public static void Run(string baseCacheDirectory, CacheGraphicsApi graphicsApi, CacheHashType hashType, string shaderProvider)
|
||||
{
|
||||
string guestBaseCacheDirectory = CacheHelper.GenerateCachePath(baseCacheDirectory, CacheGraphicsApi.Guest, "", "program");
|
||||
string hostBaseCacheDirectory = CacheHelper.GenerateCachePath(baseCacheDirectory, graphicsApi, shaderProvider, "host");
|
||||
|
||||
string guestArchivePath = CacheHelper.GetArchivePath(guestBaseCacheDirectory);
|
||||
string hostArchivePath = CacheHelper.GetArchivePath(hostBaseCacheDirectory);
|
||||
|
||||
bool isReadOnly = CacheHelper.IsArchiveReadOnly(guestArchivePath) || CacheHelper.IsArchiveReadOnly(hostArchivePath);
|
||||
|
||||
if (!isReadOnly && CacheHelper.TryReadManifestHeader(CacheHelper.GetManifestPath(guestBaseCacheDirectory), out CacheManifestHeader header))
|
||||
{
|
||||
if (NeedHashRecompute(header.Version, out ulong newVersion))
|
||||
{
|
||||
RecomputeHashes(guestBaseCacheDirectory, hostBaseCacheDirectory, graphicsApi, hashType, newVersion);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -96,6 +96,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache.Definition
|
||||
SBuffers,
|
||||
Textures,
|
||||
Images,
|
||||
default,
|
||||
Header.UseFlags.HasFlag(UseFlags.InstanceId),
|
||||
Header.UseFlags.HasFlag(UseFlags.RtLayer),
|
||||
Header.ClipDistancesWritten,
|
||||
@ -160,7 +161,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache.Definition
|
||||
/// <param name="programCode">The host shader program</param>
|
||||
/// <param name="codeHolders">The shaders code holder</param>
|
||||
/// <returns>Raw data of a new host shader cache file</returns>
|
||||
internal static byte[] Create(ReadOnlySpan<byte> programCode, ShaderCodeHolder[] codeHolders)
|
||||
internal static byte[] Create(ReadOnlySpan<byte> programCode, CachedShaderStage[] codeHolders)
|
||||
{
|
||||
HostShaderCacheHeader header = new HostShaderCacheHeader((byte)codeHolders.Length, programCode.Length);
|
||||
|
||||
|
255
Ryujinx.Graphics.Gpu/Shader/Cache/Migration.cs
Normal file
255
Ryujinx.Graphics.Gpu/Shader/Cache/Migration.cs
Normal file
@ -0,0 +1,255 @@
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Gpu.Engine.Threed;
|
||||
using Ryujinx.Graphics.Gpu.Shader.Cache.Definition;
|
||||
using Ryujinx.Graphics.Gpu.Shader.DiskCache;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
||||
{
|
||||
/// <summary>
|
||||
/// Class handling shader cache migrations.
|
||||
/// </summary>
|
||||
static class Migration
|
||||
{
|
||||
// Last codegen version before the migration to the new cache.
|
||||
private const ulong ShaderCodeGenVersion = 3054;
|
||||
|
||||
/// <summary>
|
||||
/// Migrates from the old cache format to the new one.
|
||||
/// </summary>
|
||||
/// <param name="context">GPU context</param>
|
||||
/// <param name="hostStorage">Disk cache host storage (used to create the new shader files)</param>
|
||||
/// <returns>Number of migrated shaders</returns>
|
||||
public static int MigrateFromLegacyCache(GpuContext context, DiskCacheHostStorage hostStorage)
|
||||
{
|
||||
string baseCacheDirectory = CacheHelper.GetBaseCacheDirectory(GraphicsConfig.TitleId);
|
||||
string cacheDirectory = CacheHelper.GenerateCachePath(baseCacheDirectory, CacheGraphicsApi.Guest, "", "program");
|
||||
|
||||
// If the directory does not exist, we have no old cache.
|
||||
// Exist early as the CacheManager constructor will create the directories.
|
||||
if (!Directory.Exists(cacheDirectory))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (GraphicsConfig.EnableShaderCache && GraphicsConfig.TitleId != null)
|
||||
{
|
||||
CacheManager cacheManager = new CacheManager(CacheGraphicsApi.OpenGL, CacheHashType.XxHash128, "glsl", GraphicsConfig.TitleId, ShaderCodeGenVersion);
|
||||
|
||||
bool isReadOnly = cacheManager.IsReadOnly;
|
||||
|
||||
HashSet<Hash128> invalidEntries = null;
|
||||
|
||||
if (isReadOnly)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Gpu, "Loading shader cache in read-only mode (cache in use by another program!)");
|
||||
}
|
||||
else
|
||||
{
|
||||
invalidEntries = new HashSet<Hash128>();
|
||||
}
|
||||
|
||||
ReadOnlySpan<Hash128> guestProgramList = cacheManager.GetGuestProgramList();
|
||||
|
||||
for (int programIndex = 0; programIndex < guestProgramList.Length; programIndex++)
|
||||
{
|
||||
Hash128 key = guestProgramList[programIndex];
|
||||
|
||||
byte[] guestProgram = cacheManager.GetGuestProgramByHash(ref key);
|
||||
|
||||
if (guestProgram == null)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Gpu, $"Ignoring orphan shader hash {key} in cache (is the cache incomplete?)");
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
ReadOnlySpan<byte> guestProgramReadOnlySpan = guestProgram;
|
||||
|
||||
ReadOnlySpan<GuestShaderCacheEntry> cachedShaderEntries = GuestShaderCacheEntry.Parse(ref guestProgramReadOnlySpan, out GuestShaderCacheHeader fileHeader);
|
||||
|
||||
if (cachedShaderEntries[0].Header.Stage == ShaderStage.Compute)
|
||||
{
|
||||
Debug.Assert(cachedShaderEntries.Length == 1);
|
||||
|
||||
GuestShaderCacheEntry entry = cachedShaderEntries[0];
|
||||
|
||||
byte[] code = entry.Code.AsSpan(0, entry.Header.Size - entry.Header.Cb1DataSize).ToArray();
|
||||
|
||||
Span<byte> codeSpan = entry.Code;
|
||||
byte[] cb1Data = codeSpan.Slice(codeSpan.Length - entry.Header.Cb1DataSize).ToArray();
|
||||
|
||||
ShaderProgramInfo info = new ShaderProgramInfo(
|
||||
Array.Empty<BufferDescriptor>(),
|
||||
Array.Empty<BufferDescriptor>(),
|
||||
Array.Empty<TextureDescriptor>(),
|
||||
Array.Empty<TextureDescriptor>(),
|
||||
ShaderStage.Compute,
|
||||
false,
|
||||
false,
|
||||
0,
|
||||
0);
|
||||
|
||||
GpuChannelComputeState computeState = new GpuChannelComputeState(
|
||||
entry.Header.GpuAccessorHeader.ComputeLocalSizeX,
|
||||
entry.Header.GpuAccessorHeader.ComputeLocalSizeY,
|
||||
entry.Header.GpuAccessorHeader.ComputeLocalSizeZ,
|
||||
entry.Header.GpuAccessorHeader.ComputeLocalMemorySize,
|
||||
entry.Header.GpuAccessorHeader.ComputeSharedMemorySize);
|
||||
|
||||
ShaderSpecializationState specState = new ShaderSpecializationState(computeState);
|
||||
|
||||
foreach (var td in entry.TextureDescriptors)
|
||||
{
|
||||
var handle = td.Key;
|
||||
var data = td.Value;
|
||||
|
||||
specState.RegisterTexture(
|
||||
0,
|
||||
handle,
|
||||
-1,
|
||||
data.UnpackFormat(),
|
||||
data.UnpackSrgb(),
|
||||
data.UnpackTextureTarget(),
|
||||
data.UnpackTextureCoordNormalized());
|
||||
}
|
||||
|
||||
CachedShaderStage shader = new CachedShaderStage(info, code, cb1Data);
|
||||
CachedShaderProgram program = new CachedShaderProgram(null, specState, shader);
|
||||
|
||||
hostStorage.AddShader(context, program, ReadOnlySpan<byte>.Empty);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Assert(cachedShaderEntries.Length == Constants.ShaderStages);
|
||||
|
||||
CachedShaderStage[] shaders = new CachedShaderStage[Constants.ShaderStages + 1];
|
||||
List<ShaderProgram> shaderPrograms = new List<ShaderProgram>();
|
||||
|
||||
TransformFeedbackDescriptorOld[] tfd = CacheHelper.ReadTransformFeedbackInformation(ref guestProgramReadOnlySpan, fileHeader);
|
||||
|
||||
GuestShaderCacheEntry[] entries = cachedShaderEntries.ToArray();
|
||||
|
||||
GuestGpuAccessorHeader accessorHeader = entries[0].Header.GpuAccessorHeader;
|
||||
|
||||
TessMode tessMode = new TessMode();
|
||||
|
||||
int tessPatchType = accessorHeader.TessellationModePacked & 3;
|
||||
int tessSpacing = (accessorHeader.TessellationModePacked >> 2) & 3;
|
||||
bool tessCw = (accessorHeader.TessellationModePacked & 0x10) != 0;
|
||||
|
||||
tessMode.Packed = (uint)tessPatchType;
|
||||
tessMode.Packed |= (uint)(tessSpacing << 4);
|
||||
|
||||
if (tessCw)
|
||||
{
|
||||
tessMode.Packed |= 0x100;
|
||||
}
|
||||
|
||||
PrimitiveTopology topology = accessorHeader.PrimitiveTopology switch
|
||||
{
|
||||
InputTopology.Lines => PrimitiveTopology.Lines,
|
||||
InputTopology.LinesAdjacency => PrimitiveTopology.LinesAdjacency,
|
||||
InputTopology.Triangles => PrimitiveTopology.Triangles,
|
||||
InputTopology.TrianglesAdjacency => PrimitiveTopology.TrianglesAdjacency,
|
||||
_ => PrimitiveTopology.Points
|
||||
};
|
||||
|
||||
GpuChannelGraphicsState graphicsState = new GpuChannelGraphicsState(
|
||||
accessorHeader.StateFlags.HasFlag(GuestGpuStateFlags.EarlyZForce),
|
||||
topology,
|
||||
tessMode);
|
||||
|
||||
TransformFeedbackDescriptor[] tfdNew = null;
|
||||
|
||||
if (tfd != null)
|
||||
{
|
||||
tfdNew = new TransformFeedbackDescriptor[tfd.Length];
|
||||
|
||||
for (int tfIndex = 0; tfIndex < tfd.Length; tfIndex++)
|
||||
{
|
||||
Array32<uint> varyingLocations = new Array32<uint>();
|
||||
Span<byte> varyingLocationsSpan = MemoryMarshal.Cast<uint, byte>(varyingLocations.ToSpan());
|
||||
tfd[tfIndex].VaryingLocations.CopyTo(varyingLocationsSpan.Slice(0, tfd[tfIndex].VaryingLocations.Length));
|
||||
|
||||
tfdNew[tfIndex] = new TransformFeedbackDescriptor(
|
||||
tfd[tfIndex].BufferIndex,
|
||||
tfd[tfIndex].Stride,
|
||||
tfd[tfIndex].VaryingLocations.Length,
|
||||
ref varyingLocations);
|
||||
}
|
||||
}
|
||||
|
||||
ShaderSpecializationState specState = new ShaderSpecializationState(graphicsState, tfdNew);
|
||||
|
||||
for (int i = 0; i < entries.Length; i++)
|
||||
{
|
||||
GuestShaderCacheEntry entry = entries[i];
|
||||
|
||||
if (entry == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ShaderProgramInfo info = new ShaderProgramInfo(
|
||||
Array.Empty<BufferDescriptor>(),
|
||||
Array.Empty<BufferDescriptor>(),
|
||||
Array.Empty<TextureDescriptor>(),
|
||||
Array.Empty<TextureDescriptor>(),
|
||||
(ShaderStage)(i + 1),
|
||||
false,
|
||||
false,
|
||||
0,
|
||||
0);
|
||||
|
||||
// NOTE: Vertex B comes first in the shader cache.
|
||||
byte[] code = entry.Code.AsSpan(0, entry.Header.Size - entry.Header.Cb1DataSize).ToArray();
|
||||
byte[] code2 = entry.Header.SizeA != 0 ? entry.Code.AsSpan(entry.Header.Size, entry.Header.SizeA).ToArray() : null;
|
||||
|
||||
Span<byte> codeSpan = entry.Code;
|
||||
byte[] cb1Data = codeSpan.Slice(codeSpan.Length - entry.Header.Cb1DataSize).ToArray();
|
||||
|
||||
shaders[i + 1] = new CachedShaderStage(info, code, cb1Data);
|
||||
|
||||
if (code2 != null)
|
||||
{
|
||||
shaders[0] = new CachedShaderStage(null, code2, cb1Data);
|
||||
}
|
||||
|
||||
foreach (var td in entry.TextureDescriptors)
|
||||
{
|
||||
var handle = td.Key;
|
||||
var data = td.Value;
|
||||
|
||||
specState.RegisterTexture(
|
||||
i,
|
||||
handle,
|
||||
-1,
|
||||
data.UnpackFormat(),
|
||||
data.UnpackSrgb(),
|
||||
data.UnpackTextureTarget(),
|
||||
data.UnpackTextureCoordNormalized());
|
||||
}
|
||||
}
|
||||
|
||||
CachedShaderProgram program = new CachedShaderProgram(null, specState, shaders);
|
||||
|
||||
hostStorage.AddShader(context, program, ReadOnlySpan<byte>.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
return guestProgramList.Length;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
||||
{
|
||||
struct TransformFeedbackDescriptorOld
|
||||
{
|
||||
public int BufferIndex { get; }
|
||||
public int Stride { get; }
|
||||
|
||||
public byte[] VaryingLocations { get; }
|
||||
|
||||
public TransformFeedbackDescriptorOld(int bufferIndex, int stride, byte[] varyingLocations)
|
||||
{
|
||||
BufferIndex = bufferIndex;
|
||||
Stride = stride;
|
||||
VaryingLocations = varyingLocations ?? throw new ArgumentNullException(nameof(varyingLocations));
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user