From f362bef43d3460fdd8eaf2fbd023588bee969cd6 Mon Sep 17 00:00:00 2001 From: Evan Husted Date: Sun, 29 Dec 2024 21:17:01 -0600 Subject: [PATCH] misc: Overhaul DirtyHacks saving to support storing a value alongside an off/off flag. --- src/Ryujinx.Common/BitTricks.cs | 35 ++++++++++++++ .../Configuration/DirtyHacks.cs | 47 +++++++++++++++++-- src/Ryujinx.HLE/HLEConfiguration.cs | 6 +-- .../Services/Fs/FileSystemProxy/IStorage.cs | 2 +- src/Ryujinx.HLE/Switch.cs | 4 +- src/Ryujinx/AppHost.cs | 2 +- src/Ryujinx/Headless/HeadlessRyujinx.cs | 3 -- .../UI/ViewModels/SettingsViewModel.cs | 32 +++++++++++++ .../UI/Views/Settings/SettingsHacksView.axaml | 27 +++++++++++ .../Configuration/ConfigurationFileFormat.cs | 6 +-- .../ConfigurationState.Migration.cs | 24 +++++++++- .../Configuration/ConfigurationState.Model.cs | 26 ++++++---- .../Configuration/ConfigurationState.cs | 3 +- 13 files changed, 190 insertions(+), 27 deletions(-) create mode 100644 src/Ryujinx.Common/BitTricks.cs diff --git a/src/Ryujinx.Common/BitTricks.cs b/src/Ryujinx.Common/BitTricks.cs new file mode 100644 index 000000000..d0c689291 --- /dev/null +++ b/src/Ryujinx.Common/BitTricks.cs @@ -0,0 +1,35 @@ +namespace Ryujinx.Common +{ + public class BitTricks + { + // Never actually written bit packing logic before, so I looked it up. + // This code is from https://gist.github.com/Alan-FGR/04938e93e2bffdf5802ceb218a37c195 + + public static ulong PackBitFields(uint[] values, byte[] bitFields) + { + ulong retVal = values[0]; //we set the first value right away + for (int f = 1; f < values.Length; f++) + { + retVal <<= bitFields[f]; // we shift the previous value + retVal += values[f];// and add our current value + } + return retVal; + } + + public static uint[] UnpackBitFields(ulong packed, byte[] bitFields) + { + int fields = bitFields.Length - 1; // number of fields to unpack + uint[] retArr = new uint[fields + 1]; // init return array + int curPos = 0; // current field bit position (start) + int lastEnd; // position where last field ended + for (int f = fields; f >= 0; f--) // loop from last + { + lastEnd = curPos; // we store where the last value ended + curPos += bitFields[f]; // we get where the current value starts + int leftShift = 64 - curPos; // we figure how much left shift we gotta apply for the other numbers to overflow into oblivion + retArr[f] = (uint)((packed << leftShift) >> leftShift + lastEnd); // we do magic + } + return retArr; + } + } +} diff --git a/src/Ryujinx.Common/Configuration/DirtyHacks.cs b/src/Ryujinx.Common/Configuration/DirtyHacks.cs index 6a6d4949c..12f6b019b 100644 --- a/src/Ryujinx.Common/Configuration/DirtyHacks.cs +++ b/src/Ryujinx.Common/Configuration/DirtyHacks.cs @@ -1,11 +1,52 @@ using System; +using System.Collections.Generic; +using System.Linq; namespace Ryujinx.Common.Configuration { [Flags] - public enum DirtyHacks + public enum DirtyHacks : byte { - None = 0, - Xc2MenuSoftlockFix = 1 << 10 + Xc2MenuSoftlockFix = 1, + ShaderCompilationThreadSleep = 2 + } + + public record EnabledDirtyHack(DirtyHacks Hack, int Value) + { + private static readonly byte[] _packedFormat = [8, 32]; + + public ulong Pack() => BitTricks.PackBitFields([(uint)Hack, (uint)Value], _packedFormat); + + public static EnabledDirtyHack FromPacked(ulong packedHack) + { + var unpackedFields = BitTricks.UnpackBitFields(packedHack, _packedFormat); + if (unpackedFields is not [var hack, var value]) + throw new ArgumentException(nameof(packedHack)); + + return new EnabledDirtyHack((DirtyHacks)hack, (int)value); + } + } + + public class DirtyHackCollection : Dictionary + { + public DirtyHackCollection(EnabledDirtyHack[] hacks) + { + foreach ((DirtyHacks dirtyHacks, int value) in hacks) + { + Add(dirtyHacks, value); + } + } + + public DirtyHackCollection(ulong[] packedHacks) + { + foreach ((DirtyHacks dirtyHacks, int value) in packedHacks.Select(EnabledDirtyHack.FromPacked)) + { + Add(dirtyHacks, value); + } + } + + public new int this[DirtyHacks hack] => TryGetValue(hack, out var value) ? value : -1; + + public bool IsEnabled(DirtyHacks hack) => ContainsKey(hack); } } diff --git a/src/Ryujinx.HLE/HLEConfiguration.cs b/src/Ryujinx.HLE/HLEConfiguration.cs index b44a09b22..8ac76508f 100644 --- a/src/Ryujinx.HLE/HLEConfiguration.cs +++ b/src/Ryujinx.HLE/HLEConfiguration.cs @@ -192,7 +192,7 @@ namespace Ryujinx.HLE /// /// The desired hacky workarounds. /// - public DirtyHacks Hacks { internal get; set; } + public EnabledDirtyHack[] Hacks { internal get; set; } public HLEConfiguration(VirtualFileSystem virtualFileSystem, LibHacHorizonManager libHacHorizonManager, @@ -224,7 +224,7 @@ namespace Ryujinx.HLE string multiplayerLdnPassphrase, string multiplayerLdnServer, int customVSyncInterval, - DirtyHacks dirtyHacks = DirtyHacks.None) + EnabledDirtyHack[] dirtyHacks = null) { VirtualFileSystem = virtualFileSystem; LibHacHorizonManager = libHacHorizonManager; @@ -256,7 +256,7 @@ namespace Ryujinx.HLE MultiplayerDisableP2p = multiplayerDisableP2p; MultiplayerLdnPassphrase = multiplayerLdnPassphrase; MultiplayerLdnServer = multiplayerLdnServer; - Hacks = dirtyHacks; + Hacks = dirtyHacks ?? []; } } } diff --git a/src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IStorage.cs b/src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IStorage.cs index 07ab8b386..ac5dc04e9 100644 --- a/src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IStorage.cs +++ b/src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IStorage.cs @@ -39,7 +39,7 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy using var region = context.Memory.GetWritableRegion(bufferAddress, (int)bufferLen, true); Result result = _baseStorage.Get.Read((long)offset, new OutBuffer(region.Memory.Span), (long)size); - if (context.Device.DirtyHacks.HasFlag(DirtyHacks.Xc2MenuSoftlockFix) && TitleIDs.CurrentApplication.Value == Xc2TitleId) + if (context.Device.DirtyHacks.IsEnabled(DirtyHacks.Xc2MenuSoftlockFix) && TitleIDs.CurrentApplication.Value == Xc2TitleId) { // Add a load-bearing sleep to avoid XC2 softlock // https://web.archive.org/web/20240728045136/https://github.com/Ryujinx/Ryujinx/issues/2357 diff --git a/src/Ryujinx.HLE/Switch.cs b/src/Ryujinx.HLE/Switch.cs index c630c71c7..ed1cc02d3 100644 --- a/src/Ryujinx.HLE/Switch.cs +++ b/src/Ryujinx.HLE/Switch.cs @@ -40,7 +40,7 @@ namespace Ryujinx.HLE public bool IsFrameAvailable => Gpu.Window.IsFrameAvailable; - public DirtyHacks DirtyHacks { get; } + public DirtyHackCollection DirtyHacks { get; } public Switch(HLEConfiguration configuration) { @@ -77,7 +77,7 @@ namespace Ryujinx.HLE System.EnablePtc = Configuration.EnablePtc; System.FsIntegrityCheckLevel = Configuration.FsIntegrityCheckLevel; System.GlobalAccessLogMode = Configuration.FsGlobalAccessLogMode; - DirtyHacks = Configuration.Hacks; + DirtyHacks = new DirtyHackCollection(Configuration.Hacks); UpdateVSyncInterval(); #pragma warning restore IDE0055 diff --git a/src/Ryujinx/AppHost.cs b/src/Ryujinx/AppHost.cs index cff6a44a5..b90f8b801 100644 --- a/src/Ryujinx/AppHost.cs +++ b/src/Ryujinx/AppHost.cs @@ -952,7 +952,7 @@ namespace Ryujinx.Ava ConfigurationState.Instance.Multiplayer.LdnPassphrase, ConfigurationState.Instance.Multiplayer.LdnServer, ConfigurationState.Instance.Graphics.CustomVSyncInterval.Value, - ConfigurationState.Instance.Hacks.ShowDirtyHacks ? ConfigurationState.Instance.Hacks.EnabledHacks : DirtyHacks.None)); + ConfigurationState.Instance.Hacks.ShowDirtyHacks ? ConfigurationState.Instance.Hacks.EnabledHacks : null)); } private static IHardwareDeviceDriver InitializeAudio() diff --git a/src/Ryujinx/Headless/HeadlessRyujinx.cs b/src/Ryujinx/Headless/HeadlessRyujinx.cs index 3d99fb902..3cb0afca3 100644 --- a/src/Ryujinx/Headless/HeadlessRyujinx.cs +++ b/src/Ryujinx/Headless/HeadlessRyujinx.cs @@ -14,9 +14,6 @@ using Ryujinx.Cpu; using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.Gpu; using Ryujinx.Graphics.Gpu.Shader; -using Ryujinx.Graphics.Metal; -using Ryujinx.Graphics.OpenGL; -using Ryujinx.Graphics.Vulkan; using Ryujinx.Graphics.Vulkan.MoltenVK; using Ryujinx.HLE; using Ryujinx.HLE.FileSystem; diff --git a/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs b/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs index 1f239d3ce..0afa01fc2 100644 --- a/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs @@ -66,6 +66,8 @@ namespace Ryujinx.Ava.UI.ViewModels private string _ldnServer; private bool _xc2MenuSoftlockFix = ConfigurationState.Instance.Hacks.Xc2MenuSoftlockFix; + private bool _shaderTranslationThreadSleep = ConfigurationState.Instance.Hacks.EnableShaderCompilationThreadSleep; + private int _shaderTranslationSleepDelay = ConfigurationState.Instance.Hacks.ShaderCompilationThreadSleepDelay; public int ResolutionScale { @@ -287,6 +289,28 @@ namespace Ryujinx.Ava.UI.ViewModels OnPropertyChanged(); } } + + public bool ShaderTranslationDelayEnabled + { + get => _shaderTranslationThreadSleep; + set + { + _shaderTranslationThreadSleep = value; + + OnPropertyChanged(); + } + } + + public int ShaderTranslationDelay + { + get => _shaderTranslationSleepDelay; + set + { + _shaderTranslationSleepDelay = value; + + OnPropertyChanged(); + } + } public int Language { get; set; } public int Region { get; set; } @@ -763,6 +787,8 @@ namespace Ryujinx.Ava.UI.ViewModels // Dirty Hacks config.Hacks.Xc2MenuSoftlockFix.Value = Xc2MenuSoftlockFixEnabled; + config.Hacks.EnableShaderCompilationThreadSleep.Value = ShaderTranslationDelayEnabled; + config.Hacks.ShaderCompilationThreadSleepDelay.Value = ShaderTranslationDelay; config.ToFileFormat().SaveConfig(Program.ConfigurationPath); @@ -809,5 +835,11 @@ namespace Ryujinx.Ava.UI.ViewModels "there is a low chance that the game will softlock, " + "the submenu won't show up, while background music is still there."); }); + + public static string ShaderTranslationDelayTooltip { get; } = Lambda.String(sb => + { + sb.Append( + "This hack applies the delay you specify every time shaders are attempted to be translated."); + }); } } diff --git a/src/Ryujinx/UI/Views/Settings/SettingsHacksView.axaml b/src/Ryujinx/UI/Views/Settings/SettingsHacksView.axaml index b7817f064..b4e3437ff 100644 --- a/src/Ryujinx/UI/Views/Settings/SettingsHacksView.axaml +++ b/src/Ryujinx/UI/Views/Settings/SettingsHacksView.axaml @@ -42,6 +42,33 @@ VerticalAlignment="Center" Text="Xenoblade Chronicles 2 Menu Softlock Fix" /> + + + + + + + diff --git a/src/Ryujinx/Utilities/Configuration/ConfigurationFileFormat.cs b/src/Ryujinx/Utilities/Configuration/ConfigurationFileFormat.cs index c964ed76d..540024cbd 100644 --- a/src/Ryujinx/Utilities/Configuration/ConfigurationFileFormat.cs +++ b/src/Ryujinx/Utilities/Configuration/ConfigurationFileFormat.cs @@ -17,7 +17,7 @@ namespace Ryujinx.Ava.Utilities.Configuration /// /// The current version of the file format /// - public const int CurrentVersion = 58; + public const int CurrentVersion = 59; /// /// Version of the configuration file format @@ -436,9 +436,9 @@ namespace Ryujinx.Ava.Utilities.Configuration public bool ShowDirtyHacks { get; set; } /// - /// The packed value of the enabled dirty hacks. + /// The packed values of the enabled dirty hacks. /// - public int EnabledDirtyHacks { get; set; } + public ulong[] DirtyHacks { get; set; } /// /// Loads a configuration file from disk diff --git a/src/Ryujinx/Utilities/Configuration/ConfigurationState.Migration.cs b/src/Ryujinx/Utilities/Configuration/ConfigurationState.Migration.cs index 8cfb9d53a..b0664e1b6 100644 --- a/src/Ryujinx/Utilities/Configuration/ConfigurationState.Migration.cs +++ b/src/Ryujinx/Utilities/Configuration/ConfigurationState.Migration.cs @@ -9,6 +9,7 @@ using Ryujinx.Common.Logging; using Ryujinx.HLE; using System; using System.Collections.Generic; +using System.Linq; namespace Ryujinx.Ava.Utilities.Configuration { @@ -637,6 +638,18 @@ namespace Ryujinx.Ava.Utilities.Configuration configurationFileUpdated = true; } + + // 58 migration accidentally got skipped but it worked with no issues somehow lol + + if (configurationFileFormat.Version < 59) + { + Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 59."); + + configurationFileFormat.ShowDirtyHacks = false; + configurationFileFormat.DirtyHacks = []; + + configurationFileUpdated = true; + } Logger.EnableFileLog.Value = configurationFileFormat.EnableFileLog; Graphics.ResScale.Value = configurationFileFormat.ResScale; @@ -737,7 +750,16 @@ namespace Ryujinx.Ava.Utilities.Configuration Multiplayer.LdnServer.Value = configurationFileFormat.LdnServer; Hacks.ShowDirtyHacks.Value = configurationFileFormat.ShowDirtyHacks; - Hacks.Xc2MenuSoftlockFix.Value = ((DirtyHacks)configurationFileFormat.EnabledDirtyHacks).HasFlag(DirtyHacks.Xc2MenuSoftlockFix); + + { + EnabledDirtyHack[] hacks = configurationFileFormat.DirtyHacks.Select(EnabledDirtyHack.FromPacked).ToArray(); + + Hacks.Xc2MenuSoftlockFix.Value = hacks.Any(it => it.Hack == DirtyHacks.Xc2MenuSoftlockFix); + + var shaderCompilationThreadSleep = hacks.FirstOrDefault(it => it.Hack == DirtyHacks.ShaderCompilationThreadSleep); + Hacks.EnableShaderCompilationThreadSleep.Value = shaderCompilationThreadSleep != null; + Hacks.ShaderCompilationThreadSleepDelay.Value = shaderCompilationThreadSleep?.Value ?? 0; + } if (configurationFileUpdated) { diff --git a/src/Ryujinx/Utilities/Configuration/ConfigurationState.Model.cs b/src/Ryujinx/Utilities/Configuration/ConfigurationState.Model.cs index c27b3f0e3..53192618e 100644 --- a/src/Ryujinx/Utilities/Configuration/ConfigurationState.Model.cs +++ b/src/Ryujinx/Utilities/Configuration/ConfigurationState.Model.cs @@ -9,6 +9,7 @@ using Ryujinx.Common.Helper; using Ryujinx.Common.Logging; using Ryujinx.HLE; using System.Collections.Generic; +using System.Linq; namespace Ryujinx.Ava.Utilities.Configuration { @@ -626,36 +627,43 @@ namespace Ryujinx.Ava.Utilities.Configuration public ReactiveObject ShowDirtyHacks { get; private set; } public ReactiveObject Xc2MenuSoftlockFix { get; private set; } + + public ReactiveObject EnableShaderCompilationThreadSleep { get; private set; } + + public ReactiveObject ShaderCompilationThreadSleepDelay { get; private set; } public HacksSection() { ShowDirtyHacks = new ReactiveObject(); Xc2MenuSoftlockFix = new ReactiveObject(); Xc2MenuSoftlockFix.Event += HackChanged; + EnableShaderCompilationThreadSleep = new ReactiveObject(); + EnableShaderCompilationThreadSleep.Event += HackChanged; + ShaderCompilationThreadSleepDelay = new ReactiveObject(); } private void HackChanged(object sender, ReactiveEventArgs rxe) { - Ryujinx.Common.Logging.Logger.Info?.Print(LogClass.Configuration, $"EnabledDirtyHacks set to: {EnabledHacks}", "LogValueChange"); + Ryujinx.Common.Logging.Logger.Info?.Print(LogClass.Configuration, $"EnabledDirtyHacks set to: [{EnabledHacks.Select(x => x.Hack).JoinToString(", ")}]", "LogValueChange"); } - public DirtyHacks EnabledHacks + public EnabledDirtyHack[] EnabledHacks { get { - DirtyHacks dirtyHacks = DirtyHacks.None; + List enabledHacks = []; if (Xc2MenuSoftlockFix) Apply(DirtyHacks.Xc2MenuSoftlockFix); - return dirtyHacks; + if (EnableShaderCompilationThreadSleep) + Apply(DirtyHacks.ShaderCompilationThreadSleep, ShaderCompilationThreadSleepDelay); + + return enabledHacks.ToArray(); - void Apply(DirtyHacks hack) + void Apply(DirtyHacks hack, int value = 0) { - if (dirtyHacks is not DirtyHacks.None) - dirtyHacks |= hack; - else - dirtyHacks = hack; + enabledHacks.Add(new EnabledDirtyHack(hack, value)); } } } diff --git a/src/Ryujinx/Utilities/Configuration/ConfigurationState.cs b/src/Ryujinx/Utilities/Configuration/ConfigurationState.cs index 01534bec8..95ec62e83 100644 --- a/src/Ryujinx/Utilities/Configuration/ConfigurationState.cs +++ b/src/Ryujinx/Utilities/Configuration/ConfigurationState.cs @@ -7,6 +7,7 @@ using Ryujinx.Common.Configuration.Multiplayer; using Ryujinx.Graphics.Vulkan; using Ryujinx.HLE; using System; +using System.Linq; namespace Ryujinx.Ava.Utilities.Configuration { @@ -139,7 +140,7 @@ namespace Ryujinx.Ava.Utilities.Configuration MultiplayerLdnPassphrase = Multiplayer.LdnPassphrase, LdnServer = Multiplayer.LdnServer, ShowDirtyHacks = Hacks.ShowDirtyHacks, - EnabledDirtyHacks = (int)Hacks.EnabledHacks, + DirtyHacks = Hacks.EnabledHacks.Select(it => it.Pack()).ToArray(), }; return configurationFile;