From e5858e2c7d0a46b771e874e37efb77f69120c141 Mon Sep 17 00:00:00 2001 From: gdkchan Date: Sat, 14 Dec 2019 18:18:51 -0300 Subject: [PATCH 01/10] Add a limit for the number of instructions in a function (#843) --- ARMeilleure/Decoders/Decoder.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/ARMeilleure/Decoders/Decoder.cs b/ARMeilleure/Decoders/Decoder.cs index 2311e9e96e..8eb2a99d6d 100644 --- a/ARMeilleure/Decoders/Decoder.cs +++ b/ARMeilleure/Decoders/Decoder.cs @@ -10,6 +10,11 @@ namespace ARMeilleure.Decoders { static class Decoder { + // We define a limit on the number of instructions that a function may have, + // this prevents functions being potentially too large, which would + // take too long to compile and use too much memory. + private const int MaxInstsPerFunction = 5000; + private delegate object MakeOp(InstDescriptor inst, ulong address, int opCode); private static ConcurrentDictionary _opActivators; @@ -36,10 +41,17 @@ namespace ARMeilleure.Decoders Dictionary visited = new Dictionary(); + int opsCount = 0; + Block GetBlock(ulong blkAddress) { if (!visited.TryGetValue(blkAddress, out Block block)) { + if (opsCount > MaxInstsPerFunction) + { + return null; + } + block = new Block(blkAddress); workQueue.Enqueue(block); @@ -92,6 +104,8 @@ namespace ARMeilleure.Decoders FillBlock(memory, mode, currBlock, limitAddress); + opsCount += currBlock.OpCodes.Count; + if (currBlock.OpCodes.Count != 0) { // Set child blocks. "Branch" is the block the branch instruction From 01a4c80ed5ef255a67c379fb847230c9b21efc47 Mon Sep 17 00:00:00 2001 From: Thog Date: Sat, 21 Dec 2019 20:52:31 +0100 Subject: [PATCH 02/10] Rewrite the configuration system (#831) The configuration system was quite fragile and too dependent on everything, this fix #812 . The changes: The file configuration is now entirely independent from the internal configuration state. The file configuration is versioned (current version is 1). Every configuration elements are now reactive properties that the emulator can register on to handle initialization and configuration changes. The configuration system is now in Ryujinx.Common to be accessible on every projects. Discord integration is now independent from the UI and can be reloaded. The primary controller is now configurable at runtime (NOTE: the UI currently doesn't have any options to configure real controller). The logger is entirely reloadable. You can now hotplug your controller when the emulator is running. The logger now takes name for every LogTarget to make them removable at runtime. The logger now always add the default "console" target to avoid loosing early init logs. The configuration system now generates a default file configuration if it's missing or too new. General system stability improvements to enhance the user's experience --- .../Configuration/ConfigurationFileFormat.cs | 146 +---- .../Configuration/ConfigurationState.cs | 502 ++++++++++++++++++ .../Configuration/Hid/ControllerInputId.cs | 45 ++ .../Configuration/Hid/ControllerType.cs | 11 + Ryujinx.Common/Configuration/Hid/Key.cs | 153 ++++++ .../Configuration/Hid/KeyboardHotkeys.cs | 7 + .../Configuration/Hid/NpadController.cs | 35 ++ .../Configuration/Hid/NpadControllerLeft.cs | 15 + .../Configuration/Hid/NpadControllerRight.cs | 15 + .../Configuration/Hid/NpadKeyboard.cs | 20 + .../Configuration/Hid/NpadKeyboardLeft.cs | 18 + .../Configuration/Hid/NpadKeyboardRight.cs | 18 + Ryujinx.Common/Configuration/LoggerModule.cs | 109 ++++ .../Configuration/System/Language.cs | 23 + .../Configuration}/Ui/GuiColumns.cs | 2 +- Ryujinx.Common/Logging/Logger.cs | 33 ++ .../Logging/Targets/AsyncLogTargetWrapper.cs | 2 + .../Logging/Targets/ConsoleLogTarget.cs | 7 +- .../Logging/Targets/FileLogTarget.cs | 13 +- Ryujinx.Common/Logging/Targets/ILogTarget.cs | 2 + .../Logging/Targets/JsonLogTarget.cs | 9 +- Ryujinx.Common/ReactiveObject.cs | 57 ++ Ryujinx.Common/Ryujinx.Common.csproj | 1 + Ryujinx.HLE/Input/Hid.cs | 28 +- Ryujinx.HLE/Switch.cs | 27 + Ryujinx/Config.json | 9 +- .../Configuration/DiscordIntegrationModule.cs | 92 ++++ Ryujinx/Program.cs | 47 +- Ryujinx/Ryujinx.csproj | 1 - Ryujinx/Ui/GLScreen.cs | 26 +- .../{NpadKeyboard.cs => KeyboardControls.cs} | 119 ++--- Ryujinx/Ui/MainWindow.cs | 290 ++++------ Ryujinx/Ui/NpadController.cs | 175 ++---- Ryujinx/Ui/SwitchSettings.cs | 275 ++++++---- 34 files changed, 1625 insertions(+), 707 deletions(-) rename Ryujinx/Configuration.cs => Ryujinx.Common/Configuration/ConfigurationFileFormat.cs (53%) create mode 100644 Ryujinx.Common/Configuration/ConfigurationState.cs create mode 100644 Ryujinx.Common/Configuration/Hid/ControllerInputId.cs create mode 100644 Ryujinx.Common/Configuration/Hid/ControllerType.cs create mode 100644 Ryujinx.Common/Configuration/Hid/Key.cs create mode 100644 Ryujinx.Common/Configuration/Hid/KeyboardHotkeys.cs create mode 100644 Ryujinx.Common/Configuration/Hid/NpadController.cs create mode 100644 Ryujinx.Common/Configuration/Hid/NpadControllerLeft.cs create mode 100644 Ryujinx.Common/Configuration/Hid/NpadControllerRight.cs create mode 100644 Ryujinx.Common/Configuration/Hid/NpadKeyboard.cs create mode 100644 Ryujinx.Common/Configuration/Hid/NpadKeyboardLeft.cs create mode 100644 Ryujinx.Common/Configuration/Hid/NpadKeyboardRight.cs create mode 100644 Ryujinx.Common/Configuration/LoggerModule.cs create mode 100644 Ryujinx.Common/Configuration/System/Language.cs rename {Ryujinx => Ryujinx.Common/Configuration}/Ui/GuiColumns.cs (90%) create mode 100644 Ryujinx.Common/ReactiveObject.cs create mode 100644 Ryujinx/Configuration/DiscordIntegrationModule.cs rename Ryujinx/Ui/{NpadKeyboard.cs => KeyboardControls.cs} (74%) diff --git a/Ryujinx/Configuration.cs b/Ryujinx.Common/Configuration/ConfigurationFileFormat.cs similarity index 53% rename from Ryujinx/Configuration.cs rename to Ryujinx.Common/Configuration/ConfigurationFileFormat.cs index c259f9e9d4..1a9407cb2f 100644 --- a/Ryujinx/Configuration.cs +++ b/Ryujinx.Common/Configuration/ConfigurationFileFormat.cs @@ -1,30 +1,22 @@ using JsonPrettyPrinterPlus; -using LibHac.FsSystem; -using OpenTK.Input; -using Ryujinx.Common; using Ryujinx.Common.Logging; -using Ryujinx.HLE; -using Ryujinx.HLE.HOS.SystemState; -using Ryujinx.HLE.HOS.Services; -using Ryujinx.HLE.Input; -using Ryujinx.Ui; -using Ryujinx.Ui.Input; using System; using System.Collections.Generic; using System.IO; using System.Text; -using System.Threading.Tasks; using Utf8Json; using Utf8Json.Resolvers; +using Ryujinx.Configuration.System; +using Ryujinx.Configuration.Hid; +using Ryujinx.Common.Configuration.Hid; +using Ryujinx.UI.Input; +using Ryujinx.Configuration.Ui; -namespace Ryujinx +namespace Ryujinx.Configuration { - public class Configuration + public class ConfigurationFileFormat { - /// - /// The default configuration instance - /// - public static Configuration Instance { get; private set; } + public int Version { get; set; } /// /// Dumps shaders in this local directory @@ -79,7 +71,7 @@ namespace Ryujinx /// /// Change System Language /// - public SystemLanguage SystemLanguage { get; set; } + public Language SystemLanguage { get; set; } /// /// Enables or disables Docked Mode @@ -119,7 +111,7 @@ namespace Ryujinx /// /// The primary controller's type /// - public ControllerStatus ControllerType { get; set; } + public ControllerType ControllerType { get; set; } /// /// Used to toggle columns in the GUI @@ -154,13 +146,13 @@ namespace Ryujinx /// /// Controller control bindings /// - public Ui.Input.NpadController JoystickControls { get; private set; } + public NpadController JoystickControls { get; set; } /// /// Loads a configuration file from disk /// /// The path to the JSON configuration file - public static void Load(string path) + public static ConfigurationFileFormat Load(string path) { var resolver = CompositeResolver.Create( new[] { new ConfigurationEnumFormatter() }, @@ -169,24 +161,7 @@ namespace Ryujinx using (Stream stream = File.OpenRead(path)) { - Instance = JsonSerializer.Deserialize(stream, resolver); - } - } - - /// - /// Loads a configuration file asynchronously from disk - /// - /// The path to the JSON configuration file - public static async Task LoadAsync(string path) - { - IJsonFormatterResolver resolver = CompositeResolver.Create( - new[] { new ConfigurationEnumFormatter() }, - new[] { StandardResolver.AllowPrivateSnakeCase } - ); - - using (Stream stream = File.OpenRead(path)) - { - Instance = await JsonSerializer.DeserializeAsync(stream, resolver); + return JsonSerializer.Deserialize(stream, resolver); } } @@ -194,108 +169,17 @@ namespace Ryujinx /// Save a configuration file to disk /// /// The path to the JSON configuration file - public static void SaveConfig(Configuration config, string path) + public void SaveConfig(string path) { IJsonFormatterResolver resolver = CompositeResolver.Create( new[] { new ConfigurationEnumFormatter() }, new[] { StandardResolver.AllowPrivateSnakeCase } ); - byte[] data = JsonSerializer.Serialize(config, resolver); + byte[] data = JsonSerializer.Serialize(this, resolver); File.WriteAllText(path, Encoding.UTF8.GetString(data, 0, data.Length).PrettyPrintJson()); } - /// - /// Configures a instance - /// - /// The instance to configure - public static void InitialConfigure(Switch device) - { - if (Instance == null) - { - throw new InvalidOperationException("Configuration has not been loaded yet."); - } - - SwitchSettings.ConfigureSettings(Instance); - - Logger.AddTarget(new AsyncLogTargetWrapper( - new ConsoleLogTarget(), - 1000, - AsyncLogTargetOverflowAction.Block - )); - - if (Instance.EnableFileLog) - { - Logger.AddTarget(new AsyncLogTargetWrapper( - new FileLogTarget(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Ryujinx.log")), - 1000, - AsyncLogTargetOverflowAction.Block - )); - } - - Configure(device, Instance); - } - - public static void Configure(Switch device, Configuration SwitchConfig) - { - GraphicsConfig.ShadersDumpPath = SwitchConfig.GraphicsShadersDumpPath; - - Logger.SetEnable(LogLevel.Debug, SwitchConfig.LoggingEnableDebug ); - Logger.SetEnable(LogLevel.Stub, SwitchConfig.LoggingEnableStub ); - Logger.SetEnable(LogLevel.Info, SwitchConfig.LoggingEnableInfo ); - Logger.SetEnable(LogLevel.Warning, SwitchConfig.LoggingEnableWarn ); - Logger.SetEnable(LogLevel.Error, SwitchConfig.LoggingEnableError ); - Logger.SetEnable(LogLevel.Guest, SwitchConfig.LoggingEnableGuest ); - Logger.SetEnable(LogLevel.AccessLog, SwitchConfig.LoggingEnableFsAccessLog); - - if (SwitchConfig.LoggingFilteredClasses.Length > 0) - { - foreach (var logClass in EnumExtensions.GetValues()) - { - Logger.SetEnable(logClass, false); - } - - foreach (var logClass in SwitchConfig.LoggingFilteredClasses) - { - Logger.SetEnable(logClass, true); - } - } - - MainWindow.DiscordIntegrationEnabled = SwitchConfig.EnableDiscordIntegration; - - device.EnableDeviceVsync = SwitchConfig.EnableVsync; - - device.System.State.DockedMode = SwitchConfig.DockedMode; - - device.System.State.SetLanguage(SwitchConfig.SystemLanguage); - - if (SwitchConfig.EnableMulticoreScheduling) - { - device.System.EnableMultiCoreScheduling(); - } - - device.System.FsIntegrityCheckLevel = SwitchConfig.EnableFsIntegrityChecks - ? IntegrityCheckLevel.ErrorOnInvalid - : IntegrityCheckLevel.None; - - device.System.GlobalAccessLogMode = SwitchConfig.FsGlobalAccessLogMode; - - ServiceConfiguration.IgnoreMissingServices = SwitchConfig.IgnoreMissingServices; - } - - public static void ConfigureHid(Switch device, Configuration SwitchConfig) - { - if (SwitchConfig.JoystickControls.Enabled) - { - if (!Joystick.GetState(SwitchConfig.JoystickControls.Index).IsConnected) - { - SwitchConfig.JoystickControls.SetEnabled(false); - } - } - device.Hid.InitializePrimaryController(SwitchConfig.ControllerType); - device.Hid.InitializeKeyboard(); - } - private class ConfigurationEnumFormatter : IJsonFormatter where T : struct { diff --git a/Ryujinx.Common/Configuration/ConfigurationState.cs b/Ryujinx.Common/Configuration/ConfigurationState.cs new file mode 100644 index 0000000000..050b497385 --- /dev/null +++ b/Ryujinx.Common/Configuration/ConfigurationState.cs @@ -0,0 +1,502 @@ +using Ryujinx.Common; +using Ryujinx.Common.Configuration.Hid; +using Ryujinx.Common.Logging; +using Ryujinx.Configuration.Hid; +using Ryujinx.Configuration.System; +using Ryujinx.Configuration.Ui; +using Ryujinx.UI.Input; +using System; +using System.Collections.Generic; + +namespace Ryujinx.Configuration +{ + public class ConfigurationState + { + /// + /// UI configuration section + /// + public class UiSection + { + public class Columns + { + public ReactiveObject FavColumn { get; private set; } + public ReactiveObject IconColumn { get; private set; } + public ReactiveObject AppColumn { get; private set; } + public ReactiveObject DevColumn { get; private set; } + public ReactiveObject VersionColumn { get; private set; } + public ReactiveObject TimePlayedColumn { get; private set; } + public ReactiveObject LastPlayedColumn { get; private set; } + public ReactiveObject FileExtColumn { get; private set; } + public ReactiveObject FileSizeColumn { get; private set; } + public ReactiveObject PathColumn { get; private set; } + + public Columns() + { + FavColumn = new ReactiveObject(); + IconColumn = new ReactiveObject(); + AppColumn = new ReactiveObject(); + DevColumn = new ReactiveObject(); + VersionColumn = new ReactiveObject(); + TimePlayedColumn = new ReactiveObject(); + LastPlayedColumn = new ReactiveObject(); + FileExtColumn = new ReactiveObject(); + FileSizeColumn = new ReactiveObject(); + PathColumn = new ReactiveObject(); + } + } + + /// + /// Used to toggle columns in the GUI + /// + public Columns GuiColumns { get; private set; } + + /// + /// A list of directories containing games to be used to load games into the games list + /// + public ReactiveObject> GameDirs { get; private set; } + + /// + /// Enable or disable custom themes in the GUI + /// + public ReactiveObject EnableCustomTheme { get; private set; } + + /// + /// Path to custom GUI theme + /// + public ReactiveObject CustomThemePath { get; private set; } + + public UiSection() + { + GuiColumns = new Columns(); + GameDirs = new ReactiveObject>(); + EnableCustomTheme = new ReactiveObject(); + CustomThemePath = new ReactiveObject(); + } + } + + /// + /// Logger configuration section + /// + public class LoggerSection + { + /// + /// Enables printing debug log messages + /// + public ReactiveObject EnableDebug { get; private set; } + + /// + /// Enables printing stub log messages + /// + public ReactiveObject EnableStub { get; private set; } + + /// + /// Enables printing info log messages + /// + public ReactiveObject EnableInfo { get; private set; } + + /// + /// Enables printing warning log messages + /// + public ReactiveObject EnableWarn { get; private set; } + + /// + /// Enables printing error log messages + /// + public ReactiveObject EnableError { get; private set; } + + /// + /// Enables printing guest log messages + /// + public ReactiveObject EnableGuest { get; private set; } + + /// + /// Enables printing FS access log messages + /// + public ReactiveObject EnableFsAccessLog { get; private set; } + + /// + /// Controls which log messages are written to the log targets + /// + public ReactiveObject FilteredClasses { get; private set; } + + /// + /// Enables or disables logging to a file on disk + /// + public ReactiveObject EnableFileLog { get; private set; } + + public LoggerSection() + { + EnableDebug = new ReactiveObject(); + EnableStub = new ReactiveObject(); + EnableInfo = new ReactiveObject(); + EnableWarn = new ReactiveObject(); + EnableError = new ReactiveObject(); + EnableGuest = new ReactiveObject(); + EnableFsAccessLog = new ReactiveObject(); + FilteredClasses = new ReactiveObject(); + EnableFileLog = new ReactiveObject(); + } + } + + /// + /// System configuration section + /// + public class SystemSection + { + /// + /// Change System Language + /// + public ReactiveObject Language { get; private set; } + + /// + /// Enables or disables Docked Mode + /// + public ReactiveObject EnableDockedMode { get; private set; } + + /// + /// Enables or disables multi-core scheduling of threads + /// + public ReactiveObject EnableMulticoreScheduling { get; private set; } + + /// + /// Enables integrity checks on Game content files + /// + public ReactiveObject EnableFsIntegrityChecks { get; private set; } + + /// + /// Enables FS access log output to the console. Possible modes are 0-3 + /// + public ReactiveObject FsGlobalAccessLogMode { get; private set; } + + /// + /// Enable or disable ignoring missing services + /// + public ReactiveObject IgnoreMissingServices { get; private set; } + + public SystemSection() + { + Language = new ReactiveObject(); + EnableDockedMode = new ReactiveObject(); + EnableMulticoreScheduling = new ReactiveObject(); + EnableFsIntegrityChecks = new ReactiveObject(); + FsGlobalAccessLogMode = new ReactiveObject(); + IgnoreMissingServices = new ReactiveObject(); + } + } + + /// + /// Hid configuration section + /// + public class HidSection + { + /// + /// The primary controller's type + /// + public ReactiveObject ControllerType { get; private set; } + + /// + /// Enable or disable keyboard support (Independent from controllers binding) + /// + public ReactiveObject EnableKeyboard { get; private set; } + + /// + /// Keyboard control bindings + /// + public ReactiveObject KeyboardControls { get; private set; } + + /// + /// Controller control bindings + /// + public ReactiveObject JoystickControls { get; private set; } + + public HidSection() + { + ControllerType = new ReactiveObject(); + EnableKeyboard = new ReactiveObject(); + KeyboardControls = new ReactiveObject(); + JoystickControls = new ReactiveObject(); + } + } + + /// + /// Graphics configuration section + /// + public class GraphicsSection + { + /// + /// Dumps shaders in this local directory + /// + public ReactiveObject ShadersDumpPath { get; private set; } + + /// + /// Enables or disables Vertical Sync + /// + public ReactiveObject EnableVsync { get; private set; } + + public GraphicsSection() + { + ShadersDumpPath = new ReactiveObject(); + EnableVsync = new ReactiveObject(); + } + } + + /// + /// The default configuration instance + /// + public static ConfigurationState Instance { get; private set; } + + /// + /// The Ui section + /// + public UiSection Ui { get; private set; } + + /// + /// The Logger section + /// + public LoggerSection Logger { get; private set; } + + /// + /// The System section + /// + public SystemSection System { get; private set; } + + /// + /// The Graphics section + /// + public GraphicsSection Graphics { get; private set; } + + /// + /// The Hid section + /// + public HidSection Hid { get; private set; } + + /// + /// Enables or disables Discord Rich Presence + /// + public ReactiveObject EnableDiscordIntegration { get; private set; } + + private ConfigurationState() + { + Ui = new UiSection(); + Logger = new LoggerSection(); + System = new SystemSection(); + Graphics = new GraphicsSection(); + Hid = new HidSection(); + EnableDiscordIntegration = new ReactiveObject(); + } + + public ConfigurationFileFormat ToFileFormat() + { + ConfigurationFileFormat configurationFile = new ConfigurationFileFormat + { + Version = 1, + GraphicsShadersDumpPath = Graphics.ShadersDumpPath, + LoggingEnableDebug = Logger.EnableDebug, + LoggingEnableStub = Logger.EnableStub, + LoggingEnableInfo = Logger.EnableInfo, + LoggingEnableWarn = Logger.EnableWarn, + LoggingEnableError = Logger.EnableError, + LoggingEnableGuest = Logger.EnableGuest, + LoggingEnableFsAccessLog = Logger.EnableFsAccessLog, + LoggingFilteredClasses = Logger.FilteredClasses, + EnableFileLog = Logger.EnableFileLog, + SystemLanguage = System.Language, + DockedMode = System.EnableDockedMode, + EnableDiscordIntegration = EnableDiscordIntegration, + EnableVsync = Graphics.EnableVsync, + EnableMulticoreScheduling = System.EnableMulticoreScheduling, + EnableFsIntegrityChecks = System.EnableFsIntegrityChecks, + FsGlobalAccessLogMode = System.FsGlobalAccessLogMode, + IgnoreMissingServices = System.IgnoreMissingServices, + ControllerType = Hid.ControllerType, + GuiColumns = new GuiColumns() + { + FavColumn = Ui.GuiColumns.FavColumn, + IconColumn = Ui.GuiColumns.IconColumn, + AppColumn = Ui.GuiColumns.AppColumn, + DevColumn = Ui.GuiColumns.DevColumn, + VersionColumn = Ui.GuiColumns.VersionColumn, + TimePlayedColumn = Ui.GuiColumns.TimePlayedColumn, + LastPlayedColumn = Ui.GuiColumns.LastPlayedColumn, + FileExtColumn = Ui.GuiColumns.FileExtColumn, + FileSizeColumn = Ui.GuiColumns.FileSizeColumn, + PathColumn = Ui.GuiColumns.PathColumn, + }, + GameDirs = Ui.GameDirs, + EnableCustomTheme = Ui.EnableCustomTheme, + CustomThemePath = Ui.CustomThemePath, + EnableKeyboard = Hid.EnableKeyboard, + KeyboardControls = Hid.KeyboardControls, + JoystickControls = Hid.JoystickControls + }; + + return configurationFile; + } + + public void LoadDefault() + { + Graphics.ShadersDumpPath.Value = ""; + Logger.EnableDebug.Value = false; + Logger.EnableStub.Value = true; + Logger.EnableInfo.Value = true; + Logger.EnableWarn.Value = true; + Logger.EnableError.Value = true; + Logger.EnableGuest.Value = true; + Logger.EnableFsAccessLog.Value = false; + Logger.FilteredClasses.Value = new LogClass[] { }; + Logger.EnableFileLog.Value = true; + System.Language.Value = Language.AmericanEnglish; + System.EnableDockedMode.Value = false; + EnableDiscordIntegration.Value = true; + Graphics.EnableVsync.Value = true; + System.EnableMulticoreScheduling.Value = true; + System.EnableFsIntegrityChecks.Value = true; + System.FsGlobalAccessLogMode.Value = 0; + System.IgnoreMissingServices.Value = false; + Hid.ControllerType.Value = ControllerType.Handheld; + Ui.GuiColumns.FavColumn.Value = true; + Ui.GuiColumns.IconColumn.Value = true; + Ui.GuiColumns.AppColumn.Value = true; + Ui.GuiColumns.DevColumn.Value = true; + Ui.GuiColumns.VersionColumn.Value = true; + Ui.GuiColumns.TimePlayedColumn.Value = true; + Ui.GuiColumns.LastPlayedColumn.Value = true; + Ui.GuiColumns.FileExtColumn.Value = true; + Ui.GuiColumns.FileSizeColumn.Value = true; + Ui.GuiColumns.PathColumn.Value = true; + Ui.GameDirs.Value = new List(); + Ui.EnableCustomTheme.Value = false; + Ui.CustomThemePath.Value = ""; + Hid.EnableKeyboard.Value = false; + + Hid.KeyboardControls.Value = new NpadKeyboard + { + LeftJoycon = new NpadKeyboardLeft + { + StickUp = Key.W, + StickDown = Key.S, + StickLeft = Key.A, + StickRight = Key.D, + StickButton = Key.F, + DPadUp = Key.Up, + DPadDown = Key.Down, + DPadLeft = Key.Left, + DPadRight = Key.Right, + ButtonMinus = Key.Minus, + ButtonL = Key.E, + ButtonZl = Key.Q, + }, + RightJoycon = new NpadKeyboardRight + { + StickUp = Key.I, + StickDown = Key.K, + StickLeft = Key.J, + StickRight = Key.L, + StickButton = Key.H, + ButtonA = Key.Z, + ButtonB = Key.X, + ButtonX = Key.C, + ButtonY = Key.V, + ButtonPlus = Key.Plus, + ButtonR = Key.U, + ButtonZr = Key.O, + }, + Hotkeys = new KeyboardHotkeys + { + ToggleVsync = Key.Tab + } + }; + + Hid.JoystickControls.Value = new NpadController + { + Enabled = true, + Index = 0, + Deadzone = 0.05f, + TriggerThreshold = 0.5f, + LeftJoycon = new NpadControllerLeft + { + Stick = ControllerInputId.Axis0, + StickButton = ControllerInputId.Button8, + DPadUp = ControllerInputId.Hat0Up, + DPadDown = ControllerInputId.Hat0Down, + DPadLeft = ControllerInputId.Hat0Left, + DPadRight = ControllerInputId.Hat0Right, + ButtonMinus = ControllerInputId.Button6, + ButtonL = ControllerInputId.Button4, + ButtonZl = ControllerInputId.Axis2, + }, + RightJoycon = new NpadControllerRight + { + Stick = ControllerInputId.Axis3, + StickButton = ControllerInputId.Button9, + ButtonA = ControllerInputId.Button1, + ButtonB = ControllerInputId.Button0, + ButtonX = ControllerInputId.Button3, + ButtonY = ControllerInputId.Button2, + ButtonPlus = ControllerInputId.Button7, + ButtonR = ControllerInputId.Button5, + ButtonZr = ControllerInputId.Axis5, + } + }; + } + + public void Load(ConfigurationFileFormat configurationFileFormat) + { + if (configurationFileFormat.Version != 1 && configurationFileFormat.Version != 0) + { + Common.Logging.Logger.PrintWarning(LogClass.Application, $"Unsupported configuration version {configurationFileFormat.Version}, loading default."); + + LoadDefault(); + + return; + } + + Graphics.ShadersDumpPath.Value = configurationFileFormat.GraphicsShadersDumpPath; + Logger.EnableDebug.Value = configurationFileFormat.LoggingEnableDebug; + Logger.EnableStub.Value = configurationFileFormat.LoggingEnableStub; + Logger.EnableInfo.Value = configurationFileFormat.LoggingEnableInfo; + Logger.EnableWarn.Value = configurationFileFormat.LoggingEnableWarn; + Logger.EnableError.Value = configurationFileFormat.LoggingEnableError; + Logger.EnableGuest.Value = configurationFileFormat.LoggingEnableGuest; + Logger.EnableFsAccessLog.Value = configurationFileFormat.LoggingEnableFsAccessLog; + Logger.FilteredClasses.Value = configurationFileFormat.LoggingFilteredClasses; + Logger.EnableFileLog.Value = configurationFileFormat.EnableFileLog; + System.Language.Value = configurationFileFormat.SystemLanguage; + System.EnableDockedMode.Value = configurationFileFormat.DockedMode; + System.EnableDockedMode.Value = configurationFileFormat.DockedMode; + EnableDiscordIntegration.Value = configurationFileFormat.EnableDiscordIntegration; + Graphics.EnableVsync.Value = configurationFileFormat.EnableVsync; + System.EnableMulticoreScheduling.Value = configurationFileFormat.EnableMulticoreScheduling; + System.EnableFsIntegrityChecks.Value = configurationFileFormat.EnableFsIntegrityChecks; + System.FsGlobalAccessLogMode.Value = configurationFileFormat.FsGlobalAccessLogMode; + System.IgnoreMissingServices.Value = configurationFileFormat.IgnoreMissingServices; + Hid.ControllerType.Value = configurationFileFormat.ControllerType; + Ui.GuiColumns.FavColumn.Value = configurationFileFormat.GuiColumns.FavColumn; + Ui.GuiColumns.IconColumn.Value = configurationFileFormat.GuiColumns.IconColumn; + Ui.GuiColumns.AppColumn.Value = configurationFileFormat.GuiColumns.AppColumn; + Ui.GuiColumns.DevColumn.Value = configurationFileFormat.GuiColumns.DevColumn; + Ui.GuiColumns.VersionColumn.Value = configurationFileFormat.GuiColumns.VersionColumn; + Ui.GuiColumns.TimePlayedColumn.Value = configurationFileFormat.GuiColumns.TimePlayedColumn; + Ui.GuiColumns.LastPlayedColumn.Value = configurationFileFormat.GuiColumns.LastPlayedColumn; + Ui.GuiColumns.FileExtColumn.Value = configurationFileFormat.GuiColumns.FileExtColumn; + Ui.GuiColumns.FileSizeColumn.Value = configurationFileFormat.GuiColumns.FileSizeColumn; + Ui.GuiColumns.PathColumn.Value = configurationFileFormat.GuiColumns.PathColumn; + Ui.GameDirs.Value = configurationFileFormat.GameDirs; + Ui.EnableCustomTheme.Value = configurationFileFormat.EnableCustomTheme; + Ui.CustomThemePath.Value = configurationFileFormat.CustomThemePath; + Hid.EnableKeyboard.Value = configurationFileFormat.EnableKeyboard; + Hid.KeyboardControls.Value = configurationFileFormat.KeyboardControls; + Hid.JoystickControls.Value = configurationFileFormat.JoystickControls; + } + + public static void Initialize() + { + if (Instance != null) + { + throw new InvalidOperationException("Configuration is already initialized"); + } + + Instance = new ConfigurationState(); + } + } +} diff --git a/Ryujinx.Common/Configuration/Hid/ControllerInputId.cs b/Ryujinx.Common/Configuration/Hid/ControllerInputId.cs new file mode 100644 index 0000000000..8969b6a4b8 --- /dev/null +++ b/Ryujinx.Common/Configuration/Hid/ControllerInputId.cs @@ -0,0 +1,45 @@ +namespace Ryujinx.Common.Configuration.Hid +{ + public enum ControllerInputId + { + Button0, + Button1, + Button2, + Button3, + Button4, + Button5, + Button6, + Button7, + Button8, + Button9, + Button10, + Button11, + Button12, + Button13, + Button14, + Button15, + Button16, + Button17, + Button18, + Button19, + Button20, + Axis0, + Axis1, + Axis2, + Axis3, + Axis4, + Axis5, + Hat0Up, + Hat0Down, + Hat0Left, + Hat0Right, + Hat1Up, + Hat1Down, + Hat1Left, + Hat1Right, + Hat2Up, + Hat2Down, + Hat2Left, + Hat2Right + } +} diff --git a/Ryujinx.Common/Configuration/Hid/ControllerType.cs b/Ryujinx.Common/Configuration/Hid/ControllerType.cs new file mode 100644 index 0000000000..b0613b2d66 --- /dev/null +++ b/Ryujinx.Common/Configuration/Hid/ControllerType.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.Configuration.Hid +{ + public enum ControllerType + { + ProController, + Handheld, + NpadPair, + NpadLeft, + NpadRight + } +} diff --git a/Ryujinx.Common/Configuration/Hid/Key.cs b/Ryujinx.Common/Configuration/Hid/Key.cs new file mode 100644 index 0000000000..b658396b9b --- /dev/null +++ b/Ryujinx.Common/Configuration/Hid/Key.cs @@ -0,0 +1,153 @@ +namespace Ryujinx.Configuration.Hid +{ + public enum Key + { + Unknown = 0, + ShiftLeft = 1, + LShift = 1, + ShiftRight = 2, + RShift = 2, + ControlLeft = 3, + LControl = 3, + ControlRight = 4, + RControl = 4, + AltLeft = 5, + LAlt = 5, + AltRight = 6, + RAlt = 6, + WinLeft = 7, + LWin = 7, + WinRight = 8, + RWin = 8, + Menu = 9, + F1 = 10, + F2 = 11, + F3 = 12, + F4 = 13, + F5 = 14, + F6 = 15, + F7 = 16, + F8 = 17, + F9 = 18, + F10 = 19, + F11 = 20, + F12 = 21, + F13 = 22, + F14 = 23, + F15 = 24, + F16 = 25, + F17 = 26, + F18 = 27, + F19 = 28, + F20 = 29, + F21 = 30, + F22 = 31, + F23 = 32, + F24 = 33, + F25 = 34, + F26 = 35, + F27 = 36, + F28 = 37, + F29 = 38, + F30 = 39, + F31 = 40, + F32 = 41, + F33 = 42, + F34 = 43, + F35 = 44, + Up = 45, + Down = 46, + Left = 47, + Right = 48, + Enter = 49, + Escape = 50, + Space = 51, + Tab = 52, + BackSpace = 53, + Back = 53, + Insert = 54, + Delete = 55, + PageUp = 56, + PageDown = 57, + Home = 58, + End = 59, + CapsLock = 60, + ScrollLock = 61, + PrintScreen = 62, + Pause = 63, + NumLock = 64, + Clear = 65, + Sleep = 66, + Keypad0 = 67, + Keypad1 = 68, + Keypad2 = 69, + Keypad3 = 70, + Keypad4 = 71, + Keypad5 = 72, + Keypad6 = 73, + Keypad7 = 74, + Keypad8 = 75, + Keypad9 = 76, + KeypadDivide = 77, + KeypadMultiply = 78, + KeypadSubtract = 79, + KeypadMinus = 79, + KeypadAdd = 80, + KeypadPlus = 80, + KeypadDecimal = 81, + KeypadPeriod = 81, + KeypadEnter = 82, + A = 83, + B = 84, + C = 85, + D = 86, + E = 87, + F = 88, + G = 89, + H = 90, + I = 91, + J = 92, + K = 93, + L = 94, + M = 95, + N = 96, + O = 97, + P = 98, + Q = 99, + R = 100, + S = 101, + T = 102, + U = 103, + V = 104, + W = 105, + X = 106, + Y = 107, + Z = 108, + Number0 = 109, + Number1 = 110, + Number2 = 111, + Number3 = 112, + Number4 = 113, + Number5 = 114, + Number6 = 115, + Number7 = 116, + Number8 = 117, + Number9 = 118, + Tilde = 119, + Grave = 119, + Minus = 120, + Plus = 121, + BracketLeft = 122, + LBracket = 122, + BracketRight = 123, + RBracket = 123, + Semicolon = 124, + Quote = 125, + Comma = 126, + Period = 127, + Slash = 128, + BackSlash = 129, + NonUSBackSlash = 130, + LastKey = 131 + } +} diff --git a/Ryujinx.Common/Configuration/Hid/KeyboardHotkeys.cs b/Ryujinx.Common/Configuration/Hid/KeyboardHotkeys.cs new file mode 100644 index 0000000000..1d0b050492 --- /dev/null +++ b/Ryujinx.Common/Configuration/Hid/KeyboardHotkeys.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.Configuration.Hid +{ + public struct KeyboardHotkeys + { + public Key ToggleVsync; + } +} diff --git a/Ryujinx.Common/Configuration/Hid/NpadController.cs b/Ryujinx.Common/Configuration/Hid/NpadController.cs new file mode 100644 index 0000000000..f00865d556 --- /dev/null +++ b/Ryujinx.Common/Configuration/Hid/NpadController.cs @@ -0,0 +1,35 @@ +namespace Ryujinx.Common.Configuration.Hid +{ + public class NpadController + { + /// + /// Enables or disables controller support + /// + public bool Enabled; + + /// + /// Controller Device Index + /// + public int Index; + + /// + /// Controller Analog Stick Deadzone + /// + public float Deadzone; + + /// + /// Controller Trigger Threshold + /// + public float TriggerThreshold; + + /// + /// Left JoyCon Controller Bindings + /// + public NpadControllerLeft LeftJoycon; + + /// + /// Right JoyCon Controller Bindings + /// + public NpadControllerRight RightJoycon; + } +} diff --git a/Ryujinx.Common/Configuration/Hid/NpadControllerLeft.cs b/Ryujinx.Common/Configuration/Hid/NpadControllerLeft.cs new file mode 100644 index 0000000000..54ac0f03ae --- /dev/null +++ b/Ryujinx.Common/Configuration/Hid/NpadControllerLeft.cs @@ -0,0 +1,15 @@ +namespace Ryujinx.Common.Configuration.Hid +{ + public struct NpadControllerLeft + { + public ControllerInputId Stick; + public ControllerInputId StickButton; + public ControllerInputId ButtonMinus; + public ControllerInputId ButtonL; + public ControllerInputId ButtonZl; + public ControllerInputId DPadUp; + public ControllerInputId DPadDown; + public ControllerInputId DPadLeft; + public ControllerInputId DPadRight; + } +} diff --git a/Ryujinx.Common/Configuration/Hid/NpadControllerRight.cs b/Ryujinx.Common/Configuration/Hid/NpadControllerRight.cs new file mode 100644 index 0000000000..315136d9f6 --- /dev/null +++ b/Ryujinx.Common/Configuration/Hid/NpadControllerRight.cs @@ -0,0 +1,15 @@ +namespace Ryujinx.Common.Configuration.Hid +{ + public struct NpadControllerRight + { + public ControllerInputId Stick; + public ControllerInputId StickButton; + public ControllerInputId ButtonA; + public ControllerInputId ButtonB; + public ControllerInputId ButtonX; + public ControllerInputId ButtonY; + public ControllerInputId ButtonPlus; + public ControllerInputId ButtonR; + public ControllerInputId ButtonZr; + } +} diff --git a/Ryujinx.Common/Configuration/Hid/NpadKeyboard.cs b/Ryujinx.Common/Configuration/Hid/NpadKeyboard.cs new file mode 100644 index 0000000000..911f5119ea --- /dev/null +++ b/Ryujinx.Common/Configuration/Hid/NpadKeyboard.cs @@ -0,0 +1,20 @@ +namespace Ryujinx.UI.Input +{ + public class NpadKeyboard + { + /// + /// Left JoyCon Keyboard Bindings + /// + public Configuration.Hid.NpadKeyboardLeft LeftJoycon; + + /// + /// Right JoyCon Keyboard Bindings + /// + public Configuration.Hid.NpadKeyboardRight RightJoycon; + + /// + /// Hotkey Keyboard Bindings + /// + public Configuration.Hid.KeyboardHotkeys Hotkeys; + } +} diff --git a/Ryujinx.Common/Configuration/Hid/NpadKeyboardLeft.cs b/Ryujinx.Common/Configuration/Hid/NpadKeyboardLeft.cs new file mode 100644 index 0000000000..799cdfdb8a --- /dev/null +++ b/Ryujinx.Common/Configuration/Hid/NpadKeyboardLeft.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.Configuration.Hid +{ + public struct NpadKeyboardLeft + { + public Key StickUp; + public Key StickDown; + public Key StickLeft; + public Key StickRight; + public Key StickButton; + public Key DPadUp; + public Key DPadDown; + public Key DPadLeft; + public Key DPadRight; + public Key ButtonMinus; + public Key ButtonL; + public Key ButtonZl; + } +} diff --git a/Ryujinx.Common/Configuration/Hid/NpadKeyboardRight.cs b/Ryujinx.Common/Configuration/Hid/NpadKeyboardRight.cs new file mode 100644 index 0000000000..311504bb7e --- /dev/null +++ b/Ryujinx.Common/Configuration/Hid/NpadKeyboardRight.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.Configuration.Hid +{ + public struct NpadKeyboardRight + { + public Key StickUp; + public Key StickDown; + public Key StickLeft; + public Key StickRight; + public Key StickButton; + public Key ButtonA; + public Key ButtonB; + public Key ButtonX; + public Key ButtonY; + public Key ButtonPlus; + public Key ButtonR; + public Key ButtonZr; + } +} diff --git a/Ryujinx.Common/Configuration/LoggerModule.cs b/Ryujinx.Common/Configuration/LoggerModule.cs new file mode 100644 index 0000000000..504a81418f --- /dev/null +++ b/Ryujinx.Common/Configuration/LoggerModule.cs @@ -0,0 +1,109 @@ +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using System; +using System.IO; + +namespace Ryujinx.Configuration +{ + public static class LoggerModule + { + public static void Initialize() + { + AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; + AppDomain.CurrentDomain.ProcessExit += CurrentDomain_ProcessExit; + + ConfigurationState.Instance.Logger.EnableDebug.Event += ReloadEnableDebug; + ConfigurationState.Instance.Logger.EnableStub.Event += ReloadEnableStub; + ConfigurationState.Instance.Logger.EnableInfo.Event += ReloadEnableInfo; + ConfigurationState.Instance.Logger.EnableWarn.Event += ReloadEnableWarning; + ConfigurationState.Instance.Logger.EnableError.Event += ReloadEnableError; + ConfigurationState.Instance.Logger.EnableGuest.Event += ReloadEnableGuest; + ConfigurationState.Instance.Logger.EnableFsAccessLog.Event += ReloadEnableFsAccessLog; + ConfigurationState.Instance.Logger.FilteredClasses.Event += ReloadFilteredClasses; + ConfigurationState.Instance.Logger.EnableFileLog.Event += ReloadFileLogger; + } + + private static void ReloadEnableDebug(object sender, ReactiveEventArgs e) + { + Logger.SetEnable(LogLevel.Debug, e.NewValue); + } + + private static void ReloadEnableStub(object sender, ReactiveEventArgs e) + { + Logger.SetEnable(LogLevel.Stub, e.NewValue); + } + + private static void ReloadEnableInfo(object sender, ReactiveEventArgs e) + { + Logger.SetEnable(LogLevel.Info, e.NewValue); + } + + private static void ReloadEnableWarning(object sender, ReactiveEventArgs e) + { + Logger.SetEnable(LogLevel.Warning, e.NewValue); + } + + private static void ReloadEnableError(object sender, ReactiveEventArgs e) + { + Logger.SetEnable(LogLevel.Error, e.NewValue); + } + + private static void ReloadEnableGuest(object sender, ReactiveEventArgs e) + { + Logger.SetEnable(LogLevel.Guest, e.NewValue); + } + + private static void ReloadEnableFsAccessLog(object sender, ReactiveEventArgs e) + { + Logger.SetEnable(LogLevel.AccessLog, e.NewValue); + } + + private static void ReloadFilteredClasses(object sender, ReactiveEventArgs e) + { + bool noFilter = e.NewValue.Length == 0; + + foreach (var logClass in EnumExtensions.GetValues()) + { + Logger.SetEnable(logClass, noFilter); + } + + foreach (var logClass in e.NewValue) + { + Logger.SetEnable(logClass, true); + } + } + + private static void ReloadFileLogger(object sender, ReactiveEventArgs e) + { + if (e.NewValue) + { + Logger.AddTarget(new AsyncLogTargetWrapper( + new FileLogTarget(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Ryujinx.log"), "file"), + 1000, + AsyncLogTargetOverflowAction.Block + )); + } + else + { + Logger.RemoveTarget("file"); + } + } + + private static void CurrentDomain_ProcessExit(object sender, EventArgs e) + { + Logger.Shutdown(); + } + + private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) + { + var exception = e.ExceptionObject as Exception; + + Logger.PrintError(LogClass.Emulation, $"Unhandled exception caught: {exception}"); + + if (e.IsTerminating) + { + Logger.Shutdown(); + } + } + } +} diff --git a/Ryujinx.Common/Configuration/System/Language.cs b/Ryujinx.Common/Configuration/System/Language.cs new file mode 100644 index 0000000000..d3af296ba9 --- /dev/null +++ b/Ryujinx.Common/Configuration/System/Language.cs @@ -0,0 +1,23 @@ +namespace Ryujinx.Configuration.System +{ + public enum Language + { + Japanese, + AmericanEnglish, + French, + German, + Italian, + Spanish, + Chinese, + Korean, + Dutch, + Portuguese, + Russian, + Taiwanese, + BritishEnglish, + CanadianFrench, + LatinAmericanSpanish, + SimplifiedChinese, + TraditionalChinese + } +} diff --git a/Ryujinx/Ui/GuiColumns.cs b/Ryujinx.Common/Configuration/Ui/GuiColumns.cs similarity index 90% rename from Ryujinx/Ui/GuiColumns.cs rename to Ryujinx.Common/Configuration/Ui/GuiColumns.cs index b86a273ea8..2b3524aa82 100644 --- a/Ryujinx/Ui/GuiColumns.cs +++ b/Ryujinx.Common/Configuration/Ui/GuiColumns.cs @@ -1,4 +1,4 @@ -namespace Ryujinx.Ui +namespace Ryujinx.Configuration.Ui { public struct GuiColumns { diff --git a/Ryujinx.Common/Logging/Logger.cs b/Ryujinx.Common/Logging/Logger.cs index 10b1d97037..83af97b122 100644 --- a/Ryujinx.Common/Logging/Logger.cs +++ b/Ryujinx.Common/Logging/Logger.cs @@ -37,6 +37,12 @@ namespace Ryujinx.Common.Logging m_LogTargets = new List(); m_Time = Stopwatch.StartNew(); + + // Logger should log to console by default + AddTarget(new AsyncLogTargetWrapper( + new ConsoleLogTarget("console"), + 1000, + AsyncLogTargetOverflowAction.Block)); } public static void RestartTime() @@ -44,6 +50,19 @@ namespace Ryujinx.Common.Logging m_Time.Restart(); } + private static ILogTarget GetTarget(string targetName) + { + foreach (var target in m_LogTargets) + { + if (target.Name.Equals(targetName)) + { + return target; + } + } + + return null; + } + public static void AddTarget(ILogTarget target) { m_LogTargets.Add(target); @@ -51,6 +70,20 @@ namespace Ryujinx.Common.Logging Updated += target.Log; } + public static void RemoveTarget(string target) + { + ILogTarget logTarget = GetTarget(target); + + if (logTarget != null) + { + Updated -= logTarget.Log; + + m_LogTargets.Remove(logTarget); + + logTarget.Dispose(); + } + } + public static void Shutdown() { Updated = null; diff --git a/Ryujinx.Common/Logging/Targets/AsyncLogTargetWrapper.cs b/Ryujinx.Common/Logging/Targets/AsyncLogTargetWrapper.cs index a805a83b6e..c946b67880 100644 --- a/Ryujinx.Common/Logging/Targets/AsyncLogTargetWrapper.cs +++ b/Ryujinx.Common/Logging/Targets/AsyncLogTargetWrapper.cs @@ -27,6 +27,8 @@ namespace Ryujinx.Common.Logging private readonly int _overflowTimeout; + string ILogTarget.Name { get => _target.Name; } + public AsyncLogTargetWrapper(ILogTarget target) : this(target, -1, AsyncLogTargetOverflowAction.Block) { } diff --git a/Ryujinx.Common/Logging/Targets/ConsoleLogTarget.cs b/Ryujinx.Common/Logging/Targets/ConsoleLogTarget.cs index 871076a41a..ff5c6f5acb 100644 --- a/Ryujinx.Common/Logging/Targets/ConsoleLogTarget.cs +++ b/Ryujinx.Common/Logging/Targets/ConsoleLogTarget.cs @@ -9,6 +9,10 @@ namespace Ryujinx.Common.Logging private readonly ILogFormatter _formatter; + private readonly string _name; + + string ILogTarget.Name { get => _name; } + static ConsoleLogTarget() { _logColors = new ConcurrentDictionary { @@ -19,9 +23,10 @@ namespace Ryujinx.Common.Logging }; } - public ConsoleLogTarget() + public ConsoleLogTarget(string name) { _formatter = new DefaultLogFormatter(); + _name = name; } public void Log(object sender, LogEventArgs args) diff --git a/Ryujinx.Common/Logging/Targets/FileLogTarget.cs b/Ryujinx.Common/Logging/Targets/FileLogTarget.cs index 85dc82497a..4db5f7bce5 100644 --- a/Ryujinx.Common/Logging/Targets/FileLogTarget.cs +++ b/Ryujinx.Common/Logging/Targets/FileLogTarget.cs @@ -1,4 +1,5 @@ -using System.IO; +using System; +using System.IO; using System.Text; namespace Ryujinx.Common.Logging @@ -9,13 +10,17 @@ namespace Ryujinx.Common.Logging private readonly StreamWriter _logWriter; private readonly ILogFormatter _formatter; + private readonly string _name; - public FileLogTarget(string path) - : this(path, FileShare.Read, FileMode.Append) + string ILogTarget.Name { get => _name; } + + public FileLogTarget(string path, string name) + : this(path, name, FileShare.Read, FileMode.Append) { } - public FileLogTarget(string path, FileShare fileShare, FileMode fileMode) + public FileLogTarget(string path, string name, FileShare fileShare, FileMode fileMode) { + _name = name; _logWriter = new StreamWriter(File.Open(path, fileMode, FileAccess.Write, fileShare)); _formatter = new DefaultLogFormatter(); } diff --git a/Ryujinx.Common/Logging/Targets/ILogTarget.cs b/Ryujinx.Common/Logging/Targets/ILogTarget.cs index 261c5e64b2..d4d26a936d 100644 --- a/Ryujinx.Common/Logging/Targets/ILogTarget.cs +++ b/Ryujinx.Common/Logging/Targets/ILogTarget.cs @@ -5,5 +5,7 @@ namespace Ryujinx.Common.Logging public interface ILogTarget : IDisposable { void Log(object sender, LogEventArgs args); + + string Name { get; } } } diff --git a/Ryujinx.Common/Logging/Targets/JsonLogTarget.cs b/Ryujinx.Common/Logging/Targets/JsonLogTarget.cs index 410394aa2d..3729b18d13 100644 --- a/Ryujinx.Common/Logging/Targets/JsonLogTarget.cs +++ b/Ryujinx.Common/Logging/Targets/JsonLogTarget.cs @@ -1,4 +1,5 @@ -using System.IO; +using System; +using System.IO; using Utf8Json; namespace Ryujinx.Common.Logging @@ -7,10 +8,14 @@ namespace Ryujinx.Common.Logging { private Stream _stream; private bool _leaveOpen; + private string _name; - public JsonLogTarget(Stream stream) + string ILogTarget.Name { get => _name; } + + public JsonLogTarget(Stream stream, string name) { _stream = stream; + _name = name; } public JsonLogTarget(Stream stream, bool leaveOpen) diff --git a/Ryujinx.Common/ReactiveObject.cs b/Ryujinx.Common/ReactiveObject.cs new file mode 100644 index 0000000000..be30e9b2c5 --- /dev/null +++ b/Ryujinx.Common/ReactiveObject.cs @@ -0,0 +1,57 @@ +using System; +using System.Threading; + +namespace Ryujinx.Common +{ + public class ReactiveObject + { + private ReaderWriterLock _readerWriterLock = new ReaderWriterLock(); + private T _value; + + public event EventHandler> Event; + + public T Value + { + get + { + _readerWriterLock.AcquireReaderLock(Timeout.Infinite); + T value = _value; + _readerWriterLock.ReleaseReaderLock(); + + return value; + } + set + { + _readerWriterLock.AcquireWriterLock(Timeout.Infinite); + + T oldValue = _value; + + _value = value; + + _readerWriterLock.ReleaseWriterLock(); + + if (oldValue == null || !oldValue.Equals(_value)) + { + Event?.Invoke(this, new ReactiveEventArgs(oldValue, value)); + } + } + } + + public static implicit operator T(ReactiveObject obj) + { + return obj.Value; + } + } + + public class ReactiveEventArgs + { + public T OldValue { get; } + public T NewValue { get; } + + public ReactiveEventArgs(T oldValue, T newValue) + { + OldValue = oldValue; + NewValue = newValue; + } + } +} diff --git a/Ryujinx.Common/Ryujinx.Common.csproj b/Ryujinx.Common/Ryujinx.Common.csproj index c777b402cc..7f6fa32323 100644 --- a/Ryujinx.Common/Ryujinx.Common.csproj +++ b/Ryujinx.Common/Ryujinx.Common.csproj @@ -27,6 +27,7 @@ + diff --git a/Ryujinx.HLE/Input/Hid.cs b/Ryujinx.HLE/Input/Hid.cs index 27e6a30873..5cb7f09de6 100644 --- a/Ryujinx.HLE/Input/Hid.cs +++ b/Ryujinx.HLE/Input/Hid.cs @@ -1,5 +1,7 @@ using Ryujinx.Common; +using Ryujinx.Configuration.Hid; using Ryujinx.HLE.HOS; +using System; namespace Ryujinx.HLE.Input { @@ -47,18 +49,31 @@ namespace Ryujinx.HLE.Input _keyboardOffset = HidPosition + HidKeyboardOffset; } - public void InitializePrimaryController(ControllerStatus controllerType) + private static ControllerStatus ConvertControllerTypeToState(ControllerType controllerType) { - ControllerId controllerId = controllerType == ControllerStatus.Handheld ? + switch (controllerType) + { + case ControllerType.Handheld: return ControllerStatus.Handheld; + case ControllerType.NpadLeft: return ControllerStatus.NpadLeft; + case ControllerType.NpadRight: return ControllerStatus.NpadRight; + case ControllerType.NpadPair: return ControllerStatus.NpadPair; + case ControllerType.ProController: return ControllerStatus.ProController; + default: throw new NotImplementedException(); + } + } + + public void InitializePrimaryController(ControllerType controllerType) + { + ControllerId controllerId = controllerType == ControllerType.Handheld ? ControllerId.ControllerHandheld : ControllerId.ControllerPlayer1; - if (controllerType == ControllerStatus.ProController) + if (controllerType == ControllerType.ProController) { PrimaryController = new ProController(_device, NpadColor.Black, NpadColor.Black); } else { - PrimaryController = new NpadController(controllerType, + PrimaryController = new NpadController(ConvertControllerTypeToState(controllerType), _device, (NpadColor.BodyNeonRed, NpadColor.BodyNeonRed), (NpadColor.ButtonsNeonBlue, NpadColor.ButtonsNeonBlue)); @@ -67,11 +82,6 @@ namespace Ryujinx.HLE.Input PrimaryController.Connect(controllerId); } - public void InitializeKeyboard() - { - _device.Memory.FillWithZeros(HidPosition + HidKeyboardOffset, HidKeyboardSize); - } - public ControllerButtons UpdateStickButtons( JoystickPosition leftStick, JoystickPosition rightStick) diff --git a/Ryujinx.HLE/Switch.cs b/Ryujinx.HLE/Switch.cs index 4a15f616e7..a4d07f6ac1 100644 --- a/Ryujinx.HLE/Switch.cs +++ b/Ryujinx.HLE/Switch.cs @@ -1,8 +1,12 @@ +using LibHac.FsSystem; using Ryujinx.Audio; +using Ryujinx.Configuration; using Ryujinx.Graphics; using Ryujinx.Graphics.Gal; using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.HOS; +using Ryujinx.HLE.HOS.Services; +using Ryujinx.HLE.HOS.SystemState; using Ryujinx.HLE.Input; using System; using System.Threading; @@ -60,6 +64,29 @@ namespace Ryujinx.HLE VsyncEvent = new AutoResetEvent(true); } + public void Initialize() + { + System.State.SetLanguage((SystemLanguage)ConfigurationState.Instance.System.Language.Value); + + EnableDeviceVsync = ConfigurationState.Instance.Graphics.EnableVsync; + + // TODO: Make this reloadable and implement Docking/Undocking logic. + System.State.DockedMode = ConfigurationState.Instance.System.EnableDockedMode; + + if (ConfigurationState.Instance.System.EnableMulticoreScheduling) + { + System.EnableMultiCoreScheduling(); + } + + System.FsIntegrityCheckLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks + ? IntegrityCheckLevel.ErrorOnInvalid + : IntegrityCheckLevel.None; + + System.GlobalAccessLogMode = ConfigurationState.Instance.System.FsGlobalAccessLogMode; + + ServiceConfiguration.IgnoreMissingServices = ConfigurationState.Instance.System.IgnoreMissingServices; + } + public void LoadCart(string exeFsDir, string romFsFile = null) { System.LoadCart(exeFsDir, romFsFile); diff --git a/Ryujinx/Config.json b/Ryujinx/Config.json index 8463081fab..e35a67acd7 100644 --- a/Ryujinx/Config.json +++ b/Ryujinx/Config.json @@ -1,4 +1,5 @@ { + "version": 1, "graphics_shaders_dump_path": "", "logging_enable_debug": false, "logging_enable_stub": true, @@ -7,9 +8,7 @@ "logging_enable_error": true, "logging_enable_guest": true, "logging_enable_fs_access_log": false, - "logging_filtered_classes": [ - - ], + "logging_filtered_classes": [], "enable_file_log": true, "system_language": "AmericanEnglish", "docked_mode": false, @@ -32,9 +31,7 @@ "file_size_column": true, "path_column": true }, - "game_dirs": [ - - ], + "game_dirs": [], "enable_custom_theme": false, "custom_theme_path": "", "enable_keyboard": false, diff --git a/Ryujinx/Configuration/DiscordIntegrationModule.cs b/Ryujinx/Configuration/DiscordIntegrationModule.cs new file mode 100644 index 0000000000..15540a1c82 --- /dev/null +++ b/Ryujinx/Configuration/DiscordIntegrationModule.cs @@ -0,0 +1,92 @@ +using DiscordRPC; +using Ryujinx.Common; +using System; +using System.IO; +using System.Linq; + +namespace Ryujinx.Configuration +{ + static class DiscordIntegrationModule + { + private static DiscordRpcClient DiscordClient; + + private static string LargeDescription = "Ryujinx is a Nintendo Switch emulator."; + + public static RichPresence DiscordPresence { get; private set; } + + public static void Initialize() + { + DiscordPresence = new RichPresence + { + Assets = new Assets + { + LargeImageKey = "ryujinx", + LargeImageText = LargeDescription + }, + Details = "Main Menu", + State = "Idling", + Timestamps = new Timestamps(DateTime.UtcNow) + }; + + ConfigurationState.Instance.EnableDiscordIntegration.Event += Update; + } + + private static void Update(object sender, ReactiveEventArgs e) + { + if (e.OldValue != e.NewValue) + { + // If the integration was active, disable it and unload everything + if (e.OldValue) + { + DiscordClient?.Dispose(); + + DiscordClient = null; + } + + // If we need to activate it and the client isn't active, initialize it + if (e.NewValue && DiscordClient == null) + { + DiscordClient = new DiscordRpcClient("568815339807309834"); + + DiscordClient.Initialize(); + DiscordClient.SetPresence(DiscordPresence); + } + } + } + + public static void SwitchToPlayingState(string titleId, string titleName) + { + if (File.ReadAllLines(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "RPsupported.dat")).Contains(titleId)) + { + DiscordPresence.Assets.LargeImageKey = titleId; + } + + string state = titleId; + + if (state == null) + { + state = "Ryujinx"; + } + else + { + state = state.ToUpper(); + } + + string details = "Idling"; + + if (titleName != null) + { + details = $"Playing {titleName}"; + } + + DiscordPresence.Details = details; + DiscordPresence.State = state; + DiscordPresence.Assets.LargeImageText = titleName; + DiscordPresence.Assets.SmallImageKey = "ryujinx"; + DiscordPresence.Assets.SmallImageText = LargeDescription; + DiscordPresence.Timestamps = new Timestamps(DateTime.UtcNow); + + DiscordClient?.SetPresence(DiscordPresence); + } + } +} diff --git a/Ryujinx/Program.cs b/Ryujinx/Program.cs index 96e9e8dec2..98b8d692d6 100644 --- a/Ryujinx/Program.cs +++ b/Ryujinx/Program.cs @@ -1,5 +1,6 @@ using Gtk; using Ryujinx.Common.Logging; +using Ryujinx.Configuration; using Ryujinx.Profiler; using Ryujinx.Ui; using System; @@ -16,9 +17,32 @@ namespace Ryujinx string systemPath = Environment.GetEnvironmentVariable("Path", EnvironmentVariableTarget.Machine); Environment.SetEnvironmentVariable("Path", $"{Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "bin")};{systemPath}"); - AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; - AppDomain.CurrentDomain.ProcessExit += CurrentDomain_ProcessExit; - GLib.ExceptionManager.UnhandledException += Glib_UnhandledException; + GLib.ExceptionManager.UnhandledException += Glib_UnhandledException; + + // Initialize the configuration + ConfigurationState.Initialize(); + + // Initialize the logger system + LoggerModule.Initialize(); + + // Initialize Discord integration + DiscordIntegrationModule.Initialize(); + + string configurationPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json"); + + // Now load the configuration as the other subsystems are now registered + if (File.Exists(configurationPath)) + { + ConfigurationFileFormat configurationFileFormat = ConfigurationFileFormat.Load(configurationPath); + ConfigurationState.Instance.Load(configurationFileFormat); + } + else + { + // No configuration, we load the default values and save it on disk + ConfigurationState.Instance.LoadDefault(); + ConfigurationState.Instance.ToFileFormat().SaveConfig(configurationPath); + } + Profile.Initialize(); @@ -42,23 +66,6 @@ namespace Ryujinx Application.Run(); } - private static void CurrentDomain_ProcessExit(object sender, EventArgs e) - { - Logger.Shutdown(); - } - - private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) - { - Exception exception = e.ExceptionObject as Exception; - - Logger.PrintError(LogClass.Emulation, $"Unhandled exception caught: {exception}"); - - if (e.IsTerminating) - { - Logger.Shutdown(); - } - } - private static void Glib_UnhandledException(GLib.UnhandledExceptionArgs e) { Exception exception = e.ExceptionObject as Exception; diff --git a/Ryujinx/Ryujinx.csproj b/Ryujinx/Ryujinx.csproj index c54beffe47..b231ddb8d2 100644 --- a/Ryujinx/Ryujinx.csproj +++ b/Ryujinx/Ryujinx.csproj @@ -71,7 +71,6 @@ - diff --git a/Ryujinx/Ui/GLScreen.cs b/Ryujinx/Ui/GLScreen.cs index c23a369294..e1994803a6 100644 --- a/Ryujinx/Ui/GLScreen.cs +++ b/Ryujinx/Ui/GLScreen.cs @@ -1,10 +1,12 @@ using OpenTK; using OpenTK.Graphics; using OpenTK.Input; +using Ryujinx.Configuration; using Ryujinx.Graphics.Gal; using Ryujinx.HLE; using Ryujinx.HLE.Input; using Ryujinx.Profiler.UI; +using Ryujinx.Ui; using System; using System.Threading; @@ -29,6 +31,8 @@ namespace Ryujinx.Ui private MouseState? _mouse = null; + private Input.NpadController _primaryController; + private Thread _renderThread; private bool _resizeEvent; @@ -50,6 +54,8 @@ namespace Ryujinx.Ui _device = device; _renderer = renderer; + _primaryController = new Input.NpadController(ConfigurationState.Instance.Hid.JoystickControls); + Location = new Point( (DisplayDevice.Default.Width / 2) - (Width / 2), (DisplayDevice.Default.Height / 2) - (Height / 2)); @@ -162,16 +168,16 @@ namespace Ryujinx.Ui #endif // Normal Input - currentHotkeyButtons = Configuration.Instance.KeyboardControls.GetHotkeyButtons(keyboard); - currentButton = Configuration.Instance.KeyboardControls.GetButtons(keyboard); + currentHotkeyButtons = KeyboardControls.GetHotkeyButtons(ConfigurationState.Instance.Hid.KeyboardControls, keyboard); + currentButton = KeyboardControls.GetButtons(ConfigurationState.Instance.Hid.KeyboardControls, keyboard); - if (Configuration.Instance.EnableKeyboard) + if (ConfigurationState.Instance.Hid.EnableKeyboard) { - hidKeyboard = Configuration.Instance.KeyboardControls.GetKeysDown(keyboard); + hidKeyboard = KeyboardControls.GetKeysDown(ConfigurationState.Instance.Hid.KeyboardControls, keyboard); } - (leftJoystickDx, leftJoystickDy) = Configuration.Instance.KeyboardControls.GetLeftStick(keyboard); - (rightJoystickDx, rightJoystickDy) = Configuration.Instance.KeyboardControls.GetRightStick(keyboard); + (leftJoystickDx, leftJoystickDy) = KeyboardControls.GetLeftStick(ConfigurationState.Instance.Hid.KeyboardControls, keyboard); + (rightJoystickDx, rightJoystickDy) = KeyboardControls.GetRightStick(ConfigurationState.Instance.Hid.KeyboardControls, keyboard); } if (!hidKeyboard.HasValue) @@ -183,17 +189,17 @@ namespace Ryujinx.Ui }; } - currentButton |= Configuration.Instance.JoystickControls.GetButtons(); + currentButton |= _primaryController.GetButtons(); // Keyboard has priority stick-wise if (leftJoystickDx == 0 && leftJoystickDy == 0) { - (leftJoystickDx, leftJoystickDy) = Configuration.Instance.JoystickControls.GetLeftStick(); + (leftJoystickDx, leftJoystickDy) = _primaryController.GetLeftStick(); } if (rightJoystickDx == 0 && rightJoystickDy == 0) { - (rightJoystickDx, rightJoystickDy) = Configuration.Instance.JoystickControls.GetRightStick(); + (rightJoystickDx, rightJoystickDy) = _primaryController.GetRightStick(); } leftJoystick = new JoystickPosition @@ -269,7 +275,7 @@ namespace Ryujinx.Ui _device.Hid.SetTouchPoints(); } - if (Configuration.Instance.EnableKeyboard && hidKeyboard.HasValue) + if (ConfigurationState.Instance.Hid.EnableKeyboard && hidKeyboard.HasValue) { _device.Hid.WriteKeyboard(hidKeyboard.Value); } diff --git a/Ryujinx/Ui/NpadKeyboard.cs b/Ryujinx/Ui/KeyboardControls.cs similarity index 74% rename from Ryujinx/Ui/NpadKeyboard.cs rename to Ryujinx/Ui/KeyboardControls.cs index 95fb222183..db9c0cda8c 100644 --- a/Ryujinx/Ui/NpadKeyboard.cs +++ b/Ryujinx/Ui/KeyboardControls.cs @@ -1,118 +1,67 @@ -using OpenTK.Input; +using OpenTK.Input; using Ryujinx.HLE.Input; +using Ryujinx.UI.Input; -namespace Ryujinx.Ui.Input +namespace Ryujinx.Ui { - public struct NpadKeyboardLeft + public static class KeyboardControls { - public Key StickUp; - public Key StickDown; - public Key StickLeft; - public Key StickRight; - public Key StickButton; - public Key DPadUp; - public Key DPadDown; - public Key DPadLeft; - public Key DPadRight; - public Key ButtonMinus; - public Key ButtonL; - public Key ButtonZl; - } - - public struct NpadKeyboardRight - { - public Key StickUp; - public Key StickDown; - public Key StickLeft; - public Key StickRight; - public Key StickButton; - public Key ButtonA; - public Key ButtonB; - public Key ButtonX; - public Key ButtonY; - public Key ButtonPlus; - public Key ButtonR; - public Key ButtonZr; - } - - public struct KeyboardHotkeys - { - public Key ToggleVsync; - } - - public class NpadKeyboard - { - /// - /// Left JoyCon Keyboard Bindings - /// - public NpadKeyboardLeft LeftJoycon { get; set; } - - /// - /// Right JoyCon Keyboard Bindings - /// - public NpadKeyboardRight RightJoycon { get; set; } - - /// - /// Hotkey Keyboard Bindings - /// - public KeyboardHotkeys Hotkeys { get; private set; } - - public ControllerButtons GetButtons(KeyboardState keyboard) + public static ControllerButtons GetButtons(NpadKeyboard npad, KeyboardState keyboard) { ControllerButtons buttons = 0; - if (keyboard[(Key)LeftJoycon.StickButton]) buttons |= ControllerButtons.StickLeft; - if (keyboard[(Key)LeftJoycon.DPadUp]) buttons |= ControllerButtons.DpadUp; - if (keyboard[(Key)LeftJoycon.DPadDown]) buttons |= ControllerButtons.DpadDown; - if (keyboard[(Key)LeftJoycon.DPadLeft]) buttons |= ControllerButtons.DpadLeft; - if (keyboard[(Key)LeftJoycon.DPadRight]) buttons |= ControllerButtons.DPadRight; - if (keyboard[(Key)LeftJoycon.ButtonMinus]) buttons |= ControllerButtons.Minus; - if (keyboard[(Key)LeftJoycon.ButtonL]) buttons |= ControllerButtons.L; - if (keyboard[(Key)LeftJoycon.ButtonZl]) buttons |= ControllerButtons.Zl; + if (keyboard[(Key)npad.LeftJoycon.StickButton]) buttons |= ControllerButtons.StickLeft; + if (keyboard[(Key)npad.LeftJoycon.DPadUp]) buttons |= ControllerButtons.DpadUp; + if (keyboard[(Key)npad.LeftJoycon.DPadDown]) buttons |= ControllerButtons.DpadDown; + if (keyboard[(Key)npad.LeftJoycon.DPadLeft]) buttons |= ControllerButtons.DpadLeft; + if (keyboard[(Key)npad.LeftJoycon.DPadRight]) buttons |= ControllerButtons.DPadRight; + if (keyboard[(Key)npad.LeftJoycon.ButtonMinus]) buttons |= ControllerButtons.Minus; + if (keyboard[(Key)npad.LeftJoycon.ButtonL]) buttons |= ControllerButtons.L; + if (keyboard[(Key)npad.LeftJoycon.ButtonZl]) buttons |= ControllerButtons.Zl; - if (keyboard[(Key)RightJoycon.StickButton]) buttons |= ControllerButtons.StickRight; - if (keyboard[(Key)RightJoycon.ButtonA]) buttons |= ControllerButtons.A; - if (keyboard[(Key)RightJoycon.ButtonB]) buttons |= ControllerButtons.B; - if (keyboard[(Key)RightJoycon.ButtonX]) buttons |= ControllerButtons.X; - if (keyboard[(Key)RightJoycon.ButtonY]) buttons |= ControllerButtons.Y; - if (keyboard[(Key)RightJoycon.ButtonPlus]) buttons |= ControllerButtons.Plus; - if (keyboard[(Key)RightJoycon.ButtonR]) buttons |= ControllerButtons.R; - if (keyboard[(Key)RightJoycon.ButtonZr]) buttons |= ControllerButtons.Zr; + if (keyboard[(Key)npad.RightJoycon.StickButton]) buttons |= ControllerButtons.StickRight; + if (keyboard[(Key)npad.RightJoycon.ButtonA]) buttons |= ControllerButtons.A; + if (keyboard[(Key)npad.RightJoycon.ButtonB]) buttons |= ControllerButtons.B; + if (keyboard[(Key)npad.RightJoycon.ButtonX]) buttons |= ControllerButtons.X; + if (keyboard[(Key)npad.RightJoycon.ButtonY]) buttons |= ControllerButtons.Y; + if (keyboard[(Key)npad.RightJoycon.ButtonPlus]) buttons |= ControllerButtons.Plus; + if (keyboard[(Key)npad.RightJoycon.ButtonR]) buttons |= ControllerButtons.R; + if (keyboard[(Key)npad.RightJoycon.ButtonZr]) buttons |= ControllerButtons.Zr; return buttons; } - public (short, short) GetLeftStick(KeyboardState keyboard) + public static (short, short) GetLeftStick(NpadKeyboard npad, KeyboardState keyboard) { short dx = 0; short dy = 0; - if (keyboard[(Key)LeftJoycon.StickUp]) dy = short.MaxValue; - if (keyboard[(Key)LeftJoycon.StickDown]) dy = -short.MaxValue; - if (keyboard[(Key)LeftJoycon.StickLeft]) dx = -short.MaxValue; - if (keyboard[(Key)LeftJoycon.StickRight]) dx = short.MaxValue; + if (keyboard[(Key)npad.LeftJoycon.StickUp]) dy = short.MaxValue; + if (keyboard[(Key)npad.LeftJoycon.StickDown]) dy = -short.MaxValue; + if (keyboard[(Key)npad.LeftJoycon.StickLeft]) dx = -short.MaxValue; + if (keyboard[(Key)npad.LeftJoycon.StickRight]) dx = short.MaxValue; return (dx, dy); } - public (short, short) GetRightStick(KeyboardState keyboard) + public static (short, short) GetRightStick(NpadKeyboard npad, KeyboardState keyboard) { short dx = 0; short dy = 0; - if (keyboard[(Key)RightJoycon.StickUp]) dy = short.MaxValue; - if (keyboard[(Key)RightJoycon.StickDown]) dy = -short.MaxValue; - if (keyboard[(Key)RightJoycon.StickLeft]) dx = -short.MaxValue; - if (keyboard[(Key)RightJoycon.StickRight]) dx = short.MaxValue; + if (keyboard[(Key)npad.RightJoycon.StickUp]) dy = short.MaxValue; + if (keyboard[(Key)npad.RightJoycon.StickDown]) dy = -short.MaxValue; + if (keyboard[(Key)npad.RightJoycon.StickLeft]) dx = -short.MaxValue; + if (keyboard[(Key)npad.RightJoycon.StickRight]) dx = short.MaxValue; return (dx, dy); } - public HotkeyButtons GetHotkeyButtons(KeyboardState keyboard) + public static HotkeyButtons GetHotkeyButtons(NpadKeyboard npad, KeyboardState keyboard) { HotkeyButtons buttons = 0; - if (keyboard[(Key)Hotkeys.ToggleVsync]) buttons |= HotkeyButtons.ToggleVSync; + if (keyboard[(Key)npad.Hotkeys.ToggleVsync]) buttons |= HotkeyButtons.ToggleVSync; return buttons; } @@ -267,7 +216,7 @@ namespace Ryujinx.Ui.Input new KeyMappingEntry { TargetKey = Key.NumLock, Target = 10 }, }; - public HLE.Input.Keyboard GetKeysDown(KeyboardState keyboard) + public static HLE.Input.Keyboard GetKeysDown(NpadKeyboard npad, KeyboardState keyboard) { HLE.Input.Keyboard hidKeyboard = new HLE.Input.Keyboard { diff --git a/Ryujinx/Ui/MainWindow.cs b/Ryujinx/Ui/MainWindow.cs index a24f3ed050..e0bd849479 100644 --- a/Ryujinx/Ui/MainWindow.cs +++ b/Ryujinx/Ui/MainWindow.cs @@ -1,22 +1,22 @@ -using DiscordRPC; using Gtk; -using JsonPrettyPrinterPlus; using Ryujinx.Audio; using Ryujinx.Common.Logging; -using Ryujinx.Graphics.Gal.OpenGL; using Ryujinx.Graphics.Gal; -using Ryujinx.HLE.FileSystem; +using Ryujinx.Graphics.Gal.OpenGL; using Ryujinx.Profiler; using System; -using System.Diagnostics; using System.IO; -using System.Linq; using System.Reflection; using System.Text; -using System.Threading.Tasks; using System.Threading; +using Ryujinx.Configuration; +using System.Diagnostics; +using System.Threading.Tasks; using Utf8Json; +using JsonPrettyPrinterPlus; using Utf8Json.Resolvers; +using Ryujinx.HLE.FileSystem; + using GUI = Gtk.Builder.ObjectAttribute; @@ -50,12 +50,6 @@ namespace Ryujinx.Ui private static TreeView _treeView; - public static bool DiscordIntegrationEnabled { get; set; } - - public static DiscordRpcClient DiscordClient; - - public static RichPresence DiscordPresence; - #pragma warning disable CS0649 #pragma warning disable IDE0044 [GUI] Window _mainWin; @@ -91,60 +85,39 @@ namespace Ryujinx.Ui _audioOut = InitializeAudioEngine(); - _device = new HLE.Switch(_renderer, _audioOut); + // TODO: Initialization and dispose of HLE.Switch when starting/stoping emulation. + _device = InitializeSwitchInstance(); _treeView = _gameTable; - Configuration.Load(System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json")); - Configuration.InitialConfigure(_device); - ApplyTheme(); - if (DiscordIntegrationEnabled) - { - DiscordClient = new DiscordRpcClient("568815339807309834"); - DiscordPresence = new RichPresence - { - Assets = new Assets - { - LargeImageKey = "ryujinx", - LargeImageText = "Ryujinx is an emulator for the Nintendo Switch" - }, - Details = "Main Menu", - State = "Idling", - Timestamps = new Timestamps(DateTime.UtcNow) - }; - - DiscordClient.Initialize(); - DiscordClient.SetPresence(DiscordPresence); - } - _mainWin.Icon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.Icon.png"); _stopEmulation.Sensitive = false; - if (SwitchSettings.SwitchConfig.GuiColumns.FavColumn) { _favToggle.Active = true; } - if (SwitchSettings.SwitchConfig.GuiColumns.IconColumn) { _iconToggle.Active = true; } - if (SwitchSettings.SwitchConfig.GuiColumns.AppColumn) { _appToggle.Active = true; } - if (SwitchSettings.SwitchConfig.GuiColumns.DevColumn) { _developerToggle.Active = true; } - if (SwitchSettings.SwitchConfig.GuiColumns.VersionColumn) { _versionToggle.Active = true; } - if (SwitchSettings.SwitchConfig.GuiColumns.TimePlayedColumn) { _timePlayedToggle.Active = true; } - if (SwitchSettings.SwitchConfig.GuiColumns.LastPlayedColumn) { _lastPlayedToggle.Active = true; } - if (SwitchSettings.SwitchConfig.GuiColumns.FileExtColumn) { _fileExtToggle.Active = true; } - if (SwitchSettings.SwitchConfig.GuiColumns.FileSizeColumn) { _fileSizeToggle.Active = true; } - if (SwitchSettings.SwitchConfig.GuiColumns.PathColumn) { _pathToggle.Active = true; } + if (ConfigurationState.Instance.Ui.GuiColumns.FavColumn) _favToggle.Active = true; + if (ConfigurationState.Instance.Ui.GuiColumns.IconColumn) _iconToggle.Active = true; + if (ConfigurationState.Instance.Ui.GuiColumns.AppColumn) _appToggle.Active = true; + if (ConfigurationState.Instance.Ui.GuiColumns.DevColumn) _developerToggle.Active = true; + if (ConfigurationState.Instance.Ui.GuiColumns.VersionColumn) _versionToggle.Active = true; + if (ConfigurationState.Instance.Ui.GuiColumns.TimePlayedColumn) _timePlayedToggle.Active = true; + if (ConfigurationState.Instance.Ui.GuiColumns.LastPlayedColumn) _lastPlayedToggle.Active = true; + if (ConfigurationState.Instance.Ui.GuiColumns.FileExtColumn) _fileExtToggle.Active = true; + if (ConfigurationState.Instance.Ui.GuiColumns.FileSizeColumn) _fileSizeToggle.Active = true; + if (ConfigurationState.Instance.Ui.GuiColumns.PathColumn) _pathToggle.Active = true; _gameTable.Model = _tableStore = new ListStore( - typeof(bool), - typeof(Gdk.Pixbuf), - typeof(string), - typeof(string), - typeof(string), - typeof(string), - typeof(string), - typeof(string), - typeof(string), + typeof(bool), + typeof(Gdk.Pixbuf), + typeof(string), + typeof(string), + typeof(string), + typeof(string), + typeof(string), + typeof(string), + typeof(string), typeof(string)); - + _tableStore.SetSortFunc(5, TimePlayedSort); _tableStore.SetSortFunc(6, LastPlayedSort); _tableStore.SetSortFunc(8, FileSizeSort); @@ -158,22 +131,22 @@ namespace Ryujinx.Ui internal static void ApplyTheme() { - if (!SwitchSettings.SwitchConfig.EnableCustomTheme) + if (!ConfigurationState.Instance.Ui.EnableCustomTheme) { return; } - if (File.Exists(SwitchSettings.SwitchConfig.CustomThemePath) && (System.IO.Path.GetExtension(SwitchSettings.SwitchConfig.CustomThemePath) == ".css")) + if (File.Exists(ConfigurationState.Instance.Ui.CustomThemePath) && (System.IO.Path.GetExtension(ConfigurationState.Instance.Ui.CustomThemePath) == ".css")) { CssProvider cssProvider = new CssProvider(); - cssProvider.LoadFromPath(SwitchSettings.SwitchConfig.CustomThemePath); + cssProvider.LoadFromPath(ConfigurationState.Instance.Ui.CustomThemePath); StyleContext.AddProviderForScreen(Gdk.Screen.Default, cssProvider, 800); } else { - Logger.PrintWarning(LogClass.Application, $"The \"custom_theme_path\" section in \"Config.json\" contains an invalid path: \"{SwitchSettings.SwitchConfig.CustomThemePath}\"."); + Logger.PrintWarning(LogClass.Application, $"The \"custom_theme_path\" section in \"Config.json\" contains an invalid path: \"{ConfigurationState.Instance.Ui.CustomThemePath}\"."); } } @@ -187,39 +160,48 @@ namespace Ryujinx.Ui CellRendererToggle favToggle = new CellRendererToggle(); favToggle.Toggled += FavToggle_Toggled; - if (SwitchSettings.SwitchConfig.GuiColumns.FavColumn) { _gameTable.AppendColumn("Fav", favToggle, "active", 0); } - if (SwitchSettings.SwitchConfig.GuiColumns.IconColumn) { _gameTable.AppendColumn("Icon", new CellRendererPixbuf(), "pixbuf", 1); } - if (SwitchSettings.SwitchConfig.GuiColumns.AppColumn) { _gameTable.AppendColumn("Application", new CellRendererText(), "text", 2); } - if (SwitchSettings.SwitchConfig.GuiColumns.DevColumn) { _gameTable.AppendColumn("Developer", new CellRendererText(), "text", 3); } - if (SwitchSettings.SwitchConfig.GuiColumns.VersionColumn) { _gameTable.AppendColumn("Version", new CellRendererText(), "text", 4); } - if (SwitchSettings.SwitchConfig.GuiColumns.TimePlayedColumn) { _gameTable.AppendColumn("Time Played", new CellRendererText(), "text", 5); } - if (SwitchSettings.SwitchConfig.GuiColumns.LastPlayedColumn) { _gameTable.AppendColumn("Last Played", new CellRendererText(), "text", 6); } - if (SwitchSettings.SwitchConfig.GuiColumns.FileExtColumn) { _gameTable.AppendColumn("File Ext", new CellRendererText(), "text", 7); } - if (SwitchSettings.SwitchConfig.GuiColumns.FileSizeColumn) { _gameTable.AppendColumn("File Size", new CellRendererText(), "text", 8); } - if (SwitchSettings.SwitchConfig.GuiColumns.PathColumn) { _gameTable.AppendColumn("Path", new CellRendererText(), "text", 9); } + if (ConfigurationState.Instance.Ui.GuiColumns.FavColumn) _gameTable.AppendColumn("Fav", favToggle, "active", 0); + if (ConfigurationState.Instance.Ui.GuiColumns.IconColumn) _gameTable.AppendColumn("Icon", new CellRendererPixbuf(), "pixbuf", 1); + if (ConfigurationState.Instance.Ui.GuiColumns.AppColumn) _gameTable.AppendColumn("Application", new CellRendererText(), "text", 2); + if (ConfigurationState.Instance.Ui.GuiColumns.DevColumn) _gameTable.AppendColumn("Developer", new CellRendererText(), "text", 3); + if (ConfigurationState.Instance.Ui.GuiColumns.VersionColumn) _gameTable.AppendColumn("Version", new CellRendererText(), "text", 4); + if (ConfigurationState.Instance.Ui.GuiColumns.TimePlayedColumn) _gameTable.AppendColumn("Time Played", new CellRendererText(), "text", 5); + if (ConfigurationState.Instance.Ui.GuiColumns.LastPlayedColumn) _gameTable.AppendColumn("Last Played", new CellRendererText(), "text", 6); + if (ConfigurationState.Instance.Ui.GuiColumns.FileExtColumn) _gameTable.AppendColumn("File Ext", new CellRendererText(), "text", 7); + if (ConfigurationState.Instance.Ui.GuiColumns.FileSizeColumn) _gameTable.AppendColumn("File Size", new CellRendererText(), "text", 8); + if (ConfigurationState.Instance.Ui.GuiColumns.PathColumn) _gameTable.AppendColumn("Path", new CellRendererText(), "text", 9); foreach (TreeViewColumn column in _gameTable.Columns) { - if (column.Title == "Fav") { _favColumn = column; } - else if (column.Title == "Application") { _appColumn = column; } - else if (column.Title == "Developer") { _devColumn = column; } - else if (column.Title == "Version") { _versionColumn = column; } - else if (column.Title == "Time Played") { _timePlayedColumn = column; } - else if (column.Title == "Last Played") { _lastPlayedColumn = column; } - else if (column.Title == "File Ext") { _fileExtColumn = column; } - else if (column.Title == "File Size") { _fileSizeColumn = column; } - else if (column.Title == "Path") { _pathColumn = column; } + if (column.Title == "Fav") _favColumn = column; + else if (column.Title == "Application") _appColumn = column; + else if (column.Title == "Developer") _devColumn = column; + else if (column.Title == "Version") _versionColumn = column; + else if (column.Title == "Time Played") _timePlayedColumn = column; + else if (column.Title == "Last Played") _lastPlayedColumn = column; + else if (column.Title == "File Ext") _fileExtColumn = column; + else if (column.Title == "File Size") _fileSizeColumn = column; + else if (column.Title == "Path") _pathColumn = column; } - if (SwitchSettings.SwitchConfig.GuiColumns.FavColumn) { _favColumn.SortColumnId = 0; } - if (SwitchSettings.SwitchConfig.GuiColumns.IconColumn) { _appColumn.SortColumnId = 2; } - if (SwitchSettings.SwitchConfig.GuiColumns.AppColumn) { _devColumn.SortColumnId = 3; } - if (SwitchSettings.SwitchConfig.GuiColumns.DevColumn) { _versionColumn.SortColumnId = 4; } - if (SwitchSettings.SwitchConfig.GuiColumns.TimePlayedColumn) { _timePlayedColumn.SortColumnId = 5; } - if (SwitchSettings.SwitchConfig.GuiColumns.LastPlayedColumn) { _lastPlayedColumn.SortColumnId = 6; } - if (SwitchSettings.SwitchConfig.GuiColumns.FileExtColumn) { _fileExtColumn.SortColumnId = 7; } - if (SwitchSettings.SwitchConfig.GuiColumns.FileSizeColumn) { _fileSizeColumn.SortColumnId = 8; } - if (SwitchSettings.SwitchConfig.GuiColumns.PathColumn) { _pathColumn.SortColumnId = 9; } + if (ConfigurationState.Instance.Ui.GuiColumns.FavColumn) _favColumn.SortColumnId = 0; + if (ConfigurationState.Instance.Ui.GuiColumns.AppColumn) _appColumn.SortColumnId = 2; + if (ConfigurationState.Instance.Ui.GuiColumns.DevColumn) _devColumn.SortColumnId = 3; + if (ConfigurationState.Instance.Ui.GuiColumns.VersionColumn) _versionColumn.SortColumnId = 4; + if (ConfigurationState.Instance.Ui.GuiColumns.TimePlayedColumn) _timePlayedColumn.SortColumnId = 5; + if (ConfigurationState.Instance.Ui.GuiColumns.LastPlayedColumn) _lastPlayedColumn.SortColumnId = 6; + if (ConfigurationState.Instance.Ui.GuiColumns.FileExtColumn) _fileExtColumn.SortColumnId = 7; + if (ConfigurationState.Instance.Ui.GuiColumns.FileSizeColumn) _fileSizeColumn.SortColumnId = 8; + if (ConfigurationState.Instance.Ui.GuiColumns.PathColumn) _pathColumn.SortColumnId = 9; + } + + private HLE.Switch InitializeSwitchInstance() + { + HLE.Switch instance = new HLE.Switch(_renderer, _audioOut); + + instance.Initialize(); + + return instance; } internal static async Task UpdateGameTable() @@ -233,7 +215,7 @@ namespace Ryujinx.Ui _tableStore.Clear(); - await Task.Run(() => ApplicationLibrary.LoadApplications(SwitchSettings.SwitchConfig.GameDirs, _device.System.KeySet, _device.System.State.DesiredTitleLanguage)); + await Task.Run(() => ApplicationLibrary.LoadApplications(ConfigurationState.Instance.Ui.GameDirs, _device.System.KeySet, _device.System.State.DesiredTitleLanguage)); _updatingGameTable = false; } @@ -248,6 +230,9 @@ namespace Ryujinx.Ui { Logger.RestartTime(); + // TODO: Move this somewhere else + reloadable? + GraphicsConfig.ShadersDumpPath = ConfigurationState.Instance.Graphics.ShadersDumpPath; + if (Directory.Exists(path)) { string[] romFsFiles = Directory.GetFiles(path, "*.istorage"); @@ -313,40 +298,7 @@ namespace Ryujinx.Ui _gameLoaded = true; _stopEmulation.Sensitive = true; - if (DiscordIntegrationEnabled) - { - if (File.ReadAllLines(System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "RPsupported.dat")).Contains(_device.System.TitleId)) - { - DiscordPresence.Assets.LargeImageKey = _device.System.TitleId; - } - - string state = _device.System.TitleId; - - if (state == null) - { - state = "Ryujinx"; - } - else - { - state = state.ToUpper(); - } - - string details = "Idling"; - - if (_device.System.TitleName != null) - { - details = $"Playing {_device.System.TitleName}"; - } - - DiscordPresence.Details = details; - DiscordPresence.State = state; - DiscordPresence.Assets.LargeImageText = _device.System.TitleName; - DiscordPresence.Assets.SmallImageKey = "ryujinx"; - DiscordPresence.Assets.SmallImageText = "Ryujinx is an emulator for the Nintendo Switch"; - DiscordPresence.Timestamps = new Timestamps(DateTime.UtcNow); - - DiscordClient.SetPresence(DiscordPresence); - } + DiscordIntegrationModule.SwitchToPlayingState(_device.System.TitleId, _device.System.TitleName); string metadataFolder = System.IO.Path.Combine(new VirtualFileSystem().GetBasePath(), "games", _device.System.TitleId, "gui"); string metadataFile = System.IO.Path.Combine(metadataFolder, "metadata.json"); @@ -384,8 +336,8 @@ namespace Ryujinx.Ui private static void CreateGameWindow() { - Configuration.ConfigureHid(_device, SwitchSettings.SwitchConfig); - + _device.Hid.InitializePrimaryController(ConfigurationState.Instance.Hid.ControllerType); + using (_screen = new GlScreen(_device, _renderer)) { _screen.MainLoop(); @@ -444,7 +396,6 @@ namespace Ryujinx.Ui Profile.FinishProfiling(); _device.Dispose(); _audioOut.Dispose(); - DiscordClient?.Dispose(); Logger.Shutdown(); Environment.Exit(0); } @@ -607,7 +558,7 @@ namespace Ryujinx.Ui private void Settings_Pressed(object sender, EventArgs args) { - SwitchSettings settingsWin = new SwitchSettings(_device); + SwitchSettings settingsWin = new SwitchSettings(); settingsWin.Show(); } @@ -633,121 +584,81 @@ namespace Ryujinx.Ui private void Fav_Toggled(object sender, EventArgs args) { - GuiColumns updatedColumns = SwitchSettings.SwitchConfig.GuiColumns; - - updatedColumns.FavColumn = _favToggle.Active; - SwitchSettings.SwitchConfig.GuiColumns = updatedColumns; - - Configuration.SaveConfig(SwitchSettings.SwitchConfig, System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json")); + ConfigurationState.Instance.Ui.GuiColumns.FavColumn.Value = _favToggle.Active; + SaveConfig(); UpdateColumns(); } private void Icon_Toggled(object sender, EventArgs args) { - GuiColumns updatedColumns = SwitchSettings.SwitchConfig.GuiColumns; - - updatedColumns.IconColumn = _iconToggle.Active; - SwitchSettings.SwitchConfig.GuiColumns = updatedColumns; - - Configuration.SaveConfig(SwitchSettings.SwitchConfig, System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json")); + ConfigurationState.Instance.Ui.GuiColumns.IconColumn.Value = _iconToggle.Active; + SaveConfig(); UpdateColumns(); } private void Title_Toggled(object sender, EventArgs args) { - GuiColumns updatedColumns = SwitchSettings.SwitchConfig.GuiColumns; - - updatedColumns.AppColumn = _appToggle.Active; - SwitchSettings.SwitchConfig.GuiColumns = updatedColumns; - - Configuration.SaveConfig(SwitchSettings.SwitchConfig, System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json")); + ConfigurationState.Instance.Ui.GuiColumns.AppColumn.Value = _appToggle.Active; + SaveConfig(); UpdateColumns(); } private void Developer_Toggled(object sender, EventArgs args) { - GuiColumns updatedColumns = SwitchSettings.SwitchConfig.GuiColumns; - - updatedColumns.DevColumn = _developerToggle.Active; - SwitchSettings.SwitchConfig.GuiColumns = updatedColumns; - - Configuration.SaveConfig(SwitchSettings.SwitchConfig, System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json")); + ConfigurationState.Instance.Ui.GuiColumns.DevColumn.Value = _developerToggle.Active; + SaveConfig(); UpdateColumns(); } private void Version_Toggled(object sender, EventArgs args) { - GuiColumns updatedColumns = SwitchSettings.SwitchConfig.GuiColumns; - - updatedColumns.VersionColumn = _versionToggle.Active; - SwitchSettings.SwitchConfig.GuiColumns = updatedColumns; - - Configuration.SaveConfig(SwitchSettings.SwitchConfig, System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json")); + ConfigurationState.Instance.Ui.GuiColumns.VersionColumn.Value = _versionToggle.Active; + SaveConfig(); UpdateColumns(); } private void TimePlayed_Toggled(object sender, EventArgs args) { - GuiColumns updatedColumns = SwitchSettings.SwitchConfig.GuiColumns; - - updatedColumns.TimePlayedColumn = _timePlayedToggle.Active; - SwitchSettings.SwitchConfig.GuiColumns = updatedColumns; - - Configuration.SaveConfig(SwitchSettings.SwitchConfig, System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json")); + ConfigurationState.Instance.Ui.GuiColumns.TimePlayedColumn.Value = _timePlayedToggle.Active; + SaveConfig(); UpdateColumns(); } private void LastPlayed_Toggled(object sender, EventArgs args) { - GuiColumns updatedColumns = SwitchSettings.SwitchConfig.GuiColumns; - - updatedColumns.LastPlayedColumn = _lastPlayedToggle.Active; - SwitchSettings.SwitchConfig.GuiColumns = updatedColumns; - - Configuration.SaveConfig(SwitchSettings.SwitchConfig, System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json")); + ConfigurationState.Instance.Ui.GuiColumns.LastPlayedColumn.Value = _lastPlayedToggle.Active; + SaveConfig(); UpdateColumns(); } private void FileExt_Toggled(object sender, EventArgs args) { - GuiColumns updatedColumns = SwitchSettings.SwitchConfig.GuiColumns; - - updatedColumns.FileExtColumn = _fileExtToggle.Active; - SwitchSettings.SwitchConfig.GuiColumns = updatedColumns; - - Configuration.SaveConfig(SwitchSettings.SwitchConfig, System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json")); + ConfigurationState.Instance.Ui.GuiColumns.FileExtColumn.Value = _fileExtToggle.Active; + SaveConfig(); UpdateColumns(); } private void FileSize_Toggled(object sender, EventArgs args) { - GuiColumns updatedColumns = SwitchSettings.SwitchConfig.GuiColumns; - - updatedColumns.FileSizeColumn = _fileSizeToggle.Active; - SwitchSettings.SwitchConfig.GuiColumns = updatedColumns; - - Configuration.SaveConfig(SwitchSettings.SwitchConfig, System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json")); + ConfigurationState.Instance.Ui.GuiColumns.FileSizeColumn.Value = _fileSizeToggle.Active; + SaveConfig(); UpdateColumns(); } private void Path_Toggled(object sender, EventArgs args) { - GuiColumns updatedColumns = SwitchSettings.SwitchConfig.GuiColumns; - - updatedColumns.PathColumn = _pathToggle.Active; - SwitchSettings.SwitchConfig.GuiColumns = updatedColumns; - - Configuration.SaveConfig(SwitchSettings.SwitchConfig, System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json")); + ConfigurationState.Instance.Ui.GuiColumns.PathColumn.Value = _pathToggle.Active; + SaveConfig(); UpdateColumns(); } @@ -865,5 +776,10 @@ namespace Ryujinx.Ui return 0; } } + + public static void SaveConfig() + { + ConfigurationState.Instance.ToFileFormat().SaveConfig(System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json")); + } } } diff --git a/Ryujinx/Ui/NpadController.cs b/Ryujinx/Ui/NpadController.cs index f72c407551..67961b4914 100644 --- a/Ryujinx/Ui/NpadController.cs +++ b/Ryujinx/Ui/NpadController.cs @@ -1,160 +1,57 @@ using OpenTK; using OpenTK.Input; +using Ryujinx.Common.Configuration.Hid; using Ryujinx.HLE.Input; using System; +using InnerNpadController = Ryujinx.Common.Configuration.Hid.NpadController; + namespace Ryujinx.Ui.Input { - public enum ControllerInputId - { - Button0, - Button1, - Button2, - Button3, - Button4, - Button5, - Button6, - Button7, - Button8, - Button9, - Button10, - Button11, - Button12, - Button13, - Button14, - Button15, - Button16, - Button17, - Button18, - Button19, - Button20, - Axis0, - Axis1, - Axis2, - Axis3, - Axis4, - Axis5, - Hat0Up, - Hat0Down, - Hat0Left, - Hat0Right, - Hat1Up, - Hat1Down, - Hat1Left, - Hat1Right, - Hat2Up, - Hat2Down, - Hat2Left, - Hat2Right, - } - - public struct NpadControllerLeft - { - public ControllerInputId Stick; - public ControllerInputId StickButton; - public ControllerInputId ButtonMinus; - public ControllerInputId ButtonL; - public ControllerInputId ButtonZl; - public ControllerInputId DPadUp; - public ControllerInputId DPadDown; - public ControllerInputId DPadLeft; - public ControllerInputId DPadRight; - } - - public struct NpadControllerRight - { - public ControllerInputId Stick; - public ControllerInputId StickButton; - public ControllerInputId ButtonA; - public ControllerInputId ButtonB; - public ControllerInputId ButtonX; - public ControllerInputId ButtonY; - public ControllerInputId ButtonPlus; - public ControllerInputId ButtonR; - public ControllerInputId ButtonZr; - } - public class NpadController { - /// - /// Enables or disables controller support - /// - public bool Enabled { get; private set; } + private InnerNpadController _inner; - /// - /// Controller Device Index - /// - public int Index { get; private set; } - - /// - /// Controller Analog Stick Deadzone - /// - public float Deadzone { get; private set; } - - /// - /// Controller Trigger Threshold - /// - public float TriggerThreshold { get; private set; } - - /// - /// Left JoyCon Controller Bindings - /// - public NpadControllerLeft LeftJoycon { get; private set; } - - /// - /// Right JoyCon Controller Bindings - /// - public NpadControllerRight RightJoycon { get; private set; } - - public NpadController( - bool enabled, - int index, - float deadzone, - float triggerThreshold, - NpadControllerLeft leftJoycon, - NpadControllerRight rightJoycon) + // NOTE: This should be initialized AFTER GTK for compat reasons with OpenTK SDL2 backend and GTK on Linux. + // BODY: Usage of Joystick.GetState must be defer to after GTK full initialization. Otherwise, GTK will segfault because SDL2 was already init *sighs* + public NpadController(InnerNpadController inner) { - Enabled = enabled; - Index = index; - Deadzone = deadzone; - TriggerThreshold = triggerThreshold; - LeftJoycon = leftJoycon; - RightJoycon = rightJoycon; + _inner = inner; } - public void SetEnabled(bool enabled) + private bool IsEnabled() { - Enabled = enabled; + return _inner.Enabled && Joystick.GetState(_inner.Index).IsConnected; } public ControllerButtons GetButtons() { - if (!Enabled) + if (!IsEnabled()) { return 0; } - JoystickState joystickState = Joystick.GetState(Index); + JoystickState joystickState = Joystick.GetState(_inner.Index); ControllerButtons buttons = 0; - if (IsActivated(joystickState, LeftJoycon.DPadUp)) buttons |= ControllerButtons.DpadUp; - if (IsActivated(joystickState, LeftJoycon.DPadDown)) buttons |= ControllerButtons.DpadDown; - if (IsActivated(joystickState, LeftJoycon.DPadLeft)) buttons |= ControllerButtons.DpadLeft; - if (IsActivated(joystickState, LeftJoycon.DPadRight)) buttons |= ControllerButtons.DPadRight; - if (IsActivated(joystickState, LeftJoycon.StickButton)) buttons |= ControllerButtons.StickLeft; - if (IsActivated(joystickState, LeftJoycon.ButtonMinus)) buttons |= ControllerButtons.Minus; - if (IsActivated(joystickState, LeftJoycon.ButtonL)) buttons |= ControllerButtons.L; - if (IsActivated(joystickState, LeftJoycon.ButtonZl)) buttons |= ControllerButtons.Zl; + if (IsActivated(joystickState, _inner.LeftJoycon.DPadUp)) buttons |= ControllerButtons.DpadUp; + if (IsActivated(joystickState, _inner.LeftJoycon.DPadDown)) buttons |= ControllerButtons.DpadDown; + if (IsActivated(joystickState, _inner.LeftJoycon.DPadLeft)) buttons |= ControllerButtons.DpadLeft; + if (IsActivated(joystickState, _inner.LeftJoycon.DPadRight)) buttons |= ControllerButtons.DPadRight; + if (IsActivated(joystickState, _inner.LeftJoycon.StickButton)) buttons |= ControllerButtons.StickLeft; + if (IsActivated(joystickState, _inner.LeftJoycon.ButtonMinus)) buttons |= ControllerButtons.Minus; + if (IsActivated(joystickState, _inner.LeftJoycon.ButtonL)) buttons |= ControllerButtons.L; + if (IsActivated(joystickState, _inner.LeftJoycon.ButtonZl)) buttons |= ControllerButtons.Zl; - if (IsActivated(joystickState, RightJoycon.ButtonA)) buttons |= ControllerButtons.A; - if (IsActivated(joystickState, RightJoycon.ButtonB)) buttons |= ControllerButtons.B; - if (IsActivated(joystickState, RightJoycon.ButtonX)) buttons |= ControllerButtons.X; - if (IsActivated(joystickState, RightJoycon.ButtonY)) buttons |= ControllerButtons.Y; - if (IsActivated(joystickState, RightJoycon.StickButton)) buttons |= ControllerButtons.StickRight; - if (IsActivated(joystickState, RightJoycon.ButtonPlus)) buttons |= ControllerButtons.Plus; - if (IsActivated(joystickState, RightJoycon.ButtonR)) buttons |= ControllerButtons.R; - if (IsActivated(joystickState, RightJoycon.ButtonZr)) buttons |= ControllerButtons.Zr; + if (IsActivated(joystickState, _inner.RightJoycon.ButtonA)) buttons |= ControllerButtons.A; + if (IsActivated(joystickState, _inner.RightJoycon.ButtonB)) buttons |= ControllerButtons.B; + if (IsActivated(joystickState, _inner.RightJoycon.ButtonX)) buttons |= ControllerButtons.X; + if (IsActivated(joystickState, _inner.RightJoycon.ButtonY)) buttons |= ControllerButtons.Y; + if (IsActivated(joystickState, _inner.RightJoycon.StickButton)) buttons |= ControllerButtons.StickRight; + if (IsActivated(joystickState, _inner.RightJoycon.ButtonPlus)) buttons |= ControllerButtons.Plus; + if (IsActivated(joystickState, _inner.RightJoycon.ButtonR)) buttons |= ControllerButtons.R; + if (IsActivated(joystickState, _inner.RightJoycon.ButtonZr)) buttons |= ControllerButtons.Zr; return buttons; } @@ -169,7 +66,7 @@ namespace Ryujinx.Ui.Input { int axis = controllerInputId - ControllerInputId.Axis0; - return joystickState.GetAxis(axis) > TriggerThreshold; + return joystickState.GetAxis(axis) > _inner.TriggerThreshold; } else if (controllerInputId <= ControllerInputId.Hat2Right) { @@ -190,22 +87,22 @@ namespace Ryujinx.Ui.Input public (short, short) GetLeftStick() { - if (!Enabled) + if (!IsEnabled()) { return (0, 0); } - return GetStick(LeftJoycon.Stick); + return GetStick(_inner.LeftJoycon.Stick); } public (short, short) GetRightStick() { - if (!Enabled) + if (!IsEnabled()) { return (0, 0); } - return GetStick(RightJoycon.Stick); + return GetStick(_inner.RightJoycon.Stick); } private (short, short) GetStick(ControllerInputId stickInputId) @@ -215,7 +112,7 @@ namespace Ryujinx.Ui.Input return (0, 0); } - JoystickState jsState = Joystick.GetState(Index); + JoystickState jsState = Joystick.GetState(_inner.Index); int xAxis = stickInputId - ControllerInputId.Axis0; @@ -227,8 +124,8 @@ namespace Ryujinx.Ui.Input private (short, short) ApplyDeadzone(Vector2 axis) { - return (ClampAxis(MathF.Abs(axis.X) > Deadzone ? axis.X : 0f), - ClampAxis(MathF.Abs(axis.Y) > Deadzone ? axis.Y : 0f)); + return (ClampAxis(MathF.Abs(axis.X) > _inner.Deadzone ? axis.X : 0f), + ClampAxis(MathF.Abs(axis.Y) > _inner.Deadzone ? axis.Y : 0f)); } private static short ClampAxis(float value) diff --git a/Ryujinx/Ui/SwitchSettings.cs b/Ryujinx/Ui/SwitchSettings.cs index 955c6b0b64..5c56cf7ea1 100644 --- a/Ryujinx/Ui/SwitchSettings.cs +++ b/Ryujinx/Ui/SwitchSettings.cs @@ -1,12 +1,12 @@ using Gtk; -using Ryujinx.HLE.HOS.SystemState; -using Ryujinx.HLE.Input; -using Ryujinx.Ui.Input; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; +using Ryujinx.Configuration; +using Ryujinx.Configuration.System; +using Ryujinx.Configuration.Hid; using GUI = Gtk.Builder.ObjectAttribute; @@ -14,10 +14,6 @@ namespace Ryujinx.Ui { public class SwitchSettings : Window { - internal static Configuration SwitchConfig { get; set; } - - private readonly HLE.Switch _device; - private static ListStore _gameDirsBoxStore; private static bool _listeningForKeypress; @@ -83,16 +79,12 @@ namespace Ryujinx.Ui #pragma warning restore CS0649 #pragma warning restore IDE0044 - public static void ConfigureSettings(Configuration instance) { SwitchConfig = instance; } + public SwitchSettings() : this(new Builder("Ryujinx.Ui.SwitchSettings.glade")) { } - public SwitchSettings(HLE.Switch device) : this(new Builder("Ryujinx.Ui.SwitchSettings.glade"), device) { } - - private SwitchSettings(Builder builder, HLE.Switch device) : base(builder.GetObject("_settingsWin").Handle) + private SwitchSettings(Builder builder) : base(builder.GetObject("_settingsWin").Handle) { builder.Autoconnect(this); - _device = device; - _settingsWin.Icon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.Icon.png"); _controller1Image.Pixbuf = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.JoyCon.png", 500, 500); @@ -124,60 +116,123 @@ namespace Ryujinx.Ui _controller1Type.Changed += (sender, args) => Controller_Changed(sender, args, _controller1Type.ActiveId, _controller1Image); //Setup Currents - if (SwitchConfig.EnableFileLog) _fileLogToggle.Click(); - if (SwitchConfig.LoggingEnableError) _errorLogToggle.Click(); - if (SwitchConfig.LoggingEnableWarn) _warningLogToggle.Click(); - if (SwitchConfig.LoggingEnableInfo) _infoLogToggle.Click(); - if (SwitchConfig.LoggingEnableStub) _stubLogToggle.Click(); - if (SwitchConfig.LoggingEnableDebug) _debugLogToggle.Click(); - if (SwitchConfig.LoggingEnableGuest) _guestLogToggle.Click(); - if (SwitchConfig.LoggingEnableFsAccessLog) _fsAccessLogToggle.Click(); - if (SwitchConfig.DockedMode) _dockedModeToggle.Click(); - if (SwitchConfig.EnableDiscordIntegration) _discordToggle.Click(); - if (SwitchConfig.EnableVsync) _vSyncToggle.Click(); - if (SwitchConfig.EnableMulticoreScheduling) _multiSchedToggle.Click(); - if (SwitchConfig.EnableFsIntegrityChecks) _fsicToggle.Click(); - if (SwitchConfig.IgnoreMissingServices) _ignoreToggle.Click(); - if (SwitchConfig.EnableKeyboard) _directKeyboardAccess.Click(); - if (SwitchConfig.EnableCustomTheme) _custThemeToggle.Click(); + if (ConfigurationState.Instance.Logger.EnableFileLog) + { + _fileLogToggle.Click(); + } - _systemLanguageSelect.SetActiveId(SwitchConfig.SystemLanguage.ToString()); - _controller1Type .SetActiveId(SwitchConfig.ControllerType.ToString()); + if (ConfigurationState.Instance.Logger.EnableError) + { + _errorLogToggle.Click(); + } + + if (ConfigurationState.Instance.Logger.EnableWarn) + { + _warningLogToggle.Click(); + } + + if (ConfigurationState.Instance.Logger.EnableInfo) + { + _infoLogToggle.Click(); + } + + if (ConfigurationState.Instance.Logger.EnableStub) + { + _stubLogToggle.Click(); + } + + if (ConfigurationState.Instance.Logger.EnableDebug) + { + _debugLogToggle.Click(); + } + + if (ConfigurationState.Instance.Logger.EnableGuest) + { + _guestLogToggle.Click(); + } + + if (ConfigurationState.Instance.Logger.EnableFsAccessLog) + { + _fsAccessLogToggle.Click(); + } + + if (ConfigurationState.Instance.System.EnableDockedMode) + { + _dockedModeToggle.Click(); + } + + if (ConfigurationState.Instance.EnableDiscordIntegration) + { + _discordToggle.Click(); + } + + if (ConfigurationState.Instance.Graphics.EnableVsync) + { + _vSyncToggle.Click(); + } + + if (ConfigurationState.Instance.System.EnableMulticoreScheduling) + { + _multiSchedToggle.Click(); + } + + if (ConfigurationState.Instance.System.EnableFsIntegrityChecks) + { + _fsicToggle.Click(); + } + + if (ConfigurationState.Instance.System.IgnoreMissingServices) + { + _ignoreToggle.Click(); + } + + if (ConfigurationState.Instance.Hid.EnableKeyboard) + { + _directKeyboardAccess.Click(); + } + + if (ConfigurationState.Instance.Ui.EnableCustomTheme) + { + _custThemeToggle.Click(); + } + + _systemLanguageSelect.SetActiveId(ConfigurationState.Instance.System.Language.Value.ToString()); + _controller1Type .SetActiveId(ConfigurationState.Instance.Hid.ControllerType.Value.ToString()); Controller_Changed(null, null, _controller1Type.ActiveId, _controller1Image); - _lStickUp1.Label = SwitchConfig.KeyboardControls.LeftJoycon.StickUp.ToString(); - _lStickDown1.Label = SwitchConfig.KeyboardControls.LeftJoycon.StickDown.ToString(); - _lStickLeft1.Label = SwitchConfig.KeyboardControls.LeftJoycon.StickLeft.ToString(); - _lStickRight1.Label = SwitchConfig.KeyboardControls.LeftJoycon.StickRight.ToString(); - _lStickButton1.Label = SwitchConfig.KeyboardControls.LeftJoycon.StickButton.ToString(); - _dpadUp1.Label = SwitchConfig.KeyboardControls.LeftJoycon.DPadUp.ToString(); - _dpadDown1.Label = SwitchConfig.KeyboardControls.LeftJoycon.DPadDown.ToString(); - _dpadLeft1.Label = SwitchConfig.KeyboardControls.LeftJoycon.DPadLeft.ToString(); - _dpadRight1.Label = SwitchConfig.KeyboardControls.LeftJoycon.DPadRight.ToString(); - _minus1.Label = SwitchConfig.KeyboardControls.LeftJoycon.ButtonMinus.ToString(); - _l1.Label = SwitchConfig.KeyboardControls.LeftJoycon.ButtonL.ToString(); - _zL1.Label = SwitchConfig.KeyboardControls.LeftJoycon.ButtonZl.ToString(); - _rStickUp1.Label = SwitchConfig.KeyboardControls.RightJoycon.StickUp.ToString(); - _rStickDown1.Label = SwitchConfig.KeyboardControls.RightJoycon.StickDown.ToString(); - _rStickLeft1.Label = SwitchConfig.KeyboardControls.RightJoycon.StickLeft.ToString(); - _rStickRight1.Label = SwitchConfig.KeyboardControls.RightJoycon.StickRight.ToString(); - _rStickButton1.Label = SwitchConfig.KeyboardControls.RightJoycon.StickButton.ToString(); - _a1.Label = SwitchConfig.KeyboardControls.RightJoycon.ButtonA.ToString(); - _b1.Label = SwitchConfig.KeyboardControls.RightJoycon.ButtonB.ToString(); - _x1.Label = SwitchConfig.KeyboardControls.RightJoycon.ButtonX.ToString(); - _y1.Label = SwitchConfig.KeyboardControls.RightJoycon.ButtonY.ToString(); - _plus1.Label = SwitchConfig.KeyboardControls.RightJoycon.ButtonPlus.ToString(); - _r1.Label = SwitchConfig.KeyboardControls.RightJoycon.ButtonR.ToString(); - _zR1.Label = SwitchConfig.KeyboardControls.RightJoycon.ButtonZr.ToString(); + _lStickUp1.Label = ConfigurationState.Instance.Hid.KeyboardControls.Value.LeftJoycon.StickUp.ToString(); + _lStickDown1.Label = ConfigurationState.Instance.Hid.KeyboardControls.Value.LeftJoycon.StickDown.ToString(); + _lStickLeft1.Label = ConfigurationState.Instance.Hid.KeyboardControls.Value.LeftJoycon.StickLeft.ToString(); + _lStickRight1.Label = ConfigurationState.Instance.Hid.KeyboardControls.Value.LeftJoycon.StickRight.ToString(); + _lStickButton1.Label = ConfigurationState.Instance.Hid.KeyboardControls.Value.LeftJoycon.StickButton.ToString(); + _dpadUp1.Label = ConfigurationState.Instance.Hid.KeyboardControls.Value.LeftJoycon.DPadUp.ToString(); + _dpadDown1.Label = ConfigurationState.Instance.Hid.KeyboardControls.Value.LeftJoycon.DPadDown.ToString(); + _dpadLeft1.Label = ConfigurationState.Instance.Hid.KeyboardControls.Value.LeftJoycon.DPadLeft.ToString(); + _dpadRight1.Label = ConfigurationState.Instance.Hid.KeyboardControls.Value.LeftJoycon.DPadRight.ToString(); + _minus1.Label = ConfigurationState.Instance.Hid.KeyboardControls.Value.LeftJoycon.ButtonMinus.ToString(); + _l1.Label = ConfigurationState.Instance.Hid.KeyboardControls.Value.LeftJoycon.ButtonL.ToString(); + _zL1.Label = ConfigurationState.Instance.Hid.KeyboardControls.Value.LeftJoycon.ButtonZl.ToString(); + _rStickUp1.Label = ConfigurationState.Instance.Hid.KeyboardControls.Value.RightJoycon.StickUp.ToString(); + _rStickDown1.Label = ConfigurationState.Instance.Hid.KeyboardControls.Value.RightJoycon.StickDown.ToString(); + _rStickLeft1.Label = ConfigurationState.Instance.Hid.KeyboardControls.Value.RightJoycon.StickLeft.ToString(); + _rStickRight1.Label = ConfigurationState.Instance.Hid.KeyboardControls.Value.RightJoycon.StickRight.ToString(); + _rStickButton1.Label = ConfigurationState.Instance.Hid.KeyboardControls.Value.RightJoycon.StickButton.ToString(); + _a1.Label = ConfigurationState.Instance.Hid.KeyboardControls.Value.RightJoycon.ButtonA.ToString(); + _b1.Label = ConfigurationState.Instance.Hid.KeyboardControls.Value.RightJoycon.ButtonB.ToString(); + _x1.Label = ConfigurationState.Instance.Hid.KeyboardControls.Value.RightJoycon.ButtonX.ToString(); + _y1.Label = ConfigurationState.Instance.Hid.KeyboardControls.Value.RightJoycon.ButtonY.ToString(); + _plus1.Label = ConfigurationState.Instance.Hid.KeyboardControls.Value.RightJoycon.ButtonPlus.ToString(); + _r1.Label = ConfigurationState.Instance.Hid.KeyboardControls.Value.RightJoycon.ButtonR.ToString(); + _zR1.Label = ConfigurationState.Instance.Hid.KeyboardControls.Value.RightJoycon.ButtonZr.ToString(); - _custThemePath.Buffer.Text = SwitchConfig.CustomThemePath; - _graphicsShadersDumpPath.Buffer.Text = SwitchConfig.GraphicsShadersDumpPath; - _fsLogSpinAdjustment.Value = SwitchConfig.FsGlobalAccessLogMode; + _custThemePath.Buffer.Text = ConfigurationState.Instance.Ui.CustomThemePath; + _graphicsShadersDumpPath.Buffer.Text = ConfigurationState.Instance.Graphics.ShadersDumpPath; + _fsLogSpinAdjustment.Value = ConfigurationState.Instance.System.FsGlobalAccessLogMode; _gameDirsBox.AppendColumn("", new CellRendererText(), "text", 0); _gameDirsBoxStore = new ListStore(typeof(string)); _gameDirsBox.Model = _gameDirsBoxStore; - foreach (string gameDir in SwitchConfig.GameDirs) + foreach (string gameDir in ConfigurationState.Instance.Ui.GameDirs.Value) { _gameDirsBoxStore.AppendValues(gameDir); } @@ -208,7 +263,7 @@ namespace Ryujinx.Ui string key = keyPressed.Event.Key.ToString(); string capKey = key.First().ToString().ToUpper() + key.Substring(1); - if (Enum.IsDefined(typeof(OpenTK.Input.Key), capKey)) + if (Enum.IsDefined(typeof(Configuration.Hid.Key), capKey)) { button.Label = capKey; } @@ -325,65 +380,63 @@ namespace Ryujinx.Ui _gameDirsBoxStore.IterNext(ref treeIter); } - SwitchConfig.LoggingEnableError = _errorLogToggle.Active; - SwitchConfig.LoggingEnableWarn = _warningLogToggle.Active; - SwitchConfig.LoggingEnableInfo = _infoLogToggle.Active; - SwitchConfig.LoggingEnableStub = _stubLogToggle.Active; - SwitchConfig.LoggingEnableDebug = _debugLogToggle.Active; - SwitchConfig.LoggingEnableGuest = _guestLogToggle.Active; - SwitchConfig.LoggingEnableFsAccessLog = _fsAccessLogToggle.Active; - SwitchConfig.EnableFileLog = _fileLogToggle.Active; - SwitchConfig.DockedMode = _dockedModeToggle.Active; - SwitchConfig.EnableDiscordIntegration = _discordToggle.Active; - SwitchConfig.EnableVsync = _vSyncToggle.Active; - SwitchConfig.EnableMulticoreScheduling = _multiSchedToggle.Active; - SwitchConfig.EnableFsIntegrityChecks = _fsicToggle.Active; - SwitchConfig.IgnoreMissingServices = _ignoreToggle.Active; - SwitchConfig.EnableKeyboard = _directKeyboardAccess.Active; - SwitchConfig.EnableCustomTheme = _custThemeToggle.Active; + ConfigurationState.Instance.Logger.EnableError.Value = _errorLogToggle.Active; + ConfigurationState.Instance.Logger.EnableWarn.Value = _warningLogToggle.Active; + ConfigurationState.Instance.Logger.EnableInfo.Value = _infoLogToggle.Active; + ConfigurationState.Instance.Logger.EnableStub.Value = _stubLogToggle.Active; + ConfigurationState.Instance.Logger.EnableDebug.Value = _debugLogToggle.Active; + ConfigurationState.Instance.Logger.EnableGuest.Value = _guestLogToggle.Active; + ConfigurationState.Instance.Logger.EnableFsAccessLog.Value = _fsAccessLogToggle.Active; + ConfigurationState.Instance.Logger.EnableFileLog.Value = _fileLogToggle.Active; + ConfigurationState.Instance.System.EnableDockedMode.Value = _dockedModeToggle.Active; + ConfigurationState.Instance.EnableDiscordIntegration.Value = _discordToggle.Active; + ConfigurationState.Instance.Graphics.EnableVsync.Value = _vSyncToggle.Active; + ConfigurationState.Instance.System.EnableMulticoreScheduling.Value = _multiSchedToggle.Active; + ConfigurationState.Instance.System.EnableFsIntegrityChecks.Value = _fsicToggle.Active; + ConfigurationState.Instance.System.IgnoreMissingServices.Value = _ignoreToggle.Active; + ConfigurationState.Instance.Hid.EnableKeyboard.Value = _directKeyboardAccess.Active; + ConfigurationState.Instance.Ui.EnableCustomTheme.Value = _custThemeToggle.Active; - SwitchConfig.KeyboardControls.LeftJoycon = new NpadKeyboardLeft() + ConfigurationState.Instance.Hid.KeyboardControls.Value.LeftJoycon = new NpadKeyboardLeft() { - StickUp = (OpenTK.Input.Key)Enum.Parse(typeof(OpenTK.Input.Key), _lStickUp1.Label), - StickDown = (OpenTK.Input.Key)Enum.Parse(typeof(OpenTK.Input.Key), _lStickDown1.Label), - StickLeft = (OpenTK.Input.Key)Enum.Parse(typeof(OpenTK.Input.Key), _lStickLeft1.Label), - StickRight = (OpenTK.Input.Key)Enum.Parse(typeof(OpenTK.Input.Key), _lStickRight1.Label), - StickButton = (OpenTK.Input.Key)Enum.Parse(typeof(OpenTK.Input.Key), _lStickButton1.Label), - DPadUp = (OpenTK.Input.Key)Enum.Parse(typeof(OpenTK.Input.Key), _dpadUp1.Label), - DPadDown = (OpenTK.Input.Key)Enum.Parse(typeof(OpenTK.Input.Key), _dpadDown1.Label), - DPadLeft = (OpenTK.Input.Key)Enum.Parse(typeof(OpenTK.Input.Key), _dpadLeft1.Label), - DPadRight = (OpenTK.Input.Key)Enum.Parse(typeof(OpenTK.Input.Key), _dpadRight1.Label), - ButtonMinus = (OpenTK.Input.Key)Enum.Parse(typeof(OpenTK.Input.Key), _minus1.Label), - ButtonL = (OpenTK.Input.Key)Enum.Parse(typeof(OpenTK.Input.Key), _l1.Label), - ButtonZl = (OpenTK.Input.Key)Enum.Parse(typeof(OpenTK.Input.Key), _zL1.Label), + StickUp = (Configuration.Hid.Key)Enum.Parse(typeof(Configuration.Hid.Key), _lStickUp1.Label), + StickDown = (Configuration.Hid.Key)Enum.Parse(typeof(Configuration.Hid.Key), _lStickDown1.Label), + StickLeft = (Configuration.Hid.Key)Enum.Parse(typeof(Configuration.Hid.Key), _lStickLeft1.Label), + StickRight = (Configuration.Hid.Key)Enum.Parse(typeof(Configuration.Hid.Key), _lStickRight1.Label), + StickButton = (Configuration.Hid.Key)Enum.Parse(typeof(Configuration.Hid.Key), _lStickButton1.Label), + DPadUp = (Configuration.Hid.Key)Enum.Parse(typeof(Configuration.Hid.Key), _dpadUp1.Label), + DPadDown = (Configuration.Hid.Key)Enum.Parse(typeof(Configuration.Hid.Key), _dpadDown1.Label), + DPadLeft = (Configuration.Hid.Key)Enum.Parse(typeof(Configuration.Hid.Key), _dpadLeft1.Label), + DPadRight = (Configuration.Hid.Key)Enum.Parse(typeof(Configuration.Hid.Key), _dpadRight1.Label), + ButtonMinus = (Configuration.Hid.Key)Enum.Parse(typeof(Configuration.Hid.Key), _minus1.Label), + ButtonL = (Configuration.Hid.Key)Enum.Parse(typeof(Configuration.Hid.Key), _l1.Label), + ButtonZl = (Configuration.Hid.Key)Enum.Parse(typeof(Configuration.Hid.Key), _zL1.Label), }; - SwitchConfig.KeyboardControls.RightJoycon = new NpadKeyboardRight() + ConfigurationState.Instance.Hid.KeyboardControls.Value.RightJoycon = new NpadKeyboardRight() { - StickUp = (OpenTK.Input.Key)Enum.Parse(typeof(OpenTK.Input.Key), _rStickUp1.Label), - StickDown = (OpenTK.Input.Key)Enum.Parse(typeof(OpenTK.Input.Key), _rStickDown1.Label), - StickLeft = (OpenTK.Input.Key)Enum.Parse(typeof(OpenTK.Input.Key), _rStickLeft1.Label), - StickRight = (OpenTK.Input.Key)Enum.Parse(typeof(OpenTK.Input.Key), _rStickRight1.Label), - StickButton = (OpenTK.Input.Key)Enum.Parse(typeof(OpenTK.Input.Key), _rStickButton1.Label), - ButtonA = (OpenTK.Input.Key)Enum.Parse(typeof(OpenTK.Input.Key), _a1.Label), - ButtonB = (OpenTK.Input.Key)Enum.Parse(typeof(OpenTK.Input.Key), _b1.Label), - ButtonX = (OpenTK.Input.Key)Enum.Parse(typeof(OpenTK.Input.Key), _x1.Label), - ButtonY = (OpenTK.Input.Key)Enum.Parse(typeof(OpenTK.Input.Key), _y1.Label), - ButtonPlus = (OpenTK.Input.Key)Enum.Parse(typeof(OpenTK.Input.Key), _plus1.Label), - ButtonR = (OpenTK.Input.Key)Enum.Parse(typeof(OpenTK.Input.Key), _r1.Label), - ButtonZr = (OpenTK.Input.Key)Enum.Parse(typeof(OpenTK.Input.Key), _zR1.Label), + StickUp = (Configuration.Hid.Key)Enum.Parse(typeof(Configuration.Hid.Key), _rStickUp1.Label), + StickDown = (Configuration.Hid.Key)Enum.Parse(typeof(Configuration.Hid.Key), _rStickDown1.Label), + StickLeft = (Configuration.Hid.Key)Enum.Parse(typeof(Configuration.Hid.Key), _rStickLeft1.Label), + StickRight = (Configuration.Hid.Key)Enum.Parse(typeof(Configuration.Hid.Key), _rStickRight1.Label), + StickButton = (Configuration.Hid.Key)Enum.Parse(typeof(Configuration.Hid.Key), _rStickButton1.Label), + ButtonA = (Configuration.Hid.Key)Enum.Parse(typeof(Configuration.Hid.Key), _a1.Label), + ButtonB = (Configuration.Hid.Key)Enum.Parse(typeof(Configuration.Hid.Key), _b1.Label), + ButtonX = (Configuration.Hid.Key)Enum.Parse(typeof(Configuration.Hid.Key), _x1.Label), + ButtonY = (Configuration.Hid.Key)Enum.Parse(typeof(Configuration.Hid.Key), _y1.Label), + ButtonPlus = (Configuration.Hid.Key)Enum.Parse(typeof(Configuration.Hid.Key), _plus1.Label), + ButtonR = (Configuration.Hid.Key)Enum.Parse(typeof(Configuration.Hid.Key), _r1.Label), + ButtonZr = (Configuration.Hid.Key)Enum.Parse(typeof(Configuration.Hid.Key), _zR1.Label), }; - SwitchConfig.SystemLanguage = (SystemLanguage)Enum.Parse(typeof(SystemLanguage), _systemLanguageSelect.ActiveId); - SwitchConfig.ControllerType = (ControllerStatus)Enum.Parse(typeof(ControllerStatus), _controller1Type.ActiveId); - SwitchConfig.CustomThemePath = _custThemePath.Buffer.Text; - SwitchConfig.GraphicsShadersDumpPath = _graphicsShadersDumpPath.Buffer.Text; - SwitchConfig.GameDirs = gameDirs; - SwitchConfig.FsGlobalAccessLogMode = (int)_fsLogSpinAdjustment.Value; - - Configuration.SaveConfig(SwitchConfig, System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json")); - Configuration.Configure(_device, SwitchConfig); + ConfigurationState.Instance.System.Language.Value = (Language)Enum.Parse(typeof(Language), _systemLanguageSelect.ActiveId); + ConfigurationState.Instance.Hid.ControllerType.Value = (ControllerType)Enum.Parse(typeof(ControllerType), _controller1Type.ActiveId); + ConfigurationState.Instance.Ui.CustomThemePath.Value = _custThemePath.Buffer.Text; + ConfigurationState.Instance.Graphics.ShadersDumpPath.Value = _graphicsShadersDumpPath.Buffer.Text; + ConfigurationState.Instance.Ui.GameDirs.Value = gameDirs; + ConfigurationState.Instance.System.FsGlobalAccessLogMode.Value = (int)_fsLogSpinAdjustment.Value; + MainWindow.SaveConfig(); MainWindow.ApplyTheme(); #pragma warning disable CS4014 MainWindow.UpdateGameTable(); From bd010869a5e6005331be2f6f3ed0e72dfc006d41 Mon Sep 17 00:00:00 2001 From: Xpl0itR Date: Sun, 22 Dec 2019 02:49:51 +0000 Subject: [PATCH 03/10] GUI: Implement context menu for the game table (#840) * Add context menu to the game table * Minor bugfix and cleanup * add ability to create directory if it doesn't exist * nit * dont show menu when right-clicking nothing --- Ryujinx/Ryujinx.csproj | 2 + Ryujinx/Ui/GameTableContextMenu.cs | 75 ++++++++++++++++++++++++ Ryujinx/Ui/GameTableContextMenu.glade | 18 ++++++ Ryujinx/Ui/MainWindow.cs | 82 +++++++++++++-------------- Ryujinx/Ui/MainWindow.glade | 2 +- 5 files changed, 135 insertions(+), 44 deletions(-) create mode 100644 Ryujinx/Ui/GameTableContextMenu.cs create mode 100644 Ryujinx/Ui/GameTableContextMenu.glade diff --git a/Ryujinx/Ryujinx.csproj b/Ryujinx/Ryujinx.csproj index b231ddb8d2..e610e8277e 100644 --- a/Ryujinx/Ryujinx.csproj +++ b/Ryujinx/Ryujinx.csproj @@ -43,6 +43,7 @@ + @@ -63,6 +64,7 @@ + diff --git a/Ryujinx/Ui/GameTableContextMenu.cs b/Ryujinx/Ui/GameTableContextMenu.cs new file mode 100644 index 0000000000..f8d1d6815f --- /dev/null +++ b/Ryujinx/Ui/GameTableContextMenu.cs @@ -0,0 +1,75 @@ +using Gtk; +using Ryujinx.HLE.FileSystem; +using System; +using System.Diagnostics; +using System.IO; +using System.Reflection; + +using GUI = Gtk.Builder.ObjectAttribute; + +namespace Ryujinx.Ui +{ + public class GameTableContextMenu : Menu + { + private static ListStore _gameTableStore; + private static TreeIter _rowIter; + +#pragma warning disable CS0649 +#pragma warning disable IDE0044 + [GUI] MenuItem _openSaveDir; +#pragma warning restore CS0649 +#pragma warning restore IDE0044 + + public GameTableContextMenu(ListStore gameTableStore, TreeIter rowIter) : this(new Builder("Ryujinx.Ui.GameTableContextMenu.glade"), gameTableStore, rowIter) { } + + private GameTableContextMenu(Builder builder, ListStore gameTableStore, TreeIter rowIter) : base(builder.GetObject("_contextMenu").Handle) + { + builder.Autoconnect(this); + + _openSaveDir.Activated += OpenSaveDir_Clicked; + + _gameTableStore = gameTableStore; + _rowIter = rowIter; + } + + //Events + private void OpenSaveDir_Clicked(object sender, EventArgs args) + { + string titleName = _gameTableStore.GetValue(_rowIter, 2).ToString().Split("\n")[0]; + string titleId = _gameTableStore.GetValue(_rowIter, 2).ToString().Split("\n")[1].ToLower(); + string saveDir = System.IO.Path.Combine(new VirtualFileSystem().GetNandPath(), "user", "save", "0000000000000000", "00000000000000000000000000000001", titleId, "0"); + + if (!Directory.Exists(saveDir)) + { + MessageDialog messageDialog = new MessageDialog(null, DialogFlags.Modal, MessageType.Question, ButtonsType.YesNo, null) + { + Title = "Ryujinx", + Icon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.Icon.png"), + Text = $"Could not find save directory for {titleName} [{titleId}]", + SecondaryText = "Would you like to create the directory?", + WindowPosition = WindowPosition.Center + }; + + if (messageDialog.Run() == (int)ResponseType.Yes) + { + Directory.CreateDirectory(saveDir); + } + else + { + messageDialog.Dispose(); + + return; + } + + messageDialog.Dispose(); + } + + Process.Start(new ProcessStartInfo() + { + FileName = saveDir, + UseShellExecute = true, + Verb = "open" + }); + } + } +} diff --git a/Ryujinx/Ui/GameTableContextMenu.glade b/Ryujinx/Ui/GameTableContextMenu.glade new file mode 100644 index 0000000000..2c9e097292 --- /dev/null +++ b/Ryujinx/Ui/GameTableContextMenu.glade @@ -0,0 +1,18 @@ + + + + + + True + False + + + True + False + Open the folder where saves for the application is loaded + Open Save Directory + True + + + + diff --git a/Ryujinx/Ui/MainWindow.cs b/Ryujinx/Ui/MainWindow.cs index e0bd849479..dc3315e979 100644 --- a/Ryujinx/Ui/MainWindow.cs +++ b/Ryujinx/Ui/MainWindow.cs @@ -38,16 +38,6 @@ namespace Ryujinx.Ui private static bool _gameLoaded; private static bool _ending; - private static TreeViewColumn _favColumn; - private static TreeViewColumn _appColumn; - private static TreeViewColumn _devColumn; - private static TreeViewColumn _versionColumn; - private static TreeViewColumn _timePlayedColumn; - private static TreeViewColumn _lastPlayedColumn; - private static TreeViewColumn _fileExtColumn; - private static TreeViewColumn _fileSizeColumn; - private static TreeViewColumn _pathColumn; - private static TreeView _treeView; #pragma warning disable CS0649 @@ -66,6 +56,7 @@ namespace Ryujinx.Ui [GUI] CheckMenuItem _fileSizeToggle; [GUI] CheckMenuItem _pathToggle; [GUI] TreeView _gameTable; + [GUI] TreeSelection _gameTableSelection; [GUI] Label _progressLabel; [GUI] LevelBar _progressBar; #pragma warning restore CS0649 @@ -81,6 +72,8 @@ namespace Ryujinx.Ui ApplicationLibrary.ApplicationAdded += Application_Added; + _gameTable.ButtonReleaseEvent += Row_Clicked; + _renderer = new OglRenderer(); _audioOut = InitializeAudioEngine(); @@ -173,26 +166,16 @@ namespace Ryujinx.Ui foreach (TreeViewColumn column in _gameTable.Columns) { - if (column.Title == "Fav") _favColumn = column; - else if (column.Title == "Application") _appColumn = column; - else if (column.Title == "Developer") _devColumn = column; - else if (column.Title == "Version") _versionColumn = column; - else if (column.Title == "Time Played") _timePlayedColumn = column; - else if (column.Title == "Last Played") _lastPlayedColumn = column; - else if (column.Title == "File Ext") _fileExtColumn = column; - else if (column.Title == "File Size") _fileSizeColumn = column; - else if (column.Title == "Path") _pathColumn = column; + if (column.Title == "Fav" && ConfigurationState.Instance.Ui.GuiColumns.FavColumn) column.SortColumnId = 0; + else if (column.Title == "Application" && ConfigurationState.Instance.Ui.GuiColumns.AppColumn) column.SortColumnId = 2; + else if (column.Title == "Developer" && ConfigurationState.Instance.Ui.GuiColumns.DevColumn) column.SortColumnId = 3; + else if (column.Title == "Version" && ConfigurationState.Instance.Ui.GuiColumns.VersionColumn) column.SortColumnId = 4; + else if (column.Title == "Time Played" && ConfigurationState.Instance.Ui.GuiColumns.TimePlayedColumn) column.SortColumnId = 5; + else if (column.Title == "Last Played" && ConfigurationState.Instance.Ui.GuiColumns.LastPlayedColumn) column.SortColumnId = 6; + else if (column.Title == "File Ext" && ConfigurationState.Instance.Ui.GuiColumns.FileExtColumn) column.SortColumnId = 7; + else if (column.Title == "File Size" && ConfigurationState.Instance.Ui.GuiColumns.FileSizeColumn) column.SortColumnId = 8; + else if (column.Title == "Path" && ConfigurationState.Instance.Ui.GuiColumns.PathColumn) column.SortColumnId = 9; } - - if (ConfigurationState.Instance.Ui.GuiColumns.FavColumn) _favColumn.SortColumnId = 0; - if (ConfigurationState.Instance.Ui.GuiColumns.AppColumn) _appColumn.SortColumnId = 2; - if (ConfigurationState.Instance.Ui.GuiColumns.DevColumn) _devColumn.SortColumnId = 3; - if (ConfigurationState.Instance.Ui.GuiColumns.VersionColumn) _versionColumn.SortColumnId = 4; - if (ConfigurationState.Instance.Ui.GuiColumns.TimePlayedColumn) _timePlayedColumn.SortColumnId = 5; - if (ConfigurationState.Instance.Ui.GuiColumns.LastPlayedColumn) _lastPlayedColumn.SortColumnId = 6; - if (ConfigurationState.Instance.Ui.GuiColumns.FileExtColumn) _fileExtColumn.SortColumnId = 7; - if (ConfigurationState.Instance.Ui.GuiColumns.FileSizeColumn) _fileSizeColumn.SortColumnId = 8; - if (ConfigurationState.Instance.Ui.GuiColumns.PathColumn) _pathColumn.SortColumnId = 9; } private HLE.Switch InitializeSwitchInstance() @@ -421,24 +404,24 @@ namespace Ryujinx.Ui } //Events - private void Application_Added(object sender, ApplicationAddedEventArgs e) + private void Application_Added(object sender, ApplicationAddedEventArgs args) { Application.Invoke(delegate { _tableStore.AppendValues( - e.AppData.Favorite, - new Gdk.Pixbuf(e.AppData.Icon, 75, 75), - $"{e.AppData.TitleName}\n{e.AppData.TitleId.ToUpper()}", - e.AppData.Developer, - e.AppData.Version, - e.AppData.TimePlayed, - e.AppData.LastPlayed, - e.AppData.FileExtension, - e.AppData.FileSize, - e.AppData.Path); + args.AppData.Favorite, + new Gdk.Pixbuf(args.AppData.Icon, 75, 75), + $"{args.AppData.TitleName}\n{args.AppData.TitleId.ToUpper()}", + args.AppData.Developer, + args.AppData.Version, + args.AppData.TimePlayed, + args.AppData.LastPlayed, + args.AppData.FileExtension, + args.AppData.FileSize, + args.AppData.Path); - _progressLabel.Text = $"{e.NumAppsLoaded}/{e.NumAppsFound} Games Loaded"; - _progressBar.Value = (float)e.NumAppsLoaded / e.NumAppsFound; + _progressLabel.Text = $"{args.NumAppsLoaded}/{args.NumAppsFound} Games Loaded"; + _progressBar.Value = (float)args.NumAppsLoaded / args.NumAppsFound; }); } @@ -477,12 +460,25 @@ namespace Ryujinx.Ui private void Row_Activated(object sender, RowActivatedArgs args) { - _tableStore.GetIter(out TreeIter treeIter, new TreePath(args.Path.ToString())); + _gameTableSelection.GetSelected(out TreeIter treeIter); string path = (string)_tableStore.GetValue(treeIter, 9); LoadApplication(path); } + private void Row_Clicked(object sender, ButtonReleaseEventArgs args) + { + if (args.Event.Button != 3) return; + + _gameTableSelection.GetSelected(out TreeIter treeIter); + + if (treeIter.UserData == IntPtr.Zero) return; + + GameTableContextMenu contextMenu = new GameTableContextMenu(_tableStore, treeIter); + contextMenu.ShowAll(); + contextMenu.PopupAtPointer(null); + } + private void Load_Application_File(object sender, EventArgs args) { FileChooserDialog fileChooser = new FileChooserDialog("Choose the file to open", this, FileChooserAction.Open, "Cancel", ResponseType.Cancel, "Open", ResponseType.Accept); diff --git a/Ryujinx/Ui/MainWindow.glade b/Ryujinx/Ui/MainWindow.glade index 6d66dc2007..fcf91bc4c5 100644 --- a/Ryujinx/Ui/MainWindow.glade +++ b/Ryujinx/Ui/MainWindow.glade @@ -330,7 +330,7 @@ True - + From bce8972e7a42687c2add1bd939d2c8b2441dc5f1 Mon Sep 17 00:00:00 2001 From: Thog Date: Sun, 22 Dec 2019 20:33:59 +0100 Subject: [PATCH 04/10] Fix an endge case in bsd IClient::Poll implementation (#848) This add some code to handle usage of poll without any fds. This is required by Dark Souls Remastered main loop logic as it's calling it without any fds during initialization. === General system stability improvements to enhance the user's experience. --- .../HOS/Services/Sockets/Bsd/IClient.cs | 26 ++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/Ryujinx.HLE/HOS/Services/Sockets/Bsd/IClient.cs b/Ryujinx.HLE/HOS/Services/Sockets/Bsd/IClient.cs index af9b38815e..b2b3d05260 100644 --- a/Ryujinx.HLE/HOS/Services/Sockets/Bsd/IClient.cs +++ b/Ryujinx.HLE/HOS/Services/Sockets/Bsd/IClient.cs @@ -1,11 +1,12 @@ -using Ryujinx.Common; -using Ryujinx.Common.Logging; +using Ryujinx.Common.Logging; using Ryujinx.HLE.Utilities; +using System; using System.Buffers.Binary; using System.Collections.Generic; using System.Net; using System.Net.Sockets; using System.Text; +using System.Threading; namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd { @@ -379,13 +380,26 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd } } - try + if (fdsCount != 0) { - System.Net.Sockets.Socket.Select(readEvents, writeEvents, errorEvents, timeout); + try + { + System.Net.Sockets.Socket.Select(readEvents, writeEvents, errorEvents, timeout); + } + catch (SocketException exception) + { + return WriteWinSock2Error(context, (WsaError)exception.ErrorCode); + } } - catch (SocketException exception) + else if (timeout == -1) { - return WriteWinSock2Error(context, (WsaError)exception.ErrorCode); + // FIXME: If we get a timeout of -1 and there is no fds to wait on, this should kill the KProces. (need to check that with re) + throw new InvalidOperationException(); + } + else + { + // FIXME: We should make the KThread sleep but we can't do much about it yet. + Thread.Sleep(timeout); } for (int i = 0; i < fdsCount; i++) From 17b2be7174b857a48c24592e2e6c067324821ade Mon Sep 17 00:00:00 2001 From: Thog Date: Sun, 22 Dec 2019 20:36:24 +0100 Subject: [PATCH 05/10] Also use Github Actions as a CI" (#847) --- .github/workflows/build.yml | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 .github/workflows/build.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000000..cca6c60806 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,27 @@ +name: "Build job" +on: + push: + branches: + - master + pull_request: + branches: + - '*' +jobs: + build: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macOS-latest, windows-latest] + dotnet: ['3.1.100'] + environment: ['Debug', 'Release', 'Profile Debug', 'Profile Release'] + name: ${{ matrix.environment }} build (Dotnet ${{ matrix.dotnet }}, OS ${{ matrix.os }}) + steps: + - uses: actions/checkout@master + - name: Setup dotnet + uses: actions/setup-dotnet@v1 + with: + dotnet-version: ${{ matrix.dotnet }} + - name: Build + run: dotnet build -c "${{ matrix.environment }}" + - name: Test + run: dotnet test -c "${{ matrix.environment }}" \ No newline at end of file From 87bfe681ef65ed692aa1e46e3f5f8229013cf46a Mon Sep 17 00:00:00 2001 From: gdkchan Date: Tue, 24 Dec 2019 22:54:26 -0300 Subject: [PATCH 06/10] Fix remap ioctl map offset (#852) * Fix remap ioctl map offset * Correct offset type --- .../Nv/NvDrvServices/NvHostAsGpu/NvHostAsGpuDeviceFile.cs | 8 +++++--- .../Nv/NvDrvServices/NvHostAsGpu/Types/RemapArguments.cs | 4 ++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/NvHostAsGpuDeviceFile.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/NvHostAsGpuDeviceFile.cs index 70783b4386..b48377a47e 100644 --- a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/NvHostAsGpuDeviceFile.cs +++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/NvHostAsGpuDeviceFile.cs @@ -293,13 +293,15 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu return NvInternalResult.InvalidInput; } - long result = vmm.Map(map.Address, (long)arguments[index].Offset << 16, - (long)arguments[index].Pages << 16); + long result = vmm.Map( + ((long)arguments[index].MapOffset << 16) + map.Address, + (long)arguments[index].GpuOffset << 16, + (long)arguments[index].Pages << 16); if (result < 0) { Logger.PrintWarning(LogClass.ServiceNv, - $"Page 0x{arguments[index].Offset:x16} size 0x{arguments[index].Pages:x16} not allocated!"); + $"Page 0x{arguments[index].GpuOffset:x16} size 0x{arguments[index].Pages:x16} not allocated!"); return NvInternalResult.InvalidInput; } diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/RemapArguments.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/RemapArguments.cs index 0cf324b453..bc149d424b 100644 --- a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/RemapArguments.cs +++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/RemapArguments.cs @@ -8,8 +8,8 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu.Types public ushort Flags; public ushort Kind; public int NvMapHandle; - public int Padding; - public uint Offset; + public uint MapOffset; + public uint GpuOffset; public uint Pages; } } From 55c956e2ec83b2b7f414688c4fe4ed9f1f316935 Mon Sep 17 00:00:00 2001 From: Thog Date: Thu, 26 Dec 2019 02:50:17 +0100 Subject: [PATCH 07/10] Make HLE disposable safely (#850) * Make HLE disposable safely This fix the oldest issue with the HLE code: the kernel side disposability. Changelog: - Implement KProcess::UnpauseAndTerminateAllThreadsExcept, KThread::Terminate, KThread::TerminateCurrentProcess, KThread::PrepareForTermiation and the svc post handler accurately. - Implement svcTerminateProcess and svcExitProcess. (both untested) - Fix KHandleTable::Destroy not decrementing refcount of all objects stored in the table. - Spawn a custom KProcess with the maximum priority to terminate every guest KProcess. (terminating kernel emulation safely) - General system stability improvements to enhance the user's experience. * Fix a typo in a comment in KProcess.cs * Address gdk's comments --- Ryujinx.HLE/HOS/Horizon.cs | 35 ++++- .../HOS/Kernel/Process/KHandleTable.cs | 1 + Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs | 127 +++++++++++++++--- .../HOS/Kernel/SupervisorCall/SvcHandler.cs | 10 ++ .../HOS/Kernel/SupervisorCall/SvcSystem.cs | 43 +++++- .../HOS/Kernel/SupervisorCall/SvcTable.cs | 3 +- Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs | 105 ++++++++++++++- 7 files changed, 293 insertions(+), 31 deletions(-) diff --git a/Ryujinx.HLE/HOS/Horizon.cs b/Ryujinx.HLE/HOS/Horizon.cs index aa01bfc97d..67f427b7c0 100644 --- a/Ryujinx.HLE/HOS/Horizon.cs +++ b/Ryujinx.HLE/HOS/Horizon.cs @@ -754,21 +754,42 @@ namespace Ryujinx.HLE.HOS { if (disposing) { - // Force all threads to exit. - lock (Processes) + KProcess terminationProcess = new KProcess(this); + + KThread terminationThread = new KThread(this); + + terminationThread.Initialize(0, 0, 0, 3, 0, terminationProcess, ThreadType.Kernel, () => { - foreach (KProcess process in Processes.Values) + // Force all threads to exit. + lock (Processes) { - process.StopAllThreads(); + foreach (KProcess process in Processes.Values) + { + process.Terminate(); + + // Exit ourself now! + Scheduler.ExitThread(terminationThread); + Scheduler.GetCurrentThread().Exit(); + Scheduler.RemoveThread(terminationThread); + } } + }); + + terminationThread.Start(); + + // Signal the vsync event to avoid issues of KThread waiting on it. + if (Device.EnableDeviceVsync) + { + Device.VsyncEvent.Set(); } + // This is needed as the IPC Dummy KThread is also counted in the ThreadCounter. + ThreadCounter.Signal(); + // It's only safe to release resources once all threads // have exited. ThreadCounter.Signal(); - //ThreadCounter.Wait(); // FIXME: Uncomment this - // BODY: Right now, guest processes don't exit properly because the logic waits for them to exit. - // BODY: However, this doesn't happen when you close the main window so we need to find a way to make them exit gracefully + ThreadCounter.Wait(); Scheduler.Dispose(); diff --git a/Ryujinx.HLE/HOS/Kernel/Process/KHandleTable.cs b/Ryujinx.HLE/HOS/Kernel/Process/KHandleTable.cs index 88c2e69032..e9dd14b2e6 100644 --- a/Ryujinx.HLE/HOS/Kernel/Process/KHandleTable.cs +++ b/Ryujinx.HLE/HOS/Kernel/Process/KHandleTable.cs @@ -272,6 +272,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process disposableObj.Dispose(); } + entry.Obj.DecrementReferenceCount(); entry.Obj = null; entry.Next = _nextFreeEntry; diff --git a/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs b/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs index 1b16d79a38..c74f6fca1b 100644 --- a/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs +++ b/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs @@ -38,7 +38,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process public ulong PersonalMmHeapPagesCount { get; private set; } - private ProcessState _state; + public ProcessState State { get; private set; } private object _processLock; private object _threadingLock; @@ -383,7 +383,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process Name = creationInfo.Name; - _state = ProcessState.Created; + State = ProcessState.Created; _creationTimestamp = PerformanceCounter.ElapsedMilliseconds; @@ -579,7 +579,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process { lock (_processLock) { - if (_state > ProcessState.CreatedAttached) + if (State > ProcessState.CreatedAttached) { return KernelResult.InvalidState; } @@ -733,8 +733,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Process mainThread.SetEntryArguments(0, mainThreadHandle); - ProcessState oldState = _state; - ProcessState newState = _state != ProcessState.Created + ProcessState oldState = State; + ProcessState newState = State != ProcessState.Created ? ProcessState.Attached : ProcessState.Started; @@ -768,9 +768,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Process private void SetState(ProcessState newState) { - if (_state != newState) + if (State != newState) { - _state = newState; + State = newState; _signaled = true; Signal(); @@ -820,6 +820,20 @@ namespace Ryujinx.HLE.HOS.Kernel.Process } } + public void DecrementToZeroWhileTerminatingCurrent() + { + System.ThreadCounter.Signal(); + + while (Interlocked.Decrement(ref _threadCount) != 0) + { + Destroy(); + TerminateCurrentProcess(); + } + + // Nintendo panic here because if it reaches this point, the current thread should be already dead. + // As we handle the death of the thread in the post SVC handler and inside the CPU emulator, we don't panic here. + } + public ulong GetMemoryCapacity() { ulong totalCapacity = (ulong)ResourceLimit.GetRemainingValue(LimitableResource.Memory); @@ -909,12 +923,12 @@ namespace Ryujinx.HLE.HOS.Kernel.Process lock (_processLock) { - if (_state >= ProcessState.Started) + if (State >= ProcessState.Started) { - if (_state == ProcessState.Started || - _state == ProcessState.Crashed || - _state == ProcessState.Attached || - _state == ProcessState.DebugSuspended) + if (State == ProcessState.Started || + State == ProcessState.Crashed || + State == ProcessState.Attached || + State == ProcessState.DebugSuspended) { SetState(ProcessState.Exiting); @@ -933,23 +947,98 @@ namespace Ryujinx.HLE.HOS.Kernel.Process if (shallTerminate) { - // UnpauseAndTerminateAllThreadsExcept(System.Scheduler.GetCurrentThread()); + UnpauseAndTerminateAllThreadsExcept(System.Scheduler.GetCurrentThread()); HandleTable.Destroy(); - SignalExitForDebugEvent(); + SignalExitToDebugTerminated(); SignalExit(); } return result; } - private void UnpauseAndTerminateAllThreadsExcept(KThread thread) + public void TerminateCurrentProcess() { - // TODO. + bool shallTerminate = false; + + System.CriticalSection.Enter(); + + lock (_processLock) + { + if (State >= ProcessState.Started) + { + if (State == ProcessState.Started || + State == ProcessState.Attached || + State == ProcessState.DebugSuspended) + { + SetState(ProcessState.Exiting); + + shallTerminate = true; + } + } + } + + System.CriticalSection.Leave(); + + if (shallTerminate) + { + UnpauseAndTerminateAllThreadsExcept(System.Scheduler.GetCurrentThread()); + + HandleTable.Destroy(); + + // NOTE: this is supposed to be called in receiving of the mailbox. + SignalExitToDebugExited(); + SignalExit(); + } } - private void SignalExitForDebugEvent() + private void UnpauseAndTerminateAllThreadsExcept(KThread currentThread) + { + lock (_threadingLock) + { + System.CriticalSection.Enter(); + + foreach (KThread thread in _threads) + { + if ((thread.SchedFlags & ThreadSchedState.LowMask) != ThreadSchedState.TerminationPending) + { + thread.PrepareForTermination(); + } + } + + System.CriticalSection.Leave(); + } + + KThread blockedThread = null; + + lock (_threadingLock) + { + foreach (KThread thread in _threads) + { + if (thread != currentThread && (thread.SchedFlags & ThreadSchedState.LowMask) != ThreadSchedState.TerminationPending) + { + thread.IncrementReferenceCount(); + + blockedThread = thread; + break; + } + } + } + + if (blockedThread != null) + { + blockedThread.Terminate(); + blockedThread.DecrementReferenceCount(); + } + } + + private void SignalExitToDebugTerminated() + { + // TODO: Debug events. + } + + private void SignalExitToDebugExited() { // TODO: Debug events. } @@ -976,7 +1065,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process lock (_processLock) { - if (_state != ProcessState.Exited && _signaled) + if (State != ProcessState.Exited && _signaled) { _signaled = false; @@ -999,7 +1088,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process { foreach (KThread thread in _threads) { - thread.Context.Running = false; + System.Scheduler.ExitThread(thread); System.Scheduler.CoreManager.Set(thread.HostThread); } diff --git a/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcHandler.cs b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcHandler.cs index e3a4b375b8..0bf5e5fa3d 100644 --- a/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcHandler.cs +++ b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcHandler.cs @@ -1,5 +1,6 @@ using ARMeilleure.State; using Ryujinx.HLE.HOS.Kernel.Process; +using Ryujinx.HLE.HOS.Kernel.Threading; using System; namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall @@ -29,6 +30,15 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall ExecutionContext context = (ExecutionContext)sender; svcFunc(this, context); + + PostSvcHandler(); + } + + private void PostSvcHandler() + { + KThread currentThread = _system.Scheduler.GetCurrentThread(); + + currentThread.HandlePostSyscall(); } } } \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcSystem.cs b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcSystem.cs index 6525628f26..7961f124d0 100644 --- a/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcSystem.cs +++ b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcSystem.cs @@ -17,9 +17,41 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall ExitProcess(); } + public KernelResult TerminateProcess64(int handle) + { + return TerminateProcess(handle); + } + + private KernelResult TerminateProcess(int handle) + { + KProcess process = _process.HandleTable.GetObject(handle); + + KernelResult result; + + if (process != null) + { + if (process == _system.Scheduler.GetCurrentProcess()) + { + result = KernelResult.Success; + process.DecrementToZeroWhileTerminatingCurrent(); + } + else + { + result = process.Terminate(); + process.DecrementReferenceCount(); + } + } + else + { + result = KernelResult.InvalidHandle; + } + + return result; + } + private void ExitProcess() { - _system.Scheduler.GetCurrentProcess().Terminate(); + _system.Scheduler.GetCurrentProcess().TerminateCurrentProcess(); } public KernelResult SignalEvent64(int handle) @@ -184,6 +216,15 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall { currentThread.PrintGuestStackTrace(); + // As the process is exiting, this is probably caused by emulation termination. + if (currentThread.Owner.State == ProcessState.Exiting) + { + return; + } + + // TODO: Debug events. + currentThread.Owner.TerminateCurrentProcess(); + throw new GuestBrokeExecutionException(); } else diff --git a/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcTable.cs b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcTable.cs index 9ec0931ae6..1c2121f0de 100644 --- a/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcTable.cs +++ b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcTable.cs @@ -74,7 +74,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall { 0x72, nameof(SvcHandler.ConnectToPort64) }, { 0x73, nameof(SvcHandler.SetProcessMemoryPermission64) }, { 0x77, nameof(SvcHandler.MapProcessCodeMemory64) }, - { 0x78, nameof(SvcHandler.UnmapProcessCodeMemory64) } + { 0x78, nameof(SvcHandler.UnmapProcessCodeMemory64) }, + { 0x7B, nameof(SvcHandler.TerminateProcess64) } }; _svcTable64 = new Action[0x80]; diff --git a/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs b/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs index 0232bc1684..e1a49a561a 100644 --- a/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs +++ b/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs @@ -70,7 +70,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading public ThreadSchedState SchedFlags { get; private set; } - public bool ShallBeTerminated { get; private set; } + private int _shallBeTerminated; + + public bool ShallBeTerminated { get => _shallBeTerminated != 0; set => _shallBeTerminated = value ? 1 : 0; } public bool SyncCancelled { get; set; } public bool WaitingSync { get; set; } @@ -104,7 +106,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading int priority, int defaultCpuCore, KProcess owner, - ThreadType type = ThreadType.User) + ThreadType type = ThreadType.User, + ThreadStart customHostThreadStart = null) { if ((uint)type > 3) { @@ -156,7 +159,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading is64Bits = true; } - HostThread = new Thread(() => ThreadStart(entrypoint)); + HostThread = new Thread(customHostThreadStart == null ? () => ThreadStart(entrypoint) : customHostThreadStart); Context = new ARMeilleure.State.ExecutionContext(); @@ -182,6 +185,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading ThreadUid = System.GetThreadUid(); + HostThread.Name = $"Host Thread (thread id {ThreadUid})"; + _hasBeenInitialized = true; if (owner != null) @@ -300,6 +305,100 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading DecrementReferenceCount(); } + public ThreadSchedState PrepareForTermination() + { + System.CriticalSection.Enter(); + + ThreadSchedState result; + + if (Interlocked.CompareExchange(ref _shallBeTerminated, 1, 0) == 0) + { + if ((SchedFlags & ThreadSchedState.LowMask) == ThreadSchedState.None) + { + SchedFlags = ThreadSchedState.TerminationPending; + } + else + { + if (_forcePauseFlags != ThreadSchedState.None) + { + _forcePauseFlags &= ~ThreadSchedState.ThreadPauseFlag; + + ThreadSchedState oldSchedFlags = SchedFlags; + + SchedFlags &= ThreadSchedState.LowMask; + + AdjustScheduling(oldSchedFlags); + } + + if (BasePriority >= 0x10) + { + SetPriority(0xF); + } + + if ((SchedFlags & ThreadSchedState.LowMask) == ThreadSchedState.Running) + { + // TODO: GIC distributor stuffs (sgir changes ect) + } + + SignaledObj = null; + ObjSyncResult = KernelResult.ThreadTerminating; + + ReleaseAndResume(); + } + } + + result = SchedFlags; + + System.CriticalSection.Leave(); + + return result & ThreadSchedState.LowMask; + } + + public void Terminate() + { + ThreadSchedState state = PrepareForTermination(); + + if (state != ThreadSchedState.TerminationPending) + { + System.Synchronization.WaitFor(new KSynchronizationObject[] { this }, -1, out _); + } + } + + public void HandlePostSyscall() + { + ThreadSchedState state; + + do + { + if (ShallBeTerminated || SchedFlags == ThreadSchedState.TerminationPending) + { + System.Scheduler.ExitThread(this); + Exit(); + + // As the death of the thread is handled by the CPU emulator, we differ from the official kernel and return here. + break; + } + + System.CriticalSection.Enter(); + + if (ShallBeTerminated || SchedFlags == ThreadSchedState.TerminationPending) + { + state = ThreadSchedState.TerminationPending; + } + else + { + if (_forcePauseFlags != ThreadSchedState.None) + { + CombineForcePauseFlags(); + } + + state = ThreadSchedState.Running; + } + + System.CriticalSection.Leave(); + } while (state == ThreadSchedState.TerminationPending); + } + private void ExitImpl() { System.CriticalSection.Enter(); From 1db3a66da376c073371a78042e4baecf467ed108 Mon Sep 17 00:00:00 2001 From: Thog Date: Thu, 26 Dec 2019 02:58:38 +0100 Subject: [PATCH 08/10] Fix LibHac.IStorage not being disposed in FileSystem proxy (#851) --- .../HOS/Services/Fs/FileSystemProxy/IStorage.cs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IStorage.cs b/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IStorage.cs index cc407039bc..b2db08dd38 100644 --- a/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IStorage.cs +++ b/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IStorage.cs @@ -1,9 +1,10 @@ using LibHac; using Ryujinx.HLE.HOS.Ipc; +using System; namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy { - class IStorage : IpcService + class IStorage : IpcService, IDisposable { private LibHac.Fs.IStorage _baseStorage; @@ -51,5 +52,18 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy return (ResultCode)result.Value; } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _baseStorage?.Dispose(); + } + } } } \ No newline at end of file From ad84f3a7b3b409ceab920f480dadcfe6eda62c92 Mon Sep 17 00:00:00 2001 From: Thog Date: Sun, 29 Dec 2019 23:37:54 +0100 Subject: [PATCH 09/10] Fix GUI freeze wehn closing without any emulation running (#853) --- Ryujinx.HLE/HOS/Horizon.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Ryujinx.HLE/HOS/Horizon.cs b/Ryujinx.HLE/HOS/Horizon.cs index 67f427b7c0..2e5b7a7043 100644 --- a/Ryujinx.HLE/HOS/Horizon.cs +++ b/Ryujinx.HLE/HOS/Horizon.cs @@ -766,13 +766,13 @@ namespace Ryujinx.HLE.HOS foreach (KProcess process in Processes.Values) { process.Terminate(); - - // Exit ourself now! - Scheduler.ExitThread(terminationThread); - Scheduler.GetCurrentThread().Exit(); - Scheduler.RemoveThread(terminationThread); } } + + // Exit ourself now! + Scheduler.ExitThread(terminationThread); + Scheduler.GetCurrentThread().Exit(); + Scheduler.RemoveThread(terminationThread); }); terminationThread.Start(); From 0915731a9dfc4e2b9263d4b30c2876446ff2d9b3 Mon Sep 17 00:00:00 2001 From: LDj3SNuD <35856442+LDj3SNuD@users.noreply.github.com> Date: Mon, 30 Dec 2019 02:22:47 +0100 Subject: [PATCH 10/10] Implemented fast paths for: (#846) * opt * Nit. * opt_p2 * Nit. --- ARMeilleure/CodeGen/X86/Assembler.cs | 11 + ARMeilleure/CodeGen/X86/CodeGenerator.cs | 28 +- ARMeilleure/CodeGen/X86/IntrinsicTable.cs | 3 + ARMeilleure/CodeGen/X86/IntrinsicType.cs | 1 + ARMeilleure/CodeGen/X86/X86Instruction.cs | 1 + ARMeilleure/Instructions/InstEmitAlu.cs | 81 +- .../Instructions/InstEmitSimdArithmetic.cs | 87 +- ARMeilleure/Instructions/InstEmitSimdCvt.cs | 751 +++++++++++++----- .../Instructions/InstEmitSimdHelper.cs | 79 +- .../Instructions/InstEmitSimdLogical.cs | 25 +- ARMeilleure/Instructions/InstEmitSimdMove.cs | 24 +- ARMeilleure/Instructions/InstEmitSimdShift.cs | 62 +- ARMeilleure/Instructions/SoftFallback.cs | 69 -- .../IntermediateRepresentation/Intrinsic.cs | 3 + Ryujinx.Tests/Cpu/CpuTestSimd.cs | 4 +- Ryujinx.Tests/Cpu/CpuTestSimdCvt.cs | 4 +- Ryujinx.Tests/Cpu/CpuTestSimdShImm.cs | 2 +- 17 files changed, 856 insertions(+), 379 deletions(-) diff --git a/ARMeilleure/CodeGen/X86/Assembler.cs b/ARMeilleure/CodeGen/X86/Assembler.cs index 24a122c33b..4568253ade 100644 --- a/ARMeilleure/CodeGen/X86/Assembler.cs +++ b/ARMeilleure/CodeGen/X86/Assembler.cs @@ -103,6 +103,7 @@ namespace ARMeilleure.CodeGen.X86 Add(X86Instruction.Cvtsi2sd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f2a, InstructionFlags.Vex | InstructionFlags.PrefixF2)); Add(X86Instruction.Cvtsi2ss, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f2a, InstructionFlags.Vex | InstructionFlags.PrefixF3)); Add(X86Instruction.Cvtss2sd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f5a, InstructionFlags.Vex | InstructionFlags.PrefixF3)); + Add(X86Instruction.Cvtss2si, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f2d, InstructionFlags.Vex | InstructionFlags.PrefixF3)); Add(X86Instruction.Div, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x060000f7, InstructionFlags.None)); Add(X86Instruction.Divpd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f5e, InstructionFlags.Vex | InstructionFlags.Prefix66)); Add(X86Instruction.Divps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f5e, InstructionFlags.Vex)); @@ -791,6 +792,16 @@ namespace ARMeilleure.CodeGen.X86 } } + public void WriteInstruction( + X86Instruction inst, + Operand dest, + Operand src1, + Operand src2, + OperandType type) + { + WriteInstruction(dest, src1, src2, inst, type); + } + public void WriteInstruction(X86Instruction inst, Operand dest, Operand source, byte imm) { WriteInstruction(dest, null, source, inst); diff --git a/ARMeilleure/CodeGen/X86/CodeGenerator.cs b/ARMeilleure/CodeGen/X86/CodeGenerator.cs index d1224363c0..0268665cb0 100644 --- a/ARMeilleure/CodeGen/X86/CodeGenerator.cs +++ b/ARMeilleure/CodeGen/X86/CodeGenerator.cs @@ -269,11 +269,11 @@ namespace ARMeilleure.CodeGen.X86 { if (dest.Type == OperandType.I32) { - context.Assembler.Movd(dest, source); // int _mm_cvtsi128_si32 + context.Assembler.Movd(dest, source); // int _mm_cvtsi128_si32(__m128i a) } else /* if (dest.Type == OperandType.I64) */ { - context.Assembler.Movq(dest, source); // __int64 _mm_cvtsi128_si64 + context.Assembler.Movq(dest, source); // __int64 _mm_cvtsi128_si64(__m128i a) } } else @@ -305,6 +305,26 @@ namespace ARMeilleure.CodeGen.X86 break; } + case IntrinsicType.BinaryGpr: + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + EnsureSameType(dest, src1); + + if (!HardwareCapabilities.SupportsVexEncoding) + { + EnsureSameReg(dest, src1); + } + + Debug.Assert(!dest.Type.IsInteger() && src2.Type.IsInteger()); + + context.Assembler.WriteInstruction(info.Inst, dest, src1, src2, src2.Type); + + break; + } + case IntrinsicType.BinaryImm: { Operand dest = operation.Destination; @@ -1070,11 +1090,11 @@ namespace ARMeilleure.CodeGen.X86 if (source.Type == OperandType.I32) { - context.Assembler.Movd(dest, source); + context.Assembler.Movd(dest, source); // (__m128i _mm_cvtsi32_si128(int a)) } else /* if (source.Type == OperandType.I64) */ { - context.Assembler.Movq(dest, source); + context.Assembler.Movq(dest, source); // (__m128i _mm_cvtsi64_si128(__int64 a)) } } diff --git a/ARMeilleure/CodeGen/X86/IntrinsicTable.cs b/ARMeilleure/CodeGen/X86/IntrinsicTable.cs index e87de03509..fd3b691d4f 100644 --- a/ARMeilleure/CodeGen/X86/IntrinsicTable.cs +++ b/ARMeilleure/CodeGen/X86/IntrinsicTable.cs @@ -41,8 +41,11 @@ namespace ARMeilleure.CodeGen.X86 Add(Intrinsic.X86Cvtps2pd, new IntrinsicInfo(X86Instruction.Cvtps2pd, IntrinsicType.Unary)); Add(Intrinsic.X86Cvtsd2si, new IntrinsicInfo(X86Instruction.Cvtsd2si, IntrinsicType.UnaryToGpr)); Add(Intrinsic.X86Cvtsd2ss, new IntrinsicInfo(X86Instruction.Cvtsd2ss, IntrinsicType.Binary)); + Add(Intrinsic.X86Cvtsi2sd, new IntrinsicInfo(X86Instruction.Cvtsi2sd, IntrinsicType.BinaryGpr)); Add(Intrinsic.X86Cvtsi2si, new IntrinsicInfo(X86Instruction.Movd, IntrinsicType.UnaryToGpr)); + Add(Intrinsic.X86Cvtsi2ss, new IntrinsicInfo(X86Instruction.Cvtsi2ss, IntrinsicType.BinaryGpr)); Add(Intrinsic.X86Cvtss2sd, new IntrinsicInfo(X86Instruction.Cvtss2sd, IntrinsicType.Binary)); + Add(Intrinsic.X86Cvtss2si, new IntrinsicInfo(X86Instruction.Cvtss2si, IntrinsicType.UnaryToGpr)); Add(Intrinsic.X86Divpd, new IntrinsicInfo(X86Instruction.Divpd, IntrinsicType.Binary)); Add(Intrinsic.X86Divps, new IntrinsicInfo(X86Instruction.Divps, IntrinsicType.Binary)); Add(Intrinsic.X86Divsd, new IntrinsicInfo(X86Instruction.Divsd, IntrinsicType.Binary)); diff --git a/ARMeilleure/CodeGen/X86/IntrinsicType.cs b/ARMeilleure/CodeGen/X86/IntrinsicType.cs index 4e9b33e1ed..41c52b59db 100644 --- a/ARMeilleure/CodeGen/X86/IntrinsicType.cs +++ b/ARMeilleure/CodeGen/X86/IntrinsicType.cs @@ -7,6 +7,7 @@ namespace ARMeilleure.CodeGen.X86 Unary, UnaryToGpr, Binary, + BinaryGpr, BinaryImm, Ternary, TernaryImm diff --git a/ARMeilleure/CodeGen/X86/X86Instruction.cs b/ARMeilleure/CodeGen/X86/X86Instruction.cs index a29e68fb6f..813730f2a3 100644 --- a/ARMeilleure/CodeGen/X86/X86Instruction.cs +++ b/ARMeilleure/CodeGen/X86/X86Instruction.cs @@ -38,6 +38,7 @@ namespace ARMeilleure.CodeGen.X86 Cvtsi2sd, Cvtsi2ss, Cvtss2sd, + Cvtss2si, Div, Divpd, Divps, diff --git a/ARMeilleure/Instructions/InstEmitAlu.cs b/ARMeilleure/Instructions/InstEmitAlu.cs index 947c9f70bf..ed1faae417 100644 --- a/ARMeilleure/Instructions/InstEmitAlu.cs +++ b/ARMeilleure/Instructions/InstEmitAlu.cs @@ -2,6 +2,7 @@ using ARMeilleure.Decoders; using ARMeilleure.IntermediateRepresentation; using ARMeilleure.State; using ARMeilleure.Translation; +using System.Diagnostics; using static ARMeilleure.Instructions.InstEmitAluHelper; using static ARMeilleure.Instructions.InstEmitHelper; @@ -265,16 +266,52 @@ namespace ARMeilleure.Instructions if (op.RegisterSize == RegisterSize.Int32) { - d = context.Call(new _U32_U32(SoftFallback.ReverseBits32), n); + d = EmitReverseBits32Op(context, n); } else { - d = context.Call(new _U64_U64(SoftFallback.ReverseBits64), n); + d = EmitReverseBits64Op(context, n); } SetAluDOrZR(context, d); } + private static Operand EmitReverseBits32Op(ArmEmitterContext context, Operand op) + { + Debug.Assert(op.Type == OperandType.I32); + + Operand val = context.BitwiseOr(context.ShiftRightUI(context.BitwiseAnd(op, Const(0xaaaaaaaau)), Const(1)), + context.ShiftLeft (context.BitwiseAnd(op, Const(0x55555555u)), Const(1))); + + val = context.BitwiseOr(context.ShiftRightUI(context.BitwiseAnd(val, Const(0xccccccccu)), Const(2)), + context.ShiftLeft (context.BitwiseAnd(val, Const(0x33333333u)), Const(2))); + val = context.BitwiseOr(context.ShiftRightUI(context.BitwiseAnd(val, Const(0xf0f0f0f0u)), Const(4)), + context.ShiftLeft (context.BitwiseAnd(val, Const(0x0f0f0f0fu)), Const(4))); + val = context.BitwiseOr(context.ShiftRightUI(context.BitwiseAnd(val, Const(0xff00ff00u)), Const(8)), + context.ShiftLeft (context.BitwiseAnd(val, Const(0x00ff00ffu)), Const(8))); + + return context.BitwiseOr(context.ShiftRightUI(val, Const(16)), context.ShiftLeft(val, Const(16))); + } + + private static Operand EmitReverseBits64Op(ArmEmitterContext context, Operand op) + { + Debug.Assert(op.Type == OperandType.I64); + + Operand val = context.BitwiseOr(context.ShiftRightUI(context.BitwiseAnd(op, Const(0xaaaaaaaaaaaaaaaaul)), Const(1)), + context.ShiftLeft (context.BitwiseAnd(op, Const(0x5555555555555555ul)), Const(1))); + + val = context.BitwiseOr(context.ShiftRightUI(context.BitwiseAnd(val, Const(0xccccccccccccccccul)), Const(2)), + context.ShiftLeft (context.BitwiseAnd(val, Const(0x3333333333333333ul)), Const(2))); + val = context.BitwiseOr(context.ShiftRightUI(context.BitwiseAnd(val, Const(0xf0f0f0f0f0f0f0f0ul)), Const(4)), + context.ShiftLeft (context.BitwiseAnd(val, Const(0x0f0f0f0f0f0f0f0ful)), Const(4))); + val = context.BitwiseOr(context.ShiftRightUI(context.BitwiseAnd(val, Const(0xff00ff00ff00ff00ul)), Const(8)), + context.ShiftLeft (context.BitwiseAnd(val, Const(0x00ff00ff00ff00fful)), Const(8))); + val = context.BitwiseOr(context.ShiftRightUI(context.BitwiseAnd(val, Const(0xffff0000ffff0000ul)), Const(16)), + context.ShiftLeft (context.BitwiseAnd(val, Const(0x0000ffff0000fffful)), Const(16))); + + return context.BitwiseOr(context.ShiftRightUI(val, Const(32)), context.ShiftLeft(val, Const(32))); + } + public static void Rev16(ArmEmitterContext context) { OpCodeAlu op = (OpCodeAlu)context.CurrOp; @@ -284,32 +321,60 @@ namespace ARMeilleure.Instructions if (op.RegisterSize == RegisterSize.Int32) { - d = context.Call(new _U32_U32(SoftFallback.ReverseBytes16_32), n); + d = EmitReverseBytes16_32Op(context, n); } else { - d = context.Call(new _U64_U64(SoftFallback.ReverseBytes16_64), n); + d = EmitReverseBytes16_64Op(context, n); } SetAluDOrZR(context, d); } + private static Operand EmitReverseBytes16_32Op(ArmEmitterContext context, Operand op) + { + Debug.Assert(op.Type == OperandType.I32); + + Operand val = EmitReverseBytes16_64Op(context, context.ZeroExtend32(OperandType.I64, op)); + + return context.ConvertI64ToI32(val); + } + + private static Operand EmitReverseBytes16_64Op(ArmEmitterContext context, Operand op) + { + Debug.Assert(op.Type == OperandType.I64); + + return context.BitwiseOr(context.ShiftRightUI(context.BitwiseAnd(op, Const(0xff00ff00ff00ff00ul)), Const(8)), + context.ShiftLeft (context.BitwiseAnd(op, Const(0x00ff00ff00ff00fful)), Const(8))); + } + public static void Rev32(ArmEmitterContext context) { OpCodeAlu op = (OpCodeAlu)context.CurrOp; Operand n = GetIntOrZR(context, op.Rn); + Operand d; if (op.RegisterSize == RegisterSize.Int32) { - SetAluDOrZR(context, context.ByteSwap(n)); + d = context.ByteSwap(n); } else { - Operand d = context.Call(new _U64_U64(SoftFallback.ReverseBytes32_64), n); - - SetAluDOrZR(context, d); + d = EmitReverseBytes32_64Op(context, n); } + + SetAluDOrZR(context, d); + } + + private static Operand EmitReverseBytes32_64Op(ArmEmitterContext context, Operand op) + { + Debug.Assert(op.Type == OperandType.I64); + + Operand val = EmitReverseBytes16_64Op(context, op); + + return context.BitwiseOr(context.ShiftRightUI(context.BitwiseAnd(val, Const(0xffff0000ffff0000ul)), Const(16)), + context.ShiftLeft (context.BitwiseAnd(val, Const(0x0000ffff0000fffful)), Const(16))); } public static void Rev64(ArmEmitterContext context) diff --git a/ARMeilleure/Instructions/InstEmitSimdArithmetic.cs b/ARMeilleure/Instructions/InstEmitSimdArithmetic.cs index 1a9e01c8da..4603ae0b17 100644 --- a/ARMeilleure/Instructions/InstEmitSimdArithmetic.cs +++ b/ARMeilleure/Instructions/InstEmitSimdArithmetic.cs @@ -77,7 +77,14 @@ namespace ARMeilleure.Instructions public static void Addp_V(ArmEmitterContext context) { - EmitVectorPairwiseOpZx(context, (op1, op2) => context.Add(op1, op2)); + if (Optimizations.UseSsse3) + { + EmitSsse3VectorPairwiseOp(context, X86PaddInstruction); + } + else + { + EmitVectorPairwiseOpZx(context, (op1, op2) => context.Add(op1, op2)); + } } public static void Addv_V(ArmEmitterContext context) @@ -399,7 +406,7 @@ namespace ARMeilleure.Instructions { if (Optimizations.FastFP && Optimizations.UseSse2) { - EmitVectorPairwiseOpF(context, Intrinsic.X86Addps, Intrinsic.X86Addpd); + EmitSse2VectorPairwiseOpF(context, Intrinsic.X86Addps, Intrinsic.X86Addpd); } else { @@ -547,7 +554,7 @@ namespace ARMeilleure.Instructions { if (Optimizations.FastFP && Optimizations.UseSse2) { - EmitVectorPairwiseOpF(context, Intrinsic.X86Maxps, Intrinsic.X86Maxpd); + EmitSse2VectorPairwiseOpF(context, Intrinsic.X86Maxps, Intrinsic.X86Maxpd); } else { @@ -622,7 +629,7 @@ namespace ARMeilleure.Instructions { if (Optimizations.FastFP && Optimizations.UseSse2) { - EmitVectorPairwiseOpF(context, Intrinsic.X86Minps, Intrinsic.X86Minpd); + EmitSse2VectorPairwiseOpF(context, Intrinsic.X86Minps, Intrinsic.X86Minpd); } else { @@ -664,7 +671,7 @@ namespace ARMeilleure.Instructions res = context.VectorZeroUpper64(res); } - context.Copy(GetVec(op.Rd), res); + context.Copy(d, res); } else /* if (sizeF == 1) */ { @@ -672,7 +679,7 @@ namespace ARMeilleure.Instructions res = context.AddIntrinsic(Intrinsic.X86Addpd, d, res); - context.Copy(GetVec(op.Rd), res); + context.Copy(d, res); } } else @@ -710,7 +717,7 @@ namespace ARMeilleure.Instructions res = context.VectorZeroUpper64(res); } - context.Copy(GetVec(op.Rd), res); + context.Copy(d, res); } else /* if (sizeF == 1) */ { @@ -721,7 +728,7 @@ namespace ARMeilleure.Instructions res = context.AddIntrinsic(Intrinsic.X86Mulpd, n, res); res = context.AddIntrinsic(Intrinsic.X86Addpd, d, res); - context.Copy(GetVec(op.Rd), res); + context.Copy(d, res); } } else @@ -764,7 +771,7 @@ namespace ARMeilleure.Instructions res = context.VectorZeroUpper64(res); } - context.Copy(GetVec(op.Rd), res); + context.Copy(d, res); } else /* if (sizeF == 1) */ { @@ -772,7 +779,7 @@ namespace ARMeilleure.Instructions res = context.AddIntrinsic(Intrinsic.X86Subpd, d, res); - context.Copy(GetVec(op.Rd), res); + context.Copy(d, res); } } else @@ -810,7 +817,7 @@ namespace ARMeilleure.Instructions res = context.VectorZeroUpper64(res); } - context.Copy(GetVec(op.Rd), res); + context.Copy(d, res); } else /* if (sizeF == 1) */ { @@ -821,7 +828,7 @@ namespace ARMeilleure.Instructions res = context.AddIntrinsic(Intrinsic.X86Mulpd, n, res); res = context.AddIntrinsic(Intrinsic.X86Subpd, d, res); - context.Copy(GetVec(op.Rd), res); + context.Copy(d, res); } } else @@ -2028,9 +2035,16 @@ namespace ARMeilleure.Instructions public static void Smaxp_V(ArmEmitterContext context) { - Delegate dlg = new _S64_S64_S64(Math.Max); + if (Optimizations.UseSsse3) + { + EmitSsse3VectorPairwiseOp(context, X86PmaxsInstruction); + } + else + { + Delegate dlg = new _S64_S64_S64(Math.Max); - EmitVectorPairwiseOpSx(context, (op1, op2) => context.Call(dlg, op1, op2)); + EmitVectorPairwiseOpSx(context, (op1, op2) => context.Call(dlg, op1, op2)); + } } public static void Smaxv_V(ArmEmitterContext context) @@ -2070,9 +2084,16 @@ namespace ARMeilleure.Instructions public static void Sminp_V(ArmEmitterContext context) { - Delegate dlg = new _S64_S64_S64(Math.Min); + if (Optimizations.UseSsse3) + { + EmitSsse3VectorPairwiseOp(context, X86PminsInstruction); + } + else + { + Delegate dlg = new _S64_S64_S64(Math.Min); - EmitVectorPairwiseOpSx(context, (op1, op2) => context.Call(dlg, op1, op2)); + EmitVectorPairwiseOpSx(context, (op1, op2) => context.Call(dlg, op1, op2)); + } } public static void Sminv_V(ArmEmitterContext context) @@ -2653,9 +2674,16 @@ namespace ARMeilleure.Instructions public static void Umaxp_V(ArmEmitterContext context) { - Delegate dlg = new _U64_U64_U64(Math.Max); + if (Optimizations.UseSsse3) + { + EmitSsse3VectorPairwiseOp(context, X86PmaxuInstruction); + } + else + { + Delegate dlg = new _U64_U64_U64(Math.Max); - EmitVectorPairwiseOpZx(context, (op1, op2) => context.Call(dlg, op1, op2)); + EmitVectorPairwiseOpZx(context, (op1, op2) => context.Call(dlg, op1, op2)); + } } public static void Umaxv_V(ArmEmitterContext context) @@ -2695,9 +2723,16 @@ namespace ARMeilleure.Instructions public static void Uminp_V(ArmEmitterContext context) { - Delegate dlg = new _U64_U64_U64(Math.Min); + if (Optimizations.UseSsse3) + { + EmitSsse3VectorPairwiseOp(context, X86PminuInstruction); + } + else + { + Delegate dlg = new _U64_U64_U64(Math.Min); - EmitVectorPairwiseOpZx(context, (op1, op2) => context.Call(dlg, op1, op2)); + EmitVectorPairwiseOpZx(context, (op1, op2) => context.Call(dlg, op1, op2)); + } } public static void Uminv_V(ArmEmitterContext context) @@ -3020,7 +3055,9 @@ namespace ARMeilleure.Instructions int part = op.RegisterSize == RegisterSize.Simd128 ? elems : 0; - Operand res = part == 0 ? context.VectorZero() : context.Copy(GetVec(op.Rd)); + Operand d = GetVec(op.Rd); + + Operand res = part == 0 ? context.VectorZero() : context.Copy(d); long roundConst = 1L << (eSize - 1); @@ -3041,7 +3078,7 @@ namespace ARMeilleure.Instructions res = EmitVectorInsert(context, res, de, part + index, op.Size); } - context.Copy(GetVec(op.Rd), res); + context.Copy(d, res); } public static void EmitScalarRoundOpF(ArmEmitterContext context, FPRoundingMode roundMode) @@ -3124,12 +3161,12 @@ namespace ARMeilleure.Instructions Operand n = GetVec(op.Rn); Operand m = GetVec(op.Rm); - Operand nQNaNMask = EmitSse2VectorIsQNaNOpF(context, n); - Operand mQNaNMask = EmitSse2VectorIsQNaNOpF(context, m); - Operand nNum = context.Copy(n); Operand mNum = context.Copy(m); + Operand nQNaNMask = EmitSse2VectorIsQNaNOpF(context, nNum); + Operand mQNaNMask = EmitSse2VectorIsQNaNOpF(context, mNum); + int sizeF = op.Size & 1; if (sizeF == 0) diff --git a/ARMeilleure/Instructions/InstEmitSimdCvt.cs b/ARMeilleure/Instructions/InstEmitSimdCvt.cs index e2b6dbd74c..30c1bd2089 100644 --- a/ARMeilleure/Instructions/InstEmitSimdCvt.cs +++ b/ARMeilleure/Instructions/InstEmitSimdCvt.cs @@ -163,12 +163,26 @@ namespace ARMeilleure.Instructions public static void Fcvtms_Gp(ArmEmitterContext context) { - EmitFcvt_s_Gp(context, (op1) => EmitUnaryMathCall(context, MathF.Floor, Math.Floor, op1)); + if (Optimizations.UseSse41) + { + EmitSse41Fcvts_Gp(context, FPRoundingMode.TowardsMinusInfinity, isFixed: false); + } + else + { + EmitFcvt_s_Gp(context, (op1) => EmitUnaryMathCall(context, MathF.Floor, Math.Floor, op1)); + } } public static void Fcvtmu_Gp(ArmEmitterContext context) { - EmitFcvt_u_Gp(context, (op1) => EmitUnaryMathCall(context, MathF.Floor, Math.Floor, op1)); + if (Optimizations.UseSse41) + { + EmitSse41Fcvtu_Gp(context, FPRoundingMode.TowardsMinusInfinity, isFixed: false); + } + else + { + EmitFcvt_u_Gp(context, (op1) => EmitUnaryMathCall(context, MathF.Floor, Math.Floor, op1)); + } } public static void Fcvtn_V(ArmEmitterContext context) @@ -180,11 +194,10 @@ namespace ARMeilleure.Instructions if (Optimizations.UseSse2 && sizeF == 1) { Operand d = GetVec(op.Rd); - Operand n = GetVec(op.Rn); - Operand res = context.AddIntrinsic(Intrinsic.X86Movlhps, d, context.VectorZero()); + Operand res = context.VectorZeroUpper64(d); - Operand nInt = context.AddIntrinsic(Intrinsic.X86Cvtpd2ps, n); + Operand nInt = context.AddIntrinsic(Intrinsic.X86Cvtpd2ps, GetVec(op.Rn)); nInt = context.AddIntrinsic(Intrinsic.X86Movlhps, nInt, nInt); @@ -194,7 +207,7 @@ namespace ARMeilleure.Instructions res = context.AddIntrinsic(movInst, res, nInt); - context.Copy(GetVec(op.Rd), res); + context.Copy(d, res); } else { @@ -204,7 +217,9 @@ namespace ARMeilleure.Instructions int part = op.RegisterSize == RegisterSize.Simd128 ? elems : 0; - Operand res = part == 0 ? context.VectorZero() : context.Copy(GetVec(op.Rd)); + Operand d = GetVec(op.Rd); + + Operand res = part == 0 ? context.VectorZero() : context.Copy(d); for (int index = 0; index < elems; index++) { @@ -228,7 +243,7 @@ namespace ARMeilleure.Instructions } } - context.Copy(GetVec(op.Rd), res); + context.Copy(d, res); } } @@ -282,22 +297,50 @@ namespace ARMeilleure.Instructions public static void Fcvtps_Gp(ArmEmitterContext context) { - EmitFcvt_s_Gp(context, (op1) => EmitUnaryMathCall(context, MathF.Ceiling, Math.Ceiling, op1)); + if (Optimizations.UseSse41) + { + EmitSse41Fcvts_Gp(context, FPRoundingMode.TowardsPlusInfinity, isFixed: false); + } + else + { + EmitFcvt_s_Gp(context, (op1) => EmitUnaryMathCall(context, MathF.Ceiling, Math.Ceiling, op1)); + } } public static void Fcvtpu_Gp(ArmEmitterContext context) { - EmitFcvt_u_Gp(context, (op1) => EmitUnaryMathCall(context, MathF.Ceiling, Math.Ceiling, op1)); + if (Optimizations.UseSse41) + { + EmitSse41Fcvtu_Gp(context, FPRoundingMode.TowardsPlusInfinity, isFixed: false); + } + else + { + EmitFcvt_u_Gp(context, (op1) => EmitUnaryMathCall(context, MathF.Ceiling, Math.Ceiling, op1)); + } } public static void Fcvtzs_Gp(ArmEmitterContext context) { - EmitFcvt_s_Gp(context, (op1) => op1); + if (Optimizations.UseSse41) + { + EmitSse41Fcvts_Gp(context, FPRoundingMode.TowardsZero, isFixed: false); + } + else + { + EmitFcvt_s_Gp(context, (op1) => op1); + } } public static void Fcvtzs_Gp_Fixed(ArmEmitterContext context) { - EmitFcvtzs_Gp_Fixed(context); + if (Optimizations.UseSse41) + { + EmitSse41Fcvts_Gp(context, FPRoundingMode.TowardsZero, isFixed: true); + } + else + { + EmitFcvtzs_Gp_Fixed(context); + } } public static void Fcvtzs_S(ArmEmitterContext context) @@ -338,12 +381,26 @@ namespace ARMeilleure.Instructions public static void Fcvtzu_Gp(ArmEmitterContext context) { - EmitFcvt_u_Gp(context, (op1) => op1); + if (Optimizations.UseSse41) + { + EmitSse41Fcvtu_Gp(context, FPRoundingMode.TowardsZero, isFixed: false); + } + else + { + EmitFcvt_u_Gp(context, (op1) => op1); + } } public static void Fcvtzu_Gp_Fixed(ArmEmitterContext context) { - EmitFcvtzu_Gp_Fixed(context); + if (Optimizations.UseSse41) + { + EmitSse41Fcvtu_Gp(context, FPRoundingMode.TowardsZero, isFixed: true); + } + else + { + EmitFcvtzu_Gp_Fixed(context); + } } public static void Fcvtzu_S(ArmEmitterContext context) @@ -418,16 +475,16 @@ namespace ARMeilleure.Instructions public static void Scvtf_S(ArmEmitterContext context) { - OpCodeSimd op = (OpCodeSimd)context.CurrOp; - - int sizeF = op.Size & 1; - - if (Optimizations.UseSse2 && sizeF == 0) + if (Optimizations.UseSse2) { EmitSse2Scvtf(context, scalar: true); } else { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + int sizeF = op.Size & 1; + Operand res = EmitVectorLongExtract(context, op.Rn, 0, sizeF + 2); res = EmitFPConvert(context, res, op.Size, signed: true); @@ -438,11 +495,7 @@ namespace ARMeilleure.Instructions public static void Scvtf_V(ArmEmitterContext context) { - OpCodeSimd op = (OpCodeSimd)context.CurrOp; - - int sizeF = op.Size & 1; - - if (Optimizations.UseSse2 && sizeF == 0) + if (Optimizations.UseSse2) { EmitSse2Scvtf(context, scalar: false); } @@ -454,12 +507,7 @@ namespace ARMeilleure.Instructions public static void Scvtf_V_Fixed(ArmEmitterContext context) { - OpCodeSimd op = (OpCodeSimd)context.CurrOp; - - // sizeF == ((OpCodeSimdShImm64)op).Size - 2 - int sizeF = op.Size & 1; - - if (Optimizations.UseSse2 && sizeF == 0) + if (Optimizations.UseSse2) { EmitSse2Scvtf(context, scalar: false); } @@ -495,16 +543,16 @@ namespace ARMeilleure.Instructions public static void Ucvtf_S(ArmEmitterContext context) { - OpCodeSimd op = (OpCodeSimd)context.CurrOp; - - int sizeF = op.Size & 1; - - if (Optimizations.UseSse2 && sizeF == 0) + if (Optimizations.UseSse2) { EmitSse2Ucvtf(context, scalar: true); } else { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + int sizeF = op.Size & 1; + Operand ne = EmitVectorLongExtract(context, op.Rn, 0, sizeF + 2); Operand res = EmitFPConvert(context, ne, sizeF, signed: false); @@ -515,11 +563,7 @@ namespace ARMeilleure.Instructions public static void Ucvtf_V(ArmEmitterContext context) { - OpCodeSimd op = (OpCodeSimd)context.CurrOp; - - int sizeF = op.Size & 1; - - if (Optimizations.UseSse2 && sizeF == 0) + if (Optimizations.UseSse2) { EmitSse2Ucvtf(context, scalar: false); } @@ -531,12 +575,7 @@ namespace ARMeilleure.Instructions public static void Ucvtf_V_Fixed(ArmEmitterContext context) { - OpCodeSimd op = (OpCodeSimd)context.CurrOp; - - // sizeF == ((OpCodeSimdShImm)op).Size - 2 - int sizeF = op.Size & 1; - - if (Optimizations.UseSse2 && sizeF == 0) + if (Optimizations.UseSse2) { EmitSse2Ucvtf(context, scalar: false); } @@ -830,43 +869,69 @@ namespace ARMeilleure.Instructions } } - private static void EmitSse41Fcvts(ArmEmitterContext context, FPRoundingMode roundMode, bool scalar) + private static Operand EmitSse2CvtDoubleToInt64OpF(ArmEmitterContext context, Operand opF, bool scalar) + { + Debug.Assert(opF.Type == OperandType.V128); + + Operand longL = context.AddIntrinsicLong (Intrinsic.X86Cvtsd2si, opF); // opFL + Operand res = context.VectorCreateScalar(longL); + + if (!scalar) + { + Operand opFH = context.AddIntrinsic (Intrinsic.X86Movhlps, res, opF); // res doesn't matter. + Operand longH = context.AddIntrinsicLong (Intrinsic.X86Cvtsd2si, opFH); + Operand resH = context.VectorCreateScalar(longH); + res = context.AddIntrinsic (Intrinsic.X86Movlhps, res, resH); + } + + return res; + } + + private static Operand EmitSse2CvtInt64ToDoubleOp(ArmEmitterContext context, Operand op, bool scalar) + { + Debug.Assert(op.Type == OperandType.V128); + + Operand longL = context.AddIntrinsicLong(Intrinsic.X86Cvtsi2si, op); // opL + Operand res = context.AddIntrinsic (Intrinsic.X86Cvtsi2sd, context.VectorZero(), longL); + + if (!scalar) + { + Operand opH = context.AddIntrinsic (Intrinsic.X86Movhlps, res, op); // res doesn't matter. + Operand longH = context.AddIntrinsicLong(Intrinsic.X86Cvtsi2si, opH); + Operand resH = context.AddIntrinsic (Intrinsic.X86Cvtsi2sd, res, longH); // res doesn't matter. + res = context.AddIntrinsic (Intrinsic.X86Movlhps, res, resH); + } + + return res; + } + + private static void EmitSse2Scvtf(ArmEmitterContext context, bool scalar) { OpCodeSimd op = (OpCodeSimd)context.CurrOp; Operand n = GetVec(op.Rn); - // sizeF == ((OpCodeSimdShImm64)op).Size - 2 + // sizeF == ((OpCodeSimdShImm)op).Size - 2 int sizeF = op.Size & 1; if (sizeF == 0) { - Operand nMask = context.AddIntrinsic(Intrinsic.X86Cmpps, n, n, Const((int)CmpCondition.OrderedQ)); - - Operand nScaled = context.AddIntrinsic(Intrinsic.X86Pand, nMask, n); + Operand res = context.AddIntrinsic(Intrinsic.X86Cvtdq2ps, n); if (op is OpCodeSimdShImm fixedOp) { int fBits = GetImmShr(fixedOp); - // BitConverter.Int32BitsToSingle(fpScaled) == MathF.Pow(2f, fBits) - int fpScaled = 0x3F800000 + fBits * 0x800000; + // BitConverter.Int32BitsToSingle(fpScaled) == 1f / MathF.Pow(2f, fBits) + int fpScaled = 0x3F800000 - fBits * 0x800000; - Operand scale = X86GetAllElements(context, fpScaled); + Operand fpScaledMask = scalar + ? X86GetScalar (context, fpScaled) + : X86GetAllElements(context, fpScaled); - nScaled = context.AddIntrinsic(Intrinsic.X86Mulps, nScaled, scale); + res = context.AddIntrinsic(Intrinsic.X86Mulps, res, fpScaledMask); } - Operand nRnd = context.AddIntrinsic(Intrinsic.X86Roundps, nScaled, Const(X86GetRoundControl(roundMode))); - - Operand nInt = context.AddIntrinsic(Intrinsic.X86Cvtps2dq, nRnd); - - Operand mask = X86GetAllElements(context, 0x4F000000); // 2.14748365E9f (2147483648) - - Operand mask2 = context.AddIntrinsic(Intrinsic.X86Cmpps, nRnd, mask, Const((int)CmpCondition.NotLessThan)); - - Operand res = context.AddIntrinsic(Intrinsic.X86Pxor, nInt, mask2); - if (scalar) { res = context.VectorZeroUpper96(res); @@ -880,9 +945,175 @@ namespace ARMeilleure.Instructions } else /* if (sizeF == 1) */ { - Operand nMask = context.AddIntrinsic(Intrinsic.X86Cmppd, n, n, Const((int)CmpCondition.OrderedQ)); + Operand res = EmitSse2CvtInt64ToDoubleOp(context, n, scalar); - Operand nScaled = context.AddIntrinsic(Intrinsic.X86Pand, nMask, n); + if (op is OpCodeSimdShImm fixedOp) + { + int fBits = GetImmShr(fixedOp); + + // BitConverter.Int64BitsToDouble(fpScaled) == 1d / Math.Pow(2d, fBits) + long fpScaled = 0x3FF0000000000000L - fBits * 0x10000000000000L; + + Operand fpScaledMask = scalar + ? X86GetScalar (context, fpScaled) + : X86GetAllElements(context, fpScaled); + + res = context.AddIntrinsic(Intrinsic.X86Mulpd, res, fpScaledMask); + } + + if (scalar) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + } + + private static void EmitSse2Ucvtf(ArmEmitterContext context, bool scalar) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + // sizeF == ((OpCodeSimdShImm)op).Size - 2 + int sizeF = op.Size & 1; + + if (sizeF == 0) + { + Operand mask = scalar // 65536.000f (1 << 16) + ? X86GetScalar (context, 0x47800000) + : X86GetAllElements(context, 0x47800000); + + Operand res = context.AddIntrinsic(Intrinsic.X86Psrld, n, Const(16)); + res = context.AddIntrinsic(Intrinsic.X86Cvtdq2ps, res); + res = context.AddIntrinsic(Intrinsic.X86Mulps, res, mask); + + Operand res2 = context.AddIntrinsic(Intrinsic.X86Pslld, n, Const(16)); + res2 = context.AddIntrinsic(Intrinsic.X86Psrld, res2, Const(16)); + res2 = context.AddIntrinsic(Intrinsic.X86Cvtdq2ps, res2); + + res = context.AddIntrinsic(Intrinsic.X86Addps, res, res2); + + if (op is OpCodeSimdShImm fixedOp) + { + int fBits = GetImmShr(fixedOp); + + // BitConverter.Int32BitsToSingle(fpScaled) == 1f / MathF.Pow(2f, fBits) + int fpScaled = 0x3F800000 - fBits * 0x800000; + + Operand fpScaledMask = scalar + ? X86GetScalar (context, fpScaled) + : X86GetAllElements(context, fpScaled); + + res = context.AddIntrinsic(Intrinsic.X86Mulps, res, fpScaledMask); + } + + if (scalar) + { + res = context.VectorZeroUpper96(res); + } + else if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else /* if (sizeF == 1) */ + { + Operand mask = scalar // 4294967296.0000000d (1L << 32) + ? X86GetScalar (context, 0x41F0000000000000L) + : X86GetAllElements(context, 0x41F0000000000000L); + + Operand res = context.AddIntrinsic (Intrinsic.X86Psrlq, n, Const(32)); + res = EmitSse2CvtInt64ToDoubleOp(context, res, scalar); + res = context.AddIntrinsic (Intrinsic.X86Mulpd, res, mask); + + Operand res2 = context.AddIntrinsic (Intrinsic.X86Psllq, n, Const(32)); + res2 = context.AddIntrinsic (Intrinsic.X86Psrlq, res2, Const(32)); + res2 = EmitSse2CvtInt64ToDoubleOp(context, res2, scalar); + + res = context.AddIntrinsic(Intrinsic.X86Addpd, res, res2); + + if (op is OpCodeSimdShImm fixedOp) + { + int fBits = GetImmShr(fixedOp); + + // BitConverter.Int64BitsToDouble(fpScaled) == 1d / Math.Pow(2d, fBits) + long fpScaled = 0x3FF0000000000000L - fBits * 0x10000000000000L; + + Operand fpScaledMask = scalar + ? X86GetScalar (context, fpScaled) + : X86GetAllElements(context, fpScaled); + + res = context.AddIntrinsic(Intrinsic.X86Mulpd, res, fpScaledMask); + } + + if (scalar) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + } + + private static void EmitSse41Fcvts(ArmEmitterContext context, FPRoundingMode roundMode, bool scalar) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + // sizeF == ((OpCodeSimdShImm)op).Size - 2 + int sizeF = op.Size & 1; + + if (sizeF == 0) + { + Operand nRes = context.AddIntrinsic(Intrinsic.X86Cmpps, n, n, Const((int)CmpCondition.OrderedQ)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, n); + + if (op is OpCodeSimdShImm fixedOp) + { + int fBits = GetImmShr(fixedOp); + + // BitConverter.Int32BitsToSingle(fpScaled) == MathF.Pow(2f, fBits) + int fpScaled = 0x3F800000 + fBits * 0x800000; + + Operand fpScaledMask = scalar + ? X86GetScalar (context, fpScaled) + : X86GetAllElements(context, fpScaled); + + nRes = context.AddIntrinsic(Intrinsic.X86Mulps, nRes, fpScaledMask); + } + + nRes = context.AddIntrinsic(Intrinsic.X86Roundps, nRes, Const(X86GetRoundControl(roundMode))); + + Operand nInt = context.AddIntrinsic(Intrinsic.X86Cvtps2dq, nRes); + + Operand fpMaxValMask = scalar // 2.14748365E9f (2147483648) + ? X86GetScalar (context, 0x4F000000) + : X86GetAllElements(context, 0x4F000000); + + nRes = context.AddIntrinsic(Intrinsic.X86Cmpps, nRes, fpMaxValMask, Const((int)CmpCondition.NotLessThan)); + + Operand dRes = context.AddIntrinsic(Intrinsic.X86Pxor, nInt, nRes); + + if (scalar) + { + dRes = context.VectorZeroUpper96(dRes); + } + else if (op.RegisterSize == RegisterSize.Simd64) + { + dRes = context.VectorZeroUpper64(dRes); + } + + context.Copy(GetVec(op.Rd), dRes); + } + else /* if (sizeF == 1) */ + { + Operand nRes = context.AddIntrinsic(Intrinsic.X86Cmppd, n, n, Const((int)CmpCondition.OrderedQ)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, n); if (op is OpCodeSimdShImm fixedOp) { @@ -891,41 +1122,31 @@ namespace ARMeilleure.Instructions // BitConverter.Int64BitsToDouble(fpScaled) == Math.Pow(2d, fBits) long fpScaled = 0x3FF0000000000000L + fBits * 0x10000000000000L; - Operand scale = X86GetAllElements(context, fpScaled); + Operand fpScaledMask = scalar + ? X86GetScalar (context, fpScaled) + : X86GetAllElements(context, fpScaled); - nScaled = context.AddIntrinsic(Intrinsic.X86Mulpd, nScaled, scale); + nRes = context.AddIntrinsic(Intrinsic.X86Mulpd, nRes, fpScaledMask); } - Operand nRnd = context.AddIntrinsic(Intrinsic.X86Roundpd, nScaled, Const(X86GetRoundControl(roundMode))); + nRes = context.AddIntrinsic(Intrinsic.X86Roundpd, nRes, Const(X86GetRoundControl(roundMode))); - Operand high; + Operand nLong = EmitSse2CvtDoubleToInt64OpF(context, nRes, scalar); - if (!scalar) - { - high = context.AddIntrinsic(Intrinsic.X86Unpckhpd, nRnd, nRnd); - high = context.AddIntrinsicLong(Intrinsic.X86Cvtsd2si, high); - } - else - { - high = Const(0L); - } + Operand fpMaxValMask = scalar // 9.2233720368547760E18d (9223372036854775808) + ? X86GetScalar (context, 0x43E0000000000000L) + : X86GetAllElements(context, 0x43E0000000000000L); - Operand low = context.AddIntrinsicLong(Intrinsic.X86Cvtsd2si, nRnd); + nRes = context.AddIntrinsic(Intrinsic.X86Cmppd, nRes, fpMaxValMask, Const((int)CmpCondition.NotLessThan)); - Operand nInt = EmitVectorLongCreate(context, low, high); - - Operand mask = X86GetAllElements(context, 0x43E0000000000000L); // 9.2233720368547760E18d (9223372036854775808) - - Operand mask2 = context.AddIntrinsic(Intrinsic.X86Cmppd, nRnd, mask, Const((int)CmpCondition.NotLessThan)); - - Operand res = context.AddIntrinsic(Intrinsic.X86Pxor, nInt, mask2); + Operand dRes = context.AddIntrinsic(Intrinsic.X86Pxor, nLong, nRes); if (scalar) { - res = context.VectorZeroUpper64(res); + dRes = context.VectorZeroUpper64(dRes); } - context.Copy(GetVec(op.Rd), res); + context.Copy(GetVec(op.Rd), dRes); } } @@ -940,9 +1161,8 @@ namespace ARMeilleure.Instructions if (sizeF == 0) { - Operand nMask = context.AddIntrinsic(Intrinsic.X86Cmpps, n, n, Const((int)CmpCondition.OrderedQ)); - - Operand nScaled = context.AddIntrinsic(Intrinsic.X86Pand, nMask, n); + Operand nRes = context.AddIntrinsic(Intrinsic.X86Cmpps, n, n, Const((int)CmpCondition.OrderedQ)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, n); if (op is OpCodeSimdShImm fixedOp) { @@ -951,50 +1171,53 @@ namespace ARMeilleure.Instructions // BitConverter.Int32BitsToSingle(fpScaled) == MathF.Pow(2f, fBits) int fpScaled = 0x3F800000 + fBits * 0x800000; - Operand scale = X86GetAllElements(context, fpScaled); + Operand fpScaledMask = scalar + ? X86GetScalar (context, fpScaled) + : X86GetAllElements(context, fpScaled); - nScaled = context.AddIntrinsic(Intrinsic.X86Mulps, nScaled, scale); + nRes = context.AddIntrinsic(Intrinsic.X86Mulps, nRes, fpScaledMask); } - Operand nRnd = context.AddIntrinsic(Intrinsic.X86Roundps, nScaled, Const(X86GetRoundControl(roundMode))); + nRes = context.AddIntrinsic(Intrinsic.X86Roundps, nRes, Const(X86GetRoundControl(roundMode))); - Operand nRndMask = context.AddIntrinsic(Intrinsic.X86Cmpps, nRnd, context.VectorZero(), Const((int)CmpCondition.NotLessThanOrEqual)); + Operand zero = context.VectorZero(); - Operand nRndMasked = context.AddIntrinsic(Intrinsic.X86Pand, nRnd, nRndMask); + Operand nCmp = context.AddIntrinsic(Intrinsic.X86Cmpps, nRes, zero, Const((int)CmpCondition.NotLessThanOrEqual)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, nCmp); - Operand nInt = context.AddIntrinsic(Intrinsic.X86Cvtps2dq, nRndMasked); + Operand fpMaxValMask = scalar // 2.14748365E9f (2147483648) + ? X86GetScalar (context, 0x4F000000) + : X86GetAllElements(context, 0x4F000000); - Operand mask = X86GetAllElements(context, 0x4F000000); // 2.14748365E9f (2147483648) + Operand nInt = context.AddIntrinsic(Intrinsic.X86Cvtps2dq, nRes); - Operand res = context.AddIntrinsic(Intrinsic.X86Subps, nRndMasked, mask); + nRes = context.AddIntrinsic(Intrinsic.X86Subps, nRes, fpMaxValMask); - Operand mask2 = context.AddIntrinsic(Intrinsic.X86Cmpps, res, context.VectorZero(), Const((int)CmpCondition.NotLessThanOrEqual)); + nCmp = context.AddIntrinsic(Intrinsic.X86Cmpps, nRes, zero, Const((int)CmpCondition.NotLessThanOrEqual)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, nCmp); - Operand resMasked = context.AddIntrinsic(Intrinsic.X86Pand, res, mask2); + Operand nInt2 = context.AddIntrinsic(Intrinsic.X86Cvtps2dq, nRes); - res = context.AddIntrinsic(Intrinsic.X86Cvtps2dq, resMasked); + nRes = context.AddIntrinsic(Intrinsic.X86Cmpps, nRes, fpMaxValMask, Const((int)CmpCondition.NotLessThan)); - Operand mask3 = context.AddIntrinsic(Intrinsic.X86Cmpps, resMasked, mask, Const((int)CmpCondition.NotLessThan)); - - res = context.AddIntrinsic(Intrinsic.X86Pxor, res, mask3); - res = context.AddIntrinsic(Intrinsic.X86Paddd, res, nInt); + Operand dRes = context.AddIntrinsic(Intrinsic.X86Pxor, nInt2, nRes); + dRes = context.AddIntrinsic(Intrinsic.X86Paddd, dRes, nInt); if (scalar) { - res = context.VectorZeroUpper96(res); + dRes = context.VectorZeroUpper96(dRes); } else if (op.RegisterSize == RegisterSize.Simd64) { - res = context.VectorZeroUpper64(res); + dRes = context.VectorZeroUpper64(dRes); } - context.Copy(GetVec(op.Rd), res); + context.Copy(GetVec(op.Rd), dRes); } else /* if (sizeF == 1) */ { - Operand nMask = context.AddIntrinsic(Intrinsic.X86Cmppd, n, n, Const((int)CmpCondition.OrderedQ)); - - Operand nScaled = context.AddIntrinsic(Intrinsic.X86Pand, nMask, n); + Operand nRes = context.AddIntrinsic(Intrinsic.X86Cmppd, n, n, Const((int)CmpCondition.OrderedQ)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, n); if (op is OpCodeSimdShImm fixedOp) { @@ -1003,140 +1226,251 @@ namespace ARMeilleure.Instructions // BitConverter.Int64BitsToDouble(fpScaled) == Math.Pow(2d, fBits) long fpScaled = 0x3FF0000000000000L + fBits * 0x10000000000000L; - Operand scale = X86GetAllElements(context, fpScaled); + Operand fpScaledMask = scalar + ? X86GetScalar (context, fpScaled) + : X86GetAllElements(context, fpScaled); - nScaled = context.AddIntrinsic(Intrinsic.X86Mulpd, nScaled, scale); + nRes = context.AddIntrinsic(Intrinsic.X86Mulpd, nRes, fpScaledMask); } - Operand nRnd = context.AddIntrinsic(Intrinsic.X86Roundpd, nScaled, Const(X86GetRoundControl(roundMode))); + nRes = context.AddIntrinsic(Intrinsic.X86Roundpd, nRes, Const(X86GetRoundControl(roundMode))); - Operand nRndMask = context.AddIntrinsic(Intrinsic.X86Cmppd, nRnd, context.VectorZero(), Const((int)CmpCondition.NotLessThanOrEqual)); + Operand zero = context.VectorZero(); - Operand nRndMasked = context.AddIntrinsic(Intrinsic.X86Pand, nRnd, nRndMask); + Operand nCmp = context.AddIntrinsic(Intrinsic.X86Cmppd, nRes, zero, Const((int)CmpCondition.NotLessThanOrEqual)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, nCmp); - Operand high; + Operand fpMaxValMask = scalar // 9.2233720368547760E18d (9223372036854775808) + ? X86GetScalar (context, 0x43E0000000000000L) + : X86GetAllElements(context, 0x43E0000000000000L); - if (!scalar) - { - high = context.AddIntrinsic(Intrinsic.X86Unpckhpd, nRndMasked, nRndMasked); - high = context.AddIntrinsicLong(Intrinsic.X86Cvtsd2si, high); - } - else - { - high = Const(0L); - } + Operand nLong = EmitSse2CvtDoubleToInt64OpF(context, nRes, scalar); - Operand low = context.AddIntrinsicLong(Intrinsic.X86Cvtsd2si, nRndMasked); + nRes = context.AddIntrinsic(Intrinsic.X86Subpd, nRes, fpMaxValMask); - Operand nInt = EmitVectorLongCreate(context, low, high); + nCmp = context.AddIntrinsic(Intrinsic.X86Cmppd, nRes, zero, Const((int)CmpCondition.NotLessThanOrEqual)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, nCmp); - Operand mask = X86GetAllElements(context, 0x43E0000000000000L); // 9.2233720368547760E18d (9223372036854775808) + Operand nLong2 = EmitSse2CvtDoubleToInt64OpF(context, nRes, scalar); - Operand res = context.AddIntrinsic(Intrinsic.X86Subpd, nRndMasked, mask); + nRes = context.AddIntrinsic(Intrinsic.X86Cmppd, nRes, fpMaxValMask, Const((int)CmpCondition.NotLessThan)); - Operand mask2 = context.AddIntrinsic(Intrinsic.X86Cmppd, res, context.VectorZero(), Const((int)CmpCondition.NotLessThanOrEqual)); - - Operand resMasked = context.AddIntrinsic(Intrinsic.X86Pand, res, mask2); - - if (!scalar) - { - high = context.AddIntrinsic(Intrinsic.X86Unpckhpd, resMasked, resMasked); - high = context.AddIntrinsicLong(Intrinsic.X86Cvtsd2si, high); - } - - low = context.AddIntrinsicLong(Intrinsic.X86Cvtsd2si, resMasked); - - res = EmitVectorLongCreate(context, low, high); - - Operand mask3 = context.AddIntrinsic(Intrinsic.X86Cmppd, resMasked, mask, Const((int)CmpCondition.NotLessThan)); - - res = context.AddIntrinsic(Intrinsic.X86Pxor, res, mask3); - res = context.AddIntrinsic(Intrinsic.X86Paddq, res, nInt); + Operand dRes = context.AddIntrinsic(Intrinsic.X86Pxor, nLong2, nRes); + dRes = context.AddIntrinsic(Intrinsic.X86Paddq, dRes, nLong); if (scalar) { - res = context.VectorZeroUpper64(res); + dRes = context.VectorZeroUpper64(dRes); } - context.Copy(GetVec(op.Rd), res); + context.Copy(GetVec(op.Rd), dRes); } } - private static void EmitSse2Scvtf(ArmEmitterContext context, bool scalar) + private static void EmitSse41Fcvts_Gp(ArmEmitterContext context, FPRoundingMode roundMode, bool isFixed) { - OpCodeSimd op = (OpCodeSimd)context.CurrOp; + OpCodeSimdCvt op = (OpCodeSimdCvt)context.CurrOp; Operand n = GetVec(op.Rn); - Operand res = context.AddIntrinsic(Intrinsic.X86Cvtdq2ps, n); - - if (op is OpCodeSimdShImm fixedOp) + if (op.Size == 0) { - int fBits = GetImmShr(fixedOp); + Operand nRes = context.AddIntrinsic(Intrinsic.X86Cmpss, n, n, Const((int)CmpCondition.OrderedQ)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, n); - // BitConverter.Int32BitsToSingle(fpScaled) == 1f / MathF.Pow(2f, fBits) - int fpScaled = 0x3F800000 - fBits * 0x800000; + if (isFixed) + { + // BitConverter.Int32BitsToSingle(fpScaled) == MathF.Pow(2f, op.FBits) + int fpScaled = 0x3F800000 + op.FBits * 0x800000; - Operand scale = X86GetAllElements(context, fpScaled); + Operand fpScaledMask = X86GetScalar(context, fpScaled); - res = context.AddIntrinsic(Intrinsic.X86Mulps, res, scale); + nRes = context.AddIntrinsic(Intrinsic.X86Mulss, nRes, fpScaledMask); + } + + nRes = context.AddIntrinsic(Intrinsic.X86Roundss, nRes, Const(X86GetRoundControl(roundMode))); + + Operand nIntOrLong = op.RegisterSize == RegisterSize.Int32 + ? context.AddIntrinsicInt (Intrinsic.X86Cvtss2si, nRes) + : context.AddIntrinsicLong(Intrinsic.X86Cvtss2si, nRes); + + int fpMaxVal = op.RegisterSize == RegisterSize.Int32 + ? 0x4F000000 // 2.14748365E9f (2147483648) + : 0x5F000000; // 9.223372E18f (9223372036854775808) + + Operand fpMaxValMask = X86GetScalar(context, fpMaxVal); + + nRes = context.AddIntrinsic(Intrinsic.X86Cmpss, nRes, fpMaxValMask, Const((int)CmpCondition.NotLessThan)); + + Operand nInt = context.AddIntrinsicInt(Intrinsic.X86Cvtsi2si, nRes); + + if (op.RegisterSize == RegisterSize.Int64) + { + nInt = context.SignExtend32(OperandType.I64, nInt); + } + + Operand dRes = context.BitwiseExclusiveOr(nIntOrLong, nInt); + + SetIntOrZR(context, op.Rd, dRes); } - - if (scalar) + else /* if (op.Size == 1) */ { - res = context.VectorZeroUpper96(res); - } - else if (op.RegisterSize == RegisterSize.Simd64) - { - res = context.VectorZeroUpper64(res); - } + Operand nRes = context.AddIntrinsic(Intrinsic.X86Cmpsd, n, n, Const((int)CmpCondition.OrderedQ)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, n); - context.Copy(GetVec(op.Rd), res); + if (isFixed) + { + // BitConverter.Int64BitsToDouble(fpScaled) == Math.Pow(2d, op.FBits) + long fpScaled = 0x3FF0000000000000L + op.FBits * 0x10000000000000L; + + Operand fpScaledMask = X86GetScalar(context, fpScaled); + + nRes = context.AddIntrinsic(Intrinsic.X86Mulsd, nRes, fpScaledMask); + } + + nRes = context.AddIntrinsic(Intrinsic.X86Roundsd, nRes, Const(X86GetRoundControl(roundMode))); + + Operand nIntOrLong = op.RegisterSize == RegisterSize.Int32 + ? context.AddIntrinsicInt (Intrinsic.X86Cvtsd2si, nRes) + : context.AddIntrinsicLong(Intrinsic.X86Cvtsd2si, nRes); + + long fpMaxVal = op.RegisterSize == RegisterSize.Int32 + ? 0x41E0000000000000L // 2147483648.0000000d (2147483648) + : 0x43E0000000000000L; // 9.2233720368547760E18d (9223372036854775808) + + Operand fpMaxValMask = X86GetScalar(context, fpMaxVal); + + nRes = context.AddIntrinsic(Intrinsic.X86Cmpsd, nRes, fpMaxValMask, Const((int)CmpCondition.NotLessThan)); + + Operand nLong = context.AddIntrinsicLong(Intrinsic.X86Cvtsi2si, nRes); + + if (op.RegisterSize == RegisterSize.Int32) + { + nLong = context.ConvertI64ToI32(nLong); + } + + Operand dRes = context.BitwiseExclusiveOr(nIntOrLong, nLong); + + SetIntOrZR(context, op.Rd, dRes); + } } - private static void EmitSse2Ucvtf(ArmEmitterContext context, bool scalar) + private static void EmitSse41Fcvtu_Gp(ArmEmitterContext context, FPRoundingMode roundMode, bool isFixed) { - OpCodeSimd op = (OpCodeSimd)context.CurrOp; + OpCodeSimdCvt op = (OpCodeSimdCvt)context.CurrOp; Operand n = GetVec(op.Rn); - Operand res = context.AddIntrinsic(Intrinsic.X86Psrld, n, Const(16)); - - res = context.AddIntrinsic(Intrinsic.X86Cvtdq2ps, res); - - Operand mask = X86GetAllElements(context, 0x47800000); // 65536.0f (1 << 16) - - res = context.AddIntrinsic(Intrinsic.X86Mulps, res, mask); - - Operand res2 = context.AddIntrinsic(Intrinsic.X86Pslld, n, Const(16)); - - res2 = context.AddIntrinsic(Intrinsic.X86Psrld, res2, Const(16)); - res2 = context.AddIntrinsic(Intrinsic.X86Cvtdq2ps, res2); - - res = context.AddIntrinsic(Intrinsic.X86Addps, res, res2); - - if (op is OpCodeSimdShImm fixedOp) + if (op.Size == 0) { - int fBits = GetImmShr(fixedOp); + Operand nRes = context.AddIntrinsic(Intrinsic.X86Cmpss, n, n, Const((int)CmpCondition.OrderedQ)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, n); - // BitConverter.Int32BitsToSingle(fpScaled) == 1f / MathF.Pow(2f, fBits) - int fpScaled = 0x3F800000 - fBits * 0x800000; + if (isFixed) + { + // BitConverter.Int32BitsToSingle(fpScaled) == MathF.Pow(2f, op.FBits) + int fpScaled = 0x3F800000 + op.FBits * 0x800000; - Operand scale = X86GetAllElements(context, fpScaled); + Operand fpScaledMask = X86GetScalar(context, fpScaled); - res = context.AddIntrinsic(Intrinsic.X86Mulps, res, scale); + nRes = context.AddIntrinsic(Intrinsic.X86Mulss, nRes, fpScaledMask); + } + + nRes = context.AddIntrinsic(Intrinsic.X86Roundss, nRes, Const(X86GetRoundControl(roundMode))); + + Operand zero = context.VectorZero(); + + Operand nCmp = context.AddIntrinsic(Intrinsic.X86Cmpss, nRes, zero, Const((int)CmpCondition.NotLessThanOrEqual)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, nCmp); + + int fpMaxVal = op.RegisterSize == RegisterSize.Int32 + ? 0x4F000000 // 2.14748365E9f (2147483648) + : 0x5F000000; // 9.223372E18f (9223372036854775808) + + Operand fpMaxValMask = X86GetScalar(context, fpMaxVal); + + Operand nIntOrLong = op.RegisterSize == RegisterSize.Int32 + ? context.AddIntrinsicInt (Intrinsic.X86Cvtss2si, nRes) + : context.AddIntrinsicLong(Intrinsic.X86Cvtss2si, nRes); + + nRes = context.AddIntrinsic(Intrinsic.X86Subss, nRes, fpMaxValMask); + + nCmp = context.AddIntrinsic(Intrinsic.X86Cmpss, nRes, zero, Const((int)CmpCondition.NotLessThanOrEqual)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, nCmp); + + Operand nIntOrLong2 = op.RegisterSize == RegisterSize.Int32 + ? context.AddIntrinsicInt (Intrinsic.X86Cvtss2si, nRes) + : context.AddIntrinsicLong(Intrinsic.X86Cvtss2si, nRes); + + nRes = context.AddIntrinsic(Intrinsic.X86Cmpss, nRes, fpMaxValMask, Const((int)CmpCondition.NotLessThan)); + + Operand nInt = context.AddIntrinsicInt(Intrinsic.X86Cvtsi2si, nRes); + + if (op.RegisterSize == RegisterSize.Int64) + { + nInt = context.SignExtend32(OperandType.I64, nInt); + } + + Operand dRes = context.BitwiseExclusiveOr(nIntOrLong2, nInt); + dRes = context.Add(dRes, nIntOrLong); + + SetIntOrZR(context, op.Rd, dRes); } - - if (scalar) + else /* if (op.Size == 1) */ { - res = context.VectorZeroUpper96(res); - } - else if (op.RegisterSize == RegisterSize.Simd64) - { - res = context.VectorZeroUpper64(res); - } + Operand nRes = context.AddIntrinsic(Intrinsic.X86Cmpsd, n, n, Const((int)CmpCondition.OrderedQ)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, n); - context.Copy(GetVec(op.Rd), res); + if (isFixed) + { + // BitConverter.Int64BitsToDouble(fpScaled) == Math.Pow(2d, op.FBits) + long fpScaled = 0x3FF0000000000000L + op.FBits * 0x10000000000000L; + + Operand fpScaledMask = X86GetScalar(context, fpScaled); + + nRes = context.AddIntrinsic(Intrinsic.X86Mulsd, nRes, fpScaledMask); + } + + nRes = context.AddIntrinsic(Intrinsic.X86Roundsd, nRes, Const(X86GetRoundControl(roundMode))); + + Operand zero = context.VectorZero(); + + Operand nCmp = context.AddIntrinsic(Intrinsic.X86Cmpsd, nRes, zero, Const((int)CmpCondition.NotLessThanOrEqual)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, nCmp); + + long fpMaxVal = op.RegisterSize == RegisterSize.Int32 + ? 0x41E0000000000000L // 2147483648.0000000d (2147483648) + : 0x43E0000000000000L; // 9.2233720368547760E18d (9223372036854775808) + + Operand fpMaxValMask = X86GetScalar(context, fpMaxVal); + + Operand nIntOrLong = op.RegisterSize == RegisterSize.Int32 + ? context.AddIntrinsicInt (Intrinsic.X86Cvtsd2si, nRes) + : context.AddIntrinsicLong(Intrinsic.X86Cvtsd2si, nRes); + + nRes = context.AddIntrinsic(Intrinsic.X86Subsd, nRes, fpMaxValMask); + + nCmp = context.AddIntrinsic(Intrinsic.X86Cmpsd, nRes, zero, Const((int)CmpCondition.NotLessThanOrEqual)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, nCmp); + + Operand nIntOrLong2 = op.RegisterSize == RegisterSize.Int32 + ? context.AddIntrinsicInt (Intrinsic.X86Cvtsd2si, nRes) + : context.AddIntrinsicLong(Intrinsic.X86Cvtsd2si, nRes); + + nRes = context.AddIntrinsic(Intrinsic.X86Cmpsd, nRes, fpMaxValMask, Const((int)CmpCondition.NotLessThan)); + + Operand nLong = context.AddIntrinsicLong(Intrinsic.X86Cvtsi2si, nRes); + + if (op.RegisterSize == RegisterSize.Int32) + { + nLong = context.ConvertI64ToI32(nLong); + } + + Operand dRes = context.BitwiseExclusiveOr(nIntOrLong2, nLong); + dRes = context.Add(dRes, nIntOrLong); + + SetIntOrZR(context, op.Rd, dRes); + } } private static Operand EmitVectorLongExtract(ArmEmitterContext context, int reg, int index, int size) @@ -1145,14 +1479,5 @@ namespace ARMeilleure.Instructions return context.VectorExtract(type, GetVec(reg), index); } - - private static Operand EmitVectorLongCreate(ArmEmitterContext context, Operand low, Operand high) - { - Operand vector = context.VectorCreateScalar(low); - - vector = context.VectorInsert(vector, high, 1); - - return vector; - } } } diff --git a/ARMeilleure/Instructions/InstEmitSimdHelper.cs b/ARMeilleure/Instructions/InstEmitSimdHelper.cs index 28d075dd3b..fce1bed5cb 100644 --- a/ARMeilleure/Instructions/InstEmitSimdHelper.cs +++ b/ARMeilleure/Instructions/InstEmitSimdHelper.cs @@ -16,6 +16,24 @@ namespace ARMeilleure.Instructions static class InstEmitSimdHelper { +#region "Masks" + public static readonly long[] EvenMasks = new long[] + { + 14L << 56 | 12L << 48 | 10L << 40 | 08L << 32 | 06L << 24 | 04L << 16 | 02L << 8 | 00L << 0, // B + 13L << 56 | 12L << 48 | 09L << 40 | 08L << 32 | 05L << 24 | 04L << 16 | 01L << 8 | 00L << 0, // H + 11L << 56 | 10L << 48 | 09L << 40 | 08L << 32 | 03L << 24 | 02L << 16 | 01L << 8 | 00L << 0 // S + }; + + public static readonly long[] OddMasks = new long[] + { + 15L << 56 | 13L << 48 | 11L << 40 | 09L << 32 | 07L << 24 | 05L << 16 | 03L << 8 | 01L << 0, // B + 15L << 56 | 14L << 48 | 11L << 40 | 10L << 32 | 07L << 24 | 06L << 16 | 03L << 8 | 02L << 0, // H + 15L << 56 | 14L << 48 | 13L << 40 | 12L << 32 | 07L << 24 | 06L << 16 | 05L << 8 | 04L << 0 // S + }; + + private static readonly long _zeroMask = 128L << 56 | 128L << 48 | 128L << 40 | 128L << 32 | 128L << 24 | 128L << 16 | 128L << 8 | 128L << 0; +#endregion + #region "X86 SSE Intrinsics" public static readonly Intrinsic[] X86PaddInstruction = new Intrinsic[] { @@ -189,11 +207,19 @@ namespace ARMeilleure.Instructions return vector; } + public static Operand X86GetElements(ArmEmitterContext context, long e1, long e0) + { + Operand vector0 = context.VectorCreateScalar(Const(e0)); + Operand vector1 = context.VectorCreateScalar(Const(e1)); + + return context.AddIntrinsic(Intrinsic.X86Punpcklqdq, vector0, vector1); + } + public static int X86GetRoundControl(FPRoundingMode roundMode) { switch (roundMode) { - case FPRoundingMode.ToNearest: return 8 | 0; + case FPRoundingMode.ToNearest: return 8 | 0; // even case FPRoundingMode.TowardsPlusInfinity: return 8 | 2; case FPRoundingMode.TowardsMinusInfinity: return 8 | 1; case FPRoundingMode.TowardsZero: return 8 | 3; @@ -991,6 +1017,46 @@ namespace ARMeilleure.Instructions context.Copy(GetVec(op.Rd), res); } + public static void EmitSsse3VectorPairwiseOp(ArmEmitterContext context, Intrinsic[] inst) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + if (op.RegisterSize == RegisterSize.Simd64) + { + Operand zeroEvenMask = X86GetElements(context, _zeroMask, EvenMasks[op.Size]); + Operand zeroOddMask = X86GetElements(context, _zeroMask, OddMasks [op.Size]); + + Operand mN = context.AddIntrinsic(Intrinsic.X86Punpcklqdq, n, m); // m:n + + Operand left = context.AddIntrinsic(Intrinsic.X86Pshufb, mN, zeroEvenMask); // 0:even from m:n + Operand right = context.AddIntrinsic(Intrinsic.X86Pshufb, mN, zeroOddMask); // 0:odd from m:n + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst[op.Size], left, right)); + } + else if (op.Size < 3) + { + Operand oddEvenMask = X86GetElements(context, OddMasks[op.Size], EvenMasks[op.Size]); + + Operand oddEvenN = context.AddIntrinsic(Intrinsic.X86Pshufb, n, oddEvenMask); // odd:even from n + Operand oddEvenM = context.AddIntrinsic(Intrinsic.X86Pshufb, m, oddEvenMask); // odd:even from m + + Operand left = context.AddIntrinsic(Intrinsic.X86Punpcklqdq, oddEvenN, oddEvenM); + Operand right = context.AddIntrinsic(Intrinsic.X86Punpckhqdq, oddEvenN, oddEvenM); + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst[op.Size], left, right)); + } + else + { + Operand left = context.AddIntrinsic(Intrinsic.X86Punpcklqdq, n, m); + Operand right = context.AddIntrinsic(Intrinsic.X86Punpckhqdq, n, m); + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst[3], left, right)); + } + } + public static void EmitVectorAcrossVectorOpSx(ArmEmitterContext context, Func2I emit) { EmitVectorAcrossVectorOp(context, emit, signed: true, isLong: false); @@ -1066,7 +1132,7 @@ namespace ARMeilleure.Instructions context.Copy(GetVec(op.Rd), res); } - public static void EmitVectorPairwiseOpF(ArmEmitterContext context, Intrinsic inst32, Intrinsic inst64) + public static void EmitSse2VectorPairwiseOpF(ArmEmitterContext context, Intrinsic inst32, Intrinsic inst64) { OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; @@ -1231,8 +1297,7 @@ namespace ARMeilleure.Instructions if (op.Size <= 2) { - Operand temp = add ? context.Add (ne, me) - : context.Subtract(ne, me); + Operand temp = add ? context.Add(ne, me) : context.Subtract(ne, me); de = EmitSatQ(context, temp, op.Size, signedSrc: true, signedDst: signed); } @@ -1316,7 +1381,9 @@ namespace ARMeilleure.Instructions int part = !scalar && (op.RegisterSize == RegisterSize.Simd128) ? elems : 0; - Operand res = part == 0 ? context.VectorZero() : context.Copy(GetVec(op.Rd)); + Operand d = GetVec(op.Rd); + + Operand res = part == 0 ? context.VectorZero() : context.Copy(d); for (int index = 0; index < elems; index++) { @@ -1327,7 +1394,7 @@ namespace ARMeilleure.Instructions res = EmitVectorInsert(context, res, temp, part + index, op.Size); } - context.Copy(GetVec(op.Rd), res); + context.Copy(d, res); } // TSrc (16bit, 32bit, 64bit; signed, unsigned) > TDst (8bit, 16bit, 32bit; signed, unsigned). diff --git a/ARMeilleure/Instructions/InstEmitSimdLogical.cs b/ARMeilleure/Instructions/InstEmitSimdLogical.cs index 551752d240..362296f7ff 100644 --- a/ARMeilleure/Instructions/InstEmitSimdLogical.cs +++ b/ARMeilleure/Instructions/InstEmitSimdLogical.cs @@ -1,6 +1,7 @@ using ARMeilleure.Decoders; using ARMeilleure.IntermediateRepresentation; using ARMeilleure.Translation; +using System.Diagnostics; using static ARMeilleure.Instructions.InstEmitHelper; using static ARMeilleure.Instructions.InstEmitSimdHelper; @@ -107,7 +108,7 @@ namespace ARMeilleure.Instructions res = context.VectorZeroUpper64(res); } - context.Copy(GetVec(op.Rd), res); + context.Copy(d, res); } else { @@ -158,7 +159,7 @@ namespace ARMeilleure.Instructions res = context.VectorZeroUpper64(res); } - context.Copy(GetVec(op.Rd), res); + context.Copy(d, res); } else { @@ -292,11 +293,7 @@ namespace ARMeilleure.Instructions { Operand ne = EmitVectorExtractZx(context, op.Rn, index, 0); - ne = context.ConvertI64ToI32(ne); - - Operand de = context.Call(new _U32_U32(SoftFallback.ReverseBits8), ne); - - de = context.ZeroExtend32(OperandType.I64, de); + Operand de = EmitReverseBits8Op(context, ne); res = EmitVectorInsert(context, res, de, index, 0); } @@ -304,6 +301,20 @@ namespace ARMeilleure.Instructions context.Copy(GetVec(op.Rd), res); } + private static Operand EmitReverseBits8Op(ArmEmitterContext context, Operand op) + { + Debug.Assert(op.Type == OperandType.I64); + + Operand val = context.BitwiseOr(context.ShiftRightUI(context.BitwiseAnd(op, Const(0xaaul)), Const(1)), + context.ShiftLeft (context.BitwiseAnd(op, Const(0x55ul)), Const(1))); + + val = context.BitwiseOr(context.ShiftRightUI(context.BitwiseAnd(val, Const(0xccul)), Const(2)), + context.ShiftLeft (context.BitwiseAnd(val, Const(0x33ul)), Const(2))); + + return context.BitwiseOr(context.ShiftRightUI(val, Const(4)), + context.ShiftLeft (context.BitwiseAnd(val, Const(0x0ful)), Const(4))); + } + public static void Rev16_V(ArmEmitterContext context) { if (Optimizations.UseSsse3) diff --git a/ARMeilleure/Instructions/InstEmitSimdMove.cs b/ARMeilleure/Instructions/InstEmitSimdMove.cs index 789c8c8712..a1a4635f97 100644 --- a/ARMeilleure/Instructions/InstEmitSimdMove.cs +++ b/ARMeilleure/Instructions/InstEmitSimdMove.cs @@ -13,20 +13,6 @@ namespace ARMeilleure.Instructions static partial class InstEmit { #region "Masks" - private static readonly long[] _masksE0_TrnUzpXtn = new long[] - { - 14L << 56 | 12L << 48 | 10L << 40 | 08L << 32 | 06L << 24 | 04L << 16 | 02L << 8 | 00L << 0, - 13L << 56 | 12L << 48 | 09L << 40 | 08L << 32 | 05L << 24 | 04L << 16 | 01L << 8 | 00L << 0, - 11L << 56 | 10L << 48 | 09L << 40 | 08L << 32 | 03L << 24 | 02L << 16 | 01L << 8 | 00L << 0 - }; - - private static readonly long[] _masksE1_TrnUzp = new long[] - { - 15L << 56 | 13L << 48 | 11L << 40 | 09L << 32 | 07L << 24 | 05L << 16 | 03L << 8 | 01L << 0, - 15L << 56 | 14L << 48 | 11L << 40 | 10L << 32 | 07L << 24 | 06L << 16 | 03L << 8 | 02L << 0, - 15L << 56 | 14L << 48 | 13L << 40 | 12L << 32 | 07L << 24 | 06L << 16 | 05L << 8 | 04L << 0 - }; - private static readonly long[] _masksE0_Uzp = new long[] { 13L << 56 | 09L << 48 | 05L << 40 | 01L << 32 | 12L << 24 | 08L << 16 | 04L << 8 | 00L << 0, @@ -447,7 +433,7 @@ namespace ARMeilleure.Instructions Operand res = context.VectorZeroUpper64(d); - Operand mask = X86GetAllElements(context, _masksE0_TrnUzpXtn[op.Size]); + Operand mask = X86GetAllElements(context, EvenMasks[op.Size]); Operand res2 = context.AddIntrinsic(Intrinsic.X86Pshufb, GetVec(op.Rn), mask); @@ -646,8 +632,8 @@ namespace ARMeilleure.Instructions if (op.Size < 3) { - long maskE0 = _masksE0_TrnUzpXtn[op.Size]; - long maskE1 = _masksE1_TrnUzp [op.Size]; + long maskE0 = EvenMasks[op.Size]; + long maskE1 = OddMasks [op.Size]; mask = X86GetScalar(context, maskE0); @@ -714,8 +700,8 @@ namespace ARMeilleure.Instructions if (op.Size < 3) { - long maskE0 = _masksE0_TrnUzpXtn[op.Size]; - long maskE1 = _masksE1_TrnUzp [op.Size]; + long maskE0 = EvenMasks[op.Size]; + long maskE1 = OddMasks [op.Size]; mask = X86GetScalar(context, maskE0); diff --git a/ARMeilleure/Instructions/InstEmitSimdShift.cs b/ARMeilleure/Instructions/InstEmitSimdShift.cs index 17f43c812c..19c0a74d28 100644 --- a/ARMeilleure/Instructions/InstEmitSimdShift.cs +++ b/ARMeilleure/Instructions/InstEmitSimdShift.cs @@ -4,6 +4,7 @@ using ARMeilleure.Decoders; using ARMeilleure.IntermediateRepresentation; using ARMeilleure.Translation; using System; +using System.Diagnostics; using static ARMeilleure.Instructions.InstEmitHelper; using static ARMeilleure.Instructions.InstEmitSimdHelper; @@ -16,13 +17,6 @@ namespace ARMeilleure.Instructions static partial class InstEmit { #region "Masks" - private static readonly long[] _masks_RshrnShrn = new long[] - { - 14L << 56 | 12L << 48 | 10L << 40 | 08L << 32 | 06L << 24 | 04L << 16 | 02L << 8 | 00L << 0, - 13L << 56 | 12L << 48 | 09L << 40 | 08L << 32 | 05L << 24 | 04L << 16 | 01L << 8 | 00L << 0, - 11L << 56 | 10L << 48 | 09L << 40 | 08L << 32 | 03L << 24 | 02L << 16 | 01L << 8 | 00L << 0 - }; - private static readonly long[] _masks_SliSri = new long[] // Replication masks. { 0x0101010101010101L, 0x0001000100010001L, 0x0000000100000001L, 0x0000000000000001L @@ -42,7 +36,7 @@ namespace ARMeilleure.Instructions Operand d = GetVec(op.Rd); Operand n = GetVec(op.Rn); - Operand dLow = context.AddIntrinsic(Intrinsic.X86Movlhps, d, context.VectorZero()); + Operand dLow = context.VectorZeroUpper64(d); Operand mask = null; @@ -61,7 +55,7 @@ namespace ARMeilleure.Instructions res = context.AddIntrinsic(srlInst, res, Const(shift)); - Operand mask2 = X86GetAllElements(context, _masks_RshrnShrn[op.Size]); + Operand mask2 = X86GetAllElements(context, EvenMasks[op.Size]); res = context.AddIntrinsic(Intrinsic.X86Pshufb, res, mask2); @@ -157,13 +151,13 @@ namespace ARMeilleure.Instructions Operand d = GetVec(op.Rd); Operand n = GetVec(op.Rn); - Operand dLow = context.AddIntrinsic(Intrinsic.X86Movlhps, d, context.VectorZero()); + Operand dLow = context.VectorZeroUpper64(d); Intrinsic srlInst = X86PsrlInstruction[op.Size + 1]; Operand nShifted = context.AddIntrinsic(srlInst, n, Const(shift)); - Operand mask = X86GetAllElements(context, _masks_RshrnShrn[op.Size]); + Operand mask = X86GetAllElements(context, EvenMasks[op.Size]); Operand res = context.AddIntrinsic(Intrinsic.X86Pshufb, nShifted, mask); @@ -702,9 +696,9 @@ namespace ARMeilleure.Instructions for (int index = 0; index < elems; index++) { Operand ne = EmitVectorExtractZx(context, op.Rn, index, op.Size); - Operand me = EmitVectorExtractZx(context, op.Rm, index, op.Size); + Operand me = EmitVectorExtractSx(context, op.Rm, index << op.Size, 0); - Operand e = context.Call(new _U64_U64_U64_Bool_S32(SoftFallback.UnsignedShlReg), ne, me, Const(0), Const(op.Size)); + Operand e = EmitUnsignedShlRegOp(context, ne, context.ConvertI64ToI32(me), op.Size); res = EmitVectorInsert(context, res, e, index, op.Size); } @@ -879,9 +873,7 @@ namespace ARMeilleure.Instructions e = context.Add(e, Const(roundConst)); } - e = signed - ? context.ShiftRightSI(e, Const(shift)) - : context.ShiftRightUI(e, Const(shift)); + e = signed ? context.ShiftRightSI(e, Const(shift)) : context.ShiftRightUI(e, Const(shift)); } else /* if (op.Size == 3) */ { @@ -901,6 +893,28 @@ namespace ARMeilleure.Instructions context.Copy(GetVec(op.Rd), res); } + private static Operand EmitUnsignedShlRegOp(ArmEmitterContext context, Operand op, Operand shiftLsB, int size) + { + Debug.Assert(op.Type == OperandType.I64); + Debug.Assert(shiftLsB.Type == OperandType.I32); + Debug.Assert((uint)size < 4u); + + Operand negShiftLsB = context.Negate(shiftLsB); + + Operand isPositive = context.ICompareGreaterOrEqual(shiftLsB, Const(0)); + + Operand shl = context.ShiftLeft (op, shiftLsB); + Operand shr = context.ShiftRightUI(op, negShiftLsB); + + Operand res = context.ConditionalSelect(isPositive, shl, shr); + + Operand isOutOfRange = context.BitwiseOr( + context.ICompareGreaterOrEqual(shiftLsB, Const(8 << size)), + context.ICompareGreaterOrEqual(negShiftLsB, Const(8 << size))); + + return context.ConditionalSelect(isOutOfRange, Const(0UL), res); + } + private static void EmitVectorShrImmNarrowOpZx(ArmEmitterContext context, bool round) { OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; @@ -913,7 +927,9 @@ namespace ARMeilleure.Instructions int part = op.RegisterSize == RegisterSize.Simd128 ? elems : 0; - Operand res = part == 0 ? context.VectorZero() : context.Copy(GetVec(op.Rd)); + Operand d = GetVec(op.Rd); + + Operand res = part == 0 ? context.VectorZero() : context.Copy(d); for (int index = 0; index < elems; index++) { @@ -929,7 +945,7 @@ namespace ARMeilleure.Instructions res = EmitVectorInsert(context, res, e, part + index, op.Size); } - context.Copy(GetVec(op.Rd), res); + context.Copy(d, res); } [Flags] @@ -972,7 +988,9 @@ namespace ARMeilleure.Instructions int part = !scalar && (op.RegisterSize == RegisterSize.Simd128) ? elems : 0; - Operand res = part == 0 ? context.VectorZero() : context.Copy(GetVec(op.Rd)); + Operand d = GetVec(op.Rd); + + Operand res = part == 0 ? context.VectorZero() : context.Copy(d); for (int index = 0; index < elems; index++) { @@ -985,9 +1003,7 @@ namespace ARMeilleure.Instructions e = context.Add(e, Const(roundConst)); } - e = signedSrc - ? context.ShiftRightSI(e, Const(shift)) - : context.ShiftRightUI(e, Const(shift)); + e = signedSrc ? context.ShiftRightSI(e, Const(shift)) : context.ShiftRightUI(e, Const(shift)); } else /* if (op.Size == 2 && round) */ { @@ -999,7 +1015,7 @@ namespace ARMeilleure.Instructions res = EmitVectorInsert(context, res, e, part + index, op.Size); } - context.Copy(GetVec(op.Rd), res); + context.Copy(d, res); } // dst64 = (Int(src64, signed) + roundConst) >> shift; diff --git a/ARMeilleure/Instructions/SoftFallback.cs b/ARMeilleure/Instructions/SoftFallback.cs index 8d48c34f66..10bb47df54 100644 --- a/ARMeilleure/Instructions/SoftFallback.cs +++ b/ARMeilleure/Instructions/SoftFallback.cs @@ -1240,74 +1240,5 @@ namespace ARMeilleure.Instructions : (uint)(value >> 32); } #endregion - -#region "Reverse" - public static uint ReverseBits8(uint value) - { - value = ((value & 0xaa) >> 1) | ((value & 0x55) << 1); - value = ((value & 0xcc) >> 2) | ((value & 0x33) << 2); - - return (value >> 4) | ((value & 0x0f) << 4); - } - - public static uint ReverseBits32(uint value) - { - value = ((value & 0xaaaaaaaa) >> 1) | ((value & 0x55555555) << 1); - value = ((value & 0xcccccccc) >> 2) | ((value & 0x33333333) << 2); - value = ((value & 0xf0f0f0f0) >> 4) | ((value & 0x0f0f0f0f) << 4); - value = ((value & 0xff00ff00) >> 8) | ((value & 0x00ff00ff) << 8); - - return (value >> 16) | (value << 16); - } - - public static ulong ReverseBits64(ulong value) - { - value = ((value & 0xaaaaaaaaaaaaaaaa) >> 1 ) | ((value & 0x5555555555555555) << 1 ); - value = ((value & 0xcccccccccccccccc) >> 2 ) | ((value & 0x3333333333333333) << 2 ); - value = ((value & 0xf0f0f0f0f0f0f0f0) >> 4 ) | ((value & 0x0f0f0f0f0f0f0f0f) << 4 ); - value = ((value & 0xff00ff00ff00ff00) >> 8 ) | ((value & 0x00ff00ff00ff00ff) << 8 ); - value = ((value & 0xffff0000ffff0000) >> 16) | ((value & 0x0000ffff0000ffff) << 16); - - return (value >> 32) | (value << 32); - } - - public static uint ReverseBytes16_32(uint value) => (uint)ReverseBytes16_64(value); - - public static ulong ReverseBytes16_64(ulong value) => ReverseBytes(value, RevSize.Rev16); - public static ulong ReverseBytes32_64(ulong value) => ReverseBytes(value, RevSize.Rev32); - - private enum RevSize - { - Rev16, - Rev32, - Rev64 - } - - private static ulong ReverseBytes(ulong value, RevSize size) - { - value = ((value & 0xff00ff00ff00ff00) >> 8) | ((value & 0x00ff00ff00ff00ff) << 8); - - if (size == RevSize.Rev16) - { - return value; - } - - value = ((value & 0xffff0000ffff0000) >> 16) | ((value & 0x0000ffff0000ffff) << 16); - - if (size == RevSize.Rev32) - { - return value; - } - - value = ((value & 0xffffffff00000000) >> 32) | ((value & 0x00000000ffffffff) << 32); - - if (size == RevSize.Rev64) - { - return value; - } - - throw new ArgumentException(nameof(size)); - } -#endregion } } diff --git a/ARMeilleure/IntermediateRepresentation/Intrinsic.cs b/ARMeilleure/IntermediateRepresentation/Intrinsic.cs index 57c8914d9c..c3f375c4c2 100644 --- a/ARMeilleure/IntermediateRepresentation/Intrinsic.cs +++ b/ARMeilleure/IntermediateRepresentation/Intrinsic.cs @@ -30,8 +30,11 @@ namespace ARMeilleure.IntermediateRepresentation X86Cvtps2pd, X86Cvtsd2si, X86Cvtsd2ss, + X86Cvtsi2sd, X86Cvtsi2si, + X86Cvtsi2ss, X86Cvtss2sd, + X86Cvtss2si, X86Divpd, X86Divps, X86Divsd, diff --git a/Ryujinx.Tests/Cpu/CpuTestSimd.cs b/Ryujinx.Tests/Cpu/CpuTestSimd.cs index 30dec59ac2..8f7e206972 100644 --- a/Ryujinx.Tests/Cpu/CpuTestSimd.cs +++ b/Ryujinx.Tests/Cpu/CpuTestSimd.cs @@ -2856,7 +2856,7 @@ namespace Ryujinx.Tests.Cpu SingleOpcode(opcodes, v0: v0, v1: v1); - CompareAgainstUnicorn(fpTolerances: FpTolerances.UpToOneUlpsD); // unsigned + CompareAgainstUnicorn(); } [Test, Pairwise] [Explicit] @@ -2892,7 +2892,7 @@ namespace Ryujinx.Tests.Cpu SingleOpcode(opcodes, v0: v0, v1: v1); - CompareAgainstUnicorn(fpTolerances: FpTolerances.UpToOneUlpsD); // unsigned + CompareAgainstUnicorn(); } [Test, Pairwise] diff --git a/Ryujinx.Tests/Cpu/CpuTestSimdCvt.cs b/Ryujinx.Tests/Cpu/CpuTestSimdCvt.cs index 17a2853f19..2d5c82318c 100644 --- a/Ryujinx.Tests/Cpu/CpuTestSimdCvt.cs +++ b/Ryujinx.Tests/Cpu/CpuTestSimdCvt.cs @@ -582,7 +582,7 @@ namespace Ryujinx.Tests.Cpu SingleOpcode(opcodes, x1: xn, x31: x31, v0: v0); - CompareAgainstUnicorn(fpTolerances: FpTolerances.UpToOneUlpsD); // unsigned + CompareAgainstUnicorn(); } [Test, Pairwise] [Explicit] @@ -666,7 +666,7 @@ namespace Ryujinx.Tests.Cpu SingleOpcode(opcodes, x1: xn, x31: x31, v0: v0); - CompareAgainstUnicorn(fpTolerances: FpTolerances.UpToOneUlpsD); // unsigned + CompareAgainstUnicorn(); } #endif } diff --git a/Ryujinx.Tests/Cpu/CpuTestSimdShImm.cs b/Ryujinx.Tests/Cpu/CpuTestSimdShImm.cs index 0f1b0dac25..1d208d6958 100644 --- a/Ryujinx.Tests/Cpu/CpuTestSimdShImm.cs +++ b/Ryujinx.Tests/Cpu/CpuTestSimdShImm.cs @@ -564,7 +564,7 @@ namespace Ryujinx.Tests.Cpu SingleOpcode(opcodes, v0: v0, v1: v1); - CompareAgainstUnicorn(fpTolerances: FpTolerances.UpToOneUlpsD); // unsigned + CompareAgainstUnicorn(); } [Test, Pairwise]