diff --git a/.gitignore b/.gitignore
index 0c46c50c0..6f887e638 100644
--- a/.gitignore
+++ b/.gitignore
@@ -100,6 +100,7 @@ DocProject/Help/html
# Click-Once directory
publish/
+RyubingMaintainerTools/
# Publish Web Output
*.Publish.xml
diff --git a/src/ARMeilleure/Instructions/InstEmitException.cs b/src/ARMeilleure/Instructions/InstEmitException.cs
index d30fb2fbd..a91716c64 100644
--- a/src/ARMeilleure/Instructions/InstEmitException.cs
+++ b/src/ARMeilleure/Instructions/InstEmitException.cs
@@ -19,7 +19,7 @@ namespace ARMeilleure.Instructions
context.LoadFromContext();
- context.Return(Const(op.Address));
+ InstEmitFlowHelper.EmitReturn(context, Const(op.Address));
}
public static void Svc(ArmEmitterContext context)
@@ -49,7 +49,7 @@ namespace ARMeilleure.Instructions
context.LoadFromContext();
- context.Return(Const(op.Address));
+ InstEmitFlowHelper.EmitReturn(context, Const(op.Address));
}
}
}
diff --git a/src/ARMeilleure/Instructions/InstEmitException32.cs b/src/ARMeilleure/Instructions/InstEmitException32.cs
index 57af1522b..e5bad56ef 100644
--- a/src/ARMeilleure/Instructions/InstEmitException32.cs
+++ b/src/ARMeilleure/Instructions/InstEmitException32.cs
@@ -33,7 +33,7 @@ namespace ARMeilleure.Instructions
context.LoadFromContext();
- context.Return(Const(context.CurrOp.Address));
+ InstEmitFlowHelper.EmitReturn(context, Const(context.CurrOp.Address));
}
}
}
diff --git a/src/ARMeilleure/Instructions/InstEmitFlow.cs b/src/ARMeilleure/Instructions/InstEmitFlow.cs
index a986bf66f..cb214d3d5 100644
--- a/src/ARMeilleure/Instructions/InstEmitFlow.cs
+++ b/src/ARMeilleure/Instructions/InstEmitFlow.cs
@@ -66,7 +66,7 @@ namespace ARMeilleure.Instructions
{
OpCodeBReg op = (OpCodeBReg)context.CurrOp;
- context.Return(GetIntOrZR(context, op.Rn));
+ EmitReturn(context, GetIntOrZR(context, op.Rn));
}
public static void Tbnz(ArmEmitterContext context) => EmitTb(context, onNotZero: true);
diff --git a/src/ARMeilleure/Instructions/InstEmitFlowHelper.cs b/src/ARMeilleure/Instructions/InstEmitFlowHelper.cs
index d0871b29f..74866f982 100644
--- a/src/ARMeilleure/Instructions/InstEmitFlowHelper.cs
+++ b/src/ARMeilleure/Instructions/InstEmitFlowHelper.cs
@@ -13,6 +13,10 @@ namespace ARMeilleure.Instructions
{
static class InstEmitFlowHelper
{
+ // How many calls we can have in our call stack before we give up and return to the dispatcher.
+ // This prevents stack overflows caused by deep recursive calls.
+ private const int MaxCallDepth = 200;
+
public static void EmitCondBranch(ArmEmitterContext context, Operand target, Condition cond)
{
if (cond != Condition.Al)
@@ -182,12 +186,7 @@ namespace ARMeilleure.Instructions
{
if (isReturn || context.IsSingleStep)
{
- if (target.Type == OperandType.I32)
- {
- target = context.ZeroExtend32(OperandType.I64, target);
- }
-
- context.Return(target);
+ EmitReturn(context, target);
}
else
{
@@ -195,6 +194,19 @@ namespace ARMeilleure.Instructions
}
}
+ public static void EmitReturn(ArmEmitterContext context, Operand target)
+ {
+ Operand nativeContext = context.LoadArgument(OperandType.I64, 0);
+ DecreaseCallDepth(context, nativeContext);
+
+ if (target.Type == OperandType.I32)
+ {
+ target = context.ZeroExtend32(OperandType.I64, target);
+ }
+
+ context.Return(target);
+ }
+
private static void EmitTableBranch(ArmEmitterContext context, Operand guestAddress, bool isJump)
{
context.StoreToContext();
@@ -257,6 +269,8 @@ namespace ARMeilleure.Instructions
if (isJump)
{
+ DecreaseCallDepth(context, nativeContext);
+
context.Tailcall(hostAddress, nativeContext);
}
else
@@ -278,8 +292,42 @@ namespace ARMeilleure.Instructions
Operand lblContinue = context.GetLabel(nextAddr.Value);
context.BranchIf(lblContinue, returnAddress, nextAddr, Comparison.Equal, BasicBlockFrequency.Cold);
+ DecreaseCallDepth(context, nativeContext);
+
context.Return(returnAddress);
}
}
+
+ public static void EmitCallDepthCheckAndIncrement(EmitterContext context, Operand guestAddress)
+ {
+ if (!Optimizations.EnableDeepCallRecursionProtection)
+ {
+ return;
+ }
+
+ Operand nativeContext = context.LoadArgument(OperandType.I64, 0);
+ Operand callDepthAddr = context.Add(nativeContext, Const((ulong)NativeContext.GetCallDepthOffset()));
+ Operand currentCallDepth = context.Load(OperandType.I32, callDepthAddr);
+ Operand lblDoCall = Label();
+
+ context.BranchIf(lblDoCall, currentCallDepth, Const(MaxCallDepth), Comparison.LessUI);
+ context.Store(callDepthAddr, context.Subtract(currentCallDepth, Const(1)));
+ context.Return(guestAddress);
+
+ context.MarkLabel(lblDoCall);
+ context.Store(callDepthAddr, context.Add(currentCallDepth, Const(1)));
+ }
+
+ private static void DecreaseCallDepth(EmitterContext context, Operand nativeContext)
+ {
+ if (!Optimizations.EnableDeepCallRecursionProtection)
+ {
+ return;
+ }
+
+ Operand callDepthAddr = context.Add(nativeContext, Const((ulong)NativeContext.GetCallDepthOffset()));
+ Operand currentCallDepth = context.Load(OperandType.I32, callDepthAddr);
+ context.Store(callDepthAddr, context.Subtract(currentCallDepth, Const(1)));
+ }
}
}
diff --git a/src/ARMeilleure/Optimizations.cs b/src/ARMeilleure/Optimizations.cs
index 6dd7befe7..3a76b6d93 100644
--- a/src/ARMeilleure/Optimizations.cs
+++ b/src/ARMeilleure/Optimizations.cs
@@ -13,6 +13,7 @@ namespace ARMeilleure
public static bool AllowLcqInFunctionTable { get; set; } = true;
public static bool UseUnmanagedDispatchLoop { get; set; } = true;
public static bool EnableDebugging { get; set; } = false;
+ public static bool EnableDeepCallRecursionProtection { get; set; } = true;
public static bool UseAdvSimdIfAvailable { get; set; } = true;
public static bool UseArm64AesIfAvailable { get; set; } = true;
diff --git a/src/ARMeilleure/State/ExecutionContext.cs b/src/ARMeilleure/State/ExecutionContext.cs
index fa1a4a032..365805e45 100644
--- a/src/ARMeilleure/State/ExecutionContext.cs
+++ b/src/ARMeilleure/State/ExecutionContext.cs
@@ -134,6 +134,11 @@ namespace ARMeilleure.State
public bool GetFPstateFlag(FPState flag) => _nativeContext.GetFPStateFlag(flag);
public void SetFPstateFlag(FPState flag, bool value) => _nativeContext.SetFPStateFlag(flag, value);
+ internal void ResetCallDepth()
+ {
+ _nativeContext.ResetCallDepth();
+ }
+
internal void CheckInterrupt()
{
if (Interrupted)
diff --git a/src/ARMeilleure/State/NativeContext.cs b/src/ARMeilleure/State/NativeContext.cs
index a9f1c3dab..bc9e31888 100644
--- a/src/ARMeilleure/State/NativeContext.cs
+++ b/src/ARMeilleure/State/NativeContext.cs
@@ -22,6 +22,7 @@ namespace ARMeilleure.State
public ulong ExclusiveValueHigh;
public int Running;
public long Tpidr2El0;
+ public int CallDepth;
///
/// Precise PC value used for debugging.
@@ -199,6 +200,8 @@ namespace ARMeilleure.State
public bool GetRunning() => GetStorage().Running != 0;
public void SetRunning(bool value) => GetStorage().Running = value ? 1 : 0;
+ public void ResetCallDepth() => GetStorage().CallDepth = 0;
+
public unsafe static int GetRegisterOffset(Register reg)
{
if (reg.Type == RegisterType.Integer)
@@ -284,6 +287,11 @@ namespace ARMeilleure.State
return StorageOffset(ref _dummyStorage, ref _dummyStorage.DebugPrecisePc);
}
+ public static int GetCallDepthOffset()
+ {
+ return StorageOffset(ref _dummyStorage, ref _dummyStorage.CallDepth);
+ }
+
private static int StorageOffset(ref NativeCtxStorage storage, ref T target)
{
return (int)Unsafe.ByteOffset(ref Unsafe.As(ref storage), ref target);
diff --git a/src/ARMeilleure/Translation/PTC/Ptc.cs b/src/ARMeilleure/Translation/PTC/Ptc.cs
index c69ebcadb..cfa4377db 100644
--- a/src/ARMeilleure/Translation/PTC/Ptc.cs
+++ b/src/ARMeilleure/Translation/PTC/Ptc.cs
@@ -33,7 +33,7 @@ namespace ARMeilleure.Translation.PTC
private const string OuterHeaderMagicString = "PTCohd\0\0";
private const string InnerHeaderMagicString = "PTCihd\0\0";
- private const uint InternalVersion = 7009; //! To be incremented manually for each change to the ARMeilleure project.
+ private const uint InternalVersion = 7010; //! To be incremented manually for each change to the ARMeilleure project.
private const string ActualDir = "0";
private const string BackupDir = "1";
diff --git a/src/ARMeilleure/Translation/Translator.cs b/src/ARMeilleure/Translation/Translator.cs
index 0ebb705a4..b098ff4cf 100644
--- a/src/ARMeilleure/Translation/Translator.cs
+++ b/src/ARMeilleure/Translation/Translator.cs
@@ -186,6 +186,7 @@ namespace ARMeilleure.Translation
Statistics.StartTimer();
+ context.ResetCallDepth();
ulong nextAddr = func.Execute(Stubs.ContextWrapper, context);
Statistics.StopTimer(address);
@@ -260,6 +261,7 @@ namespace ARMeilleure.Translation
Logger.StartPass(PassName.Translation);
+ InstEmitFlowHelper.EmitCallDepthCheckAndIncrement(context, Const(address));
EmitSynchronization(context);
if (blocks[0].Address != address)
diff --git a/src/ARMeilleure/Translation/TranslatorStubs.cs b/src/ARMeilleure/Translation/TranslatorStubs.cs
index 458a42434..2d95ceb99 100644
--- a/src/ARMeilleure/Translation/TranslatorStubs.cs
+++ b/src/ARMeilleure/Translation/TranslatorStubs.cs
@@ -262,10 +262,18 @@ namespace ARMeilleure.Translation
Operand runningAddress = context.Add(nativeContext, Const((ulong)NativeContext.GetRunningOffset()));
Operand dispatchAddress = context.Add(nativeContext, Const((ulong)NativeContext.GetDispatchAddressOffset()));
+ Operand callDepthAddress = context.Add(nativeContext, Const((ulong)NativeContext.GetCallDepthOffset()));
EmitSyncFpContext(context, nativeContext, true);
context.MarkLabel(beginLbl);
+
+ if (Optimizations.EnableDeepCallRecursionProtection)
+ {
+ // Reset the call depth counter, since this is our first guest function call.
+ context.Store(callDepthAddress, Const(0));
+ }
+
context.Store(dispatchAddress, guestAddress);
context.Copy(guestAddress, context.Call(Const((ulong)DispatchStub), OperandType.I64, nativeContext));
context.BranchIfFalse(endLbl, guestAddress);
diff --git a/src/Ryujinx.Audio/Renderer/Parameter/Effect/BiquadFilterEffectParameter2.cs b/src/Ryujinx.Audio/Renderer/Parameter/Effect/BiquadFilterEffectParameter2.cs
index 0c74f1e7b..b1e61f6c1 100644
--- a/src/Ryujinx.Audio/Renderer/Parameter/Effect/BiquadFilterEffectParameter2.cs
+++ b/src/Ryujinx.Audio/Renderer/Parameter/Effect/BiquadFilterEffectParameter2.cs
@@ -19,6 +19,11 @@ namespace Ryujinx.Audio.Renderer.Parameter.Effect
/// The output channel indices that will be used by the .
///
public Array6 Output;
+
+ ///
+ /// Reserved/unused.
+ ///
+ private readonly uint _padding;
///
/// Biquad filter numerator (b0, b1, b2).
diff --git a/src/Ryujinx.Common/Memory/MemoryStreamManager.cs b/src/Ryujinx.Common/Memory/MemoryStreamManager.cs
index 834210e07..88f5956e9 100644
--- a/src/Ryujinx.Common/Memory/MemoryStreamManager.cs
+++ b/src/Ryujinx.Common/Memory/MemoryStreamManager.cs
@@ -7,6 +7,9 @@ namespace Ryujinx.Common.Memory
{
private static readonly RecyclableMemoryStreamManager _shared = new();
+ private static readonly ObjectPool _streamPool =
+ new(() => new RecyclableMemoryStream(_shared, Guid.NewGuid(), null, 0));
+
///
/// We don't expose the RecyclableMemoryStreamManager directly because version 2.x
/// returns them as MemoryStream. This Shared class is here to a) offer only the GetStream() versions we use
@@ -19,7 +22,12 @@ namespace Ryujinx.Common.Memory
///
/// A RecyclableMemoryStream
public static RecyclableMemoryStream GetStream()
- => new(_shared);
+ {
+ RecyclableMemoryStream stream = _streamPool.Allocate();
+ stream.SetLength(0);
+
+ return stream;
+ }
///
/// Retrieve a new MemoryStream object with the contents copied from the provided
@@ -55,7 +63,8 @@ namespace Ryujinx.Common.Memory
RecyclableMemoryStream stream = null;
try
{
- stream = new RecyclableMemoryStream(_shared, id, tag, buffer.Length);
+ stream = _streamPool.Allocate();
+ stream.SetLength(0);
stream.Write(buffer);
stream.Position = 0;
return stream;
@@ -83,7 +92,8 @@ namespace Ryujinx.Common.Memory
RecyclableMemoryStream stream = null;
try
{
- stream = new RecyclableMemoryStream(_shared, id, tag, count);
+ stream = _streamPool.Allocate();
+ stream.SetLength(0);
stream.Write(buffer, offset, count);
stream.Position = 0;
return stream;
@@ -94,6 +104,11 @@ namespace Ryujinx.Common.Memory
throw;
}
}
+
+ public static void ReleaseStream(RecyclableMemoryStream stream)
+ {
+ _streamPool.Release(stream);
+ }
}
}
}
diff --git a/src/Ryujinx.Common/Utilities/OsUtils.cs b/src/Ryujinx.Common/Utilities/OsUtils.cs
index a0791b092..29c6e187c 100644
--- a/src/Ryujinx.Common/Utilities/OsUtils.cs
+++ b/src/Ryujinx.Common/Utilities/OsUtils.cs
@@ -20,5 +20,21 @@ namespace Ryujinx.Common.Utilities
Debug.Assert(res != -1);
}
}
+
+ // "dumpable" attribute of the calling process
+ private const int PR_SET_DUMPABLE = 4;
+
+ [DllImport("libc", SetLastError = true)]
+ private static extern int prctl(int option, int arg2);
+
+ public static void SetCoreDumpable(bool dumpable)
+ {
+ if (OperatingSystem.IsLinux())
+ {
+ int dumpableInt = dumpable ? 1 : 0;
+ int result = prctl(PR_SET_DUMPABLE, dumpableInt);
+ Debug.Assert(result == 0);
+ }
+ }
}
}
diff --git a/src/Ryujinx.Graphics.GAL/IPipeline.cs b/src/Ryujinx.Graphics.GAL/IPipeline.cs
index b8409a573..da20da870 100644
--- a/src/Ryujinx.Graphics.GAL/IPipeline.cs
+++ b/src/Ryujinx.Graphics.GAL/IPipeline.cs
@@ -82,7 +82,7 @@ namespace Ryujinx.Graphics.GAL
void SetRasterizerDiscard(bool discard);
void SetRenderTargetColorMasks(ReadOnlySpan componentMask);
- void SetRenderTargets(ITexture[] colors, ITexture depthStencil);
+ void SetRenderTargets(Span colors, ITexture depthStencil);
void SetScissors(ReadOnlySpan> regions);
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetRenderTargetsCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetRenderTargetsCommand.cs
index ca7c8c8c2..2641ae528 100644
--- a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetRenderTargetsCommand.cs
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetRenderTargetsCommand.cs
@@ -1,5 +1,6 @@
using Ryujinx.Graphics.GAL.Multithreading.Model;
using Ryujinx.Graphics.GAL.Multithreading.Resources;
+using System;
using System.Buffers;
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
@@ -8,11 +9,13 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands
{
public static readonly ArrayPool ArrayPool = ArrayPool.Create(512, 50);
public readonly CommandType CommandType => CommandType.SetRenderTargets;
+ private int _colorsCount;
private TableRef _colors;
private TableRef _depthStencil;
- public void Set(TableRef colors, TableRef depthStencil)
+ public void Set(int colorsCount, TableRef colors, TableRef depthStencil)
{
+ _colorsCount = colorsCount;
_colors = colors;
_depthStencil = depthStencil;
}
@@ -20,16 +23,15 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands
public static void Run(ref SetRenderTargetsCommand command, ThreadedRenderer threaded, IRenderer renderer)
{
ITexture[] colors = command._colors.Get(threaded);
- ITexture[] colorsCopy = ArrayPool.Rent(colors.Length);
+ Span colorsSpan = colors.AsSpan(0, command._colorsCount);
- for (int i = 0; i < colors.Length; i++)
+ for (int i = 0; i < colorsSpan.Length; i++)
{
- colorsCopy[i] = ((ThreadedTexture)colors[i])?.Base;
+ colorsSpan[i] = ((ThreadedTexture)colorsSpan[i])?.Base;
}
- renderer.Pipeline.SetRenderTargets(colorsCopy, command._depthStencil.GetAs(threaded)?.Base);
+ renderer.Pipeline.SetRenderTargets(colorsSpan, 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 ea3fd1e11..6873574b7 100644
--- a/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs
@@ -267,12 +267,12 @@ namespace Ryujinx.Graphics.GAL.Multithreading
_renderer.QueueCommand();
}
- public unsafe void SetRenderTargets(ITexture[] colors, ITexture depthStencil)
+ public unsafe void SetRenderTargets(Span colors, ITexture depthStencil)
{
ITexture[] colorsCopy = SetRenderTargetsCommand.ArrayPool.Rent(colors.Length);
- colors.CopyTo(colorsCopy, 0);
+ colors.CopyTo(colorsCopy.AsSpan());
- _renderer.New()->Set(Ref(colorsCopy), Ref(depthStencil));
+ _renderer.New()->Set(colors.Length, Ref(colorsCopy), Ref(depthStencil));
_renderer.QueueCommand();
}
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedRenderer.cs b/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedRenderer.cs
index 3c179da36..66ac31ab4 100644
--- a/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedRenderer.cs
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedRenderer.cs
@@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
///
public class ThreadedRenderer : IRenderer
{
- private const int SpanPoolBytes = 4 * 1024 * 1024;
+ private const int SpanPoolBytes = 8 * 1024 * 1024;
private const int MaxRefsPerCommand = 2;
private const int QueueCount = 10000;
diff --git a/src/Ryujinx.Graphics.Gpu/Memory/Buffer.cs b/src/Ryujinx.Graphics.Gpu/Memory/Buffer.cs
index 277a30689..f04576c2a 100644
--- a/src/Ryujinx.Graphics.Gpu/Memory/Buffer.cs
+++ b/src/Ryujinx.Graphics.Gpu/Memory/Buffer.cs
@@ -15,7 +15,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
///
/// Buffer, used to store vertex and index data, uniform and storage buffers, and others.
///
- class Buffer : INonOverlappingRange, ISyncActionHandler, IDisposable
+ class Buffer : INonOverlappingRange, ISyncActionHandler, IDisposable
{
private const ulong GranularBufferThreshold = 4096;
@@ -41,6 +41,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// End address of the buffer in guest memory.
///
public ulong EndAddress => Address + Size;
+
+ public Buffer Next { get; set; }
+ public Buffer Previous { get; set; }
///
/// Increments when the buffer is (partially) unmapped or disposed.
@@ -87,6 +90,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
private readonly bool _useGranular;
private bool _syncActionRegistered;
+ private bool _bufferInherited;
private int _referenceCount = 1;
@@ -113,7 +117,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
ulong size,
BufferStage stage,
bool sparseCompatible,
- RangeItem[] baseBuffers)
+ Buffer[] baseBuffers)
{
_context = context;
_physicalMemory = physicalMemory;
@@ -134,15 +138,15 @@ namespace Ryujinx.Graphics.Gpu.Memory
if (baseBuffers.Length != 0)
{
baseHandles = new List();
- foreach (RangeItem item in baseBuffers)
+ foreach (Buffer item in baseBuffers)
{
- if (item.Value._useGranular)
+ if (item._useGranular)
{
- baseHandles.AddRange(item.Value._memoryTrackingGranular.Handles);
+ baseHandles.AddRange(item._memoryTrackingGranular.Handles);
}
else
{
- baseHandles.Add(item.Value._memoryTracking);
+ baseHandles.Add(item._memoryTracking);
}
}
}
@@ -247,14 +251,14 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// Checks if a given range overlaps with the buffer.
///
/// Start address of the range
- /// Size in bytes of the range
+ /// End address of the range
/// True if the range overlaps, false otherwise
- public bool OverlapsWith(ulong address, ulong size)
+ public bool OverlapsWith(ulong address, ulong endAddress)
{
- return Address < address + size && address < EndAddress;
+ return Address < endAddress && address < EndAddress;
}
- public INonOverlappingRange Split(ulong splitAddress)
+ public INonOverlappingRange Split(ulong splitAddress)
{
throw new NotImplementedException();
}
@@ -426,10 +430,13 @@ namespace Ryujinx.Graphics.Gpu.Memory
{
_syncActionRegistered = false;
+ if (_bufferInherited)
+ {
+ return true;
+ }
+
if (_useGranular)
{
-
-
_modifiedRanges?.GetRanges(Address, Size, _syncRangeAction);
}
else
@@ -453,6 +460,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// The buffer to inherit from
public void InheritModifiedRanges(Buffer from)
{
+ from._bufferInherited = true;
+
if (from._modifiedRanges is { HasRanges: true })
{
if (from._syncActionRegistered && !_syncActionRegistered)
diff --git a/src/Ryujinx.Graphics.Gpu/Memory/BufferBackingState.cs b/src/Ryujinx.Graphics.Gpu/Memory/BufferBackingState.cs
index a81e7e98f..e674eb1d7 100644
--- a/src/Ryujinx.Graphics.Gpu/Memory/BufferBackingState.cs
+++ b/src/Ryujinx.Graphics.Gpu/Memory/BufferBackingState.cs
@@ -56,7 +56,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// Parent buffer
/// Initial buffer stage
/// Buffers to inherit state from
- public BufferBackingState(GpuContext context, Buffer parent, BufferStage stage, RangeItem[] baseBuffers)
+ public BufferBackingState(GpuContext context, Buffer parent, BufferStage stage, Buffer[] baseBuffers)
{
_size = (int)parent.Size;
_systemMemoryType = context.Capabilities.MemoryType;
@@ -102,9 +102,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
if (baseBuffers.Length != 0)
{
- foreach (RangeItem item in baseBuffers)
+ foreach (Buffer item in baseBuffers)
{
- CombineState(item.Value.BackingState);
+ CombineState(item.BackingState);
}
}
}
diff --git a/src/Ryujinx.Graphics.Gpu/Memory/BufferCache.cs b/src/Ryujinx.Graphics.Gpu/Memory/BufferCache.cs
index 0d623ff95..83869ed02 100644
--- a/src/Ryujinx.Graphics.Gpu/Memory/BufferCache.cs
+++ b/src/Ryujinx.Graphics.Gpu/Memory/BufferCache.cs
@@ -79,16 +79,13 @@ namespace Ryujinx.Graphics.Gpu.Memory
for (int index = 0; index < range.Count; index++)
{
MemoryRange subRange = range.GetSubRange(index);
-
- _buffers.Lock.EnterReadLock();
- Span> overlaps = _buffers.FindOverlapsAsSpan(subRange.Address, subRange.Size);
+
+ ReadOnlySpan overlaps = _buffers.FindOverlapsAsSpan(subRange.Address, subRange.Size);
for (int i = 0; i < overlaps.Length; i++)
{
- overlaps[i].Value.Unmapped(subRange.Address, subRange.Size);
+ overlaps[i].Unmapped(subRange.Address, subRange.Size);
}
-
- _buffers.Lock.ExitReadLock();
}
}
@@ -328,7 +325,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
ulong alignedEndAddress = (endAddress + alignmentMask) & ~alignmentMask;
ulong alignedSize = alignedEndAddress - alignedAddress;
- Buffer buffer = _buffers.FindOverlap(alignedAddress, alignedSize).Value;
+ Buffer buffer = _buffers.FindOverlap(alignedAddress, alignedSize);
BufferRange bufferRange = buffer.GetRange(alignedAddress, alignedSize, false);
alignedSubRanges[i] = new MemoryRange(alignedAddress, alignedSize);
@@ -395,7 +392,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
if (subRange.Address != MemoryManager.PteUnmapped)
{
- Buffer buffer = _buffers.FindOverlap(subRange.Address, subRange.Size).Value;
+ Buffer buffer = _buffers.FindOverlap(subRange.Address, subRange.Size);
virtualBuffer.AddPhysicalDependency(buffer, subRange.Address, dstOffset, subRange.Size);
physicalBuffers.Add(buffer);
@@ -487,10 +484,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// The type of usage that created the buffer
private void CreateBufferAligned(ulong address, ulong size, BufferStage stage)
{
- Buffer newBuffer = null;
-
- _buffers.Lock.EnterWriteLock();
- Span> overlaps = _buffers.FindOverlapsAsSpan(address, size);
+ ReadOnlySpan overlaps = _buffers.FindOverlapsAsSpan(address, size);
if (overlaps.Length != 0)
{
@@ -521,7 +515,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
{
// Try to grow the buffer by 1.5x of its current size.
// This improves performance in the cases where the buffer is resized often by small amounts.
- ulong existingSize = overlaps[0].Value.Size;
+ ulong existingSize = overlaps[0].Size;
ulong growthSize = (existingSize + Math.Min(existingSize >> 1, MaxDynamicGrowthSize)) & ~BufferAlignmentMask;
size = Math.Max(size, growthSize);
@@ -535,39 +529,22 @@ namespace Ryujinx.Graphics.Gpu.Memory
for (int i = 0; i < overlaps.Length; i++)
{
- anySparseCompatible |= overlaps[i].Value.SparseCompatible;
+ anySparseCompatible |= overlaps[i].SparseCompatible;
}
- RangeItem[] overlapsArray = overlaps.ToArray();
+ Buffer[] overlapsArray = overlaps.ToArray();
_buffers.RemoveRange(overlaps[0], overlaps[^1]);
- _buffers.Lock.ExitWriteLock();
-
ulong newSize = endAddress - address;
- newBuffer = CreateBufferAligned(address, newSize, stage, anySparseCompatible, overlapsArray);
- }
- else
- {
- _buffers.Lock.ExitWriteLock();
+ _buffers.Add(CreateBufferAligned(address, newSize, stage, anySparseCompatible, overlapsArray));
}
}
else
{
- _buffers.Lock.ExitWriteLock();
-
// No overlap, just create a new buffer.
- newBuffer = new(_context, _physicalMemory, address, size, stage, sparseCompatible: false, []);
- }
-
- if (newBuffer is not null)
- {
- _buffers.Lock.EnterWriteLock();
-
- _buffers.Add(newBuffer);
-
- _buffers.Lock.ExitWriteLock();
+ _buffers.Add(new(_context, _physicalMemory, address, size, stage, sparseCompatible: false, []));
}
}
@@ -583,10 +560,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
private void CreateBufferAligned(ulong address, ulong size, BufferStage stage, ulong alignment)
{
bool sparseAligned = alignment >= SparseBufferAlignmentSize;
- Buffer newBuffer = null;
- _buffers.Lock.EnterWriteLock();
- Span> overlaps = _buffers.FindOverlapsAsSpan(address, size);
+ ReadOnlySpan overlaps = _buffers.FindOverlapsAsSpan(address, size);
if (overlaps.Length != 0)
{
@@ -598,7 +573,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
if (overlaps[0].Address > address ||
overlaps[0].EndAddress < endAddress ||
(overlaps[0].Address & (alignment - 1)) != 0 ||
- (!overlaps[0].Value.SparseCompatible && sparseAligned))
+ (!overlaps[0].SparseCompatible && sparseAligned))
{
// We need to make sure the new buffer is properly aligned.
// However, after the range is aligned, it is possible that it
@@ -622,35 +597,18 @@ namespace Ryujinx.Graphics.Gpu.Memory
ulong newSize = endAddress - address;
- RangeItem[] overlapsArray = overlaps.ToArray();
+ Buffer[] overlapsArray = overlaps.ToArray();
_buffers.RemoveRange(overlaps[0], overlaps[^1]);
- _buffers.Lock.ExitWriteLock();
-
- newBuffer = CreateBufferAligned(address, newSize, stage, sparseAligned, overlapsArray);
- }
- else
- {
- _buffers.Lock.ExitWriteLock();
+ _buffers.Add(CreateBufferAligned(address, newSize, stage, sparseAligned, overlapsArray));
}
}
else
{
- _buffers.Lock.ExitWriteLock();
-
// No overlap, just create a new buffer.
- newBuffer = new(_context, _physicalMemory, address, size, stage, sparseAligned, []);
- }
-
- if (newBuffer is not null)
- {
- _buffers.Lock.EnterWriteLock();
-
- _buffers.Add(newBuffer);
-
- _buffers.Lock.ExitWriteLock();
- }
+ _buffers.Add(new(_context, _physicalMemory, address, size, stage, sparseAligned, []));
+ }
}
///
@@ -663,13 +621,13 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// The type of usage that created the buffer
/// Indicates if the buffer can be used in a sparse buffer mapping
/// Buffers overlapping the range
- private Buffer CreateBufferAligned(ulong address, ulong size, BufferStage stage, bool sparseCompatible, RangeItem[] overlaps)
+ private Buffer CreateBufferAligned(ulong address, ulong size, BufferStage stage, bool sparseCompatible, Buffer[] overlaps)
{
Buffer newBuffer = new(_context, _physicalMemory, address, size, stage, sparseCompatible, overlaps);
for (int index = 0; index < overlaps.Length; index++)
{
- Buffer buffer = overlaps[index].Value;
+ Buffer buffer = overlaps[index];
int dstOffset = (int)(buffer.Address - newBuffer.Address);
@@ -897,7 +855,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
{
MemoryRange subRange = range.GetSubRange(i);
- Buffer subBuffer = _buffers.FindOverlap(subRange.Address, subRange.Size).Value;
+ Buffer subBuffer = _buffers.FindOverlap(subRange.Address, subRange.Size);
subBuffer.SynchronizeMemory(subRange.Address, subRange.Size);
@@ -945,7 +903,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
if (size != 0)
{
- buffer = _buffers.FindOverlap(address, size).Value;
+ buffer = _buffers.FindOverlap(address, size);
buffer.CopyFromDependantVirtualBuffers();
buffer.SynchronizeMemory(address, size);
@@ -957,7 +915,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
}
else
{
- buffer = _buffers.FindOverlapFast(address, 1).Value;
+ buffer = _buffers.FindOverlapFast(address, 1);
}
return buffer;
@@ -995,7 +953,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
{
if (size != 0)
{
- Buffer buffer = _buffers.FindOverlap(address, size).Value;
+ Buffer buffer = _buffers.FindOverlap(address, size);
if (copyBackVirtual)
{
diff --git a/src/Ryujinx.Graphics.Gpu/Memory/BufferModifiedRangeList.cs b/src/Ryujinx.Graphics.Gpu/Memory/BufferModifiedRangeList.cs
index 9c50eaf2f..aef04abc6 100644
--- a/src/Ryujinx.Graphics.Gpu/Memory/BufferModifiedRangeList.cs
+++ b/src/Ryujinx.Graphics.Gpu/Memory/BufferModifiedRangeList.cs
@@ -8,7 +8,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
///
/// A range within a buffer that has been modified by the GPU.
///
- class BufferModifiedRange : INonOverlappingRange
+ class BufferModifiedRange : INonOverlappingRange
{
///
/// Start address of the range in guest memory.
@@ -24,6 +24,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// End address of the range in guest memory.
///
public ulong EndAddress => Address + Size;
+
+ public BufferModifiedRange Next { get; set; }
+ public BufferModifiedRange Previous { get; set; }
///
/// The GPU sync number at the time of the last modification.
@@ -54,14 +57,14 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// Checks if a given range overlaps with the modified range.
///
/// Start address of the range
- /// Size in bytes of the range
+ /// End address of the range
/// True if the range overlaps, false otherwise
- public bool OverlapsWith(ulong address, ulong size)
+ public bool OverlapsWith(ulong address, ulong endAddress)
{
- return Address < address + size && address < EndAddress;
+ return Address < endAddress && address < EndAddress;
}
- public INonOverlappingRange Split(ulong splitAddress)
+ public INonOverlappingRange Split(ulong splitAddress)
{
throw new NotImplementedException();
}
@@ -119,11 +122,11 @@ namespace Ryujinx.Graphics.Gpu.Memory
// Slices a given region using the modified regions in the list. Calls the action for the new slices.
Lock.EnterReadLock();
- Span> overlaps = FindOverlapsAsSpan(address, size);
+ ReadOnlySpan overlaps = FindOverlapsAsSpan(address, size);
for (int i = 0; i < overlaps.Length; i++)
{
- BufferModifiedRange overlap = overlaps[i].Value;
+ BufferModifiedRange overlap = overlaps[i];
if (overlap.Address > address)
{
@@ -157,7 +160,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
ulong syncNumber = _context.SyncNumber;
// We may overlap with some existing modified regions. They must be cut into by the new entry.
Lock.EnterWriteLock();
- (RangeItem first, RangeItem last) = FindOverlapsAsNodes(address, size);
+ (BufferModifiedRange first, BufferModifiedRange last) = FindOverlapsAsNodes(address, size);
if (first is null)
{
@@ -170,34 +173,39 @@ namespace Ryujinx.Graphics.Gpu.Memory
{
if (first.Address == address && first.EndAddress == endAddress)
{
- first.Value.SyncNumber = syncNumber;
- first.Value.Parent = this;
+ first.SyncNumber = syncNumber;
+ first.Parent = this;
Lock.ExitWriteLock();
return;
}
if (first.Address < address)
{
- first.Value.Size = address - first.Address;
- Update(first);
-
if (first.EndAddress > endAddress)
{
Add(new BufferModifiedRange(endAddress, first.EndAddress - endAddress,
- first.Value.SyncNumber, first.Value.Parent));
+ first.SyncNumber, first.Parent));
}
+
+ first.Size = address - first.Address;
}
else
{
if (first.EndAddress > endAddress)
{
- first.Value.Size = first.EndAddress - endAddress;
- first.Value.Address = endAddress;
- Update(first);
+ first.Size = first.EndAddress - endAddress;
+ first.Address = endAddress;
}
else
{
- Remove(first.Value);
+ first.Address = address;
+ first.Size = size;
+ first.SyncNumber = syncNumber;
+ first.Parent = this;
+
+ Lock.ExitWriteLock();
+
+ return;
}
}
@@ -207,38 +215,39 @@ namespace Ryujinx.Graphics.Gpu.Memory
return;
}
- BufferModifiedRange buffPre = null;
- BufferModifiedRange buffPost = null;
- bool extendsPost = false;
- bool extendsPre = false;
-
if (first.Address < address)
{
- buffPre = new BufferModifiedRange(first.Address, address - first.Address,
- first.Value.SyncNumber, first.Value.Parent);
- extendsPre = true;
+ first.Size = address - first.Address;
+ first = first.Next;
}
if (last.EndAddress > endAddress)
{
- buffPost = new BufferModifiedRange(endAddress, last.EndAddress - endAddress,
- last.Value.SyncNumber, last.Value.Parent);
- extendsPost = true;
+ last.Size = last.EndAddress - endAddress;
+ last.Address = endAddress;
+ last = last.Previous;
}
- RemoveRange(first, last);
-
- if (extendsPre)
+ if (first.Address < last.Address)
{
- Add(buffPre);
+ RemoveRange(first.Next, last);
+ first.Address = address;
+ first.Size = size;
+ first.SyncNumber = syncNumber;
+ first.Parent = this;
}
-
- if (extendsPost)
+ else if (first.Address == last.Address)
{
- Add(buffPost);
+ first.Address = address;
+ first.Size = size;
+ first.SyncNumber = syncNumber;
+ first.Parent = this;
}
-
- Add(new BufferModifiedRange(address, size, syncNumber, this));
+ else
+ {
+ Add(new BufferModifiedRange(address, size, syncNumber, this));
+ }
+
Lock.ExitWriteLock();
}
@@ -252,11 +261,11 @@ namespace Ryujinx.Graphics.Gpu.Memory
public void GetRangesAtSync(ulong address, ulong size, ulong syncNumber, Action rangeAction)
{
Lock.EnterReadLock();
- Span> overlaps = FindOverlapsAsSpan(address, size);
+ ReadOnlySpan overlaps = FindOverlapsAsSpan(address, size);
for (int i = 0; i < overlaps.Length; i++)
{
- BufferModifiedRange overlap = overlaps[i].Value;
+ BufferModifiedRange overlap = overlaps[i];
if (overlap.SyncNumber == syncNumber)
{
@@ -277,18 +286,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, out int length);
+ BufferModifiedRange[] overlaps = FindOverlapsAsArray(address, size, out int length);
Lock.ExitReadLock();
if (length != 0)
{
for (int i = 0; i < length; i++)
{
- BufferModifiedRange overlap = overlaps[i].Value;
+ BufferModifiedRange overlap = overlaps[i];
rangeAction(overlap.Address, overlap.Size);
}
- ArrayPool>.Shared.Return(overlaps);
+ ArrayPool.Shared.Return(overlaps);
}
}
@@ -301,7 +310,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
public bool HasRange(ulong address, ulong size)
{
Lock.EnterReadLock();
- RangeItem first = FindOverlapFast(address, size);
+ BufferModifiedRange first = FindOverlapFast(address, size);
bool result = first is not null;
Lock.ExitReadLock();
return result;
@@ -336,7 +345,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// The start address of the flush range
/// The end address of the flush range
private void RemoveRangesAndFlush(
- RangeItem[] overlaps,
+ BufferModifiedRange[] overlaps,
int rangeCount,
long highestDiff,
ulong currentSync,
@@ -349,7 +358,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
for (int i = 0; i < rangeCount; i++)
{
- BufferModifiedRange overlap = overlaps[i].Value;
+ BufferModifiedRange overlap = overlaps[i];
long diff = (long)(overlap.SyncNumber - currentSync);
@@ -358,7 +367,14 @@ namespace Ryujinx.Graphics.Gpu.Memory
ulong clampAddress = Math.Max(address, overlap.Address);
ulong clampEnd = Math.Min(endAddress, overlap.EndAddress);
- ClearPart(overlap, clampAddress, clampEnd);
+ if (i == 0 || i == rangeCount - 1)
+ {
+ ClearPart(overlap, clampAddress, clampEnd);
+ }
+ else
+ {
+ Remove(overlap);
+ }
RangeActionWithMigration(clampAddress, clampEnd - clampAddress, waitSync, _flushAction);
}
@@ -398,7 +414,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, out int rangeCount);
+ BufferModifiedRange[] overlaps = FindOverlapsAsArray(address, size, out int rangeCount);
if (rangeCount == 0)
{
@@ -414,7 +430,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
for (int i = 0; i < rangeCount; i++)
{
- BufferModifiedRange overlap = overlaps![i].Value;
+ BufferModifiedRange overlap = overlaps![i];
long diff = (long)(overlap.SyncNumber - currentSync);
@@ -436,7 +452,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
RemoveRangesAndFlush(overlaps, rangeCount, highestDiff, currentSync, address, endAddress);
- ArrayPool>.Shared.Return(overlaps!);
+ ArrayPool.Shared.Return(overlaps!);
Lock.ExitWriteLock();
}
@@ -452,7 +468,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
public void InheritRanges(BufferModifiedRangeList ranges, Action registerRangeAction)
{
ranges.Lock.EnterReadLock();
- BufferModifiedRange[] inheritRanges = ranges.ToArray();
+ int rangesCount = ranges.Count;
+ BufferModifiedRange[] inheritRanges = ArrayPool.Shared.Rent(ranges.Count);
+ ranges.Items.AsSpan(0, ranges.Count).CopyTo(inheritRanges);
ranges.Lock.ExitReadLock();
// Copy over the migration from the previous range list
@@ -478,22 +496,26 @@ namespace Ryujinx.Graphics.Gpu.Memory
ranges._migrationTarget = this;
Lock.EnterWriteLock();
-
- foreach (BufferModifiedRange range in inheritRanges)
+
+ for (int i = 0; i < rangesCount; i++)
{
+ BufferModifiedRange range = inheritRanges[i];
Add(range);
}
Lock.ExitWriteLock();
ulong currentSync = _context.SyncNumber;
- foreach (BufferModifiedRange range in inheritRanges)
+ for (int i = 0; i < rangesCount; i++)
{
+ BufferModifiedRange range = inheritRanges[i];
if (range.SyncNumber != currentSync)
{
registerRangeAction(range.Address, range.Size);
}
}
+
+ ArrayPool.Shared.Return(inheritRanges);
}
///
@@ -534,18 +556,25 @@ namespace Ryujinx.Graphics.Gpu.Memory
private void ClearPart(BufferModifiedRange overlap, ulong address, ulong endAddress)
{
- Remove(overlap);
-
// If the overlap extends outside of the clear range, make sure those parts still exist.
if (overlap.Address < address)
{
- Add(new BufferModifiedRange(overlap.Address, address - overlap.Address, overlap.SyncNumber, overlap.Parent));
+ if (overlap.EndAddress > endAddress)
+ {
+ Add(new BufferModifiedRange(endAddress, overlap.EndAddress - endAddress, overlap.SyncNumber, overlap.Parent));
+ }
+
+ overlap.Size = address - overlap.Address;
}
-
- if (overlap.EndAddress > endAddress)
+ else if (overlap.EndAddress > endAddress)
{
- Add(new BufferModifiedRange(endAddress, overlap.EndAddress - endAddress, overlap.SyncNumber, overlap.Parent));
+ overlap.Size = overlap.EndAddress - endAddress;
+ overlap.Address = endAddress;
+ }
+ else
+ {
+ Remove(overlap);
}
}
@@ -558,7 +587,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
{
ulong endAddress = address + size;
Lock.EnterWriteLock();
- (RangeItem first, RangeItem last) = FindOverlapsAsNodes(address, size);
+ (BufferModifiedRange first, BufferModifiedRange last) = FindOverlapsAsNodes(address, size);
if (first is null)
{
@@ -570,26 +599,24 @@ namespace Ryujinx.Graphics.Gpu.Memory
{
if (first.Address < address)
{
- first.Value.Size = address - first.Address;
- Update(first);
+ first.Size = address - first.Address;
if (first.EndAddress > endAddress)
{
Add(new BufferModifiedRange(endAddress, first.EndAddress - endAddress,
- first.Value.SyncNumber, first.Value.Parent));
+ first.SyncNumber, first.Parent));
}
}
else
{
if (first.EndAddress > endAddress)
{
- first.Value.Size = first.EndAddress - endAddress;
- first.Value.Address = endAddress;
- Update(first);
+ first.Size = first.EndAddress - endAddress;
+ first.Address = endAddress;
}
else
{
- Remove(first.Value);
+ Remove(first);
}
}
@@ -605,14 +632,14 @@ namespace Ryujinx.Graphics.Gpu.Memory
if (first.Address < address)
{
buffPre = new BufferModifiedRange(first.Address, address - first.Address,
- first.Value.SyncNumber, first.Value.Parent);
+ first.SyncNumber, first.Parent);
extendsPre = true;
}
if (last.EndAddress > endAddress)
{
buffPost = new BufferModifiedRange(endAddress, last.EndAddress - endAddress,
- last.Value.SyncNumber, last.Value.Parent);
+ last.SyncNumber, last.Parent);
extendsPost = true;
}
diff --git a/src/Ryujinx.Graphics.Gpu/Memory/VirtualRangeCache.cs b/src/Ryujinx.Graphics.Gpu/Memory/VirtualRangeCache.cs
index 1d44ee65f..0339d8617 100644
--- a/src/Ryujinx.Graphics.Gpu/Memory/VirtualRangeCache.cs
+++ b/src/Ryujinx.Graphics.Gpu/Memory/VirtualRangeCache.cs
@@ -15,7 +15,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
///
/// Represents a GPU virtual memory range.
///
- private class VirtualRange : INonOverlappingRange
+ private class VirtualRange : INonOverlappingRange
{
///
/// GPU virtual address where the range starts.
@@ -32,6 +32,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
///
public ulong EndAddress => Address + Size;
+ public VirtualRange Next { get; set; }
+ public VirtualRange Previous { get; set; }
+
///
/// Physical regions where the GPU virtual region is mapped.
///
@@ -54,14 +57,14 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// Checks if a given range overlaps with the buffer.
///
/// Start address of the range
- /// Size in bytes of the range
+ /// End address of the range
/// True if the range overlaps, false otherwise
- public bool OverlapsWith(ulong address, ulong size)
+ public bool OverlapsWith(ulong address, ulong endAddress)
{
- return Address < address + size && address < EndAddress;
+ return Address < endAddress && address < EndAddress;
}
- public INonOverlappingRange Split(ulong splitAddress)
+ public INonOverlappingRange Split(ulong splitAddress)
{
throw new NotImplementedException();
}
@@ -122,7 +125,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
ulong originalVa = gpuVa;
_virtualRanges.Lock.EnterWriteLock();
- (RangeItem first, RangeItem last) = _virtualRanges.FindOverlapsAsNodes(gpuVa, size);
+ (VirtualRange first, VirtualRange last) = _virtualRanges.FindOverlapsAsNodes(gpuVa, size);
if (first is not null)
{
@@ -147,8 +150,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
}
else
{
- found = first.Value.Range.Count == 1 || IsSparseAligned(first.Value.Range);
- range = first.Value.Range.Slice(gpuVa - first.Address, size);
+ found = first.Range.Count == 1 || IsSparseAligned(first.Range);
+ range = first.Range.Slice(gpuVa - first.Address, size);
}
}
else
diff --git a/src/Ryujinx.Graphics.OpenGL/Pipeline.cs b/src/Ryujinx.Graphics.OpenGL/Pipeline.cs
index c8ca02140..e58e6f2b9 100644
--- a/src/Ryujinx.Graphics.OpenGL/Pipeline.cs
+++ b/src/Ryujinx.Graphics.OpenGL/Pipeline.cs
@@ -1166,7 +1166,7 @@ namespace Ryujinx.Graphics.OpenGL
}
}
- public void SetRenderTargets(ITexture[] colors, ITexture depthStencil)
+ public void SetRenderTargets(Span colors, ITexture depthStencil)
{
EnsureFramebuffer();
diff --git a/src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs b/src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs
index 919c45b9d..6d03fcd0d 100644
--- a/src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs
+++ b/src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs
@@ -71,17 +71,31 @@ namespace Ryujinx.Graphics.Vulkan
HasDepthStencil = isDepthStencil;
}
- public FramebufferParams(Device device, ITexture[] colors, ITexture depthStencil)
+ public FramebufferParams(Device device, ReadOnlySpan colors, ITexture depthStencil)
{
_device = device;
- int colorsCount = colors.Count(IsValidTextureView);
+ int colorsCount = 0;
+ _colorsCanonical = new TextureView[colors.Length];
+
+ for (int i = 0; i < colors.Length; i++)
+ {
+ ITexture color = colors[i];
+ if (color is TextureView { Valid: true } view)
+ {
+ colorsCount++;
+ _colorsCanonical[i] = view;
+ }
+ else
+ {
+ _colorsCanonical[i] = null;
+ }
+ }
int count = colorsCount + (IsValidTextureView(depthStencil) ? 1 : 0);
_attachments = new Auto[count];
_colors = new TextureView[colorsCount];
- _colorsCanonical = colors.Select(color => color is TextureView view && view.Valid ? view : null).ToArray();
AttachmentSamples = new uint[count];
AttachmentFormats = new VkFormat[count];
@@ -165,9 +179,17 @@ namespace Ryujinx.Graphics.Vulkan
_totalCount = colors.Length;
}
- public FramebufferParams Update(ITexture[] colors, ITexture depthStencil)
+ public FramebufferParams Update(ReadOnlySpan colors, ITexture depthStencil)
{
- int colorsCount = colors.Count(IsValidTextureView);
+ int colorsCount = 0;
+
+ foreach (ITexture color in colors)
+ {
+ if (IsValidTextureView(color))
+ {
+ colorsCount++;
+ }
+ }
int count = colorsCount + (IsValidTextureView(depthStencil) ? 1 : 0);
diff --git a/src/Ryujinx.Graphics.Vulkan/MultiFenceHolder.cs b/src/Ryujinx.Graphics.Vulkan/MultiFenceHolder.cs
index 40ad7716d..b226ce1f3 100644
--- a/src/Ryujinx.Graphics.Vulkan/MultiFenceHolder.cs
+++ b/src/Ryujinx.Graphics.Vulkan/MultiFenceHolder.cs
@@ -1,7 +1,7 @@
+using Ryujinx.Common;
using Ryujinx.Common.Memory;
using Silk.NET.Vulkan;
using System;
-using System.Buffers;
namespace Ryujinx.Graphics.Vulkan
{
@@ -10,6 +10,8 @@ namespace Ryujinx.Graphics.Vulkan
///
class MultiFenceHolder
{
+ public static readonly ObjectPool FencePool = new(() => new FenceHolder[CommandBufferPool.MaxCommandBuffers]);
+
private const int BufferUsageTrackingGranularity = 4096;
public FenceHolder[] Fences { get; }
@@ -20,7 +22,7 @@ namespace Ryujinx.Graphics.Vulkan
///
public MultiFenceHolder()
{
- Fences = ArrayPool.Shared.Rent(CommandBufferPool.MaxCommandBuffers);
+ Fences = FencePool.Allocate();
}
///
@@ -29,7 +31,7 @@ namespace Ryujinx.Graphics.Vulkan
/// Size of the buffer
public MultiFenceHolder(int size)
{
- Fences = ArrayPool.Shared.Rent(CommandBufferPool.MaxCommandBuffers);
+ Fences = FencePool.Allocate();
_bufferUsageBitmap = new BufferUsageBitmap(size, BufferUsageTrackingGranularity);
}
diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs b/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs
index f2f68378f..0172b5b56 100644
--- a/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs
+++ b/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs
@@ -1035,7 +1035,7 @@ namespace Ryujinx.Graphics.Vulkan
}
}
- private void SetRenderTargetsInternal(ITexture[] colors, ITexture depthStencil, bool filterWriteMasked)
+ private void SetRenderTargetsInternal(Span colors, ITexture depthStencil, bool filterWriteMasked)
{
CreateFramebuffer(colors, depthStencil, filterWriteMasked);
CreateRenderPass();
@@ -1043,7 +1043,7 @@ namespace Ryujinx.Graphics.Vulkan
SignalAttachmentChange();
}
- public void SetRenderTargets(ITexture[] colors, ITexture depthStencil)
+ public void SetRenderTargets(Span colors, ITexture depthStencil)
{
_framebufferUsingColorWriteMask = false;
SetRenderTargetsInternal(colors, depthStencil, Gd.IsTBDR);
@@ -1389,7 +1389,7 @@ namespace Ryujinx.Graphics.Vulkan
_currentPipelineHandle = 0;
}
- private void CreateFramebuffer(ITexture[] colors, ITexture depthStencil, bool filterWriteMasked)
+ private void CreateFramebuffer(Span colors, ITexture depthStencil, bool filterWriteMasked)
{
if (filterWriteMasked)
{
@@ -1399,7 +1399,7 @@ namespace Ryujinx.Graphics.Vulkan
// Just try to remove duplicate attachments.
// Save a copy of the array to rebind when mask changes.
- void MaskOut()
+ void MaskOut(ReadOnlySpan colors)
{
if (!_framebufferUsingColorWriteMask)
{
@@ -1436,12 +1436,12 @@ namespace Ryujinx.Graphics.Vulkan
if (vkBlend.ColorWriteMask == 0)
{
colors[i] = null;
- MaskOut();
+ MaskOut(colors);
}
else if (vkBlend2.ColorWriteMask == 0)
{
colors[j] = null;
- MaskOut();
+ MaskOut(colors);
}
}
}
diff --git a/src/Ryujinx.Graphics.Vulkan/SyncManager.cs b/src/Ryujinx.Graphics.Vulkan/SyncManager.cs
index 149759906..15759b0de 100644
--- a/src/Ryujinx.Graphics.Vulkan/SyncManager.cs
+++ b/src/Ryujinx.Graphics.Vulkan/SyncManager.cs
@@ -1,6 +1,6 @@
using Ryujinx.Common.Logging;
using Silk.NET.Vulkan;
-using System.Buffers;
+using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
@@ -193,7 +193,8 @@ namespace Ryujinx.Graphics.Vulkan
{
_firstHandle = first.ID + 1;
_handles.RemoveAt(0);
- ArrayPool.Shared.Return(first.Waitable.Fences);
+ Array.Clear(first.Waitable.Fences);
+ MultiFenceHolder.FencePool.Release(first.Waitable.Fences);
first.Waitable = null;
}
}
diff --git a/src/Ryujinx.HLE/FileSystem/ContentManager.cs b/src/Ryujinx.HLE/FileSystem/ContentManager.cs
index 9f38de42b..d0fe0f1a7 100644
--- a/src/Ryujinx.HLE/FileSystem/ContentManager.cs
+++ b/src/Ryujinx.HLE/FileSystem/ContentManager.cs
@@ -483,10 +483,29 @@ namespace Ryujinx.HLE.FileSystem
{
if (Directory.Exists(keysSource))
{
- foreach (string filePath in Directory.EnumerateFiles(keysSource, "*.keys"))
+ string[] keyPaths = Directory.EnumerateFiles(keysSource, "*.keys").ToArray();
+
+ if (keyPaths.Length is 0)
+ throw new FileNotFoundException($"Directory '{keysSource}' contained no '.keys' files.");
+
+ foreach (string filePath in keyPaths)
{
- VerifyKeysFile(filePath);
- File.Copy(filePath, Path.Combine(installDirectory, Path.GetFileName(filePath)), true);
+ try
+ {
+ VerifyKeysFile(filePath);
+ }
+ catch (Exception e)
+ {
+ Logger.Error?.Print(LogClass.Application, e.Message);
+ continue;
+ }
+
+ string destPath = Path.Combine(installDirectory, Path.GetFileName(filePath));
+
+ if (File.Exists(destPath))
+ File.Delete(destPath);
+
+ File.Copy(filePath, destPath, true);
}
return;
@@ -501,13 +520,25 @@ namespace Ryujinx.HLE.FileSystem
using FileStream file = File.OpenRead(keysSource);
- if (info.Extension is ".keys")
+ if (info.Extension is not ".keys")
+ throw new InvalidFirmwarePackageException("Input file extension is not .keys");
+
+ try
{
VerifyKeysFile(keysSource);
- File.Copy(keysSource, Path.Combine(installDirectory, info.Name), true);
- }
- else
+ }
+ catch
+ {
throw new InvalidFirmwarePackageException("Input file is not a valid key package");
+ }
+
+ string dest = Path.Combine(installDirectory, info.Name);
+
+ if (File.Exists(dest))
+ File.Delete(dest);
+
+ // overwrite: true seems to not work on its own? https://github.com/Ryubing/Issues/issues/189
+ File.Copy(keysSource, dest, true);
}
private void FinishInstallation(string temporaryDirectory, string registeredDirectory)
@@ -985,8 +1016,8 @@ namespace Ryujinx.HLE.FileSystem
public static void VerifyKeysFile(string filePath)
{
// Verify the keys file format refers to https://github.com/Thealexbarney/LibHac/blob/master/KEYS.md
- string genericPattern = @"^[a-z0-9_]+ = [a-z0-9]+$";
- string titlePattern = @"^[a-z0-9]{32} = [a-z0-9]{32}$";
+ string genericPattern = "^[a-z0-9_]+ = [a-z0-9]+$";
+ string titlePattern = "^[a-z0-9]{32} = [a-z0-9]{32}$";
if (File.Exists(filePath))
{
@@ -994,24 +1025,13 @@ namespace Ryujinx.HLE.FileSystem
string fileName = Path.GetFileName(filePath);
string[] lines = File.ReadAllLines(filePath);
- bool verified;
- switch (fileName)
+ bool verified = fileName switch
{
- case "prod.keys":
- verified = VerifyKeys(lines, genericPattern);
- break;
- case "title.keys":
- verified = VerifyKeys(lines, titlePattern);
- break;
- case "console.keys":
- verified = VerifyKeys(lines, genericPattern);
- break;
- case "dev.keys":
- verified = VerifyKeys(lines, genericPattern);
- break;
- default:
- throw new FormatException($"Keys file name \"{fileName}\" not supported. Only \"prod.keys\", \"title.keys\", \"console.keys\", \"dev.keys\" are supported.");
- }
+ "prod.keys" or "console.keys" or "dev.keys" => VerifyKeys(lines, genericPattern),
+ "title.keys" => VerifyKeys(lines, titlePattern),
+ _ => throw new FormatException(
+ $"Keys file name \"{fileName}\" not supported. Only \"prod.keys\", \"title.keys\", \"console.keys\", \"dev.keys\" are supported.")
+ };
if (!verified)
{
diff --git a/src/Ryujinx.HLE/HOS/Ipc/IpcMessage.cs b/src/Ryujinx.HLE/HOS/Ipc/IpcMessage.cs
index 7df2b778f..13a93db39 100644
--- a/src/Ryujinx.HLE/HOS/Ipc/IpcMessage.cs
+++ b/src/Ryujinx.HLE/HOS/Ipc/IpcMessage.cs
@@ -2,6 +2,7 @@ using Microsoft.IO;
using Ryujinx.Common;
using Ryujinx.Common.Memory;
using System;
+using System.Buffers;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
@@ -37,7 +38,7 @@ namespace Ryujinx.HLE.HOS.Ipc
public IpcMessage(ReadOnlySpan data, long cmdPtr)
{
- using RecyclableMemoryStream ms = MemoryStreamManager.Shared.GetStream(data);
+ RecyclableMemoryStream ms = MemoryStreamManager.Shared.GetStream(data);
BinaryReader reader = new(ms);
@@ -123,6 +124,8 @@ namespace Ryujinx.HLE.HOS.Ipc
}
ObjectIds = [];
+
+ MemoryStreamManager.Shared.ReleaseStream(ms);
}
public RecyclableMemoryStream GetStream(long cmdPtr, ulong recvListAddr)
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Ipc/KBufferDescriptorTable.cs b/src/Ryujinx.HLE/HOS/Kernel/Ipc/KBufferDescriptorTable.cs
index 373899b7b..d22cfb469 100644
--- a/src/Ryujinx.HLE/HOS/Kernel/Ipc/KBufferDescriptorTable.cs
+++ b/src/Ryujinx.HLE/HOS/Kernel/Ipc/KBufferDescriptorTable.cs
@@ -20,6 +20,15 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
_exchangeBufferDescriptors = new List(MaxInternalBuffersCount);
}
+ public KBufferDescriptorTable Clear()
+ {
+ _sendBufferDescriptors.Clear();
+ _receiveBufferDescriptors.Clear();
+ _exchangeBufferDescriptors.Clear();
+
+ return this;
+ }
+
public Result AddSendBuffer(ulong src, ulong dst, ulong size, MemoryState state)
{
return Add(_sendBufferDescriptors, src, dst, size, state);
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Ipc/KClientSession.cs b/src/Ryujinx.HLE/HOS/Kernel/Ipc/KClientSession.cs
index 385f09020..d83e14ba3 100644
--- a/src/Ryujinx.HLE/HOS/Kernel/Ipc/KClientSession.cs
+++ b/src/Ryujinx.HLE/HOS/Kernel/Ipc/KClientSession.cs
@@ -1,3 +1,4 @@
+using Ryujinx.Common;
using Ryujinx.HLE.HOS.Kernel.Common;
using Ryujinx.HLE.HOS.Kernel.Process;
using Ryujinx.HLE.HOS.Kernel.Threading;
@@ -32,7 +33,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
{
KThread currentThread = KernelStatic.GetCurrentThread();
- KSessionRequest request = new(currentThread, customCmdBuffAddr, customCmdBuffSize);
+ KSessionRequest request = _parent.ServerSession.RequestPool.Allocate().Set(currentThread, customCmdBuffAddr, customCmdBuffSize);
KernelContext.CriticalSection.Enter();
@@ -55,7 +56,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
{
KThread currentThread = KernelStatic.GetCurrentThread();
- KSessionRequest request = new(currentThread, customCmdBuffAddr, customCmdBuffSize, asyncEvent);
+ KSessionRequest request = _parent.ServerSession.RequestPool.Allocate().Set(currentThread, customCmdBuffAddr, customCmdBuffSize, asyncEvent);
KernelContext.CriticalSection.Enter();
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Ipc/KServerSession.cs b/src/Ryujinx.HLE/HOS/Kernel/Ipc/KServerSession.cs
index edc3d819e..f2c22c9f3 100644
--- a/src/Ryujinx.HLE/HOS/Kernel/Ipc/KServerSession.cs
+++ b/src/Ryujinx.HLE/HOS/Kernel/Ipc/KServerSession.cs
@@ -10,6 +10,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
{
class KServerSession : KSynchronizationObject
{
+ public readonly ObjectPool RequestPool = new(() => new KSessionRequest());
+
private static readonly MemoryState[] _ipcMemoryStates =
[
MemoryState.IpcBuffer3,
@@ -274,6 +276,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
KernelContext.CriticalSection.Leave();
WakeClientThread(request, clientResult);
+
+ RequestPool.Release(request);
}
if (clientHeader.ReceiveListType < 2 &&
@@ -627,6 +631,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
CloseAllHandles(clientMsg, serverHeader, clientProcess);
FinishRequest(request, clientResult);
+
+ RequestPool.Release(request);
}
if (clientHeader.ReceiveListType < 2 &&
@@ -865,6 +871,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
// Unmap buffers from server.
FinishRequest(request, clientResult);
+
+ RequestPool.Release(request);
return serverResult;
}
@@ -1098,6 +1106,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
foreach (KSessionRequest request in IterateWithRemovalOfAllRequests())
{
FinishRequest(request, KernelResult.PortRemoteClosed);
+
+ RequestPool.Release(request);
}
}
@@ -1117,6 +1127,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
{
SendResultToAsyncRequestClient(request, KernelResult.PortRemoteClosed);
}
+
+ RequestPool.Release(request);
}
WakeServerThreads(KernelResult.PortRemoteClosed);
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Ipc/KSessionRequest.cs b/src/Ryujinx.HLE/HOS/Kernel/Ipc/KSessionRequest.cs
index bc3eef71e..69a0d3a02 100644
--- a/src/Ryujinx.HLE/HOS/Kernel/Ipc/KSessionRequest.cs
+++ b/src/Ryujinx.HLE/HOS/Kernel/Ipc/KSessionRequest.cs
@@ -5,18 +5,18 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
{
class KSessionRequest
{
- public KBufferDescriptorTable BufferDescriptorTable { get; }
+ public KBufferDescriptorTable BufferDescriptorTable { get; private set; }
- public KThread ClientThread { get; }
+ public KThread ClientThread { get; private set; }
public KProcess ServerProcess { get; set; }
- public KWritableEvent AsyncEvent { get; }
+ public KWritableEvent AsyncEvent { get; private set; }
- public ulong CustomCmdBuffAddr { get; }
- public ulong CustomCmdBuffSize { get; }
+ public ulong CustomCmdBuffAddr { get; private set; }
+ public ulong CustomCmdBuffSize { get; private set; }
- public KSessionRequest(
+ public KSessionRequest Set(
KThread clientThread,
ulong customCmdBuffAddr,
ulong customCmdBuffSize,
@@ -27,7 +27,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
CustomCmdBuffSize = customCmdBuffSize;
AsyncEvent = asyncEvent;
- BufferDescriptorTable = new KBufferDescriptorTable();
+ BufferDescriptorTable = BufferDescriptorTable?.Clear() ?? new KBufferDescriptorTable();
+
+ return this;
}
}
}
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Threading/KAddressArbiter.cs b/src/Ryujinx.HLE/HOS/Kernel/Threading/KAddressArbiter.cs
index 278a9b2ff..c9ac86fc9 100644
--- a/src/Ryujinx.HLE/HOS/Kernel/Threading/KAddressArbiter.cs
+++ b/src/Ryujinx.HLE/HOS/Kernel/Threading/KAddressArbiter.cs
@@ -1,10 +1,8 @@
-using Ryujinx.Common;
using Ryujinx.HLE.HOS.Kernel.Common;
using Ryujinx.HLE.HOS.Kernel.Process;
using Ryujinx.Horizon.Common;
using System;
using System.Collections.Generic;
-using System.Linq;
using System.Threading;
namespace Ryujinx.HLE.HOS.Kernel.Threading
@@ -12,12 +10,12 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
class KAddressArbiter
{
private const int HasListenersMask = 0x40000000;
- private static readonly ObjectPool _threadArrayPool = new(() => []);
private readonly KernelContext _context;
- private readonly List _condVarThreads;
- private readonly List _arbiterThreads;
+ private readonly Dictionary> _condVarThreads;
+ private readonly Dictionary> _arbiterThreads;
+ private readonly ByDynamicPriority _byDynamicPriority;
public KAddressArbiter(KernelContext context)
{
@@ -25,6 +23,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
_condVarThreads = [];
_arbiterThreads = [];
+ _byDynamicPriority = new ByDynamicPriority();
}
public Result ArbitrateLock(int ownerHandle, ulong mutexAddress, int requesterHandle)
@@ -140,9 +139,23 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
currentThread.MutexAddress = mutexAddress;
currentThread.ThreadHandleForUserMutex = threadHandle;
- currentThread.CondVarAddress = condVarAddress;
- _condVarThreads.Add(currentThread);
+ if (_condVarThreads.TryGetValue(condVarAddress, out List threads))
+ {
+ int i = 0;
+
+ if (threads.Count > 0)
+ {
+ i = threads.BinarySearch(currentThread, _byDynamicPriority);
+ if (i < 0) i = ~i;
+ }
+
+ threads.Insert(i, currentThread);
+ }
+ else
+ {
+ _condVarThreads.Add(condVarAddress, [currentThread]);
+ }
if (timeout != 0)
{
@@ -165,7 +178,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
currentThread.MutexOwner?.RemoveMutexWaiter(currentThread);
- _condVarThreads.Remove(currentThread);
+ _condVarThreads[condVarAddress].Remove(currentThread);
_context.CriticalSection.Leave();
@@ -200,13 +213,14 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
{
_context.CriticalSection.Enter();
- static bool SignalProcessWideKeyPredicate(KThread thread, ulong address)
+ int validThreads = 0;
+ _condVarThreads.TryGetValue(address, out List threads);
+
+ if (threads is not null && threads.Count > 0)
{
- return thread.CondVarAddress == address;
+ validThreads = WakeThreads(threads, count, TryAcquireMutex);
}
-
- int validThreads = WakeThreads(_condVarThreads, count, TryAcquireMutex, SignalProcessWideKeyPredicate, address);
-
+
if (validThreads == 0)
{
KernelTransfer.KernelToUser(address, 0);
@@ -315,9 +329,24 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
currentThread.MutexAddress = address;
currentThread.WaitingInArbitration = true;
+
+ if (_arbiterThreads.TryGetValue(address, out List threads))
+ {
+ int i = 0;
- _arbiterThreads.Add(currentThread);
-
+ if (threads.Count > 0)
+ {
+ i = threads.BinarySearch(currentThread, _byDynamicPriority);
+ if (i < 0) i = ~i;
+ }
+
+ threads.Insert(i, currentThread);
+ }
+ else
+ {
+ _arbiterThreads.Add(address, [currentThread]);
+ }
+
currentThread.Reschedule(ThreadSchedState.Paused);
if (timeout > 0)
@@ -336,7 +365,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
if (currentThread.WaitingInArbitration)
{
- _arbiterThreads.Remove(currentThread);
+ _arbiterThreads[address].Remove(currentThread);
currentThread.WaitingInArbitration = false;
}
@@ -392,9 +421,24 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
currentThread.MutexAddress = address;
currentThread.WaitingInArbitration = true;
+
+ if (_arbiterThreads.TryGetValue(address, out List threads))
+ {
+ int i = 0;
- _arbiterThreads.Add(currentThread);
-
+ if (threads.Count > 0)
+ {
+ i = threads.BinarySearch(currentThread, _byDynamicPriority);
+ if (i < 0) i = ~i;
+ }
+
+ threads.Insert(i, currentThread);
+ }
+ else
+ {
+ _arbiterThreads.Add(address, [currentThread]);
+ }
+
currentThread.Reschedule(ThreadSchedState.Paused);
if (timeout > 0)
@@ -413,7 +457,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
if (currentThread.WaitingInArbitration)
{
- _arbiterThreads.Remove(currentThread);
+ _arbiterThreads[address].Remove(currentThread);
currentThread.WaitingInArbitration = false;
}
@@ -486,15 +530,12 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
// or equal to the Count of threads to be signaled, or Count is zero
// or negative. It is incremented if there are no threads waiting.
int waitingCount = 0;
-
- foreach (KThread thread in _arbiterThreads)
+
+ if (_arbiterThreads.TryGetValue(address, out List threads))
{
- if (thread.MutexAddress == address &&
- ++waitingCount >= count)
- {
- break;
- }
+ waitingCount = threads.Count;
}
+
if (waitingCount > 0)
{
@@ -561,55 +602,38 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
thread.WaitingInArbitration = false;
}
- static bool ArbiterThreadPredecate(KThread thread, ulong address)
- {
- return thread.MutexAddress == address;
- }
+ _arbiterThreads.TryGetValue(address, out List threads);
- WakeThreads(_arbiterThreads, count, RemoveArbiterThread, ArbiterThreadPredecate, address);
+ if (threads is not null && threads.Count > 0)
+ {
+ WakeThreads(threads, count, RemoveArbiterThread);
+ }
}
private static int WakeThreads(
List threads,
int count,
- Action removeCallback,
- Func predicate,
- ulong address = 0)
+ Action removeCallback)
{
- 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)));
+ int validCount = count > 0 ? Math.Min(count, threads.Count) : threads.Count;
- if (count > 0)
- {
- candidatesSpan = candidatesSpan[..Math.Min(count, candidatesSpan.Length)];
- }
-
- foreach (KThread thread in candidatesSpan)
+ for (int i = 0; i < validCount; i++)
{
+ KThread thread = threads[i];
removeCallback(thread);
- threads.Remove(thread);
}
- _threadArrayPool.Release(candidates);
-
+ threads.RemoveRange(0, validCount);
+
return validCount;
}
+
+ private class ByDynamicPriority : IComparer
+ {
+ public int Compare(KThread x, KThread y)
+ {
+ return x!.DynamicPriority.CompareTo(y!.DynamicPriority);
+ }
+ }
}
}
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs b/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs
index 64cd4d595..aaa4ccd99 100644
--- a/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs
+++ b/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs
@@ -61,8 +61,6 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
public KSynchronizationObject SignaledObj { get; set; }
- public ulong CondVarAddress { get; set; }
-
private ulong _entrypoint;
private ThreadStart _customThreadStart;
private bool _forcedUnschedulable;
diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ISelfController.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ISelfController.cs
index 8e0f515ba..7aac6f3ea 100644
--- a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ISelfController.cs
+++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ISelfController.cs
@@ -416,7 +416,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
return ResultCode.InvalidParameters;
}
- Logger.Stub?.PrintStub(LogClass.ServiceAm, new { albumReportOption });
+ context.Device.UIHandler.TakeScreenshot();
return ResultCode.Success;
}
diff --git a/src/Ryujinx.HLE/HOS/Services/IpcService.cs b/src/Ryujinx.HLE/HOS/Services/IpcService.cs
index 4c354ebc6..c7dee64fb 100644
--- a/src/Ryujinx.HLE/HOS/Services/IpcService.cs
+++ b/src/Ryujinx.HLE/HOS/Services/IpcService.cs
@@ -23,6 +23,9 @@ namespace Ryujinx.HLE.HOS.Services
private int _selfId;
private bool _isDomain;
+ // cache array so we don't recreate it all the time
+ private object[] _parameters = [null];
+
public IpcService(ServerBase server = null, bool registerTipc = false)
{
Stopwatch sw = Stopwatch.StartNew();
@@ -146,7 +149,9 @@ namespace Ryujinx.HLE.HOS.Services
{
Logger.Trace?.Print(LogClass.KernelIpc, $"{service.GetType().Name}: {processRequest.Name}");
- result = (ResultCode)processRequest.Invoke(service, [context]);
+ _parameters[0] = context;
+
+ result = (ResultCode)processRequest.Invoke(service, _parameters);
}
else
{
@@ -196,7 +201,9 @@ namespace Ryujinx.HLE.HOS.Services
{
Logger.Debug?.Print(LogClass.KernelIpc, $"{GetType().Name}: {processRequest.Name}");
- result = (ResultCode)processRequest.Invoke(this, [context]);
+ _parameters[0] = context;
+
+ result = (ResultCode)processRequest.Invoke(this, _parameters);
}
else
{
diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/INvDrvServices.cs b/src/Ryujinx.HLE/HOS/Services/Nv/INvDrvServices.cs
index 598c7e6e2..ed14b3e15 100644
--- a/src/Ryujinx.HLE/HOS/Services/Nv/INvDrvServices.cs
+++ b/src/Ryujinx.HLE/HOS/Services/Nv/INvDrvServices.cs
@@ -59,6 +59,10 @@ namespace Ryujinx.HLE.HOS.Services.Nv
// TODO: This should call set:sys::GetDebugModeFlag
private readonly bool _debugModeEnabled = false;
+
+ private byte[] _ioctl2Buffer = [];
+ private byte[] _ioctlArgumentBuffer = [];
+ private byte[] _ioctl3Buffer = [];
public INvDrvServices(ServiceCtx context) : base(context.Device.System.NvDrvServer)
{
@@ -128,27 +132,38 @@ namespace Ryujinx.HLE.HOS.Services.Nv
if (!context.Memory.TryReadUnsafe(inputDataPosition, (int)inputDataSize, out arguments))
{
- arguments = new byte[inputDataSize];
+ if (_ioctlArgumentBuffer.Length < (int)inputDataSize)
+ {
+ Array.Resize(ref _ioctlArgumentBuffer, (int)inputDataSize);
+ }
+
+ arguments = _ioctlArgumentBuffer.AsSpan(0, (int)inputDataSize);
+
context.Memory.Read(inputDataPosition, arguments);
}
- else
- {
- arguments = arguments.ToArray();
- }
}
else if (isWrite)
{
- byte[] outputData = new byte[outputDataSize];
-
- arguments = new Span(outputData);
+ if (_ioctlArgumentBuffer.Length < (int)outputDataSize)
+ {
+ Array.Resize(ref _ioctlArgumentBuffer, (int)outputDataSize);
+ }
+
+ arguments = _ioctlArgumentBuffer.AsSpan(0, (int)outputDataSize);
}
else
{
- byte[] temp = new byte[inputDataSize];
+ if (!context.Memory.TryReadUnsafe(inputDataPosition, (int)inputDataSize, out arguments))
+ {
+ if (_ioctlArgumentBuffer.Length < (int)inputDataSize)
+ {
+ Array.Resize(ref _ioctlArgumentBuffer, (int)inputDataSize);
+ }
+
+ arguments = _ioctlArgumentBuffer.AsSpan(0, (int)inputDataSize);
- context.Memory.Read(inputDataPosition, temp);
-
- arguments = new Span(temp);
+ context.Memory.Read(inputDataPosition, arguments);
+ }
}
return NvResult.Success;
@@ -270,7 +285,7 @@ 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(context.Request.GetBufferType0x22(0).Position, arguments);
}
}
}
@@ -474,13 +489,15 @@ namespace Ryujinx.HLE.HOS.Services.Nv
errorCode = GetIoctlArgument(context, ioctlCommand, out Span arguments);
- byte[] inlineInBuffer = null;
-
if (!context.Memory.TryReadUnsafe(inlineInBufferPosition, (int)inlineInBufferSize, out Span inlineInBufferSpan))
{
- inlineInBuffer = _byteArrayPool.Rent((int)inlineInBufferSize);
- inlineInBufferSpan = inlineInBuffer;
- context.Memory.Read(inlineInBufferPosition, inlineInBufferSpan[..(int)inlineInBufferSize]);
+ if (_ioctl2Buffer.Length < (int)inlineInBufferSize)
+ {
+ Array.Resize(ref _ioctl2Buffer, (int)inlineInBufferSize);
+ }
+
+ inlineInBufferSpan = _ioctl2Buffer.AsSpan(0, (int)inlineInBufferSize);
+ context.Memory.Read(inlineInBufferPosition, inlineInBufferSpan);
}
if (errorCode == NvResult.Success)
@@ -489,7 +506,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv
if (errorCode == NvResult.Success)
{
- NvInternalResult internalResult = deviceFile.Ioctl2(ioctlCommand, arguments, inlineInBufferSpan[..(int)inlineInBufferSize]);
+ NvInternalResult internalResult = deviceFile.Ioctl2(ioctlCommand, arguments, inlineInBufferSpan);
if (internalResult == NvInternalResult.NotImplemented)
{
@@ -500,15 +517,10 @@ 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(context.Request.GetBufferType0x22(0).Position, arguments);
}
}
}
-
- if (inlineInBuffer is not null)
- {
- _byteArrayPool.Return(inlineInBuffer);
- }
}
context.ResponseData.Write((uint)errorCode);
@@ -531,13 +543,15 @@ namespace Ryujinx.HLE.HOS.Services.Nv
errorCode = GetIoctlArgument(context, ioctlCommand, out Span arguments);
- byte[] inlineOutBuffer = null;
-
if (!context.Memory.TryReadUnsafe(inlineOutBufferPosition, (int)inlineOutBufferSize, out Span inlineOutBufferSpan))
{
- inlineOutBuffer = _byteArrayPool.Rent((int)inlineOutBufferSize);
- inlineOutBufferSpan = inlineOutBuffer;
- context.Memory.Read(inlineOutBufferPosition, inlineOutBufferSpan[..(int)inlineOutBufferSize]);
+ if (_ioctl3Buffer.Length < (int)inlineOutBufferSize)
+ {
+ Array.Resize(ref _ioctl3Buffer, (int)inlineOutBufferSize);
+ }
+
+ inlineOutBufferSpan = _ioctl3Buffer.AsSpan(0, (int)inlineOutBufferSize);
+ context.Memory.Read(inlineOutBufferPosition, inlineOutBufferSpan);
}
if (errorCode == NvResult.Success)
@@ -546,7 +560,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv
if (errorCode == NvResult.Success)
{
- NvInternalResult internalResult = deviceFile.Ioctl3(ioctlCommand, arguments, inlineOutBufferSpan[..(int)inlineOutBufferSize]);
+ NvInternalResult internalResult = deviceFile.Ioctl3(ioctlCommand, arguments, inlineOutBufferSpan);
if (internalResult == NvInternalResult.NotImplemented)
{
@@ -557,16 +571,11 @@ 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, inlineOutBufferSpan[..(int)inlineOutBufferSize].ToArray());
+ context.Memory.Write(context.Request.GetBufferType0x22(0).Position, arguments);
+ context.Memory.Write(inlineOutBufferPosition, inlineOutBufferSpan);
}
}
}
-
- if (inlineOutBuffer is not null)
- {
- _byteArrayPool.Return(inlineOutBuffer);
- }
}
context.ResponseData.Write((uint)errorCode);
diff --git a/src/Ryujinx.HLE/HOS/Services/ServerBase.cs b/src/Ryujinx.HLE/HOS/Services/ServerBase.cs
index dcc01bf38..a54dc637e 100644
--- a/src/Ryujinx.HLE/HOS/Services/ServerBase.cs
+++ b/src/Ryujinx.HLE/HOS/Services/ServerBase.cs
@@ -454,8 +454,9 @@ namespace Ryujinx.HLE.HOS.Services
response.RawData = _responseDataStream.ToArray();
- using RecyclableMemoryStream responseStream = response.GetStreamTipc();
+ RecyclableMemoryStream responseStream = response.GetStreamTipc();
_selfProcess.CpuMemory.Write(_selfThread.TlsAddress, responseStream.GetReadOnlySequence());
+ MemoryStreamManager.Shared.ReleaseStream(responseStream);
}
else
{
@@ -464,8 +465,9 @@ namespace Ryujinx.HLE.HOS.Services
if (!isTipcCommunication)
{
- using RecyclableMemoryStream responseStream = response.GetStream((long)_selfThread.TlsAddress, recvListAddr | ((ulong)PointerBufferSize << 48));
+ RecyclableMemoryStream responseStream = response.GetStream((long)_selfThread.TlsAddress, recvListAddr | ((ulong)PointerBufferSize << 48));
_selfProcess.CpuMemory.Write(_selfThread.TlsAddress, responseStream.GetReadOnlySequence());
+ MemoryStreamManager.Shared.ReleaseStream(responseStream);
}
return shouldReply;
diff --git a/src/Ryujinx.HLE/UI/IHostUIHandler.cs b/src/Ryujinx.HLE/UI/IHostUIHandler.cs
index b5c5cb168..79b479d8a 100644
--- a/src/Ryujinx.HLE/UI/IHostUIHandler.cs
+++ b/src/Ryujinx.HLE/UI/IHostUIHandler.cs
@@ -68,5 +68,10 @@ namespace Ryujinx.HLE.UI
/// Displays the player select dialog and returns the selected profile.
///
UserProfile ShowPlayerSelectDialog();
+
+ ///
+ /// Takes a screenshot from the current renderer and saves it in the screenshots folder.
+ ///
+ void TakeScreenshot();
}
}
diff --git a/src/Ryujinx.Horizon/Sdk/OsTypes/Impl/MultiWaitImpl.cs b/src/Ryujinx.Horizon/Sdk/OsTypes/Impl/MultiWaitImpl.cs
index d723e5322..412337b6b 100644
--- a/src/Ryujinx.Horizon/Sdk/OsTypes/Impl/MultiWaitImpl.cs
+++ b/src/Ryujinx.Horizon/Sdk/OsTypes/Impl/MultiWaitImpl.cs
@@ -19,6 +19,9 @@ namespace Ryujinx.Horizon.Sdk.OsTypes.Impl
private int _waitingThreadHandle;
private MultiWaitHolderBase _signaledHolder;
+
+ ObjectPool _objectHandlePool = new(() => new int[64]);
+ ObjectPool _objectPool = new(() => new MultiWaitHolderBase[64]);
public long CurrentTime { get; private set; }
@@ -76,11 +79,15 @@ namespace Ryujinx.Horizon.Sdk.OsTypes.Impl
private MultiWaitHolderBase WaitAnyHandleImpl(bool infinite, long timeout)
{
- Span objectHandles = new int[64];
+ int[] objectHandles = _objectHandlePool.Allocate();
+ Span objectHandlesSpan = objectHandles;
+ objectHandlesSpan.Clear();
- Span objects = new MultiWaitHolderBase[64];
+ MultiWaitHolderBase[] objects = _objectPool.Allocate();
+ Span objectsSpan = objects;
+ objectsSpan.Clear();
- int count = FillObjectsArray(objectHandles, objects);
+ int count = FillObjectsArray(objectHandlesSpan, objectsSpan);
long endTime = infinite ? long.MaxValue : PerformanceCounter.ElapsedMilliseconds * 1000000;
@@ -98,7 +105,7 @@ namespace Ryujinx.Horizon.Sdk.OsTypes.Impl
}
else
{
- index = WaitSynchronization(objectHandles[..count], minTimeout);
+ index = WaitSynchronization(objectHandlesSpan[..count], minTimeout);
DebugUtil.Assert(index != WaitInvalid);
}
@@ -116,12 +123,18 @@ namespace Ryujinx.Horizon.Sdk.OsTypes.Impl
{
_signaledHolder = minTimeoutObject;
+ _objectHandlePool.Release(objectHandles);
+ _objectPool.Release(objects);
+
return _signaledHolder;
}
}
}
else
{
+ _objectHandlePool.Release(objectHandles);
+ _objectPool.Release(objects);
+
return null;
}
@@ -131,6 +144,9 @@ namespace Ryujinx.Horizon.Sdk.OsTypes.Impl
{
if (_signaledHolder != null)
{
+ _objectHandlePool.Release(objectHandles);
+ _objectPool.Release(objects);
+
return _signaledHolder;
}
}
@@ -139,8 +155,11 @@ namespace Ryujinx.Horizon.Sdk.OsTypes.Impl
default:
lock (_lock)
{
- _signaledHolder = objects[index];
+ _signaledHolder = objectsSpan[index];
+ _objectHandlePool.Release(objectHandles);
+ _objectPool.Release(objects);
+
return _signaledHolder;
}
}
diff --git a/src/Ryujinx.Input/HLE/NpadController.cs b/src/Ryujinx.Input/HLE/NpadController.cs
index dd8907a4b..84f9e89ab 100644
--- a/src/Ryujinx.Input/HLE/NpadController.cs
+++ b/src/Ryujinx.Input/HLE/NpadController.cs
@@ -532,8 +532,6 @@ 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 866504128..f2936aa72 100644
--- a/src/Ryujinx.Input/HLE/NpadManager.cs
+++ b/src/Ryujinx.Input/HLE/NpadManager.cs
@@ -20,7 +20,6 @@ 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();
@@ -40,6 +39,9 @@ namespace Ryujinx.Input.HLE
private bool _enableKeyboard;
private bool _enableMouse;
private Switch _device;
+
+ private readonly List _hleInputStates = [];
+ private readonly List _hleMotionStates = new(NpadDevices.MaxControllers);
public NpadManager(IGamepadDriver keyboardDriver, IGamepadDriver gamepadDriver, IGamepadDriver mouseDriver)
{
@@ -217,8 +219,8 @@ namespace Ryujinx.Input.HLE
{
lock (_lock)
{
- List hleInputStates = [];
- List hleMotionStates = _hleMotionStatesPool.Allocate();
+ _hleInputStates.Clear();
+ _hleMotionStates.Clear();
KeyboardInput? hleKeyboardInput = null;
@@ -260,14 +262,14 @@ namespace Ryujinx.Input.HLE
inputState.PlayerId = playerIndex;
motionState.Item1.PlayerId = playerIndex;
- hleInputStates.Add(inputState);
- hleMotionStates.Add(motionState.Item1);
+ _hleInputStates.Add(inputState);
+ _hleMotionStates.Add(motionState.Item1);
if (isJoyconPair && !motionState.Item2.Equals(default))
{
motionState.Item2.PlayerId = playerIndex;
- hleMotionStates.Add(motionState.Item2);
+ _hleMotionStates.Add(motionState.Item2);
}
}
@@ -276,8 +278,8 @@ namespace Ryujinx.Input.HLE
hleKeyboardInput = NpadController.GetHLEKeyboardInput(_keyboardDriver);
}
- _device.Hid.Npads.Update(hleInputStates);
- _device.Hid.Npads.UpdateSixAxis(hleMotionStates);
+ _device.Hid.Npads.Update(_hleInputStates);
+ _device.Hid.Npads.UpdateSixAxis(_hleMotionStates);
if (hleKeyboardInput.HasValue)
{
@@ -328,10 +330,7 @@ namespace Ryujinx.Input.HLE
_device.Hid.Mouse.Update(0, 0);
}
- _device.TamperMachine.UpdateInput(hleInputStates);
-
- hleMotionStates.Clear();
- _hleMotionStatesPool.Release(hleMotionStates);
+ _device.TamperMachine.UpdateInput(_hleInputStates);
}
}
diff --git a/src/Ryujinx.Input/IKeyboard.cs b/src/Ryujinx.Input/IKeyboard.cs
index 7fecaaa5d..c51d5aea3 100644
--- a/src/Ryujinx.Input/IKeyboard.cs
+++ b/src/Ryujinx.Input/IKeyboard.cs
@@ -8,6 +8,8 @@ namespace Ryujinx.Input
///
public interface IKeyboard : IGamepad
{
+ private static bool[] _keyState;
+
///
/// Check if a given key is pressed on the keyboard.
///
@@ -29,15 +31,17 @@ namespace Ryujinx.Input
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static KeyboardStateSnapshot GetStateSnapshot(IKeyboard keyboard)
{
+ if (_keyState is null)
+ {
+ _keyState = new bool[(int)Key.Count];
+ }
- bool[] keysState = ArrayPool.Shared.Rent((int)Key.Count);
-
for (Key key = 0; key < Key.Count; key++)
{
- keysState[(int)key] = keyboard.IsPressed(key);
+ _keyState[(int)key] = keyboard.IsPressed(key);
}
- return new KeyboardStateSnapshot(keysState);
+ return new KeyboardStateSnapshot(_keyState);
}
}
}
diff --git a/src/Ryujinx.Memory/Range/INonOverlappingRange.cs b/src/Ryujinx.Memory/Range/INonOverlappingRange.cs
index c6a0197d4..09311a830 100644
--- a/src/Ryujinx.Memory/Range/INonOverlappingRange.cs
+++ b/src/Ryujinx.Memory/Range/INonOverlappingRange.cs
@@ -3,7 +3,7 @@ namespace Ryujinx.Memory.Range
///
/// Range of memory that can be split in two.
///
- public interface INonOverlappingRange : IRange
+ public interface INonOverlappingRange : IRangeListRange where T : class, IRangeListRange
{
///
/// Split this region into two, around the specified address.
@@ -11,6 +11,6 @@ namespace Ryujinx.Memory.Range
///
/// Address to split the region around
/// The second part of the split region, with start address at the given split.
- public INonOverlappingRange Split(ulong splitAddress);
+ public INonOverlappingRange Split(ulong splitAddress);
}
}
diff --git a/src/Ryujinx.Memory/Range/IRange.cs b/src/Ryujinx.Memory/Range/IRange.cs
index c85e21d1d..c8f50a921 100644
--- a/src/Ryujinx.Memory/Range/IRange.cs
+++ b/src/Ryujinx.Memory/Range/IRange.cs
@@ -24,8 +24,8 @@ namespace Ryujinx.Memory.Range
/// Check if this range overlaps with another.
///
/// Base address
- /// Size of the range
+ /// EndAddress of the range
/// True if overlapping, false otherwise
- bool OverlapsWith(ulong address, ulong size);
+ bool OverlapsWith(ulong address, ulong endAddress);
}
}
diff --git a/src/Ryujinx.Memory/Range/NonOverlappingRangeList.cs b/src/Ryujinx.Memory/Range/NonOverlappingRangeList.cs
index 7560d2ae7..1ef51644c 100644
--- a/src/Ryujinx.Memory/Range/NonOverlappingRangeList.cs
+++ b/src/Ryujinx.Memory/Range/NonOverlappingRangeList.cs
@@ -11,7 +11,7 @@ namespace Ryujinx.Memory.Range
/// A range list that assumes ranges are non-overlapping, with list items that can be split in two to avoid overlaps.
///
/// Type of the range.
- public unsafe class NonOverlappingRangeList : RangeListBase where T : class, INonOverlappingRange
+ public class NonOverlappingRangeList : RangeListBase where T : class, INonOverlappingRange
{
public readonly ReaderWriterLockSlim Lock = new();
@@ -32,83 +32,18 @@ namespace Ryujinx.Memory.Range
/// The item to be added
public override void Add(T item)
{
+ Debug.Assert(item.Address != item.EndAddress);
+
int index = BinarySearch(item.Address);
if (index < 0)
{
index = ~index;
}
-
- RangeItem rangeItem = _rangeItemPool.Allocate().Set(item);
-
- Insert(index, rangeItem);
- }
-
- ///
- /// Updates an item's end address on the list. Address must be the same.
- ///
- /// The item to be updated
- /// True if the item was located and updated, false otherwise
- protected override bool Update(T item)
- {
- int index = BinarySearch(item.Address);
-
- if (index >= 0 && Items[index].Value.Equals(item))
- {
- RangeItem rangeItem = new(item) { Previous = Items[index].Previous, Next = Items[index].Next };
-
- if (index > 0)
- {
- Items[index - 1].Next = rangeItem;
- }
-
- if (index < Count - 1)
- {
- Items[index + 1].Previous = rangeItem;
- }
-
- Items[index] = rangeItem;
-
- return true;
- }
-
- return false;
- }
-
- ///
- /// Updates an item's end address on the list. Address must be the same.
- ///
- /// The RangeItem to be updated
- /// True if the item was located and updated, false otherwise
- protected override bool Update(RangeItem item)
- {
- int index = BinarySearch(item.Address);
-
- RangeItem rangeItem = new(item.Value) { Previous = item.Previous, Next = item.Next };
-
- if (index > 0)
- {
- Items[index - 1].Next = rangeItem;
- }
-
- if (index < Count - 1)
- {
- Items[index + 1].Previous = rangeItem;
- }
-
- Items[index] = rangeItem;
-
- return true;
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void Insert(int index, RangeItem item)
- {
- Debug.Assert(item.Address != item.EndAddress);
if (Count + 1 > Items.Length)
{
- Array.Resize(ref Items, Items.Length + BackingGrowthSize);
+ Array.Resize(ref Items, (int)(Items.Length * 1.5));
}
if (index >= Count)
@@ -145,8 +80,6 @@ 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;
@@ -173,7 +106,7 @@ namespace Ryujinx.Memory.Range
{
int index = BinarySearch(item.Address);
- if (index >= 0 && Items[index].Value.Equals(item))
+ if (index >= 0 && Items[index] == item)
{
RemoveAt(index);
@@ -188,7 +121,7 @@ namespace Ryujinx.Memory.Range
///
/// The first item in the range of items to be removed
/// The last item in the range of items to be removed
- public override void RemoveRange(RangeItem startItem, RangeItem endItem)
+ public override void RemoveRange(T startItem, T endItem)
{
if (startItem is null)
{
@@ -197,7 +130,7 @@ namespace Ryujinx.Memory.Range
if (startItem == endItem)
{
- Remove(startItem.Value);
+ Remove(startItem);
return;
}
@@ -229,42 +162,45 @@ namespace Ryujinx.Memory.Range
/// Size of the range
public void RemoveRange(ulong address, ulong size)
{
- int startIndex = BinarySearchLeftEdge(address, address + size);
+ (int startIndex, int endIndex) = BinarySearchEdges(address, address + size);
if (startIndex < 0)
{
return;
}
- int endIndex = startIndex;
-
- while (Items[endIndex] is not null && Items[endIndex].Address < address + size)
+ if (startIndex == endIndex - 1)
{
- if (endIndex == Count - 1)
- {
- break;
- }
-
- endIndex++;
+ RemoveAt(startIndex);
+ return;
}
- if (endIndex < Count - 1)
+ RemoveRangeInternal(startIndex, endIndex);
+ }
+
+ ///
+ /// Removes a range of items from the item list
+ ///
+ /// Start index of the range
+ /// End index of the range (exclusive)
+ private void RemoveRangeInternal(int index, int endIndex)
+ {
+ if (endIndex < Count)
{
- Items[endIndex + 1].Previous = startIndex > 0 ? Items[startIndex - 1] : null;
+ Items[endIndex].Previous = index > 0 ? Items[index - 1] : null;
}
- if (startIndex > 0)
+ if (index > 0)
{
- Items[startIndex - 1].Next = endIndex < Count - 1 ? Items[endIndex + 1] : null;
+ Items[index - 1].Next = endIndex < Count ? Items[endIndex] : null;
}
-
- if (endIndex < Count - 1)
+ if (endIndex < Count)
{
- Array.Copy(Items, endIndex + 1, Items, startIndex, Count - endIndex - 1);
+ Array.Copy(Items, endIndex, Items, index, Count - endIndex);
}
- Count -= endIndex - startIndex + 1;
+ Count -= endIndex - index;
}
///
@@ -296,8 +232,8 @@ namespace Ryujinx.Memory.Range
// So we need to return both the split 0-1 and 1-2 ranges.
Lock.EnterWriteLock();
- (RangeItem first, RangeItem last) = FindOverlapsAsNodes(address, size);
- list = new List();
+ (T first, T last) = FindOverlapsAsNodes(address, size);
+ list = [];
if (first is null)
{
@@ -311,42 +247,41 @@ namespace Ryujinx.Memory.Range
ulong lastAddress = address;
ulong endAddress = address + size;
- RangeItem current = first;
+ T current = first;
while (last is not null && current is not null && current.Address < endAddress)
{
- T region = current.Value;
- if (first == last && region.Address == address && region.Size == size)
+ if (first == last && current.Address == address && current.Size == size)
{
// Exact match, no splitting required.
- list.Add(region);
+ list.Add(current);
Lock.ExitWriteLock();
return;
}
- if (lastAddress < region.Address)
+ if (lastAddress < current.Address)
{
// There is a gap between this region and the last. We need to fill it.
- T fillRegion = factory(lastAddress, region.Address - lastAddress);
+ T fillRegion = factory(lastAddress, current.Address - lastAddress);
list.Add(fillRegion);
Add(fillRegion);
}
- if (region.Address < address)
+ if (current.Address < address)
{
// Split the region around our base address and take the high half.
- region = Split(region, address);
+ current = Split(current, address);
}
- if (region.EndAddress > address + size)
+ if (current.EndAddress > address + size)
{
// Split the region around our end address and take the low half.
- Split(region, address + size);
+ Split(current, address + size);
}
- list.Add(region);
- lastAddress = region.EndAddress;
+ list.Add(current);
+ lastAddress = current.EndAddress;
current = current.Next;
}
@@ -374,7 +309,6 @@ namespace Ryujinx.Memory.Range
private T Split(T region, ulong splitAddress)
{
T newRegion = (T)region.Split(splitAddress);
- Update(region);
Add(newRegion);
return newRegion;
}
@@ -386,16 +320,11 @@ namespace Ryujinx.Memory.Range
/// Size in bytes of the range
/// The leftmost overlapping item, or null if none is found
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public override RangeItem FindOverlap(ulong address, ulong size)
+ public override T FindOverlap(ulong address, ulong size)
{
int index = BinarySearchLeftEdge(address, address + size);
- if (index < 0)
- {
- return null;
- }
-
- return Items[index];
+ return index < 0 ? null : Items[index];
}
///
@@ -405,16 +334,11 @@ namespace Ryujinx.Memory.Range
/// Size in bytes of the range
/// The overlapping item, or null if none is found
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public override RangeItem FindOverlapFast(ulong address, ulong size)
+ public override T FindOverlapFast(ulong address, ulong size)
{
int index = BinarySearch(address, address + size);
- if (index < 0)
- {
- return null;
- }
-
- return Items[index];
+ return index < 0 ? null : Items[index];
}
///
@@ -424,23 +348,18 @@ namespace Ryujinx.Memory.Range
/// Size in bytes of the range
/// The first and last overlapping items, or null if none are found
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public (RangeItem, RangeItem) FindOverlapsAsNodes(ulong address, ulong size)
+ public (T, T) FindOverlapsAsNodes(ulong address, ulong size)
{
(int index, int endIndex) = BinarySearchEdges(address, address + size);
- if (index < 0)
- {
- return (null, null);
- }
-
- return (Items[index], Items[endIndex - 1]);
+ return index < 0 ? (null, null) : (Items[index], Items[endIndex - 1]);
}
- public RangeItem[] FindOverlapsAsArray(ulong address, ulong size, out int length)
+ public T[] FindOverlapsAsArray(ulong address, ulong size, out int length)
{
(int index, int endIndex) = BinarySearchEdges(address, address + size);
- RangeItem[] result;
+ T[] result;
if (index < 0)
{
@@ -449,29 +368,20 @@ namespace Ryujinx.Memory.Range
}
else
{
- result = ArrayPool>.Shared.Rent(endIndex - index);
+ result = ArrayPool.Shared.Rent(endIndex - index);
length = endIndex - index;
- Array.Copy(Items, index, result, 0, endIndex - index);
+ Items.AsSpan(index, endIndex - index).CopyTo(result);
}
return result;
}
- public Span> FindOverlapsAsSpan(ulong address, ulong size)
+ public ReadOnlySpan FindOverlapsAsSpan(ulong address, ulong size)
{
(int index, int endIndex) = BinarySearchEdges(address, address + size);
- Span> result;
-
- if (index < 0)
- {
- result = [];
- }
- else
- {
- result = Items.AsSpan().Slice(index, endIndex - index);
- }
+ ReadOnlySpan result = index < 0 ? [] : Items.AsSpan(index, endIndex - index);
return result;
}
@@ -480,7 +390,7 @@ namespace Ryujinx.Memory.Range
{
for (int i = 0; i < Count; i++)
{
- yield return Items[i].Value;
+ yield return Items[i];
}
}
}
diff --git a/src/Ryujinx.Memory/Range/RangeList.cs b/src/Ryujinx.Memory/Range/RangeList.cs
index 63025f1e8..e7ea55a94 100644
--- a/src/Ryujinx.Memory/Range/RangeList.cs
+++ b/src/Ryujinx.Memory/Range/RangeList.cs
@@ -14,14 +14,14 @@ namespace Ryujinx.Memory.Range
/// startIndex is inclusive.
/// endIndex is exclusive.
///
- public readonly struct OverlapResult where T : IRange
+ public readonly struct OverlapResult where T : class, IRangeListRange
{
public readonly int StartIndex = -1;
public readonly int EndIndex = -1;
- public readonly RangeItem QuickResult;
+ public readonly T QuickResult;
public int Count => EndIndex - StartIndex;
- public OverlapResult(int startIndex, int endIndex, RangeItem quickResult = null)
+ public OverlapResult(int startIndex, int endIndex, T quickResult = null)
{
this.StartIndex = startIndex;
this.EndIndex = endIndex;
@@ -33,7 +33,7 @@ namespace Ryujinx.Memory.Range
/// Sorted list of ranges that supports binary search.
///
/// Type of the range.
- public class RangeList : RangeListBase where T : IRange
+ public class RangeList : RangeListBase where T : class, IRangeListRange
{
public readonly ReaderWriterLockSlim Lock = new();
@@ -61,104 +61,6 @@ namespace Ryujinx.Memory.Range
index = ~index;
}
- Insert(index, new RangeItem(item));
- }
-
- ///
- /// Updates an item's end address on the list. Address must be the same.
- ///
- /// The item to be updated
- /// True if the item was located and updated, false otherwise
- protected override bool Update(T item)
- {
- int index = BinarySearch(item.Address);
-
- if (index >= 0)
- {
- while (index < Count)
- {
- if (Items[index].Value.Equals(item))
- {
- RangeItem rangeItem = new(item) { Previous = Items[index].Previous, Next = Items[index].Next };
-
- if (index > 0)
- {
- Items[index - 1].Next = rangeItem;
- }
-
- if (index < Count - 1)
- {
- Items[index + 1].Previous = rangeItem;
- }
-
- Items[index] = rangeItem;
-
- return true;
- }
-
- if (Items[index].Address > item.Address)
- {
- break;
- }
-
- index++;
- }
- }
-
- return false;
- }
-
- ///
- /// Updates an item's end address on the list. Address must be the same.
- ///
- /// The RangeItem to be updated
- /// True if the item was located and updated, false otherwise
- protected override bool Update(RangeItem item)
- {
- int index = BinarySearch(item.Address);
-
- if (index >= 0)
- {
- while (index < Count)
- {
- if (Items[index].Equals(item))
- {
- RangeItem rangeItem = new(item.Value) { Previous = item.Previous, Next = item.Next };
-
- if (index > 0)
- {
- Items[index - 1].Next = rangeItem;
- }
-
- if (index < Count - 1)
- {
- Items[index + 1].Previous = rangeItem;
- }
-
- Items[index] = rangeItem;
-
- return true;
- }
-
- if (Items[index].Address > item.Address)
- {
- break;
- }
-
- index++;
- }
- }
-
- return false;
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void Insert(int index, RangeItem item)
- {
- Debug.Assert(item.Address != item.EndAddress);
-
- Debug.Assert(item.Address % 32 == 0);
-
if (Count + 1 > Items.Length)
{
Array.Resize(ref Items, Items.Length + BackingGrowthSize);
@@ -220,7 +122,7 @@ namespace Ryujinx.Memory.Range
/// The first item in the range of items to be removed
/// The last item in the range of items to be removed
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public override void RemoveRange(RangeItem startItem, RangeItem endItem)
+ public override void RemoveRange(T startItem, T endItem)
{
if (startItem is null)
{
@@ -229,30 +131,29 @@ namespace Ryujinx.Memory.Range
if (startItem == endItem)
{
- Remove(startItem.Value);
+ Remove(startItem);
return;
}
- int startIndex = BinarySearch(startItem.Address);
- int endIndex = BinarySearch(endItem.Address);
+ (int index, int endIndex) = BinarySearchEdges(startItem.Address, endItem.EndAddress);
- if (endIndex < Count - 1)
+ if (endIndex < Count)
{
- Items[endIndex + 1].Previous = startIndex > 0 ? Items[startIndex - 1] : null;
+ Items[endIndex].Previous = index > 0 ? Items[index - 1] : null;
}
- if (startIndex > 0)
+ if (index > 0)
{
- Items[startIndex - 1].Next = endIndex < Count - 1 ? Items[endIndex + 1] : null;
+ Items[index - 1].Next = endIndex < Count ? Items[endIndex] : null;
}
- if (endIndex < Count - 1)
+ if (endIndex < Count)
{
- Array.Copy(Items, endIndex + 1, Items, startIndex, Count - endIndex - 1);
+ Array.Copy(Items, endIndex, Items, index, Count - endIndex);
}
- Count -= endIndex - startIndex + 1;
+ Count -= endIndex - index;
}
///
@@ -268,7 +169,7 @@ namespace Ryujinx.Memory.Range
{
while (index < Count)
{
- if (Items[index].Value.Equals(item))
+ if (Items[index] == item)
{
RemoveAt(index);
@@ -298,7 +199,7 @@ namespace Ryujinx.Memory.Range
/// Size in bytes of the range
/// The overlapping item, or the default value for the type if none found
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public override RangeItem FindOverlap(ulong address, ulong size)
+ public override T FindOverlap(ulong address, ulong size)
{
int index = BinarySearchLeftEdge(address, address + size);
@@ -321,7 +222,7 @@ namespace Ryujinx.Memory.Range
/// Size in bytes of the range
/// The overlapping item, or the default value for the type if none found
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public override RangeItem FindOverlapFast(ulong address, ulong size)
+ public override T FindOverlapFast(ulong address, ulong size)
{
int index = BinarySearch(address, address + size);
@@ -340,7 +241,7 @@ namespace Ryujinx.Memory.Range
/// Size in bytes of the range
/// Output array where matches will be written. It is automatically resized to fit the results
/// Range information of overlapping items found
- private OverlapResult FindOverlaps(ulong address, ulong size, ref RangeItem[] output)
+ private OverlapResult FindOverlaps(ulong address, ulong size, ref T[] output)
{
int outputCount = 0;
@@ -353,7 +254,7 @@ namespace Ryujinx.Memory.Range
for (int i = startIndex; i < Count; i++)
{
- ref RangeItem item = ref Items[i];
+ T item = Items[i];
if (item.Address >= endAddress)
{
@@ -398,7 +299,7 @@ namespace Ryujinx.Memory.Range
{
for (int i = 0; i < Count; i++)
{
- yield return Items[i].Value;
+ yield return Items[i];
}
}
}
diff --git a/src/Ryujinx.Memory/Range/RangeListBase.cs b/src/Ryujinx.Memory/Range/RangeListBase.cs
index 01fe1b0dc..9f0284253 100644
--- a/src/Ryujinx.Memory/Range/RangeListBase.cs
+++ b/src/Ryujinx.Memory/Range/RangeListBase.cs
@@ -1,56 +1,22 @@
using Ryujinx.Common;
+using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
namespace Ryujinx.Memory.Range
{
- public class RangeItem where TValue : IRange
+ public interface IRangeListRange : IRange where TValue : class, IRangeListRange
{
- public RangeItem Next;
- public RangeItem Previous;
-
- public ulong Address;
- public ulong EndAddress;
-
- public TValue Value;
-
- public RangeItem()
- {
-
- }
-
- public RangeItem(TValue value)
- {
- Address = value.Address;
- EndAddress = value.Address + value.Size;
- Value = value;
- }
-
- 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)
- {
- return Address < endAddress && address < EndAddress;
- }
+ public TValue Next { get; set; }
+ public TValue Previous { get; set; }
}
- public unsafe abstract class RangeListBase : IEnumerable where T : IRange
+ public unsafe abstract class RangeListBase : IEnumerable where T : class, IRangeListRange
{
- protected static readonly ObjectPool> _rangeItemPool = new(() => new RangeItem());
private const int BackingInitialSize = 1024;
- protected RangeItem[] Items;
+ protected T[] Items;
protected readonly int BackingGrowthSize;
public int Count { get; protected set; }
@@ -62,32 +28,18 @@ namespace Ryujinx.Memory.Range
protected RangeListBase(int backingInitialSize = BackingInitialSize)
{
BackingGrowthSize = backingInitialSize;
- Items = new RangeItem[backingInitialSize];
+ Items = new T[backingInitialSize];
}
public abstract void Add(T item);
-
- ///
- /// Updates an item's end address on the list. Address must be the same.
- ///
- /// The item to be updated
- /// True if the item was located and updated, false otherwise
- protected abstract bool Update(T item);
-
- ///
- /// Updates an item's end address on the list. Address must be the same.
- ///
- /// The RangeItem to be updated
- /// True if the item was located and updated, false otherwise
- protected abstract bool Update(RangeItem item);
public abstract bool Remove(T item);
- public abstract void RemoveRange(RangeItem startItem, RangeItem endItem);
+ public abstract void RemoveRange(T startItem, T endItem);
- public abstract RangeItem FindOverlap(ulong address, ulong size);
+ public abstract T FindOverlap(ulong address, ulong size);
- public abstract RangeItem FindOverlapFast(ulong address, ulong size);
+ public abstract T FindOverlapFast(ulong address, ulong size);
///
/// Performs binary search on the internal list of items.
@@ -106,7 +58,7 @@ namespace Ryujinx.Memory.Range
int middle = left + (range >> 1);
- ref RangeItem item = ref Items[middle];
+ T item = Items[middle];
if (item.Address == address)
{
@@ -144,7 +96,7 @@ namespace Ryujinx.Memory.Range
int middle = left + (range >> 1);
- ref RangeItem item = ref Items[middle];
+ T item = Items[middle];
if (item.OverlapsWith(address, endAddress))
{
@@ -185,7 +137,7 @@ namespace Ryujinx.Memory.Range
int middle = left + (range >> 1);
- ref RangeItem item = ref Items[middle];
+ T item = Items[middle];
bool match = item.OverlapsWith(address, endAddress);
@@ -237,7 +189,7 @@ namespace Ryujinx.Memory.Range
int middle = right - (range >> 1);
- ref RangeItem item = ref Items[middle];
+ T item = Items[middle];
bool match = item.OverlapsWith(address, endAddress);
@@ -282,7 +234,7 @@ namespace Ryujinx.Memory.Range
if (Count == 1)
{
- ref RangeItem item = ref Items[0];
+ T item = Items[0];
if (item.OverlapsWith(address, endAddress))
{
@@ -312,7 +264,7 @@ namespace Ryujinx.Memory.Range
int middle = left + (range >> 1);
- ref RangeItem item = ref Items[middle];
+ T item = Items[middle];
bool match = item.OverlapsWith(address, endAddress);
@@ -369,7 +321,7 @@ namespace Ryujinx.Memory.Range
int middle = right - (range >> 1);
- ref RangeItem item = ref Items[middle];
+ T item = Items[middle];
bool match = item.OverlapsWith(address, endAddress);
diff --git a/src/Ryujinx.Memory/Tracking/AbstractRegion.cs b/src/Ryujinx.Memory/Tracking/AbstractRegion.cs
index 7226fe954..09703a253 100644
--- a/src/Ryujinx.Memory/Tracking/AbstractRegion.cs
+++ b/src/Ryujinx.Memory/Tracking/AbstractRegion.cs
@@ -5,7 +5,7 @@ namespace Ryujinx.Memory.Tracking
///
/// A region of memory.
///
- abstract class AbstractRegion : INonOverlappingRange
+ abstract class AbstractRegion : INonOverlappingRange where T : class, INonOverlappingRange
{
///
/// Base address.
@@ -21,6 +21,9 @@ namespace Ryujinx.Memory.Tracking
/// End address.
///
public ulong EndAddress => Address + Size;
+
+ public T Next { get; set; }
+ public T Previous { get; set; }
///
/// Create a new region.
@@ -37,11 +40,11 @@ namespace Ryujinx.Memory.Tracking
/// Check if this range overlaps with another.
///
/// Base address
- /// Size of the range
+ /// End address
/// True if overlapping, false otherwise
- public bool OverlapsWith(ulong address, ulong size)
+ public bool OverlapsWith(ulong address, ulong endAddress)
{
- return Address < address + size && address < EndAddress;
+ return Address < endAddress && address < EndAddress;
}
///
@@ -68,6 +71,6 @@ namespace Ryujinx.Memory.Tracking
///
/// Address to split the region around
/// The second part of the split region, with start address at the given split.
- public abstract INonOverlappingRange Split(ulong splitAddress);
+ public abstract INonOverlappingRange Split(ulong splitAddress);
}
}
diff --git a/src/Ryujinx.Memory/Tracking/MemoryTracking.cs b/src/Ryujinx.Memory/Tracking/MemoryTracking.cs
index b4b06da8c..0160791e8 100644
--- a/src/Ryujinx.Memory/Tracking/MemoryTracking.cs
+++ b/src/Ryujinx.Memory/Tracking/MemoryTracking.cs
@@ -81,10 +81,10 @@ namespace Ryujinx.Memory.Tracking
{
NonOverlappingRangeList regions = type == 0 ? _virtualRegions : _guestVirtualRegions;
regions.Lock.EnterReadLock();
- Span> overlaps = regions.FindOverlapsAsSpan(va, size);
+ ReadOnlySpan overlaps = regions.FindOverlapsAsSpan(va, size);
for (int i = 0; i < overlaps.Length; i++)
{
- VirtualRegion region = overlaps[i].Value;
+ VirtualRegion region = overlaps[i];
// If the region has been fully remapped, signal that it has been mapped again.
bool remapped = _memoryManager.IsRangeMapped(region.Address, region.Size);
@@ -117,11 +117,11 @@ namespace Ryujinx.Memory.Tracking
{
NonOverlappingRangeList regions = type == 0 ? _virtualRegions : _guestVirtualRegions;
regions.Lock.EnterReadLock();
- Span> overlaps = regions.FindOverlapsAsSpan(va, size);
+ ReadOnlySpan overlaps = regions.FindOverlapsAsSpan(va, size);
for (int i = 0; i < overlaps.Length; i++)
{
- overlaps[i].Value.SignalMappingChanged(false);
+ overlaps[i].SignalMappingChanged(false);
}
regions.Lock.ExitReadLock();
}
@@ -301,7 +301,7 @@ 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, out int length);
+ VirtualRegion[] overlaps = regions.FindOverlapsAsArray(address, size, out int length);
regions.Lock.ExitReadLock();
if (length == 0 && !precise)
@@ -327,7 +327,7 @@ namespace Ryujinx.Memory.Tracking
for (int i = 0; i < length; i++)
{
- VirtualRegion region = overlaps[i].Value;
+ VirtualRegion region = overlaps[i];
if (precise)
{
@@ -341,7 +341,7 @@ namespace Ryujinx.Memory.Tracking
if (length != 0)
{
- ArrayPool>.Shared.Return(overlaps);
+ ArrayPool.Shared.Return(overlaps);
}
}
}
diff --git a/src/Ryujinx.Memory/Tracking/VirtualRegion.cs b/src/Ryujinx.Memory/Tracking/VirtualRegion.cs
index b86631db2..e95754c7a 100644
--- a/src/Ryujinx.Memory/Tracking/VirtualRegion.cs
+++ b/src/Ryujinx.Memory/Tracking/VirtualRegion.cs
@@ -6,7 +6,7 @@ namespace Ryujinx.Memory.Tracking
///
/// A region of virtual memory.
///
- class VirtualRegion : AbstractRegion
+ class VirtualRegion : AbstractRegion
{
public List Handles = [];
@@ -137,7 +137,7 @@ namespace Ryujinx.Memory.Tracking
}
}
- public override INonOverlappingRange Split(ulong splitAddress)
+ public override INonOverlappingRange Split(ulong splitAddress)
{
VirtualRegion newRegion = new(_tracking, splitAddress, EndAddress - splitAddress, Guest, _lastPermission);
Size = splitAddress - Address;
diff --git a/src/Ryujinx/Headless/Windows/WindowBase.cs b/src/Ryujinx/Headless/Windows/WindowBase.cs
index 8e06a3f20..49b2a389a 100644
--- a/src/Ryujinx/Headless/Windows/WindowBase.cs
+++ b/src/Ryujinx/Headless/Windows/WindowBase.cs
@@ -580,5 +580,10 @@ namespace Ryujinx.Headless
{
return AccountSaveDataManager.GetLastUsedUser();
}
+
+ public void TakeScreenshot()
+ {
+ throw new NotImplementedException();
+ }
}
}
diff --git a/src/Ryujinx/Program.cs b/src/Ryujinx/Program.cs
index abab3daca..d77e79756 100644
--- a/src/Ryujinx/Program.cs
+++ b/src/Ryujinx/Program.cs
@@ -17,6 +17,7 @@ using Ryujinx.Common.Configuration;
using Ryujinx.Common.GraphicsDriver;
using Ryujinx.Common.Logging;
using Ryujinx.Common.SystemInterop;
+using Ryujinx.Common.Utilities;
using Ryujinx.Graphics.Vulkan.MoltenVK;
using Ryujinx.Headless;
using Ryujinx.SDL3.Common;
@@ -46,7 +47,7 @@ namespace Ryujinx.Ava
public static int Main(string[] args)
{
Version = ReleaseInformation.Version;
-
+
if (OperatingSystem.IsWindows())
{
if (!OperatingSystem.IsWindowsVersionAtLeast(10, 0, 19041))
@@ -55,8 +56,11 @@ namespace Ryujinx.Ava
return 0;
}
- if (Environment.CurrentDirectory.StartsWithIgnoreCase("C:\\Program Files") ||
- Environment.CurrentDirectory.StartsWithIgnoreCase("C:\\Program Files (x86)"))
+ var programFiles = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);
+ var programFilesX86 = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86);
+
+ if (Environment.CurrentDirectory.StartsWithIgnoreCase(programFiles) ||
+ Environment.CurrentDirectory.StartsWithIgnoreCase(programFilesX86))
{
_ = Win32NativeInterop.MessageBoxA(nint.Zero, "Ryujinx is not intended to be run from the Program Files folder. Please move it out and relaunch.", $"Ryujinx {Version}", MbIconwarning);
return 0;
@@ -73,11 +77,23 @@ namespace Ryujinx.Ava
}
}
+ bool noGuiArg = ConsumeCommandLineArgument(ref args, "--no-gui") || ConsumeCommandLineArgument(ref args, "nogui");
+ bool coreDumpArg = ConsumeCommandLineArgument(ref args, "--core-dumps");
+
+ // TODO: Ryujinx causes core dumps on Linux when it exits "uncleanly", eg. through an unhandled exception.
+ // This is undesirable and causes very odd behavior during development (the process stops responding,
+ // the .NET debugger freezes or suddenly detaches, /tmp/ gets filled etc.), unless explicitly requested by the user.
+ // This needs to be investigated, but calling prctl() is better than modifying system-wide settings or leaving this be.
+ if (!coreDumpArg)
+ {
+ OsUtils.SetCoreDumpable(false);
+ }
+
PreviewerDetached = true;
- if (args.Length > 0 && args[0] is "--no-gui" or "nogui")
+ if (noGuiArg)
{
- HeadlessRyujinx.Entrypoint(args[1..]);
+ HeadlessRyujinx.Entrypoint(args);
return 0;
}
@@ -112,6 +128,14 @@ namespace Ryujinx.Ava
: [Win32RenderingMode.Software]
});
+ private static bool ConsumeCommandLineArgument(ref string[] args, string targetArgument)
+ {
+ List argList = [.. args];
+ bool found = argList.Remove(targetArgument);
+ args = argList.ToArray();
+ return found;
+ }
+
private static void Initialize(string[] args)
{
// Ensure Discord presence timestamp begins at the absolute start of when Ryujinx is launched
@@ -177,7 +201,6 @@ namespace Ryujinx.Ava
}
}
-
public static string GetDirGameUserConfig(string gameId, bool changeFolderForGame = false)
{
if (string.IsNullOrEmpty(gameId))
@@ -196,22 +219,26 @@ namespace Ryujinx.Ava
return gameDir;
}
- public static void ReloadConfig()
+ public static void ReloadConfig(bool isRunGameWithCustomConfig = false)
{
string localConfigurationPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ReleaseInformation.ConfigName);
string appDataConfigurationPath = Path.Combine(AppDataManager.BaseDirPath, ReleaseInformation.ConfigName);
- // Now load the configuration as the other subsystems are now registered
- if (File.Exists(localConfigurationPath))
- {
- ConfigurationPath = localConfigurationPath;
- }
- else if (File.Exists(appDataConfigurationPath))
- {
- ConfigurationPath = appDataConfigurationPath;
- }
+ if (!isRunGameWithCustomConfig) // To return settings from the game folder if the user configuration exists
+ {
+ // Now load the configuration as the other subsystems are now registered
+ if (File.Exists(localConfigurationPath))
+ {
+ ConfigurationPath = localConfigurationPath;
+ }
+ else if (File.Exists(appDataConfigurationPath))
+ {
+ ConfigurationPath = appDataConfigurationPath;
+ }
+ }
+
if (ConfigurationPath == null)
{
// No configuration, we load the default values and save it to disk
diff --git a/src/Ryujinx/UI/Applet/AvaHostUIHandler.cs b/src/Ryujinx/UI/Applet/AvaHostUIHandler.cs
index 38670e5d5..45235ee3f 100644
--- a/src/Ryujinx/UI/Applet/AvaHostUIHandler.cs
+++ b/src/Ryujinx/UI/Applet/AvaHostUIHandler.cs
@@ -327,5 +327,10 @@ namespace Ryujinx.Ava.UI.Applet
return profile;
}
+
+ public void TakeScreenshot()
+ {
+ _parent.ViewModel.AppHost.ScreenshotRequested = true;
+ }
}
}
diff --git a/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs
index 2236b27f6..651dc901c 100644
--- a/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs
+++ b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs
@@ -1333,7 +1333,10 @@ namespace Ryujinx.Ava.UI.ViewModels
}
}
- public void TakeScreenshot() => AppHost.ScreenshotRequested = true;
+ public void TakeScreenshot()
+ {
+ AppHost.ScreenshotRequested = true;
+ }
public void HideUi() => ShowMenuAndStatusBar = false;
diff --git a/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs b/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs
index 233c30bad..d5d9b8218 100644
--- a/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs
+++ b/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs
@@ -88,6 +88,11 @@ namespace Ryujinx.Ava.UI.ViewModels
get;
}
+ public bool IsCustomConfig
+ {
+ get;
+ }
+
public bool IsGameTitleNotNull => !string.IsNullOrEmpty(GameTitle);
public double PanelOpacity => IsGameTitleNotNull ? 0.5 : 1;
@@ -459,7 +464,7 @@ namespace Ryujinx.Ava.UI.ViewModels
using MemoryStream ms = new(gameIconData);
GameIcon = new Bitmap(ms);
}
-
+ IsCustomConfig = customConfig;
IsGameRunning = gameRunning;
GamePath = gamePath;
GameTitle = gameName;
@@ -869,16 +874,11 @@ namespace Ryujinx.Ava.UI.ViewModels
GameListNeedsRefresh = false;
}
- private static void RevertIfNotSaved()
+ private static void RevertIfNotSaved(bool isCustomConfig = false, bool isGameRunning = false)
{
- /*
- maybe this is an unnecessary check(all options need to be tested)
- if (string.IsNullOrEmpty(Program.GlobalConfigurationPath))
- {
- Program.ReloadConfig();
- }
- */
- Program.ReloadConfig();
+ // Restores settings for a custom configuration during a game, if the condition is met.
+ // If the condition is not met (parameter is false), restores global (default) configuration instead.
+ Program.ReloadConfig(isCustomConfig && isGameRunning);
}
public void ApplyButton()
@@ -895,14 +895,14 @@ namespace Ryujinx.Ava.UI.ViewModels
File.Delete(gameDir);
}
- RevertIfNotSaved();
+ RevertIfNotSaved(IsCustomConfig, IsGameRunning);
CloseWindow?.Invoke();
}
public void SaveUserConfig()
{
SaveSettings();
- RevertIfNotSaved(); // Revert global configuration after saving user configuration
+ RevertIfNotSaved(IsCustomConfig, IsGameRunning); // Revert global or custom configuration after saving user configuration
CloseWindow?.Invoke();
}
@@ -934,7 +934,7 @@ namespace Ryujinx.Ava.UI.ViewModels
public void CancelButton()
{
- RevertIfNotSaved();
+ RevertIfNotSaved(IsCustomConfig, IsGameRunning);
CloseWindow?.Invoke();
}
}
diff --git a/src/Ryujinx/UI/Windows/MainWindow.axaml.cs b/src/Ryujinx/UI/Windows/MainWindow.axaml.cs
index e7934f38a..a6393ac25 100644
--- a/src/Ryujinx/UI/Windows/MainWindow.axaml.cs
+++ b/src/Ryujinx/UI/Windows/MainWindow.axaml.cs
@@ -72,6 +72,10 @@ namespace Ryujinx.Ava.UI.Windows
// Correctly size window when 'TitleBar' is enabled (Nov. 14, 2024)
public readonly double TitleBarHeight;
+ // Avalonia will make the window higher than we set.
+ // Store the delta between those 2 values to save the correct window height when exiting.
+ private double _heightCorrection = 0;
+
public readonly double StatusBarHeight;
public readonly double MenuBarHeight;
@@ -468,6 +472,21 @@ namespace Ryujinx.Ava.UI.Windows
}
}
+ private void SaveHeightCorrection()
+ {
+ if (WindowState != WindowState.Normal)
+ {
+ return;
+ }
+
+ // Store the delta between the height we set (ViewModel.WindowHeight) and the actual height returned by Avalonia (Height).
+ _heightCorrection = Height - ViewModel.WindowHeight;
+ if (Math.Abs(_heightCorrection) > 50)
+ {
+ _heightCorrection = 0;
+ }
+ }
+
private void SaveWindowSizePosition()
{
ConfigurationState.Instance.UI.WindowStartup.WindowMaximized.Value = WindowState == WindowState.Maximized;
@@ -477,8 +496,8 @@ namespace Ryujinx.Ava.UI.Windows
{
// Since scaling is being applied to the loaded settings from disk (see SetWindowSizePosition() above), scaling should be removed from width/height before saving out to disk
// as well - otherwise anyone not using a 1.0 scale factor their window will increase in size with every subsequent launch of the program when scaling is applied (Nov. 14, 2024)
- ConfigurationState.Instance.UI.WindowStartup.WindowSizeHeight.Value = (int)(Height / Program.WindowScaleFactor);
- ConfigurationState.Instance.UI.WindowStartup.WindowSizeWidth.Value = (int)(Width / Program.WindowScaleFactor);
+ ConfigurationState.Instance.UI.WindowStartup.WindowSizeHeight.Value = (int)Math.Round((Height - _heightCorrection) / Program.WindowScaleFactor);
+ ConfigurationState.Instance.UI.WindowStartup.WindowSizeWidth.Value = (int)Math.Round(Width / Program.WindowScaleFactor);
ConfigurationState.Instance.UI.WindowStartup.WindowPositionX.Value = Position.X;
ConfigurationState.Instance.UI.WindowStartup.WindowPositionY.Value = Position.Y;
@@ -539,6 +558,8 @@ namespace Ryujinx.Ava.UI.Windows
LoadApplications();
}
+ Dispatcher.UIThread.Post(SaveHeightCorrection, DispatcherPriority.Loaded);
+
_ = CheckLaunchState();
}