Compare commits
34 Commits
Author | SHA1 | Date | |
---|---|---|---|
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 | |||
fb935fd201 | |||
f2087ca29e |
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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,46 @@ 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("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("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 +1132,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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 = 3061; //! To be incremented manually for each change to the ARMeilleure project.
|
||||
private const uint InternalVersion = 3193; //! 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);
|
||||
|
||||
|
@ -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,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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
};
|
||||
}
|
||||
|
@ -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) },
|
||||
|
@ -40,6 +40,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 +58,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 +76,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 +87,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 +204,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
TwodTexture copyTexture,
|
||||
ulong offset,
|
||||
FormatInfo formatInfo,
|
||||
bool shouldCreate,
|
||||
bool preferScaling = true,
|
||||
Size? sizeHint = null)
|
||||
{
|
||||
@ -234,6 +245,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 +496,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.
|
||||
@ -658,7 +688,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 +804,14 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
_textures.Add(texture);
|
||||
}
|
||||
|
||||
if (partiallyMapped)
|
||||
{
|
||||
lock (_partiallyMappedTextures)
|
||||
{
|
||||
_partiallyMappedTextures.Add(texture);
|
||||
}
|
||||
}
|
||||
|
||||
ShrinkOverlapsBufferIfNeeded();
|
||||
|
||||
for (int i = 0; i < overlapsCount; i++)
|
||||
@ -1069,6 +1107,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.
|
||||
|
@ -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;
|
||||
|
||||
@ -154,14 +154,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 +170,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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -339,7 +340,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 +347,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 +361,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 +395,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -113,7 +113,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
||||
dataSpan[i++] = hash;
|
||||
}
|
||||
|
||||
manifestHeader.UpdateChecksum(data.AsSpan().Slice(Unsafe.SizeOf<CacheManifestHeader>()));
|
||||
manifestHeader.UpdateChecksum(data.AsSpan(Unsafe.SizeOf<CacheManifestHeader>()));
|
||||
|
||||
MemoryMarshal.Write(data, ref manifestHeader);
|
||||
|
||||
@ -447,12 +447,12 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
||||
|
||||
if (cb1DataAddress != 0 && cb1DataSize != 0)
|
||||
{
|
||||
memoryManager.Physical.GetSpan(cb1DataAddress, cb1DataSize).CopyTo(code.AsSpan().Slice(size, cb1DataSize));
|
||||
memoryManager.Physical.GetSpan(cb1DataAddress, cb1DataSize).CopyTo(code.AsSpan(size, cb1DataSize));
|
||||
}
|
||||
|
||||
if (translatorContext2 != null)
|
||||
{
|
||||
memoryManager.GetSpan(translatorContext2.Address, sizeA).CopyTo(code.AsSpan().Slice(size + cb1DataSize, sizeA));
|
||||
memoryManager.GetSpan(translatorContext2.Address, sizeA).CopyTo(code.AsSpan(size + cb1DataSize, sizeA));
|
||||
}
|
||||
|
||||
GuestGpuAccessorHeader gpuAccessorHeader = CreateGuestGpuAccessorCache(context.GpuAccessor);
|
||||
|
@ -40,7 +40,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
/// <summary>
|
||||
/// Version of the codegen (to be changed when codegen or guest format change).
|
||||
/// </summary>
|
||||
private const ulong ShaderCodeGenVersion = 3132;
|
||||
private const ulong ShaderCodeGenVersion = 3054;
|
||||
|
||||
// Progress reporting helpers
|
||||
private volatile int _shaderCount;
|
||||
@ -206,7 +206,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
program = new ShaderProgram(entry.Header.Stage, "");
|
||||
shaderProgramInfo = hostShaderEntries[0].ToShaderProgramInfo();
|
||||
|
||||
byte[] code = entry.Code.AsSpan().Slice(0, entry.Header.Size - entry.Header.Cb1DataSize).ToArray();
|
||||
byte[] code = entry.Code.AsSpan(0, entry.Header.Size - entry.Header.Cb1DataSize).ToArray();
|
||||
|
||||
ShaderCodeHolder shader = new ShaderCodeHolder(program, shaderProgramInfo, code);
|
||||
|
||||
@ -244,7 +244,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
return true; // Exit early, the decoding step failed.
|
||||
}
|
||||
|
||||
byte[] code = entry.Code.AsSpan().Slice(0, entry.Header.Size - entry.Header.Cb1DataSize).ToArray();
|
||||
byte[] code = entry.Code.AsSpan(0, entry.Header.Size - entry.Header.Cb1DataSize).ToArray();
|
||||
|
||||
ShaderCodeHolder shader = new ShaderCodeHolder(program, shaderProgramInfo, code);
|
||||
|
||||
@ -394,8 +394,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
}
|
||||
|
||||
// NOTE: Vertex B comes first in the shader cache.
|
||||
byte[] code = entry.Code.AsSpan().Slice(0, entry.Header.Size - entry.Header.Cb1DataSize).ToArray();
|
||||
byte[] code2 = entry.Header.SizeA != 0 ? entry.Code.AsSpan().Slice(entry.Header.Size, entry.Header.SizeA).ToArray() : null;
|
||||
byte[] code = entry.Code.AsSpan(0, entry.Header.Size - entry.Header.Cb1DataSize).ToArray();
|
||||
byte[] code2 = entry.Header.SizeA != 0 ? entry.Code.AsSpan(entry.Header.Size, entry.Header.SizeA).ToArray() : null;
|
||||
|
||||
shaders[i] = new ShaderCodeHolder(program, shaderProgramInfo, code, code2);
|
||||
|
||||
|
@ -6,15 +6,15 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
{
|
||||
struct FormatTable
|
||||
{
|
||||
private static FormatInfo[] Table;
|
||||
private static SizedInternalFormat[] TableImage;
|
||||
private static FormatInfo[] _table;
|
||||
private static SizedInternalFormat[] _tableImage;
|
||||
|
||||
static FormatTable()
|
||||
{
|
||||
int tableSize = Enum.GetNames<Format>().Length;
|
||||
|
||||
Table = new FormatInfo[tableSize];
|
||||
TableImage = new SizedInternalFormat[tableSize];
|
||||
_table = new FormatInfo[tableSize];
|
||||
_tableImage = new SizedInternalFormat[tableSize];
|
||||
|
||||
Add(Format.R8Unorm, new FormatInfo(1, true, false, All.R8, PixelFormat.Red, PixelType.UnsignedByte));
|
||||
Add(Format.R8Snorm, new FormatInfo(1, true, false, All.R8Snorm, PixelFormat.Red, PixelType.Byte));
|
||||
@ -66,7 +66,7 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
Add(Format.R32G32B32A32Sint, new FormatInfo(4, false, false, All.Rgba32i, PixelFormat.RgbaInteger, PixelType.Int));
|
||||
Add(Format.S8Uint, new FormatInfo(1, false, false, All.StencilIndex8, PixelFormat.StencilIndex, PixelType.UnsignedByte));
|
||||
Add(Format.D16Unorm, new FormatInfo(1, false, false, All.DepthComponent16, PixelFormat.DepthComponent, PixelType.UnsignedShort));
|
||||
Add(Format.D24X8Unorm, new FormatInfo(1, false, false, All.DepthComponent24, PixelFormat.DepthComponent, PixelType.UnsignedInt));
|
||||
Add(Format.S8UintD24Unorm, new FormatInfo(1, false, false, All.Depth24Stencil8, PixelFormat.DepthStencil, PixelType.UnsignedInt248));
|
||||
Add(Format.D32Float, new FormatInfo(1, false, false, All.DepthComponent32f, PixelFormat.DepthComponent, PixelType.Float));
|
||||
Add(Format.D24UnormS8Uint, new FormatInfo(1, false, false, All.Depth24Stencil8, PixelFormat.DepthStencil, PixelType.UnsignedInt248));
|
||||
Add(Format.D32FloatS8Uint, new FormatInfo(1, false, false, All.Depth32fStencil8, PixelFormat.DepthStencil, PixelType.Float32UnsignedInt248Rev));
|
||||
@ -218,22 +218,22 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
|
||||
private static void Add(Format format, FormatInfo info)
|
||||
{
|
||||
Table[(int)format] = info;
|
||||
_table[(int)format] = info;
|
||||
}
|
||||
|
||||
private static void Add(Format format, SizedInternalFormat sif)
|
||||
{
|
||||
TableImage[(int)format] = sif;
|
||||
_tableImage[(int)format] = sif;
|
||||
}
|
||||
|
||||
public static FormatInfo GetFormatInfo(Format format)
|
||||
{
|
||||
return Table[(int)format];
|
||||
return _table[(int)format];
|
||||
}
|
||||
|
||||
public static SizedInternalFormat GetImageFormat(Format format)
|
||||
{
|
||||
return TableImage[(int)format];
|
||||
return _tableImage[(int)format];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -127,14 +127,13 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
private static bool IsPackedDepthStencilFormat(Format format)
|
||||
{
|
||||
return format == Format.D24UnormS8Uint ||
|
||||
format == Format.D32FloatS8Uint;
|
||||
format == Format.D32FloatS8Uint ||
|
||||
format == Format.S8UintD24Unorm;
|
||||
}
|
||||
|
||||
private static bool IsDepthOnlyFormat(Format format)
|
||||
{
|
||||
return format == Format.D16Unorm ||
|
||||
format == Format.D24X8Unorm ||
|
||||
format == Format.D32Float;
|
||||
return format == Format.D16Unorm || format == Format.D32Float;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
149
Ryujinx.Graphics.OpenGL/Image/FormatConverter.cs
Normal file
149
Ryujinx.Graphics.OpenGL/Image/FormatConverter.cs
Normal file
@ -0,0 +1,149 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Intrinsics;
|
||||
using System.Runtime.Intrinsics.X86;
|
||||
|
||||
namespace Ryujinx.Graphics.OpenGL.Image
|
||||
{
|
||||
static class FormatConverter
|
||||
{
|
||||
public unsafe static byte[] ConvertS8D24ToD24S8(ReadOnlySpan<byte> data)
|
||||
{
|
||||
byte[] output = new byte[data.Length];
|
||||
|
||||
int start = 0;
|
||||
|
||||
if (Avx2.IsSupported)
|
||||
{
|
||||
var mask = Vector256.Create(
|
||||
(byte)3, (byte)0, (byte)1, (byte)2,
|
||||
(byte)7, (byte)4, (byte)5, (byte)6,
|
||||
(byte)11, (byte)8, (byte)9, (byte)10,
|
||||
(byte)15, (byte)12, (byte)13, (byte)14,
|
||||
(byte)19, (byte)16, (byte)17, (byte)18,
|
||||
(byte)23, (byte)20, (byte)21, (byte)22,
|
||||
(byte)27, (byte)24, (byte)25, (byte)26,
|
||||
(byte)31, (byte)28, (byte)29, (byte)30);
|
||||
|
||||
int sizeAligned = data.Length & ~31;
|
||||
|
||||
fixed (byte* pInput = data, pOutput = output)
|
||||
{
|
||||
for (uint i = 0; i < sizeAligned; i += 32)
|
||||
{
|
||||
var dataVec = Avx.LoadVector256(pInput + i);
|
||||
|
||||
dataVec = Avx2.Shuffle(dataVec, mask);
|
||||
|
||||
Avx.Store(pOutput + i, dataVec);
|
||||
}
|
||||
}
|
||||
|
||||
start = sizeAligned;
|
||||
}
|
||||
else if (Ssse3.IsSupported)
|
||||
{
|
||||
var mask = Vector128.Create(
|
||||
(byte)3, (byte)0, (byte)1, (byte)2,
|
||||
(byte)7, (byte)4, (byte)5, (byte)6,
|
||||
(byte)11, (byte)8, (byte)9, (byte)10,
|
||||
(byte)15, (byte)12, (byte)13, (byte)14);
|
||||
|
||||
int sizeAligned = data.Length & ~15;
|
||||
|
||||
fixed (byte* pInput = data, pOutput = output)
|
||||
{
|
||||
for (uint i = 0; i < sizeAligned; i += 16)
|
||||
{
|
||||
var dataVec = Sse2.LoadVector128(pInput + i);
|
||||
|
||||
dataVec = Ssse3.Shuffle(dataVec, mask);
|
||||
|
||||
Sse2.Store(pOutput + i, dataVec);
|
||||
}
|
||||
}
|
||||
|
||||
start = sizeAligned;
|
||||
}
|
||||
|
||||
var outSpan = MemoryMarshal.Cast<byte, uint>(output);
|
||||
var dataSpan = MemoryMarshal.Cast<byte, uint>(data);
|
||||
for (int i = start / sizeof(uint); i < dataSpan.Length; i++)
|
||||
{
|
||||
outSpan[i] = BitOperations.RotateLeft(dataSpan[i], 8);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
public unsafe static byte[] ConvertD24S8ToS8D24(ReadOnlySpan<byte> data)
|
||||
{
|
||||
byte[] output = new byte[data.Length];
|
||||
|
||||
int start = 0;
|
||||
|
||||
if (Avx2.IsSupported)
|
||||
{
|
||||
var mask = Vector256.Create(
|
||||
(byte)1, (byte)2, (byte)3, (byte)0,
|
||||
(byte)5, (byte)6, (byte)7, (byte)4,
|
||||
(byte)9, (byte)10, (byte)11, (byte)8,
|
||||
(byte)13, (byte)14, (byte)15, (byte)12,
|
||||
(byte)17, (byte)18, (byte)19, (byte)16,
|
||||
(byte)21, (byte)22, (byte)23, (byte)20,
|
||||
(byte)25, (byte)26, (byte)27, (byte)24,
|
||||
(byte)29, (byte)30, (byte)31, (byte)28);
|
||||
|
||||
int sizeAligned = data.Length & ~31;
|
||||
|
||||
fixed (byte* pInput = data, pOutput = output)
|
||||
{
|
||||
for (uint i = 0; i < sizeAligned; i += 32)
|
||||
{
|
||||
var dataVec = Avx.LoadVector256(pInput + i);
|
||||
|
||||
dataVec = Avx2.Shuffle(dataVec, mask);
|
||||
|
||||
Avx.Store(pOutput + i, dataVec);
|
||||
}
|
||||
}
|
||||
|
||||
start = sizeAligned;
|
||||
}
|
||||
else if (Ssse3.IsSupported)
|
||||
{
|
||||
var mask = Vector128.Create(
|
||||
(byte)1, (byte)2, (byte)3, (byte)0,
|
||||
(byte)5, (byte)6, (byte)7, (byte)4,
|
||||
(byte)9, (byte)10, (byte)11, (byte)8,
|
||||
(byte)13, (byte)14, (byte)15, (byte)12);
|
||||
|
||||
int sizeAligned = data.Length & ~15;
|
||||
|
||||
fixed (byte* pInput = data, pOutput = output)
|
||||
{
|
||||
for (uint i = 0; i < sizeAligned; i += 16)
|
||||
{
|
||||
var dataVec = Sse2.LoadVector128(pInput + i);
|
||||
|
||||
dataVec = Ssse3.Shuffle(dataVec, mask);
|
||||
|
||||
Sse2.Store(pOutput + i, dataVec);
|
||||
}
|
||||
}
|
||||
|
||||
start = sizeAligned;
|
||||
}
|
||||
|
||||
var outSpan = MemoryMarshal.Cast<byte, uint>(output);
|
||||
var dataSpan = MemoryMarshal.Cast<byte, uint>(data);
|
||||
for (int i = start / sizeof(uint); i < dataSpan.Length; i++)
|
||||
{
|
||||
outSpan[i] = BitOperations.RotateRight(dataSpan[i], 8);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
}
|
||||
}
|
@ -291,7 +291,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
||||
|
||||
private static ClearBufferMask GetMask(Format format)
|
||||
{
|
||||
if (format == Format.D24UnormS8Uint || format == Format.D32FloatS8Uint)
|
||||
if (format == Format.D24UnormS8Uint || format == Format.D32FloatS8Uint || format == Format.S8UintD24Unorm)
|
||||
{
|
||||
return ClearBufferMask.DepthBufferBit | ClearBufferMask.StencilBufferBit;
|
||||
}
|
||||
@ -311,9 +311,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
||||
|
||||
private static bool IsDepthOnly(Format format)
|
||||
{
|
||||
return format == Format.D16Unorm ||
|
||||
format == Format.D24X8Unorm ||
|
||||
format == Format.D32Float;
|
||||
return format == Format.D16Unorm || format == Format.D32Float;
|
||||
}
|
||||
|
||||
public TextureView BgraSwap(TextureView from)
|
||||
|
@ -140,9 +140,11 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
||||
size += Info.GetMipSize(level);
|
||||
}
|
||||
|
||||
ReadOnlySpan<byte> data;
|
||||
|
||||
if (HwCapabilities.UsePersistentBufferForFlush)
|
||||
{
|
||||
return _renderer.PersistentBuffers.Default.GetTextureData(this, size);
|
||||
data = _renderer.PersistentBuffers.Default.GetTextureData(this, size);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -150,8 +152,15 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
||||
|
||||
WriteTo(target);
|
||||
|
||||
return new ReadOnlySpan<byte>(target.ToPointer(), size);
|
||||
data = new ReadOnlySpan<byte>(target.ToPointer(), size);
|
||||
}
|
||||
|
||||
if (Format == Format.S8UintD24Unorm)
|
||||
{
|
||||
data = FormatConverter.ConvertD24S8ToS8D24(data);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
public unsafe ReadOnlySpan<byte> GetData(int layer, int level)
|
||||
@ -285,6 +294,11 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
||||
|
||||
public void SetData(ReadOnlySpan<byte> data)
|
||||
{
|
||||
if (Format == Format.S8UintD24Unorm)
|
||||
{
|
||||
data = FormatConverter.ConvertS8D24ToD24S8(data);
|
||||
}
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* ptr = data)
|
||||
@ -296,6 +310,11 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
||||
|
||||
public void SetData(ReadOnlySpan<byte> data, int layer, int level)
|
||||
{
|
||||
if (Format == Format.S8UintD24Unorm)
|
||||
{
|
||||
data = FormatConverter.ConvertS8D24ToD24S8(data);
|
||||
}
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* ptr = data)
|
||||
|
@ -124,7 +124,7 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
|
||||
GL.GetProgramBinary(Handle, size, out _, out BinaryFormat binFormat, data);
|
||||
|
||||
BinaryPrimitives.WriteInt32LittleEndian(data.AsSpan().Slice(size, 4), (int)binFormat);
|
||||
BinaryPrimitives.WriteInt32LittleEndian(data.AsSpan(size, 4), (int)binFormat);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
@ -308,7 +308,8 @@ namespace Ryujinx.Graphics.Shader.Decoders
|
||||
int attr = offset + elemIndex * 4;
|
||||
if (attr >= AttributeConsts.UserAttributeBase && attr < AttributeConsts.UserAttributeEnd)
|
||||
{
|
||||
int index = (attr - AttributeConsts.UserAttributeBase) / 16;
|
||||
int userAttr = attr - AttributeConsts.UserAttributeBase;
|
||||
int index = userAttr / 16;
|
||||
|
||||
if (isStore)
|
||||
{
|
||||
@ -316,7 +317,7 @@ namespace Ryujinx.Graphics.Shader.Decoders
|
||||
}
|
||||
else
|
||||
{
|
||||
config.SetInputUserAttribute(index, perPatch);
|
||||
config.SetInputUserAttribute(index, (userAttr >> 2) & 3, perPatch);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -54,6 +54,11 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
private int _nextUsedInputAttributes;
|
||||
private int _thisUsedInputAttributes;
|
||||
|
||||
public UInt128 NextInputAttributesComponents { get; private set; }
|
||||
public UInt128 ThisInputAttributesComponents { get; private set; }
|
||||
public UInt128 NextInputAttributesPerPatchComponents { get; private set; }
|
||||
public UInt128 ThisInputAttributesPerPatchComponents { get; private set; }
|
||||
|
||||
private int _usedConstantBuffers;
|
||||
private int _usedStorageBuffers;
|
||||
private int _usedStorageBuffersWrite;
|
||||
@ -227,11 +232,12 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
UsedOutputAttributes |= 1 << index;
|
||||
}
|
||||
|
||||
public void SetInputUserAttribute(int index, bool perPatch)
|
||||
public void SetInputUserAttribute(int index, int component, bool perPatch)
|
||||
{
|
||||
if (perPatch)
|
||||
{
|
||||
UsedInputAttributesPerPatch |= 1 << index;
|
||||
ThisInputAttributesPerPatchComponents |= UInt128.Pow2(index * 4 + component);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -239,6 +245,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
|
||||
UsedInputAttributes |= mask;
|
||||
_thisUsedInputAttributes |= mask;
|
||||
ThisInputAttributesComponents |= UInt128.Pow2(index * 4 + component);
|
||||
}
|
||||
}
|
||||
|
||||
@ -256,6 +263,8 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
|
||||
public void MergeFromtNextStage(ShaderConfig config)
|
||||
{
|
||||
NextInputAttributesComponents = config.ThisInputAttributesComponents;
|
||||
NextInputAttributesPerPatchComponents = config.ThisInputAttributesPerPatchComponents;
|
||||
NextUsesFixedFuncAttributes = config.UsedFeatures.HasFlag(FeatureFlags.FixedFuncAttr);
|
||||
MergeOutputUserAttributes(config.UsedInputAttributes, config.UsedInputAttributesPerPatch);
|
||||
}
|
||||
@ -319,6 +328,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
public void SetAllInputUserAttributes()
|
||||
{
|
||||
UsedInputAttributes |= Constants.AllAttributesMask;
|
||||
ThisInputAttributesComponents |= ~UInt128.Zero >> (128 - Constants.MaxAttributes * 4);
|
||||
}
|
||||
|
||||
public void SetAllOutputUserAttributes()
|
||||
|
@ -214,24 +214,31 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
InitializeOutput(context, AttributeConsts.PositionX, perPatch: false);
|
||||
}
|
||||
|
||||
int usedAttributes = context.Config.UsedOutputAttributes;
|
||||
while (usedAttributes != 0)
|
||||
UInt128 usedAttributes = context.Config.NextInputAttributesComponents;
|
||||
while (usedAttributes != UInt128.Zero)
|
||||
{
|
||||
int index = BitOperations.TrailingZeroCount(usedAttributes);
|
||||
int index = usedAttributes.TrailingZeroCount();
|
||||
int vecIndex = index / 4;
|
||||
|
||||
InitializeOutput(context, AttributeConsts.UserAttributeBase + index * 16, perPatch: false);
|
||||
usedAttributes &= ~UInt128.Pow2(index);
|
||||
|
||||
usedAttributes &= ~(1 << index);
|
||||
// We don't need to initialize passthrough attributes.
|
||||
if ((context.Config.PassthroughAttributes & (1 << vecIndex)) != 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
InitializeOutputComponent(context, AttributeConsts.UserAttributeBase + index * 4, perPatch: false);
|
||||
}
|
||||
|
||||
int usedAttributesPerPatch = context.Config.UsedOutputAttributesPerPatch;
|
||||
while (usedAttributesPerPatch != 0)
|
||||
UInt128 usedAttributesPerPatch = context.Config.NextInputAttributesPerPatchComponents;
|
||||
while (usedAttributesPerPatch != UInt128.Zero)
|
||||
{
|
||||
int index = BitOperations.TrailingZeroCount(usedAttributesPerPatch);
|
||||
int index = usedAttributesPerPatch.TrailingZeroCount();
|
||||
|
||||
InitializeOutput(context, AttributeConsts.UserAttributeBase + index * 16, perPatch: true);
|
||||
InitializeOutputComponent(context, AttributeConsts.UserAttributeBase + index * 4, perPatch: true);
|
||||
|
||||
usedAttributesPerPatch &= ~(1 << index);
|
||||
usedAttributesPerPatch &= ~UInt128.Pow2(index);
|
||||
}
|
||||
|
||||
if (config.NextUsesFixedFuncAttributes)
|
||||
@ -260,6 +267,12 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
}
|
||||
}
|
||||
|
||||
private static void InitializeOutputComponent(EmitterContext context, int attrOffset, bool perPatch)
|
||||
{
|
||||
int c = (attrOffset >> 2) & 3;
|
||||
context.Copy(perPatch ? AttributePerPatch(attrOffset) : Attribute(attrOffset), ConstF(c == 3 ? 1f : 0f));
|
||||
}
|
||||
|
||||
private static void EmitOps(EmitterContext context, Block block)
|
||||
{
|
||||
for (int opIndex = 0; opIndex < block.OpCodes.Count; opIndex++)
|
||||
|
112
Ryujinx.Graphics.Shader/Translation/UInt128.cs
Normal file
112
Ryujinx.Graphics.Shader/Translation/UInt128.cs
Normal file
@ -0,0 +1,112 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Ryujinx.Graphics.Shader.Translation
|
||||
{
|
||||
struct UInt128 : IEquatable<UInt128>
|
||||
{
|
||||
public static UInt128 Zero => new UInt128() { _v0 = 0, _v1 = 0 };
|
||||
|
||||
private ulong _v0;
|
||||
private ulong _v1;
|
||||
|
||||
public UInt128(ulong low, ulong high)
|
||||
{
|
||||
_v0 = low;
|
||||
_v1 = high;
|
||||
}
|
||||
|
||||
public int TrailingZeroCount()
|
||||
{
|
||||
int count = BitOperations.TrailingZeroCount(_v0);
|
||||
if (count == 64)
|
||||
{
|
||||
count += BitOperations.TrailingZeroCount(_v1);
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
public static UInt128 Pow2(int x)
|
||||
{
|
||||
if (x >= 64)
|
||||
{
|
||||
return new UInt128(0, 1UL << (x - 64));
|
||||
}
|
||||
|
||||
return new UInt128(1UL << x, 0);
|
||||
}
|
||||
|
||||
public static UInt128 operator ~(UInt128 x)
|
||||
{
|
||||
return new UInt128(~x._v0, ~x._v1);
|
||||
}
|
||||
|
||||
public static UInt128 operator &(UInt128 x, UInt128 y)
|
||||
{
|
||||
return new UInt128(x._v0 & y._v0, x._v1 & y._v1);
|
||||
}
|
||||
|
||||
public static UInt128 operator |(UInt128 x, UInt128 y)
|
||||
{
|
||||
return new UInt128(x._v0 | y._v0, x._v1 | y._v1);
|
||||
}
|
||||
|
||||
public static UInt128 operator <<(UInt128 x, int shift)
|
||||
{
|
||||
if (shift == 0)
|
||||
{
|
||||
return new UInt128(x._v0, x._v1);
|
||||
}
|
||||
else if (shift >= 64)
|
||||
{
|
||||
return new UInt128(0, x._v0 << (shift - 64));
|
||||
}
|
||||
|
||||
ulong shiftOut = x._v0 >> (64 - shift);
|
||||
|
||||
return new UInt128(x._v0 << shift, (x._v1 << shift) | shiftOut);
|
||||
}
|
||||
|
||||
public static UInt128 operator >>(UInt128 x, int shift)
|
||||
{
|
||||
if (shift == 0)
|
||||
{
|
||||
return new UInt128(x._v0, x._v1);
|
||||
}
|
||||
else if (shift >= 64)
|
||||
{
|
||||
return new UInt128(x._v1 >> (shift - 64), 0);
|
||||
}
|
||||
|
||||
ulong shiftOut = x._v1 & ((1UL << shift) - 1);
|
||||
|
||||
return new UInt128((x._v0 >> shift) | (shiftOut << (64 - shift)), x._v1 >> shift);
|
||||
}
|
||||
|
||||
public static bool operator ==(UInt128 x, UInt128 y)
|
||||
{
|
||||
return x.Equals(y);
|
||||
}
|
||||
|
||||
public static bool operator !=(UInt128 x, UInt128 y)
|
||||
{
|
||||
return !x.Equals(y);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is UInt128 other && Equals(other);
|
||||
}
|
||||
|
||||
public bool Equals(UInt128 other)
|
||||
{
|
||||
return _v0 == other._v0 && _v1 == other._v1;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(_v0, _v1);
|
||||
}
|
||||
}
|
||||
}
|
@ -84,7 +84,10 @@ namespace Ryujinx.HLE.HOS
|
||||
|
||||
MetaLoader metaData = ReadNpdm(codeFs);
|
||||
|
||||
_device.Configuration.VirtualFileSystem.ModLoader.CollectMods(new[] { TitleId }, _device.Configuration.VirtualFileSystem.ModLoader.GetModsBasePath());
|
||||
_device.Configuration.VirtualFileSystem.ModLoader.CollectMods(
|
||||
new[] { TitleId },
|
||||
_device.Configuration.VirtualFileSystem.ModLoader.GetModsBasePath(),
|
||||
_device.Configuration.VirtualFileSystem.ModLoader.GetSdModsBasePath());
|
||||
|
||||
if (TitleId != 0)
|
||||
{
|
||||
@ -388,7 +391,10 @@ namespace Ryujinx.HLE.HOS
|
||||
|
||||
MetaLoader metaData = ReadNpdm(codeFs);
|
||||
|
||||
_device.Configuration.VirtualFileSystem.ModLoader.CollectMods(_device.Configuration.ContentManager.GetAocTitleIds().Prepend(TitleId), _device.Configuration.VirtualFileSystem.ModLoader.GetModsBasePath());
|
||||
_device.Configuration.VirtualFileSystem.ModLoader.CollectMods(
|
||||
_device.Configuration.ContentManager.GetAocTitleIds().Prepend(TitleId),
|
||||
_device.Configuration.VirtualFileSystem.ModLoader.GetModsBasePath(),
|
||||
_device.Configuration.VirtualFileSystem.ModLoader.GetSdModsBasePath());
|
||||
|
||||
if (controlNca != null)
|
||||
{
|
||||
@ -481,14 +487,14 @@ namespace Ryujinx.HLE.HOS
|
||||
|
||||
if (result.IsSuccess() && bytesRead == controlData.ByteSpan.Length)
|
||||
{
|
||||
titleName = controlData.Value.Titles[(int)device.System.State.DesiredTitleLanguage].Name.ToString();
|
||||
titleName = controlData.Value.Title[(int)device.System.State.DesiredTitleLanguage].NameString.ToString();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(titleName))
|
||||
{
|
||||
titleName = controlData.Value.Titles.ToArray().FirstOrDefault(x => x.Name[0] != 0).Name.ToString();
|
||||
titleName = controlData.Value.Title.ItemsRo.ToArray().FirstOrDefault(x => x.Name[0] != 0).NameString.ToString();
|
||||
}
|
||||
|
||||
displayVersion = controlData.Value.DisplayVersion.ToString();
|
||||
displayVersion = controlData.Value.DisplayVersionString.ToString();
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -615,20 +621,20 @@ namespace Ryujinx.HLE.HOS
|
||||
|
||||
ref ApplicationControlProperty nacp = ref ControlData.Value;
|
||||
|
||||
programInfo.Name = nacp.Titles[(int)_device.System.State.DesiredTitleLanguage].Name.ToString();
|
||||
programInfo.Name = nacp.Title[(int)_device.System.State.DesiredTitleLanguage].NameString.ToString();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(programInfo.Name))
|
||||
{
|
||||
programInfo.Name = nacp.Titles.ToArray().FirstOrDefault(x => x.Name[0] != 0).Name.ToString();
|
||||
programInfo.Name = nacp.Title.ItemsRo.ToArray().FirstOrDefault(x => x.Name[0] != 0).NameString.ToString();
|
||||
}
|
||||
|
||||
if (nacp.PresenceGroupId != 0)
|
||||
{
|
||||
programInfo.ProgramId = nacp.PresenceGroupId;
|
||||
}
|
||||
else if (nacp.SaveDataOwnerId.Value != 0)
|
||||
else if (nacp.SaveDataOwnerId != 0)
|
||||
{
|
||||
programInfo.ProgramId = nacp.SaveDataOwnerId.Value;
|
||||
programInfo.ProgramId = nacp.SaveDataOwnerId;
|
||||
}
|
||||
else if (nacp.AddOnContentBaseId != 0)
|
||||
{
|
||||
@ -776,14 +782,14 @@ namespace Ryujinx.HLE.HOS
|
||||
// The set sizes don't actually matter as long as they're non-zero because we use directory savedata.
|
||||
control.UserAccountSaveDataSize = 0x4000;
|
||||
control.UserAccountSaveDataJournalSize = 0x4000;
|
||||
control.SaveDataOwnerId = applicationId;
|
||||
control.SaveDataOwnerId = applicationId.Value;
|
||||
|
||||
Logger.Warning?.Print(LogClass.Application,
|
||||
"No control file was found for this game. Using a dummy one instead. This may cause inaccuracies in some games.");
|
||||
}
|
||||
|
||||
HorizonClient hos = _device.System.LibHacHorizonManager.RyujinxClient;
|
||||
Result resultCode = hos.Fs.EnsureApplicationCacheStorage(out _, out _, applicationId, ref control);
|
||||
Result resultCode = hos.Fs.EnsureApplicationCacheStorage(out _, out _, applicationId, in control);
|
||||
|
||||
if (resultCode.IsFailure())
|
||||
{
|
||||
@ -792,7 +798,7 @@ namespace Ryujinx.HLE.HOS
|
||||
return resultCode;
|
||||
}
|
||||
|
||||
resultCode = EnsureApplicationSaveData(hos.Fs, out _, applicationId, ref control, ref user);
|
||||
resultCode = hos.Fs.EnsureApplicationSaveData(out _, applicationId, in control, in user);
|
||||
|
||||
if (resultCode.IsFailure())
|
||||
{
|
||||
|
@ -76,6 +76,7 @@ namespace Ryujinx.HLE.HOS
|
||||
internal ServerBase BsdServer { get; private set; }
|
||||
internal ServerBase AudRenServer { get; private set; }
|
||||
internal ServerBase AudOutServer { get; private set; }
|
||||
internal ServerBase FsServer { get; private set; }
|
||||
internal ServerBase HidServer { get; private set; }
|
||||
internal ServerBase NvDrvServer { get; private set; }
|
||||
internal ServerBase TimeServer { get; private set; }
|
||||
@ -298,6 +299,7 @@ namespace Ryujinx.HLE.HOS
|
||||
BsdServer = new ServerBase(KernelContext, "BsdServer");
|
||||
AudRenServer = new ServerBase(KernelContext, "AudioRendererServer");
|
||||
AudOutServer = new ServerBase(KernelContext, "AudioOutServer");
|
||||
FsServer = new ServerBase(KernelContext, "FsServer");
|
||||
HidServer = new ServerBase(KernelContext, "HidServer");
|
||||
NvDrvServer = new ServerBase(KernelContext, "NvservicesServer");
|
||||
TimeServer = new ServerBase(KernelContext, "TimeServer");
|
||||
@ -464,7 +466,7 @@ namespace Ryujinx.HLE.HOS
|
||||
|
||||
AudioRendererManager.Dispose();
|
||||
|
||||
LibHacHorizonManager.AmClient.Fs.UnregisterProgram(LibHacHorizonManager.ApplicationClient.Os.GetCurrentProcessId().Value);
|
||||
LibHacHorizonManager.PmClient.Fs.UnregisterProgram(LibHacHorizonManager.ApplicationClient.Os.GetCurrentProcessId().Value).ThrowIfFailure();
|
||||
|
||||
KernelContext.Dispose();
|
||||
}
|
||||
|
@ -751,7 +751,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
||||
{
|
||||
KThread currentThread = KernelStatic.GetCurrentThread();
|
||||
|
||||
if (currentThread.Owner != null &&
|
||||
if (currentThread.Context.Running &&
|
||||
currentThread.Owner != null &&
|
||||
currentThread.GetUserDisableCount() != 0 &&
|
||||
currentThread.Owner.PinnedThreads[currentThread.CurrentCore] == null)
|
||||
{
|
||||
|
@ -658,10 +658,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||
|
||||
private static uint GetPsr(ARMeilleure.State.ExecutionContext context)
|
||||
{
|
||||
return (context.GetPstateFlag(ARMeilleure.State.PState.NFlag) ? (1U << (int)ARMeilleure.State.PState.NFlag) : 0U) |
|
||||
(context.GetPstateFlag(ARMeilleure.State.PState.ZFlag) ? (1U << (int)ARMeilleure.State.PState.ZFlag) : 0U) |
|
||||
(context.GetPstateFlag(ARMeilleure.State.PState.CFlag) ? (1U << (int)ARMeilleure.State.PState.CFlag) : 0U) |
|
||||
(context.GetPstateFlag(ARMeilleure.State.PState.VFlag) ? (1U << (int)ARMeilleure.State.PState.VFlag) : 0U);
|
||||
return context.Pstate & 0xFF0FFE20;
|
||||
}
|
||||
|
||||
private ThreadContext GetCurrentContext()
|
||||
@ -1371,7 +1368,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||
|
||||
PreferredCore = _originalPreferredCore;
|
||||
AffinityMask = _originalAffinityMask;
|
||||
|
||||
|
||||
if (AffinityMask != affinityMask)
|
||||
{
|
||||
if ((AffinityMask & 1UL << ActiveCore) != 0)
|
||||
|
@ -24,6 +24,7 @@ namespace Ryujinx.HLE.HOS
|
||||
public HorizonClient BcatClient { get; private set; }
|
||||
public HorizonClient FsClient { get; private set; }
|
||||
public HorizonClient NsClient { get; private set; }
|
||||
public HorizonClient PmClient { get; private set; }
|
||||
public HorizonClient SdbClient { get; private set; }
|
||||
|
||||
private SharedRef<LibHacIReader> _arpIReader;
|
||||
@ -65,6 +66,7 @@ namespace Ryujinx.HLE.HOS
|
||||
|
||||
public void InitializeSystemClients()
|
||||
{
|
||||
PmClient = Server.CreatePrivilegedHorizonClient();
|
||||
AccountClient = Server.CreateHorizonClient(new ProgramLocation(SystemProgramId.Account, StorageId.BuiltInSystem), AccountFsPermissions);
|
||||
AmClient = Server.CreateHorizonClient(new ProgramLocation(SystemProgramId.Am, StorageId.BuiltInSystem), AmFsPermissions);
|
||||
NsClient = Server.CreateHorizonClient(new ProgramLocation(SystemProgramId.Ns, StorageId.BuiltInSystem), NsFsPermissions);
|
||||
|
@ -136,7 +136,8 @@ namespace Ryujinx.HLE.HOS
|
||||
|
||||
private static bool StrEquals(string s1, string s2) => string.Equals(s1, s2, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
public string GetModsBasePath() => EnsureBaseDirStructure(AppDataManager.GetModsPath());
|
||||
public string GetModsBasePath() => EnsureBaseDirStructure(AppDataManager.GetModsPath());
|
||||
public string GetSdModsBasePath() => EnsureBaseDirStructure(AppDataManager.GetSdModsPath());
|
||||
|
||||
private string EnsureBaseDirStructure(string modsBasePath)
|
||||
{
|
||||
@ -695,7 +696,7 @@ namespace Ryujinx.HLE.HOS
|
||||
|
||||
var buildIds = programs.Select(p => p switch
|
||||
{
|
||||
NsoExecutable nso => BitConverter.ToString(nso.BuildId.Bytes.ToArray()).Replace("-", "").TrimEnd('0'),
|
||||
NsoExecutable nso => BitConverter.ToString(nso.BuildId.ItemsRo.ToArray()).Replace("-", "").TrimEnd('0'),
|
||||
NroExecutable nro => BitConverter.ToString(nro.Header.BuildId).Replace("-", "").TrimEnd('0'),
|
||||
_ => string.Empty
|
||||
}).ToList();
|
||||
|
@ -160,7 +160,7 @@ namespace Ryujinx.HLE.HOS
|
||||
|
||||
var buildIds = executables.Select(e => (e switch
|
||||
{
|
||||
NsoExecutable nso => BitConverter.ToString(nso.BuildId.Bytes.ToArray()),
|
||||
NsoExecutable nso => BitConverter.ToString(nso.BuildId.ItemsRo.ToArray()),
|
||||
NroExecutable nro => BitConverter.ToString(nro.Header.BuildId),
|
||||
_ => ""
|
||||
}).Replace("-", "").ToUpper());
|
||||
|
@ -3,6 +3,7 @@ using LibHac.Common;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Fs.Shim;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Logging;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
@ -25,7 +26,7 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
|
||||
|
||||
public UserProfile LastOpenedUser { get; private set; }
|
||||
|
||||
public AccountManager(HorizonClient horizonClient)
|
||||
public AccountManager(HorizonClient horizonClient, string initialProfileName = null)
|
||||
{
|
||||
_horizonClient = horizonClient;
|
||||
|
||||
@ -43,7 +44,14 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
|
||||
}
|
||||
else
|
||||
{
|
||||
OpenUser(_accountSaveDataManager.LastOpened);
|
||||
UserId commandLineUserProfileOverride = default;
|
||||
if (!string.IsNullOrEmpty(initialProfileName))
|
||||
{
|
||||
commandLineUserProfileOverride = _profiles.Values.FirstOrDefault(x => x.Name == initialProfileName)?.UserId ?? default;
|
||||
if (commandLineUserProfileOverride.IsNull)
|
||||
Logger.Warning?.Print(LogClass.Application, $"The command line specified profile named '{initialProfileName}' was not found");
|
||||
}
|
||||
OpenUser(commandLineUserProfileOverride.IsNull ? _accountSaveDataManager.LastOpened : commandLineUserProfileOverride);
|
||||
}
|
||||
}
|
||||
|
||||
@ -168,8 +176,8 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
|
||||
|
||||
private void DeleteSaveData(UserId userId)
|
||||
{
|
||||
SaveDataFilter saveDataFilter = new SaveDataFilter();
|
||||
saveDataFilter.SetUserId(new LibHac.Fs.UserId((ulong)userId.High, (ulong)userId.Low));
|
||||
var saveDataFilter = SaveDataFilter.Make(programId: default, saveType: default,
|
||||
new LibHac.Fs.UserId((ulong)userId.High, (ulong)userId.Low), saveDataId: default, index: default);
|
||||
|
||||
using var saveDataIterator = new UniqueRef<SaveDataIterator>();
|
||||
|
||||
|
@ -169,7 +169,7 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
|
||||
// TODO: Account actually calls nn::arp::detail::IReader::GetApplicationControlProperty() with the current Pid and store the result (NACP file) internally.
|
||||
// But since we use LibHac and we load one Application at a time, it's not necessary.
|
||||
|
||||
context.ResponseData.Write(context.Device.Application.ControlData.Value.UserAccountSwitchLock);
|
||||
context.ResponseData.Write((byte)context.Device.Application.ControlData.Value.UserAccountSwitchLock);
|
||||
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceAcc);
|
||||
|
||||
|
@ -131,7 +131,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
|
||||
}
|
||||
|
||||
HorizonClient hos = context.Device.System.LibHacHorizonManager.AmClient;
|
||||
Result result = EnsureApplicationSaveData(hos.Fs, out long requiredSize, applicationId, ref control, ref userId);
|
||||
Result result = hos.Fs.EnsureApplicationSaveData(out long requiredSize, applicationId, in control, in userId);
|
||||
|
||||
context.ResponseData.Write(requiredSize);
|
||||
|
||||
@ -148,7 +148,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
|
||||
// TODO: When above calls are implemented, switch to using ns:am
|
||||
|
||||
long desiredLanguageCode = context.Device.System.State.DesiredLanguageCode;
|
||||
int supportedLanguages = (int)context.Device.Application.ControlData.Value.SupportedLanguages;
|
||||
int supportedLanguages = (int)context.Device.Application.ControlData.Value.SupportedLanguageFlag;
|
||||
int firstSupported = BitOperations.TrailingZeroCount(supportedLanguages);
|
||||
|
||||
if (firstSupported > (int)SystemState.TitleLanguage.BrazilianPortuguese)
|
||||
@ -190,7 +190,6 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
|
||||
// GetDisplayVersion() -> nn::oe::DisplayVersion
|
||||
public ResultCode GetDisplayVersion(ServiceCtx context)
|
||||
{
|
||||
// This should work as DisplayVersion U8Span always gives a 0x10 size byte array.
|
||||
// If an NACP isn't found, the buffer will be all '\0' which seems to be the correct implementation.
|
||||
context.ResponseData.Write(context.Device.Application.ControlData.Value.DisplayVersion);
|
||||
|
||||
@ -252,7 +251,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
|
||||
BlitStruct<ApplicationControlProperty> controlHolder = context.Device.Application.ControlData;
|
||||
|
||||
Result result = _horizon.Fs.CreateApplicationCacheStorage(out long requiredSize,
|
||||
out CacheStorageTargetMedia storageTarget, applicationId, ref controlHolder.Value, index, saveSize,
|
||||
out CacheStorageTargetMedia storageTarget, applicationId, in controlHolder.Value, index, saveSize,
|
||||
journalSize);
|
||||
|
||||
if (result.IsFailure()) return (ResultCode)result.Value;
|
||||
|
@ -16,7 +16,7 @@ namespace Ryujinx.HLE.HOS.Services.Arp
|
||||
{
|
||||
launchProperty = new LibHac.Arp.ApplicationLaunchProperty
|
||||
{
|
||||
BaseStorageId = StorageId.BuiltInUser,
|
||||
StorageId = StorageId.BuiltInUser,
|
||||
ApplicationId = ApplicationId
|
||||
};
|
||||
|
||||
@ -29,7 +29,7 @@ namespace Ryujinx.HLE.HOS.Services.Arp
|
||||
{
|
||||
launchProperty = new LibHac.Arp.ApplicationLaunchProperty
|
||||
{
|
||||
BaseStorageId = StorageId.BuiltInUser,
|
||||
StorageId = StorageId.BuiltInUser,
|
||||
ApplicationId = applicationId
|
||||
};
|
||||
|
||||
|
@ -7,6 +7,8 @@ namespace Ryujinx.HLE.HOS.Services
|
||||
{
|
||||
private int _disposeState;
|
||||
|
||||
public DisposableIpcService(ServerBase server = null) : base(server) { }
|
||||
|
||||
protected abstract void Dispose(bool isDisposing);
|
||||
|
||||
public void Dispose()
|
||||
|
@ -132,22 +132,10 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
|
||||
}
|
||||
}
|
||||
|
||||
public static Result ReadFsPath(out FsPath path, ServiceCtx context, int index = 0)
|
||||
{
|
||||
ulong position = context.Request.PtrBuff[index].Position;
|
||||
ulong size = context.Request.PtrBuff[index].Size;
|
||||
|
||||
byte[] pathBytes = new byte[size];
|
||||
|
||||
context.Memory.Read(position, pathBytes);
|
||||
|
||||
return FsPath.FromSpan(out path, pathBytes);
|
||||
}
|
||||
|
||||
public static ref readonly FspPath GetFspPath(ServiceCtx context, int index = 0)
|
||||
{
|
||||
ulong position = (ulong)context.Request.PtrBuff[index].Position;
|
||||
ulong size = (ulong)context.Request.PtrBuff[index].Size;
|
||||
ulong position = context.Request.PtrBuff[index].Position;
|
||||
ulong size = context.Request.PtrBuff[index].Size;
|
||||
|
||||
ReadOnlySpan<byte> buffer = context.Memory.GetSpan(position, (int)size);
|
||||
ReadOnlySpan<FspPath> fspBuffer = MemoryMarshal.Cast<byte, FspPath>(buffer);
|
||||
@ -157,8 +145,8 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
|
||||
|
||||
public static ref readonly LibHac.FsSrv.Sf.Path GetSfPath(ServiceCtx context, int index = 0)
|
||||
{
|
||||
ulong position = (ulong)context.Request.PtrBuff[index].Position;
|
||||
ulong size = (ulong)context.Request.PtrBuff[index].Size;
|
||||
ulong position = context.Request.PtrBuff[index].Position;
|
||||
ulong size = context.Request.PtrBuff[index].Size;
|
||||
|
||||
ReadOnlySpan<byte> buffer = context.Memory.GetSpan(position, (int)size);
|
||||
ReadOnlySpan<LibHac.FsSrv.Sf.Path> pathBuffer = MemoryMarshal.Cast<byte, LibHac.FsSrv.Sf.Path>(buffer);
|
||||
|
@ -1,6 +1,6 @@
|
||||
using LibHac;
|
||||
using LibHac.Common;
|
||||
using LibHac.FsSrv;
|
||||
using LibHac.Fs;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Fs
|
||||
{
|
||||
|
@ -28,7 +28,7 @@ namespace Ryujinx.HLE.HOS.Services.Fs
|
||||
{
|
||||
private SharedRef<LibHac.FsSrv.Sf.IFileSystemProxy> _baseFileSystemProxy;
|
||||
|
||||
public IFileSystemProxy(ServiceCtx context)
|
||||
public IFileSystemProxy(ServiceCtx context) : base(context.Device.System.FsServer)
|
||||
{
|
||||
var applicationClient = context.Device.System.LibHacHorizonManager.ApplicationClient;
|
||||
_baseFileSystemProxy = applicationClient.Fs.Impl.GetFileSystemProxyServiceObject();
|
||||
|
@ -14,6 +14,9 @@ namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService
|
||||
{
|
||||
private GeneralServiceDetail _generalServiceDetail;
|
||||
|
||||
private IPInterfaceProperties _targetPropertiesCache = null;
|
||||
private UnicastIPAddressInformation _targetAddressInfoCache = null;
|
||||
|
||||
public IGeneralService()
|
||||
{
|
||||
_generalServiceDetail = new GeneralServiceDetail
|
||||
@ -22,6 +25,8 @@ namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService
|
||||
IsAnyInternetRequestAccepted = true // NOTE: Why not accept any internet request?
|
||||
};
|
||||
|
||||
NetworkChange.NetworkAddressChanged += new NetworkAddressChangedEventHandler(LocalInterfaceCacheHandler);
|
||||
|
||||
GeneralServiceManager.Add(_generalServiceDetail);
|
||||
}
|
||||
|
||||
@ -165,6 +170,11 @@ namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService
|
||||
return (null, null);
|
||||
}
|
||||
|
||||
if (_targetPropertiesCache != null && _targetAddressInfoCache != null)
|
||||
{
|
||||
return (_targetPropertiesCache, _targetAddressInfoCache);
|
||||
}
|
||||
|
||||
IPInterfaceProperties targetProperties = null;
|
||||
UnicastIPAddressInformation targetAddressInfo = null;
|
||||
|
||||
@ -194,13 +204,26 @@ namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService
|
||||
}
|
||||
}
|
||||
|
||||
_targetPropertiesCache = targetProperties;
|
||||
_targetAddressInfoCache = targetAddressInfo;
|
||||
|
||||
return (targetProperties, targetAddressInfo);
|
||||
}
|
||||
|
||||
private void LocalInterfaceCacheHandler(object sender, EventArgs e)
|
||||
{
|
||||
Logger.Info?.Print(LogClass.ServiceNifm, $"NetworkAddress changed, invalidating cached data.");
|
||||
|
||||
_targetPropertiesCache = null;
|
||||
_targetAddressInfoCache = null;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
if (isDisposing)
|
||||
{
|
||||
NetworkChange.NetworkAddressChanged -= LocalInterfaceCacheHandler;
|
||||
|
||||
GeneralServiceManager.Remove(_generalServiceDetail.ClientId);
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,24 @@
|
||||
namespace Ryujinx.HLE.HOS.Services.Nim.Ntc
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.HLE.HOS.Services.Nim.Ntc.StaticService;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Nim.Ntc
|
||||
{
|
||||
[Service("ntc")]
|
||||
class IStaticService : IpcService
|
||||
{
|
||||
public IStaticService(ServiceCtx context) { }
|
||||
|
||||
[CommandHipc(0)]
|
||||
// OpenEnsureNetworkClockAvailabilityService(u64) -> object<nn::ntc::detail::service::IEnsureNetworkClockAvailabilityService>
|
||||
public ResultCode CreateAsyncInterface(ServiceCtx context)
|
||||
{
|
||||
ulong unknown = context.RequestData.ReadUInt64();
|
||||
|
||||
MakeObject(context, new IEnsureNetworkClockAvailabilityService(context));
|
||||
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceNtc, new { unknown });
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.HLE.HOS.Ipc;
|
||||
using Ryujinx.HLE.HOS.Kernel.Common;
|
||||
using Ryujinx.HLE.HOS.Kernel.Threading;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Nim.Ntc.StaticService
|
||||
{
|
||||
class IEnsureNetworkClockAvailabilityService : IpcService
|
||||
{
|
||||
private KEvent _finishNotificationEvent;
|
||||
private ResultCode _taskResultCode;
|
||||
|
||||
public IEnsureNetworkClockAvailabilityService(ServiceCtx context)
|
||||
{
|
||||
_finishNotificationEvent = new KEvent(context.Device.System.KernelContext);
|
||||
_taskResultCode = ResultCode.Success;
|
||||
|
||||
// NOTE: The service starts a thread that polls Nintendo NTP server and syncs the time with it.
|
||||
// Additionnally it gets and uses some settings too:
|
||||
// autonomic_correction_interval_seconds, autonomic_correction_failed_retry_interval_seconds,
|
||||
// autonomic_correction_immediate_try_count_max, autonomic_correction_immediate_try_interval_milliseconds
|
||||
}
|
||||
|
||||
[CommandHipc(0)]
|
||||
// StartTask()
|
||||
public ResultCode StartTask(ServiceCtx context)
|
||||
{
|
||||
if (!context.Device.Configuration.EnableInternetAccess)
|
||||
{
|
||||
return (ResultCode)Time.ResultCode.NetworkTimeNotAvailable;
|
||||
}
|
||||
|
||||
// NOTE: Since we don't support the Nintendo NTP server, we can signal the event now to confirm the update task is done.
|
||||
_finishNotificationEvent.ReadableEvent.Signal();
|
||||
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceNtc);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandHipc(1)]
|
||||
// GetFinishNotificationEvent() -> handle<copy>
|
||||
public ResultCode GetFinishNotificationEvent(ServiceCtx context)
|
||||
{
|
||||
if (context.Process.HandleTable.GenerateHandle(_finishNotificationEvent.ReadableEvent, out int finishNotificationEventHandle) != KernelResult.Success)
|
||||
{
|
||||
throw new InvalidOperationException("Out of handles!");
|
||||
}
|
||||
|
||||
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(finishNotificationEventHandle);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandHipc(2)]
|
||||
// GetResult()
|
||||
public ResultCode GetResult(ServiceCtx context)
|
||||
{
|
||||
return _taskResultCode;
|
||||
}
|
||||
|
||||
[CommandHipc(3)]
|
||||
// Cancel()
|
||||
public ResultCode Cancel(ServiceCtx context)
|
||||
{
|
||||
// NOTE: The update task should be canceled here.
|
||||
_finishNotificationEvent.ReadableEvent.Signal();
|
||||
|
||||
_taskResultCode = (ResultCode)Time.ResultCode.NetworkTimeTaskCanceled;
|
||||
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceNtc);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,13 +1,15 @@
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Olsc
|
||||
{
|
||||
[Service("olsc:u")] // 10.0.0+
|
||||
class IOlscServiceForApplication : IpcService
|
||||
{
|
||||
private bool _initialized;
|
||||
private bool _initialized;
|
||||
private Dictionary<UserId, bool> _saveDataBackupSettingDatabase;
|
||||
|
||||
public IOlscServiceForApplication(ServiceCtx context) { }
|
||||
|
||||
@ -16,7 +18,9 @@ namespace Ryujinx.HLE.HOS.Services.Olsc
|
||||
public ResultCode Initialize(ServiceCtx context)
|
||||
{
|
||||
// NOTE: Service call arp:r GetApplicationInstanceUnregistrationNotifier with the pid and initialize some internal struct.
|
||||
// Since we will not support online savedata backup. It's fine to stub it for now.
|
||||
// Since we will not support online savedata backup, it's fine to stub it for now.
|
||||
|
||||
_saveDataBackupSettingDatabase = new Dictionary<UserId, bool>();
|
||||
|
||||
_initialized = true;
|
||||
|
||||
@ -25,12 +29,11 @@ namespace Ryujinx.HLE.HOS.Services.Olsc
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandHipc(14)]
|
||||
// SetSaveDataBackupSettingEnabled(nn::account::Uid, bool)
|
||||
public ResultCode SetSaveDataBackupSettingEnabled(ServiceCtx context)
|
||||
[CommandHipc(13)]
|
||||
// GetSaveDataBackupSetting(nn::account::Uid) -> u8
|
||||
public ResultCode GetSaveDataBackupSetting(ServiceCtx context)
|
||||
{
|
||||
UserId userId = context.RequestData.ReadStruct<UserId>();
|
||||
ulong saveDataBackupSettingEnabled = context.RequestData.ReadUInt64();
|
||||
UserId userId = context.RequestData.ReadStruct<UserId>();
|
||||
|
||||
if (!_initialized)
|
||||
{
|
||||
@ -42,8 +45,42 @@ namespace Ryujinx.HLE.HOS.Services.Olsc
|
||||
return ResultCode.NullArgument;
|
||||
}
|
||||
|
||||
// NOTE: Service store the UserId and the boolean in an internal SaveDataBackupSettingDatabase object.
|
||||
// Since we will not support online savedata backup. It's fine to stub it for now.
|
||||
if (_saveDataBackupSettingDatabase.TryGetValue(userId, out bool enabled) && enabled)
|
||||
{
|
||||
context.ResponseData.Write((byte)1); // TODO: Determine value.
|
||||
}
|
||||
else
|
||||
{
|
||||
context.ResponseData.Write((byte)2); // TODO: Determine value.
|
||||
}
|
||||
|
||||
// NOTE: Since we will not support online savedata backup, it's fine to stub it for now.
|
||||
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceOlsc, new { userId });
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandHipc(14)]
|
||||
// SetSaveDataBackupSettingEnabled(nn::account::Uid, bool)
|
||||
public ResultCode SetSaveDataBackupSettingEnabled(ServiceCtx context)
|
||||
{
|
||||
bool saveDataBackupSettingEnabled = context.RequestData.ReadUInt64() != 0;
|
||||
UserId userId = context.RequestData.ReadStruct<UserId>();
|
||||
|
||||
if (!_initialized)
|
||||
{
|
||||
return ResultCode.NotInitialized;
|
||||
}
|
||||
|
||||
if (userId.IsNull)
|
||||
{
|
||||
return ResultCode.NullArgument;
|
||||
}
|
||||
|
||||
_saveDataBackupSettingDatabase[userId] = saveDataBackupSettingEnabled;
|
||||
|
||||
// NOTE: Since we will not support online savedata backup, it's fine to stub it for now.
|
||||
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceOlsc, new { userId, saveDataBackupSettingEnabled });
|
||||
|
||||
|
@ -1,8 +1,9 @@
|
||||
using LibHac.Ns;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.HLE.HOS.Services.Arp;
|
||||
using System;
|
||||
|
||||
using static LibHac.Ns.ApplicationControlProperty;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Pctl.ParentalControlServiceFactory
|
||||
{
|
||||
class IParentalControlService : IpcService
|
||||
@ -52,8 +53,8 @@ namespace Ryujinx.HLE.HOS.Services.Pctl.ParentalControlServiceFactory
|
||||
_titleId = titleId;
|
||||
|
||||
// TODO: Call nn::arp::GetApplicationControlProperty here when implemented, if it return ResultCode.Success we assign fields.
|
||||
_ratingAge = Array.ConvertAll(context.Device.Application.ControlData.Value.RatingAge.ToArray(), Convert.ToInt32);
|
||||
_parentalControlFlag = context.Device.Application.ControlData.Value.ParentalControl;
|
||||
_ratingAge = Array.ConvertAll(context.Device.Application.ControlData.Value.RatingAge.ItemsRo.ToArray(), Convert.ToInt32);
|
||||
_parentalControlFlag = context.Device.Application.ControlData.Value.ParentalControlFlag;
|
||||
}
|
||||
}
|
||||
|
||||
@ -224,7 +225,7 @@ namespace Ryujinx.HLE.HOS.Services.Pctl.ParentalControlServiceFactory
|
||||
private ResultCode IsStereoVisionPermittedImpl()
|
||||
{
|
||||
/*
|
||||
// TODO: Application Exemptions are readed from file "appExemptions.dat" in the service savedata.
|
||||
// TODO: Application Exemptions are read from file "appExemptions.dat" in the service savedata.
|
||||
// Since we don't support the pctl savedata for now, this can be implemented later.
|
||||
|
||||
if (appExemption)
|
||||
|
@ -15,6 +15,8 @@ namespace Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService
|
||||
|
||||
internal static ResultCode GetPlayStatistics(ServiceCtx context, bool byUserId = false)
|
||||
{
|
||||
ref readonly var controlProperty = ref context.Device.Application.ControlData.Value;
|
||||
|
||||
ulong inputPosition = context.Request.SendBuff[0].Position;
|
||||
ulong inputSize = context.Request.SendBuff[0].Size;
|
||||
|
||||
@ -31,7 +33,7 @@ namespace Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService
|
||||
}
|
||||
}
|
||||
|
||||
PlayLogQueryCapability queryCapability = (PlayLogQueryCapability)context.Device.Application.ControlData.Value.PlayLogQueryCapability;
|
||||
PlayLogQueryCapability queryCapability = (PlayLogQueryCapability)controlProperty.PlayLogQueryCapability;
|
||||
|
||||
List<ulong> titleIds = new List<ulong>();
|
||||
|
||||
@ -45,7 +47,7 @@ namespace Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService
|
||||
// Check if input title ids are in the whitelist.
|
||||
foreach (ulong titleId in titleIds)
|
||||
{
|
||||
if (!context.Device.Application.ControlData.Value.PlayLogQueryableApplicationId.Contains(titleId))
|
||||
if (!controlProperty.PlayLogQueryableApplicationId.ItemsRo.Contains(titleId))
|
||||
{
|
||||
return (ResultCode)Am.ResultCode.ObjectInvalid;
|
||||
}
|
||||
|
@ -185,7 +185,7 @@ namespace Ryujinx.HLE.HOS.Services
|
||||
{
|
||||
for (int i = 0; i < request.RecvListBuff.Count; i++)
|
||||
{
|
||||
ulong size = (ulong)BinaryPrimitives.ReadInt16LittleEndian(request.RawData.AsSpan().Slice(sizesOffset + i * 2, 2));
|
||||
ulong size = (ulong)BinaryPrimitives.ReadInt16LittleEndian(request.RawData.AsSpan(sizesOffset + i * 2, 2));
|
||||
|
||||
response.PtrBuff.Add(new IpcPtrBuffDesc(tempAddr, (uint)i, size));
|
||||
|
||||
|
@ -7,16 +7,18 @@
|
||||
|
||||
Success = 0,
|
||||
|
||||
TimeServiceNotInitialized = (0 << ErrorCodeShift) | ModuleId,
|
||||
PermissionDenied = (1 << ErrorCodeShift) | ModuleId,
|
||||
TimeMismatch = (102 << ErrorCodeShift) | ModuleId,
|
||||
UninitializedClock = (103 << ErrorCodeShift) | ModuleId,
|
||||
TimeNotFound = (200 << ErrorCodeShift) | ModuleId,
|
||||
Overflow = (201 << ErrorCodeShift) | ModuleId,
|
||||
LocationNameTooLong = (801 << ErrorCodeShift) | ModuleId,
|
||||
OutOfRange = (902 << ErrorCodeShift) | ModuleId,
|
||||
TimeZoneConversionFailed = (903 << ErrorCodeShift) | ModuleId,
|
||||
TimeZoneNotFound = (989 << ErrorCodeShift) | ModuleId,
|
||||
NotImplemented = (990 << ErrorCodeShift) | ModuleId,
|
||||
TimeServiceNotInitialized = (0 << ErrorCodeShift) | ModuleId,
|
||||
PermissionDenied = (1 << ErrorCodeShift) | ModuleId,
|
||||
TimeMismatch = (102 << ErrorCodeShift) | ModuleId,
|
||||
UninitializedClock = (103 << ErrorCodeShift) | ModuleId,
|
||||
TimeNotFound = (200 << ErrorCodeShift) | ModuleId,
|
||||
Overflow = (201 << ErrorCodeShift) | ModuleId,
|
||||
LocationNameTooLong = (801 << ErrorCodeShift) | ModuleId,
|
||||
OutOfRange = (902 << ErrorCodeShift) | ModuleId,
|
||||
TimeZoneConversionFailed = (903 << ErrorCodeShift) | ModuleId,
|
||||
TimeZoneNotFound = (989 << ErrorCodeShift) | ModuleId,
|
||||
NotImplemented = (990 << ErrorCodeShift) | ModuleId,
|
||||
NetworkTimeNotAvailable = (1000 << ErrorCodeShift) | ModuleId,
|
||||
NetworkTimeTaskCanceled = (1003 << ErrorCodeShift) | ModuleId,
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ namespace Ryujinx.HLE.HOS.Services.Vi
|
||||
|
||||
if (serviceType != ViServiceType.Application)
|
||||
{
|
||||
return ResultCode.InvalidRange;
|
||||
return ResultCode.PermissionDenied;
|
||||
}
|
||||
|
||||
MakeObject(context, new IApplicationDisplayService(serviceType));
|
||||
|
@ -17,7 +17,7 @@ namespace Ryujinx.HLE.HOS.Services.Vi
|
||||
|
||||
if (serviceType != ViServiceType.Manager)
|
||||
{
|
||||
return ResultCode.InvalidRange;
|
||||
return ResultCode.PermissionDenied;
|
||||
}
|
||||
|
||||
MakeObject(context, new IApplicationDisplayService(serviceType));
|
||||
|
@ -17,7 +17,7 @@ namespace Ryujinx.HLE.HOS.Services.Vi
|
||||
|
||||
if (serviceType != ViServiceType.System)
|
||||
{
|
||||
return ResultCode.InvalidRange;
|
||||
return ResultCode.PermissionDenied;
|
||||
}
|
||||
|
||||
MakeObject(context, new IApplicationDisplayService(serviceType));
|
||||
|
@ -9,7 +9,7 @@ namespace Ryujinx.HLE.HOS.Services.Vi
|
||||
|
||||
InvalidArguments = (1 << ErrorCodeShift) | ModuleId,
|
||||
InvalidLayerSize = (4 << ErrorCodeShift) | ModuleId,
|
||||
InvalidRange = (5 << ErrorCodeShift) | ModuleId,
|
||||
PermissionDenied = (5 << ErrorCodeShift) | ModuleId,
|
||||
InvalidScalingMode = (6 << ErrorCodeShift) | ModuleId,
|
||||
InvalidValue = (7 << ErrorCodeShift) | ModuleId,
|
||||
AlreadyOpened = (9 << ErrorCodeShift) | ModuleId
|
||||
|
@ -1,7 +1,6 @@
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Cpu;
|
||||
using Ryujinx.HLE.HOS.Applets;
|
||||
using Ryujinx.HLE.HOS.Ipc;
|
||||
using Ryujinx.HLE.HOS.Kernel.Common;
|
||||
@ -22,16 +21,21 @@ namespace Ryujinx.HLE.HOS.Services.Vi.RootService
|
||||
{
|
||||
private readonly ViServiceType _serviceType;
|
||||
|
||||
private readonly List<DisplayInfo> _displayInfo;
|
||||
private readonly Dictionary<ulong, DisplayInfo> _openDisplayInfo;
|
||||
private class DisplayState
|
||||
{
|
||||
public int RetrievedEventsCount;
|
||||
}
|
||||
|
||||
private readonly List<DisplayInfo> _displayInfo;
|
||||
private readonly Dictionary<ulong, DisplayState> _openDisplays;
|
||||
|
||||
private int _vsyncEventHandle;
|
||||
|
||||
public IApplicationDisplayService(ViServiceType serviceType)
|
||||
{
|
||||
_serviceType = serviceType;
|
||||
_displayInfo = new List<DisplayInfo>();
|
||||
_openDisplayInfo = new Dictionary<ulong, DisplayInfo>();
|
||||
_serviceType = serviceType;
|
||||
_displayInfo = new List<DisplayInfo>();
|
||||
_openDisplays = new Dictionary<ulong, DisplayState>();
|
||||
|
||||
void AddDisplayInfo(string name, bool layerLimitEnabled, ulong layerLimitMax, ulong width, ulong height)
|
||||
{
|
||||
@ -64,7 +68,7 @@ namespace Ryujinx.HLE.HOS.Services.Vi.RootService
|
||||
// FIXME: Should be _serviceType != ViServiceType.Application but guests crashes if we do this check.
|
||||
if (_serviceType > ViServiceType.System)
|
||||
{
|
||||
return ResultCode.InvalidRange;
|
||||
return ResultCode.PermissionDenied;
|
||||
}
|
||||
|
||||
MakeObject(context, new HOSBinderDriverServer());
|
||||
@ -79,7 +83,7 @@ namespace Ryujinx.HLE.HOS.Services.Vi.RootService
|
||||
// FIXME: Should be _serviceType == ViServiceType.System but guests crashes if we do this check.
|
||||
if (_serviceType > ViServiceType.System)
|
||||
{
|
||||
return ResultCode.InvalidRange;
|
||||
return ResultCode.PermissionDenied;
|
||||
}
|
||||
|
||||
MakeObject(context, new ISystemDisplayService(this));
|
||||
@ -93,7 +97,7 @@ namespace Ryujinx.HLE.HOS.Services.Vi.RootService
|
||||
{
|
||||
if (_serviceType > ViServiceType.System)
|
||||
{
|
||||
return ResultCode.InvalidRange;
|
||||
return ResultCode.PermissionDenied;
|
||||
}
|
||||
|
||||
MakeObject(context, new IManagerDisplayService(this));
|
||||
@ -107,7 +111,7 @@ namespace Ryujinx.HLE.HOS.Services.Vi.RootService
|
||||
{
|
||||
if (_serviceType > ViServiceType.System)
|
||||
{
|
||||
return ResultCode.InvalidRange;
|
||||
return ResultCode.PermissionDenied;
|
||||
}
|
||||
|
||||
MakeObject(context, new HOSBinderDriverServer());
|
||||
@ -174,7 +178,7 @@ namespace Ryujinx.HLE.HOS.Services.Vi.RootService
|
||||
return ResultCode.InvalidValue;
|
||||
}
|
||||
|
||||
if (!_openDisplayInfo.TryAdd((ulong)displayId, _displayInfo[displayId]))
|
||||
if (!_openDisplays.TryAdd((ulong)displayId, new DisplayState()))
|
||||
{
|
||||
return ResultCode.AlreadyOpened;
|
||||
}
|
||||
@ -190,7 +194,7 @@ namespace Ryujinx.HLE.HOS.Services.Vi.RootService
|
||||
{
|
||||
ulong displayId = context.RequestData.ReadUInt64();
|
||||
|
||||
if (!_openDisplayInfo.Remove(displayId))
|
||||
if (!_openDisplays.Remove(displayId))
|
||||
{
|
||||
return ResultCode.InvalidValue;
|
||||
}
|
||||
@ -454,11 +458,16 @@ namespace Ryujinx.HLE.HOS.Services.Vi.RootService
|
||||
{
|
||||
ulong displayId = context.RequestData.ReadUInt64();
|
||||
|
||||
if (!_openDisplayInfo.ContainsKey(displayId))
|
||||
if (!_openDisplays.TryGetValue(displayId, out DisplayState displayState))
|
||||
{
|
||||
return ResultCode.InvalidValue;
|
||||
}
|
||||
|
||||
if (displayState.RetrievedEventsCount > 0)
|
||||
{
|
||||
return ResultCode.PermissionDenied;
|
||||
}
|
||||
|
||||
if (_vsyncEventHandle == 0)
|
||||
{
|
||||
if (context.Process.HandleTable.GenerateHandle(context.Device.System.VsyncEvent.ReadableEvent, out _vsyncEventHandle) != KernelResult.Success)
|
||||
@ -467,6 +476,7 @@ namespace Ryujinx.HLE.HOS.Services.Vi.RootService
|
||||
}
|
||||
}
|
||||
|
||||
displayState.RetrievedEventsCount++;
|
||||
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_vsyncEventHandle);
|
||||
|
||||
return ResultCode.Success;
|
||||
|
@ -8,9 +8,9 @@ namespace Ryujinx.HLE.Loaders.Executables
|
||||
class KipExecutable : IExecutable
|
||||
{
|
||||
public byte[] Program { get; }
|
||||
public Span<byte> Text => Program.AsSpan().Slice((int)TextOffset, (int)TextSize);
|
||||
public Span<byte> Ro => Program.AsSpan().Slice((int)RoOffset, (int)RoSize);
|
||||
public Span<byte> Data => Program.AsSpan().Slice((int)DataOffset, (int)DataSize);
|
||||
public Span<byte> Text => Program.AsSpan((int)TextOffset, (int)TextSize);
|
||||
public Span<byte> Ro => Program.AsSpan((int)RoOffset, (int)RoSize);
|
||||
public Span<byte> Data => Program.AsSpan((int)DataOffset, (int)DataSize);
|
||||
|
||||
public uint TextOffset { get; }
|
||||
public uint RoOffset { get; }
|
||||
@ -76,7 +76,7 @@ namespace Ryujinx.HLE.Loaders.Executables
|
||||
{
|
||||
reader.GetSegmentSize(segmentType, out int uncompressedSize).ThrowIfFailure();
|
||||
|
||||
var span = program.AsSpan().Slice((int)offset, uncompressedSize);
|
||||
var span = program.AsSpan((int)offset, uncompressedSize);
|
||||
|
||||
reader.ReadSegment(segmentType, span).ThrowIfFailure();
|
||||
|
||||
|
@ -7,9 +7,9 @@ namespace Ryujinx.HLE.Loaders.Executables
|
||||
class NroExecutable : Nro, IExecutable
|
||||
{
|
||||
public byte[] Program { get; }
|
||||
public Span<byte> Text => Program.AsSpan().Slice((int)TextOffset, (int)Header.NroSegments[0].Size);
|
||||
public Span<byte> Ro => Program.AsSpan().Slice((int)RoOffset, (int)Header.NroSegments[1].Size);
|
||||
public Span<byte> Data => Program.AsSpan().Slice((int)DataOffset, (int)Header.NroSegments[2].Size);
|
||||
public Span<byte> Text => Program.AsSpan((int)TextOffset, (int)Header.NroSegments[0].Size);
|
||||
public Span<byte> Ro => Program.AsSpan((int)RoOffset, (int)Header.NroSegments[1].Size);
|
||||
public Span<byte> Data => Program.AsSpan((int)DataOffset, (int)Header.NroSegments[2].Size);
|
||||
|
||||
public uint TextOffset => Header.NroSegments[0].FileOffset;
|
||||
public uint RoOffset => Header.NroSegments[1].FileOffset;
|
||||
|
@ -1,4 +1,4 @@
|
||||
using LibHac.Common;
|
||||
using LibHac.Common.FixedArrays;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Loader;
|
||||
using LibHac.Tools.FsSystem;
|
||||
@ -12,9 +12,9 @@ namespace Ryujinx.HLE.Loaders.Executables
|
||||
class NsoExecutable : IExecutable
|
||||
{
|
||||
public byte[] Program { get; }
|
||||
public Span<byte> Text => Program.AsSpan().Slice((int)TextOffset, (int)TextSize);
|
||||
public Span<byte> Ro => Program.AsSpan().Slice((int)RoOffset, (int)RoSize);
|
||||
public Span<byte> Data => Program.AsSpan().Slice((int)DataOffset, (int)DataSize);
|
||||
public Span<byte> Text => Program.AsSpan((int)TextOffset, (int)TextSize);
|
||||
public Span<byte> Ro => Program.AsSpan((int)RoOffset, (int)RoSize);
|
||||
public Span<byte> Data => Program.AsSpan((int)DataOffset, (int)DataSize);
|
||||
|
||||
public uint TextOffset { get; }
|
||||
public uint RoOffset { get; }
|
||||
@ -26,8 +26,8 @@ namespace Ryujinx.HLE.Loaders.Executables
|
||||
public uint DataSize { get; }
|
||||
public uint BssSize { get; }
|
||||
|
||||
public string Name;
|
||||
public Buffer32 BuildId;
|
||||
public string Name;
|
||||
public Array32<byte> BuildId;
|
||||
|
||||
public NsoExecutable(IStorage inStorage, string name = null)
|
||||
{
|
||||
@ -58,7 +58,7 @@ namespace Ryujinx.HLE.Loaders.Executables
|
||||
{
|
||||
reader.GetSegmentSize(segmentType, out uint uncompressedSize).ThrowIfFailure();
|
||||
|
||||
var span = Program.AsSpan().Slice((int)offset, (int)uncompressedSize);
|
||||
var span = Program.AsSpan((int)offset, (int)uncompressedSize);
|
||||
|
||||
reader.ReadSegment(segmentType, span).ThrowIfFailure();
|
||||
|
||||
|
@ -85,7 +85,7 @@ namespace Ryujinx.HLE.Loaders.Mods
|
||||
|
||||
Logger.Info?.Print(LogClass.ModLoader, $"Patching address offset {patchOffset:x} <= {BitConverter.ToString(patch).Replace('-', ' ')} len={patchSize}");
|
||||
|
||||
patch.AsSpan().Slice(0, patchSize).CopyTo(memory.Slice(patchOffset, patchSize));
|
||||
patch.AsSpan(0, patchSize).CopyTo(memory.Slice(patchOffset, patchSize));
|
||||
|
||||
count++;
|
||||
}
|
||||
|
@ -19,7 +19,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Concentus" Version="1.1.7" />
|
||||
<PackageReference Include="LibHac" Version="0.15.0" />
|
||||
<PackageReference Include="LibHac" Version="0.16.0" />
|
||||
<PackageReference Include="MsgPack.Cli" Version="1.0.1" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.4" />
|
||||
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta11" />
|
||||
|
@ -220,6 +220,7 @@ namespace Ryujinx.Headless.SDL2
|
||||
StickButton = ConfigGamepadInputId.LeftStick,
|
||||
InvertStickX = false,
|
||||
InvertStickY = false,
|
||||
Rotate90CW = false,
|
||||
},
|
||||
|
||||
RightJoycon = new RightJoyconCommonConfig<ConfigGamepadInputId>
|
||||
@ -241,6 +242,7 @@ namespace Ryujinx.Headless.SDL2
|
||||
StickButton = ConfigGamepadInputId.RightStick,
|
||||
InvertStickX = false,
|
||||
InvertStickY = false,
|
||||
Rotate90CW = false,
|
||||
},
|
||||
|
||||
Motion = new StandardMotionConfigController
|
||||
@ -310,7 +312,7 @@ namespace Ryujinx.Headless.SDL2
|
||||
{
|
||||
controllerConfig.RangeLeft = 1.0f;
|
||||
controllerConfig.RangeRight = 1.0f;
|
||||
|
||||
|
||||
Logger.Info?.Print(LogClass.Application, $"{config.PlayerIndex} stick range reset. Save the profile now to update your configuration");
|
||||
}
|
||||
}
|
||||
@ -396,7 +398,7 @@ namespace Ryujinx.Headless.SDL2
|
||||
if ((bool)option.EnableFileLog)
|
||||
{
|
||||
Logger.AddTarget(new AsyncLogTargetWrapper(
|
||||
new FileLogTarget(AppDomain.CurrentDomain.BaseDirectory, "file"),
|
||||
new FileLogTarget(ReleaseInformations.GetBaseApplicationDirectory(), "file"),
|
||||
1000,
|
||||
AsyncLogTargetOverflowAction.Block
|
||||
));
|
||||
|
@ -350,6 +350,14 @@ namespace Ryujinx.Input.SDL2
|
||||
{
|
||||
resultY = -resultY;
|
||||
}
|
||||
|
||||
if ((inputId == StickInputId.Left && _configuration.LeftJoyconStick.Rotate90CW) ||
|
||||
(inputId == StickInputId.Right && _configuration.RightJoyconStick.Rotate90CW))
|
||||
{
|
||||
float temp = resultX;
|
||||
resultX = resultY;
|
||||
resultY = -temp;
|
||||
}
|
||||
}
|
||||
|
||||
return (resultX, resultY);
|
||||
|
@ -50,6 +50,13 @@ namespace Ryujinx.Memory.Range
|
||||
ulong otherAddress = other.Address;
|
||||
ulong otherEndAddress = other.EndAddress;
|
||||
|
||||
// If any of the ranges if invalid (address + size overflows),
|
||||
// then they are never considered to overlap.
|
||||
if (thisEndAddress < thisAddress || otherEndAddress < otherAddress)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return thisAddress < otherEndAddress && otherAddress < thisEndAddress;
|
||||
}
|
||||
|
||||
|
@ -29,6 +29,12 @@ namespace Ryujinx.Memory.Range
|
||||
for (int i = 0; i < range.Count; i++)
|
||||
{
|
||||
var subrange = range.GetSubRange(i);
|
||||
|
||||
if (IsInvalid(ref subrange))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
_items.Add(subrange.Address, subrange.EndAddress, item);
|
||||
}
|
||||
|
||||
@ -49,6 +55,12 @@ namespace Ryujinx.Memory.Range
|
||||
for (int i = 0; i < range.Count; i++)
|
||||
{
|
||||
var subrange = range.GetSubRange(i);
|
||||
|
||||
if (IsInvalid(ref subrange))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
removed += _items.Remove(subrange.Address, item);
|
||||
}
|
||||
|
||||
@ -86,6 +98,12 @@ namespace Ryujinx.Memory.Range
|
||||
for (int i = 0; i < range.Count; i++)
|
||||
{
|
||||
var subrange = range.GetSubRange(i);
|
||||
|
||||
if (IsInvalid(ref subrange))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
overlapCount = _items.Get(subrange.Address, subrange.EndAddress, ref output, overlapCount);
|
||||
}
|
||||
|
||||
@ -124,6 +142,17 @@ namespace Ryujinx.Memory.Range
|
||||
return overlapCount;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a given sub-range of memory is invalid.
|
||||
/// Those are used to represent unmapped memory regions (holes in the region mapping).
|
||||
/// </summary>
|
||||
/// <param name="subRange">Memory range to checl</param>
|
||||
/// <returns>True if the memory range is considered invalid, false otherwise</returns>
|
||||
private static bool IsInvalid(ref MemoryRange subRange)
|
||||
{
|
||||
return subRange.Address == ulong.MaxValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all items on the list starting at the specified memory address.
|
||||
/// </summary>
|
||||
|
@ -1,4 +1,5 @@
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Logging;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
@ -81,7 +82,7 @@ namespace Ryujinx.SDL2.Common
|
||||
|
||||
SDL_EventState(SDL_EventType.SDL_CONTROLLERSENSORUPDATE, SDL_DISABLE);
|
||||
|
||||
string gamepadDbPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "SDL_GameControllerDB.txt");
|
||||
string gamepadDbPath = Path.Combine(ReleaseInformations.GetBaseApplicationDirectory(), "SDL_GameControllerDB.txt");
|
||||
|
||||
if (File.Exists(gamepadDbPath))
|
||||
{
|
||||
|
@ -257,6 +257,35 @@ namespace Ryujinx.Tests.Cpu
|
||||
return GetContext();
|
||||
}
|
||||
|
||||
public void RunPrecomputedTestCase(PrecomputedThumbTestCase test)
|
||||
{
|
||||
foreach (ushort instruction in test.Instructions)
|
||||
{
|
||||
ThumbOpcode(instruction);
|
||||
}
|
||||
|
||||
for (int i = 0; i < 15; i++)
|
||||
{
|
||||
GetContext().SetX(i, test.StartRegs[i]);
|
||||
}
|
||||
|
||||
uint startCpsr = test.StartRegs[15];
|
||||
for (int i = 0; i < 32; i++)
|
||||
{
|
||||
GetContext().SetPstateFlag((PState)i, (startCpsr & (1u << i)) != 0);
|
||||
}
|
||||
|
||||
ExecuteOpcodes(runUnicorn: false);
|
||||
|
||||
for (int i = 0; i < 15; i++)
|
||||
{
|
||||
Assert.That(GetContext().GetX(i), Is.EqualTo(test.FinalRegs[i]));
|
||||
}
|
||||
|
||||
uint finalCpsr = test.FinalRegs[15];
|
||||
Assert.That(GetContext().Pstate, Is.EqualTo(finalCpsr));
|
||||
}
|
||||
|
||||
protected void SetWorkingMemory(uint offset, byte[] data)
|
||||
{
|
||||
_memory.Write(DataBaseAddress + offset, data);
|
||||
|
57
Ryujinx.Tests/Cpu/CpuTestAluImm32.cs
Normal file
57
Ryujinx.Tests/Cpu/CpuTestAluImm32.cs
Normal file
@ -0,0 +1,57 @@
|
||||
#define AluRs32
|
||||
|
||||
using NUnit.Framework;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ryujinx.Tests.Cpu
|
||||
{
|
||||
[Category("AluImm32")]
|
||||
public sealed class CpuTestAluImm32 : CpuTest32
|
||||
{
|
||||
#if AluRs32
|
||||
|
||||
#region "ValueSource (Opcodes)"
|
||||
private static uint[] _opcodes()
|
||||
{
|
||||
return new uint[]
|
||||
{
|
||||
0xe2a00000u, // ADC R0, R0, #0
|
||||
0xe2b00000u, // ADCS R0, R0, #0
|
||||
0xe2800000u, // ADD R0, R0, #0
|
||||
0xe2900000u, // ADDS R0, R0, #0
|
||||
0xe3c00000u, // BIC R0, R0, #0
|
||||
0xe3d00000u, // BICS R0, R0, #0
|
||||
0xe2600000u, // RSB R0, R0, #0
|
||||
0xe2700000u, // RSBS R0, R0, #0
|
||||
0xe2e00000u, // RSC R0, R0, #0
|
||||
0xe2f00000u, // RSCS R0, R0, #0
|
||||
0xe2c00000u, // SBC R0, R0, #0
|
||||
0xe2d00000u, // SBCS R0, R0, #0
|
||||
0xe2400000u, // SUB R0, R0, #0
|
||||
0xe2500000u, // SUBS R0, R0, #0
|
||||
};
|
||||
}
|
||||
#endregion
|
||||
|
||||
private const int RndCnt = 2;
|
||||
private const int RndCntAmount = 2;
|
||||
|
||||
[Test, Pairwise]
|
||||
public void TestCpuTestAluImm32([ValueSource("_opcodes")] uint opcode,
|
||||
[Values(0u, 13u)] uint rd,
|
||||
[Values(1u, 13u)] uint rn,
|
||||
[Random(RndCnt)] uint imm,
|
||||
[Random(RndCnt)] uint wn,
|
||||
[Values(true, false)] bool carryIn)
|
||||
{
|
||||
opcode |= ((imm & 0xfff) << 0) | ((rn & 15) << 16) | ((rd & 15) << 12);
|
||||
|
||||
uint sp = TestContext.CurrentContext.Random.NextUInt();
|
||||
|
||||
SingleOpcode(opcode, r1: wn, sp: sp, carry: carryIn);
|
||||
|
||||
CompareAgainstUnicorn();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
1016
Ryujinx.Tests/Cpu/CpuTestT32Alu.cs
Normal file
1016
Ryujinx.Tests/Cpu/CpuTestT32Alu.cs
Normal file
File diff suppressed because it is too large
Load Diff
167
Ryujinx.Tests/Cpu/CpuTestT32Flow.cs
Normal file
167
Ryujinx.Tests/Cpu/CpuTestT32Flow.cs
Normal file
@ -0,0 +1,167 @@
|
||||
using ARMeilleure.State;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace Ryujinx.Tests.Cpu
|
||||
{
|
||||
[Category("T32Flow")]
|
||||
public sealed class CpuTestT32Flow : CpuTest32
|
||||
{
|
||||
[Test]
|
||||
public void TestT32B1()
|
||||
{
|
||||
// BNE label
|
||||
ThumbOpcode(0xf040);
|
||||
ThumbOpcode(0x8240);
|
||||
for (int i = 0; i < 576; i++)
|
||||
{
|
||||
ThumbOpcode(0xe7fe);
|
||||
}
|
||||
// label: BX LR
|
||||
ThumbOpcode(0x4770);
|
||||
|
||||
GetContext().SetPstateFlag(PState.TFlag, true);
|
||||
|
||||
ExecuteOpcodes(runUnicorn: false);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestT32B2()
|
||||
{
|
||||
// BNE label1
|
||||
ThumbOpcode(0xf040);
|
||||
ThumbOpcode(0x8242);
|
||||
// label2: BNE label3
|
||||
ThumbOpcode(0xf040);
|
||||
ThumbOpcode(0x8242);
|
||||
for (int i = 0; i < 576; i++)
|
||||
{
|
||||
ThumbOpcode(0xe7fe);
|
||||
}
|
||||
// label1: BNE label2
|
||||
ThumbOpcode(0xf47f);
|
||||
ThumbOpcode(0xadbc);
|
||||
// label3: BX LR
|
||||
ThumbOpcode(0x4770);
|
||||
|
||||
GetContext().SetPstateFlag(PState.TFlag, true);
|
||||
|
||||
ExecuteOpcodes(runUnicorn: false);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestT32B3()
|
||||
{
|
||||
// B.W label
|
||||
ThumbOpcode(0xf000);
|
||||
ThumbOpcode(0xba40);
|
||||
for (int i = 0; i < 576; i++)
|
||||
{
|
||||
ThumbOpcode(0xe7fe);
|
||||
}
|
||||
// label: BX LR
|
||||
ThumbOpcode(0x4770);
|
||||
|
||||
GetContext().SetPstateFlag(PState.TFlag, true);
|
||||
|
||||
ExecuteOpcodes(runUnicorn: false);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestT32B4()
|
||||
{
|
||||
// B.W label1
|
||||
ThumbOpcode(0xf000);
|
||||
ThumbOpcode(0xba42);
|
||||
// label2: B.W label3
|
||||
ThumbOpcode(0xf000);
|
||||
ThumbOpcode(0xba42);
|
||||
for (int i = 0; i < 576; i++)
|
||||
{
|
||||
ThumbOpcode(0xe7fe);
|
||||
}
|
||||
// label1: B.W label2
|
||||
ThumbOpcode(0xf7ff);
|
||||
ThumbOpcode(0xbdbc);
|
||||
// label3: BX LR
|
||||
ThumbOpcode(0x4770);
|
||||
|
||||
GetContext().SetPstateFlag(PState.TFlag, true);
|
||||
|
||||
ExecuteOpcodes(runUnicorn: false);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestT32Bl()
|
||||
{
|
||||
// BL label
|
||||
ThumbOpcode(0xf000);
|
||||
ThumbOpcode(0xf840);
|
||||
for (int i = 0; i < 64; i++)
|
||||
{
|
||||
ThumbOpcode(0xe7fe);
|
||||
}
|
||||
ThumbOpcode(0x4670); // label: MOV R0, LR
|
||||
ThumbOpcode(0x2100); // MOVS R1, #0
|
||||
ThumbOpcode(0x468e); // MOV LR, R1
|
||||
ThumbOpcode(0x4770); // BX LR
|
||||
|
||||
GetContext().SetPstateFlag(PState.TFlag, true);
|
||||
|
||||
ExecuteOpcodes(runUnicorn: false);
|
||||
|
||||
Assert.That(GetContext().GetX(0), Is.EqualTo(0x1005));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestT32Blx1()
|
||||
{
|
||||
// BLX label
|
||||
ThumbOpcode(0xf000);
|
||||
ThumbOpcode(0xe840);
|
||||
for (int i = 0; i < 64; i++)
|
||||
{
|
||||
ThumbOpcode(0x4770);
|
||||
}
|
||||
// .arm ; label: MOV R0, LR
|
||||
Opcode(0xe1a0000e);
|
||||
// MOV LR, #0
|
||||
Opcode(0xe3a0e000);
|
||||
// BX LR
|
||||
Opcode(0xe12fff1e);
|
||||
|
||||
GetContext().SetPstateFlag(PState.TFlag, true);
|
||||
|
||||
ExecuteOpcodes(runUnicorn: false);
|
||||
|
||||
Assert.That(GetContext().GetX(0), Is.EqualTo(0x1005));
|
||||
Assert.That(GetContext().GetPstateFlag(PState.TFlag), Is.EqualTo(false));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestT32Blx2()
|
||||
{
|
||||
// NOP
|
||||
ThumbOpcode(0xbf00);
|
||||
// BLX label
|
||||
ThumbOpcode(0xf000);
|
||||
ThumbOpcode(0xe840);
|
||||
for (int i = 0; i < 63; i++)
|
||||
{
|
||||
ThumbOpcode(0x4770);
|
||||
}
|
||||
// .arm ; label: MOV R0, LR
|
||||
Opcode(0xe1a0000e);
|
||||
// MOV LR, #0
|
||||
Opcode(0xe3a0e000);
|
||||
// BX LR
|
||||
Opcode(0xe12fff1e);
|
||||
|
||||
GetContext().SetPstateFlag(PState.TFlag, true);
|
||||
|
||||
ExecuteOpcodes(runUnicorn: false);
|
||||
|
||||
Assert.That(GetContext().GetX(0), Is.EqualTo(0x1007));
|
||||
Assert.That(GetContext().GetPstateFlag(PState.TFlag), Is.EqualTo(false));
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user