From 322c14ee3118d11347fb15f02f525abc1647179e Mon Sep 17 00:00:00 2001 From: Ac_K Date: Wed, 29 Dec 2021 14:49:10 +0100 Subject: [PATCH 1/7] hid: A little cleanup (#2950) While I'm looking to the code, I've found some syntax issue, and a little inconsistencie between `ActivateNpad` and `ActivateNpadWithRevision`. Nothing more. --- Ryujinx.HLE/HOS/Services/Hid/Hid.cs | 14 ++-- .../Services/Hid/HidDevices/NpadDevices.cs | 9 +-- Ryujinx.HLE/HOS/Services/Hid/IHidServer.cs | 77 +++++++------------ .../Services/Hid/Types/Npad/NpadStyleIndex.cs | 12 +-- 4 files changed, 43 insertions(+), 69 deletions(-) diff --git a/Ryujinx.HLE/HOS/Services/Hid/Hid.cs b/Ryujinx.HLE/HOS/Services/Hid/Hid.cs index 85c811fa2..b8833e9e1 100644 --- a/Ryujinx.HLE/HOS/Services/Hid/Hid.cs +++ b/Ryujinx.HLE/HOS/Services/Hid/Hid.cs @@ -1,17 +1,17 @@ using Ryujinx.Common; -using Ryujinx.HLE.Exceptions; using Ryujinx.Common.Configuration.Hid; -using System.Collections.Generic; -using System.Runtime.CompilerServices; using Ryujinx.Common.Memory; +using Ryujinx.HLE.Exceptions; +using Ryujinx.HLE.HOS.Kernel.Memory; using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory; using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common; -using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Mouse; -using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Keyboard; using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.DebugPad; -using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.TouchScreen; +using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Keyboard; +using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Mouse; using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad; -using Ryujinx.HLE.HOS.Kernel.Memory; +using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.TouchScreen; +using System.Collections.Generic; +using System.Runtime.CompilerServices; namespace Ryujinx.HLE.HOS.Services.Hid { diff --git a/Ryujinx.HLE/HOS/Services/Hid/HidDevices/NpadDevices.cs b/Ryujinx.HLE/HOS/Services/Hid/HidDevices/NpadDevices.cs index ff93204c9..5f56c770d 100644 --- a/Ryujinx.HLE/HOS/Services/Hid/HidDevices/NpadDevices.cs +++ b/Ryujinx.HLE/HOS/Services/Hid/HidDevices/NpadDevices.cs @@ -1,14 +1,13 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Runtime.CompilerServices; using Ryujinx.Common; -using Ryujinx.Common.Configuration.Hid; using Ryujinx.Common.Logging; using Ryujinx.HLE.HOS.Kernel.Threading; using Ryujinx.HLE.HOS.Services.Hid.Types; using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common; using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Runtime.CompilerServices; namespace Ryujinx.HLE.HOS.Services.Hid { diff --git a/Ryujinx.HLE/HOS/Services/Hid/IHidServer.cs b/Ryujinx.HLE/HOS/Services/Hid/IHidServer.cs index 140545ad9..ce7dddd34 100644 --- a/Ryujinx.HLE/HOS/Services/Hid/IHidServer.cs +++ b/Ryujinx.HLE/HOS/Services/Hid/IHidServer.cs @@ -5,9 +5,7 @@ using Ryujinx.HLE.HOS.Kernel.Common; using Ryujinx.HLE.HOS.Kernel.Threading; using Ryujinx.HLE.HOS.Services.Hid.HidServer; using Ryujinx.HLE.HOS.Services.Hid.Types; -using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad; using System; -using System.Collections.Concurrent; using System.Collections.Generic; using System.Runtime.InteropServices; @@ -642,9 +640,9 @@ namespace Ryujinx.HLE.HOS.Services.Hid // SetSupportedNpadIdType(nn::applet::AppletResourceUserId, array) public ResultCode SetSupportedNpadIdType(ServiceCtx context) { - long appletResourceUserId = context.RequestData.ReadInt64(); - ulong arrayPosition = context.Request.PtrBuff[0].Position; - ulong arraySize = context.Request.PtrBuff[0].Size; + long appletResourceUserId = context.RequestData.ReadInt64(); + ulong arrayPosition = context.Request.PtrBuff[0].Position; + ulong arraySize = context.Request.PtrBuff[0].Size; ReadOnlySpan supportedPlayerIds = MemoryMarshal.Cast(context.Memory.GetSpan(arrayPosition, (int)arraySize)); @@ -667,38 +665,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid // ActivateNpad(nn::applet::AppletResourceUserId) public ResultCode ActivateNpad(ServiceCtx context) { - long appletResourceUserId = context.RequestData.ReadInt64(); - - context.Device.Hid.Npads.Active = true; - - // Initialize entries to avoid issues with some games. - - List emptyGamepadInputs = new List(); - List emptySixAxisInputs = new List(); - - for (int player = 0; player < NpadDevices.MaxControllers; player++) - { - GamepadInput gamepadInput = new GamepadInput(); - SixAxisInput sixaxisInput = new SixAxisInput(); - - gamepadInput.PlayerId = (PlayerIndex)player; - sixaxisInput.PlayerId = (PlayerIndex)player; - - sixaxisInput.Orientation = new float[9]; - - emptyGamepadInputs.Add(gamepadInput); - emptySixAxisInputs.Add(sixaxisInput); - } - - for (int entry = 0; entry < Hid.SharedMemEntryCount; entry++) - { - context.Device.Hid.Npads.Update(emptyGamepadInputs); - context.Device.Hid.Npads.UpdateSixAxis(emptySixAxisInputs); - } - - Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId }); - - return ResultCode.Success; + return ActiveNpadImpl(context); } [CommandHipc(104)] @@ -773,12 +740,20 @@ namespace Ryujinx.HLE.HOS.Services.Hid } [CommandHipc(109)] // 5.0.0+ - // ActivateNpadWithRevision(nn::applet::AppletResourceUserId, int revision) + // ActivateNpadWithRevision(nn::applet::AppletResourceUserId, ulong revision) public ResultCode ActivateNpadWithRevision(ServiceCtx context) { - int revision = context.RequestData.ReadInt32(); + ulong revision = context.RequestData.ReadUInt64(); + + return ActiveNpadImpl(context, revision); + } + + private ResultCode ActiveNpadImpl(ServiceCtx context, ulong revision = 0) + { long appletResourceUserId = context.RequestData.ReadInt64(); + context.Device.Hid.Npads.Active = true; + // Initialize entries to avoid issues with some games. List emptyGamepadInputs = new List(); @@ -974,7 +949,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid // IsUnintendedHomeButtonInputProtectionEnabled(uint Unknown0, nn::applet::AppletResourceUserId) -> bool IsEnabled public ResultCode IsUnintendedHomeButtonInputProtectionEnabled(ServiceCtx context) { - uint unknown0 = context.RequestData.ReadUInt32(); + uint unknown0 = context.RequestData.ReadUInt32(); long appletResourceUserId = context.RequestData.ReadInt64(); context.ResponseData.Write(_unintendedHomeButtonInputProtectionEnabled); @@ -989,7 +964,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid public ResultCode EnableUnintendedHomeButtonInputProtection(ServiceCtx context) { _unintendedHomeButtonInputProtectionEnabled = context.RequestData.ReadBoolean(); - uint unknown0 = context.RequestData.ReadUInt32(); + uint unknown0 = context.RequestData.ReadUInt32(); long appletResourceUserId = context.RequestData.ReadInt64(); Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, unknown0, _unintendedHomeButtonInputProtectionEnabled }); @@ -1027,8 +1002,8 @@ namespace Ryujinx.HLE.HOS.Services.Hid public ResultCode GetVibrationDeviceInfo(ServiceCtx context) { HidVibrationDeviceHandle deviceHandle = context.RequestData.ReadStruct(); - NpadStyleIndex deviceType = (NpadStyleIndex)deviceHandle.DeviceType; - NpadIdType npadIdType = (NpadIdType)deviceHandle.PlayerId; + NpadStyleIndex deviceType = (NpadStyleIndex)deviceHandle.DeviceType; + NpadIdType npadIdType = (NpadIdType)deviceHandle.PlayerId; if (deviceType < NpadStyleIndex.System || deviceType >= NpadStyleIndex.FullKey) { @@ -1092,9 +1067,9 @@ namespace Ryujinx.HLE.HOS.Services.Hid HidVibrationDeviceHandle deviceHandle = new HidVibrationDeviceHandle { DeviceType = context.RequestData.ReadByte(), - PlayerId = context.RequestData.ReadByte(), - Position = context.RequestData.ReadByte(), - Reserved = context.RequestData.ReadByte() + PlayerId = context.RequestData.ReadByte(), + Position = context.RequestData.ReadByte(), + Reserved = context.RequestData.ReadByte() }; HidVibrationValue vibrationValue = new HidVibrationValue @@ -1123,9 +1098,9 @@ namespace Ryujinx.HLE.HOS.Services.Hid HidVibrationDeviceHandle deviceHandle = new HidVibrationDeviceHandle { DeviceType = context.RequestData.ReadByte(), - PlayerId = context.RequestData.ReadByte(), - Position = context.RequestData.ReadByte(), - Reserved = context.RequestData.ReadByte() + PlayerId = context.RequestData.ReadByte(), + Position = context.RequestData.ReadByte(), + Reserved = context.RequestData.ReadByte() }; long appletResourceUserId = context.RequestData.ReadInt64(); @@ -1185,8 +1160,8 @@ namespace Ryujinx.HLE.HOS.Services.Hid context.Memory.Read(context.Request.PtrBuff[1].Position, vibrationValueBuffer); - Span deviceHandles = MemoryMarshal.Cast(vibrationDeviceHandleBuffer); - Span vibrationValues = MemoryMarshal.Cast(vibrationValueBuffer); + Span deviceHandles = MemoryMarshal.Cast(vibrationDeviceHandleBuffer); + Span vibrationValues = MemoryMarshal.Cast(vibrationValueBuffer); if (!deviceHandles.IsEmpty && vibrationValues.Length == deviceHandles.Length) { diff --git a/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/NpadStyleIndex.cs b/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/NpadStyleIndex.cs index b85681d27..ddf5d97f5 100644 --- a/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/NpadStyleIndex.cs +++ b/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/NpadStyleIndex.cs @@ -2,12 +2,12 @@ { public enum NpadStyleIndex : byte { - FullKey = 3, - Handheld = 4, - JoyDual = 5, - JoyLeft = 6, - JoyRight = 7, + FullKey = 3, + Handheld = 4, + JoyDual = 5, + JoyLeft = 6, + JoyRight = 7, SystemExt = 32, - System = 33 + System = 33 } } From 8544b1445b33381fca63714249ac36598c413004 Mon Sep 17 00:00:00 2001 From: gdkchan Date: Wed, 29 Dec 2021 11:04:38 -0300 Subject: [PATCH 2/7] Improve SocketOption handling (#2946) --- .../HOS/Services/Sockets/Bsd/IClient.cs | 180 ++++++++++-------- .../Sockets/Bsd/Types/BsdSocketFlags.cs | 2 - .../Sockets/Bsd/Types/BsdSocketOption.cs | 119 ++++++++++++ 3 files changed, 221 insertions(+), 80 deletions(-) create mode 100644 Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocketOption.cs diff --git a/Ryujinx.HLE/HOS/Services/Sockets/Bsd/IClient.cs b/Ryujinx.HLE/HOS/Services/Sockets/Bsd/IClient.cs index 43c7ee7d9..76f80f929 100644 --- a/Ryujinx.HLE/HOS/Services/Sockets/Bsd/IClient.cs +++ b/Ryujinx.HLE/HOS/Services/Sockets/Bsd/IClient.cs @@ -13,7 +13,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd [Service("bsd:u", false)] class IClient : IpcService { - private static Dictionary _errorMap = new Dictionary + private static readonly Dictionary _errorMap = new() { // WSAEINTR {WsaError.WSAEINTR, LinuxError.EINTR}, @@ -97,6 +97,50 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd {0, 0} }; + private static readonly Dictionary _soSocketOptionMap = new() + { + { BsdSocketOption.SoDebug, SocketOptionName.Debug }, + { BsdSocketOption.SoReuseAddr, SocketOptionName.ReuseAddress }, + { BsdSocketOption.SoKeepAlive, SocketOptionName.KeepAlive }, + { BsdSocketOption.SoDontRoute, SocketOptionName.DontRoute }, + { BsdSocketOption.SoBroadcast, SocketOptionName.Broadcast }, + { BsdSocketOption.SoUseLoopBack, SocketOptionName.UseLoopback }, + { BsdSocketOption.SoLinger, SocketOptionName.Linger }, + { BsdSocketOption.SoOobInline, SocketOptionName.OutOfBandInline }, + { BsdSocketOption.SoReusePort, SocketOptionName.ReuseAddress }, + { BsdSocketOption.SoSndBuf, SocketOptionName.SendBuffer }, + { BsdSocketOption.SoRcvBuf, SocketOptionName.ReceiveBuffer }, + { BsdSocketOption.SoSndLoWat, SocketOptionName.SendLowWater }, + { BsdSocketOption.SoRcvLoWat, SocketOptionName.ReceiveLowWater }, + { BsdSocketOption.SoSndTimeo, SocketOptionName.SendTimeout }, + { BsdSocketOption.SoRcvTimeo, SocketOptionName.ReceiveTimeout }, + { BsdSocketOption.SoError, SocketOptionName.Error }, + { BsdSocketOption.SoType, SocketOptionName.Type } + }; + + private static readonly Dictionary _ipSocketOptionMap = new() + { + { BsdSocketOption.IpOptions, SocketOptionName.IPOptions }, + { BsdSocketOption.IpHdrIncl, SocketOptionName.HeaderIncluded }, + { BsdSocketOption.IpTtl, SocketOptionName.IpTimeToLive }, + { BsdSocketOption.IpMulticastIf, SocketOptionName.MulticastInterface }, + { BsdSocketOption.IpMulticastTtl, SocketOptionName.MulticastTimeToLive }, + { BsdSocketOption.IpMulticastLoop, SocketOptionName.MulticastLoopback }, + { BsdSocketOption.IpAddMembership, SocketOptionName.AddMembership }, + { BsdSocketOption.IpDropMembership, SocketOptionName.DropMembership }, + { BsdSocketOption.IpDontFrag, SocketOptionName.DontFragment }, + { BsdSocketOption.IpAddSourceMembership, SocketOptionName.AddSourceMembership }, + { BsdSocketOption.IpDropSourceMembership, SocketOptionName.DropSourceMembership } + }; + + private static readonly Dictionary _tcpSocketOptionMap = new() + { + { BsdSocketOption.TcpNoDelay, SocketOptionName.NoDelay }, + { BsdSocketOption.TcpKeepIdle, SocketOptionName.TcpKeepAliveTime }, + { BsdSocketOption.TcpKeepIntvl, SocketOptionName.TcpKeepAliveInterval }, + { BsdSocketOption.TcpKeepCnt, SocketOptionName.TcpKeepAliveRetryCount } + }; + private bool _isPrivileged; private List _sockets = new List(); @@ -118,13 +162,6 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd private static SocketFlags ConvertBsdSocketFlags(BsdSocketFlags bsdSocketFlags) { - BsdSocketFlags SupportedFlags = - BsdSocketFlags.Oob | - BsdSocketFlags.Peek | - BsdSocketFlags.DontRoute | - BsdSocketFlags.Trunc | - BsdSocketFlags.CTrunc; - SocketFlags socketFlags = SocketFlags.None; if (bsdSocketFlags.HasFlag(BsdSocketFlags.Oob)) @@ -166,6 +203,25 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd return socketFlags; } + private static bool TryConvertSocketOption(BsdSocketOption option, SocketOptionLevel level, out SocketOptionName name) + { + var table = level switch + { + SocketOptionLevel.Socket => _soSocketOptionMap, + SocketOptionLevel.IP => _ipSocketOptionMap, + SocketOptionLevel.Tcp => _tcpSocketOptionMap, + _ => null + }; + + if (table == null) + { + name = default; + return false; + } + + return table.TryGetValue(option, out name); + } + private ResultCode WriteWinSock2Error(ServiceCtx context, WsaError errorCode) { return WriteBsdResult(context, -1, ConvertError(errorCode)); @@ -820,9 +876,9 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd // GetSockOpt(u32 socket, u32 level, u32 option_name) -> (i32 ret, u32 bsd_errno, u32, buffer) public ResultCode GetSockOpt(ServiceCtx context) { - int socketFd = context.RequestData.ReadInt32(); - SocketOptionLevel level = (SocketOptionLevel)context.RequestData.ReadInt32(); - SocketOptionName optionName = (SocketOptionName)context.RequestData.ReadInt32(); + int socketFd = context.RequestData.ReadInt32(); + SocketOptionLevel level = (SocketOptionLevel)context.RequestData.ReadInt32(); + BsdSocketOption option = (BsdSocketOption)context.RequestData.ReadInt32(); (ulong bufferPosition, ulong bufferSize) = context.Request.GetBufferType0x22(); @@ -831,7 +887,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd if (socket != null) { - errno = HandleGetSocketOption(context, socket, optionName, level, bufferPosition, bufferSize); + errno = HandleGetSocketOption(context, socket, option, level, bufferPosition, bufferSize); } return WriteBsdResult(context, 0, errno); @@ -936,45 +992,26 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd private static LinuxError HandleGetSocketOption( ServiceCtx context, BsdSocket socket, - SocketOptionName optionName, + BsdSocketOption option, SocketOptionLevel level, ulong optionValuePosition, ulong optionValueSize) { try { + if (!TryConvertSocketOption(option, level, out SocketOptionName optionName)) + { + Logger.Warning?.Print(LogClass.ServiceBsd, $"Unsupported GetSockOpt Option: {option} Level: {level}"); + + return LinuxError.EOPNOTSUPP; + } + byte[] optionValue = new byte[optionValueSize]; - switch (optionName) - { - case SocketOptionName.Broadcast: - case SocketOptionName.DontLinger: - case SocketOptionName.Debug: - case SocketOptionName.Error: - case SocketOptionName.KeepAlive: - case SocketOptionName.OutOfBandInline: - case SocketOptionName.ReceiveBuffer: - case SocketOptionName.ReceiveTimeout: - case SocketOptionName.SendBuffer: - case SocketOptionName.SendTimeout: - case SocketOptionName.Type: - case SocketOptionName.Linger: - socket.Handle.GetSocketOption(level, optionName, optionValue); - context.Memory.Write(optionValuePosition, optionValue); + socket.Handle.GetSocketOption(level, optionName, optionValue); + context.Memory.Write(optionValuePosition, optionValue); - return LinuxError.SUCCESS; - - case (SocketOptionName)0x200: - socket.Handle.GetSocketOption(level, SocketOptionName.ReuseAddress, optionValue); - context.Memory.Write(optionValuePosition, optionValue); - - return LinuxError.SUCCESS; - - default: - Logger.Warning?.Print(LogClass.ServiceBsd, $"Unsupported GetSockOpt OptionName: {optionName}"); - - return LinuxError.EOPNOTSUPP; - } + return LinuxError.SUCCESS; } catch (SocketException exception) { @@ -985,47 +1022,34 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd private static LinuxError HandleSetSocketOption( ServiceCtx context, BsdSocket socket, - SocketOptionName optionName, + BsdSocketOption option, SocketOptionLevel level, ulong optionValuePosition, ulong optionValueSize) { try { - switch (optionName) + if (!TryConvertSocketOption(option, level, out SocketOptionName optionName)) { - case SocketOptionName.Broadcast: - case SocketOptionName.DontLinger: - case SocketOptionName.Debug: - case SocketOptionName.Error: - case SocketOptionName.KeepAlive: - case SocketOptionName.OutOfBandInline: - case SocketOptionName.ReceiveBuffer: - case SocketOptionName.ReceiveTimeout: - case SocketOptionName.SendBuffer: - case SocketOptionName.SendTimeout: - case SocketOptionName.Type: - case SocketOptionName.ReuseAddress: - socket.Handle.SetSocketOption(level, optionName, context.Memory.Read((ulong)optionValuePosition)); + Logger.Warning?.Print(LogClass.ServiceBsd, $"Unsupported SetSockOpt Option: {option} Level: {level}"); - return LinuxError.SUCCESS; - - case (SocketOptionName)0x200: - socket.Handle.SetSocketOption(level, SocketOptionName.ReuseAddress, context.Memory.Read((ulong)optionValuePosition)); - - return LinuxError.SUCCESS; - - case SocketOptionName.Linger: - socket.Handle.SetSocketOption(level, SocketOptionName.Linger, - new LingerOption(context.Memory.Read((ulong)optionValuePosition) != 0, context.Memory.Read((ulong)optionValuePosition + 4))); - - return LinuxError.SUCCESS; - - default: - Logger.Warning?.Print(LogClass.ServiceBsd, $"Unsupported SetSockOpt OptionName: {optionName}"); - - return LinuxError.EOPNOTSUPP; + return LinuxError.EOPNOTSUPP; } + + int value = context.Memory.Read((ulong)optionValuePosition); + + if (option == BsdSocketOption.SoLinger) + { + int value2 = context.Memory.Read((ulong)optionValuePosition + 4); + + socket.Handle.SetSocketOption(level, SocketOptionName.Linger, new LingerOption(value != 0, value2)); + } + else + { + socket.Handle.SetSocketOption(level, optionName, value); + } + + return LinuxError.SUCCESS; } catch (SocketException exception) { @@ -1037,9 +1061,9 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd // SetSockOpt(u32 socket, u32 level, u32 option_name, buffer option_value) -> (i32 ret, u32 bsd_errno) public ResultCode SetSockOpt(ServiceCtx context) { - int socketFd = context.RequestData.ReadInt32(); - SocketOptionLevel level = (SocketOptionLevel)context.RequestData.ReadInt32(); - SocketOptionName optionName = (SocketOptionName)context.RequestData.ReadInt32(); + int socketFd = context.RequestData.ReadInt32(); + SocketOptionLevel level = (SocketOptionLevel)context.RequestData.ReadInt32(); + BsdSocketOption option = (BsdSocketOption)context.RequestData.ReadInt32(); (ulong bufferPos, ulong bufferSize) = context.Request.GetBufferType0x21(); @@ -1048,7 +1072,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd if (socket != null) { - errno = HandleSetSocketOption(context, socket, optionName, level, bufferPos, bufferSize); + errno = HandleSetSocketOption(context, socket, option, level, bufferPos, bufferSize); } return WriteBsdResult(context, 0, errno); diff --git a/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocketFlags.cs b/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocketFlags.cs index 4dc56356d..ca464c09e 100644 --- a/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocketFlags.cs +++ b/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocketFlags.cs @@ -1,5 +1,3 @@ -using System.Net.Sockets; - namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd { enum BsdSocketFlags diff --git a/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocketOption.cs b/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocketOption.cs new file mode 100644 index 000000000..726e4111e --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocketOption.cs @@ -0,0 +1,119 @@ +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd +{ + enum BsdSocketOption + { + SoDebug = 0x1, + SoAcceptConn = 0x2, + SoReuseAddr = 0x4, + SoKeepAlive = 0x8, + SoDontRoute = 0x10, + SoBroadcast = 0x20, + SoUseLoopBack = 0x40, + SoLinger = 0x80, + SoOobInline = 0x100, + SoReusePort = 0x200, + SoTimestamp = 0x400, + SoNoSigpipe = 0x800, + SoAcceptFilter = 0x1000, + SoBinTime = 0x2000, + SoNoOffload = 0x4000, + SoNoDdp = 0x8000, + SoReusePortLb = 0x10000, + SoRError = 0x20000, + + SoSndBuf = 0x1001, + SoRcvBuf = 0x1002, + SoSndLoWat = 0x1003, + SoRcvLoWat = 0x1004, + SoSndTimeo = 0x1005, + SoRcvTimeo = 0x1006, + SoError = 0x1007, + SoType = 0x1008, + SoLabel = 0x1009, + SoPeerLabel = 0x1010, + SoListenQLimit = 0x1011, + SoListenQLen = 0x1012, + SoListenIncQLen = 0x1013, + SoSetFib = 0x1014, + SoUserCookie = 0x1015, + SoProtocol = 0x1016, + SoTsClock = 0x1017, + SoMaxPacingRate = 0x1018, + SoDomain = 0x1019, + + IpOptions = 1, + IpHdrIncl = 2, + IpTos = 3, + IpTtl = 4, + IpRecvOpts = 5, + IpRecvRetOpts = 6, + IpRecvDstAddr = 7, + IpRetOpts = 8, + IpMulticastIf = 9, + IpMulticastTtl = 10, + IpMulticastLoop = 11, + IpAddMembership = 12, + IpDropMembership = 13, + IpMulticastVif = 14, + IpRsvpOn = 15, + IpRsvpOff = 16, + IpRsvpVifOn = 17, + IpRsvpVifOff = 18, + IpPortRange = 19, + IpRecvIf = 20, + IpIpsecPolicy = 21, + IpOnesBcast = 23, + IpBindany = 24, + IpBindMulti = 25, + IpRssListenBucket = 26, + IpOrigDstAddr = 27, + + IpFwTableAdd = 40, + IpFwTableDel = 41, + IpFwTableFlush = 42, + IpFwTableGetSize = 43, + IpFwTableList = 44, + + IpFw3 = 48, + IpDummyNet3 = 49, + + IpFwAdd = 50, + IpFwDel = 51, + IpFwFlush = 52, + IpFwZero = 53, + IpFwGet = 54, + IpFwResetLog = 55, + + IpFwNatCfg = 56, + IpFwNatDel = 57, + IpFwNatGetConfig = 58, + IpFwNatGetLog = 59, + + IpDummyNetConfigure = 60, + IpDummyNetDel = 61, + IpDummyNetFlush = 62, + IpDummyNetGet = 64, + + IpRecvTtl = 65, + IpMinTtl = 66, + IpDontFrag = 67, + IpRecvTos = 68, + + IpAddSourceMembership = 70, + IpDropSourceMembership = 71, + IpBlockSource = 72, + IpUnblockSource = 73, + + TcpNoDelay = 1, + TcpMaxSeg = 2, + TcpNoPush = 4, + TcpNoOpt = 8, + TcpMd5Sig = 16, + TcpInfo = 32, + TcpCongestion = 64, + TcpKeepInit = 128, + TcpKeepIdle = 256, + TcpKeepIntvl = 512, + TcpKeepCnt = 1024 + } +} \ No newline at end of file From e96ef6d53250b72d084f7e6baf13e9bab485bca2 Mon Sep 17 00:00:00 2001 From: Mary Date: Thu, 30 Dec 2021 10:55:06 +0100 Subject: [PATCH 3/7] kernel: Implement thread pinning support (#2840) * kernel: Implement Thread pinning support This commit adds support for 8.x thread pinning changes and implement SynchronizePreemptionState syscall. Based on kernel 13.x reverse. * Address gdkchan's comment * kernel: fix missing critical section leave in SetActivity Fix Unity games * Implement missing bits on the interrupt handler and inline update pinning function as it cannot be generic * Fix some bugs in SetActivity and SetCoreAndAffinityMask * Address gdkchan's comments --- Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs | 56 ++- .../HOS/Kernel/SupervisorCall/Syscall.cs | 7 + .../HOS/Kernel/SupervisorCall/Syscall32.cs | 5 + .../HOS/Kernel/SupervisorCall/Syscall64.cs | 5 + .../Kernel/SupervisorCall/SyscallHandler.cs | 24 +- .../HOS/Kernel/SupervisorCall/SyscallTable.cs | 2 + .../HOS/Kernel/Threading/KScheduler.cs | 21 + Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs | 408 ++++++++++++++---- .../HOS/Kernel/Threading/ThreadSchedState.cs | 3 +- 9 files changed, 436 insertions(+), 95 deletions(-) diff --git a/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs b/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs index 0a74eace6..649cfb93f 100644 --- a/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs +++ b/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs @@ -46,6 +46,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process public KAddressArbiter AddressArbiter { get; private set; } public long[] RandomEntropy { get; private set; } + public KThread[] PinnedThreads { get; private set; } private bool _signaled; @@ -102,6 +103,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process Capabilities = new KProcessCapabilities(); RandomEntropy = new long[KScheduler.CpuCoresCount]; + PinnedThreads = new KThread[KScheduler.CpuCoresCount]; // TODO: Remove once we no longer need to initialize it externally. HandleTable = new KHandleTable(context); @@ -749,7 +751,24 @@ namespace Ryujinx.HLE.HOS.Kernel.Process { KThread currentThread = KernelStatic.GetCurrentThread(); - if (currentThread.IsSchedulable) + if (currentThread.Owner != null && + currentThread.GetUserDisableCount() != 0 && + currentThread.Owner.PinnedThreads[currentThread.CurrentCore] == null) + { + KernelContext.CriticalSection.Enter(); + + currentThread.Owner.PinThread(currentThread); + + currentThread.SetUserInterruptFlag(); + + if (currentThread.IsSchedulable) + { + KernelContext.Schedulers[currentThread.CurrentCore].Schedule(); + } + + KernelContext.CriticalSection.Leave(); + } + else if (currentThread.IsSchedulable) { KernelContext.Schedulers[currentThread.CurrentCore].Schedule(); } @@ -952,6 +971,11 @@ namespace Ryujinx.HLE.HOS.Kernel.Process { KernelContext.CriticalSection.Enter(); + if (currentThread != null && PinnedThreads[currentThread.CurrentCore] == currentThread) + { + UnpinThread(currentThread); + } + foreach (KThread thread in _threads) { if ((thread.SchedFlags & ThreadSchedState.LowMask) != ThreadSchedState.TerminationPending) @@ -1139,5 +1163,35 @@ namespace Ryujinx.HLE.HOS.Kernel.Process return KernelResult.InvalidState; } + + public void PinThread(KThread thread) + { + if (!thread.TerminationRequested) + { + PinnedThreads[thread.CurrentCore] = thread; + + thread.Pin(); + + KernelContext.ThreadReselectionRequested = true; + } + } + + public void UnpinThread(KThread thread) + { + if (!thread.TerminationRequested) + { + thread.Unpin(); + + PinnedThreads[thread.CurrentCore] = null; + + KernelContext.ThreadReselectionRequested = true; + } + } + + public bool IsExceptionUserThread(KThread thread) + { + // TODO + return false; + } } } \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/SupervisorCall/Syscall.cs b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/Syscall.cs index 59d56b4d7..2dd9d8070 100644 --- a/Ryujinx.HLE/HOS/Kernel/SupervisorCall/Syscall.cs +++ b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/Syscall.cs @@ -2655,6 +2655,13 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall }; } + public KernelResult SynchronizePreemptionState() + { + KernelStatic.GetCurrentThread().SynchronizePreemptionState(); + + return KernelResult.Success; + } + private bool IsPointingInsideKernel(ulong address) { return (address + 0x1000000000) < 0xffffff000; diff --git a/Ryujinx.HLE/HOS/Kernel/SupervisorCall/Syscall32.cs b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/Syscall32.cs index bb1cc8ad0..d955807d8 100644 --- a/Ryujinx.HLE/HOS/Kernel/SupervisorCall/Syscall32.cs +++ b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/Syscall32.cs @@ -491,5 +491,10 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall { return _syscall.SignalToAddress(address, type, value, count); } + + public KernelResult SynchronizePreemptionState32() + { + return _syscall.SynchronizePreemptionState(); + } } } diff --git a/Ryujinx.HLE/HOS/Kernel/SupervisorCall/Syscall64.cs b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/Syscall64.cs index 97ded4b5c..fc826552b 100644 --- a/Ryujinx.HLE/HOS/Kernel/SupervisorCall/Syscall64.cs +++ b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/Syscall64.cs @@ -405,5 +405,10 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall { return _syscall.SignalToAddress(address, type, value, count); } + + public KernelResult SynchronizePreemptionState64() + { + return _syscall.SynchronizePreemptionState(); + } } } diff --git a/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SyscallHandler.cs b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SyscallHandler.cs index b4e7a0bf0..5e795d356 100644 --- a/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SyscallHandler.cs +++ b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SyscallHandler.cs @@ -19,7 +19,22 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall public void SvcCall(object sender, InstExceptionEventArgs e) { - ExecutionContext context = (ExecutionContext)sender; + KThread currentThread = KernelStatic.GetCurrentThread(); + + if (currentThread.Owner != null && + currentThread.GetUserDisableCount() != 0 && + currentThread.Owner.PinnedThreads[currentThread.CurrentCore] == null) + { + _context.CriticalSection.Enter(); + + currentThread.Owner.PinThread(currentThread); + + currentThread.SetUserInterruptFlag(); + + _context.CriticalSection.Leave(); + } + + ExecutionContext context = (ExecutionContext)sender; if (context.IsAarch32) { @@ -44,13 +59,6 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall svcFunc(_syscall64, context); } - PostSvcHandler(); - } - - private void PostSvcHandler() - { - KThread currentThread = KernelStatic.GetCurrentThread(); - currentThread.HandlePostSyscall(); } } diff --git a/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SyscallTable.cs b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SyscallTable.cs index 7e9f08c0f..178dc0298 100644 --- a/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SyscallTable.cs +++ b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SyscallTable.cs @@ -71,6 +71,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall { 0x33, nameof(Syscall64.GetThreadContext364) }, { 0x34, nameof(Syscall64.WaitForAddress64) }, { 0x35, nameof(Syscall64.SignalToAddress64) }, + { 0x36, nameof(Syscall64.SynchronizePreemptionState64) }, { 0x37, nameof(Syscall64.GetResourceLimitPeakValue64) }, { 0x40, nameof(Syscall64.CreateSession64) }, { 0x41, nameof(Syscall64.AcceptSession64) }, @@ -145,6 +146,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall { 0x33, nameof(Syscall32.GetThreadContext332) }, { 0x34, nameof(Syscall32.WaitForAddress32) }, { 0x35, nameof(Syscall32.SignalToAddress32) }, + { 0x36, nameof(Syscall32.SynchronizePreemptionState32) }, { 0x37, nameof(Syscall32.GetResourceLimitPeakValue32) }, { 0x40, nameof(Syscall32.CreateSession32) }, { 0x41, nameof(Syscall32.AcceptSession32) }, diff --git a/Ryujinx.HLE/HOS/Kernel/Threading/KScheduler.cs b/Ryujinx.HLE/HOS/Kernel/Threading/KScheduler.cs index 0982ceffb..0c51b7b9a 100644 --- a/Ryujinx.HLE/HOS/Kernel/Threading/KScheduler.cs +++ b/Ryujinx.HLE/HOS/Kernel/Threading/KScheduler.cs @@ -36,6 +36,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading private readonly KThread _idleThread; public KThread PreviousThread => _previousThread; + public KThread CurrentThread => _currentThread; public long LastContextSwitchTime { get; private set; } public long TotalIdleTimeTicks => _idleThread.TotalTimeRunning; @@ -87,6 +88,26 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading { KThread thread = context.PriorityQueue.ScheduledThreads(core).FirstOrDefault(); + if (thread != null && + thread.Owner != null && + thread.Owner.PinnedThreads[core] != null && + thread.Owner.PinnedThreads[core] != thread) + { + KThread candidate = thread.Owner.PinnedThreads[core]; + + if (candidate.KernelWaitersCount == 0 && !thread.Owner.IsExceptionUserThread(candidate)) + { + if (candidate.SchedFlags == ThreadSchedState.Running) + { + thread = candidate; + } + else + { + thread = null; + } + } + } + scheduledCoresMask |= context.Schedulers[core].SelectThread(thread); } diff --git a/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs b/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs index 646292487..cf95b0154 100644 --- a/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs +++ b/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs @@ -11,6 +11,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading { class KThread : KSynchronizationObject, IKFutureSchedulerObject { + private const int TlsUserDisableCountOffset = 0x100; + private const int TlsUserInterruptFlagOffset = 0x102; + public const int MaxWaitSyncObjects = 64; private ManualResetEvent _schedulerWaitEvent; @@ -43,6 +46,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading public bool IsSchedulable => _customThreadStart == null && !_forcedUnschedulable; public ulong MutexAddress { get; set; } + public int KernelWaitersCount { get; private set; } public KProcess Owner { get; private set; } @@ -65,11 +69,14 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading private LinkedList _mutexWaiters; private LinkedListNode _mutexWaiterNode; + private LinkedList _pinnedWaiters; + public KThread MutexOwner { get; private set; } public int ThreadHandleForUserMutex { get; set; } private ThreadSchedState _forcePauseFlags; + private ThreadSchedState _forcePausePermissionFlags; public KernelResult ObjSyncResult { get; set; } @@ -79,11 +86,12 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading public int CurrentCore { get; set; } public int ActiveCore { get; set; } - private long _affinityMaskOverride; - private int _preferredCoreOverride; -#pragma warning disable CS0649 - private int _affinityOverrideCount; -#pragma warning restore CS0649 + public bool IsPinned { get; private set; } + + private long _originalAffinityMask; + private int _originalPreferredCore; + private int _originalBasePriority; + private int _coreMigrationDisableCount; public ThreadSchedState SchedFlags { get; private set; } @@ -108,6 +116,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading public long LastPc { get; set; } + private object ActivityOperationLock = new object(); + public KThread(KernelContext context) : base(context) { WaitSyncObjects = new KSynchronizationObject[MaxWaitSyncObjects]; @@ -116,6 +126,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading SiblingsPerCore = new LinkedListNode[KScheduler.CpuCoresCount]; _mutexWaiters = new LinkedList(); + _pinnedWaiters = new LinkedList(); } public KernelResult Initialize( @@ -147,6 +158,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading DynamicPriority = priority; BasePriority = priority; CurrentCore = cpuCore; + IsPinned = false; _entrypoint = entrypoint; _customThreadStart = customThreadStart; @@ -204,6 +216,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading _hasBeenInitialized = true; + _forcePausePermissionFlags = ThreadSchedState.ForcePauseMask; + if (owner != null) { owner.SubscribeThreadEventHandlers(Context); @@ -301,6 +315,11 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading { KernelContext.CriticalSection.Enter(); + if (Owner != null && Owner.PinnedThreads[KernelStatic.GetCurrentThread().CurrentCore] == this) + { + Owner.UnpinThread(this); + } + ThreadSchedState result; if (Interlocked.CompareExchange(ref _shallBeTerminated, 1, 0) == 0) @@ -405,6 +424,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading KernelContext.CriticalSection.Enter(); _forcePauseFlags &= ~ThreadSchedState.ForcePauseMask; + _forcePausePermissionFlags = 0; bool decRef = ExitImpl(); @@ -433,6 +453,19 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading return decRef; } + private int GetEffectiveRunningCore() + { + for (int coreNumber = 0; coreNumber < KScheduler.CpuCoresCount; coreNumber++) + { + if (KernelContext.Schedulers[coreNumber].CurrentThread == this) + { + return coreNumber; + } + } + + return -1; + } + public KernelResult Sleep(long timeout) { KernelContext.CriticalSection.Enter(); @@ -465,7 +498,14 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading { KernelContext.CriticalSection.Enter(); - BasePriority = priority; + if (IsPinned) + { + _originalBasePriority = priority; + } + else + { + BasePriority = priority; + } UpdatePriorityInheritance(); @@ -497,53 +537,96 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading public KernelResult SetActivity(bool pause) { - KernelResult result = KernelResult.Success; - - KernelContext.CriticalSection.Enter(); - - ThreadSchedState lowNibble = SchedFlags & ThreadSchedState.LowMask; - - if (lowNibble != ThreadSchedState.Paused && lowNibble != ThreadSchedState.Running) + lock (ActivityOperationLock) { + KernelResult result = KernelResult.Success; + + KernelContext.CriticalSection.Enter(); + + ThreadSchedState lowNibble = SchedFlags & ThreadSchedState.LowMask; + + if (lowNibble != ThreadSchedState.Paused && lowNibble != ThreadSchedState.Running) + { + KernelContext.CriticalSection.Leave(); + + return KernelResult.InvalidState; + } + + if (!ShallBeTerminated && SchedFlags != ThreadSchedState.TerminationPending) + { + if (pause) + { + // Pause, the force pause flag should be clear (thread is NOT paused). + if ((_forcePauseFlags & ThreadSchedState.ThreadPauseFlag) == 0) + { + Suspend(ThreadSchedState.ThreadPauseFlag); + } + else + { + result = KernelResult.InvalidState; + } + } + else + { + // Unpause, the force pause flag should be set (thread is paused). + if ((_forcePauseFlags & ThreadSchedState.ThreadPauseFlag) != 0) + { + Resume(ThreadSchedState.ThreadPauseFlag); + } + else + { + result = KernelResult.InvalidState; + } + } + } + KernelContext.CriticalSection.Leave(); - return KernelResult.InvalidState; - } - - KernelContext.CriticalSection.Enter(); - - if (!ShallBeTerminated && SchedFlags != ThreadSchedState.TerminationPending) - { - if (pause) + if (result == KernelResult.Success && pause) { - // Pause, the force pause flag should be clear (thread is NOT paused). - if ((_forcePauseFlags & ThreadSchedState.ThreadPauseFlag) == 0) + bool isThreadRunning = true; + + while (isThreadRunning) { - Suspend(ThreadSchedState.ThreadPauseFlag); - } - else - { - result = KernelResult.InvalidState; - } - } - else - { - // Unpause, the force pause flag should be set (thread is paused). - if ((_forcePauseFlags & ThreadSchedState.ThreadPauseFlag) != 0) - { - Resume(ThreadSchedState.ThreadPauseFlag); - } - else - { - result = KernelResult.InvalidState; + KernelContext.CriticalSection.Enter(); + + if (TerminationRequested) + { + KernelContext.CriticalSection.Leave(); + + break; + } + + isThreadRunning = false; + + if (IsPinned) + { + KThread currentThread = KernelStatic.GetCurrentThread(); + + if (currentThread.TerminationRequested) + { + KernelContext.CriticalSection.Leave(); + + result = KernelResult.ThreadTerminating; + + break; + } + + _pinnedWaiters.AddLast(currentThread); + + currentThread.Reschedule(ThreadSchedState.Paused); + } + else + { + isThreadRunning = GetEffectiveRunningCore() >= 0; + } + + KernelContext.CriticalSection.Leave(); } } + + return result; } - - KernelContext.CriticalSection.Leave(); - KernelContext.CriticalSection.Leave(); - - return result; } public void CancelSynchronization() @@ -579,58 +662,105 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading public KernelResult SetCoreAndAffinityMask(int newCore, long newAffinityMask) { - KernelContext.CriticalSection.Enter(); - - bool useOverride = _affinityOverrideCount != 0; - - // The value -3 is "do not change the preferred core". - if (newCore == -3) + lock (ActivityOperationLock) { - newCore = useOverride ? _preferredCoreOverride : PreferredCore; + KernelContext.CriticalSection.Enter(); - if ((newAffinityMask & (1 << newCore)) == 0) + bool isCoreMigrationDisabled = _coreMigrationDisableCount != 0; + + // The value -3 is "do not change the preferred core". + if (newCore == -3) { - KernelContext.CriticalSection.Leave(); + newCore = isCoreMigrationDisabled ? _originalPreferredCore : PreferredCore; - return KernelResult.InvalidCombination; - } - } - - if (useOverride) - { - _preferredCoreOverride = newCore; - _affinityMaskOverride = newAffinityMask; - } - else - { - long oldAffinityMask = AffinityMask; - - PreferredCore = newCore; - AffinityMask = newAffinityMask; - - if (oldAffinityMask != newAffinityMask) - { - int oldCore = ActiveCore; - - if (oldCore >= 0 && ((AffinityMask >> oldCore) & 1) == 0) + if ((newAffinityMask & (1 << newCore)) == 0) { - if (PreferredCore < 0) + KernelContext.CriticalSection.Leave(); + + return KernelResult.InvalidCombination; + } + } + + if (isCoreMigrationDisabled) + { + _originalPreferredCore = newCore; + _originalAffinityMask = newAffinityMask; + } + else + { + long oldAffinityMask = AffinityMask; + + PreferredCore = newCore; + AffinityMask = newAffinityMask; + + if (oldAffinityMask != newAffinityMask) + { + int oldCore = ActiveCore; + + if (oldCore >= 0 && ((AffinityMask >> oldCore) & 1) == 0) { - ActiveCore = sizeof(ulong) * 8 - 1 - BitOperations.LeadingZeroCount((ulong)AffinityMask); + if (PreferredCore < 0) + { + ActiveCore = sizeof(ulong) * 8 - 1 - BitOperations.LeadingZeroCount((ulong)AffinityMask); + } + else + { + ActiveCore = PreferredCore; + } + } + + AdjustSchedulingForNewAffinity(oldAffinityMask, oldCore); + } + } + + KernelContext.CriticalSection.Leave(); + + bool targetThreadPinned = true; + + while (targetThreadPinned) + { + KernelContext.CriticalSection.Enter(); + + if (TerminationRequested) + { + KernelContext.CriticalSection.Leave(); + + break; + } + + targetThreadPinned = false; + + int coreNumber = GetEffectiveRunningCore(); + bool isPinnedThreadCurrentlyRunning = coreNumber >= 0; + + if (isPinnedThreadCurrentlyRunning && ((1 << coreNumber) & AffinityMask) == 0) + { + if (IsPinned) + { + KThread currentThread = KernelStatic.GetCurrentThread(); + + if (currentThread.TerminationRequested) + { + KernelContext.CriticalSection.Leave(); + + return KernelResult.ThreadTerminating; + } + + _pinnedWaiters.AddLast(currentThread); + + currentThread.Reschedule(ThreadSchedState.Paused); } else { - ActiveCore = PreferredCore; + targetThreadPinned = true; } } - AdjustSchedulingForNewAffinity(oldAffinityMask, oldCore); + KernelContext.CriticalSection.Leave(); } + + return KernelResult.Success; } - - KernelContext.CriticalSection.Leave(); - - return KernelResult.Success; } private void CombineForcePauseFlags() @@ -638,7 +768,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading ThreadSchedState oldFlags = SchedFlags; ThreadSchedState lowNibble = SchedFlags & ThreadSchedState.LowMask; - SchedFlags = lowNibble | _forcePauseFlags; + SchedFlags = lowNibble | (_forcePauseFlags & _forcePausePermissionFlags); AdjustScheduling(oldFlags); } @@ -1106,7 +1236,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading foreach (KThread thread in _mutexWaiters) { thread.MutexOwner = null; - thread._preferredCoreOverride = 0; + thread._originalPreferredCore = 0; thread.ObjSyncResult = KernelResult.InvalidState; thread.ReleaseAndResume(); @@ -1116,5 +1246,113 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading Owner?.DecrementThreadCountAndTerminateIfZero(); } + + public void Pin() + { + IsPinned = true; + _coreMigrationDisableCount++; + + int activeCore = ActiveCore; + + _originalPreferredCore = PreferredCore; + _originalAffinityMask = AffinityMask; + + ActiveCore = CurrentCore; + PreferredCore = CurrentCore; + AffinityMask = 1 << CurrentCore; + + if (activeCore != CurrentCore || _originalAffinityMask != AffinityMask) + { + AdjustSchedulingForNewAffinity(_originalAffinityMask, activeCore); + } + + _originalBasePriority = BasePriority; + BasePriority = Math.Min(_originalBasePriority, BitOperations.TrailingZeroCount(Owner.Capabilities.AllowedThreadPriosMask) - 1); + UpdatePriorityInheritance(); + + // Disallows thread pausing + _forcePausePermissionFlags &= ~ThreadSchedState.ThreadPauseFlag; + CombineForcePauseFlags(); + + // TODO: Assign reduced SVC permissions + } + + public void Unpin() + { + IsPinned = false; + _coreMigrationDisableCount--; + + long affinityMask = AffinityMask; + int activeCore = ActiveCore; + + PreferredCore = _originalPreferredCore; + AffinityMask = _originalAffinityMask; + + if (AffinityMask != affinityMask) + { + if ((AffinityMask & 1 << ActiveCore) != 0) + { + if (PreferredCore >= 0) + { + ActiveCore = PreferredCore; + } + else + { + ActiveCore = sizeof(ulong) * 8 - 1 - BitOperations.LeadingZeroCount((ulong)AffinityMask); + } + + AdjustSchedulingForNewAffinity(affinityMask, activeCore); + } + } + + BasePriority = _originalBasePriority; + UpdatePriorityInheritance(); + + if (!TerminationRequested) + { + // Allows thread pausing + _forcePausePermissionFlags |= ThreadSchedState.ThreadPauseFlag; + CombineForcePauseFlags(); + + // TODO: Restore SVC permissions + } + + // Wake up waiters + foreach (KThread waiter in _pinnedWaiters) + { + waiter.ReleaseAndResume(); + } + + _pinnedWaiters.Clear(); + } + + public void SynchronizePreemptionState() + { + KernelContext.CriticalSection.Enter(); + + if (Owner != null && Owner.PinnedThreads[CurrentCore] == this) + { + ClearUserInterruptFlag(); + + Owner.UnpinThread(this); + } + + KernelContext.CriticalSection.Leave(); + } + + public ushort GetUserDisableCount() + { + return Owner.CpuMemory.Read(_tlsAddress + TlsUserDisableCountOffset); + } + + public void SetUserInterruptFlag() + { + Owner.CpuMemory.Write(_tlsAddress + TlsUserInterruptFlagOffset, 1); + } + + public void ClearUserInterruptFlag() + { + Owner.CpuMemory.Write(_tlsAddress + TlsUserInterruptFlagOffset, 0); + } } } \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Threading/ThreadSchedState.cs b/Ryujinx.HLE/HOS/Kernel/Threading/ThreadSchedState.cs index c9eaa6b33..9577075c0 100644 --- a/Ryujinx.HLE/HOS/Kernel/Threading/ThreadSchedState.cs +++ b/Ryujinx.HLE/HOS/Kernel/Threading/ThreadSchedState.cs @@ -4,11 +4,12 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading { LowMask = 0xf, HighMask = 0xfff0, - ForcePauseMask = 0x70, + ForcePauseMask = 0x1f0, ProcessPauseFlag = 1 << 4, ThreadPauseFlag = 1 << 5, ProcessDebugPauseFlag = 1 << 6, + BacktracePauseFlag = 1 << 7, KernelInitPauseFlag = 1 << 8, None = 0, From 512cce6ed33dc7b9f581eb51fe53dcaf4c81abde Mon Sep 17 00:00:00 2001 From: Ac_K Date: Thu, 30 Dec 2021 11:25:46 +0100 Subject: [PATCH 4/7] am: Stub SetMediaPlaybackStateForApplication (#2952) This PR stub `SetMediaPlaybackStateForApplication` of the am service. Accordingly to gdkchan it's needed by the Youtube app. This is checked by RE aswell. --- .../ApplicationProxy/IApplicationFunctions.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/IApplicationFunctions.cs b/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/IApplicationFunctions.cs index 8c2e92989..635408a2c 100644 --- a/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/IApplicationFunctions.cs +++ b/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/IApplicationFunctions.cs @@ -330,6 +330,19 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati return ResultCode.Success; } + [CommandHipc(60)] // 2.0.0+ + // SetMediaPlaybackStateForApplication(bool enabled) + public ResultCode SetMediaPlaybackStateForApplication(ServiceCtx context) + { + bool enabled = context.RequestData.ReadBoolean(); + + // NOTE: Service stores the "enabled" value in a private field, when enabled is false, it stores nn::os::GetSystemTick() too. + + Logger.Stub?.PrintStub(LogClass.ServiceAm, new { enabled }); + + return ResultCode.Success; + } + [CommandHipc(66)] // 3.0.0+ // InitializeGamePlayRecording(u64, handle) public ResultCode InitializeGamePlayRecording(ServiceCtx context) From b4f8ae7a759670c3fa22ff11e468c677fd64a682 Mon Sep 17 00:00:00 2001 From: Ac_K Date: Thu, 30 Dec 2021 11:42:22 +0100 Subject: [PATCH 5/7] friend: Stub IsFriendListCacheAvailable and EnsureFriendListAvailable (#2949) * friend: Stub IsFriendListCacheAvailable and EnsureFriendListAvailable This PR stubs IsFriendListCacheAvailable and EnsureFriendListAvailable call of friend service which close #2896. Sadly, Super Bomberman R Online is still stuck on the loading screen and keep calling `TryPopFromFriendInvitationStorageChannel`, probably because another issue somewhere. * Add FW version * Apply suggestions from gdkchan Co-authored-by: gdkchan * Update IFriendService.cs Co-authored-by: gdkchan --- .../Friend/ServiceCreator/IFriendService.cs | 41 ++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/IFriendService.cs b/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/IFriendService.cs index c3e1d967a..7a98b0e13 100644 --- a/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/IFriendService.cs +++ b/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/IFriendService.cs @@ -120,6 +120,45 @@ namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator return ResultCode.Success; } + [CommandHipc(10120)] // 10.0.0+ + // nn::friends::IsFriendListCacheAvailable(nn::account::Uid userId) -> bool + public ResultCode IsFriendListCacheAvailable(ServiceCtx context) + { + UserId userId = context.RequestData.ReadStruct(); + + if (userId.IsNull) + { + return ResultCode.InvalidArgument; + } + + // TODO: Service mount the friends:/ system savedata and try to load friend.cache file, returns true if exists, false otherwise. + // NOTE: If no cache is available, guest then calls nn::friends::EnsureFriendListAvailable, we can avoid that by faking the cache check. + context.ResponseData.Write(true); + + // TODO: Since we don't support friend features, it's fine to stub it for now. + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { UserId = userId.ToString() }); + + return ResultCode.Success; + } + + [CommandHipc(10121)] // 10.0.0+ + // nn::friends::EnsureFriendListAvailable(nn::account::Uid userId) + public ResultCode EnsureFriendListAvailable(ServiceCtx context) + { + UserId userId = context.RequestData.ReadStruct(); + + if (userId.IsNull) + { + return ResultCode.InvalidArgument; + } + + // TODO: Service mount the friends:/ system savedata and create a friend.cache file for the given user id. + // Since we don't support friend features, it's fine to stub it for now. + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { UserId = userId.ToString() }); + + return ResultCode.Success; + } + [CommandHipc(10400)] // nn::friends::GetBlockedUserListIds(int offset, nn::account::Uid userId) -> (u32, buffer) public ResultCode GetBlockedUserListIds(ServiceCtx context) @@ -311,4 +350,4 @@ namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator return ResultCode.Success; } } -} \ No newline at end of file +} From 1485780d90a554a9a71585ff1dd6e049b32b761e Mon Sep 17 00:00:00 2001 From: gdkchan Date: Thu, 30 Dec 2021 10:00:34 -0300 Subject: [PATCH 6/7] Fix A1B5G5R5 format for good (#2955) --- Ryujinx.Graphics.OpenGL/FormatTable.cs | 2 +- Ryujinx.Graphics.OpenGL/Image/TextureView.cs | 11 ++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/Ryujinx.Graphics.OpenGL/FormatTable.cs b/Ryujinx.Graphics.OpenGL/FormatTable.cs index b673eb48e..e3249cd6f 100644 --- a/Ryujinx.Graphics.OpenGL/FormatTable.cs +++ b/Ryujinx.Graphics.OpenGL/FormatTable.cs @@ -171,7 +171,7 @@ namespace Ryujinx.Graphics.OpenGL Add(Format.B5G6R5Unorm, new FormatInfo(3, true, false, All.Rgb565, PixelFormat.Rgb, PixelType.UnsignedShort565Reversed)); Add(Format.B5G5R5X1Unorm, new FormatInfo(4, true, false, All.Rgb5, PixelFormat.Rgba, PixelType.UnsignedShort1555Reversed)); Add(Format.B5G5R5A1Unorm, new FormatInfo(4, true, false, All.Rgb5A1, PixelFormat.Rgba, PixelType.UnsignedShort1555Reversed)); - Add(Format.A1B5G5R5Unorm, new FormatInfo(4, true, false, All.Rgb5A1, PixelFormat.Bgra, PixelType.UnsignedShort1555Reversed)); + Add(Format.A1B5G5R5Unorm, new FormatInfo(4, true, false, All.Rgb5A1, PixelFormat.Rgba, PixelType.UnsignedShort5551)); Add(Format.B8G8R8X8Unorm, new FormatInfo(4, true, false, All.Rgba8, PixelFormat.Rgba, PixelType.UnsignedByte)); Add(Format.B8G8R8A8Unorm, new FormatInfo(4, true, false, All.Rgba8, PixelFormat.Rgba, PixelType.UnsignedByte)); Add(Format.B8G8R8X8Srgb, new FormatInfo(4, false, false, All.Srgb8, PixelFormat.Rgba, PixelType.UnsignedByte)); diff --git a/Ryujinx.Graphics.OpenGL/Image/TextureView.cs b/Ryujinx.Graphics.OpenGL/Image/TextureView.cs index 137f7d6e2..40ab18ef2 100644 --- a/Ryujinx.Graphics.OpenGL/Image/TextureView.cs +++ b/Ryujinx.Graphics.OpenGL/Image/TextureView.cs @@ -72,7 +72,16 @@ namespace Ryujinx.Graphics.OpenGL.Image (int)Info.SwizzleA.Convert() }; - if (Info.Format.IsBgr()) + if (Info.Format == Format.A1B5G5R5Unorm) + { + int temp = swizzleRgba[0]; + int temp2 = swizzleRgba[1]; + swizzleRgba[0] = swizzleRgba[3]; + swizzleRgba[1] = swizzleRgba[2]; + swizzleRgba[2] = temp2; + swizzleRgba[3] = temp; + } + else if (Info.Format.IsBgr()) { // Swap B <-> R for BGRA formats, as OpenGL has no support for them // and we need to manually swap the components on read/write on the GPU. From c05c8e09d48eb36beef02fda885ec0fd36135463 Mon Sep 17 00:00:00 2001 From: gdkchan Date: Thu, 30 Dec 2021 13:10:54 -0300 Subject: [PATCH 7/7] Add support for the R4G4 texture format (#2956) --- Ryujinx.Graphics.GAL/Capabilities.cs | 3 ++ Ryujinx.Graphics.GAL/Format.cs | 1 + Ryujinx.Graphics.Gpu/Image/FormatTable.cs | 1 + Ryujinx.Graphics.Gpu/Image/Texture.cs | 10 +++-- .../Image/TextureCompatibility.cs | 5 +++ Ryujinx.Graphics.OpenGL/Renderer.cs | 31 ++++++++------- Ryujinx.Graphics.Texture/PixelConverter.cs | 39 +++++++++++++++++++ 7 files changed, 72 insertions(+), 18 deletions(-) create mode 100644 Ryujinx.Graphics.Texture/PixelConverter.cs diff --git a/Ryujinx.Graphics.GAL/Capabilities.cs b/Ryujinx.Graphics.GAL/Capabilities.cs index 20bd87c6c..54a9ae3bf 100644 --- a/Ryujinx.Graphics.GAL/Capabilities.cs +++ b/Ryujinx.Graphics.GAL/Capabilities.cs @@ -6,6 +6,7 @@ namespace Ryujinx.Graphics.GAL public bool HasVectorIndexingBug { get; } public bool SupportsAstcCompression { get; } + public bool SupportsR4G4Format { get; } public bool SupportsFragmentShaderInterlock { get; } public bool SupportsFragmentShaderOrderingIntel { get; } public bool SupportsImageLoadFormatted { get; } @@ -24,6 +25,7 @@ namespace Ryujinx.Graphics.GAL bool hasFrontFacingBug, bool hasVectorIndexingBug, bool supportsAstcCompression, + bool supportsR4G4Format, bool supportsFragmentShaderInterlock, bool supportsFragmentShaderOrderingIntel, bool supportsImageLoadFormatted, @@ -40,6 +42,7 @@ namespace Ryujinx.Graphics.GAL HasFrontFacingBug = hasFrontFacingBug; HasVectorIndexingBug = hasVectorIndexingBug; SupportsAstcCompression = supportsAstcCompression; + SupportsR4G4Format = supportsR4G4Format; SupportsFragmentShaderInterlock = supportsFragmentShaderInterlock; SupportsFragmentShaderOrderingIntel = supportsFragmentShaderOrderingIntel; SupportsImageLoadFormatted = supportsImageLoadFormatted; diff --git a/Ryujinx.Graphics.GAL/Format.cs b/Ryujinx.Graphics.GAL/Format.cs index 98b6f5068..d5e183baf 100644 --- a/Ryujinx.Graphics.GAL/Format.cs +++ b/Ryujinx.Graphics.GAL/Format.cs @@ -58,6 +58,7 @@ namespace Ryujinx.Graphics.GAL D32FloatS8Uint, R8G8B8X8Srgb, R8G8B8A8Srgb, + R4G4Unorm, R4G4B4A4Unorm, R5G5B5X1Unorm, R5G5B5A1Unorm, diff --git a/Ryujinx.Graphics.Gpu/Image/FormatTable.cs b/Ryujinx.Graphics.Gpu/Image/FormatTable.cs index 03687adee..3c97e2e27 100644 --- a/Ryujinx.Graphics.Gpu/Image/FormatTable.cs +++ b/Ryujinx.Graphics.Gpu/Image/FormatTable.cs @@ -58,6 +58,7 @@ namespace Ryujinx.Graphics.Gpu.Image { 0x25385, new FormatInfo(Format.D32FloatS8Uint, 1, 1, 8, 2) }, { 0x253b0, new FormatInfo(Format.D32FloatS8Uint, 1, 1, 8, 2) }, { 0xa4908, new FormatInfo(Format.R8G8B8A8Srgb, 1, 1, 4, 4) }, + { 0x2491e, new FormatInfo(Format.R4G4Unorm, 1, 1, 1, 2) }, { 0x24912, new FormatInfo(Format.R4G4B4A4Unorm, 1, 1, 2, 4) }, { 0x24914, new FormatInfo(Format.R5G5B5A1Unorm, 1, 1, 2, 4) }, { 0x24915, new FormatInfo(Format.R5G6B5Unorm, 1, 1, 2, 3) }, diff --git a/Ryujinx.Graphics.Gpu/Image/Texture.cs b/Ryujinx.Graphics.Gpu/Image/Texture.cs index 590356e34..6d981479a 100644 --- a/Ryujinx.Graphics.Gpu/Image/Texture.cs +++ b/Ryujinx.Graphics.Gpu/Image/Texture.cs @@ -785,7 +785,7 @@ namespace Ryujinx.Graphics.Gpu.Image // Handle compressed cases not supported by the host: // - ASTC is usually not supported on desktop cards. // - BC4/BC5 is not supported on 3D textures. - if (!_context.Capabilities.SupportsAstcCompression && Info.FormatInfo.Format.IsAstc()) + if (!_context.Capabilities.SupportsAstcCompression && Format.IsAstc()) { if (!AstcDecoder.TryDecodeToRgba8P( data.ToArray(), @@ -805,11 +805,15 @@ namespace Ryujinx.Graphics.Gpu.Image data = decoded; } - else if (Target == Target.Texture3D && Info.FormatInfo.Format.IsBc4()) + else if (!_context.Capabilities.SupportsR4G4Format && Format == Format.R4G4Unorm) + { + data = PixelConverter.ConvertR4G4ToR4G4B4A4(data); + } + else if (Target == Target.Texture3D && Format.IsBc4()) { data = BCnDecoder.DecodeBC4(data, width, height, depth, levels, layers, Info.FormatInfo.Format == Format.Bc4Snorm); } - else if (Target == Target.Texture3D && Info.FormatInfo.Format.IsBc5()) + else if (Target == Target.Texture3D && Format.IsBc5()) { data = BCnDecoder.DecodeBC5(data, width, height, depth, levels, layers, Info.FormatInfo.Format == Format.Bc5Snorm); } diff --git a/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs b/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs index c70b29719..ce9fd75c7 100644 --- a/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs +++ b/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs @@ -66,6 +66,11 @@ namespace Ryujinx.Graphics.Gpu.Image } } + if (!caps.SupportsR4G4Format && info.FormatInfo.Format == Format.R4G4Unorm) + { + return new FormatInfo(Format.R4G4B4A4Unorm, 1, 1, 2, 4); + } + if (info.Target == Target.Texture3D) { // The host API does not support 3D BC4/BC5 compressed formats. diff --git a/Ryujinx.Graphics.OpenGL/Renderer.cs b/Ryujinx.Graphics.OpenGL/Renderer.cs index 25b762b2a..0c16ec5a1 100644 --- a/Ryujinx.Graphics.OpenGL/Renderer.cs +++ b/Ryujinx.Graphics.OpenGL/Renderer.cs @@ -101,21 +101,22 @@ namespace Ryujinx.Graphics.OpenGL public Capabilities GetCapabilities() { return new Capabilities( - HwCapabilities.Vendor == HwCapabilities.GpuVendor.IntelWindows, - HwCapabilities.Vendor == HwCapabilities.GpuVendor.AmdWindows, - HwCapabilities.SupportsAstcCompression, - HwCapabilities.SupportsFragmentShaderInterlock, - HwCapabilities.SupportsFragmentShaderOrdering, - HwCapabilities.SupportsImageLoadFormatted, - HwCapabilities.SupportsMismatchingViewFormat, - HwCapabilities.SupportsNonConstantTextureOffset, - HwCapabilities.SupportsShaderBallot, - HwCapabilities.SupportsTextureShadowLod, - HwCapabilities.SupportsViewportSwizzle, - HwCapabilities.SupportsIndirectParameters, - HwCapabilities.MaximumComputeSharedMemorySize, - HwCapabilities.MaximumSupportedAnisotropy, - HwCapabilities.StorageBufferOffsetAlignment); + hasFrontFacingBug: HwCapabilities.Vendor == HwCapabilities.GpuVendor.IntelWindows, + hasVectorIndexingBug: HwCapabilities.Vendor == HwCapabilities.GpuVendor.AmdWindows, + supportsAstcCompression: HwCapabilities.SupportsAstcCompression, + supportsR4G4Format: false, + supportsFragmentShaderInterlock: HwCapabilities.SupportsFragmentShaderInterlock, + supportsFragmentShaderOrderingIntel: HwCapabilities.SupportsFragmentShaderOrdering, + supportsImageLoadFormatted: HwCapabilities.SupportsImageLoadFormatted, + supportsMismatchingViewFormat: HwCapabilities.SupportsMismatchingViewFormat, + supportsNonConstantTextureOffset: HwCapabilities.SupportsNonConstantTextureOffset, + supportsShaderBallot: HwCapabilities.SupportsShaderBallot, + supportsTextureShadowLod: HwCapabilities.SupportsTextureShadowLod, + supportsViewportSwizzle: HwCapabilities.SupportsViewportSwizzle, + supportsIndirectParameters: HwCapabilities.SupportsIndirectParameters, + maximumComputeSharedMemorySize: HwCapabilities.MaximumComputeSharedMemorySize, + maximumSupportedAnisotropy: HwCapabilities.MaximumSupportedAnisotropy, + storageBufferOffsetAlignment: HwCapabilities.StorageBufferOffsetAlignment); } public void SetBufferData(BufferHandle buffer, int offset, ReadOnlySpan data) diff --git a/Ryujinx.Graphics.Texture/PixelConverter.cs b/Ryujinx.Graphics.Texture/PixelConverter.cs new file mode 100644 index 000000000..d7e45a693 --- /dev/null +++ b/Ryujinx.Graphics.Texture/PixelConverter.cs @@ -0,0 +1,39 @@ +using System; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; + +namespace Ryujinx.Graphics.Texture +{ + public static class PixelConverter + { + public unsafe static byte[] ConvertR4G4ToR4G4B4A4(ReadOnlySpan data) + { + byte[] output = new byte[data.Length * 2]; + int start = 0; + + if (Sse41.IsSupported) + { + int sizeTrunc = data.Length & ~7; + start = sizeTrunc; + + fixed (byte* inputPtr = data, outputPtr = output) + { + for (ulong offset = 0; offset < (ulong)sizeTrunc; offset += 8) + { + Sse2.Store(outputPtr + offset * 2, Sse41.ConvertToVector128Int16(inputPtr + offset).AsByte()); + } + } + } + + Span outputSpan = MemoryMarshal.Cast(output); + + for (int i = start; i < data.Length; i++) + { + outputSpan[i] = (ushort)data[i]; + } + + return output; + } + } +}