Quellcode durchsuchen

misc: Overhaul DirtyHacks saving to support storing a value alongside an off/off flag.

Evan Husted vor 1 Jahr
Ursprung
Commit
f362bef43d

+ 35 - 0
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;
+        }
+    }
+}

+ 44 - 3
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<DirtyHacks, int>
+    {
+        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);
     }
 }

+ 3 - 3
src/Ryujinx.HLE/HLEConfiguration.cs

@@ -192,7 +192,7 @@ namespace Ryujinx.HLE
         /// <summary>
         ///     The desired hacky workarounds.
         /// </summary>
-        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 ?? [];
         }
     }
 }

+ 1 - 1
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

+ 2 - 2
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
 

+ 1 - 1
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()

+ 0 - 3
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;

+ 32 - 0
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.");
+        });
     }
 }

+ 27 - 0
src/Ryujinx/UI/Views/Settings/SettingsHacksView.axaml

@@ -42,6 +42,33 @@
                         VerticalAlignment="Center"
                         Text="Xenoblade Chronicles 2 Menu Softlock Fix" />
                 </StackPanel>
+                <Separator/>
+                <StackPanel
+                    Margin="0,10,0,0"
+                    Orientation="Horizontal"
+                    HorizontalAlignment="Center"
+                    ToolTip.Tip="{Binding ShaderTranslationDelayTooltip}">
+                    <CheckBox
+                        Margin="0"
+                        IsChecked="{Binding ShaderTranslationDelayEnabled}"/>
+                    <TextBlock VerticalAlignment="Center"
+                               Text="Arbitrary Delay on Shader Translation"/>
+                </StackPanel>
+                <Slider HorizontalAlignment="Center" 
+                        Value="{Binding ShaderTranslationDelay}"
+                        ToolTip.Tip="{Binding ShaderTranslationDelay}"
+                        Width="175"
+                        Margin="0,-3,0,0"
+                        Height="32"
+                        Padding="0,-5"
+                        TickFrequency="1"
+                        IsSnapToTickEnabled="True"
+                        LargeChange="10"
+                        SmallChange="1"
+                        VerticalAlignment="Center"
+                        Minimum="1"
+                        Maximum="1000" />
+                <Separator/>
             </StackPanel>
         </Border>
     </ScrollViewer>

+ 3 - 3
src/Ryujinx/Utilities/Configuration/ConfigurationFileFormat.cs

@@ -17,7 +17,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
         /// <summary>
         /// The current version of the file format
         /// </summary>
-        public const int CurrentVersion = 58;
+        public const int CurrentVersion = 59;
 
         /// <summary>
         /// Version of the configuration file format
@@ -436,9 +436,9 @@ namespace Ryujinx.Ava.Utilities.Configuration
         public bool ShowDirtyHacks { get; set; }
         
         /// <summary>
-        ///     The packed value of the enabled dirty hacks.
+        ///     The packed values of the enabled dirty hacks.
         /// </summary>
-        public int EnabledDirtyHacks { get; set; }
+        public ulong[] DirtyHacks { get; set; }
         
         /// <summary>
         /// Loads a configuration file from disk

+ 23 - 1
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)
             {

+ 17 - 9
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<bool> ShowDirtyHacks { get; private set; }
             
             public ReactiveObject<bool> Xc2MenuSoftlockFix { get; private set; }
+            
+            public ReactiveObject<bool> EnableShaderCompilationThreadSleep { get; private set; }
+            
+            public ReactiveObject<int> ShaderCompilationThreadSleepDelay { get; private set; }
 
             public HacksSection()
             {
                 ShowDirtyHacks = new ReactiveObject<bool>();
                 Xc2MenuSoftlockFix = new ReactiveObject<bool>();
                 Xc2MenuSoftlockFix.Event += HackChanged;
+                EnableShaderCompilationThreadSleep = new ReactiveObject<bool>();
+                EnableShaderCompilationThreadSleep.Event += HackChanged;
+                ShaderCompilationThreadSleepDelay = new ReactiveObject<int>();
             }
 
             private void HackChanged(object sender, ReactiveEventArgs<bool> 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<EnabledDirtyHack> 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));
                     }
                 }
             }

+ 2 - 1
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;