Compare commits
66 Commits
Author | SHA1 | Date | |
---|---|---|---|
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 | |||
0a24aa6af2 | |||
c9c65af59e | |||
dc063eac83 | |||
ccf23fc629 | |||
f1460d5494 | |||
644b497df1 |
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++)
|
||||
{
|
||||
|
@ -18,7 +18,7 @@ namespace ARMeilleure.Decoders
|
||||
// For lower code quality translation, we set a lower limit since we're blocking execution.
|
||||
private const int MaxInstsPerFunctionLowCq = 500;
|
||||
|
||||
public static Block[] Decode(IMemoryManager memory, ulong address, ExecutionMode mode, bool highCq, bool singleBlock)
|
||||
public static Block[] Decode(IMemoryManager memory, ulong address, ExecutionMode mode, bool highCq, DecoderMode dMode)
|
||||
{
|
||||
List<Block> blocks = new List<Block>();
|
||||
|
||||
@ -38,7 +38,7 @@ namespace ARMeilleure.Decoders
|
||||
{
|
||||
block = new Block(blkAddress);
|
||||
|
||||
if ((singleBlock && visited.Count >= 1) || opsCount > instructionLimit || !memory.IsMapped(blkAddress))
|
||||
if ((dMode != DecoderMode.MultipleBlocks && visited.Count >= 1) || opsCount > instructionLimit || !memory.IsMapped(blkAddress))
|
||||
{
|
||||
block.Exit = true;
|
||||
block.EndAddress = blkAddress;
|
||||
@ -96,6 +96,12 @@ namespace ARMeilleure.Decoders
|
||||
}
|
||||
}
|
||||
|
||||
if (dMode == DecoderMode.SingleInstruction)
|
||||
{
|
||||
// Only read at most one instruction
|
||||
limitAddress = currBlock.Address + 1;
|
||||
}
|
||||
|
||||
FillBlock(memory, mode, currBlock, limitAddress);
|
||||
|
||||
opsCount += currBlock.OpCodes.Count;
|
||||
@ -115,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);
|
||||
}
|
||||
@ -143,7 +149,7 @@ namespace ARMeilleure.Decoders
|
||||
throw new InvalidOperationException($"Decoded a single empty exit block. Entry point = 0x{address:X}.");
|
||||
}
|
||||
|
||||
if (!singleBlock)
|
||||
if (dMode == DecoderMode.MultipleBlocks)
|
||||
{
|
||||
return TailCallRemover.RunPass(address, blocks);
|
||||
}
|
||||
@ -257,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;
|
||||
}
|
||||
|
||||
@ -318,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;
|
||||
}
|
||||
|
9
ARMeilleure/Decoders/DecoderMode.cs
Normal file
9
ARMeilleure/Decoders/DecoderMode.cs
Normal file
@ -0,0 +1,9 @@
|
||||
namespace ARMeilleure.Decoders
|
||||
{
|
||||
enum DecoderMode
|
||||
{
|
||||
MultipleBlocks,
|
||||
SingleBlock,
|
||||
SingleInstruction,
|
||||
}
|
||||
}
|
@ -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
|
||||
@ -71,7 +71,7 @@ namespace ARMeilleure.Instructions
|
||||
|
||||
SetFlag(context, PState.TFlag, bitOne);
|
||||
|
||||
EmitVirtualCall(context, addr);
|
||||
EmitBxWritePc(context, addr);
|
||||
}
|
||||
|
||||
public static void Bx(ArmEmitterContext context)
|
||||
|
@ -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);
|
||||
@ -186,7 +181,7 @@ namespace ARMeilleure.Instructions
|
||||
|
||||
SetFlag(context, PState.TFlag, mode);
|
||||
|
||||
Operand addr = context.ConditionalSelect(mode, pc, context.BitwiseAnd(pc, Const(~3)));
|
||||
Operand addr = context.ConditionalSelect(mode, context.BitwiseAnd(pc, Const(~1)), context.BitwiseAnd(pc, Const(~3)));
|
||||
|
||||
InstEmitFlowHelper.EmitVirtualJump(context, addr, isReturn);
|
||||
}
|
||||
|
@ -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";
|
||||
|
@ -113,7 +113,7 @@ namespace ARMeilleure.Translation
|
||||
}
|
||||
}
|
||||
|
||||
Array.Clear(localDefs, 0, localDefs.Length);
|
||||
Array.Clear(localDefs);
|
||||
}
|
||||
|
||||
// Second pass, rename variables with definitions on different blocks.
|
||||
|
@ -209,6 +209,17 @@ namespace ARMeilleure.Translation
|
||||
return nextAddr;
|
||||
}
|
||||
|
||||
public ulong Step(State.ExecutionContext context, ulong address)
|
||||
{
|
||||
TranslatedFunction func = Translate(address, context.ExecutionMode, highCq: false, singleStep: true);
|
||||
|
||||
address = func.Execute(context);
|
||||
|
||||
EnqueueForDeletion(address, func);
|
||||
|
||||
return address;
|
||||
}
|
||||
|
||||
internal TranslatedFunction GetOrTranslate(ulong address, ExecutionMode mode)
|
||||
{
|
||||
if (!Functions.TryGetValue(address, out TranslatedFunction func))
|
||||
@ -242,7 +253,7 @@ namespace ARMeilleure.Translation
|
||||
}
|
||||
}
|
||||
|
||||
internal TranslatedFunction Translate(ulong address, ExecutionMode mode, bool highCq)
|
||||
internal TranslatedFunction Translate(ulong address, ExecutionMode mode, bool highCq, bool singleStep = false)
|
||||
{
|
||||
var context = new ArmEmitterContext(
|
||||
Memory,
|
||||
@ -255,7 +266,7 @@ namespace ARMeilleure.Translation
|
||||
|
||||
Logger.StartPass(PassName.Decoding);
|
||||
|
||||
Block[] blocks = Decoder.Decode(Memory, address, mode, highCq, singleBlock: false);
|
||||
Block[] blocks = Decoder.Decode(Memory, address, mode, highCq, singleStep ? DecoderMode.SingleInstruction : DecoderMode.MultipleBlocks);
|
||||
|
||||
Logger.EndPass(PassName.Decoding);
|
||||
|
||||
@ -285,14 +296,14 @@ namespace ARMeilleure.Translation
|
||||
|
||||
var options = highCq ? CompilerOptions.HighCq : CompilerOptions.None;
|
||||
|
||||
if (context.HasPtc)
|
||||
if (context.HasPtc && !singleStep)
|
||||
{
|
||||
options |= CompilerOptions.Relocatable;
|
||||
}
|
||||
|
||||
CompiledFunction compiledFunc = Compiler.Compile(cfg, argTypes, retType, options);
|
||||
|
||||
if (context.HasPtc)
|
||||
if (context.HasPtc && !singleStep)
|
||||
{
|
||||
Hash128 hash = Ptc.ComputeHash(Memory, address, funcSize);
|
||||
|
||||
|
@ -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; }
|
||||
@ -112,12 +112,12 @@ namespace Ryujinx.Audio.Renderer.Dsp.State
|
||||
|
||||
private ReadOnlySpan<float> GetFdnDelayTimesByLateMode(ReverbLateMode lateMode)
|
||||
{
|
||||
return FdnDelayTimes.AsSpan().Slice((int)lateMode * 4, 4);
|
||||
return FdnDelayTimes.AsSpan((int)lateMode * 4, 4);
|
||||
}
|
||||
|
||||
private ReadOnlySpan<float> GetDecayDelayTimesByLateMode(ReverbLateMode lateMode)
|
||||
{
|
||||
return DecayDelayTimes.AsSpan().Slice((int)lateMode * 4, 4);
|
||||
return DecayDelayTimes.AsSpan((int)lateMode * 4, 4);
|
||||
}
|
||||
|
||||
public ReverbState(ref ReverbParameter parameter, ulong workBuffer, bool isLongSizePreDelaySupported)
|
||||
@ -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;
|
||||
|
||||
|
@ -459,7 +459,7 @@ namespace Ryujinx.Audio.Renderer.Server.Voice
|
||||
|
||||
for (int i = 0; i < Constants.VoiceWaveBufferCount; i++)
|
||||
{
|
||||
UpdateWaveBuffer(errorInfos.AsSpan().Slice(i * 2, 2), ref WaveBuffers[i], ref parameter.WaveBuffers[i], parameter.SampleFormat, voiceUpdateState.IsWaveBufferValid[i], ref mapper, ref behaviourContext);
|
||||
UpdateWaveBuffer(errorInfos.AsSpan(i * 2, 2), ref WaveBuffers[i], ref parameter.WaveBuffers[i], parameter.SampleFormat, voiceUpdateState.IsWaveBufferValid[i], ref mapper, ref behaviourContext);
|
||||
}
|
||||
}
|
||||
|
||||
|
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;
|
||||
|
@ -1,11 +1,15 @@
|
||||
using Ryujinx.Graphics.Device;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Graphics.Device;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Gpu.Engine.Types;
|
||||
using Ryujinx.Graphics.Gpu.Image;
|
||||
using Ryujinx.Graphics.Texture;
|
||||
using Ryujinx.Memory;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Intrinsics;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Engine.Twod
|
||||
{
|
||||
@ -44,6 +48,180 @@ namespace Ryujinx.Graphics.Gpu.Engine.Twod
|
||||
/// <param name="data">Data to be written</param>
|
||||
public void Write(int offset, int data) => _state.Write(offset, data);
|
||||
|
||||
/// <summary>
|
||||
/// Determines if data is compatible between the source and destination texture.
|
||||
/// The two textures must have the same size, layout, and bytes per pixel.
|
||||
/// </summary>
|
||||
/// <param name="lhs">Info for the first texture</param>
|
||||
/// <param name="rhs">Info for the second texture</param>
|
||||
/// <param name="lhsFormat">Format of the first texture</param>
|
||||
/// <param name="rhsFormat">Format of the second texture</param>
|
||||
/// <returns>True if the data is compatible, false otherwise</returns>
|
||||
private bool IsDataCompatible(TwodTexture lhs, TwodTexture rhs, FormatInfo lhsFormat, FormatInfo rhsFormat)
|
||||
{
|
||||
if (lhsFormat.BytesPerPixel != rhsFormat.BytesPerPixel ||
|
||||
lhs.Height != rhs.Height ||
|
||||
lhs.Depth != rhs.Depth ||
|
||||
lhs.LinearLayout != rhs.LinearLayout ||
|
||||
lhs.MemoryLayout.Packed != rhs.MemoryLayout.Packed)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (lhs.LinearLayout)
|
||||
{
|
||||
return lhs.Stride == rhs.Stride;
|
||||
}
|
||||
else
|
||||
{
|
||||
return lhs.Width == rhs.Width;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determine if the given region covers the full texture, also considering width alignment.
|
||||
/// </summary>
|
||||
/// <param name="texture">The texture to check</param>
|
||||
/// <param name="formatInfo"></param>
|
||||
/// <param name="x1">Region start x</param>
|
||||
/// <param name="y1">Region start y</param>
|
||||
/// <param name="x2">Region end x</param>
|
||||
/// <param name="y2">Region end y</param>
|
||||
/// <returns>True if the region covers the full texture, false otherwise</returns>
|
||||
private bool IsCopyRegionComplete(TwodTexture texture, FormatInfo formatInfo, int x1, int y1, int x2, int y2)
|
||||
{
|
||||
if (x1 != 0 || y1 != 0 || y2 != texture.Height)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int width;
|
||||
int widthAlignment;
|
||||
|
||||
if (texture.LinearLayout)
|
||||
{
|
||||
widthAlignment = 1;
|
||||
width = texture.Stride / formatInfo.BytesPerPixel;
|
||||
}
|
||||
else
|
||||
{
|
||||
widthAlignment = Constants.GobAlignment / formatInfo.BytesPerPixel;
|
||||
width = texture.Width;
|
||||
}
|
||||
|
||||
return width == BitUtils.AlignUp(x2, widthAlignment);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a full data copy between two textures, reading and writing guest memory directly.
|
||||
/// The textures must have a matching layout, size, and bytes per pixel.
|
||||
/// </summary>
|
||||
/// <param name="src">The source texture</param>
|
||||
/// <param name="dst">The destination texture</param>
|
||||
/// <param name="w">Copy width</param>
|
||||
/// <param name="h">Copy height</param>
|
||||
/// <param name="bpp">Bytes per pixel</param>
|
||||
private void UnscaledFullCopy(TwodTexture src, TwodTexture dst, int w, int h, int bpp)
|
||||
{
|
||||
var srcCalculator = new OffsetCalculator(
|
||||
w,
|
||||
h,
|
||||
src.Stride,
|
||||
src.LinearLayout,
|
||||
src.MemoryLayout.UnpackGobBlocksInY(),
|
||||
src.MemoryLayout.UnpackGobBlocksInZ(),
|
||||
bpp);
|
||||
|
||||
(int _, int srcSize) = srcCalculator.GetRectangleRange(0, 0, w, h);
|
||||
|
||||
var memoryManager = _channel.MemoryManager;
|
||||
|
||||
ulong srcGpuVa = src.Address.Pack();
|
||||
ulong dstGpuVa = dst.Address.Pack();
|
||||
|
||||
ReadOnlySpan<byte> srcSpan = memoryManager.GetSpan(srcGpuVa, srcSize, true);
|
||||
|
||||
int width;
|
||||
int height = src.Height;
|
||||
if (src.LinearLayout)
|
||||
{
|
||||
width = src.Stride / bpp;
|
||||
}
|
||||
else
|
||||
{
|
||||
width = src.Width;
|
||||
}
|
||||
|
||||
// If the copy is not equal to the width and height of the texture, we will need to copy partially.
|
||||
// It's worth noting that it has already been established that the src and dst are the same size.
|
||||
|
||||
if (w == width && h == height)
|
||||
{
|
||||
memoryManager.Write(dstGpuVa, srcSpan);
|
||||
}
|
||||
else
|
||||
{
|
||||
using WritableRegion dstRegion = memoryManager.GetWritableRegion(dstGpuVa, srcSize, true);
|
||||
Span<byte> dstSpan = dstRegion.Memory.Span;
|
||||
|
||||
if (src.LinearLayout)
|
||||
{
|
||||
int stride = src.Stride;
|
||||
int offset = 0;
|
||||
int lineSize = width * bpp;
|
||||
|
||||
for (int y = 0; y < height; y++)
|
||||
{
|
||||
srcSpan.Slice(offset, lineSize).CopyTo(dstSpan.Slice(offset));
|
||||
|
||||
offset += stride;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Copy with the block linear layout in mind.
|
||||
// Recreate the offset calculate with bpp 1 for copy.
|
||||
|
||||
int stride = w * bpp;
|
||||
|
||||
srcCalculator = new OffsetCalculator(
|
||||
stride,
|
||||
h,
|
||||
0,
|
||||
false,
|
||||
src.MemoryLayout.UnpackGobBlocksInY(),
|
||||
src.MemoryLayout.UnpackGobBlocksInZ(),
|
||||
1);
|
||||
|
||||
int strideTrunc = BitUtils.AlignDown(stride, 16);
|
||||
|
||||
ReadOnlySpan<Vector128<byte>> srcVec = MemoryMarshal.Cast<byte, Vector128<byte>>(srcSpan);
|
||||
Span<Vector128<byte>> dstVec = MemoryMarshal.Cast<byte, Vector128<byte>>(dstSpan);
|
||||
|
||||
for (int y = 0; y < h; y++)
|
||||
{
|
||||
int x = 0;
|
||||
|
||||
srcCalculator.SetY(y);
|
||||
|
||||
for (; x < strideTrunc; x += 16)
|
||||
{
|
||||
int offset = srcCalculator.GetOffset(x) >> 4;
|
||||
|
||||
dstVec[offset] = srcVec[offset];
|
||||
}
|
||||
|
||||
for (; x < stride; x++)
|
||||
{
|
||||
int offset = srcCalculator.GetOffset(x);
|
||||
|
||||
dstSpan[offset] = srcSpan[offset];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs the blit operation, triggered by the register write.
|
||||
/// </summary>
|
||||
@ -114,16 +292,31 @@ namespace Ryujinx.Graphics.Gpu.Engine.Twod
|
||||
srcX1 = 0;
|
||||
}
|
||||
|
||||
FormatInfo dstCopyTextureFormat = dstCopyTexture.Format.Convert();
|
||||
|
||||
bool canDirectCopy = GraphicsConfig.Fast2DCopy &&
|
||||
srcX2 == dstX2 && srcY2 == dstY2 &&
|
||||
IsDataCompatible(srcCopyTexture, dstCopyTexture, srcCopyTextureFormat, dstCopyTextureFormat) &&
|
||||
IsCopyRegionComplete(srcCopyTexture, srcCopyTextureFormat, srcX1, srcY1, srcX2, srcY2) &&
|
||||
IsCopyRegionComplete(dstCopyTexture, dstCopyTextureFormat, dstX1, dstY1, dstX2, dstY2);
|
||||
|
||||
var srcTexture = memoryManager.Physical.TextureCache.FindOrCreateTexture(
|
||||
memoryManager,
|
||||
srcCopyTexture,
|
||||
offset,
|
||||
srcCopyTextureFormat,
|
||||
!canDirectCopy,
|
||||
false,
|
||||
srcHint);
|
||||
|
||||
if (srcTexture == null)
|
||||
{
|
||||
if (canDirectCopy)
|
||||
{
|
||||
// Directly copy the data on CPU.
|
||||
UnscaledFullCopy(srcCopyTexture, dstCopyTexture, srcX2, srcY2, srcCopyTextureFormat.BytesPerPixel);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@ -132,7 +325,6 @@ namespace Ryujinx.Graphics.Gpu.Engine.Twod
|
||||
// When the source texture that was found has a depth format,
|
||||
// we must enforce the target texture also has a depth format,
|
||||
// as copies between depth and color formats are not allowed.
|
||||
FormatInfo dstCopyTextureFormat;
|
||||
|
||||
if (srcTexture.Format.IsDepthOrStencil())
|
||||
{
|
||||
@ -148,6 +340,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Twod
|
||||
dstCopyTexture,
|
||||
0,
|
||||
dstCopyTextureFormat,
|
||||
true,
|
||||
srcTexture.ScaleMode == TextureScaleMode.Scaled,
|
||||
dstHint);
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -28,6 +28,14 @@ namespace Ryujinx.Graphics.Gpu
|
||||
/// </summary>
|
||||
public static bool FastGpuTime = true;
|
||||
|
||||
/// <summary>
|
||||
/// Enables or disables fast 2d engine texture copies entirely on CPU when possible.
|
||||
/// Reduces stuttering and # of textures in games that copy textures around for streaming,
|
||||
/// as textures will not need to be created for the copy, and the data does not need to be
|
||||
/// flushed from GPU.
|
||||
/// </summary>
|
||||
public static bool Fast2DCopy = true;
|
||||
|
||||
/// <summary>
|
||||
/// Enables or disables the Just-in-Time compiler for GPU Macro code.
|
||||
/// </summary>
|
||||
|
@ -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;
|
||||
@ -40,6 +39,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
private readonly PhysicalMemory _physicalMemory;
|
||||
|
||||
private readonly MultiRangeList<Texture> _textures;
|
||||
private readonly HashSet<Texture> _partiallyMappedTextures;
|
||||
|
||||
private Texture[] _textureOverlaps;
|
||||
private OverlapInfo[] _overlapInfo;
|
||||
@ -57,6 +57,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
_physicalMemory = physicalMemory;
|
||||
|
||||
_textures = new MultiRangeList<Texture>();
|
||||
_partiallyMappedTextures = new HashSet<Texture>();
|
||||
|
||||
_textureOverlaps = new Texture[OverlapsBufferInitialCapacity];
|
||||
_overlapInfo = new OverlapInfo[OverlapsBufferInitialCapacity];
|
||||
@ -74,17 +75,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
Texture[] overlaps = new Texture[10];
|
||||
int overlapCount;
|
||||
|
||||
MultiRange unmapped;
|
||||
|
||||
try
|
||||
{
|
||||
unmapped = ((MemoryManager)sender).GetPhysicalRegions(e.Address, e.Size);
|
||||
}
|
||||
catch (InvalidMemoryRegionException)
|
||||
{
|
||||
// This event fires on Map in case any mappings are overwritten. In that case, there may not be an existing mapping.
|
||||
return;
|
||||
}
|
||||
MultiRange unmapped = ((MemoryManager)sender).GetPhysicalRegions(e.Address, e.Size);
|
||||
|
||||
lock (_textures)
|
||||
{
|
||||
@ -95,6 +86,24 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
overlaps[i].Unmapped(unmapped);
|
||||
}
|
||||
|
||||
// If any range was previously unmapped, we also need to purge
|
||||
// all partially mapped texture, as they might be fully mapped now.
|
||||
for (int i = 0; i < unmapped.Count; i++)
|
||||
{
|
||||
if (unmapped.GetSubRange(i).Address == MemoryManager.PteUnmapped)
|
||||
{
|
||||
lock (_partiallyMappedTextures)
|
||||
{
|
||||
foreach (var texture in _partiallyMappedTextures)
|
||||
{
|
||||
texture.Unmapped(unmapped);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -194,6 +203,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
TwodTexture copyTexture,
|
||||
ulong offset,
|
||||
FormatInfo formatInfo,
|
||||
bool shouldCreate,
|
||||
bool preferScaling = true,
|
||||
Size? sizeHint = null)
|
||||
{
|
||||
@ -234,6 +244,11 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
flags |= TextureSearchFlags.WithUpscale;
|
||||
}
|
||||
|
||||
if (!shouldCreate)
|
||||
{
|
||||
flags |= TextureSearchFlags.NoCreate;
|
||||
}
|
||||
|
||||
Texture texture = FindOrCreateTexture(memoryManager, flags, info, 0, sizeHint);
|
||||
|
||||
texture?.SynchronizeMemory();
|
||||
@ -480,15 +495,29 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
|
||||
return texture;
|
||||
}
|
||||
else if (flags.HasFlag(TextureSearchFlags.NoCreate))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Calculate texture sizes, used to find all overlapping textures.
|
||||
SizeInfo sizeInfo = info.CalculateSizeInfo(layerSize);
|
||||
|
||||
ulong size = (ulong)sizeInfo.TotalSize;
|
||||
bool partiallyMapped = false;
|
||||
|
||||
if (range == null)
|
||||
{
|
||||
range = memoryManager.GetPhysicalRegions(info.GpuAddress, size);
|
||||
|
||||
for (int i = 0; i < range.Value.Count; i++)
|
||||
{
|
||||
if (range.Value.GetSubRange(i).Address == MemoryManager.PteUnmapped)
|
||||
{
|
||||
partiallyMapped = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find view compatible matches.
|
||||
@ -513,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)
|
||||
{
|
||||
@ -621,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)
|
||||
{
|
||||
@ -658,7 +701,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
else
|
||||
{
|
||||
bool dataOverlaps = texture.DataOverlaps(overlap, compatibility);
|
||||
|
||||
|
||||
if (!overlap.IsView && dataOverlaps && !incompatibleOverlaps.Exists(incompatible => incompatible.Group == overlap.Group))
|
||||
{
|
||||
incompatibleOverlaps.Add(new TextureIncompatibleOverlap(overlap.Group, compatibility));
|
||||
@ -774,6 +817,14 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
_textures.Add(texture);
|
||||
}
|
||||
|
||||
if (partiallyMapped)
|
||||
{
|
||||
lock (_partiallyMappedTextures)
|
||||
{
|
||||
_partiallyMappedTextures.Add(texture);
|
||||
}
|
||||
}
|
||||
|
||||
ShrinkOverlapsBufferIfNeeded();
|
||||
|
||||
for (int i = 0; i < overlapsCount; i++)
|
||||
@ -963,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,
|
||||
@ -1069,6 +1134,11 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
_textures.Remove(texture);
|
||||
}
|
||||
|
||||
lock (_partiallyMappedTextures)
|
||||
{
|
||||
_partiallyMappedTextures.Remove(texture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -236,7 +236,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Synchronize memory for a given texture.
|
||||
/// Synchronize memory for a given texture.
|
||||
/// If overlapping tracking handles are dirty, fully or partially synchronize the texture data.
|
||||
/// </summary>
|
||||
/// <param name="texture">The texture being used</param>
|
||||
@ -280,7 +280,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
|
||||
// Evaluate if any copy dependencies need to be fulfilled. A few rules:
|
||||
// If the copy handle needs to be synchronized, prefer our own state.
|
||||
// If we need to be synchronized and there is a copy present, prefer the copy.
|
||||
// If we need to be synchronized and there is a copy present, prefer the copy.
|
||||
|
||||
if (group.NeedsCopy && group.Copy(_context))
|
||||
{
|
||||
@ -618,7 +618,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Evaluate the range of tracking handles which a view texture overlaps with,
|
||||
/// Evaluate the range of tracking handles which a view texture overlaps with,
|
||||
/// using the view's position and slice/level counts.
|
||||
/// </summary>
|
||||
/// <param name="firstLayer">The first layer of the texture</param>
|
||||
@ -879,7 +879,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
int sliceStart = Math.Clamp(offset, 0, subRangeSize);
|
||||
int sliceEnd = Math.Clamp(endOffset, 0, subRangeSize);
|
||||
|
||||
if (sliceStart != sliceEnd)
|
||||
if (sliceStart != sliceEnd && item.Address != MemoryManager.PteUnmapped)
|
||||
{
|
||||
result.Add(GenerateHandle(item.Address + (ulong)sliceStart, (ulong)(sliceEnd - sliceStart)));
|
||||
}
|
||||
@ -1097,11 +1097,20 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
// Single dirty region.
|
||||
var cpuRegionHandles = new CpuRegionHandle[TextureRange.Count];
|
||||
int count = 0;
|
||||
|
||||
for (int i = 0; i < TextureRange.Count; i++)
|
||||
{
|
||||
var currentRange = TextureRange.GetSubRange(i);
|
||||
cpuRegionHandles[i] = GenerateHandle(currentRange.Address, currentRange.Size);
|
||||
if (currentRange.Address != MemoryManager.PteUnmapped)
|
||||
{
|
||||
cpuRegionHandles[count++] = GenerateHandle(currentRange.Address, currentRange.Size);
|
||||
}
|
||||
}
|
||||
|
||||
if (count != TextureRange.Count)
|
||||
{
|
||||
Array.Resize(ref cpuRegionHandles, count);
|
||||
}
|
||||
|
||||
var groupHandle = new TextureGroupHandle(this, 0, Storage.Size, _views, 0, 0, 0, _allOffsets.Length, cpuRegionHandles);
|
||||
@ -1277,7 +1286,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
TextureInfo info = Storage.Info;
|
||||
TextureInfo otherInfo = other.Storage.Info;
|
||||
|
||||
if (TextureCompatibility.ViewLayoutCompatible(info, otherInfo, level, otherLevel) &&
|
||||
if (TextureCompatibility.ViewLayoutCompatible(info, otherInfo, level, otherLevel) &&
|
||||
TextureCompatibility.CopySizeMatches(info, otherInfo, level, otherLevel))
|
||||
{
|
||||
// These textures are copy compatible. Create the dependency.
|
||||
@ -1384,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
|
||||
|
@ -12,6 +12,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
Strict = 1 << 0,
|
||||
ForSampler = 1 << 1,
|
||||
ForCopy = 1 << 2,
|
||||
WithUpscale = 1 << 3
|
||||
WithUpscale = 1 << 3,
|
||||
NoCreate = 1 << 4
|
||||
}
|
||||
}
|
@ -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();
|
||||
|
@ -28,7 +28,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
private const int PtLvl1Bit = PtPageBits;
|
||||
private const int AddressSpaceBits = PtPageBits + PtLvl1Bits + PtLvl0Bits;
|
||||
|
||||
public const ulong PteUnmapped = 0xffffffff_ffffffff;
|
||||
public const ulong PteUnmapped = ulong.MaxValue;
|
||||
|
||||
private readonly ulong[][] _pageTable;
|
||||
|
||||
@ -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>
|
||||
@ -154,14 +221,15 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// <summary>
|
||||
/// Gets a writable region from GPU mapped memory.
|
||||
/// </summary>
|
||||
/// <param name="address">Start address of the range</param>
|
||||
/// <param name="va">Start address of the range</param>
|
||||
/// <param name="size">Size in bytes to be range</param>
|
||||
/// <param name="tracked">True if write tracking is triggered on the span</param>
|
||||
/// <returns>A writable region with the data at the specified memory location</returns>
|
||||
public WritableRegion GetWritableRegion(ulong va, int size)
|
||||
public WritableRegion GetWritableRegion(ulong va, int size, bool tracked = false)
|
||||
{
|
||||
if (IsContiguous(va, size))
|
||||
{
|
||||
return Physical.GetWritableRegion(Translate(va), size);
|
||||
return Physical.GetWritableRegion(Translate(va), size, tracked);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -169,7 +237,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
|
||||
GetSpan(va, size).CopyTo(memory.Span);
|
||||
|
||||
return new WritableRegion(this, va, memory);
|
||||
return new WritableRegion(this, va, memory, tracked);
|
||||
}
|
||||
}
|
||||
|
||||
@ -254,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>
|
||||
@ -263,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)
|
||||
{
|
||||
@ -271,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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -339,7 +451,6 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// <param name="va">Virtual address of the range</param>
|
||||
/// <param name="size">Size of the range</param>
|
||||
/// <returns>Multi-range with the physical regions</returns>
|
||||
/// <exception cref="InvalidMemoryRegionException">The memory region specified by <paramref name="va"/> and <paramref name="size"/> is not fully mapped</exception>
|
||||
public MultiRange GetPhysicalRegions(ulong va, ulong size)
|
||||
{
|
||||
if (IsContiguous(va, (int)size))
|
||||
@ -347,11 +458,6 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
return new MultiRange(Translate(va), size);
|
||||
}
|
||||
|
||||
if (!IsMapped(va))
|
||||
{
|
||||
throw new InvalidMemoryRegionException($"The specified GPU virtual address 0x{va:X} is not mapped.");
|
||||
}
|
||||
|
||||
ulong regionStart = Translate(va);
|
||||
ulong regionSize = Math.Min(size, PageSize - (va & PageMask));
|
||||
|
||||
@ -366,14 +472,10 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
|
||||
for (int page = 0; page < pages - 1; page++)
|
||||
{
|
||||
if (!IsMapped(va + PageSize))
|
||||
{
|
||||
throw new InvalidMemoryRegionException($"The specified GPU virtual memory range 0x{va:X}..0x{(va + size):X} is not fully mapped.");
|
||||
}
|
||||
|
||||
ulong currPa = Translate(va);
|
||||
ulong newPa = Translate(va + PageSize);
|
||||
|
||||
if (Translate(va) + PageSize != newPa)
|
||||
if ((currPa != PteUnmapped || newPa != PteUnmapped) && currPa + PageSize != newPa)
|
||||
{
|
||||
regions.Add(new MemoryRange(regionStart, regionSize));
|
||||
regionStart = newPa;
|
||||
@ -404,18 +506,35 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
{
|
||||
MemoryRange currentRange = range.GetSubRange(i);
|
||||
|
||||
ulong address = currentRange.Address & ~PageMask;
|
||||
ulong endAddress = (currentRange.EndAddress + PageMask) & ~PageMask;
|
||||
|
||||
while (address < endAddress)
|
||||
if (currentRange.Address != PteUnmapped)
|
||||
{
|
||||
if (Translate(va) != address)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
ulong address = currentRange.Address & ~PageMask;
|
||||
ulong endAddress = (currentRange.EndAddress + PageMask) & ~PageMask;
|
||||
|
||||
va += PageSize;
|
||||
address += PageSize;
|
||||
while (address < endAddress)
|
||||
{
|
||||
if (Translate(va) != address)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
va += PageSize;
|
||||
address += PageSize;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ulong endVa = va + (((currentRange.Size) + PageMask) & ~PageMask);
|
||||
|
||||
while (va < endVa)
|
||||
{
|
||||
if (Translate(va) != PteUnmapped)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
va += PageSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -454,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>
|
||||
@ -504,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;
|
||||
}
|
||||
}
|
||||
}
|
@ -7,8 +7,6 @@ using Ryujinx.Memory.Range;
|
||||
using Ryujinx.Memory.Tracking;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Memory
|
||||
@ -19,8 +17,6 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// </summary>
|
||||
class PhysicalMemory : IDisposable
|
||||
{
|
||||
public const int PageSize = 0x1000;
|
||||
|
||||
private readonly GpuContext _context;
|
||||
private IVirtualMemoryManagerTracked _cpuMemory;
|
||||
private int _referenceCount;
|
||||
@ -103,24 +99,28 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
if (range.Count == 1)
|
||||
{
|
||||
var singleRange = range.GetSubRange(0);
|
||||
return _cpuMemory.GetSpan(singleRange.Address, (int)singleRange.Size, tracked);
|
||||
}
|
||||
else
|
||||
{
|
||||
Span<byte> data = new byte[range.GetSize()];
|
||||
|
||||
int offset = 0;
|
||||
|
||||
for (int i = 0; i < range.Count; i++)
|
||||
if (singleRange.Address != MemoryManager.PteUnmapped)
|
||||
{
|
||||
var currentRange = range.GetSubRange(i);
|
||||
int size = (int)currentRange.Size;
|
||||
_cpuMemory.GetSpan(currentRange.Address, size, tracked).CopyTo(data.Slice(offset, size));
|
||||
offset += size;
|
||||
return _cpuMemory.GetSpan(singleRange.Address, (int)singleRange.Size, tracked);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
Span<byte> data = new byte[range.GetSize()];
|
||||
|
||||
int offset = 0;
|
||||
|
||||
for (int i = 0; i < range.Count; i++)
|
||||
{
|
||||
var currentRange = range.GetSubRange(i);
|
||||
int size = (int)currentRange.Size;
|
||||
if (currentRange.Address != MemoryManager.PteUnmapped)
|
||||
{
|
||||
_cpuMemory.GetSpan(currentRange.Address, size, tracked).CopyTo(data.Slice(offset, size));
|
||||
}
|
||||
offset += size;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -156,11 +156,13 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
int offset = 0;
|
||||
for (int i = 0; i < range.Count; i++)
|
||||
{
|
||||
MemoryRange subrange = range.GetSubRange(i);
|
||||
|
||||
GetSpan(subrange.Address, (int)subrange.Size).CopyTo(memory.Span.Slice(offset, (int)subrange.Size));
|
||||
|
||||
offset += (int)subrange.Size;
|
||||
var currentRange = range.GetSubRange(i);
|
||||
int size = (int)currentRange.Size;
|
||||
if (currentRange.Address != MemoryManager.PteUnmapped)
|
||||
{
|
||||
GetSpan(currentRange.Address, size).CopyTo(memory.Span.Slice(offset, size));
|
||||
}
|
||||
offset += size;
|
||||
}
|
||||
|
||||
return new WritableRegion(new MultiRangeWritableBlock(range, this), 0, memory, tracked);
|
||||
@ -253,7 +255,10 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
if (range.Count == 1)
|
||||
{
|
||||
var singleRange = range.GetSubRange(0);
|
||||
writeCallback(singleRange.Address, data);
|
||||
if (singleRange.Address != MemoryManager.PteUnmapped)
|
||||
{
|
||||
writeCallback(singleRange.Address, data);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -263,7 +268,10 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
{
|
||||
var currentRange = range.GetSubRange(i);
|
||||
int size = (int)currentRange.Size;
|
||||
writeCallback(currentRange.Address, data.Slice(offset, size));
|
||||
if (currentRange.Address != MemoryManager.PteUnmapped)
|
||||
{
|
||||
writeCallback(currentRange.Address, data.Slice(offset, size));
|
||||
}
|
||||
offset += size;
|
||||
}
|
||||
}
|
||||
@ -288,11 +296,20 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
public GpuRegionHandle BeginTracking(MultiRange range)
|
||||
{
|
||||
var cpuRegionHandles = new CpuRegionHandle[range.Count];
|
||||
int count = 0;
|
||||
|
||||
for (int i = 0; i < range.Count; i++)
|
||||
{
|
||||
var currentRange = range.GetSubRange(i);
|
||||
cpuRegionHandles[i] = _cpuMemory.BeginTracking(currentRange.Address, currentRange.Size);
|
||||
if (currentRange.Address != MemoryManager.PteUnmapped)
|
||||
{
|
||||
cpuRegionHandles[count++] = _cpuMemory.BeginTracking(currentRange.Address, currentRange.Size);
|
||||
}
|
||||
}
|
||||
|
||||
if (count != range.Count)
|
||||
{
|
||||
Array.Resize(ref cpuRegionHandles, count);
|
||||
}
|
||||
|
||||
return new GpuRegionHandle(cpuRegionHandles);
|
||||
@ -323,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
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user