diff --git a/Directory.Packages.props b/Directory.Packages.props index 6f690e87c..2f613c8e5 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/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/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.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.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/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..563fdafd3 100644 --- a/src/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs +++ b/src/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs @@ -211,7 +211,7 @@ namespace Ryujinx.Graphics.Vulkan public void Initialize() { - IMemoryOwner dummyTextureData = ByteMemoryPool.RentCleared(4); + MemoryOwner dummyTextureData = MemoryOwner.RentCleared(4); _dummyTexture.SetData(dummyTextureData); } diff --git a/src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs b/src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs index 5c5a8f3ad..763d26eb5 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); } 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 15220ddf7..05430966e 100644 --- a/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs +++ b/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs @@ -106,7 +106,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); @@ -1831,7 +1831,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 28ddfb292..34a9ca8f7 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/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/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..f78b9ed47 100644 --- a/src/Ryujinx.Graphics.Vulkan/TextureStorage.cs +++ b/src/Ryujinx.Graphics.Vulkan/TextureStorage.cs @@ -114,7 +114,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) { @@ -284,7 +284,7 @@ namespace Ryujinx.Graphics.Vulkan 0, null, 1, - barrier); + in barrier); if (useTempCbs) { @@ -401,11 +401,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; diff --git a/src/Ryujinx.Graphics.Vulkan/TextureView.cs b/src/Ryujinx.Graphics.Vulkan/TextureView.cs index d4f26a2dd..c5453c0c7 100644 --- a/src/Ryujinx.Graphics.Vulkan/TextureView.cs +++ b/src/Ryujinx.Graphics.Vulkan/TextureView.cs @@ -117,7 +117,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 +492,7 @@ namespace Ryujinx.Graphics.Vulkan dstStageMask, DependencyFlags.None, 1, - memoryBarrier, + in memoryBarrier, 0, null, 0, @@ -557,7 +557,7 @@ namespace Ryujinx.Graphics.Vulkan 0, null, 1, - memoryBarrier); + in memoryBarrier); } public TextureView GetView(Format format) @@ -949,11 +949,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; @@ -1010,11 +1010,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); } } 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/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..8bad1a0c7 100644 --- a/src/Ryujinx.Gtk3/Program.cs +++ b/src/Ryujinx.Gtk3/Program.cs @@ -4,6 +4,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; @@ -13,7 +14,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; @@ -111,6 +111,8 @@ namespace Ryujinx if (OperatingSystem.IsMacOS()) { + MVKInitialization.InitializeResolver(); + string baseDirectory = Path.GetDirectoryName(AppDomain.CurrentDomain.BaseDirectory); string resourcesDataDir; @@ -162,12 +164,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); @@ -256,6 +252,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/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/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/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/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..5c30cd18f 100644 --- a/src/Ryujinx.Headless.SDL2/Program.cs +++ b/src/Ryujinx.Headless.SDL2/Program.cs @@ -18,6 +18,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 +89,11 @@ namespace Ryujinx.Headless.SDL2 }; } + if (OperatingSystem.IsMacOS()) + { + MVKInitialization.InitializeResolver(); + } + Parser.Default.ParseArguments(args) .WithParsed(Load) .WithNotParsed(errors => errors.Output()); 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/ApplicationLibrary.cs b/src/Ryujinx.UI.Common/App/ApplicationLibrary.cs index e7c48162a..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()); @@ -221,7 +221,7 @@ namespace Ryujinx.UI.App.Common { using UniqueRef icon = new(); - controlFs.OpenFile(ref icon.Ref, $"/icon_{_desiredTitleLanguage}.dat".ToU8Span(), OpenMode.Read).ThrowIfFailure(); + controlFs.OpenFile(ref icon.Ref, $"/icon_{DesiredLanguage}.dat".ToU8Span(), OpenMode.Read).ThrowIfFailure(); using MemoryStream stream = new(); @@ -266,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); @@ -422,35 +432,40 @@ namespace Ryujinx.UI.App.Common 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; @@ -472,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 @@ -502,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) || @@ -521,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) @@ -827,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/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..af9db7d63 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); 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/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/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/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/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; })