diff --git a/Directory.Packages.props b/Directory.Packages.props index 6919a2485..301024cf8 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -13,14 +13,14 @@ - + - + @@ -39,11 +39,11 @@ - - - - - + + + + + diff --git a/Ryujinx.sln b/Ryujinx.sln index b8304164d..76ebd573f 100644 --- a/Ryujinx.sln +++ b/Ryujinx.sln @@ -87,6 +87,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Horizon", "src\Ryuj EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Horizon.Kernel.Generators", "src\Ryujinx.Horizon.Kernel.Generators\Ryujinx.Horizon.Kernel.Generators.csproj", "{7F55A45D-4E1D-4A36-ADD3-87F29A285AA2}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.HLE.Generators", "src\Ryujinx.HLE.Generators\Ryujinx.HLE.Generators.csproj", "{B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -249,6 +251,10 @@ Global {7F55A45D-4E1D-4A36-ADD3-87F29A285AA2}.Debug|Any CPU.Build.0 = Debug|Any CPU {7F55A45D-4E1D-4A36-ADD3-87F29A285AA2}.Release|Any CPU.ActiveCfg = Release|Any CPU {7F55A45D-4E1D-4A36-ADD3-87F29A285AA2}.Release|Any CPU.Build.0 = Release|Any CPU + {B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/ARMeilleure/Decoders/OpCodeTable.cs b/src/ARMeilleure/Decoders/OpCodeTable.cs index edc004125..859535670 100644 --- a/src/ARMeilleure/Decoders/OpCodeTable.cs +++ b/src/ARMeilleure/Decoders/OpCodeTable.cs @@ -822,6 +822,10 @@ namespace ARMeilleure.Decoders SetA32("<<<<00000100xxxxxxxxxxxx1001xxxx", InstName.Umaal, InstEmit32.Umaal, OpCode32AluUmull.Create); SetA32("<<<<0000101xxxxxxxxxxxxx1001xxxx", InstName.Umlal, InstEmit32.Umlal, OpCode32AluUmull.Create); SetA32("<<<<0000100xxxxxxxxxxxxx1001xxxx", InstName.Umull, InstEmit32.Umull, OpCode32AluUmull.Create); + SetA32("<<<<01100110xxxxxxxx11110001xxxx", InstName.Uqadd16, InstEmit32.Uqadd16, OpCode32AluReg.Create); + SetA32("<<<<01100110xxxxxxxx11111001xxxx", InstName.Uqadd8, InstEmit32.Uqadd8, OpCode32AluReg.Create); + SetA32("<<<<01100110xxxxxxxx11110111xxxx", InstName.Uqsub16, InstEmit32.Uqsub16, OpCode32AluReg.Create); + SetA32("<<<<01100110xxxxxxxx11111111xxxx", InstName.Uqsub8, InstEmit32.Uqsub8, OpCode32AluReg.Create); SetA32("<<<<0110111xxxxxxxxxxxxxxx01xxxx", InstName.Usat, InstEmit32.Usat, OpCode32Sat.Create); SetA32("<<<<01101110xxxxxxxx11110011xxxx", InstName.Usat16, InstEmit32.Usat16, OpCode32Sat16.Create); SetA32("<<<<01100101xxxxxxxx11111111xxxx", InstName.Usub8, InstEmit32.Usub8, OpCode32AluReg.Create); @@ -1007,6 +1011,8 @@ namespace ARMeilleure.Decoders SetAsimd("111100100x10xxxxxxxx1011xxx0xxxx", InstName.Vqdmulh, InstEmit32.Vqdmulh, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32); SetAsimd("111100111x11<<10xxxx00101xx0xxx0", InstName.Vqmovn, InstEmit32.Vqmovn, OpCode32SimdMovn.Create, OpCode32SimdMovn.CreateT32); SetAsimd("111100111x11<<10xxxx001001x0xxx0", InstName.Vqmovun, InstEmit32.Vqmovun, OpCode32SimdMovn.Create, OpCode32SimdMovn.CreateT32); + SetAsimd("111100110x01xxxxxxxx1011xxx0xxxx", InstName.Vqrdmulh, InstEmit32.Vqrdmulh, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32); + SetAsimd("111100110x10xxxxxxxx1011xxx0xxxx", InstName.Vqrdmulh, InstEmit32.Vqrdmulh, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32); SetAsimd("1111001x1x>>>xxxxxxx100101x1xxx0", InstName.Vqrshrn, InstEmit32.Vqrshrn, OpCode32SimdShImmNarrow.Create, OpCode32SimdShImmNarrow.CreateT32); SetAsimd("111100111x>>>xxxxxxx100001x1xxx0", InstName.Vqrshrun, InstEmit32.Vqrshrun, OpCode32SimdShImmNarrow.Create, OpCode32SimdShImmNarrow.CreateT32); SetAsimd("1111001x1x>>>xxxxxxx100100x1xxx0", InstName.Vqshrn, InstEmit32.Vqshrn, OpCode32SimdShImmNarrow.Create, OpCode32SimdShImmNarrow.CreateT32); @@ -1030,6 +1036,7 @@ namespace ARMeilleure.Decoders SetAsimd("1111001x1x>>>xxxxxxx101000x1xxxx", InstName.Vshll, InstEmit32.Vshll, OpCode32SimdShImmLong.Create, OpCode32SimdShImmLong.CreateT32); // A1 encoding. SetAsimd("1111001x1x>>>xxxxxxx0000>xx1xxxx", InstName.Vshr, InstEmit32.Vshr, OpCode32SimdShImm.Create, OpCode32SimdShImm.CreateT32); SetAsimd("111100101x>>>xxxxxxx100000x1xxx0", InstName.Vshrn, InstEmit32.Vshrn, OpCode32SimdShImmNarrow.Create, OpCode32SimdShImmNarrow.CreateT32); + SetAsimd("111100111x>>>xxxxxxx0101>xx1xxxx", InstName.Vsli, InstEmit32.Vsli_I, OpCode32SimdShImm.Create, OpCode32SimdShImm.CreateT32); SetAsimd("1111001x1x>>>xxxxxxx0001>xx1xxxx", InstName.Vsra, InstEmit32.Vsra, OpCode32SimdShImm.Create, OpCode32SimdShImm.CreateT32); SetAsimd("111101001x00xxxxxxxx0000xxx0xxxx", InstName.Vst1, InstEmit32.Vst1, OpCode32SimdMemSingle.Create, OpCode32SimdMemSingle.CreateT32); SetAsimd("111101001x00xxxxxxxx0100xx0xxxxx", InstName.Vst1, InstEmit32.Vst1, OpCode32SimdMemSingle.Create, OpCode32SimdMemSingle.CreateT32); @@ -1054,6 +1061,7 @@ namespace ARMeilleure.Decoders SetAsimd("111100100x10xxxxxxxx1101xxx0xxxx", InstName.Vsub, InstEmit32.Vsub_V, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32); SetAsimd("1111001x1x< + { + EmitSaturateUqadd(context, d, context.Add(n, m), 16); + })); + } + + public static void Uqadd8(ArmEmitterContext context) + { + OpCode32AluReg op = (OpCode32AluReg)context.CurrOp; + + SetIntA32(context, op.Rd, EmitUnsigned8BitPair(context, GetIntA32(context, op.Rn), GetIntA32(context, op.Rm), (d, n, m) => + { + EmitSaturateUqadd(context, d, context.Add(n, m), 8); + })); + } + + public static void Uqsub16(ArmEmitterContext context) + { + OpCode32AluReg op = (OpCode32AluReg)context.CurrOp; + + SetIntA32(context, op.Rd, EmitUnsigned16BitPair(context, GetIntA32(context, op.Rn), GetIntA32(context, op.Rm), (d, n, m) => + { + EmitSaturateUqsub(context, d, context.Subtract(n, m), 16); + })); + } + + public static void Uqsub8(ArmEmitterContext context) + { + OpCode32AluReg op = (OpCode32AluReg)context.CurrOp; + + SetIntA32(context, op.Rd, EmitUnsigned8BitPair(context, GetIntA32(context, op.Rn), GetIntA32(context, op.Rm), (d, n, m) => + { + EmitSaturateUqsub(context, d, context.Subtract(n, m), 8); + })); + } + public static void Usat(ArmEmitterContext context) { OpCode32Sat op = (OpCode32Sat)context.CurrOp; @@ -934,6 +976,148 @@ namespace ARMeilleure.Instructions } } + private static void EmitSaturateUqadd(ArmEmitterContext context, Operand result, Operand value, uint saturateTo) + { + Debug.Assert(saturateTo <= 32); + + if (saturateTo == 32) + { + // No saturation possible for this case. + + context.Copy(result, value); + + return; + } + else if (saturateTo == 0) + { + // Result is always zero if we saturate 0 bits. + + context.Copy(result, Const(0)); + + return; + } + + // If the result is 0, the values are equal and we don't need saturation. + Operand lblNoSat = Label(); + context.BranchIfFalse(lblNoSat, context.ShiftRightUI(value, Const((int)saturateTo))); + + // Saturate. + context.Copy(result, Const(uint.MaxValue >> (32 - (int)saturateTo))); + + Operand lblExit = Label(); + context.Branch(lblExit); + + context.MarkLabel(lblNoSat); + + context.Copy(result, value); + + context.MarkLabel(lblExit); + } + + private static void EmitSaturateUqsub(ArmEmitterContext context, Operand result, Operand value, uint saturateTo) + { + Debug.Assert(saturateTo <= 32); + + if (saturateTo == 32) + { + // No saturation possible for this case. + + context.Copy(result, value); + + return; + } + else if (saturateTo == 0) + { + // Result is always zero if we saturate 0 bits. + + context.Copy(result, Const(0)); + + return; + } + + // If the result is 0, the values are equal and we don't need saturation. + Operand lblNoSat = Label(); + context.BranchIf(lblNoSat, value, Const(0), Comparison.GreaterOrEqual); + + // Saturate. + // Assumes that the value can only underflow, since this is only used for unsigned subtraction. + context.Copy(result, Const(0)); + + Operand lblExit = Label(); + context.Branch(lblExit); + + context.MarkLabel(lblNoSat); + + context.Copy(result, value); + + context.MarkLabel(lblExit); + } + + private static Operand EmitUnsigned16BitPair(ArmEmitterContext context, Operand rn, Operand rm, Action elementAction) + { + Operand tempD = context.AllocateLocal(OperandType.I32); + + Operand tempN = context.ZeroExtend16(OperandType.I32, rn); + Operand tempM = context.ZeroExtend16(OperandType.I32, rm); + elementAction(tempD, tempN, tempM); + Operand tempD2 = context.ZeroExtend16(OperandType.I32, tempD); + + tempN = context.ShiftRightUI(rn, Const(16)); + tempM = context.ShiftRightUI(rm, Const(16)); + elementAction(tempD, tempN, tempM); + return context.BitwiseOr(tempD2, context.ShiftLeft(tempD, Const(16))); + } + + private static Operand EmitSigned8BitPair(ArmEmitterContext context, Operand rn, Operand rm, Action elementAction) + { + return Emit8BitPair(context, rn, rm, elementAction, unsigned: false); + } + + private static Operand EmitUnsigned8BitPair(ArmEmitterContext context, Operand rn, Operand rm, Action elementAction) + { + return Emit8BitPair(context, rn, rm, elementAction, unsigned: true); + } + + private static Operand Emit8BitPair(ArmEmitterContext context, Operand rn, Operand rm, Action elementAction, bool unsigned) + { + Operand tempD = context.AllocateLocal(OperandType.I32); + Operand result = default; + + for (int b = 0; b < 4; b++) + { + Operand nByte = b != 0 ? context.ShiftRightUI(rn, Const(b * 8)) : rn; + Operand mByte = b != 0 ? context.ShiftRightUI(rm, Const(b * 8)) : rm; + + if (unsigned) + { + nByte = context.ZeroExtend8(OperandType.I32, nByte); + mByte = context.ZeroExtend8(OperandType.I32, mByte); + } + else + { + nByte = context.SignExtend8(OperandType.I32, nByte); + mByte = context.SignExtend8(OperandType.I32, mByte); + } + + elementAction(tempD, nByte, mByte); + + if (b == 0) + { + result = context.ZeroExtend8(OperandType.I32, tempD); + } + else if (b < 3) + { + result = context.BitwiseOr(result, context.ShiftLeft(context.ZeroExtend8(OperandType.I32, tempD), Const(b * 8))); + } + else + { + result = context.BitwiseOr(result, context.ShiftLeft(tempD, Const(24))); + } + } + + return result; + } + private static void EmitAluStore(ArmEmitterContext context, Operand value) { IOpCode32Alu op = (IOpCode32Alu)context.CurrOp; diff --git a/src/ARMeilleure/Instructions/InstEmitSimdArithmetic32.cs b/src/ARMeilleure/Instructions/InstEmitSimdArithmetic32.cs index dc2646a55..c807fc858 100644 --- a/src/ARMeilleure/Instructions/InstEmitSimdArithmetic32.cs +++ b/src/ARMeilleure/Instructions/InstEmitSimdArithmetic32.cs @@ -1246,6 +1246,33 @@ namespace ARMeilleure.Instructions EmitVectorUnaryNarrowOp32(context, (op1) => EmitSatQ(context, op1, 8 << op.Size, signedSrc: true, signedDst: false), signed: true); } + public static void Vqrdmulh(ArmEmitterContext context) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + int eSize = 8 << op.Size; + + EmitVectorBinaryOpI32(context, (op1, op2) => + { + if (op.Size == 2) + { + op1 = context.SignExtend32(OperandType.I64, op1); + op2 = context.SignExtend32(OperandType.I64, op2); + } + + Operand res = context.Multiply(op1, op2); + res = context.Add(res, Const(res.Type, 1L << (eSize - 2))); + res = context.ShiftRightSI(res, Const(eSize - 1)); + res = EmitSatQ(context, res, eSize, signedSrc: true, signedDst: true); + + if (op.Size == 2) + { + res = context.ConvertI64ToI32(res); + } + + return res; + }, signed: true); + } + public static void Vqsub(ArmEmitterContext context) { OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; diff --git a/src/ARMeilleure/Instructions/InstEmitSimdMove32.cs b/src/ARMeilleure/Instructions/InstEmitSimdMove32.cs index 9fa740997..fb2641f66 100644 --- a/src/ARMeilleure/Instructions/InstEmitSimdMove32.cs +++ b/src/ARMeilleure/Instructions/InstEmitSimdMove32.cs @@ -191,6 +191,26 @@ namespace ARMeilleure.Instructions context.Copy(GetVecA32(op.Qd), res); } + public static void Vswp(ArmEmitterContext context) + { + OpCode32Simd op = (OpCode32Simd)context.CurrOp; + + if (op.Q) + { + Operand temp = context.Copy(GetVecA32(op.Qd)); + + context.Copy(GetVecA32(op.Qd), GetVecA32(op.Qm)); + context.Copy(GetVecA32(op.Qm), temp); + } + else + { + Operand temp = ExtractScalar(context, OperandType.I64, op.Vd); + + InsertScalar(context, op.Vd, ExtractScalar(context, OperandType.I64, op.Vm)); + InsertScalar(context, op.Vm, temp); + } + } + public static void Vtbl(ArmEmitterContext context) { OpCode32SimdTbl op = (OpCode32SimdTbl)context.CurrOp; diff --git a/src/ARMeilleure/Instructions/InstEmitSimdShift32.cs b/src/ARMeilleure/Instructions/InstEmitSimdShift32.cs index e40600a47..e9e3b52b9 100644 --- a/src/ARMeilleure/Instructions/InstEmitSimdShift32.cs +++ b/src/ARMeilleure/Instructions/InstEmitSimdShift32.cs @@ -130,6 +130,36 @@ namespace ARMeilleure.Instructions EmitVectorUnaryNarrowOp32(context, (op1) => context.ShiftRightUI(op1, Const(shift))); } + public static void Vsli_I(ArmEmitterContext context) + { + OpCode32SimdShImm op = (OpCode32SimdShImm)context.CurrOp; + int shift = op.Shift; + int eSize = 8 << op.Size; + + ulong mask = shift != 0 ? ulong.MaxValue >> (64 - shift) : 0UL; + + Operand res = GetVec(op.Qd); + + int elems = op.GetBytesCount() >> op.Size; + + for (int index = 0; index < elems; index++) + { + Operand me = EmitVectorExtractZx(context, op.Qm, op.Im + index, op.Size); + + Operand neShifted = context.ShiftLeft(me, Const(shift)); + + Operand de = EmitVectorExtractZx(context, op.Qd, op.Id + index, op.Size); + + Operand deMasked = context.BitwiseAnd(de, Const(mask)); + + Operand e = context.BitwiseOr(neShifted, deMasked); + + res = EmitVectorInsert(context, res, e, op.Id + index, op.Size); + } + + context.Copy(GetVec(op.Qd), res); + } + public static void Vsra(ArmEmitterContext context) { OpCode32SimdShImm op = (OpCode32SimdShImm)context.CurrOp; diff --git a/src/ARMeilleure/Instructions/InstName.cs b/src/ARMeilleure/Instructions/InstName.cs index 457abbf49..ac85412d1 100644 --- a/src/ARMeilleure/Instructions/InstName.cs +++ b/src/ARMeilleure/Instructions/InstName.cs @@ -571,6 +571,10 @@ namespace ARMeilleure.Instructions Umaal, Umlal, Umull, + Uqadd16, + Uqadd8, + Uqsub16, + Uqsub8, Usat, Usat16, Usub8, @@ -645,6 +649,7 @@ namespace ARMeilleure.Instructions Vqdmulh, Vqmovn, Vqmovun, + Vqrdmulh, Vqrshrn, Vqrshrun, Vqshrn, @@ -666,6 +671,7 @@ namespace ARMeilleure.Instructions Vshll, Vshr, Vshrn, + Vsli, Vst1, Vst2, Vst3, @@ -682,6 +688,7 @@ namespace ARMeilleure.Instructions Vsub, Vsubl, Vsubw, + Vswp, Vtbl, Vtrn, Vtst, diff --git a/src/ARMeilleure/Translation/TranslatorQueue.cs b/src/ARMeilleure/Translation/TranslatorQueue.cs index cee2f9080..831522bc1 100644 --- a/src/ARMeilleure/Translation/TranslatorQueue.cs +++ b/src/ARMeilleure/Translation/TranslatorQueue.cs @@ -80,7 +80,10 @@ namespace ARMeilleure.Translation return true; } - Monitor.Wait(Sync); + if (!_disposed) + { + Monitor.Wait(Sync); + } } } diff --git a/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceManager.cs b/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceManager.cs index 0a035916c..da5a0ad45 100644 --- a/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceManager.cs +++ b/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceManager.cs @@ -18,16 +18,12 @@ namespace Ryujinx.Audio.Renderer.Server.Performance if (version == 2) { - return (ulong)PerformanceManagerGeneric.GetRequiredBufferSizeForPerformanceMetricsPerFrame(ref parameter); + return (ulong)PerformanceManagerGeneric.GetRequiredBufferSizeForPerformanceMetricsPerFrame(ref parameter); } if (version == 1) { - return (ulong)PerformanceManagerGeneric.GetRequiredBufferSizeForPerformanceMetricsPerFrame(ref parameter); + return (ulong)PerformanceManagerGeneric.GetRequiredBufferSizeForPerformanceMetricsPerFrame(ref parameter); } throw new NotImplementedException($"Unknown Performance metrics data format version {version}"); diff --git a/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceManagerGeneric.cs b/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceManagerGeneric.cs index 5a70a1bcf..2e5d25b9c 100644 --- a/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceManagerGeneric.cs +++ b/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceManagerGeneric.cs @@ -234,7 +234,7 @@ namespace Ryujinx.Audio.Renderer.Server.Performance { performanceEntry = null; - if (_entryDetailIndex > MaxFrameDetailCount) + if (_entryDetailIndex >= MaxFrameDetailCount) { return false; } @@ -245,7 +245,7 @@ namespace Ryujinx.Audio.Renderer.Server.Performance EntryCountOffset = (uint)CurrentHeader.GetEntryCountOffset(), }; - uint baseEntryOffset = (uint)(Unsafe.SizeOf() + GetEntriesSize() + Unsafe.SizeOf() * _entryDetailIndex); + uint baseEntryOffset = (uint)(Unsafe.SizeOf() + GetEntriesSize() + Unsafe.SizeOf() * _entryDetailIndex); ref TEntryDetail entryDetail = ref EntriesDetail[_entryDetailIndex]; diff --git a/src/Ryujinx.Common/GraphicsDriver/DriverUtilities.cs b/src/Ryujinx.Common/GraphicsDriver/DriverUtilities.cs index 7fe2a4f02..a9163f348 100644 --- a/src/Ryujinx.Common/GraphicsDriver/DriverUtilities.cs +++ b/src/Ryujinx.Common/GraphicsDriver/DriverUtilities.cs @@ -1,13 +1,33 @@ +using Ryujinx.Common.Utilities; using System; namespace Ryujinx.Common.GraphicsDriver { public static class DriverUtilities { + private static void AddMesaFlags(string envVar, string newFlags) + { + string existingFlags = Environment.GetEnvironmentVariable(envVar); + + string flags = existingFlags == null ? newFlags : $"{existingFlags},{newFlags}"; + + OsUtils.SetEnvironmentVariableNoCaching(envVar, flags); + } + + public static void InitDriverConfig(bool oglThreading) + { + if (OperatingSystem.IsLinux()) + { + AddMesaFlags("RADV_DEBUG", "nodcc"); + } + + ToggleOGLThreading(oglThreading); + } + public static void ToggleOGLThreading(bool enabled) { - Environment.SetEnvironmentVariable("mesa_glthread", enabled.ToString().ToLower()); - Environment.SetEnvironmentVariable("__GL_THREADED_OPTIMIZATIONS", enabled ? "1" : "0"); + OsUtils.SetEnvironmentVariableNoCaching("mesa_glthread", enabled.ToString().ToLower()); + OsUtils.SetEnvironmentVariableNoCaching("__GL_THREADED_OPTIMIZATIONS", enabled ? "1" : "0"); try { diff --git a/src/Ryujinx.Common/Memory/ByteMemoryPool.ByteMemoryPoolBuffer.cs b/src/Ryujinx.Common/Memory/ByteMemoryPool.ByteMemoryPoolBuffer.cs deleted file mode 100644 index 05fb29ac7..000000000 --- a/src/Ryujinx.Common/Memory/ByteMemoryPool.ByteMemoryPoolBuffer.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System; -using System.Buffers; -using System.Threading; - -namespace Ryujinx.Common.Memory -{ - public partial class ByteMemoryPool - { - /// - /// Represents a that wraps an array rented from - /// and exposes it as - /// with a length of the requested size. - /// - private sealed class ByteMemoryPoolBuffer : IMemoryOwner - { - private byte[] _array; - private readonly int _length; - - public ByteMemoryPoolBuffer(int length) - { - _array = ArrayPool.Shared.Rent(length); - _length = length; - } - - /// - /// Returns a belonging to this owner. - /// - public Memory Memory - { - get - { - byte[] array = _array; - - ObjectDisposedException.ThrowIf(array is null, this); - - return new Memory(array, 0, _length); - } - } - - public void Dispose() - { - var array = Interlocked.Exchange(ref _array, null); - - if (array != null) - { - ArrayPool.Shared.Return(array); - } - } - } - } -} diff --git a/src/Ryujinx.Common/Memory/ByteMemoryPool.cs b/src/Ryujinx.Common/Memory/ByteMemoryPool.cs deleted file mode 100644 index 6fd6a98aa..000000000 --- a/src/Ryujinx.Common/Memory/ByteMemoryPool.cs +++ /dev/null @@ -1,106 +0,0 @@ -using System; -using System.Buffers; - -namespace Ryujinx.Common.Memory -{ - /// - /// Provides a pool of re-usable byte array instances. - /// - public static partial class ByteMemoryPool - { - /// - /// Returns the maximum buffer size supported by this pool. - /// - public static int MaxBufferSize => Array.MaxLength; - - /// - /// Rents a byte memory buffer from . - /// The buffer may contain data from a prior use. - /// - /// The buffer's required length in bytes - /// A wrapping the rented memory - /// - public static IMemoryOwner Rent(long length) - => RentImpl(checked((int)length)); - - /// - /// Rents a byte memory buffer from . - /// The buffer may contain data from a prior use. - /// - /// The buffer's required length in bytes - /// A wrapping the rented memory - /// - public static IMemoryOwner Rent(ulong length) - => RentImpl(checked((int)length)); - - /// - /// Rents a byte memory buffer from . - /// The buffer may contain data from a prior use. - /// - /// The buffer's required length in bytes - /// A wrapping the rented memory - /// - public static IMemoryOwner Rent(int length) - => RentImpl(length); - - /// - /// Rents a byte memory buffer from . - /// The buffer's contents are cleared (set to all 0s) before returning. - /// - /// The buffer's required length in bytes - /// A wrapping the rented memory - /// - public static IMemoryOwner RentCleared(long length) - => RentCleared(checked((int)length)); - - /// - /// Rents a byte memory buffer from . - /// The buffer's contents are cleared (set to all 0s) before returning. - /// - /// The buffer's required length in bytes - /// A wrapping the rented memory - /// - public static IMemoryOwner RentCleared(ulong length) - => RentCleared(checked((int)length)); - - /// - /// Rents a byte memory buffer from . - /// The buffer's contents are cleared (set to all 0s) before returning. - /// - /// The buffer's required length in bytes - /// A wrapping the rented memory - /// - public static IMemoryOwner RentCleared(int length) - { - var buffer = RentImpl(length); - - buffer.Memory.Span.Clear(); - - return buffer; - } - - /// - /// Copies into a newly rented byte memory buffer. - /// - /// The byte buffer to copy - /// A wrapping the rented memory with copied to it - public static IMemoryOwner RentCopy(ReadOnlySpan buffer) - { - var copy = RentImpl(buffer.Length); - - buffer.CopyTo(copy.Memory.Span); - - return copy; - } - - private static ByteMemoryPoolBuffer RentImpl(int length) - { - if ((uint)length > Array.MaxLength) - { - throw new ArgumentOutOfRangeException(nameof(length), length, null); - } - - return new ByteMemoryPoolBuffer(length); - } - } -} diff --git a/src/Ryujinx.Common/Utilities/EmbeddedResources.cs b/src/Ryujinx.Common/Utilities/EmbeddedResources.cs index e22571c96..7530c012a 100644 --- a/src/Ryujinx.Common/Utilities/EmbeddedResources.cs +++ b/src/Ryujinx.Common/Utilities/EmbeddedResources.cs @@ -1,6 +1,6 @@ +using Ryujinx.Common.Memory; using Ryujinx.Common.Utilities; using System; -using System.Buffers; using System.IO; using System.Linq; using System.Reflection; @@ -42,14 +42,14 @@ namespace Ryujinx.Common return StreamUtils.StreamToBytes(stream); } - public static IMemoryOwner ReadFileToRentedMemory(string filename) + public static MemoryOwner ReadFileToRentedMemory(string filename) { var (assembly, path) = ResolveManifestPath(filename); return ReadFileToRentedMemory(assembly, path); } - public static IMemoryOwner ReadFileToRentedMemory(Assembly assembly, string filename) + public static MemoryOwner ReadFileToRentedMemory(Assembly assembly, string filename) { using var stream = GetStream(assembly, filename); diff --git a/src/Ryujinx.Common/Utilities/OsUtils.cs b/src/Ryujinx.Common/Utilities/OsUtils.cs new file mode 100644 index 000000000..a0791b092 --- /dev/null +++ b/src/Ryujinx.Common/Utilities/OsUtils.cs @@ -0,0 +1,24 @@ +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace Ryujinx.Common.Utilities +{ + public partial class OsUtils + { + [LibraryImport("libc", SetLastError = true)] + private static partial int setenv([MarshalAs(UnmanagedType.LPStr)] string name, [MarshalAs(UnmanagedType.LPStr)] string value, int overwrite); + + public static void SetEnvironmentVariableNoCaching(string key, string value) + { + // Set the value in the cached environment variables, too. + Environment.SetEnvironmentVariable(key, value); + + if (!OperatingSystem.IsWindows()) + { + int res = setenv(key, value, 1); + Debug.Assert(res != -1); + } + } + } +} diff --git a/src/Ryujinx.Common/Utilities/StreamUtils.cs b/src/Ryujinx.Common/Utilities/StreamUtils.cs index 74b6af5ec..aeb6e0d52 100644 --- a/src/Ryujinx.Common/Utilities/StreamUtils.cs +++ b/src/Ryujinx.Common/Utilities/StreamUtils.cs @@ -1,6 +1,5 @@ using Microsoft.IO; using Ryujinx.Common.Memory; -using System.Buffers; using System.IO; using System.Threading; using System.Threading.Tasks; @@ -16,7 +15,7 @@ namespace Ryujinx.Common.Utilities return output.ToArray(); } - public static IMemoryOwner StreamToRentedMemory(Stream input) + public static MemoryOwner StreamToRentedMemory(Stream input) { if (input is MemoryStream inputMemoryStream) { @@ -26,9 +25,9 @@ namespace Ryujinx.Common.Utilities { long bytesExpected = input.Length; - IMemoryOwner ownedMemory = ByteMemoryPool.Rent(bytesExpected); + MemoryOwner ownedMemory = MemoryOwner.Rent(checked((int)bytesExpected)); - var destSpan = ownedMemory.Memory.Span; + var destSpan = ownedMemory.Span; int totalBytesRead = 0; @@ -66,14 +65,14 @@ namespace Ryujinx.Common.Utilities return stream.ToArray(); } - private static IMemoryOwner MemoryStreamToRentedMemory(MemoryStream input) + private static MemoryOwner MemoryStreamToRentedMemory(MemoryStream input) { input.Position = 0; - IMemoryOwner ownedMemory = ByteMemoryPool.Rent(input.Length); + MemoryOwner ownedMemory = MemoryOwner.Rent(checked((int)input.Length)); // Discard the return value because we assume reading a MemoryStream always succeeds completely. - _ = input.Read(ownedMemory.Memory.Span); + _ = input.Read(ownedMemory.Span); return ownedMemory; } diff --git a/src/Ryujinx.Cpu/Jit/MemoryManagerHostTracked.cs b/src/Ryujinx.Cpu/Jit/MemoryManagerHostTracked.cs index 663d0aeb1..501109b86 100644 --- a/src/Ryujinx.Cpu/Jit/MemoryManagerHostTracked.cs +++ b/src/Ryujinx.Cpu/Jit/MemoryManagerHostTracked.cs @@ -303,9 +303,9 @@ namespace Ryujinx.Cpu.Jit } else { - IMemoryOwner memoryOwner = ByteMemoryPool.Rent(size); + MemoryOwner memoryOwner = MemoryOwner.Rent(size); - Read(va, memoryOwner.Memory.Span); + Read(va, memoryOwner.Span); return new WritableRegion(this, va, memoryOwner); } diff --git a/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitMove.cs b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitMove.cs index 88850cb33..d57750fc1 100644 --- a/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitMove.cs +++ b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitMove.cs @@ -1,6 +1,5 @@ using Ryujinx.Cpu.LightningJit.CodeGen; using Ryujinx.Cpu.LightningJit.CodeGen.Arm64; -using System.Diagnostics; namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64 { diff --git a/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitSaturate.cs b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitSaturate.cs index e2354f448..f1b6e395b 100644 --- a/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitSaturate.cs +++ b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitSaturate.cs @@ -114,7 +114,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64 InstEmitCommon.EmitUnsigned16BitPair(context, rd, rn, rm, (d, n, m) => { context.Arm64Assembler.Add(d, n, m); - EmitSaturateUnsignedRange(context, d, 16); + EmitSaturateUqadd(context, d, 16); }); } @@ -123,7 +123,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64 InstEmitCommon.EmitUnsigned8BitPair(context, rd, rn, rm, (d, n, m) => { context.Arm64Assembler.Add(d, n, m); - EmitSaturateUnsignedRange(context, d, 8); + EmitSaturateUqadd(context, d, 8); }); } @@ -140,7 +140,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64 context.Arm64Assembler.Add(d, n, m); } - EmitSaturateUnsignedRange(context, d, 16); + EmitSaturateUq(context, d, 16, e == 0); }); } @@ -157,25 +157,25 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64 context.Arm64Assembler.Sub(d, n, m); } - EmitSaturateUnsignedRange(context, d, 16); + EmitSaturateUq(context, d, 16, e != 0); }); } public static void Uqsub16(CodeGenContext context, uint rd, uint rn, uint rm) { - InstEmitCommon.EmitSigned16BitPair(context, rd, rn, rm, (d, n, m) => + InstEmitCommon.EmitUnsigned16BitPair(context, rd, rn, rm, (d, n, m) => { context.Arm64Assembler.Sub(d, n, m); - EmitSaturateUnsignedRange(context, d, 16); + EmitSaturateUqsub(context, d, 16); }); } public static void Uqsub8(CodeGenContext context, uint rd, uint rn, uint rm) { - InstEmitCommon.EmitSigned8BitPair(context, rd, rn, rm, (d, n, m) => + InstEmitCommon.EmitUnsigned8BitPair(context, rd, rn, rm, (d, n, m) => { context.Arm64Assembler.Sub(d, n, m); - EmitSaturateUnsignedRange(context, d, 8); + EmitSaturateUqsub(context, d, 8); }); } @@ -358,7 +358,17 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64 } } - private static void EmitSaturateUnsignedRange(CodeGenContext context, Operand value, uint saturateTo) + private static void EmitSaturateUqadd(CodeGenContext context, Operand value, uint saturateTo) + { + EmitSaturateUq(context, value, saturateTo, isSub: false); + } + + private static void EmitSaturateUqsub(CodeGenContext context, Operand value, uint saturateTo) + { + EmitSaturateUq(context, value, saturateTo, isSub: true); + } + + private static void EmitSaturateUq(CodeGenContext context, Operand value, uint saturateTo, bool isSub) { Debug.Assert(saturateTo <= 32); @@ -379,7 +389,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64 return; } - context.Arm64Assembler.Lsr(tempRegister.Operand, value, InstEmitCommon.Const(32 - (int)saturateTo)); + context.Arm64Assembler.Lsr(tempRegister.Operand, value, InstEmitCommon.Const((int)saturateTo)); int branchIndex = context.CodeWriter.InstructionPointer; @@ -387,7 +397,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64 context.Arm64Assembler.Cbz(tempRegister.Operand, 0); // Saturate. - context.Arm64Assembler.Mov(value, uint.MaxValue >> (32 - (int)saturateTo)); + context.Arm64Assembler.Mov(value, isSub ? 0u : uint.MaxValue >> (32 - (int)saturateTo)); int delta = context.CodeWriter.InstructionPointer - branchIndex; context.CodeWriter.WriteInstructionAt(branchIndex, context.CodeWriter.ReadInstructionAt(branchIndex) | (uint)((delta & 0x7ffff) << 5)); diff --git a/src/Ryujinx.Graphics.Device/DeviceMemoryManager.cs b/src/Ryujinx.Graphics.Device/DeviceMemoryManager.cs index fc075a264..cb1a7c3ab 100644 --- a/src/Ryujinx.Graphics.Device/DeviceMemoryManager.cs +++ b/src/Ryujinx.Graphics.Device/DeviceMemoryManager.cs @@ -1,7 +1,6 @@ using Ryujinx.Common.Memory; using Ryujinx.Memory; using System; -using System.Buffers; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -145,9 +144,9 @@ namespace Ryujinx.Graphics.Device } else { - IMemoryOwner memoryOwner = ByteMemoryPool.Rent(size); + MemoryOwner memoryOwner = MemoryOwner.Rent(size); - GetSpan(va, size).CopyTo(memoryOwner.Memory.Span); + ReadImpl(va, memoryOwner.Span); return new WritableRegion(this, va, memoryOwner, tracked: true); } diff --git a/src/Ryujinx.Graphics.Device/DeviceState.cs b/src/Ryujinx.Graphics.Device/DeviceState.cs index de8582a3b..54178a414 100644 --- a/src/Ryujinx.Graphics.Device/DeviceState.cs +++ b/src/Ryujinx.Graphics.Device/DeviceState.cs @@ -39,7 +39,10 @@ namespace Ryujinx.Graphics.Device { var field = fields[fieldIndex]; - int sizeOfField = SizeCalculator.SizeOf(field.FieldType); + var currentFieldOffset = (int)Marshal.OffsetOf(field.Name); + var nextFieldOffset = fieldIndex + 1 == fields.Length ? Unsafe.SizeOf() : (int)Marshal.OffsetOf(fields[fieldIndex + 1].Name); + + int sizeOfField = nextFieldOffset - currentFieldOffset; for (int i = 0; i < ((sizeOfField + 3) & ~3); i += 4) { diff --git a/src/Ryujinx.Graphics.Device/SizeCalculator.cs b/src/Ryujinx.Graphics.Device/SizeCalculator.cs deleted file mode 100644 index 54820ec36..000000000 --- a/src/Ryujinx.Graphics.Device/SizeCalculator.cs +++ /dev/null @@ -1,63 +0,0 @@ -using System; -using System.Reflection; - -namespace Ryujinx.Graphics.Device -{ - public static class SizeCalculator - { - public static int SizeOf(Type type) - { - // Is type a enum type? - if (type.IsEnum) - { - type = type.GetEnumUnderlyingType(); - } - - // Is type a pointer type? - if (type.IsPointer || type == typeof(IntPtr) || type == typeof(UIntPtr)) - { - return IntPtr.Size; - } - - // Is type a struct type? - if (type.IsValueType && !type.IsPrimitive) - { - // Check if the struct has a explicit size, if so, return that. - if (type.StructLayoutAttribute.Size != 0) - { - return type.StructLayoutAttribute.Size; - } - - // Otherwise we calculate the sum of the sizes of all fields. - int size = 0; - var fields = type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); - - for (int fieldIndex = 0; fieldIndex < fields.Length; fieldIndex++) - { - size += SizeOf(fields[fieldIndex].FieldType); - } - - return size; - } - - // Primitive types. - return (Type.GetTypeCode(type)) switch - { - TypeCode.SByte => sizeof(sbyte), - TypeCode.Byte => sizeof(byte), - TypeCode.Int16 => sizeof(short), - TypeCode.UInt16 => sizeof(ushort), - TypeCode.Int32 => sizeof(int), - TypeCode.UInt32 => sizeof(uint), - TypeCode.Int64 => sizeof(long), - TypeCode.UInt64 => sizeof(ulong), - TypeCode.Char => sizeof(char), - TypeCode.Single => sizeof(float), - TypeCode.Double => sizeof(double), - TypeCode.Decimal => sizeof(decimal), - TypeCode.Boolean => sizeof(bool), - _ => throw new ArgumentException($"Length for type \"{type.Name}\" is unknown."), - }; - } - } -} diff --git a/src/Ryujinx.Graphics.GAL/TextureCreateInfo.cs b/src/Ryujinx.Graphics.GAL/TextureCreateInfo.cs index 44090291d..79c84db01 100644 --- a/src/Ryujinx.Graphics.GAL/TextureCreateInfo.cs +++ b/src/Ryujinx.Graphics.GAL/TextureCreateInfo.cs @@ -1,6 +1,5 @@ using Ryujinx.Common; using System; -using System.Numerics; namespace Ryujinx.Graphics.GAL { @@ -113,25 +112,6 @@ namespace Ryujinx.Graphics.GAL return 1; } - public int GetLevelsClamped() - { - int maxSize = Width; - - if (Target != Target.Texture1D && - Target != Target.Texture1DArray) - { - maxSize = Math.Max(maxSize, Height); - } - - if (Target == Target.Texture3D) - { - maxSize = Math.Max(maxSize, Depth); - } - - int maxLevels = BitOperations.Log2((uint)maxSize) + 1; - return Math.Min(Levels, maxLevels); - } - private static int GetLevelSize(int size, int level) { return Math.Max(1, size >> level); diff --git a/src/Ryujinx.Graphics.Gpu/Engine/InlineToMemory/InlineToMemoryClass.cs b/src/Ryujinx.Graphics.Gpu/Engine/InlineToMemory/InlineToMemoryClass.cs index 93e43ce3c..78099f74a 100644 --- a/src/Ryujinx.Graphics.Gpu/Engine/InlineToMemory/InlineToMemoryClass.cs +++ b/src/Ryujinx.Graphics.Gpu/Engine/InlineToMemory/InlineToMemoryClass.cs @@ -199,7 +199,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.InlineToMemory if (target != null) { target.SynchronizeMemory(); - var dataCopy = ByteMemoryPool.RentCopy(data); + var dataCopy = MemoryOwner.RentCopy(data); target.SetData(dataCopy, 0, 0, new GAL.Rectangle(_dstX, _dstY, _lineLengthIn / target.Info.FormatInfo.BytesPerPixel, _lineCount)); target.SignalModified(); diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdateTracker.cs b/src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdateTracker.cs index e54855a8f..effcb7bbb 100644 --- a/src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdateTracker.cs +++ b/src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdateTracker.cs @@ -79,7 +79,10 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed { var field = fields[fieldIndex]; - int sizeOfField = SizeCalculator.SizeOf(field.FieldType); + var currentFieldOffset = (int)Marshal.OffsetOf(field.Name); + var nextFieldOffset = fieldIndex + 1 == fields.Length ? Unsafe.SizeOf() : (int)Marshal.OffsetOf(fields[fieldIndex + 1].Name); + + int sizeOfField = nextFieldOffset - currentFieldOffset; if (fieldToDelegate.TryGetValue(field.Name, out int entryIndex)) { diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClassState.cs b/src/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClassState.cs index dd55e7d1d..35051c6e0 100644 --- a/src/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClassState.cs +++ b/src/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClassState.cs @@ -415,7 +415,13 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed #pragma warning disable CS0649 // Field is never assigned to public int Width; public int Height; - public int Depth; + public ushort Depth; + public ushort Flags; + + public readonly bool UnpackIsLayered() + { + return (Flags & 1) == 0; + } #pragma warning restore CS0649 } diff --git a/src/Ryujinx.Graphics.Gpu/Image/Texture.cs b/src/Ryujinx.Graphics.Gpu/Image/Texture.cs index dde28dbd7..3b6c407cc 100644 --- a/src/Ryujinx.Graphics.Gpu/Image/Texture.cs +++ b/src/Ryujinx.Graphics.Gpu/Image/Texture.cs @@ -1,4 +1,5 @@ using Ryujinx.Common.Logging; +using Ryujinx.Common.Memory; using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.Gpu.Memory; using Ryujinx.Graphics.Texture; @@ -805,7 +806,7 @@ namespace Ryujinx.Graphics.Gpu.Image sliceDepth, levels, layers, - out IMemoryOwner decoded)) + out MemoryOwner decoded)) { string texInfo = $"{Info.Target} {Info.FormatInfo.Format} {Info.Width}x{Info.Height}x{Info.DepthOrLayers} levels {Info.Levels}"; diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsArrayCache.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsArrayCache.cs index 01e34c777..8b9243b1e 100644 --- a/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsArrayCache.cs +++ b/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsArrayCache.cs @@ -340,7 +340,7 @@ namespace Ryujinx.Graphics.Gpu.Image /// True if any used entries of the pool might have been modified, false otherwise public bool SamplerPoolModified() { - return SamplerPool.WasModified(ref _samplerPoolSequence); + return SamplerPool != null && SamplerPool.WasModified(ref _samplerPoolSequence); } } @@ -516,12 +516,15 @@ namespace Ryujinx.Graphics.Gpu.Image } // Check if any of our cached samplers changed on the pool. - foreach ((int samplerId, (Sampler sampler, SamplerDescriptor descriptor)) in SamplerIds) + if (SamplerPool != null) { - if (SamplerPool.GetCachedItem(samplerId) != sampler || - (sampler == null && SamplerPool.IsValidId(samplerId) && !SamplerPool.GetDescriptorRef(samplerId).Equals(descriptor))) + foreach ((int samplerId, (Sampler sampler, SamplerDescriptor descriptor)) in SamplerIds) { - return true; + if (SamplerPool.GetCachedItem(samplerId) != sampler || + (sampler == null && SamplerPool.IsValidId(samplerId) && !SamplerPool.GetDescriptorRef(samplerId).Equals(descriptor))) + { + return true; + } } } @@ -899,13 +902,19 @@ namespace Ryujinx.Graphics.Gpu.Image } } - Sampler sampler = samplerPool?.Get(samplerId); - entry.TextureIds[textureId] = (texture, descriptor); - entry.SamplerIds[samplerId] = (sampler, samplerPool?.GetDescriptorRef(samplerId) ?? default); ITexture hostTexture = texture?.GetTargetTexture(bindingInfo.Target); - ISampler hostSampler = sampler?.GetHostSampler(texture); + ISampler hostSampler = null; + + if (!isImage && bindingInfo.Target != Target.TextureBuffer) + { + Sampler sampler = samplerPool?.Get(samplerId); + + entry.SamplerIds[samplerId] = (sampler, samplerPool?.GetDescriptorRef(samplerId) ?? default); + + hostSampler = sampler?.GetHostSampler(texture); + } Format format = bindingInfo.Format; diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureCache.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureCache.cs index b9ff060e2..b6fa842e3 100644 --- a/src/Ryujinx.Graphics.Gpu/Image/TextureCache.cs +++ b/src/Ryujinx.Graphics.Gpu/Image/TextureCache.cs @@ -468,13 +468,11 @@ namespace Ryujinx.Graphics.Gpu.Image int gobBlocksInY = dsState.MemoryLayout.UnpackGobBlocksInY(); int gobBlocksInZ = dsState.MemoryLayout.UnpackGobBlocksInZ(); + layered &= size.UnpackIsLayered(); + Target target; - if (dsState.MemoryLayout.UnpackIsTarget3D()) - { - target = Target.Texture3D; - } - else if ((samplesInX | samplesInY) != 1) + if ((samplesInX | samplesInY) != 1) { target = size.Depth > 1 && layered ? Target.Texture2DMultisampleArray diff --git a/src/Ryujinx.Graphics.Gpu/Image/TexturePool.cs b/src/Ryujinx.Graphics.Gpu/Image/TexturePool.cs index a4035577d..4ed0a93c1 100644 --- a/src/Ryujinx.Graphics.Gpu/Image/TexturePool.cs +++ b/src/Ryujinx.Graphics.Gpu/Image/TexturePool.cs @@ -6,6 +6,7 @@ using Ryujinx.Memory.Range; using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Numerics; using System.Threading; namespace Ryujinx.Graphics.Gpu.Image @@ -490,6 +491,8 @@ namespace Ryujinx.Graphics.Gpu.Image levels = (maxLod - minLod) + 1; } + levels = ClampLevels(target, width, height, depthOrLayers, levels); + SwizzleComponent swizzleR = descriptor.UnpackSwizzleR().Convert(); SwizzleComponent swizzleG = descriptor.UnpackSwizzleG().Convert(); SwizzleComponent swizzleB = descriptor.UnpackSwizzleB().Convert(); @@ -540,6 +543,34 @@ namespace Ryujinx.Graphics.Gpu.Image swizzleA); } + /// + /// Clamps the amount of mipmap levels to the maximum allowed for the given texture dimensions. + /// + /// Number of texture dimensions (1D, 2D, 3D, Cube, etc) + /// Width of the texture + /// Height of the texture, ignored for 1D textures + /// Depth of the texture for 3D textures, otherwise ignored + /// Original amount of mipmap levels + /// Clamped mipmap levels + private static int ClampLevels(Target target, int width, int height, int depthOrLayers, int levels) + { + int maxSize = width; + + if (target != Target.Texture1D && + target != Target.Texture1DArray) + { + maxSize = Math.Max(maxSize, height); + } + + if (target == Target.Texture3D) + { + maxSize = Math.Max(maxSize, depthOrLayers); + } + + int maxLevels = BitOperations.Log2((uint)maxSize) + 1; + return Math.Min(levels, maxLevels); + } + /// /// Gets the texture depth-stencil mode, based on the swizzle components of each color channel. /// The depth-stencil mode is determined based on how the driver sets those parameters. diff --git a/src/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs b/src/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs index 0b6c78fac..59a940a4f 100644 --- a/src/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs +++ b/src/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs @@ -2,7 +2,6 @@ using Ryujinx.Common.Memory; using Ryujinx.Memory; using Ryujinx.Memory.Range; using System; -using System.Buffers; using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -242,9 +241,9 @@ namespace Ryujinx.Graphics.Gpu.Memory } else { - IMemoryOwner memoryOwner = ByteMemoryPool.Rent(size); + MemoryOwner memoryOwner = MemoryOwner.Rent(size); - GetSpan(va, size).CopyTo(memoryOwner.Memory.Span); + ReadImpl(va, memoryOwner.Span, tracked); return new WritableRegion(this, va, memoryOwner, tracked); } diff --git a/src/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs b/src/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs index 4d09c3aab..b22cc01b8 100644 --- a/src/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs +++ b/src/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs @@ -192,9 +192,9 @@ namespace Ryujinx.Graphics.Gpu.Memory } else { - IMemoryOwner memoryOwner = ByteMemoryPool.Rent(range.GetSize()); + MemoryOwner memoryOwner = MemoryOwner.Rent(checked((int)range.GetSize())); - Memory memory = memoryOwner.Memory; + Span memorySpan = memoryOwner.Span; int offset = 0; for (int i = 0; i < range.Count; i++) @@ -203,7 +203,7 @@ namespace Ryujinx.Graphics.Gpu.Memory int size = (int)currentRange.Size; if (currentRange.Address != MemoryManager.PteUnmapped) { - GetSpan(currentRange.Address, size).CopyTo(memory.Span.Slice(offset, size)); + GetSpan(currentRange.Address, size).CopyTo(memorySpan.Slice(offset, size)); } offset += size; } diff --git a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs index c4b5a1380..c1f592011 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs @@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache private const ushort FileFormatVersionMajor = 1; private const ushort FileFormatVersionMinor = 2; private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor; - private const uint CodeGenVersion = 6921; + private const uint CodeGenVersion = 7131; private const string SharedTocFileName = "shared.toc"; private const string SharedDataFileName = "shared.data"; diff --git a/src/Ryujinx.Graphics.OpenGL/Image/FormatConverter.cs b/src/Ryujinx.Graphics.OpenGL/Image/FormatConverter.cs index 434f25900..490c0c585 100644 --- a/src/Ryujinx.Graphics.OpenGL/Image/FormatConverter.cs +++ b/src/Ryujinx.Graphics.OpenGL/Image/FormatConverter.cs @@ -1,6 +1,5 @@ using Ryujinx.Common.Memory; using System; -using System.Buffers; using System.Numerics; using System.Runtime.InteropServices; using System.Runtime.Intrinsics; @@ -10,11 +9,11 @@ namespace Ryujinx.Graphics.OpenGL.Image { static class FormatConverter { - public unsafe static IMemoryOwner ConvertS8D24ToD24S8(ReadOnlySpan data) + public unsafe static MemoryOwner ConvertS8D24ToD24S8(ReadOnlySpan data) { - IMemoryOwner outputMemory = ByteMemoryPool.Rent(data.Length); + MemoryOwner outputMemory = MemoryOwner.Rent(data.Length); - Span output = outputMemory.Memory.Span; + Span output = outputMemory.Span; int start = 0; diff --git a/src/Ryujinx.Graphics.OpenGL/Image/TextureStorage.cs b/src/Ryujinx.Graphics.OpenGL/Image/TextureStorage.cs index 79c6cb685..0ebafb04e 100644 --- a/src/Ryujinx.Graphics.OpenGL/Image/TextureStorage.cs +++ b/src/Ryujinx.Graphics.OpenGL/Image/TextureStorage.cs @@ -48,7 +48,7 @@ namespace Ryujinx.Graphics.OpenGL.Image internalFormat = (SizedInternalFormat)format.PixelInternalFormat; } - int levels = Info.GetLevelsClamped(); + int levels = Info.Levels; switch (Info.Target) { diff --git a/src/Ryujinx.Graphics.OpenGL/Image/TextureView.cs b/src/Ryujinx.Graphics.OpenGL/Image/TextureView.cs index 8a18e6132..946eb755c 100644 --- a/src/Ryujinx.Graphics.OpenGL/Image/TextureView.cs +++ b/src/Ryujinx.Graphics.OpenGL/Image/TextureView.cs @@ -51,7 +51,7 @@ namespace Ryujinx.Graphics.OpenGL.Image pixelInternalFormat = format.PixelInternalFormat; } - int levels = Info.GetLevelsClamped(); + int levels = Info.Levels; GL.TextureView( Handle, @@ -267,7 +267,7 @@ namespace Ryujinx.Graphics.OpenGL.Image public unsafe PinnedSpan GetData() { int size = 0; - int levels = Info.GetLevelsClamped(); + int levels = Info.Levels; for (int level = 0; level < levels; level++) { @@ -426,7 +426,7 @@ namespace Ryujinx.Graphics.OpenGL.Image faces = 6; } - int levels = Info.GetLevelsClamped(); + int levels = Info.Levels; for (int level = 0; level < levels; level++) { @@ -716,7 +716,7 @@ namespace Ryujinx.Graphics.OpenGL.Image int width = Info.Width; int height = Info.Height; int depth = Info.Depth; - int levels = Info.GetLevelsClamped(); + int levels = Info.Levels; int offset = 0; diff --git a/src/Ryujinx.Graphics.Shader/Translation/RegisterUsage.cs b/src/Ryujinx.Graphics.Shader/Translation/RegisterUsage.cs index e27e47070..1c724223c 100644 --- a/src/Ryujinx.Graphics.Shader/Translation/RegisterUsage.cs +++ b/src/Ryujinx.Graphics.Shader/Translation/RegisterUsage.cs @@ -155,9 +155,14 @@ namespace Ryujinx.Graphics.Shader.Translation localInputs[block.Index] |= GetMask(register) & ~localOutputs[block.Index]; } - if (operation.Dest != null && operation.Dest.Type == OperandType.Register) + for (int dstIndex = 0; dstIndex < operation.DestsCount; dstIndex++) { - localOutputs[block.Index] |= GetMask(operation.Dest.GetRegister()); + Operand dest = operation.GetDest(dstIndex); + + if (dest != null && dest.Type == OperandType.Register) + { + localOutputs[block.Index] |= GetMask(dest.GetRegister()); + } } } } diff --git a/src/Ryujinx.Graphics.Texture/Astc/AstcDecoder.cs b/src/Ryujinx.Graphics.Texture/Astc/AstcDecoder.cs index 3f65e1225..92e39d2e0 100644 --- a/src/Ryujinx.Graphics.Texture/Astc/AstcDecoder.cs +++ b/src/Ryujinx.Graphics.Texture/Astc/AstcDecoder.cs @@ -1,7 +1,6 @@ using Ryujinx.Common.Memory; using Ryujinx.Common.Utilities; using System; -using System.Buffers; using System.Diagnostics; using System.Linq; using System.Runtime.CompilerServices; @@ -293,9 +292,9 @@ namespace Ryujinx.Graphics.Texture.Astc int depth, int levels, int layers, - out IMemoryOwner decoded) + out MemoryOwner decoded) { - decoded = ByteMemoryPool.Rent(QueryDecompressedSize(width, height, depth, levels, layers)); + decoded = MemoryOwner.Rent(QueryDecompressedSize(width, height, depth, levels, layers)); AstcDecoder decoder = new(data, decoded.Memory, blockWidth, blockHeight, width, height, depth, levels, layers); diff --git a/src/Ryujinx.Graphics.Texture/BCnDecoder.cs b/src/Ryujinx.Graphics.Texture/BCnDecoder.cs index eb85334a2..d7b1f0fa9 100644 --- a/src/Ryujinx.Graphics.Texture/BCnDecoder.cs +++ b/src/Ryujinx.Graphics.Texture/BCnDecoder.cs @@ -1,7 +1,6 @@ using Ryujinx.Common; using Ryujinx.Common.Memory; using System; -using System.Buffers; using System.Buffers.Binary; using System.Runtime.InteropServices; using System.Runtime.Intrinsics; @@ -14,7 +13,7 @@ namespace Ryujinx.Graphics.Texture private const int BlockWidth = 4; private const int BlockHeight = 4; - public static IMemoryOwner DecodeBC1(ReadOnlySpan data, int width, int height, int depth, int levels, int layers) + public static MemoryOwner DecodeBC1(ReadOnlySpan data, int width, int height, int depth, int levels, int layers) { int size = 0; @@ -23,12 +22,12 @@ namespace Ryujinx.Graphics.Texture size += Math.Max(1, width >> l) * Math.Max(1, height >> l) * Math.Max(1, depth >> l) * layers * 4; } - IMemoryOwner output = ByteMemoryPool.Rent(size); + MemoryOwner output = MemoryOwner.Rent(size); Span tile = stackalloc byte[BlockWidth * BlockHeight * 4]; Span tileAsUint = MemoryMarshal.Cast(tile); - Span outputAsUint = MemoryMarshal.Cast(output.Memory.Span); + Span outputAsUint = MemoryMarshal.Cast(output.Span); Span> tileAsVector128 = MemoryMarshal.Cast>(tile); @@ -102,7 +101,7 @@ namespace Ryujinx.Graphics.Texture return output; } - public static IMemoryOwner DecodeBC2(ReadOnlySpan data, int width, int height, int depth, int levels, int layers) + public static MemoryOwner DecodeBC2(ReadOnlySpan data, int width, int height, int depth, int levels, int layers) { int size = 0; @@ -111,12 +110,12 @@ namespace Ryujinx.Graphics.Texture size += Math.Max(1, width >> l) * Math.Max(1, height >> l) * Math.Max(1, depth >> l) * layers * 4; } - IMemoryOwner output = ByteMemoryPool.Rent(size); + MemoryOwner output = MemoryOwner.Rent(size); Span tile = stackalloc byte[BlockWidth * BlockHeight * 4]; Span tileAsUint = MemoryMarshal.Cast(tile); - Span outputAsUint = MemoryMarshal.Cast(output.Memory.Span); + Span outputAsUint = MemoryMarshal.Cast(output.Span); Span> tileAsVector128 = MemoryMarshal.Cast>(tile); @@ -197,7 +196,7 @@ namespace Ryujinx.Graphics.Texture return output; } - public static IMemoryOwner DecodeBC3(ReadOnlySpan data, int width, int height, int depth, int levels, int layers) + public static MemoryOwner DecodeBC3(ReadOnlySpan data, int width, int height, int depth, int levels, int layers) { int size = 0; @@ -206,13 +205,13 @@ namespace Ryujinx.Graphics.Texture size += Math.Max(1, width >> l) * Math.Max(1, height >> l) * Math.Max(1, depth >> l) * layers * 4; } - IMemoryOwner output = ByteMemoryPool.Rent(size); + MemoryOwner output = MemoryOwner.Rent(size); Span tile = stackalloc byte[BlockWidth * BlockHeight * 4]; Span rPal = stackalloc byte[8]; Span tileAsUint = MemoryMarshal.Cast(tile); - Span outputAsUint = MemoryMarshal.Cast(output.Memory.Span); + Span outputAsUint = MemoryMarshal.Cast(output.Span); Span> tileAsVector128 = MemoryMarshal.Cast>(tile); @@ -294,7 +293,7 @@ namespace Ryujinx.Graphics.Texture return output; } - public static IMemoryOwner DecodeBC4(ReadOnlySpan data, int width, int height, int depth, int levels, int layers, bool signed) + public static MemoryOwner DecodeBC4(ReadOnlySpan data, int width, int height, int depth, int levels, int layers, bool signed) { int size = 0; @@ -306,8 +305,8 @@ namespace Ryujinx.Graphics.Texture // Backends currently expect a stride alignment of 4 bytes, so output width must be aligned. int alignedWidth = BitUtils.AlignUp(width, 4); - IMemoryOwner output = ByteMemoryPool.Rent(size); - Span outputSpan = output.Memory.Span; + MemoryOwner output = MemoryOwner.Rent(size); + Span outputSpan = output.Span; ReadOnlySpan data64 = MemoryMarshal.Cast(data); @@ -402,7 +401,7 @@ namespace Ryujinx.Graphics.Texture return output; } - public static IMemoryOwner DecodeBC5(ReadOnlySpan data, int width, int height, int depth, int levels, int layers, bool signed) + public static MemoryOwner DecodeBC5(ReadOnlySpan data, int width, int height, int depth, int levels, int layers, bool signed) { int size = 0; @@ -414,7 +413,7 @@ namespace Ryujinx.Graphics.Texture // Backends currently expect a stride alignment of 4 bytes, so output width must be aligned. int alignedWidth = BitUtils.AlignUp(width, 2); - IMemoryOwner output = ByteMemoryPool.Rent(size); + MemoryOwner output = MemoryOwner.Rent(size); ReadOnlySpan data64 = MemoryMarshal.Cast(data); @@ -423,7 +422,7 @@ namespace Ryujinx.Graphics.Texture Span rPal = stackalloc byte[8]; Span gPal = stackalloc byte[8]; - Span outputAsUshort = MemoryMarshal.Cast(output.Memory.Span); + Span outputAsUshort = MemoryMarshal.Cast(output.Span); Span rTileAsUint = MemoryMarshal.Cast(rTile); Span gTileAsUint = MemoryMarshal.Cast(gTile); @@ -527,7 +526,7 @@ namespace Ryujinx.Graphics.Texture return output; } - public static IMemoryOwner DecodeBC6(ReadOnlySpan data, int width, int height, int depth, int levels, int layers, bool signed) + public static MemoryOwner DecodeBC6(ReadOnlySpan data, int width, int height, int depth, int levels, int layers, bool signed) { int size = 0; @@ -536,8 +535,8 @@ namespace Ryujinx.Graphics.Texture size += Math.Max(1, width >> l) * Math.Max(1, height >> l) * Math.Max(1, depth >> l) * layers * 8; } - IMemoryOwner output = ByteMemoryPool.Rent(size); - Span outputSpan = output.Memory.Span; + MemoryOwner output = MemoryOwner.Rent(size); + Span outputSpan = output.Span; int inputOffset = 0; int outputOffset = 0; @@ -566,7 +565,7 @@ namespace Ryujinx.Graphics.Texture return output; } - public static IMemoryOwner DecodeBC7(ReadOnlySpan data, int width, int height, int depth, int levels, int layers) + public static MemoryOwner DecodeBC7(ReadOnlySpan data, int width, int height, int depth, int levels, int layers) { int size = 0; @@ -575,8 +574,8 @@ namespace Ryujinx.Graphics.Texture size += Math.Max(1, width >> l) * Math.Max(1, height >> l) * Math.Max(1, depth >> l) * layers * 4; } - IMemoryOwner output = ByteMemoryPool.Rent(size); - Span outputSpan = output.Memory.Span; + MemoryOwner output = MemoryOwner.Rent(size); + Span outputSpan = output.Span; int inputOffset = 0; int outputOffset = 0; diff --git a/src/Ryujinx.Graphics.Texture/BCnEncoder.cs b/src/Ryujinx.Graphics.Texture/BCnEncoder.cs index 253ba305c..4db8a182b 100644 --- a/src/Ryujinx.Graphics.Texture/BCnEncoder.cs +++ b/src/Ryujinx.Graphics.Texture/BCnEncoder.cs @@ -2,7 +2,6 @@ using Ryujinx.Common; using Ryujinx.Common.Memory; using Ryujinx.Graphics.Texture.Encoders; using System; -using System.Buffers; namespace Ryujinx.Graphics.Texture { @@ -11,7 +10,7 @@ namespace Ryujinx.Graphics.Texture private const int BlockWidth = 4; private const int BlockHeight = 4; - public static IMemoryOwner EncodeBC7(Memory data, int width, int height, int depth, int levels, int layers) + public static MemoryOwner EncodeBC7(Memory data, int width, int height, int depth, int levels, int layers) { int size = 0; @@ -23,7 +22,7 @@ namespace Ryujinx.Graphics.Texture size += w * h * 16 * Math.Max(1, depth >> l) * layers; } - IMemoryOwner output = ByteMemoryPool.Rent(size); + MemoryOwner output = MemoryOwner.Rent(size); Memory outputMemory = output.Memory; int imageBaseIOffs = 0; diff --git a/src/Ryujinx.Graphics.Texture/ETC2Decoder.cs b/src/Ryujinx.Graphics.Texture/ETC2Decoder.cs index 52801ff47..49e7154c8 100644 --- a/src/Ryujinx.Graphics.Texture/ETC2Decoder.cs +++ b/src/Ryujinx.Graphics.Texture/ETC2Decoder.cs @@ -1,7 +1,6 @@ using Ryujinx.Common; using Ryujinx.Common.Memory; using System; -using System.Buffers; using System.Buffers.Binary; using System.Runtime.InteropServices; @@ -51,15 +50,15 @@ namespace Ryujinx.Graphics.Texture new int[] { -3, -5, -7, -9, 2, 4, 6, 8 }, }; - public static IMemoryOwner DecodeRgb(ReadOnlySpan data, int width, int height, int depth, int levels, int layers) + public static MemoryOwner DecodeRgb(ReadOnlySpan data, int width, int height, int depth, int levels, int layers) { ReadOnlySpan dataUlong = MemoryMarshal.Cast(data); int inputOffset = 0; - IMemoryOwner output = ByteMemoryPool.Rent(CalculateOutputSize(width, height, depth, levels, layers)); + MemoryOwner output = MemoryOwner.Rent(CalculateOutputSize(width, height, depth, levels, layers)); - Span outputUint = MemoryMarshal.Cast(output.Memory.Span); + Span outputUint = MemoryMarshal.Cast(output.Span); Span tile = stackalloc uint[BlockWidth * BlockHeight]; int imageBaseOOffs = 0; @@ -113,15 +112,15 @@ namespace Ryujinx.Graphics.Texture return output; } - public static IMemoryOwner DecodePta(ReadOnlySpan data, int width, int height, int depth, int levels, int layers) + public static MemoryOwner DecodePta(ReadOnlySpan data, int width, int height, int depth, int levels, int layers) { ReadOnlySpan dataUlong = MemoryMarshal.Cast(data); int inputOffset = 0; - IMemoryOwner output = ByteMemoryPool.Rent(CalculateOutputSize(width, height, depth, levels, layers)); + MemoryOwner output = MemoryOwner.Rent(CalculateOutputSize(width, height, depth, levels, layers)); - Span outputUint = MemoryMarshal.Cast(output.Memory.Span); + Span outputUint = MemoryMarshal.Cast(output.Span); Span tile = stackalloc uint[BlockWidth * BlockHeight]; int imageBaseOOffs = 0; @@ -170,15 +169,15 @@ namespace Ryujinx.Graphics.Texture return output; } - public static IMemoryOwner DecodeRgba(ReadOnlySpan data, int width, int height, int depth, int levels, int layers) + public static MemoryOwner DecodeRgba(ReadOnlySpan data, int width, int height, int depth, int levels, int layers) { ReadOnlySpan dataUlong = MemoryMarshal.Cast(data); int inputOffset = 0; - IMemoryOwner output = ByteMemoryPool.Rent(CalculateOutputSize(width, height, depth, levels, layers)); + MemoryOwner output = MemoryOwner.Rent(CalculateOutputSize(width, height, depth, levels, layers)); - Span outputUint = MemoryMarshal.Cast(output.Memory.Span); + Span outputUint = MemoryMarshal.Cast(output.Span); Span tile = stackalloc uint[BlockWidth * BlockHeight]; int imageBaseOOffs = 0; diff --git a/src/Ryujinx.Graphics.Texture/LayoutConverter.cs b/src/Ryujinx.Graphics.Texture/LayoutConverter.cs index d6732674b..5426af205 100644 --- a/src/Ryujinx.Graphics.Texture/LayoutConverter.cs +++ b/src/Ryujinx.Graphics.Texture/LayoutConverter.cs @@ -1,7 +1,6 @@ using Ryujinx.Common; using Ryujinx.Common.Memory; using System; -using System.Buffers; using System.Runtime.Intrinsics; using static Ryujinx.Graphics.Texture.BlockLinearConstants; @@ -95,7 +94,7 @@ namespace Ryujinx.Graphics.Texture }; } - public static IMemoryOwner ConvertBlockLinearToLinear( + public static MemoryOwner ConvertBlockLinearToLinear( int width, int height, int depth, @@ -121,8 +120,8 @@ namespace Ryujinx.Graphics.Texture blockHeight, bytesPerPixel); - IMemoryOwner outputOwner = ByteMemoryPool.Rent(outSize); - Span output = outputOwner.Memory.Span; + MemoryOwner outputOwner = MemoryOwner.Rent(outSize); + Span output = outputOwner.Span; int outOffs = 0; @@ -249,7 +248,7 @@ namespace Ryujinx.Graphics.Texture return outputOwner; } - public static IMemoryOwner ConvertLinearStridedToLinear( + public static MemoryOwner ConvertLinearStridedToLinear( int width, int height, int blockWidth, @@ -265,8 +264,8 @@ namespace Ryujinx.Graphics.Texture int outStride = BitUtils.AlignUp(w * bytesPerPixel, HostStrideAlignment); lineSize = Math.Min(lineSize, outStride); - IMemoryOwner output = ByteMemoryPool.Rent(h * outStride); - Span outSpan = output.Memory.Span; + MemoryOwner output = MemoryOwner.Rent(h * outStride); + Span outSpan = output.Span; int outOffs = 0; int inOffs = 0; diff --git a/src/Ryujinx.Graphics.Texture/PixelConverter.cs b/src/Ryujinx.Graphics.Texture/PixelConverter.cs index 4475cc98a..3676d9199 100644 --- a/src/Ryujinx.Graphics.Texture/PixelConverter.cs +++ b/src/Ryujinx.Graphics.Texture/PixelConverter.cs @@ -1,7 +1,6 @@ using Ryujinx.Common; using Ryujinx.Common.Memory; using System; -using System.Buffers; using System.Runtime.InteropServices; using System.Runtime.Intrinsics; using System.Runtime.Intrinsics.X86; @@ -21,13 +20,14 @@ namespace Ryujinx.Graphics.Texture return (remainder, outRemainder, length / stride); } - public unsafe static IMemoryOwner ConvertR4G4ToR4G4B4A4(ReadOnlySpan data, int width) + public unsafe static MemoryOwner ConvertR4G4ToR4G4B4A4(ReadOnlySpan data, int width) { - IMemoryOwner output = ByteMemoryPool.Rent(data.Length * 2); + MemoryOwner output = MemoryOwner.Rent(data.Length * 2); + Span outputSpan = output.Span; (int remainder, int outRemainder, int height) = GetLineRemainders(data.Length, width, 1, 2); - Span outputSpan = MemoryMarshal.Cast(output.Memory.Span); + Span outputSpanUInt16 = MemoryMarshal.Cast(outputSpan); if (remainder == 0) { @@ -38,7 +38,7 @@ namespace Ryujinx.Graphics.Texture int sizeTrunc = data.Length & ~7; start = sizeTrunc; - fixed (byte* inputPtr = data, outputPtr = output.Memory.Span) + fixed (byte* inputPtr = data, outputPtr = outputSpan) { for (ulong offset = 0; offset < (ulong)sizeTrunc; offset += 8) { @@ -49,7 +49,7 @@ namespace Ryujinx.Graphics.Texture for (int i = start; i < data.Length; i++) { - outputSpan[i] = data[i]; + outputSpanUInt16[i] = data[i]; } } else @@ -61,7 +61,7 @@ namespace Ryujinx.Graphics.Texture { for (int x = 0; x < width; x++) { - outputSpan[outOffset++] = data[offset++]; + outputSpanUInt16[outOffset++] = data[offset++]; } offset += remainder; @@ -72,16 +72,16 @@ namespace Ryujinx.Graphics.Texture return output; } - public static IMemoryOwner ConvertR5G6B5ToR8G8B8A8(ReadOnlySpan data, int width) + public static MemoryOwner ConvertR5G6B5ToR8G8B8A8(ReadOnlySpan data, int width) { - IMemoryOwner output = ByteMemoryPool.Rent(data.Length * 2); + MemoryOwner output = MemoryOwner.Rent(data.Length * 2); int offset = 0; int outOffset = 0; (int remainder, int outRemainder, int height) = GetLineRemainders(data.Length, width, 2, 4); ReadOnlySpan inputSpan = MemoryMarshal.Cast(data); - Span outputSpan = MemoryMarshal.Cast(output.Memory.Span); + Span outputSpan = MemoryMarshal.Cast(output.Span); for (int y = 0; y < height; y++) { @@ -109,16 +109,16 @@ namespace Ryujinx.Graphics.Texture return output; } - public static IMemoryOwner ConvertR5G5B5ToR8G8B8A8(ReadOnlySpan data, int width, bool forceAlpha) + public static MemoryOwner ConvertR5G5B5ToR8G8B8A8(ReadOnlySpan data, int width, bool forceAlpha) { - IMemoryOwner output = ByteMemoryPool.Rent(data.Length * 2); + MemoryOwner output = MemoryOwner.Rent(data.Length * 2); int offset = 0; int outOffset = 0; (int remainder, int outRemainder, int height) = GetLineRemainders(data.Length, width, 2, 4); ReadOnlySpan inputSpan = MemoryMarshal.Cast(data); - Span outputSpan = MemoryMarshal.Cast(output.Memory.Span); + Span outputSpan = MemoryMarshal.Cast(output.Span); for (int y = 0; y < height; y++) { @@ -146,16 +146,16 @@ namespace Ryujinx.Graphics.Texture return output; } - public static IMemoryOwner ConvertA1B5G5R5ToR8G8B8A8(ReadOnlySpan data, int width) + public static MemoryOwner ConvertA1B5G5R5ToR8G8B8A8(ReadOnlySpan data, int width) { - IMemoryOwner output = ByteMemoryPool.Rent(data.Length * 2); + MemoryOwner output = MemoryOwner.Rent(data.Length * 2); int offset = 0; int outOffset = 0; (int remainder, int outRemainder, int height) = GetLineRemainders(data.Length, width, 2, 4); ReadOnlySpan inputSpan = MemoryMarshal.Cast(data); - Span outputSpan = MemoryMarshal.Cast(output.Memory.Span); + Span outputSpan = MemoryMarshal.Cast(output.Span); for (int y = 0; y < height; y++) { @@ -183,16 +183,16 @@ namespace Ryujinx.Graphics.Texture return output; } - public static IMemoryOwner ConvertR4G4B4A4ToR8G8B8A8(ReadOnlySpan data, int width) + public static MemoryOwner ConvertR4G4B4A4ToR8G8B8A8(ReadOnlySpan data, int width) { - IMemoryOwner output = ByteMemoryPool.Rent(data.Length * 2); + MemoryOwner output = MemoryOwner.Rent(data.Length * 2); int offset = 0; int outOffset = 0; (int remainder, int outRemainder, int height) = GetLineRemainders(data.Length, width, 2, 4); ReadOnlySpan inputSpan = MemoryMarshal.Cast(data); - Span outputSpan = MemoryMarshal.Cast(output.Memory.Span); + Span outputSpan = MemoryMarshal.Cast(output.Span); for (int y = 0; y < height; y++) { diff --git a/src/Ryujinx.Graphics.Vulkan/BarrierBatch.cs b/src/Ryujinx.Graphics.Vulkan/BarrierBatch.cs index a6a006bb9..bcfb3dbfe 100644 --- a/src/Ryujinx.Graphics.Vulkan/BarrierBatch.cs +++ b/src/Ryujinx.Graphics.Vulkan/BarrierBatch.cs @@ -32,10 +32,12 @@ namespace Ryujinx.Graphics.Vulkan CommandBuffer } + private bool _feedbackLoopActive; private PipelineStageFlags _incoherentBufferWriteStages; private PipelineStageFlags _incoherentTextureWriteStages; private PipelineStageFlags _extraStages; private IncoherentBarrierType _queuedIncoherentBarrier; + private bool _queuedFeedbackLoopBarrier; public BarrierBatch(VulkanRenderer gd) { @@ -53,17 +55,6 @@ namespace Ryujinx.Graphics.Vulkan stages |= PipelineStageFlags.TransformFeedbackBitExt; } - if (!gd.IsTBDR) - { - // Desktop GPUs can transform image barriers into memory barriers. - - access |= AccessFlags.DepthStencilAttachmentWriteBit | AccessFlags.ColorAttachmentWriteBit; - access |= AccessFlags.DepthStencilAttachmentReadBit | AccessFlags.ColorAttachmentReadBit; - - stages |= PipelineStageFlags.EarlyFragmentTestsBit | PipelineStageFlags.LateFragmentTestsBit; - stages |= PipelineStageFlags.ColorAttachmentOutputBit; - } - return (access, stages); } @@ -178,16 +169,34 @@ namespace Ryujinx.Graphics.Vulkan } _queuedIncoherentBarrier = IncoherentBarrierType.None; + _queuedFeedbackLoopBarrier = false; } + else if (_feedbackLoopActive && _queuedFeedbackLoopBarrier) + { + // Feedback loop barrier. + + MemoryBarrier barrier = new MemoryBarrier() + { + SType = StructureType.MemoryBarrier, + SrcAccessMask = AccessFlags.ShaderWriteBit, + DstAccessMask = AccessFlags.ShaderReadBit + }; + + QueueBarrier(barrier, PipelineStageFlags.FragmentShaderBit, PipelineStageFlags.AllGraphicsBit); + + _queuedFeedbackLoopBarrier = false; + } + + _feedbackLoopActive = false; } } public unsafe void Flush(CommandBufferScoped cbs, bool inRenderPass, RenderPassHolder rpHolder, Action endRenderPass) { - Flush(cbs, null, inRenderPass, rpHolder, endRenderPass); + Flush(cbs, null, false, inRenderPass, rpHolder, endRenderPass); } - public unsafe void Flush(CommandBufferScoped cbs, ShaderCollection program, bool inRenderPass, RenderPassHolder rpHolder, Action endRenderPass) + public unsafe void Flush(CommandBufferScoped cbs, ShaderCollection program, bool feedbackLoopActive, bool inRenderPass, RenderPassHolder rpHolder, Action endRenderPass) { if (program != null) { @@ -195,6 +204,8 @@ namespace Ryujinx.Graphics.Vulkan _incoherentTextureWriteStages |= program.IncoherentTextureWriteStages; } + _feedbackLoopActive |= feedbackLoopActive; + FlushMemoryBarrier(program, inRenderPass); if (!inRenderPass && rpHolder != null) @@ -406,6 +417,8 @@ namespace Ryujinx.Graphics.Vulkan { _queuedIncoherentBarrier = type; } + + _queuedFeedbackLoopBarrier = true; } public void QueueTextureBarrier() diff --git a/src/Ryujinx.Graphics.Vulkan/BufferHolder.cs b/src/Ryujinx.Graphics.Vulkan/BufferHolder.cs index 3dcbc3130..e840fdc02 100644 --- a/src/Ryujinx.Graphics.Vulkan/BufferHolder.cs +++ b/src/Ryujinx.Graphics.Vulkan/BufferHolder.cs @@ -122,7 +122,7 @@ namespace Ryujinx.Graphics.Vulkan Range = (uint)size, }; - _gd.Api.CreateBufferView(_device, bufferViewCreateInfo, null, out var bufferView).ThrowOnError(); + _gd.Api.CreateBufferView(_device, in bufferViewCreateInfo, null, out var bufferView).ThrowOnError(); return new Auto(new DisposableBufferView(_gd.Api, _device, bufferView), this, _waitable, _buffer); } @@ -153,7 +153,7 @@ namespace Ryujinx.Graphics.Vulkan PipelineStageFlags.AllCommandsBit, DependencyFlags.DeviceGroupBit, 1, - memoryBarrier, + in memoryBarrier, 0, null, 0, @@ -770,7 +770,7 @@ namespace Ryujinx.Graphics.Vulkan 0, null, 1, - memoryBarrier, + in memoryBarrier, 0, null); } diff --git a/src/Ryujinx.Graphics.Vulkan/BufferManager.cs b/src/Ryujinx.Graphics.Vulkan/BufferManager.cs index 1b6ac9988..7523913ec 100644 --- a/src/Ryujinx.Graphics.Vulkan/BufferManager.cs +++ b/src/Ryujinx.Graphics.Vulkan/BufferManager.cs @@ -221,7 +221,7 @@ namespace Ryujinx.Graphics.Vulkan PBufferBinds = &bufferBind }; - gd.Api.QueueBindSparse(gd.Queue, 1, bindSparseInfo, default).ThrowOnError(); + gd.Api.QueueBindSparse(gd.Queue, 1, in bindSparseInfo, default).ThrowOnError(); } var holder = new BufferHolder(gd, _device, buffer, (int)size, storageAllocations); diff --git a/src/Ryujinx.Graphics.Vulkan/BufferState.cs b/src/Ryujinx.Graphics.Vulkan/BufferState.cs index d585dd53c..e49df765d 100644 --- a/src/Ryujinx.Graphics.Vulkan/BufferState.cs +++ b/src/Ryujinx.Graphics.Vulkan/BufferState.cs @@ -25,7 +25,10 @@ namespace Ryujinx.Graphics.Vulkan { var buffer = _buffer.Get(cbs, _offset, _size, true).Value; - gd.TransformFeedbackApi.CmdBindTransformFeedbackBuffers(cbs.CommandBuffer, binding, 1, buffer, (ulong)_offset, (ulong)_size); + ulong offset = (ulong)_offset; + ulong size = (ulong)_size; + + gd.TransformFeedbackApi.CmdBindTransformFeedbackBuffers(cbs.CommandBuffer, binding, 1, in buffer, in offset, in size); } } diff --git a/src/Ryujinx.Graphics.Vulkan/CommandBufferPool.cs b/src/Ryujinx.Graphics.Vulkan/CommandBufferPool.cs index e3938392f..e1fd3fb9d 100644 --- a/src/Ryujinx.Graphics.Vulkan/CommandBufferPool.cs +++ b/src/Ryujinx.Graphics.Vulkan/CommandBufferPool.cs @@ -45,7 +45,7 @@ namespace Ryujinx.Graphics.Vulkan Level = CommandBufferLevel.Primary, }; - api.AllocateCommandBuffers(device, allocateInfo, out CommandBuffer); + api.AllocateCommandBuffers(device, in allocateInfo, out CommandBuffer); Dependants = new List(); Waitables = new List(); @@ -83,7 +83,7 @@ namespace Ryujinx.Graphics.Vulkan CommandPoolCreateFlags.ResetCommandBufferBit, }; - api.CreateCommandPool(device, commandPoolCreateInfo, null, out _pool).ThrowOnError(); + api.CreateCommandPool(device, in commandPoolCreateInfo, null, out _pool).ThrowOnError(); // We need at least 2 command buffers to get texture data in some cases. _totalCommandBuffers = isLight ? 2 : MaxCommandBuffers; @@ -253,7 +253,7 @@ namespace Ryujinx.Graphics.Vulkan SType = StructureType.CommandBufferBeginInfo, }; - _api.BeginCommandBuffer(entry.CommandBuffer, commandBufferBeginInfo).ThrowOnError(); + _api.BeginCommandBuffer(entry.CommandBuffer, in commandBufferBeginInfo).ThrowOnError(); return new CommandBufferScoped(this, entry.CommandBuffer, cursor); } @@ -311,7 +311,7 @@ namespace Ryujinx.Graphics.Vulkan lock (_queueLock) { - _api.QueueSubmit(_queue, 1, sInfo, entry.Fence.GetUnsafe()).ThrowOnError(); + _api.QueueSubmit(_queue, 1, in sInfo, entry.Fence.GetUnsafe()).ThrowOnError(); } } } diff --git a/src/Ryujinx.Graphics.Vulkan/DescriptorSetCollection.cs b/src/Ryujinx.Graphics.Vulkan/DescriptorSetCollection.cs index 846dd5c7d..40fc01b24 100644 --- a/src/Ryujinx.Graphics.Vulkan/DescriptorSetCollection.cs +++ b/src/Ryujinx.Graphics.Vulkan/DescriptorSetCollection.cs @@ -43,7 +43,7 @@ namespace Ryujinx.Graphics.Vulkan PBufferInfo = &bufferInfo, }; - _holder.Api.UpdateDescriptorSets(_holder.Device, 1, writeDescriptorSet, 0, null); + _holder.Api.UpdateDescriptorSets(_holder.Device, 1, in writeDescriptorSet, 0, null); } } @@ -66,7 +66,7 @@ namespace Ryujinx.Graphics.Vulkan PBufferInfo = pBufferInfo, }; - _holder.Api.UpdateDescriptorSets(_holder.Device, 1, writeDescriptorSet, 0, null); + _holder.Api.UpdateDescriptorSets(_holder.Device, 1, in writeDescriptorSet, 0, null); } } @@ -84,7 +84,7 @@ namespace Ryujinx.Graphics.Vulkan PImageInfo = &imageInfo, }; - _holder.Api.UpdateDescriptorSets(_holder.Device, 1, writeDescriptorSet, 0, null); + _holder.Api.UpdateDescriptorSets(_holder.Device, 1, in writeDescriptorSet, 0, null); } } @@ -107,7 +107,7 @@ namespace Ryujinx.Graphics.Vulkan PImageInfo = pImageInfo, }; - _holder.Api.UpdateDescriptorSets(_holder.Device, 1, writeDescriptorSet, 0, null); + _holder.Api.UpdateDescriptorSets(_holder.Device, 1, in writeDescriptorSet, 0, null); } } @@ -144,7 +144,7 @@ namespace Ryujinx.Graphics.Vulkan PImageInfo = pImageInfo, }; - _holder.Api.UpdateDescriptorSets(_holder.Device, 1, writeDescriptorSet, 0, null); + _holder.Api.UpdateDescriptorSets(_holder.Device, 1, in writeDescriptorSet, 0, null); i += count - 1; } @@ -166,7 +166,7 @@ namespace Ryujinx.Graphics.Vulkan PTexelBufferView = &texelBufferView, }; - _holder.Api.UpdateDescriptorSets(_holder.Device, 1, writeDescriptorSet, 0, null); + _holder.Api.UpdateDescriptorSets(_holder.Device, 1, in writeDescriptorSet, 0, null); } } @@ -200,7 +200,7 @@ namespace Ryujinx.Graphics.Vulkan PTexelBufferView = pTexelBufferView + i, }; - _holder.Api.UpdateDescriptorSets(_holder.Device, 1, writeDescriptorSet, 0, null); + _holder.Api.UpdateDescriptorSets(_holder.Device, 1, in writeDescriptorSet, 0, null); } i += count; diff --git a/src/Ryujinx.Graphics.Vulkan/DescriptorSetManager.cs b/src/Ryujinx.Graphics.Vulkan/DescriptorSetManager.cs index 707ae1292..97669942c 100644 --- a/src/Ryujinx.Graphics.Vulkan/DescriptorSetManager.cs +++ b/src/Ryujinx.Graphics.Vulkan/DescriptorSetManager.cs @@ -40,7 +40,7 @@ namespace Ryujinx.Graphics.Vulkan PPoolSizes = pPoolsSize, }; - Api.CreateDescriptorPool(device, descriptorPoolCreateInfo, null, out _pool).ThrowOnError(); + Api.CreateDescriptorPool(device, in descriptorPoolCreateInfo, null, out _pool).ThrowOnError(); } } diff --git a/src/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs b/src/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs index 75ffca2ca..298526d51 100644 --- a/src/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs +++ b/src/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs @@ -4,6 +4,7 @@ using Ryujinx.Graphics.Shader; using Silk.NET.Vulkan; using System; using System.Buffers; +using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using CompareOp = Ryujinx.Graphics.GAL.CompareOp; @@ -42,15 +43,15 @@ namespace Ryujinx.Graphics.Vulkan private record struct TextureRef { public ShaderStage Stage; - public TextureStorage Storage; - public Auto View; + public TextureView View; + public Auto ImageView; public Auto Sampler; - public TextureRef(ShaderStage stage, TextureStorage storage, Auto view, Auto sampler) + public TextureRef(ShaderStage stage, TextureView view, Auto imageView, Auto sampler) { Stage = stage; - Storage = storage; View = view; + ImageView = imageView; Sampler = sampler; } } @@ -58,14 +59,14 @@ namespace Ryujinx.Graphics.Vulkan private record struct ImageRef { public ShaderStage Stage; - public TextureStorage Storage; - public Auto View; + public TextureView View; + public Auto ImageView; - public ImageRef(ShaderStage stage, TextureStorage storage, Auto view) + public ImageRef(ShaderStage stage, TextureView view, Auto imageView) { Stage = stage; - Storage = storage; View = view; + ImageView = imageView; } } @@ -124,6 +125,8 @@ namespace Ryujinx.Graphics.Vulkan private readonly TextureView _dummyTexture; private readonly SamplerHolder _dummySampler; + public List FeedbackLoopHazards { get; private set; } + public DescriptorSetUpdater(VulkanRenderer gd, Device device) { _gd = gd; @@ -209,10 +212,15 @@ namespace Ryujinx.Graphics.Vulkan _templateUpdater = new(); } - public void Initialize() + public void Initialize(bool isMainPipeline) { - IMemoryOwner dummyTextureData = ByteMemoryPool.RentCleared(4); + MemoryOwner dummyTextureData = MemoryOwner.RentCleared(4); _dummyTexture.SetData(dummyTextureData); + + if (isMainPipeline) + { + FeedbackLoopHazards = new(); + } } private static bool BindingOverlaps(ref DescriptorBufferInfo info, int bindingOffset, int offset, int size) @@ -275,6 +283,18 @@ namespace Ryujinx.Graphics.Vulkan public void InsertBindingBarriers(CommandBufferScoped cbs) { + if ((FeedbackLoopHazards?.Count ?? 0) > 0) + { + // Clear existing hazards - they will be rebuilt. + + foreach (TextureView hazard in FeedbackLoopHazards) + { + hazard.DecrementHazardUses(); + } + + FeedbackLoopHazards.Clear(); + } + foreach (ResourceBindingSegment segment in _program.BindingSegments[PipelineBase.TextureSetIndex]) { if (segment.Type == ResourceType.TextureAndSampler) @@ -284,7 +304,7 @@ namespace Ryujinx.Graphics.Vulkan for (int i = 0; i < segment.Count; i++) { ref var texture = ref _textureRefs[segment.Binding + i]; - texture.Storage?.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, texture.Stage.ConvertToPipelineStageFlags()); + texture.View?.PrepareForUsage(cbs, texture.Stage.ConvertToPipelineStageFlags(), FeedbackLoopHazards); } } else @@ -305,7 +325,7 @@ namespace Ryujinx.Graphics.Vulkan for (int i = 0; i < segment.Count; i++) { ref var image = ref _imageRefs[segment.Binding + i]; - image.Storage?.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, image.Stage.ConvertToPipelineStageFlags()); + image.View?.PrepareForUsage(cbs, image.Stage.ConvertToPipelineStageFlags(), FeedbackLoopHazards); } } else @@ -385,9 +405,12 @@ namespace Ryujinx.Graphics.Vulkan } else if (image is TextureView view) { - view.Storage.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, stage.ConvertToPipelineStageFlags()); + ref ImageRef iRef = ref _imageRefs[binding]; - _imageRefs[binding] = new(stage, view.Storage, view.GetView(imageFormat).GetIdentityImageView()); + iRef.View?.ClearUsage(FeedbackLoopHazards); + view?.PrepareForUsage(cbs, stage.ConvertToPipelineStageFlags(), FeedbackLoopHazards); + + iRef = new(stage, view, view.GetView(imageFormat).GetIdentityImageView()); } else { @@ -486,9 +509,12 @@ namespace Ryujinx.Graphics.Vulkan } else if (texture is TextureView view) { - view.Storage.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, stage.ConvertToPipelineStageFlags()); + ref TextureRef iRef = ref _textureRefs[binding]; - _textureRefs[binding] = new(stage, view.Storage, view.GetImageView(), ((SamplerHolder)sampler)?.GetSampler()); + iRef.View?.ClearUsage(FeedbackLoopHazards); + view?.PrepareForUsage(cbs, stage.ConvertToPipelineStageFlags(), FeedbackLoopHazards); + + iRef = new(stage, view, view.GetImageView(), ((SamplerHolder)sampler)?.GetSampler()); } else { @@ -510,7 +536,7 @@ namespace Ryujinx.Graphics.Vulkan { view.Storage.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, stage.ConvertToPipelineStageFlags()); - _textureRefs[binding] = new(stage, view.Storage, view.GetIdentityImageView(), ((SamplerHolder)sampler)?.GetSampler()); + _textureRefs[binding] = new(stage, view, view.GetIdentityImageView(), ((SamplerHolder)sampler)?.GetSampler()); SignalDirty(DirtyFlags.Texture); } @@ -836,7 +862,7 @@ namespace Ryujinx.Graphics.Vulkan ref var texture = ref textures[i]; ref var refs = ref _textureRefs[binding + i]; - texture.ImageView = refs.View?.Get(cbs).Value ?? default; + texture.ImageView = refs.ImageView?.Get(cbs).Value ?? default; texture.Sampler = refs.Sampler?.Get(cbs).Value ?? default; if (texture.ImageView.Handle == 0) @@ -886,7 +912,7 @@ namespace Ryujinx.Graphics.Vulkan for (int i = 0; i < count; i++) { - images[i].ImageView = _imageRefs[binding + i].View?.Get(cbs).Value ?? default; + images[i].ImageView = _imageRefs[binding + i].ImageView?.Get(cbs).Value ?? default; } tu.Push(images[..count]); @@ -957,7 +983,7 @@ namespace Ryujinx.Graphics.Vulkan ref var texture = ref textures[i]; ref var refs = ref _textureRefs[binding + i]; - texture.ImageView = refs.View?.Get(cbs).Value ?? default; + texture.ImageView = refs.ImageView?.Get(cbs).Value ?? default; texture.Sampler = refs.Sampler?.Get(cbs).Value ?? default; if (texture.ImageView.Handle == 0) diff --git a/src/Ryujinx.Graphics.Vulkan/FeedbackLoopAspects.cs b/src/Ryujinx.Graphics.Vulkan/FeedbackLoopAspects.cs new file mode 100644 index 000000000..22f73679d --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/FeedbackLoopAspects.cs @@ -0,0 +1,12 @@ +using System; + +namespace Ryujinx.Graphics.Vulkan +{ + [Flags] + internal enum FeedbackLoopAspects + { + None = 0, + Color = 1 << 0, + Depth = 1 << 1, + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs b/src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs index 5c5a8f3ad..8d80e9d05 100644 --- a/src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs +++ b/src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs @@ -250,7 +250,7 @@ namespace Ryujinx.Graphics.Vulkan Layers = Layers, }; - api.CreateFramebuffer(_device, framebufferCreateInfo, null, out var framebuffer).ThrowOnError(); + api.CreateFramebuffer(_device, in framebufferCreateInfo, null, out var framebuffer).ThrowOnError(); return new Auto(new DisposableFramebuffer(api, _device, framebuffer), null, _attachments); } @@ -302,6 +302,27 @@ namespace Ryujinx.Graphics.Vulkan _depthStencil?.Storage?.AddStoreOpUsage(true); } + public void ClearBindings() + { + _depthStencil?.Storage.ClearBindings(); + + for (int i = 0; i < _colorsCanonical.Length; i++) + { + _colorsCanonical[i]?.Storage.ClearBindings(); + } + } + + public void AddBindings() + { + _depthStencil?.Storage.AddBinding(_depthStencil); + + for (int i = 0; i < _colorsCanonical.Length; i++) + { + TextureView color = _colorsCanonical[i]; + color?.Storage.AddBinding(color); + } + } + public (RenderPassHolder rpHolder, Auto framebuffer) GetPassAndFramebuffer( VulkanRenderer gd, Device device, diff --git a/src/Ryujinx.Graphics.Vulkan/HardwareCapabilities.cs b/src/Ryujinx.Graphics.Vulkan/HardwareCapabilities.cs index b6694bcb3..bd17867b1 100644 --- a/src/Ryujinx.Graphics.Vulkan/HardwareCapabilities.cs +++ b/src/Ryujinx.Graphics.Vulkan/HardwareCapabilities.cs @@ -46,6 +46,8 @@ namespace Ryujinx.Graphics.Vulkan public readonly bool SupportsViewportArray2; public readonly bool SupportsHostImportedMemory; public readonly bool SupportsDepthClipControl; + public readonly bool SupportsAttachmentFeedbackLoop; + public readonly bool SupportsDynamicAttachmentFeedbackLoop; public readonly uint SubgroupSize; public readonly SampleCountFlags SupportedSampleCounts; public readonly PortabilitySubsetFlags PortabilitySubset; @@ -84,6 +86,8 @@ namespace Ryujinx.Graphics.Vulkan bool supportsViewportArray2, bool supportsHostImportedMemory, bool supportsDepthClipControl, + bool supportsAttachmentFeedbackLoop, + bool supportsDynamicAttachmentFeedbackLoop, uint subgroupSize, SampleCountFlags supportedSampleCounts, PortabilitySubsetFlags portabilitySubset, @@ -121,6 +125,8 @@ namespace Ryujinx.Graphics.Vulkan SupportsViewportArray2 = supportsViewportArray2; SupportsHostImportedMemory = supportsHostImportedMemory; SupportsDepthClipControl = supportsDepthClipControl; + SupportsAttachmentFeedbackLoop = supportsAttachmentFeedbackLoop; + SupportsDynamicAttachmentFeedbackLoop = supportsDynamicAttachmentFeedbackLoop; SubgroupSize = subgroupSize; SupportedSampleCounts = supportedSampleCounts; PortabilitySubset = portabilitySubset; diff --git a/src/Ryujinx.Graphics.Vulkan/HostMemoryAllocator.cs b/src/Ryujinx.Graphics.Vulkan/HostMemoryAllocator.cs index baccc698f..ff1565246 100644 --- a/src/Ryujinx.Graphics.Vulkan/HostMemoryAllocator.cs +++ b/src/Ryujinx.Graphics.Vulkan/HostMemoryAllocator.cs @@ -115,7 +115,7 @@ namespace Ryujinx.Graphics.Vulkan PNext = &importInfo, }; - Result result = _api.AllocateMemory(_device, memoryAllocateInfo, null, out var deviceMemory); + Result result = _api.AllocateMemory(_device, in memoryAllocateInfo, null, out var deviceMemory); if (result < Result.Success) { diff --git a/src/Ryujinx.Graphics.Vulkan/ImageArray.cs b/src/Ryujinx.Graphics.Vulkan/ImageArray.cs index e42750d3c..467b01111 100644 --- a/src/Ryujinx.Graphics.Vulkan/ImageArray.cs +++ b/src/Ryujinx.Graphics.Vulkan/ImageArray.cs @@ -95,7 +95,7 @@ namespace Ryujinx.Graphics.Vulkan { _cachedCommandBufferIndex = -1; _storages = null; - SetDirty(_gd); + SetDirty(_gd, isImage: true); } public void QueueWriteToReadBarriers(CommandBufferScoped cbs, PipelineStageFlags stageFlags) diff --git a/src/Ryujinx.Graphics.Vulkan/MemoryAllocatorBlockList.cs b/src/Ryujinx.Graphics.Vulkan/MemoryAllocatorBlockList.cs index a1acc90f9..3d42ed7e2 100644 --- a/src/Ryujinx.Graphics.Vulkan/MemoryAllocatorBlockList.cs +++ b/src/Ryujinx.Graphics.Vulkan/MemoryAllocatorBlockList.cs @@ -220,7 +220,7 @@ namespace Ryujinx.Graphics.Vulkan MemoryTypeIndex = (uint)MemoryTypeIndex, }; - _api.AllocateMemory(_device, memoryAllocateInfo, null, out var deviceMemory).ThrowOnError(); + _api.AllocateMemory(_device, in memoryAllocateInfo, null, out var deviceMemory).ThrowOnError(); IntPtr hostPointer = IntPtr.Zero; diff --git a/src/Ryujinx.Graphics.Vulkan/MoltenVK/MVKInitialization.cs b/src/Ryujinx.Graphics.Vulkan/MoltenVK/MVKInitialization.cs index 457240aa0..930d6b525 100644 --- a/src/Ryujinx.Graphics.Vulkan/MoltenVK/MVKInitialization.cs +++ b/src/Ryujinx.Graphics.Vulkan/MoltenVK/MVKInitialization.cs @@ -1,3 +1,4 @@ +using Silk.NET.Core.Loader; using Silk.NET.Vulkan; using System; using System.Runtime.InteropServices; @@ -8,6 +9,8 @@ namespace Ryujinx.Graphics.Vulkan.MoltenVK [SupportedOSPlatform("macos")] public static partial class MVKInitialization { + private const string VulkanLib = "libvulkan.dylib"; + [LibraryImport("libMoltenVK.dylib")] private static partial Result vkGetMoltenVKConfigurationMVK(IntPtr unusedInstance, out MVKConfiguration config, in IntPtr configSize); @@ -29,5 +32,20 @@ namespace Ryujinx.Graphics.Vulkan.MoltenVK vkSetMoltenVKConfigurationMVK(IntPtr.Zero, config, configSize); } + + private static string[] Resolver(string path) + { + if (path.EndsWith(VulkanLib)) + { + path = path[..^VulkanLib.Length] + "libMoltenVK.dylib"; + return [path]; + } + return Array.Empty(); + } + + public static void InitializeResolver() + { + ((DefaultPathResolver)PathResolver.Default).Resolvers.Insert(0, Resolver); + } } } diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs b/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs index 275ec9228..3cce9ffbe 100644 --- a/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs +++ b/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs @@ -2,6 +2,7 @@ using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.Shader; using Silk.NET.Vulkan; using System; +using System.Collections.Generic; using System.Linq; using System.Numerics; using System.Runtime.CompilerServices; @@ -33,6 +34,7 @@ namespace Ryujinx.Graphics.Vulkan public readonly Action EndRenderPassDelegate; protected PipelineDynamicState DynamicState; + protected bool IsMainPipeline; private PipelineState _newState; private bool _graphicsStateDirty; private bool _computeStateDirty; @@ -85,6 +87,9 @@ namespace Ryujinx.Graphics.Vulkan private bool _tfEnabled; private bool _tfActive; + private FeedbackLoopAspects _feedbackLoop; + private bool _passWritesDepthStencil; + private readonly PipelineColorBlendAttachmentState[] _storedBlend; public ulong DrawCount { get; private set; } public bool RenderPassActive { get; private set; } @@ -102,7 +107,7 @@ namespace Ryujinx.Graphics.Vulkan SType = StructureType.PipelineCacheCreateInfo, }; - gd.Api.CreatePipelineCache(device, pipelineCacheCreateInfo, null, out PipelineCache).ThrowOnError(); + gd.Api.CreatePipelineCache(device, in pipelineCacheCreateInfo, null, out PipelineCache).ThrowOnError(); _descriptorSetUpdater = new DescriptorSetUpdater(gd, device); _vertexBufferUpdater = new VertexBufferUpdater(gd); @@ -126,7 +131,7 @@ namespace Ryujinx.Graphics.Vulkan public void Initialize() { - _descriptorSetUpdater.Initialize(); + _descriptorSetUpdater.Initialize(IsMainPipeline); QuadsToTrisPattern = new IndexBufferPattern(Gd, 4, 6, 0, new[] { 0, 1, 2, 0, 2, 3 }, 4, false); TriFanToTrisPattern = new IndexBufferPattern(Gd, 3, 3, 2, new[] { int.MinValue, -1, 0 }, 1, true); @@ -834,6 +839,8 @@ namespace Ryujinx.Graphics.Vulkan _newState.DepthTestEnable = depthTest.TestEnable; _newState.DepthWriteEnable = depthTest.WriteEnable; _newState.DepthCompareOp = depthTest.Func.Convert(); + + UpdatePassDepthStencil(); SignalStateChange(); } @@ -1099,6 +1106,8 @@ namespace Ryujinx.Graphics.Vulkan _newState.StencilFrontPassOp = stencilTest.FrontDpPass.Convert(); _newState.StencilFrontDepthFailOp = stencilTest.FrontDpFail.Convert(); _newState.StencilFrontCompareOp = stencilTest.FrontFunc.Convert(); + + UpdatePassDepthStencil(); SignalStateChange(); } @@ -1446,7 +1455,23 @@ namespace Ryujinx.Graphics.Vulkan } } + if (IsMainPipeline) + { + FramebufferParams?.ClearBindings(); + } + FramebufferParams = new FramebufferParams(Device, colors, depthStencil); + + if (IsMainPipeline) + { + FramebufferParams.AddBindings(); + + _newState.FeedbackLoopAspects = FeedbackLoopAspects.None; + _bindingBarriersDirty = true; + } + + _passWritesDepthStencil = false; + UpdatePassDepthStencil(); UpdatePipelineAttachmentFormats(); } @@ -1513,11 +1538,82 @@ namespace Ryujinx.Graphics.Vulkan } } - Gd.Barriers.Flush(Cbs, _program, RenderPassActive, _rpHolder, EndRenderPassDelegate); + Gd.Barriers.Flush(Cbs, _program, _feedbackLoop != 0, RenderPassActive, _rpHolder, EndRenderPassDelegate); _descriptorSetUpdater.UpdateAndBindDescriptorSets(Cbs, PipelineBindPoint.Compute); } + private bool ChangeFeedbackLoop(FeedbackLoopAspects aspects) + { + if (_feedbackLoop != aspects) + { + if (Gd.Capabilities.SupportsDynamicAttachmentFeedbackLoop) + { + DynamicState.SetFeedbackLoop(aspects); + } + else + { + _newState.FeedbackLoopAspects = aspects; + } + + _feedbackLoop = aspects; + + return true; + } + + return false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool UpdateFeedbackLoop() + { + List hazards = _descriptorSetUpdater.FeedbackLoopHazards; + + if ((hazards?.Count ?? 0) > 0) + { + FeedbackLoopAspects aspects = 0; + + foreach (TextureView view in hazards) + { + // May need to enforce feedback loop layout here in the future. + // Though technically, it should always work with the general layout. + + if (view.Info.Format.IsDepthOrStencil()) + { + if (_passWritesDepthStencil) + { + // If depth/stencil isn't written in the pass, it doesn't count as a feedback loop. + + aspects |= FeedbackLoopAspects.Depth; + } + } + else + { + aspects |= FeedbackLoopAspects.Color; + } + } + + return ChangeFeedbackLoop(aspects); + } + else if (_feedbackLoop != 0) + { + return ChangeFeedbackLoop(FeedbackLoopAspects.None); + } + + return false; + } + + private void UpdatePassDepthStencil() + { + if (!RenderPassActive) + { + _passWritesDepthStencil = false; + } + + // Stencil test being enabled doesn't necessarily mean a write, but it's not critical to check. + _passWritesDepthStencil |= (_newState.DepthTestEnable && _newState.DepthWriteEnable) || _newState.StencilTestEnable; + } + private bool RecreateGraphicsPipelineIfNeeded() { if (AutoFlush.ShouldFlushDraw(DrawCount)) @@ -1525,7 +1621,7 @@ namespace Ryujinx.Graphics.Vulkan Gd.FlushAllCommands(); } - DynamicState.ReplayIfDirty(Gd.Api, CommandBuffer); + DynamicState.ReplayIfDirty(Gd, CommandBuffer); if (_needsIndexBufferRebind && _indexBufferPattern == null) { @@ -1559,7 +1655,15 @@ namespace Ryujinx.Graphics.Vulkan _vertexBufferUpdater.Commit(Cbs); } - if (_graphicsStateDirty || Pbp != PipelineBindPoint.Graphics) + if (_bindingBarriersDirty) + { + // Stale barriers may have been activated by switching program. Emit any that are relevant. + _descriptorSetUpdater.InsertBindingBarriers(Cbs); + + _bindingBarriersDirty = false; + } + + if (UpdateFeedbackLoop() || _graphicsStateDirty || Pbp != PipelineBindPoint.Graphics) { if (!CreatePipeline(PipelineBindPoint.Graphics)) { @@ -1568,17 +1672,9 @@ namespace Ryujinx.Graphics.Vulkan _graphicsStateDirty = false; Pbp = PipelineBindPoint.Graphics; - - if (_bindingBarriersDirty) - { - // Stale barriers may have been activated by switching program. Emit any that are relevant. - _descriptorSetUpdater.InsertBindingBarriers(Cbs); - - _bindingBarriersDirty = false; - } } - Gd.Barriers.Flush(Cbs, _program, RenderPassActive, _rpHolder, EndRenderPassDelegate); + Gd.Barriers.Flush(Cbs, _program, _feedbackLoop != 0, RenderPassActive, _rpHolder, EndRenderPassDelegate); _descriptorSetUpdater.UpdateAndBindDescriptorSets(Cbs, PipelineBindPoint.Graphics); @@ -1648,7 +1744,7 @@ namespace Ryujinx.Graphics.Vulkan ClearValueCount = 1, }; - Gd.Api.CmdBeginRenderPass(CommandBuffer, renderPassBeginInfo, SubpassContents.Inline); + Gd.Api.CmdBeginRenderPass(CommandBuffer, in renderPassBeginInfo, SubpassContents.Inline); RenderPassActive = true; } } diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineConverter.cs b/src/Ryujinx.Graphics.Vulkan/PipelineConverter.cs index 89ce10b0a..85069c6b2 100644 --- a/src/Ryujinx.Graphics.Vulkan/PipelineConverter.cs +++ b/src/Ryujinx.Graphics.Vulkan/PipelineConverter.cs @@ -116,7 +116,7 @@ namespace Ryujinx.Graphics.Vulkan DependencyCount = 1, }; - gd.Api.CreateRenderPass(device, renderPassCreateInfo, null, out var renderPass).ThrowOnError(); + gd.Api.CreateRenderPass(device, in renderPassCreateInfo, null, out var renderPass).ThrowOnError(); return new DisposableRenderPass(gd.Api, device, renderPass); } diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineDynamicState.cs b/src/Ryujinx.Graphics.Vulkan/PipelineDynamicState.cs index 1cc33f728..ad26ff7b3 100644 --- a/src/Ryujinx.Graphics.Vulkan/PipelineDynamicState.cs +++ b/src/Ryujinx.Graphics.Vulkan/PipelineDynamicState.cs @@ -1,5 +1,6 @@ using Ryujinx.Common.Memory; using Silk.NET.Vulkan; +using Silk.NET.Vulkan.Extensions.EXT; namespace Ryujinx.Graphics.Vulkan { @@ -21,6 +22,8 @@ namespace Ryujinx.Graphics.Vulkan private Array4 _blendConstants; + private FeedbackLoopAspects _feedbackLoopAspects; + public uint ViewportsCount; public Array16 Viewports; @@ -32,7 +35,8 @@ namespace Ryujinx.Graphics.Vulkan Scissor = 1 << 2, Stencil = 1 << 3, Viewport = 1 << 4, - All = Blend | DepthBias | Scissor | Stencil | Viewport, + FeedbackLoop = 1 << 5, + All = Blend | DepthBias | Scissor | Stencil | Viewport | FeedbackLoop, } private DirtyFlags _dirty; @@ -99,13 +103,22 @@ namespace Ryujinx.Graphics.Vulkan } } + public void SetFeedbackLoop(FeedbackLoopAspects aspects) + { + _feedbackLoopAspects = aspects; + + _dirty |= DirtyFlags.FeedbackLoop; + } + public void ForceAllDirty() { _dirty = DirtyFlags.All; } - public void ReplayIfDirty(Vk api, CommandBuffer commandBuffer) + public void ReplayIfDirty(VulkanRenderer gd, CommandBuffer commandBuffer) { + Vk api = gd.Api; + if (_dirty.HasFlag(DirtyFlags.Blend)) { RecordBlend(api, commandBuffer); @@ -131,6 +144,11 @@ namespace Ryujinx.Graphics.Vulkan RecordViewport(api, commandBuffer); } + if (_dirty.HasFlag(DirtyFlags.FeedbackLoop) && gd.Capabilities.SupportsDynamicAttachmentFeedbackLoop) + { + RecordFeedbackLoop(gd.DynamicFeedbackLoopApi, commandBuffer); + } + _dirty = DirtyFlags.None; } @@ -169,5 +187,17 @@ namespace Ryujinx.Graphics.Vulkan api.CmdSetViewport(commandBuffer, 0, ViewportsCount, Viewports.AsSpan()); } } + + private readonly void RecordFeedbackLoop(ExtAttachmentFeedbackLoopDynamicState api, CommandBuffer commandBuffer) + { + ImageAspectFlags aspects = (_feedbackLoopAspects & FeedbackLoopAspects.Color) != 0 ? ImageAspectFlags.ColorBit : 0; + + if ((_feedbackLoopAspects & FeedbackLoopAspects.Depth) != 0) + { + aspects |= ImageAspectFlags.DepthBit | ImageAspectFlags.StencilBit; + } + + api.CmdSetAttachmentFeedbackLoopEnable(commandBuffer, aspects); + } } } diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineFull.cs b/src/Ryujinx.Graphics.Vulkan/PipelineFull.cs index cf65eefb0..54d43bdba 100644 --- a/src/Ryujinx.Graphics.Vulkan/PipelineFull.cs +++ b/src/Ryujinx.Graphics.Vulkan/PipelineFull.cs @@ -28,6 +28,8 @@ namespace Ryujinx.Graphics.Vulkan _activeBufferMirrors = new(); CommandBuffer = (Cbs = gd.CommandBufferPool.Rent()).CommandBuffer; + + IsMainPipeline = true; } private void CopyPendingQuery() @@ -235,7 +237,7 @@ namespace Ryujinx.Graphics.Vulkan if (Pipeline != null && Pbp == PipelineBindPoint.Graphics) { - DynamicState.ReplayIfDirty(Gd.Api, CommandBuffer); + DynamicState.ReplayIfDirty(Gd, CommandBuffer); } } diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineLayoutFactory.cs b/src/Ryujinx.Graphics.Vulkan/PipelineLayoutFactory.cs index bca119f6a..8d7815616 100644 --- a/src/Ryujinx.Graphics.Vulkan/PipelineLayoutFactory.cs +++ b/src/Ryujinx.Graphics.Vulkan/PipelineLayoutFactory.cs @@ -91,7 +91,7 @@ namespace Ryujinx.Graphics.Vulkan Flags = flags, }; - gd.Api.CreateDescriptorSetLayout(device, descriptorSetLayoutCreateInfo, null, out layouts[setIndex]).ThrowOnError(); + gd.Api.CreateDescriptorSetLayout(device, in descriptorSetLayoutCreateInfo, null, out layouts[setIndex]).ThrowOnError(); } } diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineState.cs b/src/Ryujinx.Graphics.Vulkan/PipelineState.cs index 12c0e2bee..8affeff32 100644 --- a/src/Ryujinx.Graphics.Vulkan/PipelineState.cs +++ b/src/Ryujinx.Graphics.Vulkan/PipelineState.cs @@ -8,6 +8,7 @@ namespace Ryujinx.Graphics.Vulkan struct PipelineState : IDisposable { private const int RequiredSubgroupSize = 32; + private const int MaxDynamicStatesCount = 9; public PipelineUid Internal; @@ -299,6 +300,12 @@ namespace Ryujinx.Graphics.Vulkan set => Internal.Id8 = (Internal.Id8 & 0xFFFFFFFFFFFFFFBF) | ((value ? 1UL : 0UL) << 6); } + public FeedbackLoopAspects FeedbackLoopAspects + { + readonly get => (FeedbackLoopAspects)((Internal.Id8 >> 7) & 0x3); + set => Internal.Id8 = (Internal.Id8 & 0xFFFFFFFFFFFFFE7F) | (((ulong)value) << 7); + } + public bool HasTessellationControlShader; public NativeArray Stages; public PipelineLayout PipelineLayout; @@ -439,7 +446,7 @@ namespace Ryujinx.Graphics.Vulkan { SType = StructureType.PipelineInputAssemblyStateCreateInfo, PrimitiveRestartEnable = primitiveRestartEnable, - Topology = Topology, + Topology = HasTessellationControlShader ? PrimitiveTopology.PatchList : Topology, }; var tessellationState = new PipelineTessellationStateCreateInfo @@ -565,9 +572,11 @@ namespace Ryujinx.Graphics.Vulkan bool supportsExtDynamicState = gd.Capabilities.SupportsExtendedDynamicState; - int dynamicStatesCount = supportsExtDynamicState ? 8 : 7; + bool supportsFeedbackLoopDynamicState = gd.Capabilities.SupportsDynamicAttachmentFeedbackLoop; - DynamicState* dynamicStates = stackalloc DynamicState[dynamicStatesCount]; + DynamicState* dynamicStates = stackalloc DynamicState[MaxDynamicStatesCount]; + + int dynamicStatesCount = 7; dynamicStates[0] = DynamicState.Viewport; dynamicStates[1] = DynamicState.Scissor; @@ -579,7 +588,12 @@ namespace Ryujinx.Graphics.Vulkan if (supportsExtDynamicState) { - dynamicStates[7] = DynamicState.VertexInputBindingStrideExt; + dynamicStates[dynamicStatesCount++] = DynamicState.VertexInputBindingStrideExt; + } + + if (supportsFeedbackLoopDynamicState) + { + dynamicStates[dynamicStatesCount++] = DynamicState.AttachmentFeedbackLoopEnableExt; } var pipelineDynamicStateCreateInfo = new PipelineDynamicStateCreateInfo @@ -589,9 +603,27 @@ namespace Ryujinx.Graphics.Vulkan PDynamicStates = dynamicStates, }; + PipelineCreateFlags flags = 0; + + if (gd.Capabilities.SupportsAttachmentFeedbackLoop) + { + FeedbackLoopAspects aspects = FeedbackLoopAspects; + + if ((aspects & FeedbackLoopAspects.Color) != 0) + { + flags |= PipelineCreateFlags.CreateColorAttachmentFeedbackLoopBitExt; + } + + if ((aspects & FeedbackLoopAspects.Depth) != 0) + { + flags |= PipelineCreateFlags.CreateDepthStencilAttachmentFeedbackLoopBitExt; + } + } + var pipelineCreateInfo = new GraphicsPipelineCreateInfo { SType = StructureType.GraphicsPipelineCreateInfo, + Flags = flags, StageCount = StagesCount, PStages = Stages.Pointer, PVertexInputState = &vertexInputState, diff --git a/src/Ryujinx.Graphics.Vulkan/Queries/BufferedQuery.cs b/src/Ryujinx.Graphics.Vulkan/Queries/BufferedQuery.cs index 714cb2833..c9a546648 100644 --- a/src/Ryujinx.Graphics.Vulkan/Queries/BufferedQuery.cs +++ b/src/Ryujinx.Graphics.Vulkan/Queries/BufferedQuery.cs @@ -52,7 +52,7 @@ namespace Ryujinx.Graphics.Vulkan.Queries PipelineStatistics = flags, }; - gd.Api.CreateQueryPool(device, queryPoolCreateInfo, null, out _queryPool).ThrowOnError(); + gd.Api.CreateQueryPool(device, in queryPoolCreateInfo, null, out _queryPool).ThrowOnError(); } var buffer = gd.BufferManager.Create(gd, sizeof(long), forConditionalRendering: true); diff --git a/src/Ryujinx.Graphics.Vulkan/RenderPassHolder.cs b/src/Ryujinx.Graphics.Vulkan/RenderPassHolder.cs index b2dd0dd87..a364c5716 100644 --- a/src/Ryujinx.Graphics.Vulkan/RenderPassHolder.cs +++ b/src/Ryujinx.Graphics.Vulkan/RenderPassHolder.cs @@ -125,7 +125,7 @@ namespace Ryujinx.Graphics.Vulkan DependencyCount = 1, }; - gd.Api.CreateRenderPass(device, renderPassCreateInfo, null, out var renderPass).ThrowOnError(); + gd.Api.CreateRenderPass(device, in renderPassCreateInfo, null, out var renderPass).ThrowOnError(); _renderPass = new Auto(new DisposableRenderPass(gd.Api, device, renderPass)); } diff --git a/src/Ryujinx.Graphics.Vulkan/ResourceArray.cs b/src/Ryujinx.Graphics.Vulkan/ResourceArray.cs index 0880a10f0..f96b4a845 100644 --- a/src/Ryujinx.Graphics.Vulkan/ResourceArray.cs +++ b/src/Ryujinx.Graphics.Vulkan/ResourceArray.cs @@ -14,13 +14,20 @@ namespace Ryujinx.Graphics.Vulkan private int _bindCount; - protected void SetDirty(VulkanRenderer gd) + protected void SetDirty(VulkanRenderer gd, bool isImage) { ReleaseDescriptorSet(); if (_bindCount != 0) { - gd.PipelineInternal.ForceTextureDirty(); + if (isImage) + { + gd.PipelineInternal.ForceImageDirty(); + } + else + { + gd.PipelineInternal.ForceTextureDirty(); + } } } diff --git a/src/Ryujinx.Graphics.Vulkan/SamplerHolder.cs b/src/Ryujinx.Graphics.Vulkan/SamplerHolder.cs index f67daeecc..7f37ab139 100644 --- a/src/Ryujinx.Graphics.Vulkan/SamplerHolder.cs +++ b/src/Ryujinx.Graphics.Vulkan/SamplerHolder.cs @@ -68,7 +68,7 @@ namespace Ryujinx.Graphics.Vulkan samplerCreateInfo.BorderColor = BorderColor.FloatCustomExt; } - gd.Api.CreateSampler(device, samplerCreateInfo, null, out var sampler).ThrowOnError(); + gd.Api.CreateSampler(device, in samplerCreateInfo, null, out var sampler).ThrowOnError(); _sampler = new Auto(new DisposableSampler(gd.Api, device, sampler)); } diff --git a/src/Ryujinx.Graphics.Vulkan/Shader.cs b/src/Ryujinx.Graphics.Vulkan/Shader.cs index 06f3499db..1c8caffd9 100644 --- a/src/Ryujinx.Graphics.Vulkan/Shader.cs +++ b/src/Ryujinx.Graphics.Vulkan/Shader.cs @@ -64,7 +64,7 @@ namespace Ryujinx.Graphics.Vulkan PCode = (uint*)pCode, }; - api.CreateShaderModule(device, shaderModuleCreateInfo, null, out _module).ThrowOnError(); + api.CreateShaderModule(device, in shaderModuleCreateInfo, null, out _module).ThrowOnError(); } CompileStatus = ProgramLinkStatus.Success; diff --git a/src/Ryujinx.Graphics.Vulkan/TextureArray.cs b/src/Ryujinx.Graphics.Vulkan/TextureArray.cs index 31c408d64..99238b1f5 100644 --- a/src/Ryujinx.Graphics.Vulkan/TextureArray.cs +++ b/src/Ryujinx.Graphics.Vulkan/TextureArray.cs @@ -104,7 +104,7 @@ namespace Ryujinx.Graphics.Vulkan { _cachedCommandBufferIndex = -1; _storages = null; - SetDirty(_gd); + SetDirty(_gd, isImage: false); } public void QueueWriteToReadBarriers(CommandBufferScoped cbs, PipelineStageFlags stageFlags) diff --git a/src/Ryujinx.Graphics.Vulkan/TextureCopy.cs b/src/Ryujinx.Graphics.Vulkan/TextureCopy.cs index fdc0a248b..45cddd772 100644 --- a/src/Ryujinx.Graphics.Vulkan/TextureCopy.cs +++ b/src/Ryujinx.Graphics.Vulkan/TextureCopy.cs @@ -88,7 +88,7 @@ namespace Ryujinx.Graphics.Vulkan DstOffsets = dstOffsets, }; - api.CmdBlitImage(commandBuffer, srcImage, ImageLayout.General, dstImage, ImageLayout.General, 1, region, filter); + api.CmdBlitImage(commandBuffer, srcImage, ImageLayout.General, dstImage, ImageLayout.General, 1, in region, filter); copySrcLevel++; copyDstLevel++; @@ -320,13 +320,13 @@ namespace Ryujinx.Graphics.Vulkan { var region = new ImageResolve(srcSl, new Offset3D(0, 0, srcZ), dstSl, new Offset3D(0, 0, dstZ), extent); - api.CmdResolveImage(commandBuffer, srcImage, ImageLayout.General, dstImage, ImageLayout.General, 1, region); + api.CmdResolveImage(commandBuffer, srcImage, ImageLayout.General, dstImage, ImageLayout.General, 1, in region); } else { var region = new ImageCopy(srcSl, new Offset3D(0, 0, srcZ), dstSl, new Offset3D(0, 0, dstZ), extent); - api.CmdCopyImage(commandBuffer, srcImage, ImageLayout.General, dstImage, ImageLayout.General, 1, region); + api.CmdCopyImage(commandBuffer, srcImage, ImageLayout.General, dstImage, ImageLayout.General, 1, in region); } width = Math.Max(1, width >> 1); @@ -422,7 +422,7 @@ namespace Ryujinx.Graphics.Vulkan DependencyCount = 1, }; - gd.Api.CreateRenderPass2(device, renderPassCreateInfo, null, out var renderPass).ThrowOnError(); + gd.Api.CreateRenderPass2(device, in renderPassCreateInfo, null, out var renderPass).ThrowOnError(); using var rp = new Auto(new DisposableRenderPass(gd.Api, device, renderPass)); @@ -445,7 +445,7 @@ namespace Ryujinx.Graphics.Vulkan Layers = (uint)src.Layers, }; - gd.Api.CreateFramebuffer(device, framebufferCreateInfo, null, out var framebuffer).ThrowOnError(); + gd.Api.CreateFramebuffer(device, in framebufferCreateInfo, null, out var framebuffer).ThrowOnError(); using var fb = new Auto(new DisposableFramebuffer(gd.Api, device, framebuffer), null, srcView, dstView); var renderArea = new Rect2D(null, new Extent2D((uint)src.Info.Width, (uint)src.Info.Height)); @@ -465,7 +465,7 @@ namespace Ryujinx.Graphics.Vulkan // to resolve the depth-stencil texture. // TODO: Do speculative resolve and part of the same render pass as the draw to avoid // ending the current render pass? - gd.Api.CmdBeginRenderPass(cbs.CommandBuffer, renderPassBeginInfo, SubpassContents.Inline); + gd.Api.CmdBeginRenderPass(cbs.CommandBuffer, in renderPassBeginInfo, SubpassContents.Inline); gd.Api.CmdEndRenderPass(cbs.CommandBuffer); } } diff --git a/src/Ryujinx.Graphics.Vulkan/TextureStorage.cs b/src/Ryujinx.Graphics.Vulkan/TextureStorage.cs index f36db68de..10b36a3f9 100644 --- a/src/Ryujinx.Graphics.Vulkan/TextureStorage.cs +++ b/src/Ryujinx.Graphics.Vulkan/TextureStorage.cs @@ -4,6 +4,7 @@ using Silk.NET.Vulkan; using System; using System.Collections.Generic; using System.Numerics; +using System.Runtime.CompilerServices; using Format = Ryujinx.Graphics.GAL.Format; using VkBuffer = Silk.NET.Vulkan.Buffer; using VkFormat = Silk.NET.Vulkan.Format; @@ -12,6 +13,11 @@ namespace Ryujinx.Graphics.Vulkan { class TextureStorage : IDisposable { + private struct TextureSliceInfo + { + public int BindCount; + } + private const MemoryPropertyFlags DefaultImageMemoryFlags = MemoryPropertyFlags.DeviceLocalBit; @@ -43,6 +49,7 @@ namespace Ryujinx.Graphics.Vulkan private readonly Image _image; private readonly Auto _imageAuto; private readonly Auto _allocationAuto; + private readonly int _depthOrLayers; private Auto _foreignAllocationAuto; private Dictionary _aliasedStorages; @@ -55,6 +62,9 @@ namespace Ryujinx.Graphics.Vulkan private int _viewsCount; private readonly ulong _size; + private int _bindCount; + private readonly TextureSliceInfo[] _slices; + public VkFormat VkFormat { get; } public unsafe TextureStorage( @@ -73,6 +83,7 @@ namespace Ryujinx.Graphics.Vulkan var depth = (uint)(info.Target == Target.Texture3D ? info.Depth : 1); VkFormat = format; + _depthOrLayers = info.GetDepthOrLayers(); var type = info.Target.Convert(); @@ -80,7 +91,7 @@ namespace Ryujinx.Graphics.Vulkan var sampleCountFlags = ConvertToSampleCountFlags(gd.Capabilities.SupportedSampleCounts, (uint)info.Samples); - var usage = GetImageUsage(info.Format, info.Target, gd.Capabilities.SupportsShaderStorageImageMultisample); + var usage = GetImageUsage(info.Format, info.Target, gd.Capabilities); var flags = ImageCreateFlags.CreateMutableFormatBit | ImageCreateFlags.CreateExtendedUsageBit; @@ -114,7 +125,7 @@ namespace Ryujinx.Graphics.Vulkan Flags = flags, }; - gd.Api.CreateImage(device, imageCreateInfo, null, out _image).ThrowOnError(); + gd.Api.CreateImage(device, in imageCreateInfo, null, out _image).ThrowOnError(); if (foreignAllocation == null) { @@ -148,6 +159,8 @@ namespace Ryujinx.Graphics.Vulkan InitialTransition(ImageLayout.Preinitialized, ImageLayout.General); } + + _slices = new TextureSliceInfo[levels * _depthOrLayers]; } public TextureStorage CreateAliasedColorForDepthStorageUnsafe(Format format) @@ -284,7 +297,7 @@ namespace Ryujinx.Graphics.Vulkan 0, null, 1, - barrier); + in barrier); if (useTempCbs) { @@ -292,7 +305,7 @@ namespace Ryujinx.Graphics.Vulkan } } - public static ImageUsageFlags GetImageUsage(Format format, Target target, bool supportsMsStorage) + public static ImageUsageFlags GetImageUsage(Format format, Target target, in HardwareCapabilities capabilities) { var usage = DefaultUsageFlags; @@ -305,11 +318,19 @@ namespace Ryujinx.Graphics.Vulkan usage |= ImageUsageFlags.ColorAttachmentBit; } + bool supportsMsStorage = capabilities.SupportsShaderStorageImageMultisample; + if (format.IsImageCompatible() && (supportsMsStorage || !target.IsMultisample())) { usage |= ImageUsageFlags.StorageBit; } + if (capabilities.SupportsAttachmentFeedbackLoop && + (usage & (ImageUsageFlags.DepthStencilAttachmentBit | ImageUsageFlags.ColorAttachmentBit)) != 0) + { + usage |= ImageUsageFlags.AttachmentFeedbackLoopBitExt; + } + return usage; } @@ -401,11 +422,11 @@ namespace Ryujinx.Graphics.Vulkan if (to) { - _gd.Api.CmdCopyImageToBuffer(commandBuffer, image, ImageLayout.General, buffer, 1, region); + _gd.Api.CmdCopyImageToBuffer(commandBuffer, image, ImageLayout.General, buffer, 1, in region); } else { - _gd.Api.CmdCopyBufferToImage(commandBuffer, buffer, image, ImageLayout.General, 1, region); + _gd.Api.CmdCopyBufferToImage(commandBuffer, buffer, image, ImageLayout.General, 1, in region); } offset += mipSize; @@ -510,6 +531,55 @@ namespace Ryujinx.Graphics.Vulkan } } + public void AddBinding(TextureView view) + { + // Assumes a view only has a first level. + + int index = view.FirstLevel * _depthOrLayers + view.FirstLayer; + int layers = view.Layers; + + for (int i = 0; i < layers; i++) + { + ref TextureSliceInfo info = ref _slices[index++]; + + info.BindCount++; + } + + _bindCount++; + } + + public void ClearBindings() + { + if (_bindCount != 0) + { + Array.Clear(_slices, 0, _slices.Length); + + _bindCount = 0; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsBound(TextureView view) + { + if (_bindCount != 0) + { + int index = view.FirstLevel * _depthOrLayers + view.FirstLayer; + int layers = view.Layers; + + for (int i = 0; i < layers; i++) + { + ref TextureSliceInfo info = ref _slices[index++]; + + if (info.BindCount != 0) + { + return true; + } + } + } + + return false; + } + public void IncrementViewsCount() { _viewsCount++; diff --git a/src/Ryujinx.Graphics.Vulkan/TextureView.cs b/src/Ryujinx.Graphics.Vulkan/TextureView.cs index eb612da79..9b3f46662 100644 --- a/src/Ryujinx.Graphics.Vulkan/TextureView.cs +++ b/src/Ryujinx.Graphics.Vulkan/TextureView.cs @@ -23,6 +23,8 @@ namespace Ryujinx.Graphics.Vulkan private readonly Auto _imageView2dArray; private Dictionary _selfManagedViews; + private int _hazardUses; + private readonly TextureCreateInfo _info; private HashTableSlim _renderPasses; @@ -60,7 +62,7 @@ namespace Ryujinx.Graphics.Vulkan gd.Textures.Add(this); var format = _gd.FormatCapabilities.ConvertToVkFormat(info.Format); - var usage = TextureStorage.GetImageUsage(info.Format, info.Target, gd.Capabilities.SupportsShaderStorageImageMultisample); + var usage = TextureStorage.GetImageUsage(info.Format, info.Target, gd.Capabilities); var levels = (uint)info.Levels; var layers = (uint)info.GetLayers(); @@ -117,7 +119,7 @@ namespace Ryujinx.Graphics.Vulkan PNext = &imageViewUsage, }; - gd.Api.CreateImageView(device, imageCreateInfo, null, out var imageView).ThrowOnError(); + gd.Api.CreateImageView(device, in imageCreateInfo, null, out var imageView).ThrowOnError(); return new Auto(new DisposableImageView(gd.Api, device, imageView), null, storage.GetImage()); } @@ -492,7 +494,7 @@ namespace Ryujinx.Graphics.Vulkan dstStageMask, DependencyFlags.None, 1, - memoryBarrier, + in memoryBarrier, 0, null, 0, @@ -557,7 +559,7 @@ namespace Ryujinx.Graphics.Vulkan 0, null, 1, - memoryBarrier); + in memoryBarrier); } public TextureView GetView(Format format) @@ -667,8 +669,36 @@ namespace Ryujinx.Graphics.Vulkan if (PrepareOutputBuffer(cbs, hostSize, buffer, out VkBuffer copyToBuffer, out BufferHolder tempCopyHolder)) { + // No barrier necessary, as this is a temporary copy buffer. offset = 0; } + else + { + BufferHolder.InsertBufferBarrier( + _gd, + cbs.CommandBuffer, + copyToBuffer, + BufferHolder.DefaultAccessFlags, + AccessFlags.TransferWriteBit, + PipelineStageFlags.AllCommandsBit, + PipelineStageFlags.TransferBit, + offset, + outSize); + } + + InsertImageBarrier( + _gd.Api, + cbs.CommandBuffer, + image, + TextureStorage.DefaultAccessMask, + AccessFlags.TransferReadBit, + PipelineStageFlags.AllCommandsBit, + PipelineStageFlags.TransferBit, + Info.Format.ConvertAspectFlags(), + FirstLayer + layer, + FirstLevel + level, + 1, + 1); CopyFromOrToBuffer(cbs.CommandBuffer, copyToBuffer, image, hostSize, true, layer, level, 1, 1, singleSlice: true, offset, stride); @@ -677,6 +707,19 @@ namespace Ryujinx.Graphics.Vulkan CopyDataToOutputBuffer(cbs, tempCopyHolder, autoBuffer, hostSize, range.Offset); tempCopyHolder.Dispose(); } + else + { + BufferHolder.InsertBufferBarrier( + _gd, + cbs.CommandBuffer, + copyToBuffer, + AccessFlags.TransferWriteBit, + BufferHolder.DefaultAccessFlags, + PipelineStageFlags.TransferBit, + PipelineStageFlags.AllCommandsBit, + offset, + outSize); + } } private ReadOnlySpan GetData(CommandBufferPool cbp, PersistentFlushBuffer flushBuffer) @@ -908,11 +951,11 @@ namespace Ryujinx.Graphics.Vulkan if (to) { - _gd.Api.CmdCopyImageToBuffer(commandBuffer, image, ImageLayout.General, buffer, 1, region); + _gd.Api.CmdCopyImageToBuffer(commandBuffer, image, ImageLayout.General, buffer, 1, in region); } else { - _gd.Api.CmdCopyBufferToImage(commandBuffer, buffer, image, ImageLayout.General, 1, region); + _gd.Api.CmdCopyBufferToImage(commandBuffer, buffer, image, ImageLayout.General, 1, in region); } offset += mipSize; @@ -969,11 +1012,11 @@ namespace Ryujinx.Graphics.Vulkan if (to) { - _gd.Api.CmdCopyImageToBuffer(commandBuffer, image, ImageLayout.General, buffer, 1, region); + _gd.Api.CmdCopyImageToBuffer(commandBuffer, image, ImageLayout.General, buffer, 1, in region); } else { - _gd.Api.CmdCopyBufferToImage(commandBuffer, buffer, image, ImageLayout.General, 1, region); + _gd.Api.CmdCopyBufferToImage(commandBuffer, buffer, image, ImageLayout.General, 1, in region); } } @@ -993,6 +1036,34 @@ namespace Ryujinx.Graphics.Vulkan throw new NotImplementedException(); } + public void PrepareForUsage(CommandBufferScoped cbs, PipelineStageFlags flags, List feedbackLoopHazards) + { + Storage.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, flags); + + if (feedbackLoopHazards != null && Storage.IsBound(this)) + { + feedbackLoopHazards.Add(this); + _hazardUses++; + } + } + + public void ClearUsage(List feedbackLoopHazards) + { + if (_hazardUses != 0 && feedbackLoopHazards != null) + { + feedbackLoopHazards.Remove(this); + _hazardUses--; + } + } + + public void DecrementHazardUses() + { + if (_hazardUses != 0) + { + _hazardUses--; + } + } + public (RenderPassHolder rpHolder, Auto framebuffer) GetPassAndFramebuffer( VulkanRenderer gd, Device device, diff --git a/src/Ryujinx.Graphics.Vulkan/Vendor.cs b/src/Ryujinx.Graphics.Vulkan/Vendor.cs index 802771ede..55ae0cd81 100644 --- a/src/Ryujinx.Graphics.Vulkan/Vendor.cs +++ b/src/Ryujinx.Graphics.Vulkan/Vendor.cs @@ -90,11 +90,9 @@ namespace Ryujinx.Graphics.Vulkan DriverId.SamsungProprietary => "Samsung", DriverId.MesaVenus => "Venus", DriverId.MesaDozen => "Dozen", - - // TODO: Use real enum when we have an up to date Silk.NET. - (DriverId)24 => "NVK", - (DriverId)25 => "Imagination (Open)", - (DriverId)26 => "Honeykrisp", + DriverId.MesaNvk => "NVK", + DriverId.ImaginationOpenSourceMesa => "Imagination (Open)", + DriverId.MesaAgxv => "Honeykrisp", _ => id.ToString(), }; } diff --git a/src/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs b/src/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs index 5a9844cb9..2c327fdb7 100644 --- a/src/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs +++ b/src/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs @@ -44,6 +44,8 @@ namespace Ryujinx.Graphics.Vulkan "VK_EXT_4444_formats", "VK_KHR_8bit_storage", "VK_KHR_maintenance2", + "VK_EXT_attachment_feedback_loop_layout", + "VK_EXT_attachment_feedback_loop_dynamic_state", }; private static readonly string[] _requiredExtensions = { @@ -357,6 +359,28 @@ namespace Ryujinx.Graphics.Vulkan features2.PNext = &supportedFeaturesDepthClipControl; } + PhysicalDeviceAttachmentFeedbackLoopLayoutFeaturesEXT supportedFeaturesAttachmentFeedbackLoopLayout = new() + { + SType = StructureType.PhysicalDeviceAttachmentFeedbackLoopLayoutFeaturesExt, + PNext = features2.PNext, + }; + + if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_attachment_feedback_loop_layout")) + { + features2.PNext = &supportedFeaturesAttachmentFeedbackLoopLayout; + } + + PhysicalDeviceAttachmentFeedbackLoopDynamicStateFeaturesEXT supportedFeaturesDynamicAttachmentFeedbackLoopLayout = new() + { + SType = StructureType.PhysicalDeviceAttachmentFeedbackLoopDynamicStateFeaturesExt, + PNext = features2.PNext, + }; + + if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_attachment_feedback_loop_dynamic_state")) + { + features2.PNext = &supportedFeaturesDynamicAttachmentFeedbackLoopLayout; + } + PhysicalDeviceVulkan12Features supportedPhysicalDeviceVulkan12Features = new() { SType = StructureType.PhysicalDeviceVulkan12Features, @@ -531,6 +555,36 @@ namespace Ryujinx.Graphics.Vulkan pExtendedFeatures = &featuresDepthClipControl; } + PhysicalDeviceAttachmentFeedbackLoopLayoutFeaturesEXT featuresAttachmentFeedbackLoopLayout; + + if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_attachment_feedback_loop_layout") && + supportedFeaturesAttachmentFeedbackLoopLayout.AttachmentFeedbackLoopLayout) + { + featuresAttachmentFeedbackLoopLayout = new() + { + SType = StructureType.PhysicalDeviceAttachmentFeedbackLoopLayoutFeaturesExt, + PNext = pExtendedFeatures, + AttachmentFeedbackLoopLayout = true, + }; + + pExtendedFeatures = &featuresAttachmentFeedbackLoopLayout; + } + + PhysicalDeviceAttachmentFeedbackLoopDynamicStateFeaturesEXT featuresDynamicAttachmentFeedbackLoopLayout; + + if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_attachment_feedback_loop_dynamic_state") && + supportedFeaturesDynamicAttachmentFeedbackLoopLayout.AttachmentFeedbackLoopDynamicState) + { + featuresDynamicAttachmentFeedbackLoopLayout = new() + { + SType = StructureType.PhysicalDeviceAttachmentFeedbackLoopDynamicStateFeaturesExt, + PNext = pExtendedFeatures, + AttachmentFeedbackLoopDynamicState = true, + }; + + pExtendedFeatures = &featuresDynamicAttachmentFeedbackLoopLayout; + } + var enabledExtensions = _requiredExtensions.Union(_desirableExtensions.Intersect(physicalDevice.DeviceExtensions)).ToArray(); IntPtr* ppEnabledExtensions = stackalloc IntPtr[enabledExtensions.Length]; diff --git a/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs b/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs index c9ce678b7..33e41ab48 100644 --- a/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs +++ b/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs @@ -38,6 +38,7 @@ namespace Ryujinx.Graphics.Vulkan internal KhrPushDescriptor PushDescriptorApi { get; private set; } internal ExtTransformFeedback TransformFeedbackApi { get; private set; } internal KhrDrawIndirectCount DrawIndirectCountApi { get; private set; } + internal ExtAttachmentFeedbackLoopDynamicState DynamicFeedbackLoopApi { get; private set; } internal uint QueueFamilyIndex { get; private set; } internal Queue Queue { get; private set; } @@ -149,6 +150,11 @@ namespace Ryujinx.Graphics.Vulkan DrawIndirectCountApi = drawIndirectCountApi; } + if (Api.TryGetDeviceExtension(_instance.Instance, _device, out ExtAttachmentFeedbackLoopDynamicState dynamicFeedbackLoopApi)) + { + DynamicFeedbackLoopApi = dynamicFeedbackLoopApi; + } + if (maxQueueCount >= 2) { Api.GetDeviceQueue(_device, queueFamilyIndex, 1, out var backgroundQueue); @@ -243,6 +249,16 @@ namespace Ryujinx.Graphics.Vulkan SType = StructureType.PhysicalDeviceDepthClipControlFeaturesExt, }; + PhysicalDeviceAttachmentFeedbackLoopLayoutFeaturesEXT featuresAttachmentFeedbackLoop = new() + { + SType = StructureType.PhysicalDeviceAttachmentFeedbackLoopLayoutFeaturesExt, + }; + + PhysicalDeviceAttachmentFeedbackLoopDynamicStateFeaturesEXT featuresDynamicAttachmentFeedbackLoop = new() + { + SType = StructureType.PhysicalDeviceAttachmentFeedbackLoopDynamicStateFeaturesExt, + }; + PhysicalDevicePortabilitySubsetFeaturesKHR featuresPortabilitySubset = new() { SType = StructureType.PhysicalDevicePortabilitySubsetFeaturesKhr, @@ -279,6 +295,22 @@ namespace Ryujinx.Graphics.Vulkan features2.PNext = &featuresDepthClipControl; } + bool supportsAttachmentFeedbackLoop = _physicalDevice.IsDeviceExtensionPresent("VK_EXT_attachment_feedback_loop_layout"); + + if (supportsAttachmentFeedbackLoop) + { + featuresAttachmentFeedbackLoop.PNext = features2.PNext; + features2.PNext = &featuresAttachmentFeedbackLoop; + } + + bool supportsDynamicAttachmentFeedbackLoop = _physicalDevice.IsDeviceExtensionPresent("VK_EXT_attachment_feedback_loop_dynamic_state"); + + if (supportsDynamicAttachmentFeedbackLoop) + { + featuresDynamicAttachmentFeedbackLoop.PNext = features2.PNext; + features2.PNext = &featuresDynamicAttachmentFeedbackLoop; + } + bool usePortability = _physicalDevice.IsDeviceExtensionPresent("VK_KHR_portability_subset"); if (usePortability) @@ -401,6 +433,8 @@ namespace Ryujinx.Graphics.Vulkan _physicalDevice.IsDeviceExtensionPresent("VK_NV_viewport_array2"), _physicalDevice.IsDeviceExtensionPresent(ExtExternalMemoryHost.ExtensionName), supportsDepthClipControl && featuresDepthClipControl.DepthClipControl, + supportsAttachmentFeedbackLoop && featuresAttachmentFeedbackLoop.AttachmentFeedbackLoopLayout, + supportsDynamicAttachmentFeedbackLoop && featuresDynamicAttachmentFeedbackLoop.AttachmentFeedbackLoopDynamicState, propertiesSubgroup.SubgroupSize, supportedSampleCounts, portabilityFlags, diff --git a/src/Ryujinx.Graphics.Vulkan/Window.cs b/src/Ryujinx.Graphics.Vulkan/Window.cs index efb0b31f9..d67362be3 100644 --- a/src/Ryujinx.Graphics.Vulkan/Window.cs +++ b/src/Ryujinx.Graphics.Vulkan/Window.cs @@ -160,7 +160,7 @@ namespace Ryujinx.Graphics.Vulkan SwizzleComponent.Blue, SwizzleComponent.Alpha); - _gd.SwapchainApi.CreateSwapchain(_device, swapchainCreateInfo, null, out _swapchain).ThrowOnError(); + _gd.SwapchainApi.CreateSwapchain(_device, in swapchainCreateInfo, null, out _swapchain).ThrowOnError(); _gd.SwapchainApi.GetSwapchainImages(_device, _swapchain, &imageCount, null); @@ -187,14 +187,14 @@ namespace Ryujinx.Graphics.Vulkan for (int i = 0; i < _imageAvailableSemaphores.Length; i++) { - _gd.Api.CreateSemaphore(_device, semaphoreCreateInfo, null, out _imageAvailableSemaphores[i]).ThrowOnError(); + _gd.Api.CreateSemaphore(_device, in semaphoreCreateInfo, null, out _imageAvailableSemaphores[i]).ThrowOnError(); } _renderFinishedSemaphores = new Semaphore[imageCount]; for (int i = 0; i < _renderFinishedSemaphores.Length; i++) { - _gd.Api.CreateSemaphore(_device, semaphoreCreateInfo, null, out _renderFinishedSemaphores[i]).ThrowOnError(); + _gd.Api.CreateSemaphore(_device, in semaphoreCreateInfo, null, out _renderFinishedSemaphores[i]).ThrowOnError(); } } @@ -220,7 +220,7 @@ namespace Ryujinx.Graphics.Vulkan SubresourceRange = subresourceRange, }; - _gd.Api.CreateImageView(_device, imageCreateInfo, null, out var imageView).ThrowOnError(); + _gd.Api.CreateImageView(_device, in imageCreateInfo, null, out var imageView).ThrowOnError(); return new TextureView(_gd, _device, new DisposableImageView(_gd.Api, _device, imageView), info, format); } @@ -479,7 +479,7 @@ namespace Ryujinx.Graphics.Vulkan lock (_gd.QueueLock) { - _gd.SwapchainApi.QueuePresent(_gd.Queue, presentInfo); + _gd.SwapchainApi.QueuePresent(_gd.Queue, in presentInfo); } } @@ -611,7 +611,7 @@ namespace Ryujinx.Graphics.Vulkan 0, null, 1, - barrier); + in barrier); } private void CaptureFrame(TextureView texture, int x, int y, int width, int height, bool isBgra, bool flipX, bool flipY) diff --git a/src/Ryujinx.Gtk3/Program.cs b/src/Ryujinx.Gtk3/Program.cs index 0fb712885..2d350374b 100644 --- a/src/Ryujinx.Gtk3/Program.cs +++ b/src/Ryujinx.Gtk3/Program.cs @@ -4,6 +4,8 @@ using Ryujinx.Common.Configuration; using Ryujinx.Common.GraphicsDriver; using Ryujinx.Common.Logging; using Ryujinx.Common.SystemInterop; +using Ryujinx.Common.Utilities; +using Ryujinx.Graphics.Vulkan.MoltenVK; using Ryujinx.Modules; using Ryujinx.SDL2.Common; using Ryujinx.UI; @@ -13,7 +15,6 @@ using Ryujinx.UI.Common.Configuration; using Ryujinx.UI.Common.Helper; using Ryujinx.UI.Common.SystemInfo; using Ryujinx.UI.Widgets; -using SixLabors.ImageSharp.Formats.Jpeg; using System; using System.Collections.Generic; using System.Diagnostics; @@ -41,9 +42,6 @@ namespace Ryujinx [LibraryImport("user32.dll", SetLastError = true)] public static partial int MessageBoxA(IntPtr hWnd, [MarshalAs(UnmanagedType.LPStr)] string text, [MarshalAs(UnmanagedType.LPStr)] string caption, uint type); - [LibraryImport("libc", SetLastError = true)] - private static partial int setenv([MarshalAs(UnmanagedType.LPStr)] string name, [MarshalAs(UnmanagedType.LPStr)] string value, int overwrite); - private const uint MbIconWarning = 0x30; static Program() @@ -105,12 +103,13 @@ namespace Ryujinx throw new NotSupportedException("Failed to initialize multi-threading support."); } - Environment.SetEnvironmentVariable("GDK_BACKEND", "x11"); - setenv("GDK_BACKEND", "x11", 1); + OsUtils.SetEnvironmentVariableNoCaching("GDK_BACKEND", "x11"); } if (OperatingSystem.IsMacOS()) { + MVKInitialization.InitializeResolver(); + string baseDirectory = Path.GetDirectoryName(AppDomain.CurrentDomain.BaseDirectory); string resourcesDataDir; @@ -123,19 +122,13 @@ namespace Ryujinx resourcesDataDir = baseDirectory; } - static void SetEnvironmentVariableNoCaching(string key, string value) - { - int res = setenv(key, value, 1); - Debug.Assert(res != -1); - } - // On macOS, GTK3 needs XDG_DATA_DIRS to be set, otherwise it will try searching for "gschemas.compiled" in system directories. - SetEnvironmentVariableNoCaching("XDG_DATA_DIRS", Path.Combine(resourcesDataDir, "share")); + OsUtils.SetEnvironmentVariableNoCaching("XDG_DATA_DIRS", Path.Combine(resourcesDataDir, "share")); // On macOS, GTK3 needs GDK_PIXBUF_MODULE_FILE to be set, otherwise it will try searching for "loaders.cache" in system directories. - SetEnvironmentVariableNoCaching("GDK_PIXBUF_MODULE_FILE", Path.Combine(resourcesDataDir, "lib", "gdk-pixbuf-2.0", "2.10.0", "loaders.cache")); + OsUtils.SetEnvironmentVariableNoCaching("GDK_PIXBUF_MODULE_FILE", Path.Combine(resourcesDataDir, "lib", "gdk-pixbuf-2.0", "2.10.0", "loaders.cache")); - SetEnvironmentVariableNoCaching("GTK_IM_MODULE_FILE", Path.Combine(resourcesDataDir, "lib", "gtk-3.0", "3.0.0", "immodules.cache")); + OsUtils.SetEnvironmentVariableNoCaching("GTK_IM_MODULE_FILE", Path.Combine(resourcesDataDir, "lib", "gtk-3.0", "3.0.0", "immodules.cache")); } string systemPath = Environment.GetEnvironmentVariable("Path", EnvironmentVariableTarget.Machine); @@ -162,12 +155,6 @@ namespace Ryujinx }); }; - // Sets ImageSharp Jpeg Encoder Quality. - SixLabors.ImageSharp.Configuration.Default.ImageFormatsManager.SetEncoder(JpegFormat.Instance, new JpegEncoder() - { - Quality = 100, - }); - string localConfigurationPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ReleaseInformation.ConfigName); string appDataConfigurationPath = Path.Combine(AppDataManager.BaseDirPath, ReleaseInformation.ConfigName); @@ -237,9 +224,9 @@ namespace Ryujinx // Logging system information. PrintSystemInfo(); - // Enable OGL multithreading on the driver, when available. + // Enable OGL multithreading on the driver, and some other flags. BackendThreading threadingMode = ConfigurationState.Instance.Graphics.BackendThreading; - DriverUtilities.ToggleOGLThreading(threadingMode == BackendThreading.Off); + DriverUtilities.InitDriverConfig(threadingMode == BackendThreading.Off); // Initialize Gtk. Application.Init(); @@ -256,6 +243,12 @@ namespace Ryujinx MainWindow mainWindow = new(); mainWindow.Show(); + // Load the game table if no application was requested by the command line + if (CommandLineState.LaunchPathArg == null) + { + mainWindow.UpdateGameTable(); + } + if (OperatingSystem.IsLinux()) { int currentVmMaxMapCount = LinuxHelper.VmMaxMapCount; diff --git a/src/Ryujinx.Gtk3/Ryujinx.Gtk3.csproj b/src/Ryujinx.Gtk3/Ryujinx.Gtk3.csproj index b4453f9d7..722d6080b 100644 --- a/src/Ryujinx.Gtk3/Ryujinx.Gtk3.csproj +++ b/src/Ryujinx.Gtk3/Ryujinx.Gtk3.csproj @@ -30,7 +30,6 @@ - diff --git a/src/Ryujinx.Gtk3/UI/MainWindow.cs b/src/Ryujinx.Gtk3/UI/MainWindow.cs index 7f9eceb38..66c0afae0 100644 --- a/src/Ryujinx.Gtk3/UI/MainWindow.cs +++ b/src/Ryujinx.Gtk3/UI/MainWindow.cs @@ -187,7 +187,10 @@ namespace Ryujinx.UI : IntegrityCheckLevel.None; // Instantiate GUI objects. - ApplicationLibrary = new ApplicationLibrary(_virtualFileSystem, checkLevel); + ApplicationLibrary = new ApplicationLibrary(_virtualFileSystem, checkLevel) + { + DesiredLanguage = ConfigurationState.Instance.System.Language, + }; _uiHandler = new GtkHostUIHandler(this); _deviceExitStatus = new AutoResetEvent(false); @@ -325,7 +328,6 @@ namespace Ryujinx.UI _hideUI.Label = _hideUI.Label.Replace("SHOWUIKEY", ConfigurationState.Instance.Hid.Hotkeys.Value.ShowUI.ToString()); UpdateColumns(); - UpdateGameTable(); ConfigurationState.Instance.UI.GameDirs.Event += (sender, args) => { @@ -738,7 +740,8 @@ namespace Ryujinx.UI Thread applicationLibraryThread = new(() => { - ApplicationLibrary.LoadApplications(ConfigurationState.Instance.UI.GameDirs, ConfigurationState.Instance.System.Language); + ApplicationLibrary.DesiredLanguage = ConfigurationState.Instance.System.Language; + ApplicationLibrary.LoadApplications(ConfigurationState.Instance.UI.GameDirs); _updatingGameTable = false; }) diff --git a/src/Ryujinx.Gtk3/UI/RendererWidgetBase.cs b/src/Ryujinx.Gtk3/UI/RendererWidgetBase.cs index 0e636792d..12139e87d 100644 --- a/src/Ryujinx.Gtk3/UI/RendererWidgetBase.cs +++ b/src/Ryujinx.Gtk3/UI/RendererWidgetBase.cs @@ -13,16 +13,13 @@ using Ryujinx.Input.HLE; using Ryujinx.UI.Common.Configuration; using Ryujinx.UI.Common.Helper; using Ryujinx.UI.Widgets; -using SixLabors.ImageSharp; -using SixLabors.ImageSharp.Formats.Png; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; +using SkiaSharp; using System; using System.Diagnostics; using System.IO; +using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; -using Image = SixLabors.ImageSharp.Image; using Key = Ryujinx.Input.Key; using ScalingFilter = Ryujinx.Graphics.GAL.ScalingFilter; using Switch = Ryujinx.HLE.Switch; @@ -404,23 +401,31 @@ namespace Ryujinx.UI return; } - Image image = e.IsBgra ? Image.LoadPixelData(e.Data, e.Width, e.Height) - : Image.LoadPixelData(e.Data, e.Width, e.Height); + var colorType = e.IsBgra ? SKColorType.Bgra8888 : SKColorType.Rgba8888; + using var image = new SKBitmap(new SKImageInfo(e.Width, e.Height, colorType, SKAlphaType.Premul)); - if (e.FlipX) + Marshal.Copy(e.Data, 0, image.GetPixels(), e.Data.Length); + using var surface = SKSurface.Create(image.Info); + var canvas = surface.Canvas; + + if (e.FlipX || e.FlipY) { - image.Mutate(x => x.Flip(FlipMode.Horizontal)); + canvas.Clear(SKColors.Transparent); + + float scaleX = e.FlipX ? -1 : 1; + float scaleY = e.FlipY ? -1 : 1; + + var matrix = SKMatrix.CreateScale(scaleX, scaleY, image.Width / 2f, image.Height / 2f); + + canvas.SetMatrix(matrix); } + canvas.DrawBitmap(image, new SKPoint()); - if (e.FlipY) - { - image.Mutate(x => x.Flip(FlipMode.Vertical)); - } - - image.SaveAsPng(path, new PngEncoder() - { - ColorType = PngColorType.Rgb, - }); + surface.Flush(); + using var snapshot = surface.Snapshot(); + using var encoded = snapshot.Encode(SKEncodedImageFormat.Png, 80); + using var file = File.OpenWrite(path); + encoded.SaveTo(file); image.Dispose(); diff --git a/src/Ryujinx.Gtk3/UI/Widgets/GameTableContextMenu.cs b/src/Ryujinx.Gtk3/UI/Widgets/GameTableContextMenu.cs index e37906d5b..a3e3d4c8c 100644 --- a/src/Ryujinx.Gtk3/UI/Widgets/GameTableContextMenu.cs +++ b/src/Ryujinx.Gtk3/UI/Widgets/GameTableContextMenu.cs @@ -473,7 +473,7 @@ namespace Ryujinx.UI.Widgets private void ManageDlc_Clicked(object sender, EventArgs args) { - new DlcWindow(_virtualFileSystem, _applicationData.IdString, _applicationData).Show(); + new DlcWindow(_virtualFileSystem, _applicationData.IdBaseString, _applicationData).Show(); } private void ManageCheats_Clicked(object sender, EventArgs args) diff --git a/src/Ryujinx.Gtk3/UI/Windows/AvatarWindow.cs b/src/Ryujinx.Gtk3/UI/Windows/AvatarWindow.cs index d9ecd47b7..fcd960df0 100644 --- a/src/Ryujinx.Gtk3/UI/Windows/AvatarWindow.cs +++ b/src/Ryujinx.Gtk3/UI/Windows/AvatarWindow.cs @@ -9,16 +9,13 @@ using LibHac.Tools.FsSystem.NcaUtils; using Ryujinx.Common.Memory; using Ryujinx.HLE.FileSystem; using Ryujinx.UI.Common.Configuration; -using SixLabors.ImageSharp; -using SixLabors.ImageSharp.Formats.Png; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; +using SkiaSharp; using System; using System.Buffers.Binary; using System.Collections.Generic; using System.IO; using System.Reflection; -using Image = SixLabors.ImageSharp.Image; +using System.Runtime.InteropServices; namespace Ryujinx.UI.Windows { @@ -144,9 +141,11 @@ namespace Ryujinx.UI.Windows stream.Position = 0; - Image avatarImage = Image.LoadPixelData(DecompressYaz0(stream), 256, 256); + using var avatarImage = new SKBitmap(new SKImageInfo(256, 256, SKColorType.Rgba8888)); + var data = DecompressYaz0(stream); + Marshal.Copy(data, 0, avatarImage.GetPixels(), data.Length); - avatarImage.SaveAsPng(streamPng); + avatarImage.Encode(streamPng, SKEncodedImageFormat.Png, 80); _avatarDict.Add(item.FullPath, streamPng.ToArray()); } @@ -170,15 +169,23 @@ namespace Ryujinx.UI.Windows { using MemoryStream streamJpg = MemoryStreamManager.Shared.GetStream(); - Image avatarImage = Image.Load(data, new PngDecoder()); + using var avatarImage = SKBitmap.Decode(data); + using var surface = SKSurface.Create(avatarImage.Info); - avatarImage.Mutate(x => x.BackgroundColor(new Rgba32( + var background = new SKColor( (byte)(_backgroundColor.Red * 255), (byte)(_backgroundColor.Green * 255), (byte)(_backgroundColor.Blue * 255), (byte)(_backgroundColor.Alpha * 255) - ))); - avatarImage.SaveAsJpeg(streamJpg); + ); + var canvas = surface.Canvas; + canvas.Clear(background); + canvas.DrawBitmap(avatarImage, new SKPoint()); + + surface.Flush(); + using var snapshot = surface.Snapshot(); + using var encoded = snapshot.Encode(SKEncodedImageFormat.Jpeg, 80); + encoded.SaveTo(streamJpg); return streamJpg.ToArray(); } diff --git a/src/Ryujinx.Gtk3/UI/Windows/DlcWindow.cs b/src/Ryujinx.Gtk3/UI/Windows/DlcWindow.cs index b69cc0032..fb3189e1c 100644 --- a/src/Ryujinx.Gtk3/UI/Windows/DlcWindow.cs +++ b/src/Ryujinx.Gtk3/UI/Windows/DlcWindow.cs @@ -24,7 +24,7 @@ namespace Ryujinx.UI.Windows public class DlcWindow : Window { private readonly VirtualFileSystem _virtualFileSystem; - private readonly string _applicationId; + private readonly string _applicationIdBase; private readonly string _dlcJsonPath; private readonly List _dlcContainerList; @@ -36,16 +36,16 @@ namespace Ryujinx.UI.Windows [GUI] TreeSelection _dlcTreeSelection; #pragma warning restore CS0649, IDE0044 - public DlcWindow(VirtualFileSystem virtualFileSystem, string titleId, ApplicationData applicationData) : this(new Builder("Ryujinx.Gtk3.UI.Windows.DlcWindow.glade"), virtualFileSystem, titleId, applicationData) { } + public DlcWindow(VirtualFileSystem virtualFileSystem, string applicationIdBase, ApplicationData applicationData) : this(new Builder("Ryujinx.Gtk3.UI.Windows.DlcWindow.glade"), virtualFileSystem, applicationIdBase, applicationData) { } - private DlcWindow(Builder builder, VirtualFileSystem virtualFileSystem, string applicationId, ApplicationData applicationData) : base(builder.GetRawOwnedObject("_dlcWindow")) + private DlcWindow(Builder builder, VirtualFileSystem virtualFileSystem, string applicationIdBase, ApplicationData applicationData) : base(builder.GetRawOwnedObject("_dlcWindow")) { builder.Autoconnect(this); - _applicationId = applicationId; + _applicationIdBase = applicationIdBase; _virtualFileSystem = virtualFileSystem; - _dlcJsonPath = System.IO.Path.Combine(AppDataManager.GamesDirPath, _applicationId, "dlc.json"); - _baseTitleInfoLabel.Text = $"DLC Available for {applicationData.Name} [{applicationId.ToUpper()}]"; + _dlcJsonPath = System.IO.Path.Combine(AppDataManager.GamesDirPath, _applicationIdBase, "dlc.json"); + _baseTitleInfoLabel.Text = $"DLC Available for {applicationData.Name} [{applicationIdBase.ToUpper()}]"; try { @@ -163,7 +163,7 @@ namespace Ryujinx.UI.Windows if (nca.Header.ContentType == NcaContentType.PublicData) { - if (nca.GetProgramIdBase() != (ulong.Parse(_applicationId, NumberStyles.HexNumber) & ~0x1FFFUL)) + if (nca.GetProgramIdBase() != ulong.Parse(_applicationIdBase, NumberStyles.HexNumber)) { continue; } diff --git a/src/Ryujinx.Gtk3/UI/Windows/TitleUpdateWindow.cs b/src/Ryujinx.Gtk3/UI/Windows/TitleUpdateWindow.cs index 3ac972ead..a08f59597 100644 --- a/src/Ryujinx.Gtk3/UI/Windows/TitleUpdateWindow.cs +++ b/src/Ryujinx.Gtk3/UI/Windows/TitleUpdateWindow.cs @@ -51,7 +51,7 @@ namespace Ryujinx.UI.Windows _applicationData = applicationData; _virtualFileSystem = virtualFileSystem; - _updateJsonPath = System.IO.Path.Combine(AppDataManager.GamesDirPath, applicationData.IdString, "updates.json"); + _updateJsonPath = System.IO.Path.Combine(AppDataManager.GamesDirPath, applicationData.IdBaseString, "updates.json"); _radioButtonToPathDictionary = new Dictionary(); try @@ -67,7 +67,7 @@ namespace Ryujinx.UI.Windows }; } - _baseTitleInfoLabel.Text = $"Updates Available for {applicationData.Name} [{applicationData.IdString}]"; + _baseTitleInfoLabel.Text = $"Updates Available for {applicationData.Name} [{applicationData.IdBaseString}]"; // Try to get updates from PFS first AddUpdate(_applicationData.Path, true); diff --git a/src/Ryujinx.Gtk3/UI/Windows/UserProfilesManagerWindow.cs b/src/Ryujinx.Gtk3/UI/Windows/UserProfilesManagerWindow.cs index d1e5fa9fc..77afc5d1f 100644 --- a/src/Ryujinx.Gtk3/UI/Windows/UserProfilesManagerWindow.cs +++ b/src/Ryujinx.Gtk3/UI/Windows/UserProfilesManagerWindow.cs @@ -4,15 +4,13 @@ using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.HOS.Services.Account.Acc; using Ryujinx.UI.Common.Configuration; using Ryujinx.UI.Widgets; -using SixLabors.ImageSharp; -using SixLabors.ImageSharp.Processing; +using SkiaSharp; using System; using System.Collections.Generic; using System.IO; using System.Reflection; using System.Threading; using System.Threading.Tasks; -using Image = SixLabors.ImageSharp.Image; namespace Ryujinx.UI.Windows { @@ -177,13 +175,13 @@ namespace Ryujinx.UI.Windows private void ProcessProfileImage(byte[] buffer) { - using Image image = Image.Load(buffer); + using var image = SKBitmap.Decode(buffer); - image.Mutate(x => x.Resize(256, 256)); + image.Resize(new SKImageInfo(256, 256), SKFilterQuality.High); using MemoryStream streamJpg = MemoryStreamManager.Shared.GetStream(); - image.SaveAsJpeg(streamJpg); + image.Encode(streamJpg, SKEncodedImageFormat.Jpeg, 80); _bufferImageProfile = streamJpg.ToArray(); } diff --git a/src/Ryujinx.HLE.Generators/CodeGenerator.cs b/src/Ryujinx.HLE.Generators/CodeGenerator.cs new file mode 100644 index 000000000..7e4848ad3 --- /dev/null +++ b/src/Ryujinx.HLE.Generators/CodeGenerator.cs @@ -0,0 +1,63 @@ +using System.Text; + +namespace Ryujinx.HLE.Generators +{ + class CodeGenerator + { + private const int IndentLength = 4; + + private readonly StringBuilder _sb; + private int _currentIndentCount; + + public CodeGenerator() + { + _sb = new StringBuilder(); + } + + public void EnterScope(string header = null) + { + if (header != null) + { + AppendLine(header); + } + + AppendLine("{"); + IncreaseIndentation(); + } + + public void LeaveScope(string suffix = "") + { + DecreaseIndentation(); + AppendLine($"}}{suffix}"); + } + + public void IncreaseIndentation() + { + _currentIndentCount++; + } + + public void DecreaseIndentation() + { + if (_currentIndentCount - 1 >= 0) + { + _currentIndentCount--; + } + } + + public void AppendLine() + { + _sb.AppendLine(); + } + + public void AppendLine(string text) + { + _sb.Append(' ', IndentLength * _currentIndentCount); + _sb.AppendLine(text); + } + + public override string ToString() + { + return _sb.ToString(); + } + } +} diff --git a/src/Ryujinx.HLE.Generators/IpcServiceGenerator.cs b/src/Ryujinx.HLE.Generators/IpcServiceGenerator.cs new file mode 100644 index 000000000..19fdbe197 --- /dev/null +++ b/src/Ryujinx.HLE.Generators/IpcServiceGenerator.cs @@ -0,0 +1,76 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using System.Linq; + +namespace Ryujinx.HLE.Generators +{ + [Generator] + public class IpcServiceGenerator : ISourceGenerator + { + public void Execute(GeneratorExecutionContext context) + { + var syntaxReceiver = (ServiceSyntaxReceiver)context.SyntaxReceiver; + CodeGenerator generator = new CodeGenerator(); + + generator.AppendLine("using System;"); + generator.EnterScope($"namespace Ryujinx.HLE.HOS.Services.Sm"); + generator.EnterScope($"partial class IUserInterface"); + + generator.EnterScope($"public IpcService? GetServiceInstance(Type type, ServiceCtx context, object? parameter = null)"); + foreach (var className in syntaxReceiver.Types) + { + if (className.Modifiers.Any(SyntaxKind.AbstractKeyword) || className.Modifiers.Any(SyntaxKind.PrivateKeyword) || !className.AttributeLists.Any(x => x.Attributes.Any(y => y.ToString().StartsWith("Service")))) + continue; + var name = GetFullName(className, context).Replace("global::", ""); + if (!name.StartsWith("Ryujinx.HLE.HOS.Services")) + continue; + var constructors = className.ChildNodes().Where(x => x.IsKind(SyntaxKind.ConstructorDeclaration)).Select(y => y as ConstructorDeclarationSyntax); + + if (!constructors.Any(x => x.ParameterList.Parameters.Count >= 1)) + continue; + + if (constructors.Where(x => x.ParameterList.Parameters.Count >= 1).FirstOrDefault().ParameterList.Parameters[0].Type.ToString() == "ServiceCtx") + { + generator.EnterScope($"if (type == typeof({GetFullName(className, context)}))"); + if (constructors.Any(x => x.ParameterList.Parameters.Count == 2)) + { + var type = constructors.Where(x => x.ParameterList.Parameters.Count == 2).FirstOrDefault().ParameterList.Parameters[1].Type; + var model = context.Compilation.GetSemanticModel(type.SyntaxTree); + var typeSymbol = model.GetSymbolInfo(type).Symbol as INamedTypeSymbol; + var fullName = typeSymbol.ToString(); + generator.EnterScope("if (parameter != null)"); + generator.AppendLine($"return new {GetFullName(className, context)}(context, ({fullName})parameter);"); + generator.LeaveScope(); + } + + if (constructors.Any(x => x.ParameterList.Parameters.Count == 1)) + { + generator.AppendLine($"return new {GetFullName(className, context)}(context);"); + } + + generator.LeaveScope(); + } + } + + generator.AppendLine("return null;"); + generator.LeaveScope(); + + generator.LeaveScope(); + generator.LeaveScope(); + context.AddSource($"IUserInterface.g.cs", generator.ToString()); + } + + private string GetFullName(ClassDeclarationSyntax syntaxNode, GeneratorExecutionContext context) + { + var typeSymbol = context.Compilation.GetSemanticModel(syntaxNode.SyntaxTree).GetDeclaredSymbol(syntaxNode); + + return typeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + } + + public void Initialize(GeneratorInitializationContext context) + { + context.RegisterForSyntaxNotifications(() => new ServiceSyntaxReceiver()); + } + } +} diff --git a/src/Ryujinx.HLE.Generators/Ryujinx.HLE.Generators.csproj b/src/Ryujinx.HLE.Generators/Ryujinx.HLE.Generators.csproj new file mode 100644 index 000000000..eeab9c0e9 --- /dev/null +++ b/src/Ryujinx.HLE.Generators/Ryujinx.HLE.Generators.csproj @@ -0,0 +1,19 @@ + + + + netstandard2.0 + true + true + Generated + true + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + diff --git a/src/Ryujinx.HLE.Generators/ServiceSyntaxReceiver.cs b/src/Ryujinx.HLE.Generators/ServiceSyntaxReceiver.cs new file mode 100644 index 000000000..e4269cb9a --- /dev/null +++ b/src/Ryujinx.HLE.Generators/ServiceSyntaxReceiver.cs @@ -0,0 +1,24 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using System.Collections.Generic; + +namespace Ryujinx.HLE.Generators +{ + internal class ServiceSyntaxReceiver : ISyntaxReceiver + { + public HashSet Types = new HashSet(); + + public void OnVisitSyntaxNode(SyntaxNode syntaxNode) + { + if (syntaxNode is ClassDeclarationSyntax classDeclaration) + { + if (classDeclaration.BaseList == null) + { + return; + } + + Types.Add(classDeclaration); + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Applets/AppletManager.cs b/src/Ryujinx.HLE/HOS/Applets/AppletManager.cs index 30300f1b6..3c34d5c78 100644 --- a/src/Ryujinx.HLE/HOS/Applets/AppletManager.cs +++ b/src/Ryujinx.HLE/HOS/Applets/AppletManager.cs @@ -8,27 +8,24 @@ namespace Ryujinx.HLE.HOS.Applets { static class AppletManager { - private static readonly Dictionary _appletMapping; - - static AppletManager() - { - _appletMapping = new Dictionary - { - { AppletId.Error, typeof(ErrorApplet) }, - { AppletId.PlayerSelect, typeof(PlayerSelectApplet) }, - { AppletId.Controller, typeof(ControllerApplet) }, - { AppletId.SoftwareKeyboard, typeof(SoftwareKeyboardApplet) }, - { AppletId.LibAppletWeb, typeof(BrowserApplet) }, - { AppletId.LibAppletShop, typeof(BrowserApplet) }, - { AppletId.LibAppletOff, typeof(BrowserApplet) }, - }; - } - public static IApplet Create(AppletId applet, Horizon system) { - if (_appletMapping.TryGetValue(applet, out Type appletClass)) + switch (applet) { - return (IApplet)Activator.CreateInstance(appletClass, system); + case AppletId.Controller: + return new ControllerApplet(system); + case AppletId.Error: + return new ErrorApplet(system); + case AppletId.PlayerSelect: + return new PlayerSelectApplet(system); + case AppletId.SoftwareKeyboard: + return new SoftwareKeyboardApplet(system); + case AppletId.LibAppletWeb: + return new BrowserApplet(system); + case AppletId.LibAppletShop: + return new BrowserApplet(system); + case AppletId.LibAppletOff: + return new BrowserApplet(system); } throw new NotImplementedException($"{applet} applet is not implemented."); diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRenderer.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRenderer.cs index 3f7516e6a..239535ad5 100644 --- a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRenderer.cs +++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRenderer.cs @@ -112,11 +112,16 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard { // Update the parameters that were provided. _state.InputText = inputText ?? _state.InputText; - _state.CursorBegin = cursorBegin.GetValueOrDefault(_state.CursorBegin); - _state.CursorEnd = cursorEnd.GetValueOrDefault(_state.CursorEnd); + _state.CursorBegin = Math.Max(0, cursorBegin.GetValueOrDefault(_state.CursorBegin)); + _state.CursorEnd = Math.Min(cursorEnd.GetValueOrDefault(_state.CursorEnd), _state.InputText.Length); _state.OverwriteMode = overwriteMode.GetValueOrDefault(_state.OverwriteMode); _state.TypingEnabled = typingEnabled.GetValueOrDefault(_state.TypingEnabled); + var begin = _state.CursorBegin; + var end = _state.CursorEnd; + _state.CursorBegin = Math.Min(begin, end); + _state.CursorEnd = Math.Max(begin, end); + // Reset the cursor blink. _state.TextBoxBlinkCounter = 0; diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRendererBase.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRendererBase.cs index 9e48568e1..cc62eca1d 100644 --- a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRendererBase.cs +++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRendererBase.cs @@ -1,14 +1,9 @@ using Ryujinx.HLE.UI; using Ryujinx.Memory; -using SixLabors.Fonts; -using SixLabors.ImageSharp; -using SixLabors.ImageSharp.Drawing.Processing; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; +using SkiaSharp; using System; using System.Diagnostics; using System.IO; -using System.Numerics; using System.Reflection; using System.Runtime.InteropServices; @@ -29,38 +24,39 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard private readonly object _bufferLock = new(); private RenderingSurfaceInfo _surfaceInfo = null; - private Image _surface = null; + private SKImageInfo _imageInfo; + private SKSurface _surface = null; private byte[] _bufferData = null; - private readonly Image _ryujinxLogo = null; - private readonly Image _padAcceptIcon = null; - private readonly Image _padCancelIcon = null; - private readonly Image _keyModeIcon = null; + private readonly SKBitmap _ryujinxLogo = null; + private readonly SKBitmap _padAcceptIcon = null; + private readonly SKBitmap _padCancelIcon = null; + private readonly SKBitmap _keyModeIcon = null; private readonly float _textBoxOutlineWidth; private readonly float _padPressedPenWidth; - private readonly Color _textNormalColor; - private readonly Color _textSelectedColor; - private readonly Color _textOverCursorColor; + private readonly SKColor _textNormalColor; + private readonly SKColor _textSelectedColor; + private readonly SKColor _textOverCursorColor; - private readonly Brush _panelBrush; - private readonly Brush _disabledBrush; - private readonly Brush _cursorBrush; - private readonly Brush _selectionBoxBrush; + private readonly SKPaint _panelBrush; + private readonly SKPaint _disabledBrush; + private readonly SKPaint _cursorBrush; + private readonly SKPaint _selectionBoxBrush; - private readonly Pen _textBoxOutlinePen; - private readonly Pen _cursorPen; - private readonly Pen _selectionBoxPen; - private readonly Pen _padPressedPen; + private readonly SKPaint _textBoxOutlinePen; + private readonly SKPaint _cursorPen; + private readonly SKPaint _selectionBoxPen; + private readonly SKPaint _padPressedPen; private readonly int _inputTextFontSize; - private Font _messageFont; - private Font _inputTextFont; - private Font _labelsTextFont; + private SKFont _messageFont; + private SKFont _inputTextFont; + private SKFont _labelsTextFont; - private RectangleF _panelRectangle; - private Point _logoPosition; + private SKRect _panelRectangle; + private SKPoint _logoPosition; private float _messagePositionY; public SoftwareKeyboardRendererBase(IHostUITheme uiTheme) @@ -78,10 +74,10 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard _padCancelIcon = LoadResource(typeof(SoftwareKeyboardRendererBase).Assembly, padCancelIconPath, 0, 0); _keyModeIcon = LoadResource(typeof(SoftwareKeyboardRendererBase).Assembly, keyModeIconPath, 0, 0); - Color panelColor = ToColor(uiTheme.DefaultBackgroundColor, 255); - Color panelTransparentColor = ToColor(uiTheme.DefaultBackgroundColor, 150); - Color borderColor = ToColor(uiTheme.DefaultBorderColor); - Color selectionBackgroundColor = ToColor(uiTheme.SelectionBackgroundColor); + var panelColor = ToColor(uiTheme.DefaultBackgroundColor, 255); + var panelTransparentColor = ToColor(uiTheme.DefaultBackgroundColor, 150); + var borderColor = ToColor(uiTheme.DefaultBorderColor); + var selectionBackgroundColor = ToColor(uiTheme.SelectionBackgroundColor); _textNormalColor = ToColor(uiTheme.DefaultForegroundColor); _textSelectedColor = ToColor(uiTheme.SelectionForegroundColor); @@ -92,15 +88,29 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard _textBoxOutlineWidth = 2; _padPressedPenWidth = 2; - _panelBrush = new SolidBrush(panelColor); - _disabledBrush = new SolidBrush(panelTransparentColor); - _cursorBrush = new SolidBrush(_textNormalColor); - _selectionBoxBrush = new SolidBrush(selectionBackgroundColor); + _panelBrush = new SKPaint() + { + Color = panelColor, + IsAntialias = true + }; + _disabledBrush = new SKPaint() + { + Color = panelTransparentColor, + IsAntialias = true + }; + _cursorBrush = new SKPaint() { Color = _textNormalColor, IsAntialias = true }; + _selectionBoxBrush = new SKPaint() { Color = selectionBackgroundColor, IsAntialias = true }; - _textBoxOutlinePen = Pens.Solid(borderColor, _textBoxOutlineWidth); - _cursorPen = Pens.Solid(_textNormalColor, cursorWidth); - _selectionBoxPen = Pens.Solid(selectionBackgroundColor, cursorWidth); - _padPressedPen = Pens.Solid(borderColor, _padPressedPenWidth); + _textBoxOutlinePen = new SKPaint() + { + Color = borderColor, + StrokeWidth = _textBoxOutlineWidth, + IsStroke = true, + IsAntialias = true + }; + _cursorPen = new SKPaint() { Color = _textNormalColor, StrokeWidth = cursorWidth, IsStroke = true, IsAntialias = true }; + _selectionBoxPen = new SKPaint() { Color = selectionBackgroundColor, StrokeWidth = cursorWidth, IsStroke = true, IsAntialias = true }; + _padPressedPen = new SKPaint() { Color = borderColor, StrokeWidth = _padPressedPenWidth, IsStroke = true, IsAntialias = true }; _inputTextFontSize = 20; @@ -123,9 +133,10 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard { try { - _messageFont = SystemFonts.CreateFont(fontFamily, 26, FontStyle.Regular); - _inputTextFont = SystemFonts.CreateFont(fontFamily, _inputTextFontSize, FontStyle.Regular); - _labelsTextFont = SystemFonts.CreateFont(fontFamily, 24, FontStyle.Regular); + using var typeface = SKTypeface.FromFamilyName(fontFamily, SKFontStyle.Normal); + _messageFont = new SKFont(typeface, 26); + _inputTextFont = new SKFont(typeface, _inputTextFontSize); + _labelsTextFont = new SKFont(typeface, 24); return; } @@ -137,7 +148,7 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard throw new Exception($"None of these fonts were found in the system: {String.Join(", ", availableFonts)}!"); } - private static Color ToColor(ThemeColor color, byte? overrideAlpha = null, bool flipRgb = false) + private static SKColor ToColor(ThemeColor color, byte? overrideAlpha = null, bool flipRgb = false) { var a = (byte)(color.A * 255); var r = (byte)(color.R * 255); @@ -151,34 +162,33 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard b = (byte)(255 - b); } - return Color.FromRgba(r, g, b, overrideAlpha.GetValueOrDefault(a)); + return new SKColor(r, g, b, overrideAlpha.GetValueOrDefault(a)); } - private static Image LoadResource(Assembly assembly, string resourcePath, int newWidth, int newHeight) + private static SKBitmap LoadResource(Assembly assembly, string resourcePath, int newWidth, int newHeight) { Stream resourceStream = assembly.GetManifestResourceStream(resourcePath); return LoadResource(resourceStream, newWidth, newHeight); } - private static Image LoadResource(Stream resourceStream, int newWidth, int newHeight) + private static SKBitmap LoadResource(Stream resourceStream, int newWidth, int newHeight) { Debug.Assert(resourceStream != null); - var image = Image.Load(resourceStream); + var bitmap = SKBitmap.Decode(resourceStream); if (newHeight != 0 && newWidth != 0) { - image.Mutate(x => x.Resize(newWidth, newHeight, KnownResamplers.Lanczos3)); + var resized = bitmap.Resize(new SKImageInfo(newWidth, newHeight), SKFilterQuality.High); + if (resized != null) + { + bitmap.Dispose(); + bitmap = resized; + } } - return image; - } - - private static void SetGraphicsOptions(IImageProcessingContext context) - { - context.GetGraphicsOptions().Antialias = true; - context.GetDrawingOptions().GraphicsOptions.Antialias = true; + return bitmap; } private void DrawImmutableElements() @@ -187,22 +197,18 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard { return; } + var canvas = _surface.Canvas; - _surface.Mutate(context => - { - SetGraphicsOptions(context); + canvas.Clear(SKColors.Transparent); + canvas.DrawRect(_panelRectangle, _panelBrush); + canvas.DrawBitmap(_ryujinxLogo, _logoPosition); - context.Clear(Color.Transparent); - context.Fill(_panelBrush, _panelRectangle); - context.DrawImage(_ryujinxLogo, _logoPosition, 1); + float halfWidth = _panelRectangle.Width / 2; + float buttonsY = _panelRectangle.Top + 185; - float halfWidth = _panelRectangle.Width / 2; - float buttonsY = _panelRectangle.Y + 185; + SKPoint disableButtonPosition = new(halfWidth + 180, buttonsY); - PointF disableButtonPosition = new(halfWidth + 180, buttonsY); - - DrawControllerToggle(context, disableButtonPosition); - }); + DrawControllerToggle(canvas, disableButtonPosition); } public void DrawMutableElements(SoftwareKeyboardUIState state) @@ -212,40 +218,43 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard return; } - _surface.Mutate(context => + using var paint = new SKPaint(_messageFont) { - var messageRectangle = MeasureString(MessageText, _messageFont); - float messagePositionX = (_panelRectangle.Width - messageRectangle.Width) / 2 - messageRectangle.X; - float messagePositionY = _messagePositionY - messageRectangle.Y; - var messagePosition = new PointF(messagePositionX, messagePositionY); - var messageBoundRectangle = new RectangleF(messagePositionX, messagePositionY, messageRectangle.Width, messageRectangle.Height); + Color = _textNormalColor, + IsAntialias = true + }; - SetGraphicsOptions(context); + var canvas = _surface.Canvas; + var messageRectangle = MeasureString(MessageText, paint); + float messagePositionX = (_panelRectangle.Width - messageRectangle.Width) / 2 - messageRectangle.Left; + float messagePositionY = _messagePositionY - messageRectangle.Top; + var messagePosition = new SKPoint(messagePositionX, messagePositionY); + var messageBoundRectangle = SKRect.Create(messagePositionX, messagePositionY, messageRectangle.Width, messageRectangle.Height); - context.Fill(_panelBrush, messageBoundRectangle); + canvas.DrawRect(messageBoundRectangle, _panelBrush); - context.DrawText(MessageText, _messageFont, _textNormalColor, messagePosition); + canvas.DrawText(MessageText, messagePosition.X, messagePosition.Y + _messageFont.Metrics.XHeight + _messageFont.Metrics.Descent, paint); - if (!state.TypingEnabled) - { - // Just draw a semi-transparent rectangle on top to fade the component with the background. - // TODO (caian): This will not work if one decides to add make background semi-transparent as well. + if (!state.TypingEnabled) + { + // Just draw a semi-transparent rectangle on top to fade the component with the background. + // TODO (caian): This will not work if one decides to add make background semi-transparent as well. - context.Fill(_disabledBrush, messageBoundRectangle); - } + canvas.DrawRect(messageBoundRectangle, _disabledBrush); + } - DrawTextBox(context, state); + DrawTextBox(canvas, state); - float halfWidth = _panelRectangle.Width / 2; - float buttonsY = _panelRectangle.Y + 185; + float halfWidth = _panelRectangle.Width / 2; + float buttonsY = _panelRectangle.Top + 185; - PointF acceptButtonPosition = new(halfWidth - 180, buttonsY); - PointF cancelButtonPosition = new(halfWidth, buttonsY); - PointF disableButtonPosition = new(halfWidth + 180, buttonsY); + SKPoint acceptButtonPosition = new(halfWidth - 180, buttonsY); + SKPoint cancelButtonPosition = new(halfWidth, buttonsY); + SKPoint disableButtonPosition = new(halfWidth + 180, buttonsY); + + DrawPadButton(canvas, acceptButtonPosition, _padAcceptIcon, AcceptText, state.AcceptPressed, state.ControllerEnabled); + DrawPadButton(canvas, cancelButtonPosition, _padCancelIcon, CancelText, state.CancelPressed, state.ControllerEnabled); - DrawPadButton(context, acceptButtonPosition, _padAcceptIcon, AcceptText, state.AcceptPressed, state.ControllerEnabled); - DrawPadButton(context, cancelButtonPosition, _padCancelIcon, CancelText, state.CancelPressed, state.ControllerEnabled); - }); } public void CreateSurface(RenderingSurfaceInfo surfaceInfo) @@ -268,7 +277,8 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard Debug.Assert(_surfaceInfo.Height <= totalHeight); Debug.Assert(_surfaceInfo.Pitch * _surfaceInfo.Height <= _surfaceInfo.Size); - _surface = new Image((int)totalWidth, (int)totalHeight); + _imageInfo = new SKImageInfo((int)totalWidth, (int)totalHeight, SKColorType.Rgba8888); + _surface = SKSurface.Create(_imageInfo); ComputeConstants(); DrawImmutableElements(); @@ -282,76 +292,81 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard int panelHeight = 240; int panelPositionY = totalHeight - panelHeight; - _panelRectangle = new RectangleF(0, panelPositionY, totalWidth, panelHeight); + _panelRectangle = SKRect.Create(0, panelPositionY, totalWidth, panelHeight); _messagePositionY = panelPositionY + 60; int logoPositionX = (totalWidth - _ryujinxLogo.Width) / 2; int logoPositionY = panelPositionY + 18; - _logoPosition = new Point(logoPositionX, logoPositionY); + _logoPosition = new SKPoint(logoPositionX, logoPositionY); } - private static RectangleF MeasureString(string text, Font font) + private static SKRect MeasureString(string text, SKPaint paint) { - TextOptions options = new(font); + SKRect bounds = SKRect.Empty; if (text == "") { - FontRectangle emptyRectangle = TextMeasurer.MeasureSize(" ", options); - - return new RectangleF(0, emptyRectangle.Y, 0, emptyRectangle.Height); + paint.MeasureText(" ", ref bounds); + } + else + { + paint.MeasureText(text, ref bounds); } - FontRectangle rectangle = TextMeasurer.MeasureSize(text, options); - - return new RectangleF(rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height); + return bounds; } - private static RectangleF MeasureString(ReadOnlySpan text, Font font) + private static SKRect MeasureString(ReadOnlySpan text, SKPaint paint) { - TextOptions options = new(font); + SKRect bounds = SKRect.Empty; if (text == "") { - FontRectangle emptyRectangle = TextMeasurer.MeasureSize(" ", options); - return new RectangleF(0, emptyRectangle.Y, 0, emptyRectangle.Height); + paint.MeasureText(" ", ref bounds); + } + else + { + paint.MeasureText(text, ref bounds); } - FontRectangle rectangle = TextMeasurer.MeasureSize(text, options); - - return new RectangleF(rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height); + return bounds; } - private void DrawTextBox(IImageProcessingContext context, SoftwareKeyboardUIState state) + private void DrawTextBox(SKCanvas canvas, SoftwareKeyboardUIState state) { - var inputTextRectangle = MeasureString(state.InputText, _inputTextFont); + using var textPaint = new SKPaint(_labelsTextFont) + { + IsAntialias = true, + Color = _textNormalColor + }; + var inputTextRectangle = MeasureString(state.InputText, textPaint); - float boxWidth = (int)(Math.Max(300, inputTextRectangle.Width + inputTextRectangle.X + 8)); + float boxWidth = (int)(Math.Max(300, inputTextRectangle.Width + inputTextRectangle.Left + 8)); float boxHeight = 32; - float boxY = _panelRectangle.Y + 110; + float boxY = _panelRectangle.Top + 110; float boxX = (int)((_panelRectangle.Width - boxWidth) / 2); - RectangleF boxRectangle = new(boxX, boxY, boxWidth, boxHeight); + SKRect boxRectangle = SKRect.Create(boxX, boxY, boxWidth, boxHeight); - RectangleF boundRectangle = new(_panelRectangle.X, boxY - _textBoxOutlineWidth, + SKRect boundRectangle = SKRect.Create(_panelRectangle.Left, boxY - _textBoxOutlineWidth, _panelRectangle.Width, boxHeight + 2 * _textBoxOutlineWidth); - context.Fill(_panelBrush, boundRectangle); + canvas.DrawRect(boundRectangle, _panelBrush); - context.Draw(_textBoxOutlinePen, boxRectangle); + canvas.DrawRect(boxRectangle, _textBoxOutlinePen); - float inputTextX = (_panelRectangle.Width - inputTextRectangle.Width) / 2 - inputTextRectangle.X; + float inputTextX = (_panelRectangle.Width - inputTextRectangle.Width) / 2 - inputTextRectangle.Left; float inputTextY = boxY + 5; - var inputTextPosition = new PointF(inputTextX, inputTextY); - - context.DrawText(state.InputText, _inputTextFont, _textNormalColor, inputTextPosition); + var inputTextPosition = new SKPoint(inputTextX, inputTextY); + canvas.DrawText(state.InputText, inputTextPosition.X, inputTextPosition.Y + (_labelsTextFont.Metrics.XHeight + _labelsTextFont.Metrics.Descent), textPaint); // Draw the cursor on top of the text and redraw the text with a different color if necessary. - Color cursorTextColor; - Brush cursorBrush; - Pen cursorPen; + SKColor cursorTextColor; + SKPaint cursorBrush; + SKPaint cursorPen; float cursorPositionYTop = inputTextY + 1; float cursorPositionYBottom = cursorPositionYTop + _inputTextFontSize + 1; @@ -371,12 +386,12 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard ReadOnlySpan textUntilBegin = state.InputText.AsSpan(0, state.CursorBegin); ReadOnlySpan textUntilEnd = state.InputText.AsSpan(0, state.CursorEnd); - var selectionBeginRectangle = MeasureString(textUntilBegin, _inputTextFont); - var selectionEndRectangle = MeasureString(textUntilEnd, _inputTextFont); + var selectionBeginRectangle = MeasureString(textUntilBegin, textPaint); + var selectionEndRectangle = MeasureString(textUntilEnd, textPaint); cursorVisible = true; - cursorPositionXLeft = inputTextX + selectionBeginRectangle.Width + selectionBeginRectangle.X; - cursorPositionXRight = inputTextX + selectionEndRectangle.Width + selectionEndRectangle.X; + cursorPositionXLeft = inputTextX + selectionBeginRectangle.Width + selectionBeginRectangle.Left; + cursorPositionXRight = inputTextX + selectionEndRectangle.Width + selectionEndRectangle.Left; } else { @@ -390,10 +405,10 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard int cursorBegin = Math.Min(state.InputText.Length, state.CursorBegin); ReadOnlySpan textUntilCursor = state.InputText.AsSpan(0, cursorBegin); - var cursorTextRectangle = MeasureString(textUntilCursor, _inputTextFont); + var cursorTextRectangle = MeasureString(textUntilCursor, textPaint); cursorVisible = true; - cursorPositionXLeft = inputTextX + cursorTextRectangle.Width + cursorTextRectangle.X; + cursorPositionXLeft = inputTextX + cursorTextRectangle.Width + cursorTextRectangle.Left; if (state.OverwriteMode) { @@ -402,8 +417,8 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard if (state.CursorBegin < state.InputText.Length) { textUntilCursor = state.InputText.AsSpan(0, cursorBegin + 1); - cursorTextRectangle = MeasureString(textUntilCursor, _inputTextFont); - cursorPositionXRight = inputTextX + cursorTextRectangle.Width + cursorTextRectangle.X; + cursorTextRectangle = MeasureString(textUntilCursor, textPaint); + cursorPositionXRight = inputTextX + cursorTextRectangle.Width + cursorTextRectangle.Left; } else { @@ -430,29 +445,32 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard if (cursorWidth == 0) { - PointF[] points = { - new PointF(cursorPositionXLeft, cursorPositionYTop), - new PointF(cursorPositionXLeft, cursorPositionYBottom), - }; - - context.DrawLine(cursorPen, points); + canvas.DrawLine(new SKPoint(cursorPositionXLeft, cursorPositionYTop), + new SKPoint(cursorPositionXLeft, cursorPositionYBottom), + cursorPen); } else { - var cursorRectangle = new RectangleF(cursorPositionXLeft, cursorPositionYTop, cursorWidth, cursorHeight); + var cursorRectangle = SKRect.Create(cursorPositionXLeft, cursorPositionYTop, cursorWidth, cursorHeight); - context.Draw(cursorPen, cursorRectangle); - context.Fill(cursorBrush, cursorRectangle); + canvas.DrawRect(cursorRectangle, cursorPen); + canvas.DrawRect(cursorRectangle, cursorBrush); - Image textOverCursor = new((int)cursorRectangle.Width, (int)cursorRectangle.Height); - textOverCursor.Mutate(context => + using var textOverCursor = SKSurface.Create(new SKImageInfo((int)cursorRectangle.Width, (int)cursorRectangle.Height, SKColorType.Rgba8888)); + var textOverCanvas = textOverCursor.Canvas; + var textRelativePosition = new SKPoint(inputTextPosition.X - cursorRectangle.Left, inputTextPosition.Y - cursorRectangle.Top); + + using var cursorPaint = new SKPaint(_inputTextFont) { - var textRelativePosition = new PointF(inputTextPosition.X - cursorRectangle.X, inputTextPosition.Y - cursorRectangle.Y); - context.DrawText(state.InputText, _inputTextFont, cursorTextColor, textRelativePosition); - }); + Color = cursorTextColor, + IsAntialias = true + }; - var cursorPosition = new Point((int)cursorRectangle.X, (int)cursorRectangle.Y); - context.DrawImage(textOverCursor, cursorPosition, 1); + textOverCanvas.DrawText(state.InputText, textRelativePosition.X, textRelativePosition.Y + _inputTextFont.Metrics.XHeight + _inputTextFont.Metrics.Descent, cursorPaint); + + var cursorPosition = new SKPoint((int)cursorRectangle.Left, (int)cursorRectangle.Top); + textOverCursor.Flush(); + canvas.DrawSurface(textOverCursor, cursorPosition); } } else if (!state.TypingEnabled) @@ -460,11 +478,11 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard // Just draw a semi-transparent rectangle on top to fade the component with the background. // TODO (caian): This will not work if one decides to add make background semi-transparent as well. - context.Fill(_disabledBrush, boundRectangle); + canvas.DrawRect(boundRectangle, _disabledBrush); } } - private void DrawPadButton(IImageProcessingContext context, PointF point, Image icon, string label, bool pressed, bool enabled) + private void DrawPadButton(SKCanvas canvas, SKPoint point, SKBitmap icon, string label, bool pressed, bool enabled) { // Use relative positions so we can center the entire drawing later. @@ -473,12 +491,18 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard float iconWidth = icon.Width; float iconHeight = icon.Height; - var labelRectangle = MeasureString(label, _labelsTextFont); + using var paint = new SKPaint(_labelsTextFont) + { + Color = _textNormalColor, + IsAntialias = true + }; - float labelPositionX = iconWidth + 8 - labelRectangle.X; + var labelRectangle = MeasureString(label, paint); + + float labelPositionX = iconWidth + 8 - labelRectangle.Left; float labelPositionY = 3; - float fullWidth = labelPositionX + labelRectangle.Width + labelRectangle.X; + float fullWidth = labelPositionX + labelRectangle.Width + labelRectangle.Left; float fullHeight = iconHeight; // Convert all relative positions into absolute. @@ -489,24 +513,24 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard iconX += originX; iconY += originY; - var iconPosition = new Point((int)iconX, (int)iconY); - var labelPosition = new PointF(labelPositionX + originX, labelPositionY + originY); + var iconPosition = new SKPoint((int)iconX, (int)iconY); + var labelPosition = new SKPoint(labelPositionX + originX, labelPositionY + originY); - var selectedRectangle = new RectangleF(originX - 2 * _padPressedPenWidth, originY - 2 * _padPressedPenWidth, + var selectedRectangle = SKRect.Create(originX - 2 * _padPressedPenWidth, originY - 2 * _padPressedPenWidth, fullWidth + 4 * _padPressedPenWidth, fullHeight + 4 * _padPressedPenWidth); - var boundRectangle = new RectangleF(originX, originY, fullWidth, fullHeight); + var boundRectangle = SKRect.Create(originX, originY, fullWidth, fullHeight); boundRectangle.Inflate(4 * _padPressedPenWidth, 4 * _padPressedPenWidth); - context.Fill(_panelBrush, boundRectangle); - context.DrawImage(icon, iconPosition, 1); - context.DrawText(label, _labelsTextFont, _textNormalColor, labelPosition); + canvas.DrawRect(boundRectangle, _panelBrush); + canvas.DrawBitmap(icon, iconPosition); + canvas.DrawText(label, labelPosition.X, labelPosition.Y + _labelsTextFont.Metrics.XHeight + _labelsTextFont.Metrics.Descent, paint); if (enabled) { if (pressed) { - context.Draw(_padPressedPen, selectedRectangle); + canvas.DrawRect(selectedRectangle, _padPressedPen); } } else @@ -514,21 +538,26 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard // Just draw a semi-transparent rectangle on top to fade the component with the background. // TODO (caian): This will not work if one decides to add make background semi-transparent as well. - context.Fill(_disabledBrush, boundRectangle); + canvas.DrawRect(boundRectangle, _disabledBrush); } } - private void DrawControllerToggle(IImageProcessingContext context, PointF point) + private void DrawControllerToggle(SKCanvas canvas, SKPoint point) { - var labelRectangle = MeasureString(ControllerToggleText, _labelsTextFont); + using var paint = new SKPaint(_labelsTextFont) + { + IsAntialias = true, + Color = _textNormalColor + }; + var labelRectangle = MeasureString(ControllerToggleText, paint); // Use relative positions so we can center the entire drawing later. float keyWidth = _keyModeIcon.Width; float keyHeight = _keyModeIcon.Height; - float labelPositionX = keyWidth + 8 - labelRectangle.X; - float labelPositionY = -labelRectangle.Y - 1; + float labelPositionX = keyWidth + 8 - labelRectangle.Left; + float labelPositionY = -labelRectangle.Top - 1; float keyX = 0; float keyY = (int)((labelPositionY + labelRectangle.Height - keyHeight) / 2); @@ -544,14 +573,14 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard keyX += originX; keyY += originY; - var labelPosition = new PointF(labelPositionX + originX, labelPositionY + originY); - var overlayPosition = new Point((int)keyX, (int)keyY); + var labelPosition = new SKPoint(labelPositionX + originX, labelPositionY + originY); + var overlayPosition = new SKPoint((int)keyX, (int)keyY); - context.DrawImage(_keyModeIcon, overlayPosition, 1); - context.DrawText(ControllerToggleText, _labelsTextFont, _textNormalColor, labelPosition); + canvas.DrawBitmap(_keyModeIcon, overlayPosition); + canvas.DrawText(ControllerToggleText, labelPosition.X, labelPosition.Y + _labelsTextFont.Metrics.XHeight, paint); } - public void CopyImageToBuffer() + public unsafe void CopyImageToBuffer() { lock (_bufferLock) { @@ -561,21 +590,20 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard } // Convert the pixel format used in the image to the one used in the Switch surface. + _surface.Flush(); - if (!_surface.DangerousTryGetSinglePixelMemory(out Memory pixels)) + var buffer = new byte[_imageInfo.BytesSize]; + fixed (byte* bufferPtr = buffer) { - return; + if (!_surface.ReadPixels(_imageInfo, (nint)bufferPtr, _imageInfo.RowBytes, 0, 0)) + { + return; + } } - _bufferData = MemoryMarshal.AsBytes(pixels.Span).ToArray(); - Span dataConvert = MemoryMarshal.Cast(_bufferData); + _bufferData = buffer; - Debug.Assert(_bufferData.Length == _surfaceInfo.Size); - - for (int i = 0; i < dataConvert.Length; i++) - { - dataConvert[i] = BitOperations.RotateRight(dataConvert[i], 8); - } + Debug.Assert(buffer.Length == _surfaceInfo.Size); } } diff --git a/src/Ryujinx.HLE/HOS/Kernel/Memory/AddressSpaceType.cs b/src/Ryujinx.HLE/HOS/Kernel/Memory/AddressSpaceType.cs deleted file mode 100644 index 8dfa4303f..000000000 --- a/src/Ryujinx.HLE/HOS/Kernel/Memory/AddressSpaceType.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Ryujinx.HLE.HOS.Kernel.Memory -{ - enum AddressSpaceType - { - Addr32Bits = 0, - Addr36Bits = 1, - Addr32BitsNoMap = 2, - Addr39Bits = 3, - } -} diff --git a/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTableBase.cs b/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTableBase.cs index 58bbc0dbf..bf2bbb97b 100644 --- a/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTableBase.cs +++ b/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTableBase.cs @@ -58,11 +58,10 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory public ulong AslrRegionStart { get; private set; } public ulong AslrRegionEnd { get; private set; } -#pragma warning disable IDE0052 // Remove unread private member private ulong _heapCapacity; -#pragma warning restore IDE0052 public ulong PhysicalMemoryUsage { get; private set; } + public ulong AliasRegionExtraSize { get; private set; } private readonly KMemoryBlockManager _blockManager; @@ -98,30 +97,21 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory _reservedAddressSpaceSize = reservedAddressSpaceSize; } - private static readonly int[] _addrSpaceSizes = { 32, 36, 32, 39 }; - public Result InitializeForProcess( - AddressSpaceType addrSpaceType, - bool aslrEnabled, + ProcessCreationFlags flags, bool fromBack, MemoryRegion memRegion, ulong address, ulong size, KMemoryBlockSlabManager slabManager) { - if ((uint)addrSpaceType > (uint)AddressSpaceType.Addr39Bits) - { - throw new ArgumentException($"AddressSpaceType bigger than {(uint)AddressSpaceType.Addr39Bits}: {(uint)addrSpaceType}", nameof(addrSpaceType)); - } - _contextId = Context.ContextIdManager.GetId(); ulong addrSpaceBase = 0; - ulong addrSpaceSize = 1UL << _addrSpaceSizes[(int)addrSpaceType]; + ulong addrSpaceSize = 1UL << GetAddressSpaceWidth(flags); Result result = CreateUserAddressSpace( - addrSpaceType, - aslrEnabled, + flags, fromBack, addrSpaceBase, addrSpaceSize, @@ -138,6 +128,22 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory return result; } + private static int GetAddressSpaceWidth(ProcessCreationFlags flags) + { + switch (flags & ProcessCreationFlags.AddressSpaceMask) + { + case ProcessCreationFlags.AddressSpace32Bit: + case ProcessCreationFlags.AddressSpace32BitWithoutAlias: + return 32; + case ProcessCreationFlags.AddressSpace64BitDeprecated: + return 36; + case ProcessCreationFlags.AddressSpace64Bit: + return 39; + } + + throw new ArgumentException($"Invalid process flags {flags}", nameof(flags)); + } + private struct Region { public ulong Start; @@ -147,8 +153,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory } private Result CreateUserAddressSpace( - AddressSpaceType addrSpaceType, - bool aslrEnabled, + ProcessCreationFlags flags, bool fromBack, ulong addrSpaceStart, ulong addrSpaceEnd, @@ -168,9 +173,11 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory ulong stackAndTlsIoStart; ulong stackAndTlsIoEnd; - switch (addrSpaceType) + AliasRegionExtraSize = 0; + + switch (flags & ProcessCreationFlags.AddressSpaceMask) { - case AddressSpaceType.Addr32Bits: + case ProcessCreationFlags.AddressSpace32Bit: aliasRegion.Size = 0x40000000; heapRegion.Size = 0x40000000; stackRegion.Size = 0; @@ -183,7 +190,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory stackAndTlsIoEnd = 0x40000000; break; - case AddressSpaceType.Addr36Bits: + case ProcessCreationFlags.AddressSpace64BitDeprecated: aliasRegion.Size = 0x180000000; heapRegion.Size = 0x180000000; stackRegion.Size = 0; @@ -196,7 +203,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory stackAndTlsIoEnd = 0x80000000; break; - case AddressSpaceType.Addr32BitsNoMap: + case ProcessCreationFlags.AddressSpace32BitWithoutAlias: aliasRegion.Size = 0; heapRegion.Size = 0x80000000; stackRegion.Size = 0; @@ -209,7 +216,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory stackAndTlsIoEnd = 0x40000000; break; - case AddressSpaceType.Addr39Bits: + case ProcessCreationFlags.AddressSpace64Bit: if (_reservedAddressSpaceSize < addrSpaceEnd) { int addressSpaceWidth = (int)ulong.Log2(_reservedAddressSpaceSize); @@ -218,8 +225,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory heapRegion.Size = 0x180000000; stackRegion.Size = 1UL << (addressSpaceWidth - 8); tlsIoRegion.Size = 1UL << (addressSpaceWidth - 3); - CodeRegionStart = BitUtils.AlignDown(address, RegionAlignment); - codeRegionSize = BitUtils.AlignUp(endAddr, RegionAlignment) - CodeRegionStart; + CodeRegionStart = BitUtils.AlignDown(address, RegionAlignment); + codeRegionSize = BitUtils.AlignUp(endAddr, RegionAlignment) - CodeRegionStart; stackAndTlsIoStart = 0; stackAndTlsIoEnd = 0; AslrRegionStart = 0x8000000; @@ -239,9 +246,16 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory stackAndTlsIoStart = 0; stackAndTlsIoEnd = 0; } + + if (flags.HasFlag(ProcessCreationFlags.EnableAliasRegionExtraSize)) + { + AliasRegionExtraSize = addrSpaceEnd / 8; + aliasRegion.Size += AliasRegionExtraSize; + } break; + default: - throw new ArgumentException($"AddressSpaceType bigger than {(uint)AddressSpaceType.Addr39Bits}: {(uint)addrSpaceType}", nameof(addrSpaceType)); + throw new ArgumentException($"Invalid process flags {flags}", nameof(flags)); } CodeRegionEnd = CodeRegionStart + codeRegionSize; @@ -266,6 +280,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory ulong aslrMaxOffset = mapAvailableSize - mapTotalSize; + bool aslrEnabled = flags.HasFlag(ProcessCreationFlags.EnableAslr); + _aslrEnabled = aslrEnabled; AddrSpaceStart = addrSpaceStart; @@ -725,7 +741,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory { address = 0; - if (size > HeapRegionEnd - HeapRegionStart) + if (size > HeapRegionEnd - HeapRegionStart || size > _heapCapacity) { return KernelResult.OutOfMemory; } diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs index 6008548be..422f03c64 100644 --- a/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs +++ b/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs @@ -126,8 +126,6 @@ namespace Ryujinx.HLE.HOS.Kernel.Process _contextFactory = contextFactory ?? new ProcessContextFactory(); _customThreadStart = customThreadStart; - AddressSpaceType addrSpaceType = (AddressSpaceType)((int)(creationInfo.Flags & ProcessCreationFlags.AddressSpaceMask) >> (int)ProcessCreationFlags.AddressSpaceShift); - Pid = KernelContext.NewKipId(); if (Pid == 0 || Pid >= KernelConstants.InitialProcessId) @@ -137,8 +135,6 @@ namespace Ryujinx.HLE.HOS.Kernel.Process InitializeMemoryManager(creationInfo.Flags); - bool aslrEnabled = creationInfo.Flags.HasFlag(ProcessCreationFlags.EnableAslr); - ulong codeAddress = creationInfo.CodeAddress; ulong codeSize = (ulong)creationInfo.CodePagesCount * KPageTableBase.PageSize; @@ -148,9 +144,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Process : KernelContext.SmallMemoryBlockSlabManager; Result result = MemoryManager.InitializeForProcess( - addrSpaceType, - aslrEnabled, - !aslrEnabled, + creationInfo.Flags, + !creationInfo.Flags.HasFlag(ProcessCreationFlags.EnableAslr), memRegion, codeAddress, codeSize, @@ -234,8 +229,6 @@ namespace Ryujinx.HLE.HOS.Kernel.Process : KernelContext.SmallMemoryBlockSlabManager; } - AddressSpaceType addrSpaceType = (AddressSpaceType)((int)(creationInfo.Flags & ProcessCreationFlags.AddressSpaceMask) >> (int)ProcessCreationFlags.AddressSpaceShift); - Pid = KernelContext.NewProcessId(); if (Pid == ulong.MaxValue || Pid < KernelConstants.InitialProcessId) @@ -245,16 +238,13 @@ namespace Ryujinx.HLE.HOS.Kernel.Process InitializeMemoryManager(creationInfo.Flags); - bool aslrEnabled = creationInfo.Flags.HasFlag(ProcessCreationFlags.EnableAslr); - ulong codeAddress = creationInfo.CodeAddress; ulong codeSize = codePagesCount * KPageTableBase.PageSize; Result result = MemoryManager.InitializeForProcess( - addrSpaceType, - aslrEnabled, - !aslrEnabled, + creationInfo.Flags, + !creationInfo.Flags.HasFlag(ProcessCreationFlags.EnableAslr), memRegion, codeAddress, codeSize, @@ -309,8 +299,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Process private Result ParseProcessInfo(ProcessCreationInfo creationInfo) { // Ensure that the current kernel version is equal or above to the minimum required. - uint requiredKernelVersionMajor = (uint)Capabilities.KernelReleaseVersion >> 19; - uint requiredKernelVersionMinor = ((uint)Capabilities.KernelReleaseVersion >> 15) & 0xf; + uint requiredKernelVersionMajor = Capabilities.KernelReleaseVersion >> 19; + uint requiredKernelVersionMinor = (Capabilities.KernelReleaseVersion >> 15) & 0xf; if (KernelContext.EnableVersionChecks) { @@ -519,12 +509,10 @@ namespace Ryujinx.HLE.HOS.Kernel.Process return result; } -#pragma warning disable CA1822 // Mark member as static private void GenerateRandomEntropy() { // TODO. } -#pragma warning restore CA1822 public Result Start(int mainThreadPriority, ulong stackSize) { @@ -1182,5 +1170,10 @@ namespace Ryujinx.HLE.HOS.Kernel.Process // TODO return false; } + + public bool IsSvcPermitted(int svcId) + { + return Capabilities.IsSvcPermitted(svcId); + } } } diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/KProcessCapabilities.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/KProcessCapabilities.cs index 314aadf36..ebab67bb8 100644 --- a/src/Ryujinx.HLE/HOS/Kernel/Process/KProcessCapabilities.cs +++ b/src/Ryujinx.HLE/HOS/Kernel/Process/KProcessCapabilities.cs @@ -8,6 +8,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Process { class KProcessCapabilities { + private const int SvcMaskElementBits = 8; + public byte[] SvcAccessMask { get; } public byte[] IrqAccessMask { get; } @@ -22,7 +24,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process public KProcessCapabilities() { // length / number of bits of the underlying type - SvcAccessMask = new byte[KernelConstants.SupervisorCallCount / 8]; + SvcAccessMask = new byte[KernelConstants.SupervisorCallCount / SvcMaskElementBits]; IrqAccessMask = new byte[0x80]; } @@ -208,7 +210,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process return KernelResult.MaximumExceeded; } - SvcAccessMask[svcId / 8] |= (byte)(1 << (svcId & 7)); + SvcAccessMask[svcId / SvcMaskElementBits] |= (byte)(1 << (svcId % SvcMaskElementBits)); } break; @@ -324,5 +326,13 @@ namespace Ryujinx.HLE.HOS.Kernel.Process return mask << (int)min; } + + public bool IsSvcPermitted(int svcId) + { + int index = svcId / SvcMaskElementBits; + int mask = 1 << (svcId % SvcMaskElementBits); + + return (uint)svcId < KernelConstants.SupervisorCallCount && (SvcAccessMask[index] & mask) != 0; + } } } diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessCreationFlags.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessCreationFlags.cs index f0e43e023..1b62a29d4 100644 --- a/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessCreationFlags.cs +++ b/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessCreationFlags.cs @@ -29,6 +29,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Process PoolPartitionMask = 0xf << PoolPartitionShift, OptimizeMemoryAllocation = 1 << 11, + DisableDeviceAddressSpaceMerge = 1 << 12, + EnableAliasRegionExtraSize = 1 << 13, All = Is64Bit | @@ -38,6 +40,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Process IsApplication | DeprecatedUseSecureMemory | PoolPartitionMask | - OptimizeMemoryAllocation, + OptimizeMemoryAllocation | + DisableDeviceAddressSpaceMerge | + EnableAliasRegionExtraSize, } } diff --git a/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/InfoType.cs b/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/InfoType.cs index c0db82105..cbaae8780 100644 --- a/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/InfoType.cs +++ b/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/InfoType.cs @@ -21,14 +21,17 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall SystemResourceSizeTotal, SystemResourceSizeUsed, ProgramId, - // NOTE: Added in 4.0.0, removed in 5.0.0. - InitialProcessIdRange, + InitialProcessIdRange, // NOTE: Added in 4.0.0, removed in 5.0.0. UserExceptionContextAddress, TotalNonSystemMemorySize, UsedNonSystemMemorySize, IsApplication, FreeThreadCount, ThreadTickCount, + IsSvcPermitted, + IoRegionHint, + AliasRegionExtraSize, + MesosphereCurrentProcess = 65001, } } diff --git a/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/Syscall.cs b/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/Syscall.cs index 8f104b0b7..2f487243d 100644 --- a/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/Syscall.cs +++ b/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/Syscall.cs @@ -84,6 +84,17 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall return KernelResult.InvalidSize; } + if (info.Flags.HasFlag(ProcessCreationFlags.EnableAliasRegionExtraSize)) + { + if ((info.Flags & ProcessCreationFlags.AddressSpaceMask) != ProcessCreationFlags.AddressSpace64Bit || + info.SystemResourcePagesCount <= 0) + { + return KernelResult.InvalidState; + } + + // TODO: Check that we are in debug mode. + } + if (info.Flags.HasFlag(ProcessCreationFlags.OptimizeMemoryAllocation) && !info.Flags.HasFlag(ProcessCreationFlags.IsApplication)) { @@ -139,7 +150,6 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall return handleTable.GenerateHandle(process, out handle); } -#pragma warning disable CA1822 // Mark member as static public Result StartProcess(int handle, int priority, int cpuCore, ulong mainThreadStackSize) { KProcess process = KernelStatic.GetCurrentProcess().HandleTable.GetObject(handle); @@ -172,17 +182,14 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall return Result.Success; } -#pragma warning restore CA1822 [Svc(0x5f)] -#pragma warning disable CA1822 // Mark member as static public Result FlushProcessDataCache(int processHandle, ulong address, ulong size) { // FIXME: This needs to be implemented as ARMv7 doesn't have any way to do cache maintenance operations on EL0. // As we don't support (and don't actually need) to flush the cache, this is stubbed. return Result.Success; } -#pragma warning restore CA1822 // IPC @@ -256,7 +263,6 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall } [Svc(0x22)] -#pragma warning disable CA1822 // Mark member as static public Result SendSyncRequestWithUserBuffer( [PointerSized] ulong messagePtr, [PointerSized] ulong messageSize, @@ -306,7 +312,6 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall return result; } -#pragma warning restore CA1822 [Svc(0x23)] public Result SendAsyncRequestWithUserBuffer( @@ -896,7 +901,6 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall } [Svc(2)] -#pragma warning disable CA1822 // Mark member as static public Result SetMemoryPermission([PointerSized] ulong address, [PointerSized] ulong size, KMemoryPermission permission) { if (!PageAligned(address)) @@ -928,10 +932,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall return currentProcess.MemoryManager.SetMemoryPermission(address, size, permission); } -#pragma warning restore CA1822 [Svc(3)] -#pragma warning disable CA1822 // Mark member as static public Result SetMemoryAttribute( [PointerSized] ulong address, [PointerSized] ulong size, @@ -979,10 +981,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall return result; } -#pragma warning restore CA1822 [Svc(4)] -#pragma warning disable CA1822 // Mark member as static public Result MapMemory([PointerSized] ulong dst, [PointerSized] ulong src, [PointerSized] ulong size) { if (!PageAligned(src | dst)) @@ -1018,10 +1018,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall return process.MemoryManager.Map(dst, src, size); } -#pragma warning restore CA1822 [Svc(5)] -#pragma warning disable CA1822 // Mark member as static public Result UnmapMemory([PointerSized] ulong dst, [PointerSized] ulong src, [PointerSized] ulong size) { if (!PageAligned(src | dst)) @@ -1057,7 +1055,6 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall return process.MemoryManager.Unmap(dst, src, size); } -#pragma warning restore CA1822 [Svc(6)] public Result QueryMemory([PointerSized] ulong infoPtr, [PointerSized] out ulong pageInfo, [PointerSized] ulong address) @@ -1074,7 +1071,6 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall return result; } -#pragma warning disable CA1822 // Mark member as static public Result QueryMemory(out MemoryInfo info, out ulong pageInfo, ulong address) { KProcess process = KernelStatic.GetCurrentProcess(); @@ -1094,10 +1090,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall return Result.Success; } -#pragma warning restore CA1822 [Svc(0x13)] -#pragma warning disable CA1822 // Mark member as static public Result MapSharedMemory(int handle, [PointerSized] ulong address, [PointerSized] ulong size, KMemoryPermission permission) { if (!PageAligned(address)) @@ -1143,10 +1137,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall currentProcess, permission); } -#pragma warning restore CA1822 [Svc(0x14)] -#pragma warning disable CA1822 // Mark member as static public Result UnmapSharedMemory(int handle, [PointerSized] ulong address, [PointerSized] ulong size) { if (!PageAligned(address)) @@ -1186,7 +1178,6 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall size, currentProcess); } -#pragma warning restore CA1822 [Svc(0x15)] public Result CreateTransferMemory(out int handle, [PointerSized] ulong address, [PointerSized] ulong size, KMemoryPermission permission) @@ -1253,7 +1244,6 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall } [Svc(0x51)] -#pragma warning disable CA1822 // Mark member as static public Result MapTransferMemory(int handle, [PointerSized] ulong address, [PointerSized] ulong size, KMemoryPermission permission) { if (!PageAligned(address)) @@ -1299,10 +1289,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall currentProcess, permission); } -#pragma warning restore CA1822 [Svc(0x52)] -#pragma warning disable CA1822 // Mark member as static public Result UnmapTransferMemory(int handle, [PointerSized] ulong address, [PointerSized] ulong size) { if (!PageAligned(address)) @@ -1342,10 +1330,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall size, currentProcess); } -#pragma warning restore CA1822 [Svc(0x2c)] -#pragma warning disable CA1822 // Mark member as static public Result MapPhysicalMemory([PointerSized] ulong address, [PointerSized] ulong size) { if (!PageAligned(address)) @@ -1380,10 +1366,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall return process.MemoryManager.MapPhysicalMemory(address, size); } -#pragma warning restore CA1822 [Svc(0x2d)] -#pragma warning disable CA1822 // Mark member as static public Result UnmapPhysicalMemory([PointerSized] ulong address, [PointerSized] ulong size) { if (!PageAligned(address)) @@ -1418,7 +1402,6 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall return process.MemoryManager.UnmapPhysicalMemory(address, size); } -#pragma warning restore CA1822 [Svc(0x4b)] public Result CreateCodeMemory(out int handle, [PointerSized] ulong address, [PointerSized] ulong size) @@ -1462,7 +1445,6 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall } [Svc(0x4c)] -#pragma warning disable CA1822 // Mark member as static public Result ControlCodeMemory( int handle, CodeMemoryOperation op, @@ -1540,10 +1522,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall return KernelResult.InvalidEnumValue; } } -#pragma warning restore CA1822 [Svc(0x73)] -#pragma warning disable CA1822 // Mark member as static public Result SetProcessMemoryPermission( int handle, ulong src, @@ -1584,10 +1564,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall return targetProcess.MemoryManager.SetProcessMemoryPermission(src, size, permission); } -#pragma warning restore CA1822 [Svc(0x74)] -#pragma warning disable CA1822 // Mark member as static public Result MapProcessMemory( [PointerSized] ulong dst, int handle, @@ -1643,10 +1621,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall return dstProcess.MemoryManager.MapPages(dst, pageList, MemoryState.ProcessMemory, KMemoryPermission.ReadAndWrite); } -#pragma warning restore CA1822 [Svc(0x75)] -#pragma warning disable CA1822 // Mark member as static public Result UnmapProcessMemory( [PointerSized] ulong dst, int handle, @@ -1691,10 +1667,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall return Result.Success; } -#pragma warning restore CA1822 [Svc(0x77)] -#pragma warning disable CA1822 // Mark member as static public Result MapProcessCodeMemory(int handle, ulong dst, ulong src, ulong size) { if (!PageAligned(dst) || !PageAligned(src)) @@ -1731,10 +1705,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall return targetProcess.MemoryManager.MapProcessCodeMemory(dst, src, size); } -#pragma warning restore CA1822 [Svc(0x78)] -#pragma warning disable CA1822 // Mark member as static public Result UnmapProcessCodeMemory(int handle, ulong dst, ulong src, ulong size) { if (!PageAligned(dst) || !PageAligned(src)) @@ -1771,7 +1743,6 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall return targetProcess.MemoryManager.UnmapProcessCodeMemory(dst, src, size); } -#pragma warning restore CA1822 private static bool PageAligned(ulong address) { @@ -1781,7 +1752,6 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall // System [Svc(0x7b)] -#pragma warning disable CA1822 // Mark member as static public Result TerminateProcess(int handle) { KProcess process = KernelStatic.GetCurrentProcess(); @@ -1810,15 +1780,12 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall return result; } -#pragma warning restore CA1822 [Svc(7)] -#pragma warning disable CA1822 // Mark member as static public void ExitProcess() { KernelStatic.GetCurrentProcess().TerminateCurrentProcess(); } -#pragma warning restore CA1822 [Svc(0x11)] public Result SignalEvent(int handle) @@ -1911,7 +1878,6 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall } [Svc(0x26)] -#pragma warning disable CA1822 // Mark member as static public void Break(ulong reason) { KThread currentThread = KernelStatic.GetCurrentThread(); @@ -1937,10 +1903,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall Logger.Debug?.Print(LogClass.KernelSvc, "Debugger triggered."); } } -#pragma warning restore CA1822 [Svc(0x27)] -#pragma warning disable CA1822 // Mark member as static public void OutputDebugString([PointerSized] ulong strPtr, [PointerSized] ulong size) { KProcess process = KernelStatic.GetCurrentProcess(); @@ -1949,7 +1913,6 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall Logger.Warning?.Print(LogClass.KernelSvc, str); } -#pragma warning restore CA1822 [Svc(0x29)] public Result GetInfo(out ulong value, InfoType id, int handle, long subId) @@ -1978,6 +1941,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall case InfoType.UsedNonSystemMemorySize: case InfoType.IsApplication: case InfoType.FreeThreadCount: + case InfoType.AliasRegionExtraSize: { if (subId != 0) { @@ -2006,22 +1970,19 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall value = process.MemoryManager.AliasRegionStart; break; case InfoType.AliasRegionSize: - value = (process.MemoryManager.AliasRegionEnd - - process.MemoryManager.AliasRegionStart); + value = process.MemoryManager.AliasRegionEnd - process.MemoryManager.AliasRegionStart; break; case InfoType.HeapRegionAddress: value = process.MemoryManager.HeapRegionStart; break; case InfoType.HeapRegionSize: - value = (process.MemoryManager.HeapRegionEnd - - process.MemoryManager.HeapRegionStart); + value = process.MemoryManager.HeapRegionEnd - process.MemoryManager.HeapRegionStart; break; case InfoType.TotalMemorySize: value = process.GetMemoryCapacity(); break; - case InfoType.UsedMemorySize: value = process.GetMemoryUsage(); break; @@ -2029,7 +1990,6 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall case InfoType.AslrRegionAddress: value = process.MemoryManager.GetAddrSpaceBaseAddr(); break; - case InfoType.AslrRegionSize: value = process.MemoryManager.GetAddrSpaceSize(); break; @@ -2038,20 +1998,17 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall value = process.MemoryManager.StackRegionStart; break; case InfoType.StackRegionSize: - value = (process.MemoryManager.StackRegionEnd - - process.MemoryManager.StackRegionStart); + value = process.MemoryManager.StackRegionEnd - process.MemoryManager.StackRegionStart; break; case InfoType.SystemResourceSizeTotal: value = process.PersonalMmHeapPagesCount * KPageTableBase.PageSize; break; - case InfoType.SystemResourceSizeUsed: if (process.PersonalMmHeapPagesCount != 0) { value = process.MemoryManager.GetMmUsedPages() * KPageTableBase.PageSize; } - break; case InfoType.ProgramId: @@ -2065,7 +2022,6 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall case InfoType.TotalNonSystemMemorySize: value = process.GetMemoryCapacityWithoutPersonalMmHeap(); break; - case InfoType.UsedNonSystemMemorySize: value = process.GetMemoryUsageWithoutPersonalMmHeap(); break; @@ -2084,10 +2040,12 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall { value = 0; } + break; + case InfoType.AliasRegionExtraSize: + value = process.MemoryManager.AliasRegionExtraSize; break; } - break; } @@ -2104,7 +2062,6 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall } value = KernelStatic.GetCurrentProcess().Debug ? 1UL : 0UL; - break; } @@ -2136,7 +2093,6 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall value = (uint)resLimHandle; } - break; } @@ -2155,7 +2111,6 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall } value = (ulong)KTimeManager.ConvertHostTicksToTicks(_context.Schedulers[currentCore].TotalIdleTimeTicks); - break; } @@ -2174,7 +2129,6 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall KProcess currentProcess = KernelStatic.GetCurrentProcess(); value = currentProcess.RandomEntropy[subId]; - break; } @@ -2220,7 +2174,22 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall value = (ulong)KTimeManager.ConvertHostTicksToTicks(totalTimeRunning); } + break; + } + case InfoType.IsSvcPermitted: + { + if (handle != 0) + { + return KernelResult.InvalidHandle; + } + + if (subId != 0x36) + { + return KernelResult.InvalidCombination; + } + + value = KernelStatic.GetCurrentProcess().IsSvcPermitted((int)subId) ? 1UL : 0UL; break; } @@ -2231,7 +2200,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall return KernelResult.InvalidHandle; } - if ((ulong)subId != 0) + if (subId != 0) { return KernelResult.InvalidCombination; } @@ -2246,8 +2215,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall return result; } - value = (ulong)outHandle; - + value = (uint)outHandle; break; } @@ -2398,7 +2366,6 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall } [Svc(0x30)] -#pragma warning disable CA1822 // Mark member as static public Result GetResourceLimitLimitValue(out long limitValue, int handle, LimitableResource resource) { limitValue = 0; @@ -2419,10 +2386,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall return Result.Success; } -#pragma warning restore CA1822 [Svc(0x31)] -#pragma warning disable CA1822 // Mark member as static public Result GetResourceLimitCurrentValue(out long limitValue, int handle, LimitableResource resource) { limitValue = 0; @@ -2443,10 +2408,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall return Result.Success; } -#pragma warning restore CA1822 [Svc(0x37)] -#pragma warning disable CA1822 // Mark member as static public Result GetResourceLimitPeakValue(out long peak, int handle, LimitableResource resource) { peak = 0; @@ -2467,7 +2430,6 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall return Result.Success; } -#pragma warning restore CA1822 [Svc(0x7d)] public Result CreateResourceLimit(out int handle) @@ -2480,7 +2442,6 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall } [Svc(0x7e)] -#pragma warning disable CA1822 // Mark member as static public Result SetResourceLimitLimitValue(int handle, LimitableResource resource, long limitValue) { if (resource >= LimitableResource.Count) @@ -2497,7 +2458,6 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall return resourceLimit.SetLimitValue(resource, limitValue); } -#pragma warning restore CA1822 // Thread @@ -2577,7 +2537,6 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall } [Svc(9)] -#pragma warning disable CA1822 // Mark member as static public Result StartThread(int handle) { KProcess process = KernelStatic.GetCurrentProcess(); @@ -2604,17 +2563,14 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall return KernelResult.InvalidHandle; } } -#pragma warning restore CA1822 [Svc(0xa)] -#pragma warning disable CA1822 // Mark member as static public void ExitThread() { KThread currentThread = KernelStatic.GetCurrentThread(); currentThread.Exit(); } -#pragma warning restore CA1822 [Svc(0xb)] public void SleepThread(long timeout) @@ -2641,7 +2597,6 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall } [Svc(0xc)] -#pragma warning disable CA1822 // Mark member as static public Result GetThreadPriority(out int priority, int handle) { KProcess process = KernelStatic.GetCurrentProcess(); @@ -2661,10 +2616,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall return KernelResult.InvalidHandle; } } -#pragma warning restore CA1822 [Svc(0xd)] -#pragma warning disable CA1822 // Mark member as static public Result SetThreadPriority(int handle, int priority) { // TODO: NPDM check. @@ -2682,10 +2635,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall return Result.Success; } -#pragma warning restore CA1822 [Svc(0xe)] -#pragma warning disable CA1822 // Mark member as static public Result GetThreadCoreMask(out int preferredCore, out ulong affinityMask, int handle) { KProcess process = KernelStatic.GetCurrentProcess(); @@ -2707,10 +2658,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall return KernelResult.InvalidHandle; } } -#pragma warning restore CA1822 [Svc(0xf)] -#pragma warning disable CA1822 // Mark member as static public Result SetThreadCoreMask(int handle, int preferredCore, ulong affinityMask) { KProcess currentProcess = KernelStatic.GetCurrentProcess(); @@ -2758,18 +2707,14 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall return thread.SetCoreAndAffinityMask(preferredCore, affinityMask); } -#pragma warning restore CA1822 [Svc(0x10)] -#pragma warning disable CA1822 // Mark member as static public int GetCurrentProcessorNumber() { return KernelStatic.GetCurrentThread().CurrentCore; } -#pragma warning restore CA1822 [Svc(0x25)] -#pragma warning disable CA1822 // Mark member as static public Result GetThreadId(out ulong threadUid, int handle) { KProcess process = KernelStatic.GetCurrentProcess(); @@ -2789,10 +2734,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall return KernelResult.InvalidHandle; } } -#pragma warning restore CA1822 [Svc(0x32)] -#pragma warning disable CA1822 // Mark member as static public Result SetThreadActivity(int handle, bool pause) { KProcess process = KernelStatic.GetCurrentProcess(); @@ -2816,10 +2759,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall return thread.SetActivity(pause); } -#pragma warning restore CA1822 [Svc(0x33)] -#pragma warning disable CA1822 // Mark member as static public Result GetThreadContext3([PointerSized] ulong address, int handle) { KProcess currentProcess = KernelStatic.GetCurrentProcess(); @@ -2853,7 +2794,6 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall return result; } -#pragma warning restore CA1822 // Thread synchronization @@ -2986,7 +2926,6 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall } [Svc(0x1a)] -#pragma warning disable CA1822 // Mark member as static public Result ArbitrateLock(int ownerHandle, [PointerSized] ulong mutexAddress, int requesterHandle) { if (IsPointingInsideKernel(mutexAddress)) @@ -3003,10 +2942,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall return currentProcess.AddressArbiter.ArbitrateLock(ownerHandle, mutexAddress, requesterHandle); } -#pragma warning restore CA1822 [Svc(0x1b)] -#pragma warning disable CA1822 // Mark member as static public Result ArbitrateUnlock([PointerSized] ulong mutexAddress) { if (IsPointingInsideKernel(mutexAddress)) @@ -3023,10 +2960,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall return currentProcess.AddressArbiter.ArbitrateUnlock(mutexAddress); } -#pragma warning restore CA1822 [Svc(0x1c)] -#pragma warning disable CA1822 // Mark member as static public Result WaitProcessWideKeyAtomic( [PointerSized] ulong mutexAddress, [PointerSized] ulong condVarAddress, @@ -3056,10 +2991,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall handle, timeout); } -#pragma warning restore CA1822 [Svc(0x1d)] -#pragma warning disable CA1822 // Mark member as static public Result SignalProcessWideKey([PointerSized] ulong address, int count) { KProcess currentProcess = KernelStatic.GetCurrentProcess(); @@ -3068,10 +3001,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall return Result.Success; } -#pragma warning restore CA1822 [Svc(0x34)] -#pragma warning disable CA1822 // Mark member as static public Result WaitForAddress([PointerSized] ulong address, ArbitrationType type, int value, long timeout) { if (IsPointingInsideKernel(address)) @@ -3102,10 +3033,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall _ => KernelResult.InvalidEnumValue, }; } -#pragma warning restore CA1822 [Svc(0x35)] -#pragma warning disable CA1822 // Mark member as static public Result SignalToAddress([PointerSized] ulong address, SignalType type, int value, int count) { if (IsPointingInsideKernel(address)) @@ -3131,17 +3060,14 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall _ => KernelResult.InvalidEnumValue, }; } -#pragma warning restore CA1822 [Svc(0x36)] -#pragma warning disable CA1822 // Mark member as static public Result SynchronizePreemptionState() { KernelStatic.GetCurrentThread().SynchronizePreemptionState(); return Result.Success; } -#pragma warning restore CA1822 // Not actual syscalls, used by HLE services and such. diff --git a/src/Ryujinx.HLE/HOS/Services/Caps/CaptureManager.cs b/src/Ryujinx.HLE/HOS/Services/Caps/CaptureManager.cs index 91a8958e6..bf0c7e9dc 100644 --- a/src/Ryujinx.HLE/HOS/Services/Caps/CaptureManager.cs +++ b/src/Ryujinx.HLE/HOS/Services/Caps/CaptureManager.cs @@ -1,10 +1,10 @@ using Ryujinx.Common.Memory; using Ryujinx.HLE.HOS.Services.Caps.Types; -using SixLabors.ImageSharp; -using SixLabors.ImageSharp.PixelFormats; +using SkiaSharp; using System; using System.IO; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Security.Cryptography; namespace Ryujinx.HLE.HOS.Services.Caps @@ -118,7 +118,11 @@ namespace Ryujinx.HLE.HOS.Services.Caps } // NOTE: The saved JPEG file doesn't have the limitation in the extra EXIF data. - Image.LoadPixelData(screenshotData, 1280, 720).SaveAsJpegAsync(filePath); + using var bitmap = new SKBitmap(new SKImageInfo(1280, 720, SKColorType.Rgba8888)); + Marshal.Copy(screenshotData, 0, bitmap.GetPixels(), screenshotData.Length); + using var data = bitmap.Encode(SKEncodedImageFormat.Jpeg, 80); + using var file = File.OpenWrite(filePath); + data.SaveTo(file); return ResultCode.Success; } diff --git a/src/Ryujinx.HLE/HOS/Services/Nim/IShopServiceAccessServerInterface.cs b/src/Ryujinx.HLE/HOS/Services/Nim/IShopServiceAccessServerInterface.cs index 52412489a..d7e276ea0 100644 --- a/src/Ryujinx.HLE/HOS/Services/Nim/IShopServiceAccessServerInterface.cs +++ b/src/Ryujinx.HLE/HOS/Services/Nim/IShopServiceAccessServerInterface.cs @@ -40,5 +40,12 @@ namespace Ryujinx.HLE.HOS.Services.Nim return ResultCode.Success; } + + [CommandCmif(5)] // 17.0.0+ + // CreateServerInterface2(pid, handle, u64) -> object + public ResultCode CreateServerInterface2(ServiceCtx context) + { + return CreateServerInterface(context); + } } } diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/VsmsMappingArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/VsmsMappingArguments.cs index 1b56fcc44..baada9197 100644 --- a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/VsmsMappingArguments.cs +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/VsmsMappingArguments.cs @@ -9,5 +9,6 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrlGpu.Types public byte Sm0TpcIndex; public byte Sm1GpcIndex; public byte Sm1TpcIndex; + public uint Reserved; } } diff --git a/src/Ryujinx.HLE/HOS/Services/Sm/IUserInterface.cs b/src/Ryujinx.HLE/HOS/Services/Sm/IUserInterface.cs index 3dc82035f..7a90c664e 100644 --- a/src/Ryujinx.HLE/HOS/Services/Sm/IUserInterface.cs +++ b/src/Ryujinx.HLE/HOS/Services/Sm/IUserInterface.cs @@ -2,6 +2,7 @@ using Ryujinx.Common.Logging; using Ryujinx.HLE.HOS.Ipc; using Ryujinx.HLE.HOS.Kernel; using Ryujinx.HLE.HOS.Kernel.Ipc; +using Ryujinx.HLE.HOS.Services.Apm; using Ryujinx.Horizon.Common; using System; using System.Collections.Generic; @@ -12,7 +13,7 @@ using System.Text; namespace Ryujinx.HLE.HOS.Services.Sm { - class IUserInterface : IpcService + partial class IUserInterface : IpcService { private static readonly Dictionary _services; @@ -95,9 +96,7 @@ namespace Ryujinx.HLE.HOS.Services.Sm { ServiceAttribute serviceAttribute = (ServiceAttribute)type.GetCustomAttributes(typeof(ServiceAttribute)).First(service => ((ServiceAttribute)service).Name == name); - IpcService service = serviceAttribute.Parameter != null - ? (IpcService)Activator.CreateInstance(type, context, serviceAttribute.Parameter) - : (IpcService)Activator.CreateInstance(type, context); + IpcService service = GetServiceInstance(type, context, serviceAttribute.Parameter); service.TrySetServer(_commonServer); service.Server.AddSessionObj(session.ServerSession, service); diff --git a/src/Ryujinx.HLE/Loaders/Npdm/ACI0.cs b/src/Ryujinx.HLE/Loaders/Npdm/ACI0.cs index 9a5b6b0aa..8d828e8ed 100644 --- a/src/Ryujinx.HLE/Loaders/Npdm/ACI0.cs +++ b/src/Ryujinx.HLE/Loaders/Npdm/ACI0.cs @@ -15,6 +15,12 @@ namespace Ryujinx.HLE.Loaders.Npdm public ServiceAccessControl ServiceAccessControl { get; private set; } public KernelAccessControl KernelAccessControl { get; private set; } + /// The stream doesn't contain valid ACI0 data. + /// The stream does not support reading, is , or is already closed. + /// The end of the stream is reached. + /// The stream is closed. + /// An I/O error occurred. + /// The FsAccessHeader.ContentOwnerId section is not implemented. public Aci0(Stream stream, int offset) { stream.Seek(offset, SeekOrigin.Begin); diff --git a/src/Ryujinx.HLE/Loaders/Npdm/ACID.cs b/src/Ryujinx.HLE/Loaders/Npdm/ACID.cs index ab30b40ca..57d0ee274 100644 --- a/src/Ryujinx.HLE/Loaders/Npdm/ACID.cs +++ b/src/Ryujinx.HLE/Loaders/Npdm/ACID.cs @@ -19,6 +19,11 @@ namespace Ryujinx.HLE.Loaders.Npdm public ServiceAccessControl ServiceAccessControl { get; private set; } public KernelAccessControl KernelAccessControl { get; private set; } + /// The stream doesn't contain valid ACID data. + /// The stream does not support reading, is , or is already closed. + /// The end of the stream is reached. + /// The stream is closed. + /// An I/O error occurred. public Acid(Stream stream, int offset) { stream.Seek(offset, SeekOrigin.Begin); diff --git a/src/Ryujinx.HLE/Loaders/Npdm/FsAccessControl.cs b/src/Ryujinx.HLE/Loaders/Npdm/FsAccessControl.cs index f17ca348b..a369f9f2d 100644 --- a/src/Ryujinx.HLE/Loaders/Npdm/FsAccessControl.cs +++ b/src/Ryujinx.HLE/Loaders/Npdm/FsAccessControl.cs @@ -11,6 +11,10 @@ namespace Ryujinx.HLE.Loaders.Npdm public int Unknown3 { get; private set; } public int Unknown4 { get; private set; } + /// The stream does not support reading, is , or is already closed. + /// The end of the stream is reached. + /// The stream is closed. + /// An I/O error occurred. public FsAccessControl(Stream stream, int offset, int size) { stream.Seek(offset, SeekOrigin.Begin); diff --git a/src/Ryujinx.HLE/Loaders/Npdm/FsAccessHeader.cs b/src/Ryujinx.HLE/Loaders/Npdm/FsAccessHeader.cs index 5987be0ef..249f8dd9d 100644 --- a/src/Ryujinx.HLE/Loaders/Npdm/FsAccessHeader.cs +++ b/src/Ryujinx.HLE/Loaders/Npdm/FsAccessHeader.cs @@ -9,6 +9,12 @@ namespace Ryujinx.HLE.Loaders.Npdm public int Version { get; private set; } public ulong PermissionsBitmask { get; private set; } + /// The stream contains invalid data. + /// The ContentOwnerId section is not implemented. + /// The stream does not support reading, is , or is already closed. + /// The end of the stream is reached. + /// The stream is closed. + /// An I/O error occurred. public FsAccessHeader(Stream stream, int offset, int size) { stream.Seek(offset, SeekOrigin.Begin); diff --git a/src/Ryujinx.HLE/Loaders/Npdm/KernelAccessControl.cs b/src/Ryujinx.HLE/Loaders/Npdm/KernelAccessControl.cs index 171243799..979c6f669 100644 --- a/src/Ryujinx.HLE/Loaders/Npdm/KernelAccessControl.cs +++ b/src/Ryujinx.HLE/Loaders/Npdm/KernelAccessControl.cs @@ -6,6 +6,10 @@ namespace Ryujinx.HLE.Loaders.Npdm { public int[] Capabilities { get; private set; } + /// The stream does not support reading, is , or is already closed. + /// The end of the stream is reached. + /// The stream is closed. + /// An I/O error occurred. public KernelAccessControl(Stream stream, int offset, int size) { stream.Seek(offset, SeekOrigin.Begin); diff --git a/src/Ryujinx.HLE/Loaders/Npdm/Npdm.cs b/src/Ryujinx.HLE/Loaders/Npdm/Npdm.cs index 622d7ee03..4a99de98c 100644 --- a/src/Ryujinx.HLE/Loaders/Npdm/Npdm.cs +++ b/src/Ryujinx.HLE/Loaders/Npdm/Npdm.cs @@ -24,6 +24,13 @@ namespace Ryujinx.HLE.Loaders.Npdm public Aci0 Aci0 { get; private set; } public Acid Acid { get; private set; } + /// The stream doesn't contain valid NPDM data. + /// The FsAccessHeader.ContentOwnerId section is not implemented. + /// The stream does not support reading, is , or is already closed. + /// An error occured while reading bytes from the stream. + /// The end of the stream is reached. + /// The stream is closed. + /// An I/O error occurred. public Npdm(Stream stream) { BinaryReader reader = new(stream); diff --git a/src/Ryujinx.HLE/Loaders/Npdm/ServiceAccessControl.cs b/src/Ryujinx.HLE/Loaders/Npdm/ServiceAccessControl.cs index bb6df27fa..b6bc6492d 100644 --- a/src/Ryujinx.HLE/Loaders/Npdm/ServiceAccessControl.cs +++ b/src/Ryujinx.HLE/Loaders/Npdm/ServiceAccessControl.cs @@ -9,6 +9,11 @@ namespace Ryujinx.HLE.Loaders.Npdm { public IReadOnlyDictionary Services { get; private set; } + /// The stream does not support reading, is , or is already closed. + /// An error occured while reading bytes from the stream. + /// The end of the stream is reached. + /// The stream is closed. + /// An I/O error occurred. public ServiceAccessControl(Stream stream, int offset, int size) { stream.Seek(offset, SeekOrigin.Begin); diff --git a/src/Ryujinx.HLE/Loaders/Processes/Extensions/NcaExtensions.cs b/src/Ryujinx.HLE/Loaders/Processes/Extensions/NcaExtensions.cs index da5637209..2928ac7fe 100644 --- a/src/Ryujinx.HLE/Loaders/Processes/Extensions/NcaExtensions.cs +++ b/src/Ryujinx.HLE/Loaders/Processes/Extensions/NcaExtensions.cs @@ -139,7 +139,7 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions ulong titleIdBase = mainNca.GetProgramIdBase(); // Load update information if exists. - string titleUpdateMetadataPath = Path.Combine(AppDataManager.GamesDirPath, mainNca.Header.TitleId.ToString("x16"), "updates.json"); + string titleUpdateMetadataPath = Path.Combine(AppDataManager.GamesDirPath, titleIdBase.ToString("x16"), "updates.json"); if (File.Exists(titleUpdateMetadataPath)) { updatePath = JsonHelper.DeserializeFromFile(titleUpdateMetadataPath, _applicationSerializerContext.TitleUpdateMetadata).Selected; diff --git a/src/Ryujinx.HLE/Loaders/Processes/Extensions/PartitionFileSystemExtensions.cs b/src/Ryujinx.HLE/Loaders/Processes/Extensions/PartitionFileSystemExtensions.cs index bee2572a8..b3590d9bd 100644 --- a/src/Ryujinx.HLE/Loaders/Processes/Extensions/PartitionFileSystemExtensions.cs +++ b/src/Ryujinx.HLE/Loaders/Processes/Extensions/PartitionFileSystemExtensions.cs @@ -118,7 +118,7 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions device.Configuration.ContentManager.ClearAocData(); // Load DownloadableContents. - string addOnContentMetadataPath = System.IO.Path.Combine(AppDataManager.GamesDirPath, mainNca.Header.TitleId.ToString("x16"), "dlc.json"); + string addOnContentMetadataPath = System.IO.Path.Combine(AppDataManager.GamesDirPath, mainNca.GetProgramIdBase().ToString("x16"), "dlc.json"); if (File.Exists(addOnContentMetadataPath)) { List dlcContainerList = JsonHelper.DeserializeFromFile(addOnContentMetadataPath, _contentSerializerContext.ListDownloadableContentContainer); diff --git a/src/Ryujinx.HLE/Ryujinx.HLE.csproj b/src/Ryujinx.HLE/Ryujinx.HLE.csproj index 0fcf9e4b5..a7bb3cd7f 100644 --- a/src/Ryujinx.HLE/Ryujinx.HLE.csproj +++ b/src/Ryujinx.HLE/Ryujinx.HLE.csproj @@ -2,6 +2,7 @@ net8.0 + true @@ -11,6 +12,7 @@ + @@ -24,8 +26,8 @@ - - + + diff --git a/src/Ryujinx.Headless.SDL2/Program.cs b/src/Ryujinx.Headless.SDL2/Program.cs index 85aff6712..07995dbdd 100644 --- a/src/Ryujinx.Headless.SDL2/Program.cs +++ b/src/Ryujinx.Headless.SDL2/Program.cs @@ -7,6 +7,7 @@ using Ryujinx.Common.Configuration.Hid; using Ryujinx.Common.Configuration.Hid.Controller; using Ryujinx.Common.Configuration.Hid.Controller.Motion; using Ryujinx.Common.Configuration.Hid.Keyboard; +using Ryujinx.Common.GraphicsDriver; using Ryujinx.Common.Logging; using Ryujinx.Common.Logging.Targets; using Ryujinx.Common.SystemInterop; @@ -18,6 +19,7 @@ using Ryujinx.Graphics.Gpu; using Ryujinx.Graphics.Gpu.Shader; using Ryujinx.Graphics.OpenGL; using Ryujinx.Graphics.Vulkan; +using Ryujinx.Graphics.Vulkan.MoltenVK; using Ryujinx.Headless.SDL2.OpenGL; using Ryujinx.Headless.SDL2.Vulkan; using Ryujinx.HLE; @@ -88,6 +90,11 @@ namespace Ryujinx.Headless.SDL2 }; } + if (OperatingSystem.IsMacOS()) + { + MVKInitialization.InitializeResolver(); + } + Parser.Default.ParseArguments(args) .WithParsed(Load) .WithNotParsed(errors => errors.Output()); @@ -457,6 +464,8 @@ namespace Ryujinx.Headless.SDL2 GraphicsConfig.ShadersDumpPath = option.GraphicsShadersDumpPath; GraphicsConfig.EnableMacroHLE = !option.DisableMacroHLE; + DriverUtilities.InitDriverConfig(option.BackendThreading == BackendThreading.Off); + while (true) { LoadApplication(option); diff --git a/src/Ryujinx.Horizon/Ngc/Ipc/Service.cs b/src/Ryujinx.Horizon/Ngc/Ipc/Service.cs index 828c09199..740f893f8 100644 --- a/src/Ryujinx.Horizon/Ngc/Ipc/Service.cs +++ b/src/Ryujinx.Horizon/Ngc/Ipc/Service.cs @@ -26,7 +26,11 @@ namespace Ryujinx.Horizon.Ngc.Ipc } [CmifCommand(1)] - public Result Check(out uint checkMask, ReadOnlySpan text, uint regionMask, ProfanityFilterOption option) + public Result Check( + out uint checkMask, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan text, + uint regionMask, + ProfanityFilterOption option) { lock (_profanityFilter) { diff --git a/src/Ryujinx.Horizon/Sdk/OsTypes/Impl/MultiWaitImpl.cs b/src/Ryujinx.Horizon/Sdk/OsTypes/Impl/MultiWaitImpl.cs index 2aefb0db5..406352003 100644 --- a/src/Ryujinx.Horizon/Sdk/OsTypes/Impl/MultiWaitImpl.cs +++ b/src/Ryujinx.Horizon/Sdk/OsTypes/Impl/MultiWaitImpl.cs @@ -21,6 +21,8 @@ namespace Ryujinx.Horizon.Sdk.OsTypes.Impl public long CurrentTime { get; private set; } + public IEnumerable MultiWaits => _multiWaits; + public MultiWaitImpl() { _multiWaits = new List(); diff --git a/src/Ryujinx.Horizon/Sdk/OsTypes/MultiWait.cs b/src/Ryujinx.Horizon/Sdk/OsTypes/MultiWait.cs index 0e73e3f88..41d17802a 100644 --- a/src/Ryujinx.Horizon/Sdk/OsTypes/MultiWait.cs +++ b/src/Ryujinx.Horizon/Sdk/OsTypes/MultiWait.cs @@ -1,4 +1,5 @@ using Ryujinx.Horizon.Sdk.OsTypes.Impl; +using System.Collections.Generic; namespace Ryujinx.Horizon.Sdk.OsTypes { @@ -6,6 +7,8 @@ namespace Ryujinx.Horizon.Sdk.OsTypes { private readonly MultiWaitImpl _impl; + public IEnumerable MultiWaits => _impl.MultiWaits; + public MultiWait() { _impl = new MultiWaitImpl(); diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Hipc/ServerManagerBase.cs b/src/Ryujinx.Horizon/Sdk/Sf/Hipc/ServerManagerBase.cs index 9886e1cbf..570e3c802 100644 --- a/src/Ryujinx.Horizon/Sdk/Sf/Hipc/ServerManagerBase.cs +++ b/src/Ryujinx.Horizon/Sdk/Sf/Hipc/ServerManagerBase.cs @@ -3,6 +3,7 @@ using Ryujinx.Horizon.Sdk.OsTypes; using Ryujinx.Horizon.Sdk.Sf.Cmif; using Ryujinx.Horizon.Sdk.Sm; using System; +using System.Linq; namespace Ryujinx.Horizon.Sdk.Sf.Hipc { @@ -116,6 +117,18 @@ namespace Ryujinx.Horizon.Sdk.Sf.Hipc while (WaitAndProcessRequestsImpl()) { } + + // Unlink pending sessions, dispose expects them to be already unlinked. + + ServerSession[] serverSessions = Enumerable.OfType(_multiWait.MultiWaits).ToArray(); + + foreach (ServerSession serverSession in serverSessions) + { + if (serverSession.IsLinked) + { + serverSession.UnlinkFromMultiWaitHolder(); + } + } } public void WaitAndProcessRequests() diff --git a/src/Ryujinx.Memory/Range/IMultiRangeItem.cs b/src/Ryujinx.Memory/Range/IMultiRangeItem.cs index 87fde2465..5f9611c75 100644 --- a/src/Ryujinx.Memory/Range/IMultiRangeItem.cs +++ b/src/Ryujinx.Memory/Range/IMultiRangeItem.cs @@ -4,6 +4,22 @@ namespace Ryujinx.Memory.Range { MultiRange Range { get; } - ulong BaseAddress => Range.GetSubRange(0).Address; + ulong BaseAddress + { + get + { + for (int index = 0; index < Range.Count; index++) + { + MemoryRange subRange = Range.GetSubRange(index); + + if (!MemoryRange.IsInvalid(ref subRange)) + { + return subRange.Address; + } + } + + return MemoryRange.InvalidAddress; + } + } } } diff --git a/src/Ryujinx.Memory/Range/MemoryRange.cs b/src/Ryujinx.Memory/Range/MemoryRange.cs index 46aca9ba0..20e9d00bb 100644 --- a/src/Ryujinx.Memory/Range/MemoryRange.cs +++ b/src/Ryujinx.Memory/Range/MemoryRange.cs @@ -5,6 +5,11 @@ namespace Ryujinx.Memory.Range /// public readonly record struct MemoryRange { + /// + /// Special address value used to indicate than an address is invalid. + /// + internal const ulong InvalidAddress = ulong.MaxValue; + /// /// An empty memory range, with a null address and zero size. /// @@ -58,13 +63,24 @@ namespace Ryujinx.Memory.Range return thisAddress < otherEndAddress && otherAddress < thisEndAddress; } + /// + /// Checks if a given sub-range of memory is invalid. + /// Those are used to represent unmapped memory regions (holes in the region mapping). + /// + /// Memory range to check + /// True if the memory range is considered invalid, false otherwise + internal static bool IsInvalid(ref MemoryRange subRange) + { + return subRange.Address == InvalidAddress; + } + /// /// Returns a string summary of the memory range. /// /// A string summary of the memory range public override string ToString() { - if (Address == ulong.MaxValue) + if (Address == InvalidAddress) { return $"[Unmapped 0x{Size:X}]"; } diff --git a/src/Ryujinx.Memory/Range/MultiRangeList.cs b/src/Ryujinx.Memory/Range/MultiRangeList.cs index 1804ff5c8..c3c6ae797 100644 --- a/src/Ryujinx.Memory/Range/MultiRangeList.cs +++ b/src/Ryujinx.Memory/Range/MultiRangeList.cs @@ -30,7 +30,7 @@ namespace Ryujinx.Memory.Range { var subrange = range.GetSubRange(i); - if (IsInvalid(ref subrange)) + if (MemoryRange.IsInvalid(ref subrange)) { continue; } @@ -56,7 +56,7 @@ namespace Ryujinx.Memory.Range { var subrange = range.GetSubRange(i); - if (IsInvalid(ref subrange)) + if (MemoryRange.IsInvalid(ref subrange)) { continue; } @@ -99,7 +99,7 @@ namespace Ryujinx.Memory.Range { var subrange = range.GetSubRange(i); - if (IsInvalid(ref subrange)) + if (MemoryRange.IsInvalid(ref subrange)) { continue; } @@ -142,17 +142,6 @@ namespace Ryujinx.Memory.Range return overlapCount; } - /// - /// Checks if a given sub-range of memory is invalid. - /// Those are used to represent unmapped memory regions (holes in the region mapping). - /// - /// Memory range to checl - /// True if the memory range is considered invalid, false otherwise - private static bool IsInvalid(ref MemoryRange subRange) - { - return subRange.Address == ulong.MaxValue; - } - /// /// Gets all items on the list starting at the specified memory address. /// diff --git a/src/Ryujinx.Memory/VirtualMemoryManagerBase.cs b/src/Ryujinx.Memory/VirtualMemoryManagerBase.cs index 506e25f66..f41072244 100644 --- a/src/Ryujinx.Memory/VirtualMemoryManagerBase.cs +++ b/src/Ryujinx.Memory/VirtualMemoryManagerBase.cs @@ -130,9 +130,9 @@ namespace Ryujinx.Memory } else { - IMemoryOwner memoryOwner = ByteMemoryPool.Rent(size); + MemoryOwner memoryOwner = MemoryOwner.Rent(size); - Read(va, memoryOwner.Memory.Span); + Read(va, memoryOwner.Span); return new WritableRegion(this, va, memoryOwner); } diff --git a/src/Ryujinx.Tests/Cpu/CpuTestAlu32.cs b/src/Ryujinx.Tests/Cpu/CpuTestAlu32.cs index 41365c624..132ddfd0e 100644 --- a/src/Ryujinx.Tests/Cpu/CpuTestAlu32.cs +++ b/src/Ryujinx.Tests/Cpu/CpuTestAlu32.cs @@ -25,6 +25,24 @@ namespace Ryujinx.Tests.Cpu }; } + private static uint[] UQAddSub16() + { + return new[] + { + 0xe6600f10u, // UQADD16 R0, R0, R0 + 0xe6600f70u, // UQSUB16 R0, R0, R0 + }; + } + + private static uint[] UQAddSub8() + { + return new[] + { + 0xe6600f90u, // UQADD8 R0, R0, R0 + 0xe6600ff0u, // UQSUB8 R0, R0, R0 + }; + } + private static uint[] SsatUsat() { return new[] @@ -182,6 +200,42 @@ namespace Ryujinx.Tests.Cpu CompareAgainstUnicorn(); } + [Test, Pairwise] + public void U_Q_AddSub_16([ValueSource(nameof(UQAddSub16))] uint opcode, + [Values(0u, 0xdu)] uint rd, + [Values(1u)] uint rm, + [Values(2u)] uint rn, + [Random(RndCnt)] uint w0, + [Random(RndCnt)] uint w1, + [Random(RndCnt)] uint w2) + { + opcode |= ((rm & 15) << 0) | ((rd & 15) << 12) | ((rn & 15) << 16); + + uint sp = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, r0: w0, r1: w1, r2: w2, sp: sp); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void U_Q_AddSub_8([ValueSource(nameof(UQAddSub8))] uint opcode, + [Values(0u, 0xdu)] uint rd, + [Values(1u)] uint rm, + [Values(2u)] uint rn, + [Random(RndCnt)] uint w0, + [Random(RndCnt)] uint w1, + [Random(RndCnt)] uint w2) + { + opcode |= ((rm & 15) << 0) | ((rd & 15) << 12) | ((rn & 15) << 16); + + uint sp = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, r0: w0, r1: w1, r2: w2, sp: sp); + + CompareAgainstUnicorn(); + } + [Test, Pairwise] public void Uadd8_Sel([Values(0u)] uint rd, [Values(1u)] uint rm, diff --git a/src/Ryujinx.Tests/Cpu/CpuTestSimd32.cs b/src/Ryujinx.Tests/Cpu/CpuTestSimd32.cs index 6087a6834..f843fd561 100644 --- a/src/Ryujinx.Tests/Cpu/CpuTestSimd32.cs +++ b/src/Ryujinx.Tests/Cpu/CpuTestSimd32.cs @@ -327,6 +327,32 @@ namespace Ryujinx.Tests.Cpu CompareAgainstUnicorn(); } + + [Test, Pairwise, Description("VSWP D0, D0")] + public void Vswp([Values(0u, 1u)] uint rd, + [Values(0u, 1u)] uint rm, + [Values] bool q) + { + uint opcode = 0xf3b20000u; // VSWP D0, D0 + + if (q) + { + opcode |= 1u << 6; + + rd &= ~1u; + rm &= ~1u; + } + + opcode |= ((rd & 0xf) << 12) | ((rd & 0x10) << 18); + opcode |= ((rm & 0xf) << 0) | ((rm & 0x10) << 1); + + V128 v0 = new(TestContext.CurrentContext.Random.NextULong(), TestContext.CurrentContext.Random.NextULong()); + V128 v1 = new(TestContext.CurrentContext.Random.NextULong(), TestContext.CurrentContext.Random.NextULong()); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } #endif } } diff --git a/src/Ryujinx.Tests/Cpu/CpuTestSimdReg32.cs b/src/Ryujinx.Tests/Cpu/CpuTestSimdReg32.cs index 38e08bf89..843273dc2 100644 --- a/src/Ryujinx.Tests/Cpu/CpuTestSimdReg32.cs +++ b/src/Ryujinx.Tests/Cpu/CpuTestSimdReg32.cs @@ -909,6 +909,39 @@ namespace Ryujinx.Tests.Cpu CompareAgainstUnicorn(); } + [Test, Pairwise, Description("VQRDMULH. , , ")] + public void Vqrdmulh_I([Range(0u, 5u)] uint rd, + [Range(0u, 5u)] uint rn, + [Range(0u, 5u)] uint rm, + [ValueSource(nameof(_8B4H2S1D_))] ulong z, + [ValueSource(nameof(_8B4H2S1D_))] ulong a, + [ValueSource(nameof(_8B4H2S1D_))] ulong b, + [Values(1u, 2u)] uint size) // + { + rd >>= 1; + rd <<= 1; + rn >>= 1; + rn <<= 1; + rm >>= 1; + rm <<= 1; + + uint opcode = 0xf3100b40u & ~(3u << 20); // VQRDMULH.S16 Q0, Q0, Q0 + + opcode |= ((rd & 0xf) << 12) | ((rd & 0x10) << 18); + opcode |= ((rn & 0xf) << 16) | ((rn & 0x10) << 3); + opcode |= ((rm & 0xf) << 0) | ((rm & 0x10) << 1); + + opcode |= (size & 0x3) << 20; + + V128 v0 = MakeVectorE0E1(z, ~z); + V128 v1 = MakeVectorE0E1(a, ~a); + V128 v2 = MakeVectorE0E1(b, ~b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + [Test, Pairwise] public void Vp_Add_Long_Accumulate([Values(0u, 2u, 4u, 8u)] uint rd, [Values(0u, 2u, 4u, 8u)] uint rm, diff --git a/src/Ryujinx.Tests/Cpu/CpuTestSimdShImm32.cs b/src/Ryujinx.Tests/Cpu/CpuTestSimdShImm32.cs index 39b50867f..7375f4d55 100644 --- a/src/Ryujinx.Tests/Cpu/CpuTestSimdShImm32.cs +++ b/src/Ryujinx.Tests/Cpu/CpuTestSimdShImm32.cs @@ -202,7 +202,7 @@ namespace Ryujinx.Tests.Cpu } [Test, Pairwise, Description("VSHL. {}, , #")] - public void Vshl_Imm([Values(0u)] uint rd, + public void Vshl_Imm([Values(0u, 1u)] uint rd, [Values(2u, 0u)] uint rm, [Values(0u, 1u, 2u, 3u)] uint size, [Random(RndCntShiftImm)] uint shiftImm, @@ -262,6 +262,40 @@ namespace Ryujinx.Tests.Cpu CompareAgainstUnicorn(); } + [Test, Pairwise, Description("VSLI. {}, , #")] + public void Vsli([Values(0u, 1u)] uint rd, + [Values(2u, 0u)] uint rm, + [Values(0u, 1u, 2u, 3u)] uint size, + [Random(RndCntShiftImm)] uint shiftImm, + [Random(RndCnt)] ulong z, + [Random(RndCnt)] ulong a, + [Random(RndCnt)] ulong b, + [Values] bool q) + { + uint opcode = 0xf3800510u; // VORR.I32 D0, #0x800000 (immediate value changes it into SLI) + if (q) + { + opcode |= 1 << 6; + rm <<= 1; + rd <<= 1; + } + + uint imm = 1u << ((int)size + 3); + imm |= shiftImm & (imm - 1); + + opcode |= ((rm & 0xf) << 0) | ((rm & 0x10) << 1); + opcode |= ((rd & 0xf) << 12) | ((rd & 0x10) << 18); + opcode |= ((imm & 0x3f) << 16) | ((imm & 0x40) << 1); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, z); + V128 v2 = MakeVectorE0E1(b, z); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + [Test, Pairwise] public void Vqshrn_Vqrshrn_Vrshrn_Imm([ValueSource(nameof(_Vqshrn_Vqrshrn_Vrshrn_Imm_))] uint opcode, [Values(0u, 1u)] uint rd, diff --git a/src/Ryujinx.UI.Common/App/ApplicationData.cs b/src/Ryujinx.UI.Common/App/ApplicationData.cs index 7108defc3..08bd2677d 100644 --- a/src/Ryujinx.UI.Common/App/ApplicationData.cs +++ b/src/Ryujinx.UI.Common/App/ApplicationData.cs @@ -42,6 +42,8 @@ namespace Ryujinx.UI.App.Common [JsonIgnore] public ulong IdBase => Id & ~0x1FFFUL; + [JsonIgnore] public string IdBaseString => IdBase.ToString("x16"); + public static string GetBuildId(VirtualFileSystem virtualFileSystem, IntegrityCheckLevel checkLevel, string titleFilePath) { using FileStream file = new(titleFilePath, FileMode.Open, FileAccess.Read); diff --git a/src/Ryujinx.UI.Common/App/ApplicationLibrary.cs b/src/Ryujinx.UI.Common/App/ApplicationLibrary.cs index 2baf06087..2defc1f6c 100644 --- a/src/Ryujinx.UI.Common/App/ApplicationLibrary.cs +++ b/src/Ryujinx.UI.Common/App/ApplicationLibrary.cs @@ -34,6 +34,7 @@ namespace Ryujinx.UI.App.Common { public class ApplicationLibrary { + public Language DesiredLanguage { get; set; } public event EventHandler ApplicationAdded; public event EventHandler ApplicationCountUpdated; @@ -45,7 +46,6 @@ namespace Ryujinx.UI.App.Common private readonly VirtualFileSystem _virtualFileSystem; private readonly IntegrityCheckLevel _checkLevel; - private Language _desiredTitleLanguage; private CancellationTokenSource _cancellationToken; private static readonly ApplicationJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions()); @@ -72,37 +72,43 @@ namespace Ryujinx.UI.App.Common return resourceByteArray; } + /// The npdm file doesn't contain valid data. + /// The FsAccessHeader.ContentOwnerId section is not implemented. + /// An error occured while reading bytes from the stream. + /// The end of the stream is reached. + /// An I/O error occurred. private ApplicationData GetApplicationFromExeFs(PartitionFileSystem pfs, string filePath) { ApplicationData data = new() { Icon = _nspIcon, + Path = filePath, }; using UniqueRef npdmFile = new(); - try + Result result = pfs.OpenFile(ref npdmFile.Ref, "/main.npdm".ToU8Span(), OpenMode.Read); + + if (ResultFs.PathNotFound.Includes(result)) { - Result result = pfs.OpenFile(ref npdmFile.Ref, "/main.npdm".ToU8Span(), OpenMode.Read); + Npdm npdm = new(npdmFile.Get.AsStream()); - if (ResultFs.PathNotFound.Includes(result)) - { - Npdm npdm = new(npdmFile.Get.AsStream()); - - data.Name = npdm.TitleName; - data.Id = npdm.Aci0.TitleId; - } - - return data; + data.Name = npdm.TitleName; + data.Id = npdm.Aci0.TitleId; } - catch (Exception exception) - { - Logger.Warning?.Print(LogClass.Application, $"The file encountered was not of a valid type. File: '{filePath}' Error: {exception.Message}"); - return null; - } + return data; } + /// The configured key set is missing a key. + /// The NCA header could not be decrypted. + /// The NCA version is not supported. + /// An error occured while reading PFS data. + /// The npdm file doesn't contain valid data. + /// The FsAccessHeader.ContentOwnerId section is not implemented. + /// An error occured while reading bytes from the stream. + /// The end of the stream is reached. + /// An I/O error occurred. private ApplicationData GetApplicationFromNsp(PartitionFileSystem pfs, string filePath) { bool isExeFs = false; @@ -170,99 +176,88 @@ namespace Ryujinx.UI.App.Common return null; } + /// The configured key set is missing a key. + /// The NCA header could not be decrypted. + /// The NCA version is not supported. + /// An error occured while reading PFS data. private List GetApplicationsFromPfs(IFileSystem pfs, string filePath) { var applications = new List(); string extension = Path.GetExtension(filePath).ToLower(); - try + foreach ((ulong titleId, ContentMetaData content) in pfs.GetContentData(ContentMetaType.Application, _virtualFileSystem, _checkLevel)) { - foreach ((ulong titleId, ContentMetaData content) in pfs.GetContentData(ContentMetaType.Application, _virtualFileSystem, _checkLevel)) + ApplicationData applicationData = new() { - ApplicationData applicationData = new() + Id = titleId, + Path = filePath, + }; + + Nca mainNca = content.GetNcaByType(_virtualFileSystem.KeySet, ContentType.Program); + Nca controlNca = content.GetNcaByType(_virtualFileSystem.KeySet, ContentType.Control); + + BlitStruct controlHolder = new(1); + + IFileSystem controlFs = controlNca?.OpenFileSystem(NcaSectionType.Data, _checkLevel); + + // Check if there is an update available. + if (IsUpdateApplied(mainNca, out IFileSystem updatedControlFs)) + { + // Replace the original ControlFs by the updated one. + controlFs = updatedControlFs; + } + + if (controlFs == null) + { + continue; + } + + ReadControlData(controlFs, controlHolder.ByteSpan); + + GetApplicationInformation(ref controlHolder.Value, ref applicationData); + + // Read the icon from the ControlFS and store it as a byte array + try + { + using UniqueRef icon = new(); + + controlFs.OpenFile(ref icon.Ref, $"/icon_{DesiredLanguage}.dat".ToU8Span(), OpenMode.Read).ThrowIfFailure(); + + using MemoryStream stream = new(); + + icon.Get.AsStream().CopyTo(stream); + applicationData.Icon = stream.ToArray(); + } + catch (HorizonResultException) + { + foreach (DirectoryEntryEx entry in controlFs.EnumerateEntries("/", "*")) { - Id = titleId, - Path = filePath, - }; + if (entry.Name == "control.nacp") + { + continue; + } - Nca mainNca = content.GetNcaByType(_virtualFileSystem.KeySet, ContentType.Program); - Nca controlNca = content.GetNcaByType(_virtualFileSystem.KeySet, ContentType.Control); + using var icon = new UniqueRef(); - BlitStruct controlHolder = new(1); - - IFileSystem controlFs = controlNca?.OpenFileSystem(NcaSectionType.Data, _checkLevel); - - // Check if there is an update available. - if (IsUpdateApplied(mainNca, out IFileSystem updatedControlFs)) - { - // Replace the original ControlFs by the updated one. - controlFs = updatedControlFs; - } - - if (controlFs == null) - { - continue; - } - - ReadControlData(controlFs, controlHolder.ByteSpan); - - GetApplicationInformation(ref controlHolder.Value, ref applicationData); - - // Read the icon from the ControlFS and store it as a byte array - try - { - using UniqueRef icon = new(); - - controlFs.OpenFile(ref icon.Ref, $"/icon_{_desiredTitleLanguage}.dat".ToU8Span(), OpenMode.Read).ThrowIfFailure(); + controlFs.OpenFile(ref icon.Ref, entry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); using MemoryStream stream = new(); icon.Get.AsStream().CopyTo(stream); applicationData.Icon = stream.ToArray(); - } - catch (HorizonResultException) - { - foreach (DirectoryEntryEx entry in controlFs.EnumerateEntries("/", "*")) + + if (applicationData.Icon != null) { - if (entry.Name == "control.nacp") - { - continue; - } - - using var icon = new UniqueRef(); - - controlFs.OpenFile(ref icon.Ref, entry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); - - using MemoryStream stream = new(); - - icon.Get.AsStream().CopyTo(stream); - applicationData.Icon = stream.ToArray(); - - if (applicationData.Icon != null) - { - break; - } + break; } - - applicationData.Icon ??= extension == ".xci" ? _xciIcon : _nspIcon; } - applicationData.ControlHolder = controlHolder; - - applications.Add(applicationData); + applicationData.Icon ??= extension == ".xci" ? _xciIcon : _nspIcon; } - } - catch (MissingKeyException exception) - { - Logger.Warning?.Print(LogClass.Application, $"Your key set is missing a key with the name: {exception.Name}"); - } - catch (InvalidDataException) - { - Logger.Warning?.Print(LogClass.Application, $"The header key is incorrect or missing and therefore the NCA header content type check has failed. Errored File: {filePath}"); - } - catch (Exception exception) - { - Logger.Warning?.Print(LogClass.Application, $"The file encountered was not of a valid type. File: '{filePath}' Error: {exception}"); + + applicationData.ControlHolder = controlHolder; + + applications.Add(applicationData); } return applications; @@ -271,8 +266,18 @@ namespace Ryujinx.UI.App.Common public bool TryGetApplicationsFromFile(string applicationPath, out List applications) { applications = []; + long fileSize; - long fileSize = new FileInfo(applicationPath).Length; + try + { + fileSize = new FileInfo(applicationPath).Length; + } + catch (FileNotFoundException) + { + Logger.Warning?.Print(LogClass.Application, $"The file was not found: '{applicationPath}'"); + + return false; + } BlitStruct controlHolder = new(1); @@ -319,52 +324,43 @@ namespace Ryujinx.UI.App.Common BinaryReader reader = new(file); ApplicationData application = new(); - try + file.Seek(24, SeekOrigin.Begin); + + int assetOffset = reader.ReadInt32(); + + if (Encoding.ASCII.GetString(Read(assetOffset, 4)) == "ASET") { - file.Seek(24, SeekOrigin.Begin); + byte[] iconSectionInfo = Read(assetOffset + 8, 0x10); - int assetOffset = reader.ReadInt32(); + long iconOffset = BitConverter.ToInt64(iconSectionInfo, 0); + long iconSize = BitConverter.ToInt64(iconSectionInfo, 8); - if (Encoding.ASCII.GetString(Read(assetOffset, 4)) == "ASET") + ulong nacpOffset = reader.ReadUInt64(); + ulong nacpSize = reader.ReadUInt64(); + + // Reads and stores game icon as byte array + if (iconSize > 0) { - byte[] iconSectionInfo = Read(assetOffset + 8, 0x10); - - long iconOffset = BitConverter.ToInt64(iconSectionInfo, 0); - long iconSize = BitConverter.ToInt64(iconSectionInfo, 8); - - ulong nacpOffset = reader.ReadUInt64(); - ulong nacpSize = reader.ReadUInt64(); - - // Reads and stores game icon as byte array - if (iconSize > 0) - { - application.Icon = Read(assetOffset + iconOffset, (int)iconSize); - } - else - { - application.Icon = _nroIcon; - } - - // Read the NACP data - Read(assetOffset + (int)nacpOffset, (int)nacpSize).AsSpan().CopyTo(controlHolder.ByteSpan); - - GetApplicationInformation(ref controlHolder.Value, ref application); + application.Icon = Read(assetOffset + iconOffset, (int)iconSize); } else { application.Icon = _nroIcon; - application.Name = Path.GetFileNameWithoutExtension(applicationPath); } - application.ControlHolder = controlHolder; - applications.Add(application); - } - catch - { - Logger.Warning?.Print(LogClass.Application, $"The file encountered was not of a valid type. Errored File: {applicationPath}"); + // Read the NACP data + Read(assetOffset + (int)nacpOffset, (int)nacpSize).AsSpan().CopyTo(controlHolder.ByteSpan); - return false; + GetApplicationInformation(ref controlHolder.Value, ref application); } + else + { + application.Icon = _nroIcon; + application.Name = Path.GetFileNameWithoutExtension(applicationPath); + } + + application.ControlHolder = controlHolder; + applications.Add(application); break; @@ -377,34 +373,21 @@ namespace Ryujinx.UI.App.Common } case ".nca": { - try + ApplicationData application = new(); + + Nca nca = new(_virtualFileSystem.KeySet, new FileStream(applicationPath, FileMode.Open, FileAccess.Read).AsStorage()); + + if (!nca.IsProgram() || nca.IsPatch()) { - ApplicationData application = new(); - - Nca nca = new(_virtualFileSystem.KeySet, new FileStream(applicationPath, FileMode.Open, FileAccess.Read).AsStorage()); - - if (!nca.IsProgram() || nca.IsPatch()) - { - return false; - } - - application.Icon = _ncaIcon; - application.Name = Path.GetFileNameWithoutExtension(applicationPath); - application.ControlHolder = controlHolder; - - applications.Add(application); - } - catch (InvalidDataException) - { - Logger.Warning?.Print(LogClass.Application, $"The NCA header content type check has failed. This is usually because the header key is incorrect or missing. Errored File: {applicationPath}"); - } - catch - { - Logger.Warning?.Print(LogClass.Application, $"The file encountered was not of a valid type. Errored File: {applicationPath}"); - return false; } + application.Icon = _ncaIcon; + application.Name = Path.GetFileNameWithoutExtension(applicationPath); + application.ControlHolder = controlHolder; + + applications.Add(application); + break; } // If its an NSO we just set defaults @@ -417,48 +400,72 @@ namespace Ryujinx.UI.App.Common }; applications.Add(application); + break; } } } + catch (MissingKeyException exception) + { + Logger.Warning?.Print(LogClass.Application, $"Your key set is missing a key with the name: {exception.Name}"); + + return false; + } + catch (InvalidDataException) + { + Logger.Warning?.Print(LogClass.Application, $"The header key is incorrect or missing and therefore the NCA header content type check has failed. Errored File: {applicationPath}"); + + return false; + } catch (IOException exception) { Logger.Warning?.Print(LogClass.Application, exception.Message); return false; } + catch (Exception exception) + { + Logger.Warning?.Print(LogClass.Application, $"The file encountered was not of a valid type. File: '{applicationPath}' Error: {exception}"); + + return false; + } foreach (var data in applications) { - ApplicationMetadata appMetadata = LoadAndSaveMetaData(data.IdString, appMetadata => + // Only load metadata for applications with an ID + if (data.Id != 0) { - appMetadata.Title = data.Name; - - // Only do the migration if time_played has a value and timespan_played hasn't been updated yet. - if (appMetadata.TimePlayedOld != default && appMetadata.TimePlayed == TimeSpan.Zero) + ApplicationMetadata appMetadata = LoadAndSaveMetaData(data.IdString, appMetadata => { - appMetadata.TimePlayed = TimeSpan.FromSeconds(appMetadata.TimePlayedOld); - appMetadata.TimePlayedOld = default; - } + appMetadata.Title = data.Name; - // Only do the migration if last_played has a value and last_played_utc doesn't exist yet. - if (appMetadata.LastPlayedOld != default && !appMetadata.LastPlayed.HasValue) - { - // Migrate from string-based last_played to DateTime-based last_played_utc. - if (DateTime.TryParse(appMetadata.LastPlayedOld, out DateTime lastPlayedOldParsed)) + // Only do the migration if time_played has a value and timespan_played hasn't been updated yet. + if (appMetadata.TimePlayedOld != default && appMetadata.TimePlayed == TimeSpan.Zero) { - appMetadata.LastPlayed = lastPlayedOldParsed; - - // Migration successful: deleting last_played from the metadata file. - appMetadata.LastPlayedOld = default; + appMetadata.TimePlayed = TimeSpan.FromSeconds(appMetadata.TimePlayedOld); + appMetadata.TimePlayedOld = default; } - } - }); + // Only do the migration if last_played has a value and last_played_utc doesn't exist yet. + if (appMetadata.LastPlayedOld != default && !appMetadata.LastPlayed.HasValue) + { + // Migrate from string-based last_played to DateTime-based last_played_utc. + if (DateTime.TryParse(appMetadata.LastPlayedOld, out DateTime lastPlayedOldParsed)) + { + appMetadata.LastPlayed = lastPlayedOldParsed; + + // Migration successful: deleting last_played from the metadata file. + appMetadata.LastPlayedOld = default; + } + + } + }); + + data.Favorite = appMetadata.Favorite; + data.TimePlayed = appMetadata.TimePlayed; + data.LastPlayed = appMetadata.LastPlayed; + } - data.Favorite = appMetadata.Favorite; - data.TimePlayed = appMetadata.TimePlayed; - data.LastPlayed = appMetadata.LastPlayed; data.FileExtension = Path.GetExtension(applicationPath).TrimStart('.').ToUpper(); data.FileSize = fileSize; data.Path = applicationPath; @@ -480,13 +487,11 @@ namespace Ryujinx.UI.App.Common controlFile.Get.Read(out _, 0, outProperty, ReadOption.None).ThrowIfFailure(); } - public void LoadApplications(List appDirs, Language desiredTitleLanguage) + public void LoadApplications(List appDirs) { int numApplicationsFound = 0; int numApplicationsLoaded = 0; - _desiredTitleLanguage = desiredTitleLanguage; - _cancellationToken = new CancellationTokenSource(); // Builds the applications list with paths to found applications @@ -510,7 +515,13 @@ namespace Ryujinx.UI.App.Common try { - IEnumerable files = Directory.EnumerateFiles(appDir, "*", SearchOption.AllDirectories).Where(file => + EnumerationOptions options = new() + { + RecurseSubdirectories = true, + IgnoreInaccessible = false, + }; + + IEnumerable files = Directory.EnumerateFiles(appDir, "*", options).Where(file => { return (Path.GetExtension(file).ToLower() is ".nsp" && ConfigurationState.Instance.UI.ShownFileTypes.NSP.Value) || @@ -529,14 +540,18 @@ namespace Ryujinx.UI.App.Common } var fileInfo = new FileInfo(app); - string extension = fileInfo.Extension.ToLower(); - if (!fileInfo.Attributes.HasFlag(FileAttributes.Hidden) && extension is ".nsp" or ".pfs0" or ".xci" or ".nca" or ".nro" or ".nso") + try { var fullPath = fileInfo.ResolveLinkTarget(true)?.FullName ?? fileInfo.FullName; + applicationPaths.Add(fullPath); numApplicationsFound++; } + catch (IOException exception) + { + Logger.Warning?.Print(LogClass.Application, $"Failed to resolve the full path to file: \"{app}\" Error: {exception}"); + } } } catch (UnauthorizedAccessException) @@ -835,7 +850,7 @@ namespace Ryujinx.UI.App.Common private void GetApplicationInformation(ref ApplicationControlProperty controlData, ref ApplicationData data) { - _ = Enum.TryParse(_desiredTitleLanguage.ToString(), out TitleLanguage desiredTitleLanguage); + _ = Enum.TryParse(DesiredLanguage.ToString(), out TitleLanguage desiredTitleLanguage); if (controlData.Title.ItemsRo.Length > (int)desiredTitleLanguage) { diff --git a/src/Ryujinx.UI.Common/Helper/ShortcutHelper.cs b/src/Ryujinx.UI.Common/Helper/ShortcutHelper.cs index 58bdc90e6..1849f40cb 100644 --- a/src/Ryujinx.UI.Common/Helper/ShortcutHelper.cs +++ b/src/Ryujinx.UI.Common/Helper/ShortcutHelper.cs @@ -1,10 +1,7 @@ using Ryujinx.Common; using Ryujinx.Common.Configuration; using ShellLink; -using SixLabors.ImageSharp; -using SixLabors.ImageSharp.Formats.Png; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; +using SkiaSharp; using System; using System.Collections.Generic; using System.IO; @@ -21,8 +18,8 @@ namespace Ryujinx.UI.Common.Helper iconPath += ".ico"; MemoryStream iconDataStream = new(iconData); - var image = Image.Load(iconDataStream); - image.Mutate(x => x.Resize(128, 128)); + using var image = SKBitmap.Decode(iconDataStream); + image.Resize(new SKImageInfo(128, 128), SKFilterQuality.High); SaveBitmapAsIcon(image, iconPath); var shortcut = Shortcut.CreateShortcut(basePath, GetArgsString(applicationFilePath, applicationId), iconPath, 0); @@ -37,8 +34,10 @@ namespace Ryujinx.UI.Common.Helper var desktopFile = EmbeddedResources.ReadAllText("Ryujinx.UI.Common/shortcut-template.desktop"); iconPath += ".png"; - var image = Image.Load(iconData); - image.SaveAsPng(iconPath); + var image = SKBitmap.Decode(iconData); + using var data = image.Encode(SKEncodedImageFormat.Png, 100); + using var file = File.OpenWrite(iconPath); + data.SaveTo(file); using StreamWriter outputFile = new(Path.Combine(desktopPath, cleanedAppName + ".desktop")); outputFile.Write(desktopFile, cleanedAppName, iconPath, $"{basePath} {GetArgsString(applicationFilePath, applicationId)}"); @@ -78,8 +77,10 @@ namespace Ryujinx.UI.Common.Helper } const string IconName = "icon.png"; - var image = Image.Load(iconData); - image.SaveAsPng(Path.Combine(resourceFolderPath, IconName)); + var image = SKBitmap.Decode(iconData); + using var data = image.Encode(SKEncodedImageFormat.Png, 100); + using var file = File.OpenWrite(Path.Combine(resourceFolderPath, IconName)); + data.SaveTo(file); // plist file using StreamWriter outputFile = new(Path.Combine(contentFolderPath, "Info.plist")); @@ -148,7 +149,7 @@ namespace Ryujinx.UI.Common.Helper /// The source bitmap image that will be saved as an .ico file /// The location that the new .ico file will be saved too (Make sure to include '.ico' in the path). [SupportedOSPlatform("windows")] - private static void SaveBitmapAsIcon(Image source, string filePath) + private static void SaveBitmapAsIcon(SKBitmap source, string filePath) { // Code Modified From https://stackoverflow.com/a/11448060/368354 by Benlitz byte[] header = { 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 32, 0, 0, 0, 0, 0, 22, 0, 0, 0 }; @@ -156,13 +157,16 @@ namespace Ryujinx.UI.Common.Helper fs.Write(header); // Writing actual data - source.Save(fs, PngFormat.Instance); + using var data = source.Encode(SKEncodedImageFormat.Png, 100); + data.SaveTo(fs); // Getting data length (file length minus header) long dataLength = fs.Length - header.Length; // Write it in the correct place fs.Seek(14, SeekOrigin.Begin); fs.WriteByte((byte)dataLength); fs.WriteByte((byte)(dataLength >> 8)); + fs.WriteByte((byte)(dataLength >> 16)); + fs.WriteByte((byte)(dataLength >> 24)); } } } diff --git a/src/Ryujinx/AppHost.cs b/src/Ryujinx/AppHost.cs index 8c643f340..0db8ef414 100644 --- a/src/Ryujinx/AppHost.cs +++ b/src/Ryujinx/AppHost.cs @@ -367,32 +367,24 @@ namespace Ryujinx.Ava } var colorType = e.IsBgra ? SKColorType.Bgra8888 : SKColorType.Rgba8888; - using var bitmap = new SKBitmap(new SKImageInfo(e.Width, e.Height, colorType, SKAlphaType.Premul)); + using SKBitmap bitmap = new SKBitmap(new SKImageInfo(e.Width, e.Height, colorType, SKAlphaType.Premul)); Marshal.Copy(e.Data, 0, bitmap.GetPixels(), e.Data.Length); - SKBitmap bitmapToSave = null; + using SKBitmap bitmapToSave = new SKBitmap(bitmap.Width, bitmap.Height); + using SKCanvas canvas = new SKCanvas(bitmapToSave); - if (e.FlipX || e.FlipY) - { - bitmapToSave = new SKBitmap(bitmap.Width, bitmap.Height); + canvas.Clear(SKColors.Black); - using var canvas = new SKCanvas(bitmapToSave); + float scaleX = e.FlipX ? -1 : 1; + float scaleY = e.FlipY ? -1 : 1; - canvas.Clear(SKColors.Transparent); + var matrix = SKMatrix.CreateScale(scaleX, scaleY, bitmap.Width / 2f, bitmap.Height / 2f); - float scaleX = e.FlipX ? -1 : 1; - float scaleY = e.FlipY ? -1 : 1; + canvas.SetMatrix(matrix); + canvas.DrawBitmap(bitmap, SKPoint.Empty); - var matrix = SKMatrix.CreateScale(scaleX, scaleY, bitmap.Width / 2f, bitmap.Height / 2f); - - canvas.SetMatrix(matrix); - - canvas.DrawBitmap(bitmap, new SKPoint(e.FlipX ? -bitmap.Width : 0, e.FlipY ? -bitmap.Height : 0)); - } - - SaveBitmapAsPng(bitmapToSave ?? bitmap, path); - bitmapToSave?.Dispose(); + SaveBitmapAsPng(bitmapToSave, path); Logger.Notice.Print(LogClass.Application, $"Screenshot saved to {path}", "Screenshot"); } diff --git a/src/Ryujinx/Common/Locale/LocaleExtension.cs b/src/Ryujinx/Common/Locale/LocaleExtension.cs index 40661bf3a..b5964aa85 100644 --- a/src/Ryujinx/Common/Locale/LocaleExtension.cs +++ b/src/Ryujinx/Common/Locale/LocaleExtension.cs @@ -21,7 +21,7 @@ namespace Ryujinx.Ava.Common.Locale var builder = new CompiledBindingPathBuilder(); - builder.SetRawSource(LocaleManager.Instance) + builder .Property(new ClrPropertyInfo("Item", obj => (LocaleManager.Instance[keyToUse]), null, @@ -32,7 +32,10 @@ namespace Ryujinx.Ava.Common.Locale var path = builder.Build(); - var binding = new CompiledBindingExtension(path); + var binding = new CompiledBindingExtension(path) + { + Source = LocaleManager.Instance + }; return binding.ProvideValue(serviceProvider); } diff --git a/src/Ryujinx/Common/Locale/LocaleManager.cs b/src/Ryujinx/Common/Locale/LocaleManager.cs index 257611e65..96f648761 100644 --- a/src/Ryujinx/Common/Locale/LocaleManager.cs +++ b/src/Ryujinx/Common/Locale/LocaleManager.cs @@ -139,9 +139,11 @@ namespace Ryujinx.Ava.Common.Locale foreach (var item in locale) { - this[item.Key] = item.Value; + _localeStrings[item.Key] = item.Value; } + OnPropertyChanged("Item"); + LocaleChanged?.Invoke(); } diff --git a/src/Ryujinx/Program.cs b/src/Ryujinx/Program.cs index 976963422..6c83cedcf 100644 --- a/src/Ryujinx/Program.cs +++ b/src/Ryujinx/Program.cs @@ -7,6 +7,7 @@ using Ryujinx.Common.Configuration; using Ryujinx.Common.GraphicsDriver; using Ryujinx.Common.Logging; using Ryujinx.Common.SystemInterop; +using Ryujinx.Graphics.Vulkan.MoltenVK; using Ryujinx.Modules; using Ryujinx.SDL2.Common; using Ryujinx.UI.Common; @@ -80,6 +81,11 @@ namespace Ryujinx.Ava // Parse arguments CommandLineState.ParseArguments(args); + if (OperatingSystem.IsMacOS()) + { + MVKInitialization.InitializeResolver(); + } + // Delete backup files after updating. Task.Run(Updater.CleanupUpdate); @@ -111,8 +117,8 @@ namespace Ryujinx.Ava // Logging system information. PrintSystemInfo(); - // Enable OGL multithreading on the driver, when available. - DriverUtilities.ToggleOGLThreading(ConfigurationState.Instance.Graphics.BackendThreading == BackendThreading.Off); + // Enable OGL multithreading on the driver, and some other flags. + DriverUtilities.InitDriverConfig(ConfigurationState.Instance.Graphics.BackendThreading == BackendThreading.Off); // Check if keys exists. if (!File.Exists(Path.Combine(AppDataManager.KeysDirPath, "prod.keys"))) diff --git a/src/Ryujinx/UI/Applet/AvaloniaDynamicTextInputHandler.cs b/src/Ryujinx/UI/Applet/AvaloniaDynamicTextInputHandler.cs index 531d00611..0e7cfb8e6 100644 --- a/src/Ryujinx/UI/Applet/AvaloniaDynamicTextInputHandler.cs +++ b/src/Ryujinx/UI/Applet/AvaloniaDynamicTextInputHandler.cs @@ -41,17 +41,12 @@ namespace Ryujinx.Ava.UI.Applet private void TextChanged(string text) { - TextChangedEvent?.Invoke(text ?? string.Empty, _hiddenTextBox.SelectionStart, _hiddenTextBox.SelectionEnd, true); + TextChangedEvent?.Invoke(text ?? string.Empty, _hiddenTextBox.SelectionStart, _hiddenTextBox.SelectionEnd, false); } private void SelectionChanged(int selection) { - if (_hiddenTextBox.SelectionEnd < _hiddenTextBox.SelectionStart) - { - _hiddenTextBox.SelectionStart = _hiddenTextBox.SelectionEnd; - } - - TextChangedEvent?.Invoke(_hiddenTextBox.Text ?? string.Empty, _hiddenTextBox.SelectionStart, _hiddenTextBox.SelectionEnd, true); + TextChangedEvent?.Invoke(_hiddenTextBox.Text ?? string.Empty, _hiddenTextBox.SelectionStart, _hiddenTextBox.SelectionEnd, false); } private void AvaloniaDynamicTextInputHandler_TextInput(object sender, string text) diff --git a/src/Ryujinx/UI/Helpers/OffscreenTextBox.cs b/src/Ryujinx/UI/Helpers/OffscreenTextBox.cs index a055f3353..dd736037e 100644 --- a/src/Ryujinx/UI/Helpers/OffscreenTextBox.cs +++ b/src/Ryujinx/UI/Helpers/OffscreenTextBox.cs @@ -1,11 +1,14 @@ using Avalonia.Controls; using Avalonia.Input; using Avalonia.Interactivity; +using System; namespace Ryujinx.Ava.UI.Helpers { public class OffscreenTextBox : TextBox { + protected override Type StyleKeyOverride => typeof(TextBox); + public static RoutedEvent GetKeyDownRoutedEvent() { return KeyDownEvent; diff --git a/src/Ryujinx/UI/Models/TitleUpdateModel.cs b/src/Ryujinx/UI/Models/TitleUpdateModel.cs index cde37bf91..46f6f46d8 100644 --- a/src/Ryujinx/UI/Models/TitleUpdateModel.cs +++ b/src/Ryujinx/UI/Models/TitleUpdateModel.cs @@ -1,21 +1,20 @@ -using LibHac.Ns; using Ryujinx.Ava.Common.Locale; namespace Ryujinx.Ava.UI.Models { public class TitleUpdateModel { - public ApplicationControlProperty Control { get; } + public uint Version { get; } public string Path { get; } + public string Label { get; } - public string Label => LocaleManager.Instance.UpdateAndGetDynamicValue( - System.IO.Path.GetExtension(Path)?.ToLower() == ".xci" ? LocaleKeys.TitleBundledUpdateVersionLabel : LocaleKeys.TitleUpdateVersionLabel, - Control.DisplayVersionString.ToString() - ); - - public TitleUpdateModel(ApplicationControlProperty control, string path) + public TitleUpdateModel(uint version, string displayVersion, string path) { - Control = control; + Version = version; + Label = LocaleManager.Instance.UpdateAndGetDynamicValue( + System.IO.Path.GetExtension(path)?.ToLower() == ".xci" ? LocaleKeys.TitleBundledUpdateVersionLabel : LocaleKeys.TitleUpdateVersionLabel, + displayVersion + ); Path = path; } } diff --git a/src/Ryujinx/UI/ViewModels/AppListFavoriteComparable.cs b/src/Ryujinx/UI/ViewModels/AppListFavoriteComparable.cs new file mode 100644 index 000000000..e80984508 --- /dev/null +++ b/src/Ryujinx/UI/ViewModels/AppListFavoriteComparable.cs @@ -0,0 +1,43 @@ +using Ryujinx.UI.App.Common; +using System; + +namespace Ryujinx.Ava.UI.ViewModels +{ + /// + /// Implements a custom comparer which is used for sorting titles by favorite on a UI. + /// Returns a sorted list of favorites in alphabetical order, followed by all non-favorites sorted alphabetical. + /// + public readonly struct AppListFavoriteComparable : IComparable + { + /// + /// The application data being compared. + /// + private readonly ApplicationData app; + + /// + /// Constructs a new with the specified application data. + /// + /// The app data being compared. + public AppListFavoriteComparable(ApplicationData app) + { + ArgumentNullException.ThrowIfNull(app, nameof(app)); + this.app = app; + } + + /// + public readonly int CompareTo(object o) + { + if (o is AppListFavoriteComparable other) + { + if (app.Favorite == other.app.Favorite) + { + return string.Compare(app.Name, other.app.Name, StringComparison.OrdinalIgnoreCase); + } + + return app.Favorite ? -1 : 1; + } + + throw new InvalidCastException($"Cannot cast {o.GetType()} to {nameof(AppListFavoriteComparable)}"); + } + } +} diff --git a/src/Ryujinx/UI/ViewModels/DownloadableContentManagerViewModel.cs b/src/Ryujinx/UI/ViewModels/DownloadableContentManagerViewModel.cs index 0f500513a..c919a7ad1 100644 --- a/src/Ryujinx/UI/ViewModels/DownloadableContentManagerViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/DownloadableContentManagerViewModel.cs @@ -103,7 +103,7 @@ namespace Ryujinx.Ava.UI.ViewModels _storageProvider = desktop.MainWindow.StorageProvider; } - _downloadableContentJsonPath = Path.Combine(AppDataManager.GamesDirPath, applicationData.IdString, "dlc.json"); + _downloadableContentJsonPath = Path.Combine(AppDataManager.GamesDirPath, applicationData.IdBaseString, "dlc.json"); if (!File.Exists(_downloadableContentJsonPath)) { @@ -263,7 +263,7 @@ namespace Ryujinx.Ava.UI.ViewModels var content = new DownloadableContentModel(nca.Header.TitleId.ToString("X16"), path, fileEntry.FullPath, true); DownloadableContents.Add(content); - SelectedDownloadableContents.Add(content); + Dispatcher.UIThread.InvokeAsync(() => SelectedDownloadableContents.Add(content)); success = true; } diff --git a/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs index 134e90300..bd9f165b9 100644 --- a/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs @@ -965,8 +965,8 @@ namespace Ryujinx.Ava.UI.ViewModels : SortExpressionComparer.Descending(app => app.FileSize), ApplicationSort.Path => IsAscending ? SortExpressionComparer.Ascending(app => app.Path) : SortExpressionComparer.Descending(app => app.Path), - ApplicationSort.Favorite => !IsAscending ? SortExpressionComparer.Ascending(app => app.Favorite) - : SortExpressionComparer.Descending(app => app.Favorite), + ApplicationSort.Favorite => IsAscending ? SortExpressionComparer.Ascending(app => new AppListFavoriteComparable(app)) + : SortExpressionComparer.Descending(app => new AppListFavoriteComparable(app)), _ => null, #pragma warning restore IDE0055 }; diff --git a/src/Ryujinx/UI/ViewModels/TitleUpdateViewModel.cs b/src/Ryujinx/UI/ViewModels/TitleUpdateViewModel.cs index 6c38edb37..e9b39dfe1 100644 --- a/src/Ryujinx/UI/ViewModels/TitleUpdateViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/TitleUpdateViewModel.cs @@ -88,7 +88,7 @@ namespace Ryujinx.Ava.UI.ViewModels StorageProvider = desktop.MainWindow.StorageProvider; } - TitleUpdateJsonPath = Path.Combine(AppDataManager.GamesDirPath, ApplicationData.IdString, "updates.json"); + TitleUpdateJsonPath = Path.Combine(AppDataManager.GamesDirPath, ApplicationData.IdBaseString, "updates.json"); try { @@ -96,7 +96,7 @@ namespace Ryujinx.Ava.UI.ViewModels } catch { - Logger.Warning?.Print(LogClass.Application, $"Failed to deserialize title update data for {ApplicationData.IdString} at {TitleUpdateJsonPath}"); + Logger.Warning?.Print(LogClass.Application, $"Failed to deserialize title update data for {ApplicationData.IdBaseString} at {TitleUpdateJsonPath}"); TitleUpdateWindowData = new TitleUpdateMetadata { @@ -131,26 +131,11 @@ namespace Ryujinx.Ava.UI.ViewModels public void SortUpdates() { - var list = TitleUpdates.ToList(); - - list.Sort((first, second) => - { - if (string.IsNullOrEmpty(first.Control.DisplayVersionString.ToString())) - { - return -1; - } - - if (string.IsNullOrEmpty(second.Control.DisplayVersionString.ToString())) - { - return 1; - } - - return Version.Parse(first.Control.DisplayVersionString.ToString()).CompareTo(Version.Parse(second.Control.DisplayVersionString.ToString())) * -1; - }); + var sortedUpdates = TitleUpdates.OrderByDescending(update => update.Version); Views.Clear(); Views.Add(new BaseModel()); - Views.AddRange(list); + Views.AddRange(sortedUpdates); if (SelectedUpdate == null) { @@ -169,7 +154,7 @@ namespace Ryujinx.Ava.UI.ViewModels } } - private void AddUpdate(string path, bool ignoreNotFound = false) + private void AddUpdate(string path, bool ignoreNotFound = false, bool selected = false) { if (!File.Exists(path) || TitleUpdates.Any(x => x.Path == path)) { @@ -204,7 +189,15 @@ namespace Ryujinx.Ava.UI.ViewModels controlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None).OpenFile(ref nacpFile.Ref, "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure(); nacpFile.Get.Read(out _, 0, SpanHelpers.AsByteSpan(ref controlData), ReadOption.None).ThrowIfFailure(); - TitleUpdates.Add(new TitleUpdateModel(controlData, path)); + var displayVersion = controlData.DisplayVersionString.ToString(); + var update = new TitleUpdateModel(content.Version.Version, displayVersion, path); + + TitleUpdates.Add(update); + + if (selected) + { + Dispatcher.UIThread.InvokeAsync(() => SelectedUpdate = update); + } } else { @@ -245,7 +238,7 @@ namespace Ryujinx.Ava.UI.ViewModels foreach (var file in result) { - AddUpdate(file.Path.LocalPath); + AddUpdate(file.Path.LocalPath, selected: true); } SortUpdates(); diff --git a/src/Ryujinx/UI/ViewModels/UserProfileViewModel.cs b/src/Ryujinx/UI/ViewModels/UserProfileViewModel.cs index 70274847f..0b65e6d13 100644 --- a/src/Ryujinx/UI/ViewModels/UserProfileViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/UserProfileViewModel.cs @@ -1,7 +1,7 @@ -using Microsoft.IdentityModel.Tokens; using Ryujinx.Ava.UI.Models; using System; using System.Collections.ObjectModel; +using System.Linq; namespace Ryujinx.Ava.UI.ViewModels { @@ -11,7 +11,7 @@ namespace Ryujinx.Ava.UI.ViewModels { Profiles = new ObservableCollection(); LostProfiles = new ObservableCollection(); - IsEmpty = LostProfiles.IsNullOrEmpty(); + IsEmpty = !LostProfiles.Any(); } public ObservableCollection Profiles { get; set; } diff --git a/src/Ryujinx/UI/Windows/DownloadableContentManagerWindow.axaml.cs b/src/Ryujinx/UI/Windows/DownloadableContentManagerWindow.axaml.cs index 72cd96319..ab04fd68f 100644 --- a/src/Ryujinx/UI/Windows/DownloadableContentManagerWindow.axaml.cs +++ b/src/Ryujinx/UI/Windows/DownloadableContentManagerWindow.axaml.cs @@ -38,7 +38,7 @@ namespace Ryujinx.Ava.UI.Windows SecondaryButtonText = "", CloseButtonText = "", Content = new DownloadableContentManagerWindow(virtualFileSystem, applicationData), - Title = string.Format(LocaleManager.Instance[LocaleKeys.DlcWindowTitle], applicationData.Name, applicationData.IdString), + Title = string.Format(LocaleManager.Instance[LocaleKeys.DlcWindowTitle], applicationData.Name, applicationData.IdBaseString), }; Style bottomBorder = new(x => x.OfType().Name("DialogSpace").Child().OfType()); diff --git a/src/Ryujinx/UI/Windows/MainWindow.axaml b/src/Ryujinx/UI/Windows/MainWindow.axaml index 6c2042f93..3a2e02c26 100644 --- a/src/Ryujinx/UI/Windows/MainWindow.axaml +++ b/src/Ryujinx/UI/Windows/MainWindow.axaml @@ -42,12 +42,10 @@ - - + diff --git a/src/Ryujinx/UI/Windows/MainWindow.axaml.cs b/src/Ryujinx/UI/Windows/MainWindow.axaml.cs index dc5336ab3..348412e78 100644 --- a/src/Ryujinx/UI/Windows/MainWindow.axaml.cs +++ b/src/Ryujinx/UI/Windows/MainWindow.axaml.cs @@ -37,6 +37,7 @@ namespace Ryujinx.Ava.UI.Windows internal static MainWindowViewModel MainWindowViewModel { get; private set; } private bool _isLoading; + private bool _applicationsLoadedOnce; private UserChannelPersistence _userChannelPersistence; private static bool _deferLoad; @@ -224,7 +225,10 @@ namespace Ryujinx.Ava.UI.Windows ? IntegrityCheckLevel.ErrorOnInvalid : IntegrityCheckLevel.None; - ApplicationLibrary = new ApplicationLibrary(VirtualFileSystem, checkLevel); + ApplicationLibrary = new ApplicationLibrary(VirtualFileSystem, checkLevel) + { + DesiredLanguage = ConfigurationState.Instance.System.Language, + }; // Save data created before we supported extra data in directory save data will not work properly if // given empty extra data. Luckily some of that extra data can be created using the data from the @@ -472,7 +476,11 @@ namespace Ryujinx.Ava.UI.Windows ViewModel.RefreshFirmwareStatus(); - LoadApplications(); + // Load applications if no application was requested by the command line + if (!_deferLoad) + { + LoadApplications(); + } #pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed CheckLaunchState(); @@ -485,6 +493,12 @@ namespace Ryujinx.Ava.UI.Windows if (MainContent.Content != content) { + // Load applications while switching to the GameLibrary if we haven't done that yet + if (!_applicationsLoadedOnce && content == GameLibrary) + { + LoadApplications(); + } + MainContent.Content = content; } } @@ -581,6 +595,7 @@ namespace Ryujinx.Ava.UI.Windows public void LoadApplications() { + _applicationsLoadedOnce = true; ViewModel.Applications.Clear(); StatusBarView.LoadProgressBar.IsVisible = true; @@ -622,7 +637,8 @@ namespace Ryujinx.Ava.UI.Windows Thread applicationLibraryThread = new(() => { - ApplicationLibrary.LoadApplications(ConfigurationState.Instance.UI.GameDirs, ConfigurationState.Instance.System.Language); + ApplicationLibrary.DesiredLanguage = ConfigurationState.Instance.System.Language; + ApplicationLibrary.LoadApplications(ConfigurationState.Instance.UI.GameDirs); _isLoading = false; }) diff --git a/src/Ryujinx/UI/Windows/TitleUpdateWindow.axaml.cs b/src/Ryujinx/UI/Windows/TitleUpdateWindow.axaml.cs index 8de5cb145..af917e7f3 100644 --- a/src/Ryujinx/UI/Windows/TitleUpdateWindow.axaml.cs +++ b/src/Ryujinx/UI/Windows/TitleUpdateWindow.axaml.cs @@ -40,7 +40,7 @@ namespace Ryujinx.Ava.UI.Windows SecondaryButtonText = "", CloseButtonText = "", Content = new TitleUpdateWindow(virtualFileSystem, applicationData), - Title = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.GameUpdateWindowHeading, applicationData.Name, applicationData.IdString), + Title = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.GameUpdateWindowHeading, applicationData.Name, applicationData.IdBaseString), }; Style bottomBorder = new(x => x.OfType().Name("DialogSpace").Child().OfType());