diff --git a/ARMeilleure/Instructions/InstEmitMemoryExHelper.cs b/ARMeilleure/Instructions/InstEmitMemoryExHelper.cs index 317e4276ee..15f5e2abc9 100644 --- a/ARMeilleure/Instructions/InstEmitMemoryExHelper.cs +++ b/ARMeilleure/Instructions/InstEmitMemoryExHelper.cs @@ -19,19 +19,8 @@ namespace ARMeilleure.Instructions if (size == 4) { - Operand isUnalignedAddr = InstEmitMemoryHelper.EmitAddressCheck(context, address, size); - - Operand lblFastPath = Label(); - - context.BranchIfFalse(lblFastPath, isUnalignedAddr); - - // The call is not expected to return (it should throw). - context.Call(typeof(NativeInterface).GetMethod(nameof(NativeInterface.ThrowInvalidMemoryAccess)), address); - - context.MarkLabel(lblFastPath); - // Only 128-bit CAS is guaranteed to have a atomic load. - Operand physAddr = InstEmitMemoryHelper.EmitPtPointerLoad(context, address, null, write: false); + Operand physAddr = InstEmitMemoryHelper.EmitPtPointerLoad(context, address, null, write: false, 4); Operand zero = context.VectorZero(); @@ -119,20 +108,8 @@ namespace ARMeilleure.Instructions context.BranchIfTrue(lblExit, exFailed); - // STEP 2: We have exclusive access, make sure that the address is valid. - Operand isUnalignedAddr = InstEmitMemoryHelper.EmitAddressCheck(context, address, size); - - Operand lblFastPath = Label(); - - context.BranchIfFalse(lblFastPath, isUnalignedAddr); - - // The call is not expected to return (it should throw). - context.Call(typeof(NativeInterface).GetMethod(nameof(NativeInterface.ThrowInvalidMemoryAccess)), address); - - // STEP 3: We have exclusive access and the address is valid, attempt the store using CAS. - context.MarkLabel(lblFastPath); - - Operand physAddr = InstEmitMemoryHelper.EmitPtPointerLoad(context, address, null, write: true); + // STEP 2: We have exclusive access and the address is valid, attempt the store using CAS. + Operand physAddr = InstEmitMemoryHelper.EmitPtPointerLoad(context, address, null, write: true, size); Operand exValuePtr = context.Add(arg0, Const((long)NativeContext.GetExclusiveValueOffset())); Operand exValue = size switch @@ -151,7 +128,7 @@ namespace ARMeilleure.Instructions _ => context.CompareAndSwap(physAddr, exValue, value) }; - // STEP 4: Check if we succeeded by comparing expected and in-memory values. + // STEP 3: Check if we succeeded by comparing expected and in-memory values. Operand storeFailed; if (size == 4) diff --git a/ARMeilleure/Instructions/InstEmitMemoryHelper.cs b/ARMeilleure/Instructions/InstEmitMemoryHelper.cs index fd5c5bca30..cb4fae8f9b 100644 --- a/ARMeilleure/Instructions/InstEmitMemoryHelper.cs +++ b/ARMeilleure/Instructions/InstEmitMemoryHelper.cs @@ -127,11 +127,7 @@ namespace ARMeilleure.Instructions Operand lblSlowPath = Label(); Operand lblEnd = Label(); - Operand isUnalignedAddr = EmitAddressCheck(context, address, size); - - context.BranchIfTrue(lblSlowPath, isUnalignedAddr); - - Operand physAddr = EmitPtPointerLoad(context, address, lblSlowPath, write: false); + Operand physAddr = EmitPtPointerLoad(context, address, lblSlowPath, write: false, size); Operand value = null; @@ -161,18 +157,7 @@ namespace ARMeilleure.Instructions throw new ArgumentOutOfRangeException(nameof(size)); } - Operand isUnalignedAddr = EmitAddressCheck(context, address, size); - - Operand lblFastPath = Label(); - - context.BranchIfFalse(lblFastPath, isUnalignedAddr, BasicBlockFrequency.Cold); - - // The call is not expected to return (it should throw). - context.Call(typeof(NativeInterface).GetMethod(nameof(NativeInterface.ThrowInvalidMemoryAccess)), address); - - context.MarkLabel(lblFastPath); - - Operand physAddr = EmitPtPointerLoad(context, address, null, write: false); + Operand physAddr = EmitPtPointerLoad(context, address, null, write: false, size); return size switch { @@ -195,11 +180,7 @@ namespace ARMeilleure.Instructions Operand lblSlowPath = Label(); Operand lblEnd = Label(); - Operand isUnalignedAddr = EmitAddressCheck(context, address, size); - - context.BranchIfTrue(lblSlowPath, isUnalignedAddr); - - Operand physAddr = EmitPtPointerLoad(context, address, lblSlowPath, write: false); + Operand physAddr = EmitPtPointerLoad(context, address, lblSlowPath, write: false, size); Operand value = null; @@ -233,11 +214,7 @@ namespace ARMeilleure.Instructions Operand lblSlowPath = Label(); Operand lblEnd = Label(); - Operand isUnalignedAddr = EmitAddressCheck(context, address, size); - - context.BranchIfTrue(lblSlowPath, isUnalignedAddr); - - Operand physAddr = EmitPtPointerLoad(context, address, lblSlowPath, write: true); + Operand physAddr = EmitPtPointerLoad(context, address, lblSlowPath, write: true, size); Operand value = GetInt(context, rt); @@ -270,18 +247,7 @@ namespace ARMeilleure.Instructions throw new ArgumentOutOfRangeException(nameof(size)); } - Operand isUnalignedAddr = EmitAddressCheck(context, address, size); - - Operand lblFastPath = Label(); - - context.BranchIfFalse(lblFastPath, isUnalignedAddr, BasicBlockFrequency.Cold); - - // The call is not expected to return (it should throw). - context.Call(typeof(NativeInterface).GetMethod(nameof(NativeInterface.ThrowInvalidMemoryAccess)), address); - - context.MarkLabel(lblFastPath); - - Operand physAddr = EmitPtPointerLoad(context, address, null, write: true); + Operand physAddr = EmitPtPointerLoad(context, address, null, write: true, size); if (size < 3 && value.Type == OperandType.I64) { @@ -312,11 +278,7 @@ namespace ARMeilleure.Instructions Operand lblSlowPath = Label(); Operand lblEnd = Label(); - Operand isUnalignedAddr = EmitAddressCheck(context, address, size); - - context.BranchIfTrue(lblSlowPath, isUnalignedAddr); - - Operand physAddr = EmitPtPointerLoad(context, address, lblSlowPath, write: true); + Operand physAddr = EmitPtPointerLoad(context, address, lblSlowPath, write: true, size); Operand value = GetVec(rt); @@ -338,61 +300,49 @@ namespace ARMeilleure.Instructions context.MarkLabel(lblEnd); } - public static Operand EmitAddressCheck(ArmEmitterContext context, Operand address, int size) + public static Operand EmitPtPointerLoad(ArmEmitterContext context, Operand address, Operand lblSlowPath, bool write, int size) { - ulong addressCheckMask = ~((1UL << context.Memory.AddressSpaceBits) - 1); - - addressCheckMask |= (1u << size) - 1; - - return context.BitwiseAnd(address, Const(address.Type, (long)addressCheckMask)); - } - - public static Operand EmitPtPointerLoad(ArmEmitterContext context, Operand address, Operand lblSlowPath, bool write) - { - int ptLevelBits = context.Memory.AddressSpaceBits - 12; // 12 = Number of page bits. + int ptLevelBits = context.Memory.AddressSpaceBits - PageBits; int ptLevelSize = 1 << ptLevelBits; int ptLevelMask = ptLevelSize - 1; + Operand addrRotated = size != 0 ? context.RotateRight(address, Const(size)) : address; + Operand addrShifted = context.ShiftRightUI(addrRotated, Const(PageBits - size)); + Operand pte = Ptc.State == PtcState.Disabled ? Const(context.Memory.PageTablePointer.ToInt64()) : Const(context.Memory.PageTablePointer.ToInt64(), true, Ptc.PageTablePointerIndex); - int bit = PageBits; + Operand pteOffset = context.BitwiseAnd(addrShifted, Const(addrShifted.Type, ptLevelMask)); - // Load page table entry from the page table. - // This was designed to support multi-level page tables of any size, however right - // now we only use flat page tables (so there's only one level). - // The page table entry contains the host address where the page is located. - // Additionally, the higher 16-bits of the host address may contain extra information - // used for write tracking, so this must be handled here aswell. - do + if (pteOffset.Type == OperandType.I32) { - Operand addrPart = context.ShiftRightUI(address, Const(bit)); - - bit += ptLevelBits; - - if (bit < context.Memory.AddressSpaceBits) - { - addrPart = context.BitwiseAnd(addrPart, Const(addrPart.Type, ptLevelMask)); - } - - Operand pteOffset = context.ShiftLeft(addrPart, Const(3)); - - if (pteOffset.Type == OperandType.I32) - { - pteOffset = context.ZeroExtend32(OperandType.I64, pteOffset); - } - - Operand pteAddress = context.Add(pte, pteOffset); - - pte = context.Load(OperandType.I64, pteAddress); + pteOffset = context.ZeroExtend32(OperandType.I64, pteOffset); } - while (bit < context.Memory.AddressSpaceBits); + + pte = context.Load(OperandType.I64, context.Add(pte, context.ShiftLeft(pteOffset, Const(3)))); + + if (addrShifted.Type == OperandType.I32) + { + addrShifted = context.ZeroExtend32(OperandType.I64, addrShifted); + } + + // If the VA is out of range, or not aligned to the access size, force PTE to 0 by masking it. + pte = context.BitwiseAnd(pte, context.ShiftRightSI(context.Add(addrShifted, Const(-(long)ptLevelSize)), Const(63))); if (lblSlowPath != null) { - ulong protection = (write ? 3UL : 1UL) << 48; - context.BranchIfTrue(lblSlowPath, context.BitwiseAnd(pte, Const(protection))); + if (write) + { + pte = context.ShiftLeft(pte, Const(1)); + context.BranchIf(lblSlowPath, pte, Const(0L), Comparison.LessOrEqual); + pte = context.ShiftRightUI(pte, Const(1)); + } + else + { + context.BranchIf(lblSlowPath, pte, Const(0L), Comparison.LessOrEqual); + pte = context.BitwiseAnd(pte, Const(0xffffffffffffUL)); // Ignore any software protection bits. (they are still used by C# memory access) + } } else { @@ -401,13 +351,15 @@ namespace ARMeilleure.Instructions Operand lblNotWatched = Label(); - // Is the page currently being tracked for read/write? If so we need to call MarkRegionAsModified. + // Is the page currently being tracked for read/write? If so we need to call SignalMemoryTracking. context.BranchIf(lblNotWatched, pte, Const(0L), Comparison.GreaterOrEqual, BasicBlockFrequency.Cold); - // Mark the region as modified. Size here doesn't matter as address is assumed to be size aligned here. + // Signal memory tracking. Size here doesn't matter as address is assumed to be size aligned here. context.Call(typeof(NativeInterface).GetMethod(nameof(NativeInterface.SignalMemoryTracking)), address, Const(1UL), Const(write ? 1 : 0)); context.MarkLabel(lblNotWatched); + pte = context.BitwiseAnd(pte, Const(0xffffffffffffUL)); // Ignore any software protection bits. (they are still used by C# memory access) + Operand lblNonNull = Label(); // Skip exception if the PTE address is non-null (not zero). @@ -418,8 +370,6 @@ namespace ARMeilleure.Instructions context.MarkLabel(lblNonNull); } - pte = context.BitwiseAnd(pte, Const(0xffffffffffffUL)); // Ignore any software protection bits. (they are still used by c# memory access) - Operand pageOffset = context.BitwiseAnd(address, Const(address.Type, PageMask)); if (pageOffset.Type == OperandType.I32) diff --git a/Ryujinx.Common/Configuration/ConfigurationFileFormat.cs b/Ryujinx.Common/Configuration/ConfigurationFileFormat.cs index 79993d879f..901c823e5a 100644 --- a/Ryujinx.Common/Configuration/ConfigurationFileFormat.cs +++ b/Ryujinx.Common/Configuration/ConfigurationFileFormat.cs @@ -14,7 +14,7 @@ namespace Ryujinx.Configuration /// /// The current version of the file format /// - public const int CurrentVersion = 20; + public const int CurrentVersion = 22; public int Version { get; set; } @@ -133,6 +133,11 @@ namespace Ryujinx.Configuration /// public bool ShowConfirmExit { get; set; } + /// + /// Hide Cursor on Idle + /// + public bool HideCursorOnIdle { get; set; } + /// /// Enables or disables Vertical Sync /// @@ -253,4 +258,4 @@ namespace Ryujinx.Configuration JsonHelper.Serialize(fileStream, this, true); } } -} \ No newline at end of file +} diff --git a/Ryujinx.Common/Configuration/ConfigurationState.cs b/Ryujinx.Common/Configuration/ConfigurationState.cs index 61cc8f899e..d51ee9efc6 100644 --- a/Ryujinx.Common/Configuration/ConfigurationState.cs +++ b/Ryujinx.Common/Configuration/ConfigurationState.cs @@ -365,6 +365,11 @@ namespace Ryujinx.Configuration /// public ReactiveObject ShowConfirmExit { get; private set; } + /// + /// Hide Cursor on Idle + /// + public ReactiveObject HideCursorOnIdle { get; private set; } + private ConfigurationState() { Ui = new UiSection(); @@ -375,6 +380,7 @@ namespace Ryujinx.Configuration EnableDiscordIntegration = new ReactiveObject(); CheckUpdatesOnStart = new ReactiveObject(); ShowConfirmExit = new ReactiveObject(); + HideCursorOnIdle = new ReactiveObject(); } public ConfigurationFileFormat ToFileFormat() @@ -420,6 +426,7 @@ namespace Ryujinx.Configuration EnableDiscordIntegration = EnableDiscordIntegration, CheckUpdatesOnStart = CheckUpdatesOnStart, ShowConfirmExit = ShowConfirmExit, + HideCursorOnIdle = HideCursorOnIdle, EnableVsync = Graphics.EnableVsync, EnableShaderCache = Graphics.EnableShaderCache, EnablePtc = System.EnablePtc, @@ -483,6 +490,7 @@ namespace Ryujinx.Configuration EnableDiscordIntegration.Value = true; CheckUpdatesOnStart.Value = true; ShowConfirmExit.Value = true; + HideCursorOnIdle.Value = false; Graphics.EnableVsync.Value = true; Graphics.EnableShaderCache.Value = true; System.EnablePtc.Value = true; @@ -787,6 +795,15 @@ namespace Ryujinx.Configuration configurationFileUpdated = true; } + if (configurationFileFormat.Version < 22) + { + Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 22."); + + configurationFileFormat.HideCursorOnIdle = false; + + configurationFileUpdated = true; + } + List inputConfig = new List(); inputConfig.AddRange(configurationFileFormat.ControllerConfig); inputConfig.AddRange(configurationFileFormat.KeyboardConfig); @@ -814,6 +831,7 @@ namespace Ryujinx.Configuration EnableDiscordIntegration.Value = configurationFileFormat.EnableDiscordIntegration; CheckUpdatesOnStart.Value = configurationFileFormat.CheckUpdatesOnStart; ShowConfirmExit.Value = configurationFileFormat.ShowConfirmExit; + HideCursorOnIdle.Value = configurationFileFormat.HideCursorOnIdle; Graphics.EnableVsync.Value = configurationFileFormat.EnableVsync; Graphics.EnableShaderCache.Value = configurationFileFormat.EnableShaderCache; System.EnablePtc.Value = configurationFileFormat.EnablePtc; diff --git a/Ryujinx.Cpu/MemoryManager.cs b/Ryujinx.Cpu/MemoryManager.cs index cef2012656..8c8bd3a4c2 100644 --- a/Ryujinx.Cpu/MemoryManager.cs +++ b/Ryujinx.Cpu/MemoryManager.cs @@ -21,6 +21,8 @@ namespace Ryujinx.Cpu private const int PteSize = 8; + private const int PointerTagBit = 62; + private readonly InvalidAccessHandler _invalidAccessHandler; /// @@ -556,11 +558,12 @@ namespace Ryujinx.Cpu // Protection is inverted on software pages, since the default value is 0. protection = (~protection) & MemoryPermission.ReadAndWrite; - long tag = (long)protection << 48; - if (tag > 0) + long tag = protection switch { - tag |= long.MinValue; // If any protection is present, the whole pte is negative. - } + MemoryPermission.None => 0L, + MemoryPermission.Read => 2L << PointerTagBit, + _ => 3L << PointerTagBit + }; ulong endVa = (va + size + PageMask) & ~(ulong)PageMask; long invTagMask = ~(0xffffL << 48); @@ -628,7 +631,7 @@ namespace Ryujinx.Cpu // tracking using host guard pages in future, but also supporting platforms where this is not possible. // Write tag includes read protection, since we don't have any read actions that aren't performed before write too. - long tag = (write ? 3L : 1L) << 48; + long tag = (write ? 3L : 2L) << PointerTagBit; ulong endVa = (va + size + PageMask) & ~(ulong)PageMask; diff --git a/Ryujinx/Config.json b/Ryujinx/Config.json index 76c0a139aa..804b020c4d 100644 --- a/Ryujinx/Config.json +++ b/Ryujinx/Config.json @@ -1,5 +1,5 @@ { - "version": 20, + "version": 22, "res_scale": 1, "res_scale_custom": 1, "max_anisotropy": -1, @@ -22,6 +22,7 @@ "enable_discord_integration": true, "check_updates_on_start": true, "show_confirm_exit": true, + "hide_cursor_on_idle": false, "enable_vsync": true, "enable_shader_cache": true, "enable_ptc": true, diff --git a/Ryujinx/Ui/GLRenderer.cs b/Ryujinx/Ui/GLRenderer.cs index 35c00a332f..52dcedbc9a 100644 --- a/Ryujinx/Ui/GLRenderer.cs +++ b/Ryujinx/Ui/GLRenderer.cs @@ -42,6 +42,8 @@ namespace Ryujinx.Ui private double _mouseY; private bool _mousePressed; + private DateTime _lastCursorMoveTime = DateTime.Now; + private bool _toggleFullscreen; private bool _toggleDockedMode; @@ -62,6 +64,8 @@ namespace Ryujinx.Ui private GraphicsDebugLevel _glLogLevel; private readonly ManualResetEvent _exitEvent; + + private Gdk.Cursor _invisibleCursor = new Gdk.Cursor (Gdk.Display.Default, Gdk.CursorType.BlankCursor); public GlRenderer(Switch device, GraphicsDebugLevel glLogLevel) : base (GetGraphicsMode(), @@ -304,9 +308,37 @@ namespace Ryujinx.Ui _mouseY = evnt.Y; } + ResetCursorIdle(); + return false; } + private void ResetCursorIdle() + { + if (ConfigurationState.Instance.HideCursorOnIdle) + { + _lastCursorMoveTime = DateTime.Now; + } + + if (Window.Cursor != null) + { + Window.Cursor = null; + } + } + + private void HideCursorIdle() + { + if (ConfigurationState.Instance.HideCursorOnIdle) + { + TimeSpan elapsedTime = DateTime.Now.Subtract(_lastCursorMoveTime); + + if (elapsedTime.TotalSeconds > 8) + { + Gtk.Application.Invoke(delegate { Window.Cursor = _invisibleCursor; }); + } + } + } + protected override void OnGetPreferredHeight(out int minimumHeight, out int naturalHeight) { Gdk.Monitor monitor = Display.GetMonitorAtWindow(Window); @@ -485,6 +517,8 @@ namespace Ryujinx.Ui MotionDevice motionDevice = new MotionDevice(_dsuClient); + HideCursorIdle(); + foreach (InputConfig inputConfig in ConfigurationState.Instance.Hid.InputConfig.Value) { ControllerKeys currentButton = 0; diff --git a/Ryujinx/Ui/Windows/SettingsWindow.cs b/Ryujinx/Ui/Windows/SettingsWindow.cs index ba64226c37..e839a366c6 100644 --- a/Ryujinx/Ui/Windows/SettingsWindow.cs +++ b/Ryujinx/Ui/Windows/SettingsWindow.cs @@ -43,6 +43,7 @@ namespace Ryujinx.Ui.Windows [GUI] CheckButton _discordToggle; [GUI] CheckButton _checkUpdatesToggle; [GUI] CheckButton _showConfirmExitToggle; + [GUI] CheckButton _hideCursorOnIdleToggle; [GUI] CheckButton _vSyncToggle; [GUI] CheckButton _shaderCacheToggle; [GUI] CheckButton _ptcToggle; @@ -185,6 +186,11 @@ namespace Ryujinx.Ui.Windows _showConfirmExitToggle.Click(); } + if (ConfigurationState.Instance.HideCursorOnIdle) + { + _hideCursorOnIdleToggle.Click(); + } + if (ConfigurationState.Instance.Graphics.EnableVsync) { _vSyncToggle.Click(); @@ -403,6 +409,7 @@ namespace Ryujinx.Ui.Windows ConfigurationState.Instance.EnableDiscordIntegration.Value = _discordToggle.Active; ConfigurationState.Instance.CheckUpdatesOnStart.Value = _checkUpdatesToggle.Active; ConfigurationState.Instance.ShowConfirmExit.Value = _showConfirmExitToggle.Active; + ConfigurationState.Instance.HideCursorOnIdle.Value = _hideCursorOnIdleToggle.Active; ConfigurationState.Instance.Graphics.EnableVsync.Value = _vSyncToggle.Active; ConfigurationState.Instance.Graphics.EnableShaderCache.Value = _shaderCacheToggle.Active; ConfigurationState.Instance.System.EnablePtc.Value = _ptcToggle.Active; @@ -582,4 +589,4 @@ namespace Ryujinx.Ui.Windows Dispose(); } } -} \ No newline at end of file +} diff --git a/Ryujinx/Ui/Windows/SettingsWindow.glade b/Ryujinx/Ui/Windows/SettingsWindow.glade index 5ead8dd820..e9d241f80b 100644 --- a/Ryujinx/Ui/Windows/SettingsWindow.glade +++ b/Ryujinx/Ui/Windows/SettingsWindow.glade @@ -150,7 +150,23 @@ False True 5 - 1 + 2 + + + + + Hide Cursor On Idle + True + True + False + start + True + + + False + True + 5 + 3 diff --git a/Ryujinx/_schema.json b/Ryujinx/_schema.json index eff1f9d4cf..b61e9ed259 100644 --- a/Ryujinx/_schema.json +++ b/Ryujinx/_schema.json @@ -1209,6 +1209,17 @@ true, false ] + }, + "hide_cursor_on_idle": { + "$id": "#/properties/hide_cursor_on_idle", + "type": "boolean", + "title": "Hide Cursor On Idle", + "description": "Hides the cursor after being idle for 5 seconds", + "default": false, + "examples": [ + true, + false + ] }, "enable_vsync": { "$id": "#/properties/enable_vsync",