From 92b61f9d7305fd543a1e96b918512f6a9f423318 Mon Sep 17 00:00:00 2001 From: LotP <22-lotp@users.noreply.git.ryujinx.app> Date: Thu, 30 Oct 2025 20:55:58 -0500 Subject: [PATCH 1/2] Memory changes 3 (ryubing/ryujinx!202) See merge request ryubing/ryujinx!202 --- src/ARMeilleure/CodeGen/X86/Assembler.cs | 6 +- .../Renderer/Dsp/Command/DeviceSinkCommand.cs | 11 +- .../Renderer/Server/Voice/VoiceInfo.cs | 16 +- .../Collections/IntervalTree.cs | 15 +- .../Commands/SetRenderTargetsCommand.cs | 16 +- .../Multithreading/ThreadedPipeline.cs | 5 +- .../Engine/Threed/DrawManager.cs | 2 +- .../Engine/Threed/StateUpdater.cs | 8 +- src/Ryujinx.Graphics.Gpu/GpuContext.cs | 29 ++- src/Ryujinx.Graphics.Gpu/Image/Texture.cs | 10 +- .../Image/TextureGroup.cs | 136 +++++++---- src/Ryujinx.Graphics.Gpu/Memory/Buffer.cs | 30 ++- .../Memory/BufferModifiedRangeList.cs | 24 +- .../Shader/ShaderSpecializationState.cs | 14 +- src/Ryujinx.Graphics.Vulkan/BufferHolder.cs | 6 +- .../FramebufferParams.cs | 214 +++++++++++++++--- .../MultiFenceHolder.cs | 21 +- src/Ryujinx.Graphics.Vulkan/PipelineBase.cs | 8 +- .../RenderPassHolder.cs | 15 +- src/Ryujinx.Graphics.Vulkan/SyncManager.cs | 2 + .../Kernel/Common/KSynchronizationObject.cs | 9 +- .../HOS/Kernel/Memory/KMemoryBlock.cs | 17 +- .../HOS/Kernel/Memory/KMemoryInfo.cs | 24 +- .../HOS/Kernel/Memory/KMemoryPermission.cs | 6 +- .../HOS/Kernel/Memory/KPageTableBase.cs | 36 ++- .../HOS/Kernel/Process/HleProcessDebugger.cs | 2 + .../HOS/Kernel/Threading/KAddressArbiter.cs | 60 ++++- .../HOS/Kernel/Threading/KThread.cs | 18 +- .../HOS/Services/Nv/INvDrvServices.cs | 37 ++- .../Sdk/Sf/HipcCommandProcessor.cs | 42 ++-- src/Ryujinx.Input.SDL2/SDL2Gamepad.cs | 10 +- src/Ryujinx.Input.SDL2/SDL2JoyCon.cs | 6 +- src/Ryujinx.Input/HLE/NpadController.cs | 5 +- src/Ryujinx.Input/HLE/NpadManager.cs | 9 +- src/Ryujinx.Input/IKeyboard.cs | 4 +- src/Ryujinx.Input/IMouse.cs | 3 +- src/Ryujinx.Input/KeyboardStateSnapshot.cs | 6 +- src/Ryujinx.Input/MouseStateSnapshot.cs | 6 +- .../Range/NonOverlappingRangeList.cs | 13 +- src/Ryujinx.Memory/Range/RangeList.cs | 37 --- src/Ryujinx.Memory/Range/RangeListBase.cs | 47 ++-- src/Ryujinx.Memory/Tracking/MemoryTracking.cs | 14 +- .../UI/ViewModels/Input/InputViewModel.cs | 2 +- 43 files changed, 686 insertions(+), 315 deletions(-) diff --git a/src/ARMeilleure/CodeGen/X86/Assembler.cs b/src/ARMeilleure/CodeGen/X86/Assembler.cs index c27ee43cb..5a8312806 100644 --- a/src/ARMeilleure/CodeGen/X86/Assembler.cs +++ b/src/ARMeilleure/CodeGen/X86/Assembler.cs @@ -1107,17 +1107,17 @@ namespace ARMeilleure.CodeGen.X86 } else { - if (flags.HasFlag(InstructionFlags.Prefix66)) + if ((flags & InstructionFlags.Prefix66) != 0) { WriteByte(0x66); } - if (flags.HasFlag(InstructionFlags.PrefixF2)) + if ((flags & InstructionFlags.PrefixF2) != 0f) { WriteByte(0xf2); } - if (flags.HasFlag(InstructionFlags.PrefixF3)) + if ((flags & InstructionFlags.PrefixF3) != 0f) { WriteByte(0xf3); } diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/DeviceSinkCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/DeviceSinkCommand.cs index aeb187b41..08968fa53 100644 --- a/src/Ryujinx.Audio/Renderer/Dsp/Command/DeviceSinkCommand.cs +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/DeviceSinkCommand.cs @@ -1,6 +1,7 @@ using Ryujinx.Audio.Integration; using Ryujinx.Audio.Renderer.Server.Sink; using System; +using System.Buffers; using System.Runtime.CompilerServices; using System.Text; @@ -35,7 +36,9 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command Enabled = true; NodeId = nodeId; - DeviceName = Encoding.ASCII.GetString(sink.Parameter.DeviceName).TrimEnd('\0'); + // Unused and wasting time and memory, re-add if needed + // DeviceName = Encoding.ASCII.GetString(sink.Parameter.DeviceName).TrimEnd('\0'); + SessionId = sessionId; InputCount = sink.Parameter.InputCount; InputBufferIndices = new ushort[InputCount]; @@ -88,7 +91,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command inputCount = bufferCount; } - short[] outputBuffer = new short[inputCount * SampleCount]; + short[] outputBuffer = ArrayPool.Shared.Rent((int)inputCount * SampleCount); for (int i = 0; i < bufferCount; i++) { @@ -100,7 +103,9 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command } } - device.AppendBuffer(outputBuffer, inputCount); + device.AppendBuffer(outputBuffer.AsSpan(..((int)inputCount * SampleCount)), inputCount); + + ArrayPool.Shared.Return(outputBuffer); } else { diff --git a/src/Ryujinx.Audio/Renderer/Server/Voice/VoiceInfo.cs b/src/Ryujinx.Audio/Renderer/Server/Voice/VoiceInfo.cs index 558a66baa..666e2a9e6 100644 --- a/src/Ryujinx.Audio/Renderer/Server/Voice/VoiceInfo.cs +++ b/src/Ryujinx.Audio/Renderer/Server/Voice/VoiceInfo.cs @@ -188,6 +188,8 @@ namespace Ryujinx.Audio.Renderer.Server.Voice /// public Span BiquadFilterNeedInitialization => SpanHelpers.AsSpan(ref _biquadFilterNeedInitialization); + private static List _waveBufferUpdaterErrorInfosList; + /// /// Initialize the . /// @@ -216,6 +218,8 @@ namespace Ryujinx.Audio.Renderer.Server.Voice DataSourceStateAddressInfo.Setup(0, 0); InitializeWaveBuffers(); + + _waveBufferUpdaterErrorInfosList ??= []; } /// @@ -587,14 +591,14 @@ namespace Ryujinx.Audio.Renderer.Server.Voice Span waveBuffersSpan = WaveBuffers.AsSpan(); Span pWaveBuffersSpan = parameter.WaveBuffers.AsSpan(); - List errorInfosList = []; + _waveBufferUpdaterErrorInfosList.Clear(); for (int i = 0; i < Constants.VoiceWaveBufferCount; i++) { - UpdateWaveBuffer(errorInfosList, ref waveBuffersSpan[i], ref pWaveBuffersSpan[i], parameter.SampleFormat, voiceState.IsWaveBufferValid[i], mapper, ref behaviourInfo); + UpdateWaveBuffer(_waveBufferUpdaterErrorInfosList, ref waveBuffersSpan[i], ref pWaveBuffersSpan[i], parameter.SampleFormat, voiceState.IsWaveBufferValid[i], mapper, ref behaviourInfo); } - errorInfos = errorInfosList.ToArray(); + errorInfos = _waveBufferUpdaterErrorInfosList.ToArray(); } /// @@ -628,14 +632,14 @@ namespace Ryujinx.Audio.Renderer.Server.Voice Span waveBuffersSpan = WaveBuffers.AsSpan(); Span pWaveBuffersSpan = parameter.WaveBuffers.AsSpan(); - List errorInfosList = []; + _waveBufferUpdaterErrorInfosList.Clear(); for (int i = 0; i < Constants.VoiceWaveBufferCount; i++) { - UpdateWaveBuffer(errorInfosList, ref waveBuffersSpan[i], ref pWaveBuffersSpan[i], parameter.SampleFormat, voiceState.IsWaveBufferValid[i], mapper, ref behaviourInfo); + UpdateWaveBuffer(_waveBufferUpdaterErrorInfosList, ref waveBuffersSpan[i], ref pWaveBuffersSpan[i], parameter.SampleFormat, voiceState.IsWaveBufferValid[i], mapper, ref behaviourInfo); } - errorInfos = errorInfosList.ToArray(); + errorInfos = _waveBufferUpdaterErrorInfosList.ToArray(); } /// diff --git a/src/Ryujinx.Common/Collections/IntervalTree.cs b/src/Ryujinx.Common/Collections/IntervalTree.cs index 27503c5b7..a094a5a61 100644 --- a/src/Ryujinx.Common/Collections/IntervalTree.cs +++ b/src/Ryujinx.Common/Collections/IntervalTree.cs @@ -24,7 +24,10 @@ namespace Ryujinx.Common.Collections /// is null public int Get(TKey key, ref TValue[] overlaps) { - ArgumentNullException.ThrowIfNull(key); + if (!typeof(TKey).IsValueType) + { + ArgumentNullException.ThrowIfNull(key); + } IntervalTreeNode node = GetNode(key); @@ -91,7 +94,10 @@ namespace Ryujinx.Common.Collections /// Number of deleted values public int Remove(TKey key, TValue value) { - ArgumentNullException.ThrowIfNull(key); + if (!typeof(TKey).IsValueType) + { + ArgumentNullException.ThrowIfNull(key); + } int removed = Delete(key, value); @@ -144,7 +150,10 @@ namespace Ryujinx.Common.Collections /// is null private IntervalTreeNode GetNode(TKey key) { - ArgumentNullException.ThrowIfNull(key); + if (!typeof(TKey).IsValueType) + { + ArgumentNullException.ThrowIfNull(key); + } IntervalTreeNode node = Root; while (node != null) diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetRenderTargetsCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetRenderTargetsCommand.cs index 063cf1e03..ca7c8c8c2 100644 --- a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetRenderTargetsCommand.cs +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetRenderTargetsCommand.cs @@ -1,11 +1,12 @@ using Ryujinx.Graphics.GAL.Multithreading.Model; using Ryujinx.Graphics.GAL.Multithreading.Resources; -using System.Linq; +using System.Buffers; namespace Ryujinx.Graphics.GAL.Multithreading.Commands { struct SetRenderTargetsCommand : IGALCommand, IGALCommand { + public static readonly ArrayPool ArrayPool = ArrayPool.Create(512, 50); public readonly CommandType CommandType => CommandType.SetRenderTargets; private TableRef _colors; private TableRef _depthStencil; @@ -18,7 +19,18 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands public static void Run(ref SetRenderTargetsCommand command, ThreadedRenderer threaded, IRenderer renderer) { - renderer.Pipeline.SetRenderTargets(command._colors.Get(threaded).Select(color => ((ThreadedTexture)color)?.Base).ToArray(), command._depthStencil.GetAs(threaded)?.Base); + ITexture[] colors = command._colors.Get(threaded); + ITexture[] colorsCopy = ArrayPool.Rent(colors.Length); + + for (int i = 0; i < colors.Length; i++) + { + colorsCopy[i] = ((ThreadedTexture)colors[i])?.Base; + } + + renderer.Pipeline.SetRenderTargets(colorsCopy, command._depthStencil.GetAs(threaded)?.Base); + + ArrayPool.Return(colorsCopy); + ArrayPool.Return(colors); } } } diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs b/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs index c0fb6361e..ea3fd1e11 100644 --- a/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs +++ b/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs @@ -269,7 +269,10 @@ namespace Ryujinx.Graphics.GAL.Multithreading public unsafe void SetRenderTargets(ITexture[] colors, ITexture depthStencil) { - _renderer.New()->Set(Ref(colors.ToArray()), Ref(depthStencil)); + ITexture[] colorsCopy = SetRenderTargetsCommand.ArrayPool.Rent(colors.Length); + colors.CopyTo(colorsCopy, 0); + + _renderer.New()->Set(Ref(colorsCopy), Ref(depthStencil)); _renderer.QueueCommand(); } diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Threed/DrawManager.cs b/src/Ryujinx.Graphics.Gpu/Engine/Threed/DrawManager.cs index c9f3e8bad..f97f80251 100644 --- a/src/Ryujinx.Graphics.Gpu/Engine/Threed/DrawManager.cs +++ b/src/Ryujinx.Graphics.Gpu/Engine/Threed/DrawManager.cs @@ -451,7 +451,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed // TODO: Confirm behaviour on hardware. // When this is active, the origin appears to be on the bottom. - if (_state.State.YControl.HasFlag(YControl.NegateY)) + if ((_state.State.YControl & YControl.NegateY) != 0) { dstY0 -= dstHeight; } diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs b/src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs index 7bcf16ae7..fe57c05ee 100644 --- a/src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs +++ b/src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs @@ -646,7 +646,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed int width = scissor.X2 - x; int height = scissor.Y2 - y; - if (_state.State.YControl.HasFlag(YControl.NegateY)) + if ((_state.State.YControl & YControl.NegateY) != 0) { ref ScreenScissorState screenScissor = ref _state.State.ScreenScissorState; y = screenScissor.Height - height - y; @@ -730,7 +730,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed FaceState face = _state.State.FaceState; bool disableTransform = _state.State.ViewportTransformEnable == 0; - bool yNegate = yControl.HasFlag(YControl.NegateY); + bool yNegate = (yControl & YControl.NegateY) != 0; UpdateFrontFace(yControl, face.FrontFace); UpdateDepthMode(); @@ -1230,7 +1230,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed /// Front face private void UpdateFrontFace(YControl yControl, FrontFace frontFace) { - bool isUpperLeftOrigin = !yControl.HasFlag(YControl.TriangleRastFlip); + bool isUpperLeftOrigin = (yControl & YControl.TriangleRastFlip) == 0; if (isUpperLeftOrigin) { @@ -1521,7 +1521,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed { // Make sure we update the viewport size on the support buffer if it will be consumed on the new shader. - if (!_fsReadsFragCoord && _state.State.YControl.HasFlag(YControl.NegateY)) + if (!_fsReadsFragCoord && (_state.State.YControl & YControl.NegateY) != 0) { UpdateSupportBufferViewportSize(); } diff --git a/src/Ryujinx.Graphics.Gpu/GpuContext.cs b/src/Ryujinx.Graphics.Gpu/GpuContext.cs index d0b8277da..8b1277c47 100644 --- a/src/Ryujinx.Graphics.Gpu/GpuContext.cs +++ b/src/Ryujinx.Graphics.Gpu/GpuContext.cs @@ -381,9 +381,9 @@ namespace Ryujinx.Graphics.Gpu /// Modifiers for how host sync should be created internal void CreateHostSyncIfNeeded(HostSyncFlags flags) { - bool syncpoint = flags.HasFlag(HostSyncFlags.Syncpoint); - bool strict = flags.HasFlag(HostSyncFlags.Strict); - bool force = flags.HasFlag(HostSyncFlags.Force); + bool syncPoint = (flags & HostSyncFlags.Syncpoint) == HostSyncFlags.Syncpoint; + bool strict = (flags & HostSyncFlags.Strict) == HostSyncFlags.Strict; + bool force = (flags & HostSyncFlags.Force) == HostSyncFlags.Force; if (BufferMigrations.Count > 0) { @@ -402,24 +402,37 @@ namespace Ryujinx.Graphics.Gpu } } - if (force || _pendingSync || (syncpoint && SyncpointActions.Count > 0)) + if (force || _pendingSync || (syncPoint && SyncpointActions.Count > 0)) { foreach (ISyncActionHandler action in SyncActions) { - action.SyncPreAction(syncpoint); + action.SyncPreAction(syncPoint); } foreach (ISyncActionHandler action in SyncpointActions) { - action.SyncPreAction(syncpoint); + action.SyncPreAction(syncPoint); } Renderer.CreateSync(SyncNumber, strict); SyncNumber++; - SyncActions.RemoveAll(action => action.SyncAction(syncpoint)); - SyncpointActions.RemoveAll(action => action.SyncAction(syncpoint)); + for (int i = 0; i < SyncActions.Count; i++) + { + if (SyncActions[i].SyncAction(syncPoint)) + { + SyncActions.RemoveAt(i--); + } + } + + for (int i = 0; i < SyncpointActions.Count; i++) + { + if (SyncpointActions[i].SyncAction(syncPoint)) + { + SyncpointActions.RemoveAt(i--); + } + } } _pendingSync = false; diff --git a/src/Ryujinx.Graphics.Gpu/Image/Texture.cs b/src/Ryujinx.Graphics.Gpu/Image/Texture.cs index 9540df548..35fc38d0b 100644 --- a/src/Ryujinx.Graphics.Gpu/Image/Texture.cs +++ b/src/Ryujinx.Graphics.Gpu/Image/Texture.cs @@ -1628,7 +1628,15 @@ namespace Ryujinx.Graphics.Gpu.Image { lock (_poolOwners) { - int references = _poolOwners.RemoveAll(entry => entry.Pool == pool && entry.ID == id || id == -1); + int references = 0; + for (int i = 0; i < _poolOwners.Count; i++) + { + if (_poolOwners[i].Pool == pool && _poolOwners[i].ID == id || id == -1) + { + _poolOwners.RemoveAt(i--); + references++; + } + } if (references == 0) { diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs index 7f38df129..8865c46cc 100644 --- a/src/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs +++ b/src/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs @@ -45,7 +45,11 @@ namespace Ryujinx.Graphics.Gpu.Image /// private const int GranularLayerThreshold = 8; - private delegate void HandlesCallbackDelegate(int baseHandle, int regionCount, bool split = false); + private delegate bool HandlesCallbackDelegate(int baseHandle, int regionCount, bool split = false, bool specialData = false); + + private readonly HandlesCallbackDelegate _signalModifyingCallback; + private readonly HandlesCallbackDelegate _discardDataCallback; + private readonly HandlesCallbackDelegate _checkDirtyCallback; /// /// The storage texture associated with this group. @@ -126,6 +130,10 @@ namespace Ryujinx.Graphics.Gpu.Image _incompatibleOverlaps = incompatibleOverlaps; _flushIncompatibleOverlaps = TextureCompatibility.IsFormatHostIncompatible(storage.Info, context.Capabilities); + + _signalModifyingCallback = SignalModifyingCallback; + _discardDataCallback = DiscardDataCallback; + _checkDirtyCallback = CheckDirtyCallback; } /// @@ -253,29 +261,33 @@ namespace Ryujinx.Graphics.Gpu.Image /// True to consume the dirty flags and reprotect, false to leave them as is /// True if a flag was dirty, false otherwise public bool CheckDirty(Texture texture, bool consume) + { + EvaluateRelevantHandles(texture, _checkDirtyCallback, out bool dirty, consume); + + return dirty; + } + + bool CheckDirtyCallback(int baseHandle, int regionCount, bool split, bool consume) { bool dirty = false; - - EvaluateRelevantHandles(texture, (baseHandle, regionCount, split) => + + for (int i = 0; i < regionCount; i++) { - for (int i = 0; i < regionCount; i++) + TextureGroupHandle group = _handles[baseHandle + i]; + + foreach (RegionHandle handle in group.Handles) { - TextureGroupHandle group = _handles[baseHandle + i]; - - foreach (RegionHandle handle in group.Handles) + if (handle.Dirty) { - if (handle.Dirty) + if (consume) { - if (consume) - { - handle.Reprotect(); - } - - dirty = true; + handle.Reprotect(); } + + dirty = true; } } - }); + } return dirty; } @@ -287,15 +299,19 @@ namespace Ryujinx.Graphics.Gpu.Image /// The texture being discarded public void DiscardData(Texture texture) { - EvaluateRelevantHandles(texture, (baseHandle, regionCount, split) => + EvaluateRelevantHandles(texture, _discardDataCallback, out _); + } + + bool DiscardDataCallback(int baseHandle, int regionCount, bool split, bool bound) + { + for (int i = 0; i < regionCount; i++) { - for (int i = 0; i < regionCount; i++) - { - TextureGroupHandle group = _handles[baseHandle + i]; + TextureGroupHandle group = _handles[baseHandle + i]; - group.DiscardData(); - } - }); + group.DiscardData(); + } + + return true; } /// @@ -307,7 +323,7 @@ namespace Ryujinx.Graphics.Gpu.Image { FlushIncompatibleOverlapsIfNeeded(); - EvaluateRelevantHandles(texture, (baseHandle, regionCount, split) => + EvaluateRelevantHandles(texture, (baseHandle, regionCount, split, bound) => { bool dirty = false; bool anyModified = false; @@ -383,7 +399,9 @@ namespace Ryujinx.Graphics.Gpu.Image texture.SynchronizeFull(); } } - }); + + return true; + }, out _); } /// @@ -460,7 +478,7 @@ namespace Ryujinx.Graphics.Gpu.Image /// The texture to synchronize dependents of public void SynchronizeDependents(Texture texture) { - EvaluateRelevantHandles(texture, (baseHandle, regionCount, split) => + EvaluateRelevantHandles(texture, (baseHandle, regionCount, split, bound) => { for (int i = 0; i < regionCount; i++) { @@ -468,7 +486,9 @@ namespace Ryujinx.Graphics.Gpu.Image group.SynchronizeDependents(); } - }); + + return true; + }, out _); } /// @@ -550,7 +570,7 @@ namespace Ryujinx.Graphics.Gpu.Image tracked = tracked || ShouldFlushTriggerTracking(); bool flushed = false; - EvaluateRelevantHandles(texture, (baseHandle, regionCount, split) => + EvaluateRelevantHandles(texture, (baseHandle, regionCount, split, bound) => { int startSlice = 0; int endSlice = 0; @@ -604,7 +624,9 @@ namespace Ryujinx.Graphics.Gpu.Image flushed = true; } - }); + + return true; + }, out _); Storage.SignalModifiedDirty(); @@ -693,7 +715,7 @@ namespace Ryujinx.Graphics.Gpu.Image ClearIncompatibleOverlaps(texture); - EvaluateRelevantHandles(texture, (baseHandle, regionCount, split) => + EvaluateRelevantHandles(texture, (baseHandle, regionCount, split, bound) => { for (int i = 0; i < regionCount; i++) { @@ -701,7 +723,9 @@ namespace Ryujinx.Graphics.Gpu.Image group.SignalModified(_context); } - }); + + return true; + }, out _); } /// @@ -714,16 +738,20 @@ namespace Ryujinx.Graphics.Gpu.Image ModifiedSequence = _context.GetModifiedSequence(); ClearIncompatibleOverlaps(texture); - - EvaluateRelevantHandles(texture, (baseHandle, regionCount, split) => + + EvaluateRelevantHandles(texture, _signalModifyingCallback, out _, bound); + } + + bool SignalModifyingCallback(int baseHandle, int regionCount, bool split, bool bound) + { + for (int i = 0; i < regionCount; i++) { - for (int i = 0; i < regionCount; i++) - { - TextureGroupHandle group = _handles[baseHandle + i]; + TextureGroupHandle group = _handles[baseHandle + i]; - group.SignalModifying(bound, _context); - } - }); + group.SignalModifying(bound, _context); + } + + return true; } /// @@ -767,16 +795,16 @@ namespace Ryujinx.Graphics.Gpu.Image /// A function to be called with the base index of the range of handles for the given texture, and the number of handles it covers. /// This can be called for multiple disjoint ranges, if required. /// - private void EvaluateRelevantHandles(Texture texture, HandlesCallbackDelegate callback) + private void EvaluateRelevantHandles(Texture texture, HandlesCallbackDelegate callback, out bool result, bool specialData = false) { if (texture == Storage || !(_hasMipViews || _hasLayerViews)) { - callback(0, _handles.Length); + result = callback(0, _handles.Length, specialData: specialData); return; } - EvaluateRelevantHandles(texture.FirstLayer, texture.FirstLevel, texture.Info.GetSlices(), texture.Info.Levels, callback); + EvaluateRelevantHandles(texture.FirstLayer, texture.FirstLevel, texture.Info.GetSlices(), texture.Info.Levels, callback, out result, specialData); } /// @@ -791,11 +819,13 @@ namespace Ryujinx.Graphics.Gpu.Image /// A function to be called with the base index of the range of handles for the given texture, and the number of handles it covers. /// This can be called for multiple disjoint ranges, if required. /// - private void EvaluateRelevantHandles(int firstLayer, int firstLevel, int slices, int levels, HandlesCallbackDelegate callback) + private void EvaluateRelevantHandles(int firstLayer, int firstLevel, int slices, int levels, HandlesCallbackDelegate callback, out bool result, bool specialData = false) { int targetLayerHandles = _hasLayerViews ? slices : 1; int targetLevelHandles = _hasMipViews ? levels : 1; + result = false; + if (_isBuffer) { return; @@ -808,7 +838,7 @@ namespace Ryujinx.Graphics.Gpu.Image { // When there are no layer views, the mips are at a consistent offset. - callback(firstLevel, targetLevelHandles); + result = callback(firstLevel, targetLevelHandles, specialData: specialData); } else { @@ -822,7 +852,7 @@ namespace Ryujinx.Graphics.Gpu.Image while (levels-- > 1) { - callback(firstLayer + levelIndex, slices); + result = callback(firstLayer + levelIndex, slices, specialData: specialData); levelIndex += layerCount; layerCount = Math.Max(layerCount >> 1, 1); @@ -839,7 +869,7 @@ namespace Ryujinx.Graphics.Gpu.Image totalSize += layerCount; } - callback(firstLayer + levelIndex, totalSize); + result = callback(firstLayer + levelIndex, totalSize, specialData: specialData); } } } @@ -856,12 +886,12 @@ namespace Ryujinx.Graphics.Gpu.Image for (int i = 0; i < slices; i++) { - callback(firstLevel + (firstLayer + i) * levelHandles, targetLevelHandles, true); + result = callback(firstLevel + (firstLayer + i) * levelHandles, targetLevelHandles, true, specialData: specialData); } } else { - callback(firstLevel + firstLayer * levelHandles, targetLevelHandles + (targetLayerHandles - 1) * levelHandles); + result = callback(firstLevel + firstLayer * levelHandles, targetLevelHandles + (targetLayerHandles - 1) * levelHandles, specialData: specialData); } } } @@ -1439,8 +1469,16 @@ namespace Ryujinx.Graphics.Gpu.Image List<(int BaseHandle, int RegionCount)> targetRange = []; List<(int BaseHandle, int RegionCount)> otherRange = []; - EvaluateRelevantHandles(firstLayer, firstLevel, other.Info.GetSlices(), other.Info.Levels, (baseHandle, regionCount, split) => targetRange.Add((baseHandle, regionCount))); - otherGroup.EvaluateRelevantHandles(other, (baseHandle, regionCount, split) => otherRange.Add((baseHandle, regionCount))); + EvaluateRelevantHandles(firstLayer, firstLevel, other.Info.GetSlices(), other.Info.Levels, (baseHandle, regionCount, split, specialData) => + { + targetRange.Add((baseHandle, regionCount)); + return true; + }, out _); + otherGroup.EvaluateRelevantHandles(other, (baseHandle, regionCount, split, specialData) => + { + otherRange.Add((baseHandle, regionCount)); + return true; + }, out _); int targetIndex = 0; int otherIndex = 0; diff --git a/src/Ryujinx.Graphics.Gpu/Memory/Buffer.cs b/src/Ryujinx.Graphics.Gpu/Memory/Buffer.cs index c4b848035..7861eb4fe 100644 --- a/src/Ryujinx.Graphics.Gpu/Memory/Buffer.cs +++ b/src/Ryujinx.Graphics.Gpu/Memory/Buffer.cs @@ -93,6 +93,9 @@ namespace Ryujinx.Graphics.Gpu.Memory private ulong _dirtyStart = ulong.MaxValue; private ulong _dirtyEnd = ulong.MaxValue; + private readonly Action _syncPreRangeAction; + private readonly Action _syncRangeAction; + /// /// Creates a new instance of the buffer. /// @@ -177,6 +180,9 @@ namespace Ryujinx.Graphics.Gpu.Memory _modifiedDelegate = RegionModified; _virtualDependenciesLock = new ReaderWriterLockSlim(); + + _syncPreRangeAction = SyncPreRangeAction; + _syncRangeAction = SyncRangeAction; } /// @@ -401,13 +407,15 @@ namespace Ryujinx.Graphics.Gpu.Memory if (_preFlush.ShouldCopy) { - _modifiedRanges?.GetRangesAtSync(Address, Size, _context.SyncNumber, (address, size) => - { - _preFlush.CopyModified(address, size); - }); + _modifiedRanges?.GetRangesAtSync(Address, Size, _context.SyncNumber, _syncPreRangeAction); } } } + + void SyncPreRangeAction(ulong address, ulong size) + { + _preFlush.CopyModified(address, size); + } /// /// Action to be performed when a syncpoint is reached after modification. @@ -420,11 +428,9 @@ namespace Ryujinx.Graphics.Gpu.Memory if (_useGranular) { - _modifiedRanges?.GetRanges(Address, Size, (address, size) => - { - _memoryTrackingGranular.RegisterAction(address, size, _externalFlushDelegate); - SynchronizeMemory(address, size); - }); + + + _modifiedRanges?.GetRanges(Address, Size, _syncRangeAction); } else { @@ -434,6 +440,12 @@ namespace Ryujinx.Graphics.Gpu.Memory return true; } + + void SyncRangeAction(ulong address, ulong size) + { + _memoryTrackingGranular.RegisterAction(address, size, _externalFlushDelegate); + SynchronizeMemory(address, size); + } /// /// Inherit modified and dirty ranges from another buffer. diff --git a/src/Ryujinx.Graphics.Gpu/Memory/BufferModifiedRangeList.cs b/src/Ryujinx.Graphics.Gpu/Memory/BufferModifiedRangeList.cs index fee4b11c0..9c50eaf2f 100644 --- a/src/Ryujinx.Graphics.Gpu/Memory/BufferModifiedRangeList.cs +++ b/src/Ryujinx.Graphics.Gpu/Memory/BufferModifiedRangeList.cs @@ -1,5 +1,6 @@ using Ryujinx.Memory.Range; using System; +using System.Buffers; using System.Linq; namespace Ryujinx.Graphics.Gpu.Memory @@ -276,13 +277,18 @@ namespace Ryujinx.Graphics.Gpu.Memory { // We use the non-span method here because keeping the lock will cause a deadlock. Lock.EnterReadLock(); - RangeItem[] overlaps = FindOverlapsAsArray(address, size); + RangeItem[] overlaps = FindOverlapsAsArray(address, size, out int length); Lock.ExitReadLock(); - for (int i = 0; i < overlaps.Length; i++) + if (length != 0) { - BufferModifiedRange overlap = overlaps[i].Value; - rangeAction(overlap.Address, overlap.Size); + for (int i = 0; i < length; i++) + { + BufferModifiedRange overlap = overlaps[i].Value; + rangeAction(overlap.Address, overlap.Size); + } + + ArrayPool>.Shared.Return(overlaps); } } @@ -392,9 +398,7 @@ namespace Ryujinx.Graphics.Gpu.Memory Lock.EnterWriteLock(); // We use the non-span method here because the array is partially modified by the code, which would invalidate a span. - RangeItem[] overlaps = FindOverlapsAsArray(address, size); - - int rangeCount = overlaps.Length; + RangeItem[] overlaps = FindOverlapsAsArray(address, size, out int rangeCount); if (rangeCount == 0) { @@ -410,7 +414,7 @@ namespace Ryujinx.Graphics.Gpu.Memory for (int i = 0; i < rangeCount; i++) { - BufferModifiedRange overlap = overlaps[i].Value; + BufferModifiedRange overlap = overlaps![i].Value; long diff = (long)(overlap.SyncNumber - currentSync); @@ -430,7 +434,9 @@ namespace Ryujinx.Graphics.Gpu.Memory // Wait for the syncpoint. _context.Renderer.WaitSync(currentSync + (ulong)highestDiff); - RemoveRangesAndFlush(overlaps.ToArray(), rangeCount, highestDiff, currentSync, address, endAddress); + RemoveRangesAndFlush(overlaps, rangeCount, highestDiff, currentSync, address, endAddress); + + ArrayPool>.Shared.Return(overlaps!); Lock.ExitWriteLock(); } diff --git a/src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs b/src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs index 8ce937e44..abe5c9840 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs @@ -397,7 +397,7 @@ namespace Ryujinx.Graphics.Gpu.Shader /// True if queried, false otherwise public bool IsPrimitiveTopologyQueried() { - return _queriedState.HasFlag(QueriedStateFlags.PrimitiveTopology); + return (_queriedState & QueriedStateFlags.PrimitiveTopology) == QueriedStateFlags.PrimitiveTopology; } /// @@ -904,7 +904,7 @@ namespace Ryujinx.Graphics.Gpu.Shader specState.PipelineState = pipelineState; } - if (specState._queriedState.HasFlag(QueriedStateFlags.TransformFeedback)) + if ((specState._queriedState & QueriedStateFlags.TransformFeedback) == QueriedStateFlags.TransformFeedback) { ushort tfCount = 0; dataReader.Read(ref tfCount); @@ -930,7 +930,7 @@ namespace Ryujinx.Graphics.Gpu.Shader specState._textureSpecialization[textureKey] = textureState; } - if (specState._queriedState.HasFlag(QueriedStateFlags.TextureArrayFromBuffer)) + if ((specState._queriedState & QueriedStateFlags.TextureArrayFromBuffer) == QueriedStateFlags.TextureArrayFromBuffer) { dataReader.Read(ref count); @@ -946,7 +946,7 @@ namespace Ryujinx.Graphics.Gpu.Shader } } - if (specState._queriedState.HasFlag(QueriedStateFlags.TextureArrayFromPool)) + if ((specState._queriedState & QueriedStateFlags.TextureArrayFromPool) == QueriedStateFlags.TextureArrayFromPool) { dataReader.Read(ref count); @@ -1006,7 +1006,7 @@ namespace Ryujinx.Graphics.Gpu.Shader dataWriter.WriteWithMagicAndSize(ref pipelineState, PgpsMagic); } - if (_queriedState.HasFlag(QueriedStateFlags.TransformFeedback)) + if ((_queriedState & QueriedStateFlags.TransformFeedback) == QueriedStateFlags.TransformFeedback) { ushort tfCount = (ushort)TransformFeedbackDescriptors.Length; dataWriter.Write(ref tfCount); @@ -1029,7 +1029,7 @@ namespace Ryujinx.Graphics.Gpu.Shader dataWriter.WriteWithMagicAndSize(ref textureState.Value, TexsMagic); } - if (_queriedState.HasFlag(QueriedStateFlags.TextureArrayFromBuffer)) + if ((_queriedState & QueriedStateFlags.TextureArrayFromBuffer) == QueriedStateFlags.TextureArrayFromBuffer) { count = (ushort)_textureArrayFromBufferSpecialization.Count; dataWriter.Write(ref count); @@ -1044,7 +1044,7 @@ namespace Ryujinx.Graphics.Gpu.Shader } } - if (_queriedState.HasFlag(QueriedStateFlags.TextureArrayFromPool)) + if ((_queriedState & QueriedStateFlags.TextureArrayFromPool) == QueriedStateFlags.TextureArrayFromPool) { count = (ushort)_textureArrayFromPoolSpecialization.Count; dataWriter.Write(ref count); diff --git a/src/Ryujinx.Graphics.Vulkan/BufferHolder.cs b/src/Ryujinx.Graphics.Vulkan/BufferHolder.cs index 1e54e8ece..38cd02449 100644 --- a/src/Ryujinx.Graphics.Vulkan/BufferHolder.cs +++ b/src/Ryujinx.Graphics.Vulkan/BufferHolder.cs @@ -58,6 +58,8 @@ namespace Ryujinx.Graphics.Vulkan private Dictionary _mirrors; private bool _useMirrors; + private Action _decrementReferenceCount; + public BufferHolder(VulkanRenderer gd, Device device, VkBuffer buffer, MemoryAllocation allocation, int size, BufferAllocationType type, BufferAllocationType currentType) { _gd = gd; @@ -75,6 +77,8 @@ namespace Ryujinx.Graphics.Vulkan _flushLock = new ReaderWriterLockSlim(); _useMirrors = gd.IsTBDR; + + _decrementReferenceCount = _buffer.DecrementReferenceCount; } public BufferHolder(VulkanRenderer gd, Device device, VkBuffer buffer, Auto allocation, int size, BufferAllocationType type, BufferAllocationType currentType, int offset) @@ -444,7 +448,7 @@ namespace Ryujinx.Graphics.Vulkan _flushLock.ExitReadLock(); - return PinnedSpan.UnsafeFromSpan(result, _buffer.DecrementReferenceCount); + return PinnedSpan.UnsafeFromSpan(result, _decrementReferenceCount); } BackgroundResource resource = _gd.BackgroundResources.Get(); diff --git a/src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs b/src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs index a2384c08e..46b9cacb9 100644 --- a/src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs +++ b/src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs @@ -1,6 +1,7 @@ using Ryujinx.Graphics.GAL; using Silk.NET.Vulkan; using System; +using System.Collections.Generic; using System.Linq; using Format = Ryujinx.Graphics.GAL.Format; using VkFormat = Silk.NET.Vulkan.Format; @@ -10,26 +11,27 @@ namespace Ryujinx.Graphics.Vulkan class FramebufferParams { private readonly Device _device; - private readonly Auto[] _attachments; - private readonly TextureView[] _colors; - private readonly TextureView _depthStencil; - private readonly TextureView[] _colorsCanonical; - private readonly TextureView _baseAttachment; - private readonly uint _validColorAttachments; + private Auto[] _attachments; + private TextureView[] _colors; + private TextureView _depthStencil; + private TextureView[] _colorsCanonical; + private TextureView _baseAttachment; + private uint _validColorAttachments; + private int _totalCount; - public uint Width { get; } - public uint Height { get; } - public uint Layers { get; } + public uint Width { get; private set; } + public uint Height { get; private set; } + public uint Layers { get; private set; } - public uint[] AttachmentSamples { get; } - public VkFormat[] AttachmentFormats { get; } - public int[] AttachmentIndices { get; } - public uint AttachmentIntegerFormatMask { get; } - public bool LogicOpsAllowed { get; } + public uint[] AttachmentSamples { get; private set; } + public VkFormat[] AttachmentFormats { get; private set; } + public int[] AttachmentIndices { get; private set; } + public uint AttachmentIntegerFormatMask { get; private set; } + public bool LogicOpsAllowed { get; private set; } - public int AttachmentsCount { get; } - public int MaxColorAttachmentIndex => AttachmentIndices.Length > 0 ? AttachmentIndices[^1] : -1; - public bool HasDepthStencil { get; } + public int AttachmentsCount { get; private set; } + public int MaxColorAttachmentIndex => ColorAttachmentsCount > 0 ? AttachmentIndices[ColorAttachmentsCount - 1] : -1; + public bool HasDepthStencil { get; private set; } public int ColorAttachmentsCount => AttachmentsCount - (HasDepthStencil ? 1 : 0); public FramebufferParams(Device device, TextureView view, uint width, uint height) @@ -50,7 +52,7 @@ namespace Ryujinx.Graphics.Vulkan else { _colors = [view]; - _colorsCanonical = _colors; + _colorsCanonical = [view]; } Width = width; @@ -64,6 +66,7 @@ namespace Ryujinx.Graphics.Vulkan LogicOpsAllowed = !format.IsFloatOrSrgb(); AttachmentsCount = 1; + _totalCount = 1; HasDepthStencil = isDepthStencil; } @@ -133,7 +136,7 @@ namespace Ryujinx.Graphics.Vulkan AttachmentIntegerFormatMask = attachmentIntegerFormatMask; LogicOpsAllowed = !allFormatsFloatOrSrgb; - if (depthStencil is TextureView dsTexture && dsTexture.Valid) + if (depthStencil is TextureView { Valid: true } dsTexture) { _attachments[count - 1] = dsTexture.GetImageViewForAttachment(); _depthStencil = dsTexture; @@ -159,11 +162,151 @@ namespace Ryujinx.Graphics.Vulkan Layers = layers; AttachmentsCount = count; + _totalCount = colors.Length; + } + + public FramebufferParams Update(ITexture[] colors, ITexture depthStencil) + { + int colorsCount = colors.Count(IsValidTextureView); + + int count = colorsCount + (IsValidTextureView(depthStencil) ? 1 : 0); + + Array.Clear(_attachments); + Array.Clear(_colors); + + if (_attachments.Length < count) + { + Array.Resize(ref _attachments, count); + } + if (_colors.Length < colorsCount) + { + Array.Resize(ref _colors, colorsCount); + } + if (_colorsCanonical.Length < colors.Length) + { + Array.Resize(ref _colorsCanonical, colors.Length); + } + + for (int i = 0; i < colors.Length; i++) + { + ITexture color = colors[i]; + if (color is TextureView { Valid: true } view) + { + _colorsCanonical[i] = view; + } + else + { + _colorsCanonical[i] = null; + } + } + + Array.Clear(AttachmentSamples); + Array.Clear(AttachmentFormats); + Array.Clear(AttachmentIndices); + + if (AttachmentSamples.Length < count) + { + uint[] attachmentSamples = AttachmentSamples; + Array.Resize(ref attachmentSamples, count); + AttachmentSamples = attachmentSamples; + } + if (AttachmentFormats.Length < count) + { + VkFormat[] attachmentFormats = AttachmentFormats; + Array.Resize(ref attachmentFormats, count); + AttachmentFormats = attachmentFormats; + } + if (AttachmentIndices.Length < colorsCount) + { + int[] attachmentIndices = AttachmentIndices; + Array.Resize(ref attachmentIndices, colorsCount); + AttachmentIndices = attachmentIndices; + } + + uint width = uint.MaxValue; + uint height = uint.MaxValue; + uint layers = uint.MaxValue; + + int index = 0; + uint attachmentIntegerFormatMask = 0; + bool allFormatsFloatOrSrgb = colorsCount != 0; + + _validColorAttachments = 0; + _baseAttachment = null; + + for (int bindIndex = 0; bindIndex < colors.Length; bindIndex++) + { + TextureView texture = _colorsCanonical[bindIndex]; + if (texture is not null) + { + _attachments[index] = texture.GetImageViewForAttachment(); + _colors[index] = texture; + _validColorAttachments |= 1u << bindIndex; + _baseAttachment = texture; + + AttachmentSamples[index] = (uint)texture.Info.Samples; + AttachmentFormats[index] = texture.VkFormat; + AttachmentIndices[index] = bindIndex; + + Format format = texture.Info.Format; + + if (format.IsInteger()) + { + attachmentIntegerFormatMask |= 1u << bindIndex; + } + + allFormatsFloatOrSrgb &= format.IsFloatOrSrgb(); + + width = Math.Min(width, (uint)texture.Width); + height = Math.Min(height, (uint)texture.Height); + layers = Math.Min(layers, (uint)texture.Layers); + + if (++index >= colorsCount) + { + break; + } + } + } + + AttachmentIntegerFormatMask = attachmentIntegerFormatMask; + LogicOpsAllowed = !allFormatsFloatOrSrgb; + _depthStencil = null; + HasDepthStencil = false; + + if (depthStencil is TextureView { Valid: true } dsTexture) + { + _attachments[count - 1] = dsTexture.GetImageViewForAttachment(); + _depthStencil = dsTexture; + _baseAttachment ??= dsTexture; + + AttachmentSamples[count - 1] = (uint)dsTexture.Info.Samples; + AttachmentFormats[count - 1] = dsTexture.VkFormat; + + width = Math.Min(width, (uint)dsTexture.Width); + height = Math.Min(height, (uint)dsTexture.Height); + layers = Math.Min(layers, (uint)dsTexture.Layers); + + HasDepthStencil = true; + } + + if (count == 0) + { + width = height = layers = 1; + } + + Width = width; + Height = height; + Layers = layers; + + AttachmentsCount = count; + _totalCount = colors.Length; + + return this; } public Auto GetAttachment(int index) { - if ((uint)index >= _attachments.Length) + if ((uint)index >= AttachmentsCount) { return null; } @@ -183,7 +326,7 @@ namespace Ryujinx.Graphics.Vulkan public ComponentType GetAttachmentComponentType(int index) { - if (_colors != null && (uint)index < _colors.Length) + if (_colors != null && (uint)index < ColorAttachmentsCount) { Format format = _colors[index].Info.Format; @@ -218,7 +361,7 @@ namespace Ryujinx.Graphics.Vulkan private static bool IsValidTextureView(ITexture texture) { - return texture is TextureView view && view.Valid; + return texture is TextureView { Valid: true }; } public ClearRect GetClearRect(Rectangle scissor, int layer, int layerCount) @@ -233,9 +376,9 @@ namespace Ryujinx.Graphics.Vulkan public unsafe Auto Create(Vk api, CommandBufferScoped cbs, Auto renderPass) { - ImageView* attachments = stackalloc ImageView[_attachments.Length]; + ImageView* attachments = stackalloc ImageView[AttachmentsCount]; - for (int i = 0; i < _attachments.Length; i++) + for (int i = 0; i < AttachmentsCount; i++) { attachments[i] = _attachments[i].Get(cbs).Value; } @@ -244,7 +387,7 @@ namespace Ryujinx.Graphics.Vulkan { SType = StructureType.FramebufferCreateInfo, RenderPass = renderPass.Get(cbs).Value, - AttachmentCount = (uint)_attachments.Length, + AttachmentCount = (uint)AttachmentsCount, PAttachments = attachments, Width = Width, Height = Height, @@ -252,14 +395,13 @@ namespace Ryujinx.Graphics.Vulkan }; api.CreateFramebuffer(_device, in framebufferCreateInfo, null, out Framebuffer framebuffer).ThrowOnError(); - return new Auto(new DisposableFramebuffer(api, _device, framebuffer), null, _attachments); + return new Auto(new DisposableFramebuffer(api, _device, framebuffer), null, _attachments[..AttachmentsCount]); } public TextureView[] GetAttachmentViews() { - TextureView[] result = new TextureView[_attachments.Length]; - - _colors?.CopyTo(result, 0); + TextureView[] result = new TextureView[AttachmentsCount]; + _colors?.AsSpan(..ColorAttachmentsCount).CopyTo(result.AsSpan()); if (_depthStencil != null) { @@ -278,8 +420,11 @@ namespace Ryujinx.Graphics.Vulkan { if (_colors != null) { - foreach (TextureView color in _colors) + int count = ColorAttachmentsCount; + + for (int i = 0; i < count; i++) { + TextureView color = _colors[i]; // If Clear or DontCare were used, this would need to be write bit. color.Storage?.QueueLoadOpBarrier(cbs, false); } @@ -294,8 +439,11 @@ namespace Ryujinx.Graphics.Vulkan { if (_colors != null) { - foreach (TextureView color in _colors) + int count = ColorAttachmentsCount; + + for (int i = 0; i < count; i++) { + TextureView color = _colors[i]; color.Storage?.AddStoreOpUsage(false); } } @@ -307,7 +455,7 @@ namespace Ryujinx.Graphics.Vulkan { _depthStencil?.Storage.ClearBindings(); - for (int i = 0; i < _colorsCanonical.Length; i++) + for (int i = 0; i < _totalCount; i++) { _colorsCanonical[i]?.Storage.ClearBindings(); } @@ -317,7 +465,7 @@ namespace Ryujinx.Graphics.Vulkan { _depthStencil?.Storage.AddBinding(_depthStencil); - for (int i = 0; i < _colorsCanonical.Length; i++) + for (int i = 0; i < _totalCount; i++) { TextureView color = _colorsCanonical[i]; color?.Storage.AddBinding(color); diff --git a/src/Ryujinx.Graphics.Vulkan/MultiFenceHolder.cs b/src/Ryujinx.Graphics.Vulkan/MultiFenceHolder.cs index e9ef39cda..40ad7716d 100644 --- a/src/Ryujinx.Graphics.Vulkan/MultiFenceHolder.cs +++ b/src/Ryujinx.Graphics.Vulkan/MultiFenceHolder.cs @@ -1,6 +1,7 @@ using Ryujinx.Common.Memory; using Silk.NET.Vulkan; using System; +using System.Buffers; namespace Ryujinx.Graphics.Vulkan { @@ -11,7 +12,7 @@ namespace Ryujinx.Graphics.Vulkan { private const int BufferUsageTrackingGranularity = 4096; - private readonly FenceHolder[] _fences; + public FenceHolder[] Fences { get; } private readonly BufferUsageBitmap _bufferUsageBitmap; /// @@ -19,7 +20,7 @@ namespace Ryujinx.Graphics.Vulkan /// public MultiFenceHolder() { - _fences = new FenceHolder[CommandBufferPool.MaxCommandBuffers]; + Fences = ArrayPool.Shared.Rent(CommandBufferPool.MaxCommandBuffers); } /// @@ -28,7 +29,7 @@ namespace Ryujinx.Graphics.Vulkan /// Size of the buffer public MultiFenceHolder(int size) { - _fences = new FenceHolder[CommandBufferPool.MaxCommandBuffers]; + Fences = ArrayPool.Shared.Rent(CommandBufferPool.MaxCommandBuffers); _bufferUsageBitmap = new BufferUsageBitmap(size, BufferUsageTrackingGranularity); } @@ -90,7 +91,7 @@ namespace Ryujinx.Graphics.Vulkan /// True if the command buffer's previous fence value was null public bool AddFence(int cbIndex, FenceHolder fence) { - ref FenceHolder fenceRef = ref _fences[cbIndex]; + ref FenceHolder fenceRef = ref Fences[cbIndex]; if (fenceRef == null) { @@ -107,7 +108,7 @@ namespace Ryujinx.Graphics.Vulkan /// Command buffer index of the command buffer that owns the fence public void RemoveFence(int cbIndex) { - _fences[cbIndex] = null; + Fences[cbIndex] = null; } /// @@ -117,7 +118,7 @@ namespace Ryujinx.Graphics.Vulkan /// True if referenced, false otherwise public bool HasFence(int cbIndex) { - return _fences[cbIndex] != null; + return Fences[cbIndex] != null; } /// @@ -227,9 +228,9 @@ namespace Ryujinx.Graphics.Vulkan { int count = 0; - for (int i = 0; i < _fences.Length; i++) + for (int i = 0; i < Fences.Length; i++) { - FenceHolder fence = _fences[i]; + FenceHolder fence = Fences[i]; if (fence != null) { @@ -251,9 +252,9 @@ namespace Ryujinx.Graphics.Vulkan { int count = 0; - for (int i = 0; i < _fences.Length; i++) + for (int i = 0; i < Fences.Length; i++) { - FenceHolder fence = _fences[i]; + FenceHolder fence = Fences[i]; if (fence != null && _bufferUsageBitmap.OverlapsWith(i, offset, size)) { diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs b/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs index fb244d307..26682ad20 100644 --- a/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs +++ b/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs @@ -1453,7 +1453,7 @@ namespace Ryujinx.Graphics.Vulkan FramebufferParams?.ClearBindings(); } - FramebufferParams = new FramebufferParams(Device, colors, depthStencil); + FramebufferParams = FramebufferParams?.Update(colors, depthStencil) ?? new FramebufferParams(Device, colors, depthStencil); if (IsMainPipeline) { @@ -1471,18 +1471,18 @@ namespace Ryujinx.Graphics.Vulkan protected void UpdatePipelineAttachmentFormats() { Span dstAttachmentFormats = _newState.Internal.AttachmentFormats.AsSpan(); - FramebufferParams.AttachmentFormats.CopyTo(dstAttachmentFormats); + FramebufferParams.AttachmentFormats.AsSpan(..FramebufferParams.AttachmentsCount).CopyTo(dstAttachmentFormats); _newState.Internal.AttachmentIntegerFormatMask = FramebufferParams.AttachmentIntegerFormatMask; _newState.Internal.LogicOpsAllowed = FramebufferParams.LogicOpsAllowed; - for (int i = FramebufferParams.AttachmentFormats.Length; i < dstAttachmentFormats.Length; i++) + for (int i = FramebufferParams.AttachmentsCount; i < dstAttachmentFormats.Length; i++) { dstAttachmentFormats[i] = 0; } _newState.ColorBlendAttachmentStateCount = (uint)(FramebufferParams.MaxColorAttachmentIndex + 1); _newState.HasDepthStencil = FramebufferParams.HasDepthStencil; - _newState.SamplesCount = FramebufferParams.AttachmentSamples.Length != 0 ? FramebufferParams.AttachmentSamples[0] : 1; + _newState.SamplesCount = FramebufferParams.AttachmentsCount != 0 ? FramebufferParams.AttachmentSamples[0] : 1; } protected unsafe void CreateRenderPass() diff --git a/src/Ryujinx.Graphics.Vulkan/RenderPassHolder.cs b/src/Ryujinx.Graphics.Vulkan/RenderPassHolder.cs index 2c2d381a9..7b477366b 100644 --- a/src/Ryujinx.Graphics.Vulkan/RenderPassHolder.cs +++ b/src/Ryujinx.Graphics.Vulkan/RenderPassHolder.cs @@ -178,17 +178,16 @@ namespace Ryujinx.Graphics.Vulkan { if (_forcedFences.Count > 0) { - _forcedFences.RemoveAll((entry) => + for (int i = 0; i < _forcedFences.Count; i++) { - if (entry.Texture.Disposed) + if (_forcedFences[i].Texture.Disposed) { - return true; + _forcedFences.RemoveAt(i--); + continue; } - - entry.Texture.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, entry.StageFlags); - - return false; - }); + + _forcedFences[i].Texture.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, _forcedFences[i].StageFlags); + } } } diff --git a/src/Ryujinx.Graphics.Vulkan/SyncManager.cs b/src/Ryujinx.Graphics.Vulkan/SyncManager.cs index 5f6214a4f..149759906 100644 --- a/src/Ryujinx.Graphics.Vulkan/SyncManager.cs +++ b/src/Ryujinx.Graphics.Vulkan/SyncManager.cs @@ -1,5 +1,6 @@ using Ryujinx.Common.Logging; using Silk.NET.Vulkan; +using System.Buffers; using System.Collections.Generic; using System.Diagnostics; using System.Linq; @@ -192,6 +193,7 @@ namespace Ryujinx.Graphics.Vulkan { _firstHandle = first.ID + 1; _handles.RemoveAt(0); + ArrayPool.Shared.Return(first.Waitable.Fences); first.Waitable = null; } } diff --git a/src/Ryujinx.HLE/HOS/Kernel/Common/KSynchronizationObject.cs b/src/Ryujinx.HLE/HOS/Kernel/Common/KSynchronizationObject.cs index 50fe01069..fba8d0590 100644 --- a/src/Ryujinx.HLE/HOS/Kernel/Common/KSynchronizationObject.cs +++ b/src/Ryujinx.HLE/HOS/Kernel/Common/KSynchronizationObject.cs @@ -1,3 +1,4 @@ +using Ryujinx.Common; using Ryujinx.HLE.HOS.Kernel.Threading; using System.Collections.Generic; @@ -5,6 +6,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Common { class KSynchronizationObject : KAutoObject { + private static readonly ObjectPool> _nodePool = new(() => new LinkedListNode(null)); + public LinkedList WaitingThreads { get; } public KSynchronizationObject(KernelContext context) : base(context) @@ -14,12 +17,16 @@ namespace Ryujinx.HLE.HOS.Kernel.Common public LinkedListNode AddWaitingThread(KThread thread) { - return WaitingThreads.AddLast(thread); + LinkedListNode node = _nodePool.Allocate(); + node.Value = thread; + WaitingThreads.AddLast(node); + return node; } public void RemoveWaitingThread(LinkedListNode node) { WaitingThreads.Remove(node); + _nodePool.Release(node); } public virtual void Signal() diff --git a/src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryBlock.cs b/src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryBlock.cs index d2c4aadf3..25be81b10 100644 --- a/src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryBlock.cs +++ b/src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryBlock.cs @@ -110,7 +110,22 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory { ulong size = PagesCount * KPageTableBase.PageSize; - return new KMemoryInfo( + return KMemoryInfo.Pool.Allocate().Set( + BaseAddress, + size, + State, + Permission, + Attribute, + SourcePermission, + IpcRefCount, + DeviceRefCount); + } + + public KMemoryInfo GetInfo(KMemoryInfo oldInfo) + { + ulong size = PagesCount * KPageTableBase.PageSize; + + return oldInfo.Set( BaseAddress, size, State, diff --git a/src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryInfo.cs b/src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryInfo.cs index 4db484d04..087eaea1d 100644 --- a/src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryInfo.cs +++ b/src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryInfo.cs @@ -1,19 +1,23 @@ +using Ryujinx.Common; + namespace Ryujinx.HLE.HOS.Kernel.Memory { class KMemoryInfo { - public ulong Address { get; } - public ulong Size { get; } + public static readonly ObjectPool Pool = new(() => new KMemoryInfo()); + + public ulong Address { get; private set; } + public ulong Size { get; private set; } - public MemoryState State { get; } - public KMemoryPermission Permission { get; } - public MemoryAttribute Attribute { get; } - public KMemoryPermission SourcePermission { get; } + public MemoryState State { get; private set; } + public KMemoryPermission Permission { get; private set; } + public MemoryAttribute Attribute { get;private set; } + public KMemoryPermission SourcePermission { get; private set; } - public int IpcRefCount { get; } - public int DeviceRefCount { get; } + public int IpcRefCount { get; private set; } + public int DeviceRefCount { get; private set; } - public KMemoryInfo( + public KMemoryInfo Set( ulong address, ulong size, MemoryState state, @@ -31,6 +35,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory SourcePermission = sourcePermission; IpcRefCount = ipcRefCount; DeviceRefCount = deviceRefCount; + + return this; } } } diff --git a/src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryPermission.cs b/src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryPermission.cs index 32734574e..f5baede08 100644 --- a/src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryPermission.cs +++ b/src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryPermission.cs @@ -25,17 +25,17 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory { MemoryPermission output = MemoryPermission.None; - if (permission.HasFlag(KMemoryPermission.Read)) + if ((permission & KMemoryPermission.Read) == KMemoryPermission.Read) { output = MemoryPermission.Read; } - if (permission.HasFlag(KMemoryPermission.Write)) + if ((permission & KMemoryPermission.Write) == KMemoryPermission.Write) { output |= MemoryPermission.Write; } - if (permission.HasFlag(KMemoryPermission.Execute)) + if ((permission & KMemoryPermission.Execute) == KMemoryPermission.Execute) { output |= MemoryPermission.Execute; } diff --git a/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTableBase.cs b/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTableBase.cs index bc59b0b4d..8f56879ed 100644 --- a/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTableBase.cs +++ b/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTableBase.cs @@ -998,7 +998,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory } else { - return new KMemoryInfo( + return KMemoryInfo.Pool.Allocate().Set( AddrSpaceEnd, ~AddrSpaceEnd + 1, MemoryState.Reserved, @@ -2544,10 +2544,10 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory KMemoryPermission firstPermission = info.Permission; MemoryAttribute firstAttribute = info.Attribute; - do + info = currBlock.GetInfo(info); + + while (info.Address + info.Size - 1 < endAddr - 1 && (currBlock = currBlock.Successor) != null) { - info = currBlock.GetInfo(); - // Check if the block state matches what we expect. if (firstState != info.State || firstPermission != info.Permission || @@ -2559,11 +2559,16 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory outState = MemoryState.Unmapped; outPermission = KMemoryPermission.None; outAttribute = MemoryAttribute.None; + + KMemoryInfo.Pool.Release(info); return false; } + + info = currBlock.GetInfo(info); } - while (info.Address + info.Size - 1 < endAddr - 1 && (currBlock = currBlock.Successor) != null); + + KMemoryInfo.Pool.Release(info); outState = firstState; outPermission = firstPermission; @@ -2582,16 +2587,26 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory MemoryAttribute attributeMask, MemoryAttribute attributeExpected) { - foreach (KMemoryInfo info in IterateOverRange(address, address + size)) + KMemoryBlock currBlock = _blockManager.FindBlock(address); + + KMemoryInfo info = currBlock.GetInfo(); + + while (info.Address + info.Size - 1 < address + size - 1 && (currBlock = currBlock.Successor) != null) { // Check if the block state matches what we expect. if ((info.State & stateMask) != stateExpected || (info.Permission & permissionMask) != permissionExpected || (info.Attribute & attributeMask) != attributeExpected) { + KMemoryInfo.Pool.Release(info); + return false; } + + info = currBlock.GetInfo(info); } + + KMemoryInfo.Pool.Release(info); return true; } @@ -2641,6 +2656,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory ulong currBaseAddr = info.Address + reservedPagesCount * PageSize; ulong currEndAddr = info.Address + info.Size; + + KMemoryInfo.Pool.Release(info); if (aslrAddress >= regionStart && aslrAddress >= currBaseAddr && @@ -2721,6 +2738,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory allocationEndAddr <= regionEndAddr && allocationEndAddr <= currEndAddr) { + KMemoryInfo.Pool.Release(info); return address; } } @@ -2731,9 +2749,11 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory { break; } - - info = currBlock.GetInfo(); + + info = currBlock.GetInfo(info); } + + KMemoryInfo.Pool.Release(info); return 0; } diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/HleProcessDebugger.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/HleProcessDebugger.cs index 71ee7a086..8146538f9 100644 --- a/src/Ryujinx.HLE/HOS/Kernel/Process/HleProcessDebugger.cs +++ b/src/Ryujinx.HLE/HOS/Kernel/Process/HleProcessDebugger.cs @@ -386,6 +386,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Process } ulong rwdataStart = roInfo.Address + roInfo.Size; + + KMemoryInfo.Pool.Release(roInfo); try { diff --git a/src/Ryujinx.HLE/HOS/Kernel/Threading/KAddressArbiter.cs b/src/Ryujinx.HLE/HOS/Kernel/Threading/KAddressArbiter.cs index 62b57da04..278a9b2ff 100644 --- a/src/Ryujinx.HLE/HOS/Kernel/Threading/KAddressArbiter.cs +++ b/src/Ryujinx.HLE/HOS/Kernel/Threading/KAddressArbiter.cs @@ -1,3 +1,4 @@ +using Ryujinx.Common; using Ryujinx.HLE.HOS.Kernel.Common; using Ryujinx.HLE.HOS.Kernel.Process; using Ryujinx.Horizon.Common; @@ -11,6 +12,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading class KAddressArbiter { private const int HasListenersMask = 0x40000000; + private static readonly ObjectPool _threadArrayPool = new(() => []); private readonly KernelContext _context; @@ -198,9 +200,14 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading { _context.CriticalSection.Enter(); - WakeThreads(_condVarThreads, count, TryAcquireMutex, x => x.CondVarAddress == address); + static bool SignalProcessWideKeyPredicate(KThread thread, ulong address) + { + return thread.CondVarAddress == address; + } - if (!_condVarThreads.Any(x => x.CondVarAddress == address)) + int validThreads = WakeThreads(_condVarThreads, count, TryAcquireMutex, SignalProcessWideKeyPredicate, address); + + if (validThreads == 0) { KernelTransfer.KernelToUser(address, 0); } @@ -480,9 +487,10 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading // or negative. It is incremented if there are no threads waiting. int waitingCount = 0; - foreach (KThread thread in _arbiterThreads.Where(x => x.MutexAddress == address)) + foreach (KThread thread in _arbiterThreads) { - if (++waitingCount >= count) + if (thread.MutexAddress == address && + ++waitingCount >= count) { break; } @@ -553,23 +561,55 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading thread.WaitingInArbitration = false; } - WakeThreads(_arbiterThreads, count, RemoveArbiterThread, x => x.MutexAddress == address); + static bool ArbiterThreadPredecate(KThread thread, ulong address) + { + return thread.MutexAddress == address; + } + + WakeThreads(_arbiterThreads, count, RemoveArbiterThread, ArbiterThreadPredecate, address); } - private static void WakeThreads( + private static int WakeThreads( List threads, int count, Action removeCallback, - Func predicate) + Func predicate, + ulong address = 0) { - IOrderedEnumerable candidates = threads.Where(predicate).OrderBy(x => x.DynamicPriority); - KThread[] toSignal = (count > 0 ? candidates.Take(count) : candidates).ToArray(); + KThread[] candidates = _threadArrayPool.Allocate(); + if (candidates.Length < threads.Count) + { + Array.Resize(ref candidates, threads.Count); + } + + int validCount = 0; + + for (int i = 0; i < threads.Count; i++) + { + if (predicate(threads[i], address)) + { + candidates[validCount++] = threads[i]; + } + } + + Span candidatesSpan = candidates.AsSpan(..validCount); + + candidatesSpan.Sort((x, y) => (x.DynamicPriority.CompareTo(y.DynamicPriority))); - foreach (KThread thread in toSignal) + if (count > 0) + { + candidatesSpan = candidatesSpan[..Math.Min(count, candidatesSpan.Length)]; + } + + foreach (KThread thread in candidatesSpan) { removeCallback(thread); threads.Remove(thread); } + + _threadArrayPool.Release(candidates); + + return validCount; } } } diff --git a/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs b/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs index 2ebcd80d7..64cd4d595 100644 --- a/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs +++ b/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs @@ -48,8 +48,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading public KThreadContext ThreadContext { get; private set; } - public int DynamicPriority { get; set; } - public ulong AffinityMask { get; set; } + public int DynamicPriority { get; private set; } + public ulong AffinityMask { get; private set; } public ulong ThreadUid { get; private set; } @@ -83,18 +83,18 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading public long LastScheduledTime { get; set; } - public LinkedListNode[] SiblingsPerCore { get; private set; } + public readonly LinkedListNode[] SiblingsPerCore; public LinkedList Withholder { get; set; } - public LinkedListNode WithholderNode { get; set; } + public readonly LinkedListNode WithholderNode; - public LinkedListNode ProcessListNode { get; set; } + public readonly LinkedListNode ProcessListNode; private readonly LinkedList _mutexWaiters; - private LinkedListNode _mutexWaiterNode; + private readonly LinkedListNode _mutexWaiterNode; private readonly LinkedList _pinnedWaiters; - private LinkedListNode _pinnedWaiterNode; + private readonly LinkedListNode _pinnedWaiterNode; public KThread MutexOwner { get; private set; } @@ -1070,11 +1070,11 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading if (nextPrio != null) { - thread._mutexWaiterNode = _mutexWaiters.AddBefore(nextPrio, thread); + _mutexWaiters.AddBefore(nextPrio, thread._mutexWaiterNode); } else { - thread._mutexWaiterNode = _mutexWaiters.AddLast(thread); + _mutexWaiters.AddLast(thread._mutexWaiterNode); } } diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/INvDrvServices.cs b/src/Ryujinx.HLE/HOS/Services/Nv/INvDrvServices.cs index 89ef76616..598c7e6e2 100644 --- a/src/Ryujinx.HLE/HOS/Services/Nv/INvDrvServices.cs +++ b/src/Ryujinx.HLE/HOS/Services/Nv/INvDrvServices.cs @@ -14,6 +14,7 @@ using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap; using Ryujinx.HLE.HOS.Services.Nv.Types; using Ryujinx.Memory; using System; +using System.Buffers; using System.Collections.Generic; using System.Reflection; @@ -46,6 +47,8 @@ namespace Ryujinx.HLE.HOS.Services.Nv { "/dev/nvhost-dbg-gpu", typeof(NvHostDbgGpuDeviceFile) }, { "/dev/nvhost-prof-gpu", typeof(NvHostProfGpuDeviceFile) }, }; + + private static readonly ArrayPool _byteArrayPool = ArrayPool.Create(); public static IdDictionary DeviceFileIdRegistry = new(); @@ -471,10 +474,13 @@ namespace Ryujinx.HLE.HOS.Services.Nv errorCode = GetIoctlArgument(context, ioctlCommand, out Span arguments); - if (!context.Memory.TryReadUnsafe(inlineInBufferPosition, (int)inlineInBufferSize, out Span inlineInBuffer)) + byte[] inlineInBuffer = null; + + if (!context.Memory.TryReadUnsafe(inlineInBufferPosition, (int)inlineInBufferSize, out Span inlineInBufferSpan)) { - inlineInBuffer = new byte[inlineInBufferSize]; - context.Memory.Read(inlineInBufferPosition, inlineInBuffer); + inlineInBuffer = _byteArrayPool.Rent((int)inlineInBufferSize); + inlineInBufferSpan = inlineInBuffer; + context.Memory.Read(inlineInBufferPosition, inlineInBufferSpan[..(int)inlineInBufferSize]); } if (errorCode == NvResult.Success) @@ -483,7 +489,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv if (errorCode == NvResult.Success) { - NvInternalResult internalResult = deviceFile.Ioctl2(ioctlCommand, arguments, inlineInBuffer); + NvInternalResult internalResult = deviceFile.Ioctl2(ioctlCommand, arguments, inlineInBufferSpan[..(int)inlineInBufferSize]); if (internalResult == NvInternalResult.NotImplemented) { @@ -498,6 +504,11 @@ namespace Ryujinx.HLE.HOS.Services.Nv } } } + + if (inlineInBuffer is not null) + { + _byteArrayPool.Return(inlineInBuffer); + } } context.ResponseData.Write((uint)errorCode); @@ -520,10 +531,13 @@ namespace Ryujinx.HLE.HOS.Services.Nv errorCode = GetIoctlArgument(context, ioctlCommand, out Span arguments); - if (!context.Memory.TryReadUnsafe(inlineOutBufferPosition, (int)inlineOutBufferSize, out Span inlineOutBuffer)) + byte[] inlineOutBuffer = null; + + if (!context.Memory.TryReadUnsafe(inlineOutBufferPosition, (int)inlineOutBufferSize, out Span inlineOutBufferSpan)) { - inlineOutBuffer = new byte[inlineOutBufferSize]; - context.Memory.Read(inlineOutBufferPosition, inlineOutBuffer); + inlineOutBuffer = _byteArrayPool.Rent((int)inlineOutBufferSize); + inlineOutBufferSpan = inlineOutBuffer; + context.Memory.Read(inlineOutBufferPosition, inlineOutBufferSpan[..(int)inlineOutBufferSize]); } if (errorCode == NvResult.Success) @@ -532,7 +546,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv if (errorCode == NvResult.Success) { - NvInternalResult internalResult = deviceFile.Ioctl3(ioctlCommand, arguments, inlineOutBuffer); + NvInternalResult internalResult = deviceFile.Ioctl3(ioctlCommand, arguments, inlineOutBufferSpan[..(int)inlineOutBufferSize]); if (internalResult == NvInternalResult.NotImplemented) { @@ -544,10 +558,15 @@ namespace Ryujinx.HLE.HOS.Services.Nv if ((ioctlCommand.DirectionValue & NvIoctl.Direction.Write) != 0) { context.Memory.Write(context.Request.GetBufferType0x22(0).Position, arguments.ToArray()); - context.Memory.Write(inlineOutBufferPosition, inlineOutBuffer.ToArray()); + context.Memory.Write(inlineOutBufferPosition, inlineOutBufferSpan[..(int)inlineOutBufferSize].ToArray()); } } } + + if (inlineOutBuffer is not null) + { + _byteArrayPool.Return(inlineOutBuffer); + } } context.ResponseData.Write((uint)errorCode); diff --git a/src/Ryujinx.Horizon/Sdk/Sf/HipcCommandProcessor.cs b/src/Ryujinx.Horizon/Sdk/Sf/HipcCommandProcessor.cs index ec0acbdea..9844d8d3d 100644 --- a/src/Ryujinx.Horizon/Sdk/Sf/HipcCommandProcessor.cs +++ b/src/Ryujinx.Horizon/Sdk/Sf/HipcCommandProcessor.cs @@ -48,36 +48,36 @@ namespace Ryujinx.Horizon.Sdk.Sf case CommandArgType.Buffer: HipcBufferFlags flags = argInfo.BufferFlags; - if (flags.HasFlag(HipcBufferFlags.In)) + if ((flags & HipcBufferFlags.In) != 0) { - if (flags.HasFlag(HipcBufferFlags.AutoSelect)) + if ((flags & HipcBufferFlags.AutoSelect) != 0) { _inMapAliasBuffersCount++; _inPointerBuffersCount++; } - else if (flags.HasFlag(HipcBufferFlags.MapAlias)) + else if ((flags & HipcBufferFlags.MapAlias) != 0) { _inMapAliasBuffersCount++; } - else if (flags.HasFlag(HipcBufferFlags.Pointer)) + else if ((flags & HipcBufferFlags.Pointer) != 0) { _inPointerBuffersCount++; } } else { - bool autoSelect = flags.HasFlag(HipcBufferFlags.AutoSelect); - if (autoSelect || flags.HasFlag(HipcBufferFlags.Pointer)) + bool autoSelect = (flags & HipcBufferFlags.AutoSelect) != 0; + if (autoSelect || (flags & HipcBufferFlags.Pointer) != 0) { _outPointerBuffersCount++; - if (flags.HasFlag(HipcBufferFlags.FixedSize)) + if ((flags & HipcBufferFlags.FixedSize) != 0) { _outFixedSizePointerBuffersCount++; } } - if (autoSelect || flags.HasFlag(HipcBufferFlags.MapAlias)) + if (autoSelect || (flags & HipcBufferFlags.MapAlias) != 0) { _outMapAliasBuffersCount++; } @@ -150,17 +150,17 @@ namespace Ryujinx.Horizon.Sdk.Sf HipcBufferFlags flags = _args[i].BufferFlags; bool isMapAlias; - if (flags.HasFlag(HipcBufferFlags.MapAlias)) + if ((flags & HipcBufferFlags.MapAlias) != 0) { isMapAlias = true; } - else if (flags.HasFlag(HipcBufferFlags.Pointer)) + else if ((flags & HipcBufferFlags.Pointer) != 0) { isMapAlias = false; } - else /* if (flags.HasFlag(HipcBufferFlags.HipcAutoSelect)) */ + else /* if (flags & HipcBufferFlags.HipcAutoSelect)) */ { - HipcBufferDescriptor descriptor = flags.HasFlag(HipcBufferFlags.In) + HipcBufferDescriptor descriptor = (flags & HipcBufferFlags.In) != 0 ? context.Request.Data.SendBuffers[sendMapAliasIndex] : context.Request.Data.ReceiveBuffers[recvMapAliasIndex]; @@ -171,7 +171,7 @@ namespace Ryujinx.Horizon.Sdk.Sf if (isMapAlias) { - HipcBufferDescriptor descriptor = flags.HasFlag(HipcBufferFlags.In) + HipcBufferDescriptor descriptor = (flags & HipcBufferFlags.In) != 0 ? context.Request.Data.SendBuffers[sendMapAliasIndex++] : context.Request.Data.ReceiveBuffers[recvMapAliasIndex++]; @@ -184,7 +184,7 @@ namespace Ryujinx.Horizon.Sdk.Sf } else { - if (flags.HasFlag(HipcBufferFlags.In)) + if ((flags & HipcBufferFlags.In) != 0) { HipcStaticDescriptor descriptor = context.Request.Data.SendStatics[sendPointerIndex++]; ulong address = descriptor.Address; @@ -197,11 +197,11 @@ namespace Ryujinx.Horizon.Sdk.Sf pointerBufferTail = Math.Max(pointerBufferTail, address + size); } } - else /* if (flags.HasFlag(HipcBufferFlags.Out)) */ + else /* if (flags & HipcBufferFlags.Out)) */ { ulong size; - if (flags.HasFlag(HipcBufferFlags.FixedSize)) + if ((flags & HipcBufferFlags.FixedSize) != 0) { size = _args[i].BufferFixedSize; } @@ -234,12 +234,12 @@ namespace Ryujinx.Horizon.Sdk.Sf private static bool IsMapTransferModeValid(HipcBufferFlags flags, HipcBufferMode mode) { - if (flags.HasFlag(HipcBufferFlags.MapTransferAllowsNonSecure)) + if ((flags & HipcBufferFlags.MapTransferAllowsNonSecure) != 0) { return mode == HipcBufferMode.NonSecure; } - if (flags.HasFlag(HipcBufferFlags.MapTransferAllowsNonDevice)) + if ((flags & HipcBufferFlags.MapTransferAllowsNonDevice) != 0) { return mode == HipcBufferMode.NonDevice; } @@ -259,18 +259,18 @@ namespace Ryujinx.Horizon.Sdk.Sf } HipcBufferFlags flags = _args[i].BufferFlags; - if (!flags.HasFlag(HipcBufferFlags.Out)) + if ((flags & HipcBufferFlags.Out) == 0) { continue; } PointerAndSize buffer = _bufferRanges[i]; - if (flags.HasFlag(HipcBufferFlags.Pointer)) + if ((flags & HipcBufferFlags.Pointer) != 0) { response.SendStatics[recvPointerIndex] = new HipcStaticDescriptor(buffer.Address, (ushort)buffer.Size, recvPointerIndex); } - else if (flags.HasFlag(HipcBufferFlags.AutoSelect)) + else if ((flags & HipcBufferFlags.AutoSelect) != 0) { if (!isBufferMapAlias[i]) { diff --git a/src/Ryujinx.Input.SDL2/SDL2Gamepad.cs b/src/Ryujinx.Input.SDL2/SDL2Gamepad.cs index 4b868efeb..39401b4be 100644 --- a/src/Ryujinx.Input.SDL2/SDL2Gamepad.cs +++ b/src/Ryujinx.Input.SDL2/SDL2Gamepad.cs @@ -88,7 +88,7 @@ namespace Ryujinx.Input.SDL2 _triggerThreshold = 0.0f; // Enable motion tracking - if (Features.HasFlag(GamepadFeaturesFlag.Motion)) + if ((Features & GamepadFeaturesFlag.Motion) != 0) { if (SDL_GameControllerSetSensorEnabled(_gamepadHandle, SDL_SensorType.SDL_SENSOR_ACCEL, SDL_bool.SDL_TRUE) != 0) { @@ -104,7 +104,7 @@ namespace Ryujinx.Input.SDL2 public void SetLed(uint packedRgb) { - if (!Features.HasFlag(GamepadFeaturesFlag.Led)) + if ((Features & GamepadFeaturesFlag.Led) == 0) return; byte red = packedRgb > 0 ? (byte)(packedRgb >> 16) : (byte)0; @@ -166,7 +166,7 @@ namespace Ryujinx.Input.SDL2 public void Rumble(float lowFrequency, float highFrequency, uint durationMs) { - if (!Features.HasFlag(GamepadFeaturesFlag.Rumble)) + if ((Features & GamepadFeaturesFlag.Rumble) == 0) return; ushort lowFrequencyRaw = (ushort)(lowFrequency * ushort.MaxValue); @@ -197,7 +197,7 @@ namespace Ryujinx.Input.SDL2 _ => SDL_SensorType.SDL_SENSOR_INVALID }; - if (!Features.HasFlag(GamepadFeaturesFlag.Motion) || sensorType is SDL_SensorType.SDL_SENSOR_INVALID) + if ((Features & GamepadFeaturesFlag.Motion) == 0 || sensorType is SDL_SensorType.SDL_SENSOR_INVALID) return Vector3.Zero; const int ElementCount = 3; @@ -232,7 +232,7 @@ namespace Ryujinx.Input.SDL2 { _configuration = (StandardControllerInputConfig)configuration; - if (Features.HasFlag(GamepadFeaturesFlag.Led) && _configuration.Led.EnableLed) + if ((Features & GamepadFeaturesFlag.Led) != 0 && _configuration.Led.EnableLed) { if (_configuration.Led.TurnOffLed) (this as IGamepad).ClearLed(); diff --git a/src/Ryujinx.Input.SDL2/SDL2JoyCon.cs b/src/Ryujinx.Input.SDL2/SDL2JoyCon.cs index ee80881c2..1762f19d3 100644 --- a/src/Ryujinx.Input.SDL2/SDL2JoyCon.cs +++ b/src/Ryujinx.Input.SDL2/SDL2JoyCon.cs @@ -82,7 +82,7 @@ namespace Ryujinx.Input.SDL2 Features = GetFeaturesFlag(); // Enable motion tracking - if (Features.HasFlag(GamepadFeaturesFlag.Motion)) + if ((Features & GamepadFeaturesFlag.Motion) != 0) { if (SDL_GameControllerSetSensorEnabled(_gamepadHandle, SDL_SensorType.SDL_SENSOR_ACCEL, SDL_bool.SDL_TRUE) != 0) @@ -162,7 +162,7 @@ namespace Ryujinx.Input.SDL2 public void Rumble(float lowFrequency, float highFrequency, uint durationMs) { - if (!Features.HasFlag(GamepadFeaturesFlag.Rumble)) + if ((Features & GamepadFeaturesFlag.Rumble) == 0) return; ushort lowFrequencyRaw = (ushort)(lowFrequency * ushort.MaxValue); @@ -194,7 +194,7 @@ namespace Ryujinx.Input.SDL2 _ => SDL_SensorType.SDL_SENSOR_INVALID }; - if (!Features.HasFlag(GamepadFeaturesFlag.Motion) || sensorType is SDL_SensorType.SDL_SENSOR_INVALID) + if ((Features & GamepadFeaturesFlag.Motion) == 0 || sensorType is SDL_SensorType.SDL_SENSOR_INVALID) return Vector3.Zero; const int ElementCount = 3; diff --git a/src/Ryujinx.Input/HLE/NpadController.cs b/src/Ryujinx.Input/HLE/NpadController.cs index b3979003e..dd8907a4b 100644 --- a/src/Ryujinx.Input/HLE/NpadController.cs +++ b/src/Ryujinx.Input/HLE/NpadController.cs @@ -5,6 +5,7 @@ using Ryujinx.Common.Configuration.Hid.Controller.Motion; using Ryujinx.Common.Logging; using Ryujinx.HLE.HOS.Services.Hid; using System; +using System.Buffers; using System.Collections.Concurrent; using System.Numerics; using System.Runtime.CompilerServices; @@ -291,7 +292,7 @@ namespace Ryujinx.Input.HLE { if (controllerConfig.Motion.MotionBackend == MotionInputBackendType.GamepadDriver) { - if (gamepad.Features.HasFlag(GamepadFeaturesFlag.Motion)) + if ((gamepad.Features & GamepadFeaturesFlag.Motion) != 0) { Vector3 accelerometer = gamepad.GetMotionData(MotionInputId.Accelerometer); Vector3 gyroscope = gamepad.GetMotionData(MotionInputId.Gyroscope); @@ -531,6 +532,8 @@ namespace Ryujinx.Input.HLE hidKeyboard.Modifier |= value << entry.Target; } + + ArrayPool.Shared.Return(keyboardState.KeysState); return hidKeyboard; diff --git a/src/Ryujinx.Input/HLE/NpadManager.cs b/src/Ryujinx.Input/HLE/NpadManager.cs index 21219d91b..e4fe9f15a 100644 --- a/src/Ryujinx.Input/HLE/NpadManager.cs +++ b/src/Ryujinx.Input/HLE/NpadManager.cs @@ -1,8 +1,10 @@ +using Ryujinx.Common; using Ryujinx.Common.Configuration.Hid; using Ryujinx.Common.Configuration.Hid.Controller; using Ryujinx.Common.Configuration.Hid.Keyboard; using Ryujinx.HLE.HOS.Services.Hid; using System; +using System.Buffers; using System.Collections.Generic; using System.Diagnostics; using System.Linq; @@ -18,6 +20,7 @@ namespace Ryujinx.Input.HLE { public class NpadManager : IDisposable { + private static readonly ObjectPool> _hleMotionStatesPool = new (() => new List(NpadDevices.MaxControllers)); private readonly CemuHookClient _cemuHookClient; private readonly Lock _lock = new(); @@ -215,7 +218,7 @@ namespace Ryujinx.Input.HLE lock (_lock) { List hleInputStates = []; - List hleMotionStates = new(NpadDevices.MaxControllers); + List hleMotionStates = _hleMotionStatesPool.Allocate(); KeyboardInput? hleKeyboardInput = null; @@ -317,6 +320,8 @@ namespace Ryujinx.Input.HLE Vector2 position = IMouse.GetScreenPosition(mouseInput.Position, mouse.ClientSize, aspectRatio); _device.Hid.Mouse.Update((int)position.X, (int)position.Y, buttons, (int)mouseInput.Scroll.X, (int)mouseInput.Scroll.Y, true); + + ArrayPool.Shared.Return(mouseInput.ButtonState); } else { @@ -324,6 +329,8 @@ namespace Ryujinx.Input.HLE } _device.TamperMachine.UpdateInput(hleInputStates); + + _hleMotionStatesPool.Release(hleMotionStates); } } diff --git a/src/Ryujinx.Input/IKeyboard.cs b/src/Ryujinx.Input/IKeyboard.cs index 2fc660112..7fecaaa5d 100644 --- a/src/Ryujinx.Input/IKeyboard.cs +++ b/src/Ryujinx.Input/IKeyboard.cs @@ -1,3 +1,4 @@ +using System.Buffers; using System.Runtime.CompilerServices; namespace Ryujinx.Input @@ -28,7 +29,8 @@ namespace Ryujinx.Input [MethodImpl(MethodImplOptions.AggressiveInlining)] static KeyboardStateSnapshot GetStateSnapshot(IKeyboard keyboard) { - bool[] keysState = new bool[(int)Key.Count]; + + bool[] keysState = ArrayPool.Shared.Rent((int)Key.Count); for (Key key = 0; key < Key.Count; key++) { diff --git a/src/Ryujinx.Input/IMouse.cs b/src/Ryujinx.Input/IMouse.cs index e20e7798d..45307ce06 100644 --- a/src/Ryujinx.Input/IMouse.cs +++ b/src/Ryujinx.Input/IMouse.cs @@ -1,3 +1,4 @@ +using System.Buffers; using System.Drawing; using System.Numerics; @@ -47,7 +48,7 @@ namespace Ryujinx.Input /// A snaphost of the state of the mouse. public static MouseStateSnapshot GetMouseStateSnapshot(IMouse mouse) { - bool[] buttons = new bool[(int)MouseButton.Count]; + bool[] buttons = ArrayPool.Shared.Rent((int)MouseButton.Count); mouse.Buttons.CopyTo(buttons, 0); diff --git a/src/Ryujinx.Input/KeyboardStateSnapshot.cs b/src/Ryujinx.Input/KeyboardStateSnapshot.cs index e0374a861..9b40b46db 100644 --- a/src/Ryujinx.Input/KeyboardStateSnapshot.cs +++ b/src/Ryujinx.Input/KeyboardStateSnapshot.cs @@ -7,7 +7,7 @@ namespace Ryujinx.Input /// public class KeyboardStateSnapshot { - private readonly bool[] _keysState; + public readonly bool[] KeysState; /// /// Create a new . @@ -15,7 +15,7 @@ namespace Ryujinx.Input /// The keys state public KeyboardStateSnapshot(bool[] keysState) { - _keysState = keysState; + KeysState = keysState; } /// @@ -24,6 +24,6 @@ namespace Ryujinx.Input /// The key /// True if the given key is pressed [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool IsPressed(Key key) => _keysState[(int)key]; + public bool IsPressed(Key key) => KeysState[(int)key]; } } diff --git a/src/Ryujinx.Input/MouseStateSnapshot.cs b/src/Ryujinx.Input/MouseStateSnapshot.cs index 9efc9f9c7..519d119c1 100644 --- a/src/Ryujinx.Input/MouseStateSnapshot.cs +++ b/src/Ryujinx.Input/MouseStateSnapshot.cs @@ -8,7 +8,7 @@ namespace Ryujinx.Input /// public class MouseStateSnapshot { - private readonly bool[] _buttonState; + public readonly bool[] ButtonState; /// /// The position of the mouse cursor @@ -28,7 +28,7 @@ namespace Ryujinx.Input /// The scroll delta public MouseStateSnapshot(bool[] buttonState, Vector2 position, Vector2 scroll) { - _buttonState = buttonState; + ButtonState = buttonState; Position = position; Scroll = scroll; @@ -40,6 +40,6 @@ namespace Ryujinx.Input /// The button /// True if the given button is pressed [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool IsPressed(MouseButton button) => _buttonState[(int)button]; + public bool IsPressed(MouseButton button) => ButtonState[(int)button]; } } diff --git a/src/Ryujinx.Memory/Range/NonOverlappingRangeList.cs b/src/Ryujinx.Memory/Range/NonOverlappingRangeList.cs index b493134c9..7560d2ae7 100644 --- a/src/Ryujinx.Memory/Range/NonOverlappingRangeList.cs +++ b/src/Ryujinx.Memory/Range/NonOverlappingRangeList.cs @@ -1,4 +1,5 @@ using System; +using System.Buffers; using System.Collections.Generic; using System.Diagnostics; using System.Runtime.CompilerServices; @@ -38,7 +39,7 @@ namespace Ryujinx.Memory.Range index = ~index; } - RangeItem rangeItem = new(item); + RangeItem rangeItem = _rangeItemPool.Allocate().Set(item); Insert(index, rangeItem); } @@ -144,6 +145,8 @@ namespace Ryujinx.Memory.Range [MethodImpl(MethodImplOptions.AggressiveInlining)] private void RemoveAt(int index) { + _rangeItemPool.Release(Items[index]); + if (index < Count - 1) { Items[index + 1].Previous = index > 0 ? Items[index - 1] : null; @@ -433,7 +436,7 @@ namespace Ryujinx.Memory.Range return (Items[index], Items[endIndex - 1]); } - public RangeItem[] FindOverlapsAsArray(ulong address, ulong size) + public RangeItem[] FindOverlapsAsArray(ulong address, ulong size, out int length) { (int index, int endIndex) = BinarySearchEdges(address, address + size); @@ -441,11 +444,13 @@ namespace Ryujinx.Memory.Range if (index < 0) { - result = []; + result = null; + length = 0; } else { - result = new RangeItem[endIndex - index]; + result = ArrayPool>.Shared.Rent(endIndex - index); + length = endIndex - index; Array.Copy(Items, index, result, 0, endIndex - index); } diff --git a/src/Ryujinx.Memory/Range/RangeList.cs b/src/Ryujinx.Memory/Range/RangeList.cs index be0e34653..63025f1e8 100644 --- a/src/Ryujinx.Memory/Range/RangeList.cs +++ b/src/Ryujinx.Memory/Range/RangeList.cs @@ -36,8 +36,6 @@ namespace Ryujinx.Memory.Range public class RangeList : RangeListBase where T : IRange { public readonly ReaderWriterLockSlim Lock = new(); - - private readonly Dictionary> _quickAccess = new(AddressEqualityComparer.Comparer); /// /// Creates a new range list. @@ -93,11 +91,6 @@ namespace Ryujinx.Memory.Range Items[index + 1].Previous = rangeItem; } - foreach (ulong address in Items[index].QuickAccessAddresses) - { - _quickAccess.Remove(address); - } - Items[index] = rangeItem; return true; @@ -142,11 +135,6 @@ namespace Ryujinx.Memory.Range Items[index + 1].Previous = rangeItem; } - foreach (ulong address in item.QuickAccessAddresses) - { - _quickAccess.Remove(address); - } - Items[index] = rangeItem; return true; @@ -210,11 +198,6 @@ namespace Ryujinx.Memory.Range [MethodImpl(MethodImplOptions.AggressiveInlining)] private void RemoveAt(int index) { - foreach (ulong address in Items[index].QuickAccessAddresses) - { - _quickAccess.Remove(address); - } - if (index < Count - 1) { Items[index + 1].Previous = index > 0 ? Items[index - 1] : null; @@ -253,15 +236,6 @@ namespace Ryujinx.Memory.Range int startIndex = BinarySearch(startItem.Address); int endIndex = BinarySearch(endItem.Address); - for (int i = startIndex; i <= endIndex; i++) - { - _quickAccess.Remove(Items[i].Address); - foreach (ulong addr in Items[i].QuickAccessAddresses) - { - _quickAccess.Remove(addr); - } - } - if (endIndex < Count - 1) { Items[endIndex + 1].Previous = startIndex > 0 ? Items[startIndex - 1] : null; @@ -349,11 +323,6 @@ namespace Ryujinx.Memory.Range [MethodImpl(MethodImplOptions.AggressiveInlining)] public override RangeItem FindOverlapFast(ulong address, ulong size) { - if (_quickAccess.TryGetValue(address, out RangeItem quickResult)) - { - return quickResult; - } - int index = BinarySearch(address, address + size); if (index < 0) @@ -361,12 +330,6 @@ namespace Ryujinx.Memory.Range return null; } - if (Items[index].OverlapsWith(address, address + 1)) - { - _quickAccess.Add(address, Items[index]); - Items[index].QuickAccessAddresses.Add(address); - } - return Items[index]; } diff --git a/src/Ryujinx.Memory/Range/RangeListBase.cs b/src/Ryujinx.Memory/Range/RangeListBase.cs index a1a48b087..01fe1b0dc 100644 --- a/src/Ryujinx.Memory/Range/RangeListBase.cs +++ b/src/Ryujinx.Memory/Range/RangeListBase.cs @@ -1,20 +1,42 @@ -using System.Collections; +using Ryujinx.Common; +using System.Collections; using System.Collections.Generic; using System.Runtime.CompilerServices; namespace Ryujinx.Memory.Range { - public class RangeItem(TValue value) where TValue : IRange + public class RangeItem where TValue : IRange { public RangeItem Next; public RangeItem Previous; - public readonly ulong Address = value.Address; - public readonly ulong EndAddress = value.Address + value.Size; + public ulong Address; + public ulong EndAddress; - public readonly TValue Value = value; + public TValue Value; + + public RangeItem() + { + + } + + public RangeItem(TValue value) + { + Address = value.Address; + EndAddress = value.Address + value.Size; + Value = value; + } - public readonly List QuickAccessAddresses = []; + public RangeItem Set(TValue value) + { + Next = null; + Previous = null; + Address = value.Address; + EndAddress = value.Address + value.Size; + Value = value; + + return this; + } [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool OverlapsWith(ulong address, ulong endAddress) @@ -23,20 +45,9 @@ namespace Ryujinx.Memory.Range } } - class AddressEqualityComparer : IEqualityComparer - { - public bool Equals(ulong u1, ulong u2) - { - return u1 == u2; - } - - public int GetHashCode(ulong value) => (int)(value << 5); - - public static readonly AddressEqualityComparer Comparer = new(); - } - public unsafe abstract class RangeListBase : IEnumerable where T : IRange { + protected static readonly ObjectPool> _rangeItemPool = new(() => new RangeItem()); private const int BackingInitialSize = 1024; protected RangeItem[] Items; diff --git a/src/Ryujinx.Memory/Tracking/MemoryTracking.cs b/src/Ryujinx.Memory/Tracking/MemoryTracking.cs index cd97e6fdf..b4b06da8c 100644 --- a/src/Ryujinx.Memory/Tracking/MemoryTracking.cs +++ b/src/Ryujinx.Memory/Tracking/MemoryTracking.cs @@ -1,5 +1,6 @@ using Ryujinx.Memory.Range; using System; +using System.Buffers; using System.Collections.Generic; namespace Ryujinx.Memory.Tracking @@ -300,10 +301,10 @@ namespace Ryujinx.Memory.Tracking // We use the non-span method here because keeping the lock will cause a deadlock. regions.Lock.EnterReadLock(); - RangeItem[] overlaps = regions.FindOverlapsAsArray(address, size); + RangeItem[] overlaps = regions.FindOverlapsAsArray(address, size, out int length); regions.Lock.ExitReadLock(); - if (overlaps.Length == 0 && !precise) + if (length == 0 && !precise) { if (_memoryManager.IsRangeMapped(address, size)) { @@ -323,8 +324,8 @@ namespace Ryujinx.Memory.Tracking // Increase the access size to trigger handles with misaligned accesses. size += (ulong)_pageSize; } - - for (int i = 0; i < overlaps.Length; i++) + + for (int i = 0; i < length; i++) { VirtualRegion region = overlaps[i].Value; @@ -337,6 +338,11 @@ namespace Ryujinx.Memory.Tracking region.Signal(address, size, write, exemptId); } } + + if (length != 0) + { + ArrayPool>.Shared.Return(overlaps); + } } } diff --git a/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs b/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs index fe2dd4db8..7b3857760 100644 --- a/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs @@ -95,7 +95,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input public bool IsRight { get; set; } public bool IsLeft { get; set; } public string RevertDeviceId { get; set; } - public bool HasLed => SelectedGamepad.Features.HasFlag(GamepadFeaturesFlag.Led); + public bool HasLed => (SelectedGamepad.Features & GamepadFeaturesFlag.Led) != 0; public bool CanClearLed => SelectedGamepad.Name.ContainsIgnoreCase("DualSense"); public event Action NotifyChangesEvent; From 2c9b1930185269e2280cd6cfa188424de29a9d06 Mon Sep 17 00:00:00 2001 From: LotP <22-lotp@users.noreply.git.ryujinx.app> Date: Thu, 30 Oct 2025 21:09:24 -0500 Subject: [PATCH 2/2] Fix compiler warning (ryubing/ryujinx!203) See merge request ryubing/ryujinx!203 --- assets/locales.json | 23 +++++++++---------- .../UI/Applet/ProfileSelectorDialog.axaml.cs | 6 +++++ .../UI/Views/Input/LedInputView.axaml.cs | 6 +++++ .../GameSpecificSettingsWindow.axaml.cs | 6 +++++ 4 files changed, 29 insertions(+), 12 deletions(-) diff --git a/assets/locales.json b/assets/locales.json index 4fd582866..02f7d033c 100644 --- a/assets/locales.json +++ b/assets/locales.json @@ -295,7 +295,7 @@ { "ID": "MenuBarFileOpenFromFile", "Translations": { - "ar_SA": "_تحميل التطبيق...", + "ar_SA": "_تحميل التطبيق...", "de_DE": "_Anwendung laden...", "el_GR": "_Φόρτωση εφαρμογής...", "en_US": "_Load Application...", @@ -1346,7 +1346,7 @@ "ID": "MenuBarHelpMultiplayer", "Translations": { "ar_SA": "متعدد اللاعبين (LDN/LAN)", - "de_DE": "Multiplayer (LDN/LAN)", + "de_DE": "", "el_GR": "Πολλαπλοί Παίκτες (LDN/LAN)", "en_US": "Multiplayer (LDN/LAN)", "es_ES": "Multijugador (LDN/LAN)", @@ -2527,7 +2527,7 @@ "es_ES": "Logotipo", "fr_FR": null, "he_IL": "", - "it_IT": "Logo", + "it_IT": "", "ja_JP": "ロゴ", "ko_KR": "로고", "no_NO": "", @@ -3250,14 +3250,14 @@ "el_GR": "Διεπαφή", "en_US": "Interface", "es_ES": "Interfaz", - "fr_FR": "Interface", + "fr_FR": "", "he_IL": "ממשק", "it_IT": "Interfaccia", "ja_JP": "インターフェース", "ko_KR": "인터페이스", "no_NO": "Grensesnitt", "pl_PL": "Interfejs", - "pt_BR": "Interface", + "pt_BR": "", "ru_RU": "Интерфейс", "sv_SE": "Gränssnitt", "th_TH": "อินเทอร์เฟซ", @@ -4027,7 +4027,7 @@ "es_ES": null, "fr_FR": "Australie", "he_IL": "אוסטרליה", - "it_IT": "Australia", + "it_IT": "", "ja_JP": "オーストラリア", "ko_KR": "호주", "no_NO": "", @@ -6465,7 +6465,6 @@ "uk_UA": "Я хочу скинути налаштування.", "zh_CN": "我要重置我的设置。", "zh_TW": "我想重設我的設定。" - } }, { @@ -19272,7 +19271,7 @@ "ID": "SettingsTabHotkeys", "Translations": { "ar_SA": "اختصارات", - "de_DE": "Hotkeys", + "de_DE": "", "el_GR": "Συντομεύσεις", "en_US": "Hotkeys", "es_ES": "Atajos", @@ -19578,7 +19577,7 @@ "es_ES": null, "fr_FR": null, "he_IL": "אמיבו", - "it_IT": "Amiibo", + "it_IT": "", "ja_JP": "", "ko_KR": null, "no_NO": "", @@ -23025,7 +23024,7 @@ "de_DE": "Emulierte vertikale Synchronisation. \"Switch\" emuliert die 60Hz-Bildwiederholfrequenz der Switch. \"Unbounded\" ist eine unbegrenzte Bildwiederholfrequenz.", "el_GR": "", "en_US": "Emulated Vertical Sync. 'Switch' emulates the Switch's refresh rate of 60Hz. 'Unbounded' is an unbounded refresh rate.", - "es_ES": "Sincronización vertical emulada. ‘Switch’ emula la frecuencia de actualización de la Switch de 60 Hz. ‘Sin límite’ es una frecuencia de actualización sin límite.", + "es_ES": "Sincronización vertical emulada. ‘Switch’ emula la frecuencia de actualización de la Switch de 60\u202FHz. ‘Sin límite’ es una frecuencia de actualización sin límite.", "fr_FR": "VSync émulé. 'Switch' émule le taux de rafraîchissement de la Switch (60Hz). 'Sans Limite' est un taux de rafraîchissement qui n'est pas limité.", "he_IL": "", "it_IT": "Sincronizzazione verticale emulata. \"Switch\" emula la frequenza di aggiornamento di Nintendo Switch (60Hz). \"Nessun limite\" non impone alcun limite alla frequenza di aggiornamento.", @@ -23050,7 +23049,7 @@ "de_DE": "Emulierte vertikale Synchronisation. \"Switch\" emuliert die 60Hz-Bildwiederholfrequenz der Switch. „Unbounded“ ist eine unbegrenzte Bildwiederholfrequenz. „Benutzerdefinierte Bildwiederholfrequenz“ emuliert die angegebene benutzerdefinierte Bildwiederholfrequenz.", "el_GR": "", "en_US": "Emulated Vertical Sync. 'Switch' emulates the Switch's refresh rate of 60Hz. 'Unbounded' is an unbounded refresh rate. 'Custom Refresh Rate' emulates the specified custom refresh rate.", - "es_ES": "Sincronización Vertical Emulada. ‘Switch’ emula la frecuencia de actualización de la Switch de 60 Hz. ‘Sin límite’ es una frecuencia de actualización sin límite. ‘Frecuencia de actualización personalizada’ emula la frecuencia de actualización personalizada especificada.", + "es_ES": "Sincronización Vertical Emulada. ‘Switch’ emula la frecuencia de actualización de la Switch de 60\u202FHz. ‘Sin límite’ es una frecuencia de actualización sin límite. ‘Frecuencia de actualización personalizada’ emula la frecuencia de actualización personalizada especificada.", "fr_FR": "VSync émulé. 'Switch' émule le taux de rafraîchissement de la Switch (60Hz). 'Sans Limite' est un taux de rafraîchissement qui n'est pas limité. 'Taux de Rafraîchissement Customisé' émule le taux de rafraîchissement spécifié.", "he_IL": "", "it_IT": "Sincronizzazione verticale emulata. \"Switch\" emula la frequenza di aggiornamento di Nintendo Switch (60Hz). \"Nessun limite\" non impone alcun limite alla frequenza di aggiornamento. \"Frequenza di aggiornamento personalizzata\" emula la frequenza di aggiornamento specificata.", @@ -24844,4 +24843,4 @@ } } ] -} +} \ No newline at end of file diff --git a/src/Ryujinx/UI/Applet/ProfileSelectorDialog.axaml.cs b/src/Ryujinx/UI/Applet/ProfileSelectorDialog.axaml.cs index 838a2f10a..6fdfaab5a 100644 --- a/src/Ryujinx/UI/Applet/ProfileSelectorDialog.axaml.cs +++ b/src/Ryujinx/UI/Applet/ProfileSelectorDialog.axaml.cs @@ -18,6 +18,12 @@ namespace Ryujinx.Ava.UI.Applet { public partial class ProfileSelectorDialog : RyujinxControl { + //Fix compiler warning + public ProfileSelectorDialog() + { + + } + public ProfileSelectorDialog(ProfileSelectorDialogViewModel viewModel) { DataContext = ViewModel = viewModel; diff --git a/src/Ryujinx/UI/Views/Input/LedInputView.axaml.cs b/src/Ryujinx/UI/Views/Input/LedInputView.axaml.cs index cc1166dd7..dab553f82 100644 --- a/src/Ryujinx/UI/Views/Input/LedInputView.axaml.cs +++ b/src/Ryujinx/UI/Views/Input/LedInputView.axaml.cs @@ -10,6 +10,12 @@ namespace Ryujinx.UI.Views.Input { public partial class LedInputView : RyujinxControl { + //Fix compiler warning + public LedInputView() + { + + } + public LedInputView(ControllerInputViewModel viewModel) { ViewModel = new LedInputViewModel diff --git a/src/Ryujinx/UI/Windows/GameSpecificSettingsWindow.axaml.cs b/src/Ryujinx/UI/Windows/GameSpecificSettingsWindow.axaml.cs index dc9ce56d6..6fe583224 100644 --- a/src/Ryujinx/UI/Windows/GameSpecificSettingsWindow.axaml.cs +++ b/src/Ryujinx/UI/Windows/GameSpecificSettingsWindow.axaml.cs @@ -11,6 +11,12 @@ namespace Ryujinx.Ava.UI.Windows { internal readonly SettingsViewModel ViewModel; + //Fix compiler warning + public GameSpecificSettingsWindow() + { + + } + public GameSpecificSettingsWindow(MainWindowViewModel viewModel, bool findUserConfigDir = true) { Title = string.Format(LocaleManager.Instance[LocaleKeys.SettingsWithInfo], viewModel.SelectedApplication.Name, viewModel.SelectedApplication.IdString);