diff --git a/src/ARMeilleure/CodeGen/X86/Assembler.cs b/src/ARMeilleure/CodeGen/X86/Assembler.cs index 7062564e3..727e2bc61 100644 --- a/src/ARMeilleure/CodeGen/X86/Assembler.cs +++ b/src/ARMeilleure/CodeGen/X86/Assembler.cs @@ -1106,17 +1106,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 80405ee75..e6af538a4 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; @@ -30,7 +31,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]; @@ -83,7 +86,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++) { @@ -95,7 +98,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 251c84ac2..7ad4e979a 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 1c9b962f9..81f8fdb8b 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 144999d6d..ff499d736 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 3b06cd7ec..c235d789f 100644 --- a/src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs +++ b/src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs @@ -645,7 +645,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 var screenScissor = ref _state.State.ScreenScissorState; y = screenScissor.Height - height - y; @@ -729,7 +729,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed var 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(); @@ -1229,7 +1229,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) { @@ -1520,7 +1520,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 b759db5b5..17b554bc2 100644 --- a/src/Ryujinx.Graphics.Gpu/GpuContext.cs +++ b/src/Ryujinx.Graphics.Gpu/GpuContext.cs @@ -371,9 +371,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) { @@ -392,24 +392,37 @@ namespace Ryujinx.Graphics.Gpu } } - if (force || _pendingSync || (syncpoint && SyncpointActions.Count > 0)) + if (force || _pendingSync || (syncPoint && SyncpointActions.Count > 0)) { foreach (var action in SyncActions) { - action.SyncPreAction(syncpoint); + action.SyncPreAction(syncPoint); } foreach (var 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 188697e0a..c4215f196 100644 --- a/src/Ryujinx.Graphics.Gpu/Image/Texture.cs +++ b/src/Ryujinx.Graphics.Gpu/Image/Texture.cs @@ -1616,7 +1616,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 0d104418a..6c9aa85c8 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; } /// @@ -254,29 +262,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, _) => + 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; } @@ -288,15 +300,19 @@ namespace Ryujinx.Graphics.Gpu.Image /// The texture being discarded public void DiscardData(Texture texture) { - EvaluateRelevantHandles(texture, (baseHandle, regionCount, _) => + 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; } /// @@ -308,7 +324,7 @@ namespace Ryujinx.Graphics.Gpu.Image { FlushIncompatibleOverlapsIfNeeded(); - EvaluateRelevantHandles(texture, (baseHandle, regionCount, split) => + EvaluateRelevantHandles(texture, (baseHandle, regionCount, split, _) => { bool dirty = false; bool anyModified = false; @@ -384,7 +400,9 @@ namespace Ryujinx.Graphics.Gpu.Image texture.SynchronizeFull(); } } - }); + + return true; + }, out _); } /// @@ -461,7 +479,7 @@ namespace Ryujinx.Graphics.Gpu.Image /// The texture to synchronize dependents of public void SynchronizeDependents(Texture texture) { - EvaluateRelevantHandles(texture, (baseHandle, regionCount, _) => + EvaluateRelevantHandles(texture, (baseHandle, regionCount, _, _) => { for (int i = 0; i < regionCount; i++) { @@ -469,7 +487,9 @@ namespace Ryujinx.Graphics.Gpu.Image group.SynchronizeDependents(); } - }); + + return true; + }, out _); } /// @@ -551,7 +571,7 @@ namespace Ryujinx.Graphics.Gpu.Image tracked = tracked || ShouldFlushTriggerTracking(); bool flushed = false; - EvaluateRelevantHandles(texture, (baseHandle, regionCount, split) => + EvaluateRelevantHandles(texture, (baseHandle, regionCount, split, _) => { int startSlice = 0; int endSlice = 0; @@ -605,7 +625,9 @@ namespace Ryujinx.Graphics.Gpu.Image flushed = true; } - }); + + return true; + }, out _); Storage.SignalModifiedDirty(); @@ -694,7 +716,7 @@ namespace Ryujinx.Graphics.Gpu.Image ClearIncompatibleOverlaps(texture); - EvaluateRelevantHandles(texture, (baseHandle, regionCount, _) => + EvaluateRelevantHandles(texture, (baseHandle, regionCount, _, _) => { for (int i = 0; i < regionCount; i++) { @@ -702,7 +724,9 @@ namespace Ryujinx.Graphics.Gpu.Image group.SignalModified(_context); } - }); + + return true; + }, out _); } /// @@ -715,16 +739,20 @@ namespace Ryujinx.Graphics.Gpu.Image ModifiedSequence = _context.GetModifiedSequence(); ClearIncompatibleOverlaps(texture); - - EvaluateRelevantHandles(texture, (baseHandle, regionCount, _) => + + 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; } /// @@ -768,16 +796,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); } /// @@ -792,11 +820,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; @@ -809,7 +839,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 { @@ -823,7 +853,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); @@ -840,7 +870,7 @@ namespace Ryujinx.Graphics.Gpu.Image totalSize += layerCount; } - callback(firstLayer + levelIndex, totalSize); + result = callback(firstLayer + levelIndex, totalSize, specialData: specialData); } } } @@ -857,12 +887,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); } } } @@ -1441,8 +1471,16 @@ namespace Ryujinx.Graphics.Gpu.Image var targetRange = new List<(int BaseHandle, int RegionCount)>(); var otherRange = new List<(int BaseHandle, int RegionCount)>(); - EvaluateRelevantHandles(firstLayer, firstLevel, other.Info.GetSlices(), other.Info.Levels, (baseHandle, regionCount, _) => targetRange.Add((baseHandle, regionCount))); - otherGroup.EvaluateRelevantHandles(other, (baseHandle, regionCount, _) => otherRange.Add((baseHandle, regionCount))); + EvaluateRelevantHandles(firstLayer, firstLevel, other.Info.GetSlices(), other.Info.Levels, (baseHandle, regionCount, _, _) => + { + targetRange.Add((baseHandle, regionCount)); + return true; + }, out _); + otherGroup.EvaluateRelevantHandles(other, (baseHandle, regionCount, _, _) => + { + 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 1d9cff1c4..b9cbbb0df 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 6f5f1628a..3ad4afb3d 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs @@ -395,7 +395,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; } /// @@ -902,7 +902,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); @@ -928,7 +928,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); @@ -944,7 +944,7 @@ namespace Ryujinx.Graphics.Gpu.Shader } } - if (specState._queriedState.HasFlag(QueriedStateFlags.TextureArrayFromPool)) + if ((specState._queriedState & QueriedStateFlags.TextureArrayFromPool) == QueriedStateFlags.TextureArrayFromPool) { dataReader.Read(ref count); @@ -1007,7 +1007,7 @@ namespace Ryujinx.Graphics.Gpu.Shader } } - if (_queriedState.HasFlag(QueriedStateFlags.TransformFeedback)) + if ((_queriedState & QueriedStateFlags.TransformFeedback) == QueriedStateFlags.TransformFeedback) { ushort tfCount = (ushort)TransformFeedbackDescriptors.Length; dataWriter.Write(ref tfCount); @@ -1030,7 +1030,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); @@ -1045,7 +1045,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 d9e0ef13c..c081715d5 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 b1fea0d09..ddbee09f5 100644 --- a/src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs +++ b/src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs @@ -1,7 +1,9 @@ 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; namespace Ryujinx.Graphics.Vulkan @@ -9,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) @@ -49,7 +52,7 @@ namespace Ryujinx.Graphics.Vulkan else { _colors = [view]; - _colorsCanonical = _colors; + _colorsCanonical = [view]; } Width = width; @@ -63,6 +66,7 @@ namespace Ryujinx.Graphics.Vulkan LogicOpsAllowed = !format.IsFloatOrSrgb(); AttachmentsCount = 1; + _totalCount = 1; HasDepthStencil = isDepthStencil; } @@ -132,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; @@ -158,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; } @@ -182,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) { var format = _colors[index].Info.Format; @@ -217,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) @@ -232,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; } @@ -243,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, @@ -251,14 +395,13 @@ namespace Ryujinx.Graphics.Vulkan }; api.CreateFramebuffer(_device, in framebufferCreateInfo, null, out var 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() { - var result = new TextureView[_attachments.Length]; - - _colors?.CopyTo(result, 0); + var result = new TextureView[AttachmentsCount]; + _colors?.AsSpan(..ColorAttachmentsCount).CopyTo(result.AsSpan()); if (_depthStencil != null) { @@ -277,8 +420,11 @@ namespace Ryujinx.Graphics.Vulkan { if (_colors != null) { - foreach (var 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); } @@ -293,8 +439,11 @@ namespace Ryujinx.Graphics.Vulkan { if (_colors != null) { - foreach (var color in _colors) + int count = ColorAttachmentsCount; + + for (int i = 0; i < count; i++) { + TextureView color = _colors[i]; color.Storage?.AddStoreOpUsage(false); } } @@ -306,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(); } @@ -316,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 b42524712..b141d3870 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++) { - var fence = _fences[i]; + var 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++) { - var fence = _fences[i]; + var 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 c4b20a618..ed60892f6 100644 --- a/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs +++ b/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs @@ -1448,7 +1448,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) { @@ -1466,18 +1466,18 @@ namespace Ryujinx.Graphics.Vulkan protected void UpdatePipelineAttachmentFormats() { var 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 4b05a8669..d45c4921c 100644 --- a/src/Ryujinx.Graphics.Vulkan/RenderPassHolder.cs +++ b/src/Ryujinx.Graphics.Vulkan/RenderPassHolder.cs @@ -182,17 +182,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 92936093b..432f4afa9 100644 --- a/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTableBase.cs +++ b/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTableBase.cs @@ -1005,7 +1005,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory } else { - return new KMemoryInfo( + return KMemoryInfo.Pool.Allocate().Set( AddrSpaceEnd, ~AddrSpaceEnd + 1, MemoryState.Reserved, @@ -2557,10 +2557,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 || @@ -2572,11 +2572,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; @@ -2595,16 +2600,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; } @@ -2654,6 +2669,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 && @@ -2734,6 +2751,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory allocationEndAddr <= regionEndAddr && allocationEndAddr <= currEndAddr) { + KMemoryInfo.Pool.Release(info); return address; } } @@ -2744,9 +2762,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 2356b1bf7..d6d51b938 100644 --- a/src/Ryujinx.HLE/HOS/Kernel/Process/HleProcessDebugger.cs +++ b/src/Ryujinx.HLE/HOS/Kernel/Process/HleProcessDebugger.cs @@ -383,6 +383,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Process } var 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 1219ad442..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) { - var candidates = threads.Where(predicate).OrderBy(x => x.DynamicPriority); - var 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 cb001ef0f..fd1ba4d9b 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 6d716f92d..510fbd3b3 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 dc34f791a..7b6e85654 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: var 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++; } @@ -149,17 +149,17 @@ namespace Ryujinx.Horizon.Sdk.Sf var 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)) */ { - var descriptor = flags.HasFlag(HipcBufferFlags.In) + var descriptor = (flags & HipcBufferFlags.In) != 0 ? context.Request.Data.SendBuffers[sendMapAliasIndex] : context.Request.Data.ReceiveBuffers[recvMapAliasIndex]; @@ -170,7 +170,7 @@ namespace Ryujinx.Horizon.Sdk.Sf if (isMapAlias) { - var descriptor = flags.HasFlag(HipcBufferFlags.In) + var descriptor = (flags & HipcBufferFlags.In) != 0 ? context.Request.Data.SendBuffers[sendMapAliasIndex++] : context.Request.Data.ReceiveBuffers[recvMapAliasIndex++]; @@ -183,7 +183,7 @@ namespace Ryujinx.Horizon.Sdk.Sf } else { - if (flags.HasFlag(HipcBufferFlags.In)) + if ((flags & HipcBufferFlags.In) != 0) { var descriptor = context.Request.Data.SendStatics[sendPointerIndex++]; ulong address = descriptor.Address; @@ -196,11 +196,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; } @@ -233,12 +233,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; } @@ -258,18 +258,18 @@ namespace Ryujinx.Horizon.Sdk.Sf } var flags = _args[i].BufferFlags; - if (!flags.HasFlag(HipcBufferFlags.Out)) + if ((flags & HipcBufferFlags.Out) == 0) { continue; } var 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 3a422a9dd..0534b1646 100644 --- a/src/Ryujinx.Input.SDL2/SDL2Gamepad.cs +++ b/src/Ryujinx.Input.SDL2/SDL2Gamepad.cs @@ -84,7 +84,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) { @@ -145,7 +145,7 @@ namespace Ryujinx.Input.SDL2 public void Rumble(float lowFrequency, float highFrequency, uint durationMs) { - if (Features.HasFlag(GamepadFeaturesFlag.Rumble)) + if ((Features & GamepadFeaturesFlag.Rumble) == 0) { ushort lowFrequencyRaw = (ushort)(lowFrequency * ushort.MaxValue); ushort highFrequencyRaw = (ushort)(highFrequency * ushort.MaxValue); @@ -184,32 +184,32 @@ namespace Ryujinx.Input.SDL2 sensorType = SDL_SensorType.SDL_SENSOR_GYRO; } - if (Features.HasFlag(GamepadFeaturesFlag.Motion) && sensorType != SDL_SensorType.SDL_SENSOR_INVALID) + if ((Features & GamepadFeaturesFlag.Motion) == 0 || sensorType is SDL_SensorType.SDL_SENSOR_INVALID) + return Vector3.Zero; + + const int ElementCount = 3; + + unsafe { - const int ElementCount = 3; + float* values = stackalloc float[ElementCount]; - unsafe + int result = SDL_GameControllerGetSensorData(_gamepadHandle, sensorType, (nint)values, ElementCount); + + if (result == 0) { - float* values = stackalloc float[ElementCount]; + Vector3 value = new(values[0], values[1], values[2]); - int result = SDL_GameControllerGetSensorData(_gamepadHandle, sensorType, (nint)values, ElementCount); - - if (result == 0) + if (inputId == MotionInputId.Gyroscope) { - Vector3 value = new(values[0], values[1], values[2]); - - if (inputId == MotionInputId.Gyroscope) - { - return RadToDegree(value); - } - - if (inputId == MotionInputId.Accelerometer) - { - return GsToMs2(value); - } - - return value; + return RadToDegree(value); } + + if (inputId == MotionInputId.Accelerometer) + { + return GsToMs2(value); + } + + return value; } } diff --git a/src/Ryujinx.Input.SDL2/SDL2JoyCon.cs b/src/Ryujinx.Input.SDL2/SDL2JoyCon.cs index ed3edfe25..60b7b0857 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) @@ -163,7 +163,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); @@ -195,7 +195,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 8e0bd0996..e146788ee 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 29711bf4e..e6d4dfd43 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; @@ -17,6 +19,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(); @@ -204,8 +207,8 @@ namespace Ryujinx.Input.HLE { lock (_lock) { - List hleInputStates = new(); - List hleMotionStates = new(NpadDevices.MaxControllers); + List hleInputStates = []; + List hleMotionStates = _hleMotionStatesPool.Allocate(); KeyboardInput? hleKeyboardInput = null; @@ -309,6 +312,8 @@ namespace Ryujinx.Input.HLE var 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 @@ -317,6 +322,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 3511e7213..6c3201474 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); + } } }