Compare commits
32 Commits
Author | SHA1 | Date | |
---|---|---|---|
1402d8391d | |||
e3b36db71c | |||
ba0171d054 | |||
d1146a5af2 | |||
79408b68c3 | |||
d461d4f68b | |||
b45d30acf8 | |||
df70442c46 | |||
e2ffa5a125 | |||
73feac5819 | |||
e5ad1dfa48 | |||
79becc4b78 | |||
223172ac0b | |||
8c9633d72f | |||
1f93fd52d9 | |||
aac7bbd378 | |||
bed516bfda | |||
69b05f9918 | |||
fb7c80e928 | |||
bb2f9df0a1 | |||
54bfaa125d | |||
7af9fcbc06 | |||
ee174be57c | |||
0bcbe32367 | |||
b97ff4da5e | |||
747081d2c7 | |||
497199bb50 | |||
bd9ac0fdaa | |||
ac21abbb9d | |||
a3dd04deef | |||
3705c20668 | |||
7b35ebc64a |
@ -121,7 +121,7 @@ namespace ARMeilleure.Decoders
|
||||
currBlock.Branch = GetBlock((ulong)op.Immediate);
|
||||
}
|
||||
|
||||
if (!IsUnconditionalBranch(lastOp) || isCall)
|
||||
if (isCall || !(IsUnconditionalBranch(lastOp) || IsTrap(lastOp)))
|
||||
{
|
||||
currBlock.Next = GetBlock(currBlock.EndAddress);
|
||||
}
|
||||
@ -263,6 +263,11 @@ namespace ARMeilleure.Decoders
|
||||
// so we must consider such operations as a branch in potential aswell.
|
||||
if (opCode is IOpCode32Alu opAlu && opAlu.Rd == RegisterAlias.Aarch32Pc)
|
||||
{
|
||||
if (opCode is OpCodeT32)
|
||||
{
|
||||
return opCode.Instruction.Name != InstName.Tst && opCode.Instruction.Name != InstName.Teq &&
|
||||
opCode.Instruction.Name != InstName.Cmp && opCode.Instruction.Name != InstName.Cmn;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -324,9 +329,13 @@ namespace ARMeilleure.Decoders
|
||||
}
|
||||
|
||||
private static bool IsException(OpCode opCode)
|
||||
{
|
||||
return IsTrap(opCode) || opCode.Instruction.Name == InstName.Svc;
|
||||
}
|
||||
|
||||
private static bool IsTrap(OpCode opCode)
|
||||
{
|
||||
return opCode.Instruction.Name == InstName.Brk ||
|
||||
opCode.Instruction.Name == InstName.Svc ||
|
||||
opCode.Instruction.Name == InstName.Trap ||
|
||||
opCode.Instruction.Name == InstName.Und;
|
||||
}
|
||||
|
@ -13,11 +13,25 @@ namespace ARMeilleure.Decoders
|
||||
Cond = (Condition)((uint)opCode >> 28);
|
||||
}
|
||||
|
||||
public bool IsThumb()
|
||||
{
|
||||
return this is OpCodeT16 || this is OpCodeT32;
|
||||
}
|
||||
|
||||
public uint GetPc()
|
||||
{
|
||||
// Due to backwards compatibility and legacy behavior of ARMv4 CPUs pipeline,
|
||||
// the PC actually points 2 instructions ahead.
|
||||
return (uint)Address + (uint)OpCodeSizeInBytes * 2;
|
||||
if (IsThumb())
|
||||
{
|
||||
// PC is ahead by 4 in thumb mode whether or not the current instruction
|
||||
// is 16 or 32 bit.
|
||||
return (uint)Address + 4u;
|
||||
}
|
||||
else
|
||||
{
|
||||
return (uint)Address + 8u;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
namespace ARMeilleure.Decoders
|
||||
{
|
||||
class OpCodeT16BImm11 : OpCode32, IOpCode32BImm
|
||||
class OpCodeT16BImm11 : OpCodeT16, IOpCode32BImm
|
||||
{
|
||||
public long Immediate { get; }
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
namespace ARMeilleure.Decoders
|
||||
{
|
||||
class OpCodeT16BImm8 : OpCode32, IOpCode32BImm
|
||||
class OpCodeT16BImm8 : OpCodeT16, IOpCode32BImm
|
||||
{
|
||||
public long Immediate { get; }
|
||||
|
||||
|
14
ARMeilleure/Decoders/OpCodeT32.cs
Normal file
14
ARMeilleure/Decoders/OpCodeT32.cs
Normal file
@ -0,0 +1,14 @@
|
||||
namespace ARMeilleure.Decoders
|
||||
{
|
||||
class OpCodeT32 : OpCode32
|
||||
{
|
||||
public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32(inst, address, opCode);
|
||||
|
||||
public OpCodeT32(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode)
|
||||
{
|
||||
Cond = Condition.Al;
|
||||
|
||||
OpCodeSizeInBytes = 4;
|
||||
}
|
||||
}
|
||||
}
|
20
ARMeilleure/Decoders/OpCodeT32Alu.cs
Normal file
20
ARMeilleure/Decoders/OpCodeT32Alu.cs
Normal file
@ -0,0 +1,20 @@
|
||||
namespace ARMeilleure.Decoders
|
||||
{
|
||||
class OpCodeT32Alu : OpCodeT32, IOpCode32Alu
|
||||
{
|
||||
public int Rd { get; }
|
||||
public int Rn { get; }
|
||||
|
||||
public bool? SetFlags { get; }
|
||||
|
||||
public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32Alu(inst, address, opCode);
|
||||
|
||||
public OpCodeT32Alu(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode)
|
||||
{
|
||||
Rd = (opCode >> 8) & 0xf;
|
||||
Rn = (opCode >> 16) & 0xf;
|
||||
|
||||
SetFlags = ((opCode >> 20) & 1) != 0;
|
||||
}
|
||||
}
|
||||
}
|
38
ARMeilleure/Decoders/OpCodeT32AluImm.cs
Normal file
38
ARMeilleure/Decoders/OpCodeT32AluImm.cs
Normal file
@ -0,0 +1,38 @@
|
||||
using ARMeilleure.Common;
|
||||
using System.Runtime.Intrinsics;
|
||||
|
||||
namespace ARMeilleure.Decoders
|
||||
{
|
||||
class OpCodeT32AluImm : OpCodeT32Alu, IOpCode32AluImm
|
||||
{
|
||||
public int Immediate { get; }
|
||||
|
||||
public bool IsRotated { get; }
|
||||
|
||||
public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32AluImm(inst, address, opCode);
|
||||
|
||||
private static readonly Vector128<int> _factor = Vector128.Create(1, 0x00010001, 0x01000100, 0x01010101);
|
||||
|
||||
public OpCodeT32AluImm(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode)
|
||||
{
|
||||
int imm8 = (opCode >> 0) & 0xff;
|
||||
int imm3 = (opCode >> 12) & 7;
|
||||
int imm1 = (opCode >> 26) & 1;
|
||||
|
||||
int imm12 = imm8 | (imm3 << 8) | (imm1 << 11);
|
||||
|
||||
if ((imm12 >> 10) == 0)
|
||||
{
|
||||
Immediate = imm8 * _factor.GetElement((imm12 >> 8) & 3);
|
||||
IsRotated = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
int shift = imm12 >> 7;
|
||||
|
||||
Immediate = BitUtils.RotateRight(0x80 | (imm12 & 0x7f), shift, 32);
|
||||
IsRotated = shift != 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
20
ARMeilleure/Decoders/OpCodeT32AluRsImm.cs
Normal file
20
ARMeilleure/Decoders/OpCodeT32AluRsImm.cs
Normal file
@ -0,0 +1,20 @@
|
||||
namespace ARMeilleure.Decoders
|
||||
{
|
||||
class OpCodeT32AluRsImm : OpCodeT32Alu, IOpCode32AluRsImm
|
||||
{
|
||||
public int Rm { get; }
|
||||
public int Immediate { get; }
|
||||
|
||||
public ShiftType ShiftType { get; }
|
||||
|
||||
public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32AluRsImm(inst, address, opCode);
|
||||
|
||||
public OpCodeT32AluRsImm(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode)
|
||||
{
|
||||
Rm = (opCode >> 0) & 0xf;
|
||||
Immediate = ((opCode >> 6) & 3) | ((opCode >> 10) & 0x1c);
|
||||
|
||||
ShiftType = (ShiftType)((opCode >> 4) & 3);
|
||||
}
|
||||
}
|
||||
}
|
29
ARMeilleure/Decoders/OpCodeT32BImm20.cs
Normal file
29
ARMeilleure/Decoders/OpCodeT32BImm20.cs
Normal file
@ -0,0 +1,29 @@
|
||||
using ARMeilleure.Instructions;
|
||||
|
||||
namespace ARMeilleure.Decoders
|
||||
{
|
||||
class OpCodeT32BImm20 : OpCodeT32, IOpCode32BImm
|
||||
{
|
||||
public long Immediate { get; }
|
||||
|
||||
public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32BImm20(inst, address, opCode);
|
||||
|
||||
public OpCodeT32BImm20(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode)
|
||||
{
|
||||
uint pc = GetPc();
|
||||
|
||||
int imm11 = (opCode >> 0) & 0x7ff;
|
||||
int j2 = (opCode >> 11) & 1;
|
||||
int j1 = (opCode >> 13) & 1;
|
||||
int imm6 = (opCode >> 16) & 0x3f;
|
||||
int s = (opCode >> 26) & 1;
|
||||
|
||||
int imm32 = imm11 | (imm6 << 11) | (j1 << 17) | (j2 << 18) | (s << 19);
|
||||
imm32 = (imm32 << 13) >> 12;
|
||||
|
||||
Immediate = pc + imm32;
|
||||
|
||||
Cond = (Condition)((opCode >> 22) & 0xf);
|
||||
}
|
||||
}
|
||||
}
|
35
ARMeilleure/Decoders/OpCodeT32BImm24.cs
Normal file
35
ARMeilleure/Decoders/OpCodeT32BImm24.cs
Normal file
@ -0,0 +1,35 @@
|
||||
using ARMeilleure.Instructions;
|
||||
|
||||
namespace ARMeilleure.Decoders
|
||||
{
|
||||
class OpCodeT32BImm24 : OpCodeT32, IOpCode32BImm
|
||||
{
|
||||
public long Immediate { get; }
|
||||
|
||||
public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32BImm24(inst, address, opCode);
|
||||
|
||||
public OpCodeT32BImm24(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode)
|
||||
{
|
||||
uint pc = GetPc();
|
||||
|
||||
if (inst.Name == InstName.Blx)
|
||||
{
|
||||
pc &= ~3u;
|
||||
}
|
||||
|
||||
int imm11 = (opCode >> 0) & 0x7ff;
|
||||
int j2 = (opCode >> 11) & 1;
|
||||
int j1 = (opCode >> 13) & 1;
|
||||
int imm10 = (opCode >> 16) & 0x3ff;
|
||||
int s = (opCode >> 26) & 1;
|
||||
|
||||
int i1 = j1 ^ s ^ 1;
|
||||
int i2 = j2 ^ s ^ 1;
|
||||
|
||||
int imm32 = imm11 | (imm10 << 11) | (i2 << 21) | (i1 << 22) | (s << 23);
|
||||
imm32 = (imm32 << 9) >> 8;
|
||||
|
||||
Immediate = pc + imm32;
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -10,11 +10,6 @@ namespace ARMeilleure.Instructions
|
||||
{
|
||||
static class InstEmitHelper
|
||||
{
|
||||
public static bool IsThumb(OpCode op)
|
||||
{
|
||||
return op is OpCodeT16;
|
||||
}
|
||||
|
||||
public static Operand GetExtendedM(ArmEmitterContext context, int rm, IntType type)
|
||||
{
|
||||
Operand value = GetIntOrZR(context, rm);
|
||||
|
@ -130,11 +130,6 @@ namespace ARMeilleure.Instructions
|
||||
bool ordered = (accType & AccessType.Ordered) != 0;
|
||||
bool exclusive = (accType & AccessType.Exclusive) != 0;
|
||||
|
||||
if (ordered)
|
||||
{
|
||||
EmitBarrier(context);
|
||||
}
|
||||
|
||||
Operand address = context.Copy(GetIntOrSP(context, op.Rn));
|
||||
|
||||
Operand t = GetIntOrZR(context, op.Rt);
|
||||
@ -163,6 +158,11 @@ namespace ARMeilleure.Instructions
|
||||
{
|
||||
EmitStoreExclusive(context, address, t, exclusive, op.Size, op.Rs, a32: false);
|
||||
}
|
||||
|
||||
if (ordered)
|
||||
{
|
||||
EmitBarrier(context);
|
||||
}
|
||||
}
|
||||
|
||||
private static void EmitBarrier(ArmEmitterContext context)
|
||||
|
@ -146,13 +146,13 @@ namespace ARMeilleure.Instructions
|
||||
var exclusive = (accType & AccessType.Exclusive) != 0;
|
||||
var ordered = (accType & AccessType.Ordered) != 0;
|
||||
|
||||
if (ordered)
|
||||
{
|
||||
EmitBarrier(context);
|
||||
}
|
||||
|
||||
if ((accType & AccessType.Load) != 0)
|
||||
{
|
||||
if (ordered)
|
||||
{
|
||||
EmitBarrier(context);
|
||||
}
|
||||
|
||||
if (size == DWordSizeLog2)
|
||||
{
|
||||
// Keep loads atomic - make the call to get the whole region and then decompose it into parts
|
||||
@ -219,6 +219,11 @@ namespace ARMeilleure.Instructions
|
||||
Operand value = context.ZeroExtend32(OperandType.I64, GetIntA32(context, op.Rt));
|
||||
EmitStoreExclusive(context, address, value, exclusive, size, op.Rd, a32: true);
|
||||
}
|
||||
|
||||
if (ordered)
|
||||
{
|
||||
EmitBarrier(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 = 3138; //! 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";
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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,7 +1,7 @@
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Graphics.Device;
|
||||
using Ryujinx.Graphics.Gpu.Engine.Threed;
|
||||
using Ryujinx.Graphics.Gpu.Memory;
|
||||
using Ryujinx.Graphics.Texture;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@ -330,11 +330,95 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma
|
||||
{
|
||||
// TODO: Implement remap functionality.
|
||||
// Buffer to buffer copy.
|
||||
memoryManager.Physical.BufferCache.CopyBuffer(memoryManager, srcGpuVa, dstGpuVa, size);
|
||||
|
||||
bool srcIsPitchKind = memoryManager.GetKind(srcGpuVa).IsPitch();
|
||||
bool dstIsPitchKind = memoryManager.GetKind(dstGpuVa).IsPitch();
|
||||
|
||||
if (!srcIsPitchKind && dstIsPitchKind)
|
||||
{
|
||||
CopyGobBlockLinearToLinear(memoryManager, srcGpuVa, dstGpuVa, size);
|
||||
}
|
||||
else if (srcIsPitchKind && !dstIsPitchKind)
|
||||
{
|
||||
CopyGobLinearToBlockLinear(memoryManager, srcGpuVa, dstGpuVa, size);
|
||||
}
|
||||
else
|
||||
{
|
||||
memoryManager.Physical.BufferCache.CopyBuffer(memoryManager, srcGpuVa, dstGpuVa, size);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies block linear data with block linear GOBs to a block linear destination with linear GOBs.
|
||||
/// </summary>
|
||||
/// <param name="memoryManager">GPU memory manager</param>
|
||||
/// <param name="srcGpuVa">Source GPU virtual address</param>
|
||||
/// <param name="dstGpuVa">Destination GPU virtual address</param>
|
||||
/// <param name="size">Size in bytes of the copy</param>
|
||||
private static void CopyGobBlockLinearToLinear(MemoryManager memoryManager, ulong srcGpuVa, ulong dstGpuVa, ulong size)
|
||||
{
|
||||
if (((srcGpuVa | dstGpuVa | size) & 0xf) == 0)
|
||||
{
|
||||
for (ulong offset = 0; offset < size; offset += 16)
|
||||
{
|
||||
Vector128<byte> data = memoryManager.Read<Vector128<byte>>(ConvertGobLinearToBlockLinearAddress(srcGpuVa + offset), true);
|
||||
memoryManager.Write(dstGpuVa + offset, data);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (ulong offset = 0; offset < size; offset++)
|
||||
{
|
||||
byte data = memoryManager.Read<byte>(ConvertGobLinearToBlockLinearAddress(srcGpuVa + offset), true);
|
||||
memoryManager.Write(dstGpuVa + offset, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies block linear data with linear GOBs to a block linear destination with block linear GOBs.
|
||||
/// </summary>
|
||||
/// <param name="memoryManager">GPU memory manager</param>
|
||||
/// <param name="srcGpuVa">Source GPU virtual address</param>
|
||||
/// <param name="dstGpuVa">Destination GPU virtual address</param>
|
||||
/// <param name="size">Size in bytes of the copy</param>
|
||||
private static void CopyGobLinearToBlockLinear(MemoryManager memoryManager, ulong srcGpuVa, ulong dstGpuVa, ulong size)
|
||||
{
|
||||
if (((srcGpuVa | dstGpuVa | size) & 0xf) == 0)
|
||||
{
|
||||
for (ulong offset = 0; offset < size; offset += 16)
|
||||
{
|
||||
Vector128<byte> data = memoryManager.Read<Vector128<byte>>(srcGpuVa + offset, true);
|
||||
memoryManager.Write(ConvertGobLinearToBlockLinearAddress(dstGpuVa + offset), data);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (ulong offset = 0; offset < size; offset++)
|
||||
{
|
||||
byte data = memoryManager.Read<byte>(srcGpuVa + offset, true);
|
||||
memoryManager.Write(ConvertGobLinearToBlockLinearAddress(dstGpuVa + offset), data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the GOB block linear address from a linear address.
|
||||
/// </summary>
|
||||
/// <param name="address">Linear address</param>
|
||||
/// <returns>Block linear address</returns>
|
||||
private static ulong ConvertGobLinearToBlockLinearAddress(ulong address)
|
||||
{
|
||||
// y2 y1 y0 x5 x4 x3 x2 x1 x0 -> x5 y2 y1 x4 y0 x3 x2 x1 x0
|
||||
return (address & ~0x1f0UL) |
|
||||
((address & 0x40) >> 2) |
|
||||
((address & 0x10) << 1) |
|
||||
((address & 0x180) >> 1) |
|
||||
((address & 0x20) << 3);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a buffer to buffer, or buffer to texture copy, then optionally releases a semaphore.
|
||||
/// </summary>
|
||||
|
@ -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
|
||||
};
|
||||
}
|
||||
|
@ -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) },
|
||||
|
@ -7,7 +7,6 @@ using Ryujinx.Graphics.Gpu.Engine.Types;
|
||||
using Ryujinx.Graphics.Gpu.Image;
|
||||
using Ryujinx.Graphics.Gpu.Memory;
|
||||
using Ryujinx.Graphics.Texture;
|
||||
using Ryujinx.Memory;
|
||||
using Ryujinx.Memory.Range;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -362,7 +362,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
return DepthStencilMode.Depth;
|
||||
}
|
||||
|
||||
if (format == Format.D24X8Unorm || format == Format.D24UnormS8Uint)
|
||||
if (format == Format.D24UnormS8Uint)
|
||||
{
|
||||
return component == SwizzleComponent.Red
|
||||
? DepthStencilMode.Stencil
|
||||
|
@ -17,6 +17,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
private const ulong BufferAlignmentSize = 0x1000;
|
||||
private const ulong BufferAlignmentMask = BufferAlignmentSize - 1;
|
||||
|
||||
private const ulong MaxDynamicGrowthSize = 0x100000;
|
||||
|
||||
private readonly GpuContext _context;
|
||||
private readonly PhysicalMemory _physicalMemory;
|
||||
|
||||
@ -166,10 +168,35 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
// Otherwise, we must delete the overlapping buffers and create a bigger buffer
|
||||
// that fits all the data we need. We also need to copy the contents from the
|
||||
// old buffer(s) to the new buffer.
|
||||
|
||||
ulong endAddress = address + size;
|
||||
|
||||
if (_bufferOverlaps[0].Address > address || _bufferOverlaps[0].EndAddress < endAddress)
|
||||
{
|
||||
// Check if the following conditions are met:
|
||||
// - We have a single overlap.
|
||||
// - The overlap starts at or before the requested range. That is, the overlap happens at the end.
|
||||
// - The size delta between the new, merged buffer and the old one is of at most 2 pages.
|
||||
// In this case, we attempt to extend the buffer further than the requested range,
|
||||
// this can potentially avoid future resizes if the application keeps using overlapping
|
||||
// sequential memory.
|
||||
// Allowing for 2 pages (rather than just one) is necessary to catch cases where the
|
||||
// range crosses a page, and after alignment, ends having a size of 2 pages.
|
||||
if (overlapsCount == 1 &&
|
||||
address >= _bufferOverlaps[0].Address &&
|
||||
endAddress - _bufferOverlaps[0].EndAddress <= BufferAlignmentSize * 2)
|
||||
{
|
||||
// Try to grow the buffer by 1.5x of its current size.
|
||||
// This improves performance in the cases where the buffer is resized often by small amounts.
|
||||
ulong existingSize = _bufferOverlaps[0].Size;
|
||||
ulong growthSize = (existingSize + Math.Min(existingSize >> 1, MaxDynamicGrowthSize)) & ~BufferAlignmentMask;
|
||||
|
||||
size = Math.Max(size, growthSize);
|
||||
endAddress = address + size;
|
||||
|
||||
overlapsCount = _buffers.FindOverlapsNonOverlapping(address, size, ref _bufferOverlaps);
|
||||
}
|
||||
|
||||
for (int index = 0; index < overlapsCount; index++)
|
||||
{
|
||||
Buffer buffer = _bufferOverlaps[index];
|
||||
@ -183,7 +210,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
}
|
||||
}
|
||||
|
||||
Buffer newBuffer = new Buffer(_context, _physicalMemory, address, endAddress - address, _bufferOverlaps.Take(overlapsCount));
|
||||
ulong newSize = endAddress - address;
|
||||
|
||||
Buffer newBuffer = new Buffer(_context, _physicalMemory, address, newSize, _bufferOverlaps.Take(overlapsCount));
|
||||
|
||||
lock (_buffers)
|
||||
{
|
||||
@ -202,7 +231,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
buffer.DisposeData();
|
||||
}
|
||||
|
||||
newBuffer.SynchronizeMemory(address, endAddress - address);
|
||||
newBuffer.SynchronizeMemory(address, newSize);
|
||||
|
||||
// Existing buffers were modified, we need to rebind everything.
|
||||
NotifyBuffersModified?.Invoke();
|
||||
|
@ -255,6 +255,49 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes data to GPU mapped memory, stopping at the first unmapped page at the memory region, if any.
|
||||
/// </summary>
|
||||
/// <param name="va">GPU virtual address to write the data into</param>
|
||||
/// <param name="data">The data to be written</param>
|
||||
public void WriteMapped(ulong va, ReadOnlySpan<byte> data)
|
||||
{
|
||||
if (IsContiguous(va, data.Length))
|
||||
{
|
||||
Physical.Write(Translate(va), data);
|
||||
}
|
||||
else
|
||||
{
|
||||
int offset = 0, size;
|
||||
|
||||
if ((va & PageMask) != 0)
|
||||
{
|
||||
ulong pa = Translate(va);
|
||||
|
||||
size = Math.Min(data.Length, (int)PageSize - (int)(va & PageMask));
|
||||
|
||||
if (pa != PteUnmapped && Physical.IsMapped(pa))
|
||||
{
|
||||
Physical.Write(pa, data.Slice(0, size));
|
||||
}
|
||||
|
||||
offset += size;
|
||||
}
|
||||
|
||||
for (; offset < data.Length; offset += size)
|
||||
{
|
||||
ulong pa = Translate(va + (ulong)offset);
|
||||
|
||||
size = Math.Min(data.Length - offset, (int)PageSize);
|
||||
|
||||
if (pa != PteUnmapped && Physical.IsMapped(pa))
|
||||
{
|
||||
Physical.Write(pa, data.Slice(offset, size));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maps a given range of pages to the specified CPU virtual address.
|
||||
/// </summary>
|
||||
@ -264,7 +307,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// <param name="pa">CPU virtual address to map into</param>
|
||||
/// <param name="va">GPU virtual address to be mapped</param>
|
||||
/// <param name="size">Size in bytes of the mapping</param>
|
||||
public void Map(ulong pa, ulong va, ulong size)
|
||||
/// <param name="kind">Kind of the resource located at the mapping</param>
|
||||
public void Map(ulong pa, ulong va, ulong size, PteKind kind)
|
||||
{
|
||||
lock (_pageTable)
|
||||
{
|
||||
@ -272,7 +316,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
|
||||
for (ulong offset = 0; offset < size; offset += PageSize)
|
||||
{
|
||||
SetPte(va + offset, pa + offset);
|
||||
SetPte(va + offset, PackPte(pa + offset, kind));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -462,14 +506,37 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
return PteUnmapped;
|
||||
}
|
||||
|
||||
ulong baseAddress = GetPte(va);
|
||||
ulong pte = GetPte(va);
|
||||
|
||||
if (baseAddress == PteUnmapped)
|
||||
if (pte == PteUnmapped)
|
||||
{
|
||||
return PteUnmapped;
|
||||
}
|
||||
|
||||
return baseAddress + (va & PageMask);
|
||||
return UnpackPaFromPte(pte) + (va & PageMask);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the kind of a given memory page.
|
||||
/// This might indicate the type of resource that can be allocated on the page, and also texture tiling.
|
||||
/// </summary>
|
||||
/// <param name="va">GPU virtual address</param>
|
||||
/// <returns>Kind of the memory page</returns>
|
||||
public PteKind GetKind(ulong va)
|
||||
{
|
||||
if (!ValidateAddress(va))
|
||||
{
|
||||
return PteKind.Invalid;
|
||||
}
|
||||
|
||||
ulong pte = GetPte(va);
|
||||
|
||||
if (pte == PteUnmapped)
|
||||
{
|
||||
return PteKind.Invalid;
|
||||
}
|
||||
|
||||
return UnpackKindFromPte(pte);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -512,5 +579,36 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
|
||||
_pageTable[l0][l1] = pte;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a page table entry from a physical address and kind.
|
||||
/// </summary>
|
||||
/// <param name="pa">Physical address</param>
|
||||
/// <param name="kind">Kind</param>
|
||||
/// <returns>Page table entry</returns>
|
||||
private static ulong PackPte(ulong pa, PteKind kind)
|
||||
{
|
||||
return pa | ((ulong)kind << 56);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unpacks kind from a page table entry.
|
||||
/// </summary>
|
||||
/// <param name="pte">Page table entry</param>
|
||||
/// <returns>Kind</returns>
|
||||
private static PteKind UnpackKindFromPte(ulong pte)
|
||||
{
|
||||
return (PteKind)(pte >> 56);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unpacks physical address from a page table entry.
|
||||
/// </summary>
|
||||
/// <param name="pte">Page table entry</param>
|
||||
/// <returns>Physical address</returns>
|
||||
private static ulong UnpackPaFromPte(ulong pte)
|
||||
{
|
||||
return pte & 0xffffffffffffffUL;
|
||||
}
|
||||
}
|
||||
}
|
@ -340,6 +340,16 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
return _cpuMemory.BeginSmartGranularTracking(address, size, granularity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the page at a given address is mapped on CPU memory.
|
||||
/// </summary>
|
||||
/// <param name="address">CPU virtual address of the page to check</param>
|
||||
/// <returns>True if mapped, false otherwise</returns>
|
||||
public bool IsMapped(ulong address)
|
||||
{
|
||||
return _cpuMemory.IsMapped(address);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Release our reference to the CPU memory manager.
|
||||
/// </summary>
|
||||
|
268
Ryujinx.Graphics.Gpu/Memory/PteKind.cs
Normal file
268
Ryujinx.Graphics.Gpu/Memory/PteKind.cs
Normal file
@ -0,0 +1,268 @@
|
||||
namespace Ryujinx.Graphics.Gpu.Memory
|
||||
{
|
||||
/// <summary>
|
||||
/// Kind of the resource at the given memory mapping.
|
||||
/// </summary>
|
||||
public enum PteKind : byte
|
||||
{
|
||||
Invalid = 0xff,
|
||||
Pitch = 0x00,
|
||||
Z16 = 0x01,
|
||||
Z162C = 0x02,
|
||||
Z16MS22C = 0x03,
|
||||
Z16MS42C = 0x04,
|
||||
Z16MS82C = 0x05,
|
||||
Z16MS162C = 0x06,
|
||||
Z162Z = 0x07,
|
||||
Z16MS22Z = 0x08,
|
||||
Z16MS42Z = 0x09,
|
||||
Z16MS82Z = 0x0a,
|
||||
Z16MS162Z = 0x0b,
|
||||
Z162CZ = 0x36,
|
||||
Z16MS22CZ = 0x37,
|
||||
Z16MS42CZ = 0x38,
|
||||
Z16MS82CZ = 0x39,
|
||||
Z16MS162CZ = 0x5f,
|
||||
Z164CZ = 0x0c,
|
||||
Z16MS24CZ = 0x0d,
|
||||
Z16MS44CZ = 0x0e,
|
||||
Z16MS84CZ = 0x0f,
|
||||
Z16MS164CZ = 0x10,
|
||||
S8Z24 = 0x11,
|
||||
S8Z241Z = 0x12,
|
||||
S8Z24MS21Z = 0x13,
|
||||
S8Z24MS41Z = 0x14,
|
||||
S8Z24MS81Z = 0x15,
|
||||
S8Z24MS161Z = 0x16,
|
||||
S8Z242CZ = 0x17,
|
||||
S8Z24MS22CZ = 0x18,
|
||||
S8Z24MS42CZ = 0x19,
|
||||
S8Z24MS82CZ = 0x1a,
|
||||
S8Z24MS162CZ = 0x1b,
|
||||
S8Z242CS = 0x1c,
|
||||
S8Z24MS22CS = 0x1d,
|
||||
S8Z24MS42CS = 0x1e,
|
||||
S8Z24MS82CS = 0x1f,
|
||||
S8Z24MS162CS = 0x20,
|
||||
S8Z244CSZV = 0x21,
|
||||
S8Z24MS24CSZV = 0x22,
|
||||
S8Z24MS44CSZV = 0x23,
|
||||
S8Z24MS84CSZV = 0x24,
|
||||
S8Z24MS164CSZV = 0x25,
|
||||
V8Z24MS4VC12 = 0x26,
|
||||
V8Z24MS4VC4 = 0x27,
|
||||
V8Z24MS8VC8 = 0x28,
|
||||
V8Z24MS8VC24 = 0x29,
|
||||
V8Z24MS4VC121ZV = 0x2e,
|
||||
V8Z24MS4VC41ZV = 0x2f,
|
||||
V8Z24MS8VC81ZV = 0x30,
|
||||
V8Z24MS8VC241ZV = 0x31,
|
||||
V8Z24MS4VC122CS = 0x32,
|
||||
V8Z24MS4VC42CS = 0x33,
|
||||
V8Z24MS8VC82CS = 0x34,
|
||||
V8Z24MS8VC242CS = 0x35,
|
||||
V8Z24MS4VC122CZV = 0x3a,
|
||||
V8Z24MS4VC42CZV = 0x3b,
|
||||
V8Z24MS8VC82CZV = 0x3c,
|
||||
V8Z24MS8VC242CZV = 0x3d,
|
||||
V8Z24MS4VC122ZV = 0x3e,
|
||||
V8Z24MS4VC42ZV = 0x3f,
|
||||
V8Z24MS8VC82ZV = 0x40,
|
||||
V8Z24MS8VC242ZV = 0x41,
|
||||
V8Z24MS4VC124CSZV = 0x42,
|
||||
V8Z24MS4VC44CSZV = 0x43,
|
||||
V8Z24MS8VC84CSZV = 0x44,
|
||||
V8Z24MS8VC244CSZV = 0x45,
|
||||
Z24S8 = 0x46,
|
||||
Z24S81Z = 0x47,
|
||||
Z24S8MS21Z = 0x48,
|
||||
Z24S8MS41Z = 0x49,
|
||||
Z24S8MS81Z = 0x4a,
|
||||
Z24S8MS161Z = 0x4b,
|
||||
Z24S82CS = 0x4c,
|
||||
Z24S8MS22CS = 0x4d,
|
||||
Z24S8MS42CS = 0x4e,
|
||||
Z24S8MS82CS = 0x4f,
|
||||
Z24S8MS162CS = 0x50,
|
||||
Z24S82CZ = 0x51,
|
||||
Z24S8MS22CZ = 0x52,
|
||||
Z24S8MS42CZ = 0x53,
|
||||
Z24S8MS82CZ = 0x54,
|
||||
Z24S8MS162CZ = 0x55,
|
||||
Z24S84CSZV = 0x56,
|
||||
Z24S8MS24CSZV = 0x57,
|
||||
Z24S8MS44CSZV = 0x58,
|
||||
Z24S8MS84CSZV = 0x59,
|
||||
Z24S8MS164CSZV = 0x5a,
|
||||
Z24V8MS4VC12 = 0x5b,
|
||||
Z24V8MS4VC4 = 0x5c,
|
||||
Z24V8MS8VC8 = 0x5d,
|
||||
Z24V8MS8VC24 = 0x5e,
|
||||
YUVB8C12Y = 0x60,
|
||||
YUVB8C22Y = 0x61,
|
||||
YUVB10C12Y = 0x62,
|
||||
YUVB10C22Y = 0x6b,
|
||||
YUVB12C12Y = 0x6c,
|
||||
YUVB12C22Y = 0x6d,
|
||||
Z24V8MS4VC121ZV = 0x63,
|
||||
Z24V8MS4VC41ZV = 0x64,
|
||||
Z24V8MS8VC81ZV = 0x65,
|
||||
Z24V8MS8VC241ZV = 0x66,
|
||||
Z24V8MS4VC122CS = 0x67,
|
||||
Z24V8MS4VC42CS = 0x68,
|
||||
Z24V8MS8VC82CS = 0x69,
|
||||
Z24V8MS8VC242CS = 0x6a,
|
||||
Z24V8MS4VC122CZV = 0x6f,
|
||||
Z24V8MS4VC42CZV = 0x70,
|
||||
Z24V8MS8VC82CZV = 0x71,
|
||||
Z24V8MS8VC242CZV = 0x72,
|
||||
Z24V8MS4VC122ZV = 0x73,
|
||||
Z24V8MS4VC42ZV = 0x74,
|
||||
Z24V8MS8VC82ZV = 0x75,
|
||||
Z24V8MS8VC242ZV = 0x76,
|
||||
Z24V8MS4VC124CSZV = 0x77,
|
||||
Z24V8MS4VC44CSZV = 0x78,
|
||||
Z24V8MS8VC84CSZV = 0x79,
|
||||
Z24V8MS8VC244CSZV = 0x7a,
|
||||
ZF32 = 0x7b,
|
||||
ZF321Z = 0x7c,
|
||||
ZF32MS21Z = 0x7d,
|
||||
ZF32MS41Z = 0x7e,
|
||||
ZF32MS81Z = 0x7f,
|
||||
ZF32MS161Z = 0x80,
|
||||
ZF322CS = 0x81,
|
||||
ZF32MS22CS = 0x82,
|
||||
ZF32MS42CS = 0x83,
|
||||
ZF32MS82CS = 0x84,
|
||||
ZF32MS162CS = 0x85,
|
||||
ZF322CZ = 0x86,
|
||||
ZF32MS22CZ = 0x87,
|
||||
ZF32MS42CZ = 0x88,
|
||||
ZF32MS82CZ = 0x89,
|
||||
ZF32MS162CZ = 0x8a,
|
||||
X8Z24X16V8S8MS4VC12 = 0x8b,
|
||||
X8Z24X16V8S8MS4VC4 = 0x8c,
|
||||
X8Z24X16V8S8MS8VC8 = 0x8d,
|
||||
X8Z24X16V8S8MS8VC24 = 0x8e,
|
||||
X8Z24X16V8S8MS4VC121CS = 0x8f,
|
||||
X8Z24X16V8S8MS4VC41CS = 0x90,
|
||||
X8Z24X16V8S8MS8VC81CS = 0x91,
|
||||
X8Z24X16V8S8MS8VC241CS = 0x92,
|
||||
X8Z24X16V8S8MS4VC121ZV = 0x97,
|
||||
X8Z24X16V8S8MS4VC41ZV = 0x98,
|
||||
X8Z24X16V8S8MS8VC81ZV = 0x99,
|
||||
X8Z24X16V8S8MS8VC241ZV = 0x9a,
|
||||
X8Z24X16V8S8MS4VC121CZV = 0x9b,
|
||||
X8Z24X16V8S8MS4VC41CZV = 0x9c,
|
||||
X8Z24X16V8S8MS8VC81CZV = 0x9d,
|
||||
X8Z24X16V8S8MS8VC241CZV = 0x9e,
|
||||
X8Z24X16V8S8MS4VC122CS = 0x9f,
|
||||
X8Z24X16V8S8MS4VC42CS = 0xa0,
|
||||
X8Z24X16V8S8MS8VC82CS = 0xa1,
|
||||
X8Z24X16V8S8MS8VC242CS = 0xa2,
|
||||
X8Z24X16V8S8MS4VC122CSZV = 0xa3,
|
||||
X8Z24X16V8S8MS4VC42CSZV = 0xa4,
|
||||
X8Z24X16V8S8MS8VC82CSZV = 0xa5,
|
||||
X8Z24X16V8S8MS8VC242CSZV = 0xa6,
|
||||
ZF32X16V8S8MS4VC12 = 0xa7,
|
||||
ZF32X16V8S8MS4VC4 = 0xa8,
|
||||
ZF32X16V8S8MS8VC8 = 0xa9,
|
||||
ZF32X16V8S8MS8VC24 = 0xaa,
|
||||
ZF32X16V8S8MS4VC121CS = 0xab,
|
||||
ZF32X16V8S8MS4VC41CS = 0xac,
|
||||
ZF32X16V8S8MS8VC81CS = 0xad,
|
||||
ZF32X16V8S8MS8VC241CS = 0xae,
|
||||
ZF32X16V8S8MS4VC121ZV = 0xb3,
|
||||
ZF32X16V8S8MS4VC41ZV = 0xb4,
|
||||
ZF32X16V8S8MS8VC81ZV = 0xb5,
|
||||
ZF32X16V8S8MS8VC241ZV = 0xb6,
|
||||
ZF32X16V8S8MS4VC121CZV = 0xb7,
|
||||
ZF32X16V8S8MS4VC41CZV = 0xb8,
|
||||
ZF32X16V8S8MS8VC81CZV = 0xb9,
|
||||
ZF32X16V8S8MS8VC241CZV = 0xba,
|
||||
ZF32X16V8S8MS4VC122CS = 0xbb,
|
||||
ZF32X16V8S8MS4VC42CS = 0xbc,
|
||||
ZF32X16V8S8MS8VC82CS = 0xbd,
|
||||
ZF32X16V8S8MS8VC242CS = 0xbe,
|
||||
ZF32X16V8S8MS4VC122CSZV = 0xbf,
|
||||
ZF32X16V8S8MS4VC42CSZV = 0xc0,
|
||||
ZF32X16V8S8MS8VC82CSZV = 0xc1,
|
||||
ZF32X16V8S8MS8VC242CSZV = 0xc2,
|
||||
ZF32X24S8 = 0xc3,
|
||||
ZF32X24S81CS = 0xc4,
|
||||
ZF32X24S8MS21CS = 0xc5,
|
||||
ZF32X24S8MS41CS = 0xc6,
|
||||
ZF32X24S8MS81CS = 0xc7,
|
||||
ZF32X24S8MS161CS = 0xc8,
|
||||
ZF32X24S82CSZV = 0xce,
|
||||
ZF32X24S8MS22CSZV = 0xcf,
|
||||
ZF32X24S8MS42CSZV = 0xd0,
|
||||
ZF32X24S8MS82CSZV = 0xd1,
|
||||
ZF32X24S8MS162CSZV = 0xd2,
|
||||
ZF32X24S82CS = 0xd3,
|
||||
ZF32X24S8MS22CS = 0xd4,
|
||||
ZF32X24S8MS42CS = 0xd5,
|
||||
ZF32X24S8MS82CS = 0xd6,
|
||||
ZF32X24S8MS162CS = 0xd7,
|
||||
S8 = 0x2a,
|
||||
S82S = 0x2b,
|
||||
Generic16Bx2 = 0xfe,
|
||||
C322C = 0xd8,
|
||||
C322CBR = 0xd9,
|
||||
C322CBA = 0xda,
|
||||
C322CRA = 0xdb,
|
||||
C322BRA = 0xdc,
|
||||
C32MS22C = 0xdd,
|
||||
C32MS22CBR = 0xde,
|
||||
C32MS24CBRA = 0xcc,
|
||||
C32MS42C = 0xdf,
|
||||
C32MS42CBR = 0xe0,
|
||||
C32MS42CBA = 0xe1,
|
||||
C32MS42CRA = 0xe2,
|
||||
C32MS42BRA = 0xe3,
|
||||
C32MS44CBRA = 0x2c,
|
||||
C32MS8MS162C = 0xe4,
|
||||
C32MS8MS162CRA = 0xe5,
|
||||
C642C = 0xe6,
|
||||
C642CBR = 0xe7,
|
||||
C642CBA = 0xe8,
|
||||
C642CRA = 0xe9,
|
||||
C642BRA = 0xea,
|
||||
C64MS22C = 0xeb,
|
||||
C64MS22CBR = 0xec,
|
||||
C64MS24CBRA = 0xcd,
|
||||
C64MS42C = 0xed,
|
||||
C64MS42CBR = 0xee,
|
||||
C64MS42CBA = 0xef,
|
||||
C64MS42CRA = 0xf0,
|
||||
C64MS42BRA = 0xf1,
|
||||
C64MS44CBRA = 0x2d,
|
||||
C64MS8MS162C = 0xf2,
|
||||
C64MS8MS162CRA = 0xf3,
|
||||
C1282C = 0xf4,
|
||||
C1282CR = 0xf5,
|
||||
C128MS22C = 0xf6,
|
||||
C128MS22CR = 0xf7,
|
||||
C128MS42C = 0xf8,
|
||||
C128MS42CR = 0xf9,
|
||||
C128MS8MS162C = 0xfa,
|
||||
C128MS8MS162CR = 0xfb,
|
||||
X8C24 = 0xfc,
|
||||
PitchNoSwizzle = 0xfd,
|
||||
SmSkedMessage = 0xca,
|
||||
SmHostMessage = 0xcb
|
||||
}
|
||||
|
||||
static class PteKindExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Checks if the kind is pitch.
|
||||
/// </summary>
|
||||
/// <param name="kind">Kind to check</param>
|
||||
/// <returns>True if pitch, false otherwise</returns>
|
||||
public static bool IsPitch(this PteKind kind)
|
||||
{
|
||||
return kind == PteKind.Pitch || kind == PteKind.PitchNoSwizzle;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
namespace Ryujinx.Graphics.Gpu.Memory
|
||||
{
|
||||
/// <summary>
|
||||
/// Name of a GPU resource.
|
||||
/// </summary>
|
||||
public enum ResourceName
|
||||
{
|
||||
Buffer,
|
||||
Texture,
|
||||
TexturePool,
|
||||
SamplerPool
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -15,11 +15,13 @@ namespace Ryujinx.Graphics.Nvdec.FFmpeg
|
||||
public Plane UPlane => new Plane((IntPtr)Frame->data[1], UvStride * UvHeight);
|
||||
public Plane VPlane => new Plane((IntPtr)Frame->data[2], UvStride * UvHeight);
|
||||
|
||||
public FrameField Field => Frame->interlaced_frame != 0 ? FrameField.Interlaced : FrameField.Progressive;
|
||||
|
||||
public int Width => Frame->width;
|
||||
public int Height => Frame->height;
|
||||
public int Stride => Frame->linesize[0];
|
||||
public int UvWidth => (Frame->width + 1) >> 1;
|
||||
public int UvHeight => (Frame->height + 1) >> 1;
|
||||
public int UvWidth => (Width + 1) >> 1;
|
||||
public int UvHeight => (Height + 1) >> 1;
|
||||
public int UvStride => Frame->linesize[1];
|
||||
|
||||
public Surface(int width, int height)
|
||||
|
@ -486,8 +486,7 @@ namespace Ryujinx.Graphics.Nvdec.Vp9.Dsp
|
||||
Idct8(tempIn, tempOut);
|
||||
for (j = 0; j < 8; ++j)
|
||||
{
|
||||
dest[j * stride + i] = ClipPixelAdd(dest[j * stride + i],
|
||||
BitUtils.RoundPowerOfTwo(tempOut[j], 5));
|
||||
dest[j * stride + i] = ClipPixelAdd(dest[j * stride + i], BitUtils.RoundPowerOfTwo(tempOut[j], 5));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,8 @@ namespace Ryujinx.Graphics.Nvdec.Vp9.Types
|
||||
public unsafe Plane UPlane => new Plane((IntPtr)UBuffer.ToPointer(), UBuffer.Length);
|
||||
public unsafe Plane VPlane => new Plane((IntPtr)VBuffer.ToPointer(), VBuffer.Length);
|
||||
|
||||
public FrameField Field => FrameField.Progressive;
|
||||
|
||||
public int Width { get; }
|
||||
public int Height { get; }
|
||||
public int AlignedWidth { get; }
|
||||
|
@ -31,7 +31,24 @@ namespace Ryujinx.Graphics.Nvdec
|
||||
|
||||
if (decoder.Decode(ref info, outputSurface, bitstream))
|
||||
{
|
||||
SurfaceWriter.Write(rm.Gmm, outputSurface, lumaOffset, chromaOffset);
|
||||
if (outputSurface.Field == FrameField.Progressive)
|
||||
{
|
||||
SurfaceWriter.Write(
|
||||
rm.Gmm,
|
||||
outputSurface,
|
||||
lumaOffset + pictureInfo.LumaFrameOffset,
|
||||
chromaOffset + pictureInfo.ChromaFrameOffset);
|
||||
}
|
||||
else
|
||||
{
|
||||
SurfaceWriter.WriteInterlaced(
|
||||
rm.Gmm,
|
||||
outputSurface,
|
||||
lumaOffset + pictureInfo.LumaTopFieldOffset,
|
||||
chromaOffset + pictureInfo.ChromaTopFieldOffset,
|
||||
lumaOffset + pictureInfo.LumaBottomFieldOffset,
|
||||
chromaOffset + pictureInfo.ChromaBottomFieldOffset);
|
||||
}
|
||||
}
|
||||
|
||||
rm.Cache.Put(outputSurface);
|
||||
|
@ -16,7 +16,7 @@ namespace Ryujinx.Graphics.Nvdec.Image
|
||||
int width = surface.Width;
|
||||
int height = surface.Height;
|
||||
int stride = surface.Stride;
|
||||
|
||||
|
||||
ReadOnlySpan<byte> luma = gmm.DeviceGetSpan(lumaOffset, GetBlockLinearSize(width, height, 1));
|
||||
|
||||
ReadLuma(surface.YPlane.AsSpan(), luma, stride, width, height);
|
||||
|
@ -38,6 +38,55 @@ namespace Ryujinx.Graphics.Nvdec.Image
|
||||
surface.UvHeight);
|
||||
}
|
||||
|
||||
public static void WriteInterlaced(
|
||||
MemoryManager gmm,
|
||||
ISurface surface,
|
||||
uint lumaTopOffset,
|
||||
uint chromaTopOffset,
|
||||
uint lumaBottomOffset,
|
||||
uint chromaBottomOffset)
|
||||
{
|
||||
int lumaSize = GetBlockLinearSize(surface.Width, surface.Height / 2, 1);
|
||||
|
||||
using var lumaTop = gmm.GetWritableRegion(ExtendOffset(lumaTopOffset), lumaSize);
|
||||
using var lumaBottom = gmm.GetWritableRegion(ExtendOffset(lumaBottomOffset), lumaSize);
|
||||
|
||||
WriteLuma(
|
||||
lumaTop.Memory.Span,
|
||||
surface.YPlane.AsSpan(),
|
||||
surface.Stride * 2,
|
||||
surface.Width,
|
||||
surface.Height / 2);
|
||||
|
||||
WriteLuma(
|
||||
lumaBottom.Memory.Span,
|
||||
surface.YPlane.AsSpan().Slice(surface.Stride),
|
||||
surface.Stride * 2,
|
||||
surface.Width,
|
||||
surface.Height / 2);
|
||||
|
||||
int chromaSize = GetBlockLinearSize(surface.UvWidth, surface.UvHeight / 2, 2);
|
||||
|
||||
using var chromaTop = gmm.GetWritableRegion(ExtendOffset(chromaTopOffset), chromaSize);
|
||||
using var chromaBottom = gmm.GetWritableRegion(ExtendOffset(chromaBottomOffset), chromaSize);
|
||||
|
||||
WriteChroma(
|
||||
chromaTop.Memory.Span,
|
||||
surface.UPlane.AsSpan(),
|
||||
surface.VPlane.AsSpan(),
|
||||
surface.UvStride * 2,
|
||||
surface.UvWidth,
|
||||
surface.UvHeight / 2);
|
||||
|
||||
WriteChroma(
|
||||
chromaBottom.Memory.Span,
|
||||
surface.UPlane.AsSpan().Slice(surface.UvStride),
|
||||
surface.VPlane.AsSpan().Slice(surface.UvStride),
|
||||
surface.UvStride * 2,
|
||||
surface.UvWidth,
|
||||
surface.UvHeight / 2);
|
||||
}
|
||||
|
||||
private static void WriteLuma(Span<byte> dst, ReadOnlySpan<byte> src, int srcStride, int width, int height)
|
||||
{
|
||||
LayoutConverter.ConvertLinearToBlockLinear(dst, width, height, srcStride, 1, 2, src);
|
||||
|
@ -26,10 +26,10 @@ namespace Ryujinx.Graphics.Nvdec.Types.H264
|
||||
public uint Transform8x8ModeFlag;
|
||||
public uint LumaPitch;
|
||||
public uint ChromaPitch;
|
||||
public uint LumaTopOffset;
|
||||
public uint LumaBottomOffset;
|
||||
public uint LumaTopFieldOffset;
|
||||
public uint LumaBottomFieldOffset;
|
||||
public uint LumaFrameOffset;
|
||||
public uint ChromaTopOffset;
|
||||
public uint ChromaTopFieldOffset;
|
||||
public uint ChromaBottomFieldOffset;
|
||||
public uint ChromaFrameOffset;
|
||||
public uint HistBufferSize;
|
||||
|
@ -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)
|
||||
|
@ -612,7 +612,7 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
_program?.Bind();
|
||||
_unit0Sampler?.Bind(0);
|
||||
|
||||
GL.ViewportArray(0, 1, _viewportArray);
|
||||
RestoreViewport0();
|
||||
|
||||
Enable(EnableCap.CullFace, _cullEnable);
|
||||
Enable(EnableCap.StencilTest, _stencilTestEnable);
|
||||
@ -1478,6 +1478,11 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
_currentComponentMasks |= componentMaskAtIndex;
|
||||
}
|
||||
|
||||
public void RestoreClipControl()
|
||||
{
|
||||
GL.ClipControl(_clipOrigin, _clipDepthMode);
|
||||
}
|
||||
|
||||
public void RestoreScissor0Enable()
|
||||
{
|
||||
if ((_scissorEnables & 1u) != 0)
|
||||
@ -1494,6 +1499,14 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
}
|
||||
}
|
||||
|
||||
public void RestoreViewport0()
|
||||
{
|
||||
if (_viewportArray.Length > 0)
|
||||
{
|
||||
GL.ViewportArray(0, 1, _viewportArray);
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryHostConditionalRendering(ICounterEvent value, ulong compare, bool isEqual)
|
||||
{
|
||||
if (value is CounterQueueEvent)
|
||||
|
@ -27,11 +27,12 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
{
|
||||
GL.Disable(EnableCap.FramebufferSrgb);
|
||||
|
||||
CopyTextureToFrameBufferRGB(0, GetCopyFramebufferHandleLazy(), (TextureView)texture, crop);
|
||||
CopyTextureToFrameBufferRGB(0, GetCopyFramebufferHandleLazy(), (TextureView)texture, crop, swapBuffersCallback);
|
||||
|
||||
GL.Enable(EnableCap.FramebufferSrgb);
|
||||
|
||||
swapBuffersCallback();
|
||||
// Restore unpack alignment to 4, as performance overlays such as RTSS may change this to load their resources.
|
||||
GL.PixelStore(PixelStoreParameter.UnpackAlignment, 4);
|
||||
}
|
||||
|
||||
public void SetSize(int width, int height)
|
||||
@ -40,7 +41,7 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
_height = height;
|
||||
}
|
||||
|
||||
private void CopyTextureToFrameBufferRGB(int drawFramebuffer, int readFramebuffer, TextureView view, ImageCrop crop)
|
||||
private void CopyTextureToFrameBufferRGB(int drawFramebuffer, int readFramebuffer, TextureView view, ImageCrop crop, Action swapBuffersCallback)
|
||||
{
|
||||
(int oldDrawFramebufferHandle, int oldReadFramebufferHandle) = ((Pipeline)_renderer.Pipeline).GetBoundFramebuffers();
|
||||
|
||||
@ -139,11 +140,20 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
((Pipeline)_renderer.Pipeline).RestoreComponentMask(i);
|
||||
}
|
||||
|
||||
// Set clip control, viewport and the framebuffer to the output to placate overlays and OBS capture.
|
||||
GL.ClipControl(ClipOrigin.LowerLeft, ClipDepthMode.NegativeOneToOne);
|
||||
GL.Viewport(0, 0, _width, _height);
|
||||
GL.BindFramebuffer(FramebufferTarget.Framebuffer, drawFramebuffer);
|
||||
|
||||
swapBuffersCallback();
|
||||
|
||||
GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, oldReadFramebufferHandle);
|
||||
GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, oldDrawFramebufferHandle);
|
||||
|
||||
((Pipeline)_renderer.Pipeline).RestoreClipControl();
|
||||
((Pipeline)_renderer.Pipeline).RestoreScissor0Enable();
|
||||
((Pipeline)_renderer.Pipeline).RestoreRasterizerDiscard();
|
||||
((Pipeline)_renderer.Pipeline).RestoreViewport0();
|
||||
|
||||
if (viewConverted != view)
|
||||
{
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -48,38 +48,10 @@ namespace Ryujinx.Graphics.Vic
|
||||
|
||||
int one = 1 << (mtx.MatrixRShift + 8);
|
||||
|
||||
|
||||
// NOTE: This is buggy on .NET 5.0.100, we use a workaround for now (see https://github.com/dotnet/runtime/issues/44704)
|
||||
// TODO: Uncomment this when fixed.
|
||||
//Vector128<int> col1 = Vector128.Create(mtx.MatrixCoeff00, mtx.MatrixCoeff10, mtx.MatrixCoeff20, 0);
|
||||
//Vector128<int> col2 = Vector128.Create(mtx.MatrixCoeff01, mtx.MatrixCoeff11, mtx.MatrixCoeff21, 0);
|
||||
//Vector128<int> col3 = Vector128.Create(mtx.MatrixCoeff02, mtx.MatrixCoeff12, mtx.MatrixCoeff22, one);
|
||||
//Vector128<int> col4 = Vector128.Create(mtx.MatrixCoeff03, mtx.MatrixCoeff13, mtx.MatrixCoeff23, 0);
|
||||
|
||||
Vector128<int> col1 = new Vector128<int>();
|
||||
Vector128<int> col2 = new Vector128<int>();
|
||||
Vector128<int> col3 = new Vector128<int>();
|
||||
Vector128<int> col4 = new Vector128<int>();
|
||||
|
||||
col1 = Sse41.Insert(col1, mtx.MatrixCoeff00, 0);
|
||||
col1 = Sse41.Insert(col1, mtx.MatrixCoeff10, 1);
|
||||
col1 = Sse41.Insert(col1, mtx.MatrixCoeff20, 2);
|
||||
col1 = Sse41.Insert(col1, 0, 3);
|
||||
|
||||
col2 = Sse41.Insert(col2, mtx.MatrixCoeff01, 0);
|
||||
col2 = Sse41.Insert(col2, mtx.MatrixCoeff11, 1);
|
||||
col2 = Sse41.Insert(col2, mtx.MatrixCoeff21, 2);
|
||||
col2 = Sse41.Insert(col2, 0, 3);
|
||||
|
||||
col3 = Sse41.Insert(col3, mtx.MatrixCoeff02, 0);
|
||||
col3 = Sse41.Insert(col3, mtx.MatrixCoeff12, 1);
|
||||
col3 = Sse41.Insert(col3, mtx.MatrixCoeff22, 2);
|
||||
col3 = Sse41.Insert(col3, one, 3);
|
||||
|
||||
col4 = Sse41.Insert(col4, mtx.MatrixCoeff03, 0);
|
||||
col4 = Sse41.Insert(col4, mtx.MatrixCoeff13, 1);
|
||||
col4 = Sse41.Insert(col4, mtx.MatrixCoeff23, 2);
|
||||
col4 = Sse41.Insert(col4, 0, 3);
|
||||
Vector128<int> col1 = Vector128.Create(mtx.MatrixCoeff00, mtx.MatrixCoeff10, mtx.MatrixCoeff20, 0);
|
||||
Vector128<int> col2 = Vector128.Create(mtx.MatrixCoeff01, mtx.MatrixCoeff11, mtx.MatrixCoeff21, 0);
|
||||
Vector128<int> col3 = Vector128.Create(mtx.MatrixCoeff02, mtx.MatrixCoeff12, mtx.MatrixCoeff22, one);
|
||||
Vector128<int> col4 = Vector128.Create(mtx.MatrixCoeff03, mtx.MatrixCoeff13, mtx.MatrixCoeff23, 0);
|
||||
|
||||
Vector128<int> rShift = Vector128.CreateScalar(mtx.MatrixRShift);
|
||||
Vector128<ushort> clMin = Vector128.Create((ushort)slot.SlotConfig.SoftClampLow);
|
||||
|
@ -14,7 +14,7 @@ namespace Ryujinx.Graphics.Vic.Image
|
||||
/// If the required buffer is larger than this, it won't be
|
||||
/// added to the pool to avoid long term high memory usage.
|
||||
/// </summary>
|
||||
private const int MaxBufferSize = 2048 * 1280;
|
||||
private const int MaxBufferSize = 2048 * 2048;
|
||||
|
||||
private struct PoolItem
|
||||
{
|
||||
|
@ -2,16 +2,85 @@
|
||||
|
||||
namespace Ryujinx.Graphics.Vic.Image
|
||||
{
|
||||
ref struct RentedBuffer
|
||||
{
|
||||
public static RentedBuffer Empty => new RentedBuffer(Span<byte>.Empty, -1);
|
||||
|
||||
public Span<byte> Data;
|
||||
public int Index;
|
||||
|
||||
public RentedBuffer(Span<byte> data, int index)
|
||||
{
|
||||
Data = data;
|
||||
Index = index;
|
||||
}
|
||||
|
||||
public void Return(BufferPool<byte> pool)
|
||||
{
|
||||
if (Index != -1)
|
||||
{
|
||||
pool.Return(Index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ref struct InputSurface
|
||||
{
|
||||
public ReadOnlySpan<byte> Buffer0;
|
||||
public ReadOnlySpan<byte> Buffer1;
|
||||
public ReadOnlySpan<byte> Buffer2;
|
||||
|
||||
public int Buffer0Index;
|
||||
public int Buffer1Index;
|
||||
public int Buffer2Index;
|
||||
|
||||
public int Width;
|
||||
public int Height;
|
||||
|
||||
public int UvWidth;
|
||||
public int UvHeight;
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
Buffer0Index = -1;
|
||||
Buffer1Index = -1;
|
||||
Buffer2Index = -1;
|
||||
}
|
||||
|
||||
public void SetBuffer0(RentedBuffer buffer)
|
||||
{
|
||||
Buffer0 = buffer.Data;
|
||||
Buffer0Index = buffer.Index;
|
||||
}
|
||||
|
||||
public void SetBuffer1(RentedBuffer buffer)
|
||||
{
|
||||
Buffer1 = buffer.Data;
|
||||
Buffer1Index = buffer.Index;
|
||||
}
|
||||
|
||||
public void SetBuffer2(RentedBuffer buffer)
|
||||
{
|
||||
Buffer2 = buffer.Data;
|
||||
Buffer2Index = buffer.Index;
|
||||
}
|
||||
|
||||
public void Return(BufferPool<byte> pool)
|
||||
{
|
||||
if (Buffer0Index != -1)
|
||||
{
|
||||
pool.Return(Buffer0Index);
|
||||
}
|
||||
|
||||
if (Buffer1Index != -1)
|
||||
{
|
||||
pool.Return(Buffer1Index);
|
||||
}
|
||||
|
||||
if (Buffer2Index != -1)
|
||||
{
|
||||
pool.Return(Buffer2Index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Graphics.Gpu.Memory;
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Graphics.Texture;
|
||||
using Ryujinx.Graphics.Vic.Types;
|
||||
using System;
|
||||
@ -12,24 +12,32 @@ namespace Ryujinx.Graphics.Vic.Image
|
||||
{
|
||||
static class SurfaceReader
|
||||
{
|
||||
public static Surface Read(ResourceManager rm, ref SlotSurfaceConfig config, ref PlaneOffsets offsets)
|
||||
public static Surface Read(
|
||||
ResourceManager rm,
|
||||
ref SlotConfig config,
|
||||
ref SlotSurfaceConfig surfaceConfig,
|
||||
ref Array8<PlaneOffsets> offsets)
|
||||
{
|
||||
switch (config.SlotPixelFormat)
|
||||
switch (surfaceConfig.SlotPixelFormat)
|
||||
{
|
||||
case PixelFormat.Y8___V8U8_N420: return ReadNv12(rm, ref config, ref offsets);
|
||||
case PixelFormat.Y8___V8U8_N420: return ReadNv12(rm, ref config, ref surfaceConfig, ref offsets);
|
||||
}
|
||||
|
||||
Logger.Error?.Print(LogClass.Vic, $"Unsupported pixel format \"{config.SlotPixelFormat}\".");
|
||||
Logger.Error?.Print(LogClass.Vic, $"Unsupported pixel format \"{surfaceConfig.SlotPixelFormat}\".");
|
||||
|
||||
int lw = config.SlotLumaWidth + 1;
|
||||
int lh = config.SlotLumaHeight + 1;
|
||||
int lw = surfaceConfig.SlotLumaWidth + 1;
|
||||
int lh = surfaceConfig.SlotLumaHeight + 1;
|
||||
|
||||
return new Surface(rm.SurfacePool, lw, lh);
|
||||
}
|
||||
|
||||
private unsafe static Surface ReadNv12(ResourceManager rm, ref SlotSurfaceConfig config, ref PlaneOffsets offsets)
|
||||
private unsafe static Surface ReadNv12(
|
||||
ResourceManager rm,
|
||||
ref SlotConfig config,
|
||||
ref SlotSurfaceConfig surfaceConfig,
|
||||
ref Array8<PlaneOffsets> offsets)
|
||||
{
|
||||
InputSurface input = ReadSurface(rm.Gmm, ref config, ref offsets, 1, 2);
|
||||
InputSurface input = ReadSurface(rm, ref config, ref surfaceConfig, ref offsets, 1, 2);
|
||||
|
||||
int width = input.Width;
|
||||
int height = input.Height;
|
||||
@ -160,6 +168,8 @@ namespace Ryujinx.Graphics.Vic.Image
|
||||
}
|
||||
}
|
||||
|
||||
input.Return(rm.BufferPool);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
@ -170,84 +180,227 @@ namespace Ryujinx.Graphics.Vic.Image
|
||||
}
|
||||
|
||||
private static InputSurface ReadSurface(
|
||||
MemoryManager gmm,
|
||||
ref SlotSurfaceConfig config,
|
||||
ref PlaneOffsets offsets,
|
||||
ResourceManager rm,
|
||||
ref SlotConfig config,
|
||||
ref SlotSurfaceConfig surfaceConfig,
|
||||
ref Array8<PlaneOffsets> offsets,
|
||||
int bytesPerPixel,
|
||||
int planes)
|
||||
{
|
||||
InputSurface surface = new InputSurface();
|
||||
|
||||
int gobBlocksInY = 1 << config.SlotBlkHeight;
|
||||
surface.Initialize();
|
||||
|
||||
bool linear = config.SlotBlkKind == 0;
|
||||
int gobBlocksInY = 1 << surfaceConfig.SlotBlkHeight;
|
||||
|
||||
int lw = config.SlotLumaWidth + 1;
|
||||
int lh = config.SlotLumaHeight + 1;
|
||||
bool linear = surfaceConfig.SlotBlkKind == 0;
|
||||
|
||||
int cw = config.SlotChromaWidth + 1;
|
||||
int ch = config.SlotChromaHeight + 1;
|
||||
int lw = surfaceConfig.SlotLumaWidth + 1;
|
||||
int lh = surfaceConfig.SlotLumaHeight + 1;
|
||||
|
||||
int cw = surfaceConfig.SlotChromaWidth + 1;
|
||||
int ch = surfaceConfig.SlotChromaHeight + 1;
|
||||
|
||||
// Interlaced inputs have double the height when deinterlaced.
|
||||
int heightShift = config.FrameFormat.IsField() ? 1 : 0;
|
||||
|
||||
surface.Width = lw;
|
||||
surface.Height = lh;
|
||||
surface.Height = lh << heightShift;
|
||||
surface.UvWidth = cw;
|
||||
surface.UvHeight = ch;
|
||||
surface.UvHeight = ch << heightShift;
|
||||
|
||||
if (planes > 0)
|
||||
{
|
||||
surface.Buffer0 = ReadBuffer(gmm, offsets.LumaOffset, linear, lw, lh, bytesPerPixel, gobBlocksInY);
|
||||
surface.SetBuffer0(ReadBuffer(rm, ref config, ref offsets, linear, 0, lw, lh, bytesPerPixel, gobBlocksInY));
|
||||
}
|
||||
|
||||
if (planes > 1)
|
||||
{
|
||||
surface.Buffer1 = ReadBuffer(gmm, offsets.ChromaUOffset, linear, cw, ch, planes == 2 ? 2 : 1, gobBlocksInY);
|
||||
surface.SetBuffer1(ReadBuffer(rm, ref config, ref offsets, linear, 1, cw, ch, planes == 2 ? 2 : 1, gobBlocksInY));
|
||||
}
|
||||
|
||||
if (planes > 2)
|
||||
{
|
||||
surface.Buffer2 = ReadBuffer(gmm, offsets.ChromaVOffset, linear, cw, ch, 1, gobBlocksInY);
|
||||
surface.SetBuffer2(ReadBuffer(rm, ref config, ref offsets, linear, 2, cw, ch, 1, gobBlocksInY));
|
||||
}
|
||||
|
||||
return surface;
|
||||
}
|
||||
|
||||
private static ReadOnlySpan<byte> ReadBuffer(
|
||||
MemoryManager gmm,
|
||||
uint offset,
|
||||
private static RentedBuffer ReadBuffer(
|
||||
ResourceManager rm,
|
||||
ref SlotConfig config,
|
||||
ref Array8<PlaneOffsets> offsets,
|
||||
bool linear,
|
||||
int plane,
|
||||
int width,
|
||||
int height,
|
||||
int bytesPerPixel,
|
||||
int gobBlocksInY)
|
||||
{
|
||||
FrameFormat frameFormat = config.FrameFormat;
|
||||
bool isLuma = plane == 0;
|
||||
bool isField = frameFormat.IsField();
|
||||
bool isTopField = frameFormat.IsTopField(isLuma);
|
||||
int stride = GetPitch(width, bytesPerPixel);
|
||||
uint offset = GetOffset(ref offsets[0], plane);
|
||||
|
||||
int dstStart = 0;
|
||||
int dstStride = stride;
|
||||
|
||||
if (isField)
|
||||
{
|
||||
dstStart = isTopField ? 0 : stride;
|
||||
dstStride = stride * 2;
|
||||
}
|
||||
|
||||
RentedBuffer buffer;
|
||||
|
||||
if (linear)
|
||||
{
|
||||
buffer = ReadBufferLinear(rm, offset, width, height, dstStart, dstStride, bytesPerPixel);
|
||||
}
|
||||
else
|
||||
{
|
||||
buffer = ReadBufferBlockLinear(rm, offset, width, height, dstStart, dstStride, bytesPerPixel, gobBlocksInY);
|
||||
}
|
||||
|
||||
if (isField || frameFormat.IsInterlaced())
|
||||
{
|
||||
RentedBuffer prevBuffer = RentedBuffer.Empty;
|
||||
RentedBuffer nextBuffer = RentedBuffer.Empty;
|
||||
|
||||
if (config.PrevFieldEnable)
|
||||
{
|
||||
prevBuffer = ReadBufferNoDeinterlace(rm, ref offsets[1], linear, plane, width, height, bytesPerPixel, gobBlocksInY);
|
||||
}
|
||||
|
||||
if (config.NextFieldEnable)
|
||||
{
|
||||
nextBuffer = ReadBufferNoDeinterlace(rm, ref offsets[2], linear, plane, width, height, bytesPerPixel, gobBlocksInY);
|
||||
}
|
||||
|
||||
int w = width * bytesPerPixel;
|
||||
|
||||
switch (config.DeinterlaceMode)
|
||||
{
|
||||
case DeinterlaceMode.Weave:
|
||||
Scaler.DeinterlaceWeave(buffer.Data, prevBuffer.Data, w, stride, isTopField);
|
||||
break;
|
||||
case DeinterlaceMode.BobField:
|
||||
Scaler.DeinterlaceBob(buffer.Data, w, stride, isTopField);
|
||||
break;
|
||||
case DeinterlaceMode.Bob:
|
||||
bool isCurrentTop = isLuma ? config.IsEven : config.ChromaEven;
|
||||
Scaler.DeinterlaceBob(buffer.Data, w, stride, isCurrentTop ^ frameFormat.IsInterlacedBottomFirst());
|
||||
break;
|
||||
case DeinterlaceMode.NewBob:
|
||||
case DeinterlaceMode.Disi1:
|
||||
Scaler.DeinterlaceMotionAdaptive(buffer.Data, prevBuffer.Data, nextBuffer.Data, w, stride, isTopField);
|
||||
break;
|
||||
case DeinterlaceMode.WeaveLumaBobFieldChroma:
|
||||
if (isLuma)
|
||||
{
|
||||
Scaler.DeinterlaceWeave(buffer.Data, prevBuffer.Data, w, stride, isTopField);
|
||||
}
|
||||
else
|
||||
{
|
||||
Scaler.DeinterlaceBob(buffer.Data, w, stride, isTopField);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
Logger.Error?.Print(LogClass.Vic, $"Unsupported deinterlace mode \"{config.DeinterlaceMode}\".");
|
||||
break;
|
||||
}
|
||||
|
||||
prevBuffer.Return(rm.BufferPool);
|
||||
nextBuffer.Return(rm.BufferPool);
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
private static uint GetOffset(ref PlaneOffsets offsets, int plane)
|
||||
{
|
||||
return plane switch
|
||||
{
|
||||
0 => offsets.LumaOffset,
|
||||
1 => offsets.ChromaUOffset,
|
||||
2 => offsets.ChromaVOffset,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(plane))
|
||||
};
|
||||
}
|
||||
|
||||
private static RentedBuffer ReadBufferNoDeinterlace(
|
||||
ResourceManager rm,
|
||||
ref PlaneOffsets offsets,
|
||||
bool linear,
|
||||
int plane,
|
||||
int width,
|
||||
int height,
|
||||
int bytesPerPixel,
|
||||
int gobBlocksInY)
|
||||
{
|
||||
int stride = GetPitch(width, bytesPerPixel);
|
||||
uint offset = GetOffset(ref offsets, plane);
|
||||
|
||||
if (linear)
|
||||
{
|
||||
return gmm.GetSpan(ExtendOffset(offset), stride * height);
|
||||
return ReadBufferLinear(rm, offset, width, height, 0, stride, bytesPerPixel);
|
||||
}
|
||||
|
||||
return ReadBuffer(gmm, offset, width, height, stride, bytesPerPixel, gobBlocksInY);
|
||||
return ReadBufferBlockLinear(rm, offset, width, height, 0, stride, bytesPerPixel, gobBlocksInY);
|
||||
}
|
||||
|
||||
private static ReadOnlySpan<byte> ReadBuffer(
|
||||
MemoryManager gmm,
|
||||
private static RentedBuffer ReadBufferLinear(
|
||||
ResourceManager rm,
|
||||
uint offset,
|
||||
int width,
|
||||
int height,
|
||||
int dstStart,
|
||||
int dstStride,
|
||||
int bytesPerPixel)
|
||||
{
|
||||
int srcStride = GetPitch(width, bytesPerPixel);
|
||||
int inSize = srcStride * height;
|
||||
|
||||
ReadOnlySpan<byte> src = rm.Gmm.GetSpan(ExtendOffset(offset), inSize);
|
||||
|
||||
int outSize = dstStride * height;
|
||||
int bufferIndex = rm.BufferPool.RentMinimum(outSize, out byte[] buffer);
|
||||
Span<byte> dst = buffer;
|
||||
dst = dst.Slice(0, outSize);
|
||||
|
||||
for (int y = 0; y < height; y++)
|
||||
{
|
||||
src.Slice(y * srcStride, srcStride).CopyTo(dst.Slice(dstStart + y * dstStride, srcStride));
|
||||
}
|
||||
|
||||
return new RentedBuffer(dst, bufferIndex);
|
||||
}
|
||||
|
||||
private static RentedBuffer ReadBufferBlockLinear(
|
||||
ResourceManager rm,
|
||||
uint offset,
|
||||
int width,
|
||||
int height,
|
||||
int dstStart,
|
||||
int dstStride,
|
||||
int bytesPerPixel,
|
||||
int gobBlocksInY)
|
||||
{
|
||||
int inSize = GetBlockLinearSize(width, height, bytesPerPixel, gobBlocksInY);
|
||||
|
||||
ReadOnlySpan<byte> src = gmm.GetSpan(ExtendOffset(offset), inSize);
|
||||
ReadOnlySpan<byte> src = rm.Gmm.GetSpan(ExtendOffset(offset), inSize);
|
||||
|
||||
Span<byte> dst = new byte[dstStride * height];
|
||||
int outSize = dstStride * height;
|
||||
int bufferIndex = rm.BufferPool.RentMinimum(outSize, out byte[] buffer);
|
||||
Span<byte> dst = buffer;
|
||||
dst = dst.Slice(0, outSize);
|
||||
|
||||
LayoutConverter.ConvertBlockLinearToLinear(dst, width, height, dstStride, bytesPerPixel, gobBlocksInY, src);
|
||||
LayoutConverter.ConvertBlockLinearToLinear(dst.Slice(dstStart), width, height, dstStride, bytesPerPixel, gobBlocksInY, src);
|
||||
|
||||
return dst;
|
||||
return new RentedBuffer(dst, bufferIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ namespace Ryujinx.Graphics.Vic.Image
|
||||
switch (config.OutPixelFormat)
|
||||
{
|
||||
case PixelFormat.A8B8G8R8:
|
||||
case PixelFormat.X8B8G8R8:
|
||||
case PixelFormat.X8B8G8R8:
|
||||
WriteA8B8G8R8(rm, input, ref config, ref offsets);
|
||||
break;
|
||||
case PixelFormat.A8R8G8B8:
|
||||
@ -433,7 +433,7 @@ namespace Ryujinx.Graphics.Vic.Image
|
||||
{
|
||||
if (linear)
|
||||
{
|
||||
rm.Gmm.Write(ExtendOffset(offset), src);
|
||||
rm.Gmm.WriteMapped(ExtendOffset(offset), src);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -456,7 +456,7 @@ namespace Ryujinx.Graphics.Vic.Image
|
||||
|
||||
LayoutConverter.ConvertLinearToBlockLinear(dst, width, height, dstStride, bytesPerPixel, gobBlocksInY, src);
|
||||
|
||||
rm.Gmm.Write(ExtendOffset(offset), dst);
|
||||
rm.Gmm.WriteMapped(ExtendOffset(offset), dst);
|
||||
|
||||
rm.BufferPool.Return(dstIndex);
|
||||
}
|
||||
|
124
Ryujinx.Graphics.Vic/Scaler.cs
Normal file
124
Ryujinx.Graphics.Vic/Scaler.cs
Normal file
@ -0,0 +1,124 @@
|
||||
using System;
|
||||
using System.Runtime.Intrinsics;
|
||||
using System.Runtime.Intrinsics.X86;
|
||||
|
||||
namespace Ryujinx.Graphics.Vic
|
||||
{
|
||||
static class Scaler
|
||||
{
|
||||
public static void DeinterlaceWeave(Span<byte> data, ReadOnlySpan<byte> prevData, int width, int fieldSize, bool isTopField)
|
||||
{
|
||||
// Prev I Curr I Curr P
|
||||
// TTTTTTTT BBBBBBBB TTTTTTTT
|
||||
// -------- -------- BBBBBBBB
|
||||
|
||||
if (isTopField)
|
||||
{
|
||||
for (int offset = 0; offset < data.Length; offset += fieldSize * 2)
|
||||
{
|
||||
prevData.Slice(offset >> 1, width).CopyTo(data.Slice(offset + fieldSize, width));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int offset = 0; offset < data.Length; offset += fieldSize * 2)
|
||||
{
|
||||
prevData.Slice(offset >> 1, width).CopyTo(data.Slice(offset, width));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void DeinterlaceBob(Span<byte> data, int width, int fieldSize, bool isTopField)
|
||||
{
|
||||
// Curr I Curr P
|
||||
// TTTTTTTT TTTTTTTT
|
||||
// -------- TTTTTTTT
|
||||
|
||||
if (isTopField)
|
||||
{
|
||||
for (int offset = 0; offset < data.Length; offset += fieldSize * 2)
|
||||
{
|
||||
data.Slice(offset, width).CopyTo(data.Slice(offset + fieldSize, width));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int offset = 0; offset < data.Length; offset += fieldSize * 2)
|
||||
{
|
||||
data.Slice(offset + fieldSize, width).CopyTo(data.Slice(offset, width));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe static void DeinterlaceMotionAdaptive(
|
||||
Span<byte> data,
|
||||
ReadOnlySpan<byte> prevData,
|
||||
ReadOnlySpan<byte> nextData,
|
||||
int width,
|
||||
int fieldSize,
|
||||
bool isTopField)
|
||||
{
|
||||
// Very simple motion adaptive algorithm.
|
||||
// If the pixel changed between previous and next frame, use Bob, otherwise use Weave.
|
||||
//
|
||||
// Example pseudo code:
|
||||
// C_even = (P_even == N_even) ? P_even : C_odd
|
||||
// Where: C is current frame, P is previous frame and N is next frame, and even/odd are the fields.
|
||||
//
|
||||
// Note: This does not fully match the hardware algorithm.
|
||||
// The motion adaptive deinterlacing implemented on hardware is considerably more complex,
|
||||
// and hard to implement accurately without proper documentation as for example, the
|
||||
// method used for motion estimation is unknown.
|
||||
|
||||
int start = isTopField ? fieldSize : 0;
|
||||
int otherFieldOffset = isTopField ? -fieldSize : fieldSize;
|
||||
|
||||
fixed (byte* pData = data, pPrevData = prevData, pNextData = nextData)
|
||||
{
|
||||
for (int offset = start; offset < data.Length; offset += fieldSize * 2)
|
||||
{
|
||||
int refOffset = (offset - start) >> 1;
|
||||
int x = 0;
|
||||
|
||||
if (Avx2.IsSupported)
|
||||
{
|
||||
for (; x < (width & ~0x1f); x += 32)
|
||||
{
|
||||
Vector256<byte> prevPixels = Avx.LoadVector256(pPrevData + refOffset + x);
|
||||
Vector256<byte> nextPixels = Avx.LoadVector256(pNextData + refOffset + x);
|
||||
Vector256<byte> bob = Avx.LoadVector256(pData + offset + otherFieldOffset + x);
|
||||
Vector256<byte> diff = Avx2.CompareEqual(prevPixels, nextPixels);
|
||||
Avx.Store(pData + offset + x, Avx2.BlendVariable(bob, prevPixels, diff));
|
||||
}
|
||||
}
|
||||
else if (Sse41.IsSupported)
|
||||
{
|
||||
for (; x < (width & ~0xf); x += 16)
|
||||
{
|
||||
Vector128<byte> prevPixels = Sse2.LoadVector128(pPrevData + refOffset + x);
|
||||
Vector128<byte> nextPixels = Sse2.LoadVector128(pNextData + refOffset + x);
|
||||
Vector128<byte> bob = Sse2.LoadVector128(pData + offset + otherFieldOffset + x);
|
||||
Vector128<byte> diff = Sse2.CompareEqual(prevPixels, nextPixels);
|
||||
Sse2.Store(pData + offset + x, Sse41.BlendVariable(bob, prevPixels, diff));
|
||||
}
|
||||
}
|
||||
|
||||
for (; x < width; x++)
|
||||
{
|
||||
byte prevPixel = prevData[refOffset + x];
|
||||
byte nextPixel = nextData[refOffset + x];
|
||||
|
||||
if (nextPixel != prevPixel)
|
||||
{
|
||||
data[offset + x] = data[offset + otherFieldOffset + x];
|
||||
}
|
||||
else
|
||||
{
|
||||
data[offset + x] = prevPixel;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
12
Ryujinx.Graphics.Vic/Types/DeinterlaceMode.cs
Normal file
12
Ryujinx.Graphics.Vic/Types/DeinterlaceMode.cs
Normal file
@ -0,0 +1,12 @@
|
||||
namespace Ryujinx.Graphics.Vic.Types
|
||||
{
|
||||
enum DeinterlaceMode
|
||||
{
|
||||
Weave,
|
||||
BobField,
|
||||
Bob,
|
||||
NewBob,
|
||||
Disi1,
|
||||
WeaveLumaBobFieldChroma
|
||||
}
|
||||
}
|
79
Ryujinx.Graphics.Vic/Types/FrameFormat.cs
Normal file
79
Ryujinx.Graphics.Vic/Types/FrameFormat.cs
Normal file
@ -0,0 +1,79 @@
|
||||
namespace Ryujinx.Graphics.Vic.Types
|
||||
{
|
||||
enum FrameFormat
|
||||
{
|
||||
Progressive,
|
||||
InterlacedTopFieldFirst,
|
||||
InterlacedBottomFieldFirst,
|
||||
TopField,
|
||||
BottomField,
|
||||
SubPicProgressive,
|
||||
SubPicInterlacedTopFieldFirst,
|
||||
SubPicInterlacedBottomFieldFirst,
|
||||
SubPicTopField,
|
||||
SubPicBottomField,
|
||||
TopFieldChromaBottom,
|
||||
BottomFieldChromaTop,
|
||||
SubPicTopFieldChromaBottom,
|
||||
SubPicBottomFieldChromaTop
|
||||
}
|
||||
|
||||
static class FrameFormatExtensions
|
||||
{
|
||||
public static bool IsField(this FrameFormat frameFormat)
|
||||
{
|
||||
switch (frameFormat)
|
||||
{
|
||||
case FrameFormat.TopField:
|
||||
case FrameFormat.BottomField:
|
||||
case FrameFormat.SubPicTopField:
|
||||
case FrameFormat.SubPicBottomField:
|
||||
case FrameFormat.TopFieldChromaBottom:
|
||||
case FrameFormat.BottomFieldChromaTop:
|
||||
case FrameFormat.SubPicTopFieldChromaBottom:
|
||||
case FrameFormat.SubPicBottomFieldChromaTop:
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool IsInterlaced(this FrameFormat frameFormat)
|
||||
{
|
||||
switch (frameFormat)
|
||||
{
|
||||
case FrameFormat.InterlacedTopFieldFirst:
|
||||
case FrameFormat.InterlacedBottomFieldFirst:
|
||||
case FrameFormat.SubPicInterlacedTopFieldFirst:
|
||||
case FrameFormat.SubPicInterlacedBottomFieldFirst:
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool IsInterlacedBottomFirst(this FrameFormat frameFormat)
|
||||
{
|
||||
return frameFormat == FrameFormat.InterlacedBottomFieldFirst ||
|
||||
frameFormat == FrameFormat.SubPicInterlacedBottomFieldFirst;
|
||||
}
|
||||
|
||||
public static bool IsTopField(this FrameFormat frameFormat, bool isLuma)
|
||||
{
|
||||
switch (frameFormat)
|
||||
{
|
||||
case FrameFormat.TopField:
|
||||
case FrameFormat.SubPicTopField:
|
||||
return true;
|
||||
case FrameFormat.TopFieldChromaBottom:
|
||||
case FrameFormat.SubPicTopFieldChromaBottom:
|
||||
return isLuma;
|
||||
case FrameFormat.BottomFieldChromaTop:
|
||||
case FrameFormat.SubPicBottomFieldChromaTop:
|
||||
return !isLuma;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@ -27,7 +27,7 @@
|
||||
public bool PrevMotionFieldEnable => _word0.Extract(13);
|
||||
public bool PpMotionFieldEnable => _word0.Extract(14);
|
||||
public bool CombMotionFieldEnable => _word0.Extract(15);
|
||||
public int FrameFormat => _word0.Extract(16, 4);
|
||||
public FrameFormat FrameFormat => (FrameFormat)_word0.Extract(16, 4);
|
||||
public int FilterLengthY => _word0.Extract(20, 2);
|
||||
public int FilterLengthX => _word0.Extract(22, 2);
|
||||
public int Panoramic => _word0.Extract(24, 12);
|
||||
@ -36,7 +36,7 @@
|
||||
public int FilterDetail => _word1.Extract(74, 10);
|
||||
public int ChromaNoise => _word1.Extract(84, 10);
|
||||
public int ChromaDetail => _word1.Extract(94, 10);
|
||||
public int DeinterlaceMode => _word1.Extract(104, 4);
|
||||
public DeinterlaceMode DeinterlaceMode => (DeinterlaceMode)_word1.Extract(104, 4);
|
||||
public int MotionAccumWeight => _word1.Extract(108, 3);
|
||||
public int NoiseIir => _word1.Extract(111, 11);
|
||||
public int LightLevel => _word1.Extract(122, 4);
|
||||
|
@ -43,9 +43,9 @@ namespace Ryujinx.Graphics.Vic
|
||||
continue;
|
||||
}
|
||||
|
||||
var offsets = _state.State.SetSurfacexSlotx[i][0];
|
||||
ref var offsets = ref _state.State.SetSurfacexSlotx[i];
|
||||
|
||||
using Surface src = SurfaceReader.Read(_rm, ref slot.SlotSurfaceConfig, ref offsets);
|
||||
using Surface src = SurfaceReader.Read(_rm, ref slot.SlotConfig, ref slot.SlotSurfaceConfig, ref offsets);
|
||||
|
||||
Blender.BlendOne(output, src, ref slot);
|
||||
}
|
||||
|
8
Ryujinx.Graphics.Video/FrameField.cs
Normal file
8
Ryujinx.Graphics.Video/FrameField.cs
Normal file
@ -0,0 +1,8 @@
|
||||
namespace Ryujinx.Graphics.Video
|
||||
{
|
||||
public enum FrameField
|
||||
{
|
||||
Progressive,
|
||||
Interlaced
|
||||
}
|
||||
}
|
@ -8,6 +8,8 @@ namespace Ryujinx.Graphics.Video
|
||||
Plane UPlane { get; }
|
||||
Plane VPlane { get; }
|
||||
|
||||
FrameField Field { get; }
|
||||
|
||||
int Width { get; }
|
||||
int Height { get; }
|
||||
int Stride { get; }
|
||||
|
@ -1,19 +0,0 @@
|
||||
namespace Ryujinx.HLE.FileSystem.Content
|
||||
{
|
||||
static class ContentPath
|
||||
{
|
||||
public const string SystemContent = "@SystemContent";
|
||||
public const string UserContent = "@UserContent";
|
||||
public const string SdCardContent = "@SdCardContent";
|
||||
public const string SdCard = "@SdCard";
|
||||
public const string CalibFile = "@CalibFile";
|
||||
public const string Safe = "@Safe";
|
||||
public const string User = "@User";
|
||||
public const string System = "@System";
|
||||
public const string Host = "@Host";
|
||||
public const string GamecardApp = "@GcApp";
|
||||
public const string GamecardContents = "@GcS00000001";
|
||||
public const string GamecardUpdate = "@upp";
|
||||
public const string RegisteredUpdate = "@RegUpdate";
|
||||
}
|
||||
}
|
@ -1,91 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
using static Ryujinx.HLE.FileSystem.VirtualFileSystem;
|
||||
|
||||
namespace Ryujinx.HLE.FileSystem.Content
|
||||
{
|
||||
internal static class LocationHelper
|
||||
{
|
||||
public static string GetRealPath(VirtualFileSystem fileSystem, string switchContentPath)
|
||||
{
|
||||
string basePath = fileSystem.GetBasePath();
|
||||
|
||||
switch (switchContentPath)
|
||||
{
|
||||
case ContentPath.SystemContent:
|
||||
return Path.Combine(basePath, SystemNandPath, "Contents");
|
||||
case ContentPath.UserContent:
|
||||
return Path.Combine(basePath, UserNandPath, "Contents");
|
||||
case ContentPath.SdCardContent:
|
||||
return Path.Combine(fileSystem.GetSdCardPath(), "Nintendo", "Contents");
|
||||
case ContentPath.System:
|
||||
return Path.Combine(basePath, SystemNandPath);
|
||||
case ContentPath.User:
|
||||
return Path.Combine(basePath, UserNandPath);
|
||||
default:
|
||||
throw new NotSupportedException($"Content Path `{switchContentPath}` is not supported.");
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetContentPath(ContentStorageId contentStorageId)
|
||||
{
|
||||
switch (contentStorageId)
|
||||
{
|
||||
case ContentStorageId.NandSystem:
|
||||
return ContentPath.SystemContent;
|
||||
case ContentStorageId.NandUser:
|
||||
return ContentPath.UserContent;
|
||||
case ContentStorageId.SdCard:
|
||||
return ContentPath.SdCardContent;
|
||||
default:
|
||||
throw new NotSupportedException($"Content Storage `{contentStorageId}` is not supported.");
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetContentRoot(StorageId storageId)
|
||||
{
|
||||
switch (storageId)
|
||||
{
|
||||
case StorageId.NandSystem:
|
||||
return ContentPath.SystemContent;
|
||||
case StorageId.NandUser:
|
||||
return ContentPath.UserContent;
|
||||
case StorageId.SdCard:
|
||||
return ContentPath.SdCardContent;
|
||||
default:
|
||||
throw new NotSupportedException($"Storage Id `{storageId}` is not supported.");
|
||||
}
|
||||
}
|
||||
|
||||
public static StorageId GetStorageId(string contentPathString)
|
||||
{
|
||||
string cleanedPath = contentPathString.Split(':')[0];
|
||||
|
||||
switch (cleanedPath)
|
||||
{
|
||||
case ContentPath.SystemContent:
|
||||
case ContentPath.System:
|
||||
return StorageId.NandSystem;
|
||||
|
||||
case ContentPath.UserContent:
|
||||
case ContentPath.User:
|
||||
return StorageId.NandUser;
|
||||
|
||||
case ContentPath.SdCardContent:
|
||||
return StorageId.SdCard;
|
||||
|
||||
case ContentPath.Host:
|
||||
return StorageId.Host;
|
||||
|
||||
case ContentPath.GamecardApp:
|
||||
case ContentPath.GamecardContents:
|
||||
case ContentPath.GamecardUpdate:
|
||||
return StorageId.GameCard;
|
||||
|
||||
default:
|
||||
return StorageId.None;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
namespace Ryujinx.HLE.FileSystem.Content
|
||||
{
|
||||
public enum ContentStorageId
|
||||
{
|
||||
NandSystem,
|
||||
NandUser,
|
||||
SdCard
|
||||
}
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
namespace Ryujinx.HLE.FileSystem.Content
|
||||
{
|
||||
enum TitleType
|
||||
{
|
||||
SystemPrograms = 0x01,
|
||||
SystemDataArchive = 0x02,
|
||||
SystemUpdate = 0x03,
|
||||
FirmwarePackageA = 0x04,
|
||||
FirmwarePackageB = 0x05,
|
||||
RegularApplication = 0x80,
|
||||
Update = 0x81,
|
||||
AddOnContent = 0x82,
|
||||
DeltaTitle = 0x83
|
||||
}
|
||||
}
|
@ -20,7 +20,7 @@ using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using Path = System.IO.Path;
|
||||
|
||||
namespace Ryujinx.HLE.FileSystem.Content
|
||||
namespace Ryujinx.HLE.FileSystem
|
||||
{
|
||||
public class ContentManager
|
||||
{
|
||||
@ -110,8 +110,8 @@ namespace Ryujinx.HLE.FileSystem.Content
|
||||
|
||||
try
|
||||
{
|
||||
contentPathString = LocationHelper.GetContentRoot(storageId);
|
||||
contentDirectory = LocationHelper.GetRealPath(_virtualFileSystem, contentPathString);
|
||||
contentPathString = ContentPath.GetContentPath(storageId);
|
||||
contentDirectory = ContentPath.GetRealPath(_virtualFileSystem, contentPathString);
|
||||
registeredDirectory = Path.Combine(contentDirectory, "registered");
|
||||
}
|
||||
catch (NotSupportedException)
|
||||
@ -367,8 +367,7 @@ namespace Ryujinx.HLE.FileSystem.Content
|
||||
{
|
||||
LocationEntry locationEntry = GetLocation(titleId, contentType, storageId);
|
||||
|
||||
return locationEntry.ContentPath != null ?
|
||||
LocationHelper.GetStorageId(locationEntry.ContentPath) : StorageId.None;
|
||||
return locationEntry.ContentPath != null ? ContentPath.GetStorageId(locationEntry.ContentPath) : StorageId.None;
|
||||
}
|
||||
}
|
||||
|
||||
@ -493,8 +492,8 @@ namespace Ryujinx.HLE.FileSystem.Content
|
||||
|
||||
public void InstallFirmware(string firmwareSource)
|
||||
{
|
||||
string contentPathString = LocationHelper.GetContentRoot(StorageId.NandSystem);
|
||||
string contentDirectory = LocationHelper.GetRealPath(_virtualFileSystem, contentPathString);
|
||||
string contentPathString = ContentPath.GetContentPath(StorageId.BuiltInSystem);
|
||||
string contentDirectory = ContentPath.GetRealPath(_virtualFileSystem, contentPathString);
|
||||
string registeredDirectory = Path.Combine(contentDirectory, "registered");
|
||||
string temporaryDirectory = Path.Combine(contentDirectory, "temp");
|
||||
|
||||
@ -998,9 +997,9 @@ namespace Ryujinx.HLE.FileSystem.Content
|
||||
|
||||
foreach (var entry in updateNcas)
|
||||
{
|
||||
foreach (var nca in entry.Value)
|
||||
foreach (var (type, path) in entry.Value)
|
||||
{
|
||||
extraNcas += nca.path + Environment.NewLine;
|
||||
extraNcas += path + Environment.NewLine;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1019,7 +1018,7 @@ namespace Ryujinx.HLE.FileSystem.Content
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
var locationEnties = _locationEntries[StorageId.NandSystem];
|
||||
var locationEnties = _locationEntries[StorageId.BuiltInSystem];
|
||||
|
||||
foreach (var entry in locationEnties)
|
||||
{
|
82
Ryujinx.HLE/FileSystem/ContentPath.cs
Normal file
82
Ryujinx.HLE/FileSystem/ContentPath.cs
Normal file
@ -0,0 +1,82 @@
|
||||
using LibHac.Fs;
|
||||
using LibHac.Ncm;
|
||||
using Ryujinx.Common.Configuration;
|
||||
using System;
|
||||
|
||||
using static Ryujinx.HLE.FileSystem.VirtualFileSystem;
|
||||
using Path = System.IO.Path;
|
||||
|
||||
namespace Ryujinx.HLE.FileSystem
|
||||
{
|
||||
internal static class ContentPath
|
||||
{
|
||||
public const string SystemContent = "@SystemContent";
|
||||
public const string UserContent = "@UserContent";
|
||||
public const string SdCardContent = "@SdCardContent";
|
||||
public const string SdCard = "@Sdcard";
|
||||
public const string CalibFile = "@CalibFile";
|
||||
public const string Safe = "@Safe";
|
||||
public const string User = "@User";
|
||||
public const string System = "@System";
|
||||
public const string Host = "@Host";
|
||||
public const string GamecardApp = "@GcApp";
|
||||
public const string GamecardContents = "@GcS00000001";
|
||||
public const string GamecardUpdate = "@upp";
|
||||
public const string RegisteredUpdate = "@RegUpdate";
|
||||
|
||||
public const string Nintendo = "Nintendo";
|
||||
public const string Contents = "Contents";
|
||||
|
||||
public static string GetRealPath(VirtualFileSystem fileSystem, string switchContentPath)
|
||||
{
|
||||
return switchContentPath switch
|
||||
{
|
||||
SystemContent => Path.Combine(AppDataManager.BaseDirPath, SystemNandPath, Contents),
|
||||
UserContent => Path.Combine(AppDataManager.BaseDirPath, UserNandPath, Contents),
|
||||
SdCardContent => Path.Combine(fileSystem.GetSdCardPath(), Nintendo, Contents),
|
||||
System => Path.Combine(AppDataManager.BaseDirPath, SystemNandPath),
|
||||
User => Path.Combine(AppDataManager.BaseDirPath, UserNandPath),
|
||||
_ => throw new NotSupportedException($"Content Path \"`{switchContentPath}`\" is not supported.")
|
||||
};
|
||||
}
|
||||
|
||||
public static string GetContentPath(ContentStorageId contentStorageId)
|
||||
{
|
||||
return contentStorageId switch
|
||||
{
|
||||
ContentStorageId.System => SystemContent,
|
||||
ContentStorageId.User => UserContent,
|
||||
ContentStorageId.SdCard => SdCardContent,
|
||||
_ => throw new NotSupportedException($"Content Storage Id \"`{contentStorageId}`\" is not supported.")
|
||||
};
|
||||
}
|
||||
|
||||
public static string GetContentPath(StorageId storageId)
|
||||
{
|
||||
return storageId switch
|
||||
{
|
||||
StorageId.BuiltInSystem => SystemContent,
|
||||
StorageId.BuiltInUser => UserContent,
|
||||
StorageId.SdCard => SdCardContent,
|
||||
_ => throw new NotSupportedException($"Storage Id \"`{storageId}`\" is not supported.")
|
||||
};
|
||||
}
|
||||
|
||||
public static StorageId GetStorageId(string contentPathString)
|
||||
{
|
||||
return contentPathString.Split(':')[0] switch
|
||||
{
|
||||
SystemContent or
|
||||
System => StorageId.BuiltInSystem,
|
||||
UserContent or
|
||||
User => StorageId.BuiltInUser,
|
||||
SdCardContent => StorageId.SdCard,
|
||||
Host => StorageId.Host,
|
||||
GamecardApp or
|
||||
GamecardContents or
|
||||
GamecardUpdate => StorageId.GameCard,
|
||||
_ => StorageId.None
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -8,7 +8,6 @@ namespace Ryujinx.HLE.FileSystem
|
||||
{
|
||||
public class EncryptedFileSystemCreator : IEncryptedFileSystemCreator
|
||||
{
|
||||
|
||||
public Result Create(ref SharedRef<IFileSystem> outEncryptedFileSystem,
|
||||
ref SharedRef<IFileSystem> baseFileSystem, IEncryptedFileSystemCreator.KeyId idIndex,
|
||||
in EncryptionSeed encryptionSeed)
|
||||
@ -18,10 +17,10 @@ namespace Ryujinx.HLE.FileSystem
|
||||
return ResultFs.InvalidArgument.Log();
|
||||
}
|
||||
|
||||
// Todo: Reenable when AesXtsFileSystem is fixed
|
||||
// TODO: Reenable when AesXtsFileSystem is fixed.
|
||||
outEncryptedFileSystem = SharedRef<IFileSystem>.CreateMove(ref baseFileSystem);
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
using LibHac.FsSystem;
|
||||
|
||||
namespace Ryujinx.HLE.FileSystem.Content
|
||||
namespace Ryujinx.HLE.FileSystem
|
||||
{
|
||||
public struct LocationEntry
|
||||
{
|
@ -1,12 +0,0 @@
|
||||
namespace Ryujinx.HLE.FileSystem
|
||||
{
|
||||
enum SaveDataType : byte
|
||||
{
|
||||
SystemSaveData,
|
||||
SaveData,
|
||||
BcatDeliveryCacheStorage,
|
||||
DeviceSaveData,
|
||||
TemporaryStorage,
|
||||
CacheStorage
|
||||
}
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||
|
||||
namespace Ryujinx.HLE.FileSystem
|
||||
{
|
||||
struct SaveInfo
|
||||
{
|
||||
public ulong TitleId { get; private set; }
|
||||
public long SaveId { get; private set; }
|
||||
public SaveDataType SaveDataType { get; private set; }
|
||||
public SaveSpaceId SaveSpaceId { get; private set; }
|
||||
public UserId UserId { get; private set; }
|
||||
|
||||
public SaveInfo(
|
||||
ulong titleId,
|
||||
long saveId,
|
||||
SaveDataType saveDataType,
|
||||
SaveSpaceId saveSpaceId,
|
||||
UserId userId = new UserId())
|
||||
{
|
||||
TitleId = titleId;
|
||||
SaveId = saveId;
|
||||
SaveDataType = saveDataType;
|
||||
SaveSpaceId = saveSpaceId;
|
||||
UserId = userId;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
namespace Ryujinx.HLE.FileSystem
|
||||
{
|
||||
enum SaveSpaceId
|
||||
{
|
||||
NandSystem,
|
||||
NandUser,
|
||||
SdCard,
|
||||
TemporaryStorage
|
||||
}
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
namespace Ryujinx.HLE.FileSystem
|
||||
{
|
||||
public enum StorageId
|
||||
{
|
||||
None,
|
||||
Host,
|
||||
GameCard,
|
||||
NandSystem,
|
||||
NandUser,
|
||||
SdCard
|
||||
}
|
||||
}
|
@ -1,8 +1,7 @@
|
||||
using Ryujinx.HLE.Utilities;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace Ryujinx.HLE.FileSystem.Content
|
||||
namespace Ryujinx.HLE.FileSystem
|
||||
{
|
||||
public class SystemVersion
|
||||
{
|
@ -13,7 +13,6 @@ using LibHac.Tools.Fs;
|
||||
using LibHac.Tools.FsSystem;
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.HLE.FileSystem.Content;
|
||||
using Ryujinx.HLE.HOS;
|
||||
using System;
|
||||
using System.Buffers.Text;
|
||||
@ -28,20 +27,29 @@ namespace Ryujinx.HLE.FileSystem
|
||||
{
|
||||
public class VirtualFileSystem : IDisposable
|
||||
{
|
||||
public const string NandPath = AppDataManager.DefaultNandDir;
|
||||
public const string SdCardPath = AppDataManager.DefaultSdcardDir;
|
||||
public static string SafeNandPath = Path.Combine(AppDataManager.DefaultNandDir, "safe");
|
||||
public static string SystemNandPath = Path.Combine(AppDataManager.DefaultNandDir, "system");
|
||||
public static string UserNandPath = Path.Combine(AppDataManager.DefaultNandDir, "user");
|
||||
|
||||
public static string SafeNandPath = Path.Combine(NandPath, "safe");
|
||||
public static string SystemNandPath = Path.Combine(NandPath, "system");
|
||||
public static string UserNandPath = Path.Combine(NandPath, "user");
|
||||
public KeySet KeySet { get; private set; }
|
||||
public EmulatedGameCard GameCard { get; private set; }
|
||||
public EmulatedSdCard SdCard { get; private set; }
|
||||
public ModLoader ModLoader { get; private set; }
|
||||
public Stream RomFs { get; private set; }
|
||||
|
||||
private static bool _isInitialized = false;
|
||||
|
||||
public KeySet KeySet { get; private set; }
|
||||
public EmulatedGameCard GameCard { get; private set; }
|
||||
public EmulatedSdCard SdCard { get; private set; }
|
||||
public static VirtualFileSystem CreateInstance()
|
||||
{
|
||||
if (_isInitialized)
|
||||
{
|
||||
throw new InvalidOperationException("VirtualFileSystem can only be instantiated once!");
|
||||
}
|
||||
|
||||
public ModLoader ModLoader { get; private set; }
|
||||
_isInitialized = true;
|
||||
|
||||
return new VirtualFileSystem();
|
||||
}
|
||||
|
||||
private VirtualFileSystem()
|
||||
{
|
||||
@ -49,8 +57,6 @@ namespace Ryujinx.HLE.FileSystem
|
||||
ModLoader = new ModLoader(); // Should only be created once
|
||||
}
|
||||
|
||||
public Stream RomFs { get; private set; }
|
||||
|
||||
public void LoadRomFs(string fileName)
|
||||
{
|
||||
RomFs = new FileStream(fileName, FileMode.Open, FileAccess.Read);
|
||||
@ -79,7 +85,7 @@ namespace Ryujinx.HLE.FileSystem
|
||||
|
||||
string fullPath = Path.GetFullPath(Path.Combine(basePath, fileName));
|
||||
|
||||
if (!fullPath.StartsWith(GetBasePath()))
|
||||
if (!fullPath.StartsWith(AppDataManager.BaseDirPath))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
@ -87,14 +93,8 @@ namespace Ryujinx.HLE.FileSystem
|
||||
return fullPath;
|
||||
}
|
||||
|
||||
internal string GetBasePath() => AppDataManager.BaseDirPath;
|
||||
internal string GetSdCardPath() => MakeFullPath(SdCardPath);
|
||||
public string GetNandPath() => MakeFullPath(NandPath);
|
||||
|
||||
public string GetFullPartitionPath(string partitionPath)
|
||||
{
|
||||
return MakeFullPath(partitionPath);
|
||||
}
|
||||
internal string GetSdCardPath() => MakeFullPath(AppDataManager.DefaultSdcardDir);
|
||||
public string GetNandPath() => MakeFullPath(AppDataManager.DefaultNandDir);
|
||||
|
||||
public string SwitchPathToSystemPath(string switchPath)
|
||||
{
|
||||
@ -110,7 +110,7 @@ namespace Ryujinx.HLE.FileSystem
|
||||
|
||||
public string SystemPathToSwitchPath(string systemPath)
|
||||
{
|
||||
string baseSystemPath = GetBasePath() + Path.DirectorySeparatorChar;
|
||||
string baseSystemPath = AppDataManager.BaseDirPath + Path.DirectorySeparatorChar;
|
||||
|
||||
if (systemPath.StartsWith(baseSystemPath))
|
||||
{
|
||||
@ -136,8 +136,7 @@ namespace Ryujinx.HLE.FileSystem
|
||||
switch (path)
|
||||
{
|
||||
case ContentPath.SdCard:
|
||||
case "@Sdcard":
|
||||
path = SdCardPath;
|
||||
path = AppDataManager.DefaultSdcardDir;
|
||||
break;
|
||||
case ContentPath.User:
|
||||
path = UserNandPath;
|
||||
@ -146,7 +145,7 @@ namespace Ryujinx.HLE.FileSystem
|
||||
path = SystemNandPath;
|
||||
break;
|
||||
case ContentPath.SdCardContent:
|
||||
path = Path.Combine(SdCardPath, "Nintendo", "Contents");
|
||||
path = Path.Combine(AppDataManager.DefaultSdcardDir, "Nintendo", "Contents");
|
||||
break;
|
||||
case ContentPath.UserContent:
|
||||
path = Path.Combine(UserNandPath, "Contents");
|
||||
@ -156,27 +155,19 @@ namespace Ryujinx.HLE.FileSystem
|
||||
break;
|
||||
}
|
||||
|
||||
string fullPath = Path.Combine(GetBasePath(), path);
|
||||
string fullPath = Path.Combine(AppDataManager.BaseDirPath, path);
|
||||
|
||||
if (isDirectory)
|
||||
if (isDirectory && !Directory.Exists(fullPath))
|
||||
{
|
||||
if (!Directory.Exists(fullPath))
|
||||
{
|
||||
Directory.CreateDirectory(fullPath);
|
||||
}
|
||||
Directory.CreateDirectory(fullPath);
|
||||
}
|
||||
|
||||
return fullPath;
|
||||
}
|
||||
|
||||
public DriveInfo GetDrive()
|
||||
{
|
||||
return new DriveInfo(Path.GetPathRoot(GetBasePath()));
|
||||
}
|
||||
|
||||
public void InitializeFsServer(LibHac.Horizon horizon, out HorizonClient fsServerClient)
|
||||
{
|
||||
LocalFileSystem serverBaseFs = new LocalFileSystem(GetBasePath());
|
||||
LocalFileSystem serverBaseFs = new LocalFileSystem(AppDataManager.BaseDirPath);
|
||||
|
||||
fsServerClient = horizon.CreatePrivilegedHorizonClient();
|
||||
var fsServer = new FileSystemServer(fsServerClient);
|
||||
@ -505,7 +496,7 @@ namespace Ryujinx.HLE.FileSystem
|
||||
|
||||
bool canFixBySaveDataId = extraData.Attribute.StaticSaveDataId == 0 && info.StaticSaveDataId != 0;
|
||||
|
||||
bool hasEmptyOwnerId = extraData.OwnerId == 0 && info.Type != LibHac.Fs.SaveDataType.System;
|
||||
bool hasEmptyOwnerId = extraData.OwnerId == 0 && info.Type != SaveDataType.System;
|
||||
|
||||
if (!canFixByProgramId && !canFixBySaveDataId && !hasEmptyOwnerId)
|
||||
{
|
||||
@ -523,7 +514,7 @@ namespace Ryujinx.HLE.FileSystem
|
||||
|
||||
// The rest of the extra data can't be created from the save data info.
|
||||
// On user saves the owner ID will almost certainly be the same as the program ID.
|
||||
if (info.Type != LibHac.Fs.SaveDataType.System)
|
||||
if (info.Type != SaveDataType.System)
|
||||
{
|
||||
extraData.OwnerId = info.ProgramId.Value;
|
||||
}
|
||||
@ -580,11 +571,6 @@ namespace Ryujinx.HLE.FileSystem
|
||||
}
|
||||
};
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
RomFs?.Dispose();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
@ -594,20 +580,8 @@ namespace Ryujinx.HLE.FileSystem
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
Unload();
|
||||
RomFs?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public static VirtualFileSystem CreateInstance()
|
||||
{
|
||||
if (_isInitialized)
|
||||
{
|
||||
throw new InvalidOperationException("VirtualFileSystem can only be instantiated once!");
|
||||
}
|
||||
|
||||
_isInitialized = true;
|
||||
|
||||
return new VirtualFileSystem();
|
||||
}
|
||||
}
|
||||
}
|
@ -3,7 +3,6 @@ using Ryujinx.Audio.Integration;
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using Ryujinx.HLE.FileSystem.Content;
|
||||
using Ryujinx.HLE.HOS;
|
||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||
using Ryujinx.HLE.HOS.SystemState;
|
||||
|
@ -2,10 +2,10 @@
|
||||
using LibHac.Fs;
|
||||
using LibHac.Fs.Fsa;
|
||||
using LibHac.FsSystem;
|
||||
using LibHac.Ncm;
|
||||
using LibHac.Tools.FsSystem;
|
||||
using LibHac.Tools.FsSystem.NcaUtils;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using Ryujinx.HLE.HOS.Services.Am.AppletAE;
|
||||
using Ryujinx.HLE.HOS.SystemState;
|
||||
using System;
|
||||
@ -106,7 +106,7 @@ namespace Ryujinx.HLE.HOS.Applets.Error
|
||||
|
||||
private string GetMessageText(uint module, uint description, string key)
|
||||
{
|
||||
string binaryTitleContentPath = _horizon.ContentManager.GetInstalledContentPath(ErrorMessageBinaryTitleId, StorageId.NandSystem, NcaContentType.Data);
|
||||
string binaryTitleContentPath = _horizon.ContentManager.GetInstalledContentPath(ErrorMessageBinaryTitleId, StorageId.BuiltInSystem, NcaContentType.Data);
|
||||
|
||||
using (LibHac.Fs.IStorage ncaFileStream = new LocalStorage(_horizon.Device.FileSystem.SwitchPathToSystemPath(binaryTitleContentPath), FileAccess.Read, FileMode.Open))
|
||||
{
|
||||
|
@ -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())
|
||||
{
|
||||
|
@ -10,7 +10,7 @@ using Ryujinx.Audio.Integration;
|
||||
using Ryujinx.Audio.Output;
|
||||
using Ryujinx.Audio.Renderer.Device;
|
||||
using Ryujinx.Audio.Renderer.Server;
|
||||
using Ryujinx.HLE.FileSystem.Content;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using Ryujinx.HLE.HOS.Kernel;
|
||||
using Ryujinx.HLE.HOS.Kernel.Memory;
|
||||
using Ryujinx.HLE.HOS.Kernel.Process;
|
||||
@ -238,6 +238,7 @@ namespace Ryujinx.HLE.HOS
|
||||
SurfaceFlinger = new SurfaceFlinger(device);
|
||||
|
||||
InitializeAudioRenderer();
|
||||
InitializeServices();
|
||||
}
|
||||
|
||||
private void InitializeAudioRenderer()
|
||||
@ -288,7 +289,7 @@ namespace Ryujinx.HLE.HOS
|
||||
AudioManager.Start();
|
||||
}
|
||||
|
||||
public void InitializeServices()
|
||||
private void InitializeServices()
|
||||
{
|
||||
SmServer = new ServerBase(KernelContext, "SmServer", () => new IUserInterface(KernelContext));
|
||||
|
||||
@ -466,7 +467,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;
|
||||
|
@ -1,4 +1,4 @@
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using LibHac.Ncm;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Arp
|
||||
{
|
||||
@ -20,7 +20,7 @@ namespace Ryujinx.HLE.HOS.Services.Arp
|
||||
{
|
||||
TitleId = 0x00,
|
||||
Version = 0x00,
|
||||
BaseGameStorageId = (byte)StorageId.NandSystem,
|
||||
BaseGameStorageId = (byte)StorageId.BuiltInSystem,
|
||||
UpdateGameStorageId = (byte)StorageId.None
|
||||
};
|
||||
}
|
||||
@ -35,7 +35,7 @@ namespace Ryujinx.HLE.HOS.Services.Arp
|
||||
{
|
||||
TitleId = context.Device.Application.TitleId,
|
||||
Version = 0x00,
|
||||
BaseGameStorageId = (byte)StorageId.NandSystem,
|
||||
BaseGameStorageId = (byte)StorageId.BuiltInSystem,
|
||||
UpdateGameStorageId = (byte)StorageId.None
|
||||
};
|
||||
}
|
||||
|
@ -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
|
||||
};
|
||||
|
||||
|
@ -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
|
||||
{
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user