Compare commits

...

4 commits

Author SHA1 Message Date
_Neo_
f15a1148a4 Further fixing some Amiibo shenanigans 2025-12-07 14:04:15 +02:00
Neo
8cef34450b Merge branch ryujinx:master into ui-actions-new 2025-12-07 03:32:51 -06:00
LotP
3a593b6084 Fix kaddressarbiter crash (ryubing/ryujinx!235)
See merge request ryubing/ryujinx!235
2025-12-06 20:16:43 -06:00
LotP
c3155fcadb Memory Changes 3.2 (ryubing/ryujinx!234)
See merge request ryubing/ryujinx!234
2025-12-06 17:19:19 -06:00
39 changed files with 573 additions and 675 deletions

1
.gitignore vendored
View file

@ -100,6 +100,7 @@ DocProject/Help/html
# Click-Once directory # Click-Once directory
publish/ publish/
RyubingMaintainerTools/
# Publish Web Output # Publish Web Output
*.Publish.xml *.Publish.xml

View file

@ -7,6 +7,9 @@ namespace Ryujinx.Common.Memory
{ {
private static readonly RecyclableMemoryStreamManager _shared = new(); private static readonly RecyclableMemoryStreamManager _shared = new();
private static readonly ObjectPool<RecyclableMemoryStream> _streamPool =
new(() => new RecyclableMemoryStream(_shared, Guid.NewGuid(), null, 0));
/// <summary> /// <summary>
/// We don't expose the <c>RecyclableMemoryStreamManager</c> directly because version 2.x /// We don't expose the <c>RecyclableMemoryStreamManager</c> directly because version 2.x
/// returns them as <c>MemoryStream</c>. This Shared class is here to a) offer only the GetStream() versions we use /// returns them as <c>MemoryStream</c>. This Shared class is here to a) offer only the GetStream() versions we use
@ -19,7 +22,12 @@ namespace Ryujinx.Common.Memory
/// </summary> /// </summary>
/// <returns>A <c>RecyclableMemoryStream</c></returns> /// <returns>A <c>RecyclableMemoryStream</c></returns>
public static RecyclableMemoryStream GetStream() public static RecyclableMemoryStream GetStream()
=> new(_shared); {
RecyclableMemoryStream stream = _streamPool.Allocate();
stream.SetLength(0);
return stream;
}
/// <summary> /// <summary>
/// Retrieve a new <c>MemoryStream</c> object with the contents copied from the provided /// Retrieve a new <c>MemoryStream</c> object with the contents copied from the provided
@ -55,7 +63,8 @@ namespace Ryujinx.Common.Memory
RecyclableMemoryStream stream = null; RecyclableMemoryStream stream = null;
try try
{ {
stream = new RecyclableMemoryStream(_shared, id, tag, buffer.Length); stream = _streamPool.Allocate();
stream.SetLength(0);
stream.Write(buffer); stream.Write(buffer);
stream.Position = 0; stream.Position = 0;
return stream; return stream;
@ -83,7 +92,8 @@ namespace Ryujinx.Common.Memory
RecyclableMemoryStream stream = null; RecyclableMemoryStream stream = null;
try try
{ {
stream = new RecyclableMemoryStream(_shared, id, tag, count); stream = _streamPool.Allocate();
stream.SetLength(0);
stream.Write(buffer, offset, count); stream.Write(buffer, offset, count);
stream.Position = 0; stream.Position = 0;
return stream; return stream;
@ -94,6 +104,11 @@ namespace Ryujinx.Common.Memory
throw; throw;
} }
} }
public static void ReleaseStream(RecyclableMemoryStream stream)
{
_streamPool.Release(stream);
}
} }
} }
} }

View file

@ -82,7 +82,7 @@ namespace Ryujinx.Graphics.GAL
void SetRasterizerDiscard(bool discard); void SetRasterizerDiscard(bool discard);
void SetRenderTargetColorMasks(ReadOnlySpan<uint> componentMask); void SetRenderTargetColorMasks(ReadOnlySpan<uint> componentMask);
void SetRenderTargets(ITexture[] colors, ITexture depthStencil); void SetRenderTargets(Span<ITexture> colors, ITexture depthStencil);
void SetScissors(ReadOnlySpan<Rectangle<int>> regions); void SetScissors(ReadOnlySpan<Rectangle<int>> regions);

View file

@ -1,5 +1,6 @@
using Ryujinx.Graphics.GAL.Multithreading.Model; using Ryujinx.Graphics.GAL.Multithreading.Model;
using Ryujinx.Graphics.GAL.Multithreading.Resources; using Ryujinx.Graphics.GAL.Multithreading.Resources;
using System;
using System.Buffers; using System.Buffers;
namespace Ryujinx.Graphics.GAL.Multithreading.Commands namespace Ryujinx.Graphics.GAL.Multithreading.Commands
@ -8,11 +9,13 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands
{ {
public static readonly ArrayPool<ITexture> ArrayPool = ArrayPool<ITexture>.Create(512, 50); public static readonly ArrayPool<ITexture> ArrayPool = ArrayPool<ITexture>.Create(512, 50);
public readonly CommandType CommandType => CommandType.SetRenderTargets; public readonly CommandType CommandType => CommandType.SetRenderTargets;
private int _colorsCount;
private TableRef<ITexture[]> _colors; private TableRef<ITexture[]> _colors;
private TableRef<ITexture> _depthStencil; private TableRef<ITexture> _depthStencil;
public void Set(TableRef<ITexture[]> colors, TableRef<ITexture> depthStencil) public void Set(int colorsCount, TableRef<ITexture[]> colors, TableRef<ITexture> depthStencil)
{ {
_colorsCount = colorsCount;
_colors = colors; _colors = colors;
_depthStencil = depthStencil; _depthStencil = depthStencil;
} }
@ -20,16 +23,15 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands
public static void Run(ref SetRenderTargetsCommand command, ThreadedRenderer threaded, IRenderer renderer) public static void Run(ref SetRenderTargetsCommand command, ThreadedRenderer threaded, IRenderer renderer)
{ {
ITexture[] colors = command._colors.Get(threaded); ITexture[] colors = command._colors.Get(threaded);
ITexture[] colorsCopy = ArrayPool.Rent(colors.Length); Span<ITexture> 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<ThreadedTexture>(threaded)?.Base); renderer.Pipeline.SetRenderTargets(colorsSpan, command._depthStencil.GetAs<ThreadedTexture>(threaded)?.Base);
ArrayPool.Return(colorsCopy);
ArrayPool.Return(colors); ArrayPool.Return(colors);
} }
} }

View file

@ -267,12 +267,12 @@ namespace Ryujinx.Graphics.GAL.Multithreading
_renderer.QueueCommand(); _renderer.QueueCommand();
} }
public unsafe void SetRenderTargets(ITexture[] colors, ITexture depthStencil) public unsafe void SetRenderTargets(Span<ITexture> colors, ITexture depthStencil)
{ {
ITexture[] colorsCopy = SetRenderTargetsCommand.ArrayPool.Rent(colors.Length); ITexture[] colorsCopy = SetRenderTargetsCommand.ArrayPool.Rent(colors.Length);
colors.CopyTo(colorsCopy, 0); colors.CopyTo(colorsCopy.AsSpan());
_renderer.New<SetRenderTargetsCommand>()->Set(Ref(colorsCopy), Ref(depthStencil)); _renderer.New<SetRenderTargetsCommand>()->Set(colors.Length, Ref(colorsCopy), Ref(depthStencil));
_renderer.QueueCommand(); _renderer.QueueCommand();
} }

View file

@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
/// </summary> /// </summary>
public class ThreadedRenderer : IRenderer 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 MaxRefsPerCommand = 2;
private const int QueueCount = 10000; private const int QueueCount = 10000;

View file

@ -15,7 +15,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <summary> /// <summary>
/// Buffer, used to store vertex and index data, uniform and storage buffers, and others. /// Buffer, used to store vertex and index data, uniform and storage buffers, and others.
/// </summary> /// </summary>
class Buffer : INonOverlappingRange, ISyncActionHandler, IDisposable class Buffer : INonOverlappingRange<Buffer>, ISyncActionHandler, IDisposable
{ {
private const ulong GranularBufferThreshold = 4096; private const ulong GranularBufferThreshold = 4096;
@ -41,6 +41,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// End address of the buffer in guest memory. /// End address of the buffer in guest memory.
/// </summary> /// </summary>
public ulong EndAddress => Address + Size; public ulong EndAddress => Address + Size;
public Buffer Next { get; set; }
public Buffer Previous { get; set; }
/// <summary> /// <summary>
/// Increments when the buffer is (partially) unmapped or disposed. /// Increments when the buffer is (partially) unmapped or disposed.
@ -87,6 +90,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
private readonly bool _useGranular; private readonly bool _useGranular;
private bool _syncActionRegistered; private bool _syncActionRegistered;
private bool _bufferInherited;
private int _referenceCount = 1; private int _referenceCount = 1;
@ -113,7 +117,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
ulong size, ulong size,
BufferStage stage, BufferStage stage,
bool sparseCompatible, bool sparseCompatible,
RangeItem<Buffer>[] baseBuffers) Buffer[] baseBuffers)
{ {
_context = context; _context = context;
_physicalMemory = physicalMemory; _physicalMemory = physicalMemory;
@ -134,15 +138,15 @@ namespace Ryujinx.Graphics.Gpu.Memory
if (baseBuffers.Length != 0) if (baseBuffers.Length != 0)
{ {
baseHandles = new List<IRegionHandle>(); baseHandles = new List<IRegionHandle>();
foreach (RangeItem<Buffer> 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 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. /// Checks if a given range overlaps with the buffer.
/// </summary> /// </summary>
/// <param name="address">Start address of the range</param> /// <param name="address">Start address of the range</param>
/// <param name="size">Size in bytes of the range</param> /// <param name="endAddress">End address of the range</param>
/// <returns>True if the range overlaps, false otherwise</returns> /// <returns>True if the range overlaps, false otherwise</returns>
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<Buffer> Split(ulong splitAddress)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }
@ -426,10 +430,13 @@ namespace Ryujinx.Graphics.Gpu.Memory
{ {
_syncActionRegistered = false; _syncActionRegistered = false;
if (_bufferInherited)
{
return true;
}
if (_useGranular) if (_useGranular)
{ {
_modifiedRanges?.GetRanges(Address, Size, _syncRangeAction); _modifiedRanges?.GetRanges(Address, Size, _syncRangeAction);
} }
else else
@ -453,6 +460,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="from">The buffer to inherit from</param> /// <param name="from">The buffer to inherit from</param>
public void InheritModifiedRanges(Buffer from) public void InheritModifiedRanges(Buffer from)
{ {
from._bufferInherited = true;
if (from._modifiedRanges is { HasRanges: true }) if (from._modifiedRanges is { HasRanges: true })
{ {
if (from._syncActionRegistered && !_syncActionRegistered) if (from._syncActionRegistered && !_syncActionRegistered)

View file

@ -56,7 +56,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="parent">Parent buffer</param> /// <param name="parent">Parent buffer</param>
/// <param name="stage">Initial buffer stage</param> /// <param name="stage">Initial buffer stage</param>
/// <param name="baseBuffers">Buffers to inherit state from</param> /// <param name="baseBuffers">Buffers to inherit state from</param>
public BufferBackingState(GpuContext context, Buffer parent, BufferStage stage, RangeItem<Buffer>[] baseBuffers) public BufferBackingState(GpuContext context, Buffer parent, BufferStage stage, Buffer[] baseBuffers)
{ {
_size = (int)parent.Size; _size = (int)parent.Size;
_systemMemoryType = context.Capabilities.MemoryType; _systemMemoryType = context.Capabilities.MemoryType;
@ -102,9 +102,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
if (baseBuffers.Length != 0) if (baseBuffers.Length != 0)
{ {
foreach (RangeItem<Buffer> item in baseBuffers) foreach (Buffer item in baseBuffers)
{ {
CombineState(item.Value.BackingState); CombineState(item.BackingState);
} }
} }
} }

View file

@ -79,16 +79,13 @@ namespace Ryujinx.Graphics.Gpu.Memory
for (int index = 0; index < range.Count; index++) for (int index = 0; index < range.Count; index++)
{ {
MemoryRange subRange = range.GetSubRange(index); MemoryRange subRange = range.GetSubRange(index);
_buffers.Lock.EnterReadLock(); ReadOnlySpan<Buffer> overlaps = _buffers.FindOverlapsAsSpan(subRange.Address, subRange.Size);
Span<RangeItem<Buffer>> overlaps = _buffers.FindOverlapsAsSpan(subRange.Address, subRange.Size);
for (int i = 0; i < overlaps.Length; i++) 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 alignedEndAddress = (endAddress + alignmentMask) & ~alignmentMask;
ulong alignedSize = alignedEndAddress - alignedAddress; ulong alignedSize = alignedEndAddress - alignedAddress;
Buffer buffer = _buffers.FindOverlap(alignedAddress, alignedSize).Value; Buffer buffer = _buffers.FindOverlap(alignedAddress, alignedSize);
BufferRange bufferRange = buffer.GetRange(alignedAddress, alignedSize, false); BufferRange bufferRange = buffer.GetRange(alignedAddress, alignedSize, false);
alignedSubRanges[i] = new MemoryRange(alignedAddress, alignedSize); alignedSubRanges[i] = new MemoryRange(alignedAddress, alignedSize);
@ -395,7 +392,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
if (subRange.Address != MemoryManager.PteUnmapped) 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); virtualBuffer.AddPhysicalDependency(buffer, subRange.Address, dstOffset, subRange.Size);
physicalBuffers.Add(buffer); physicalBuffers.Add(buffer);
@ -487,10 +484,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="stage">The type of usage that created the buffer</param> /// <param name="stage">The type of usage that created the buffer</param>
private void CreateBufferAligned(ulong address, ulong size, BufferStage stage) private void CreateBufferAligned(ulong address, ulong size, BufferStage stage)
{ {
Buffer newBuffer = null; ReadOnlySpan<Buffer> overlaps = _buffers.FindOverlapsAsSpan(address, size);
_buffers.Lock.EnterWriteLock();
Span<RangeItem<Buffer>> overlaps = _buffers.FindOverlapsAsSpan(address, size);
if (overlaps.Length != 0) 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. // 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. // 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; ulong growthSize = (existingSize + Math.Min(existingSize >> 1, MaxDynamicGrowthSize)) & ~BufferAlignmentMask;
size = Math.Max(size, growthSize); size = Math.Max(size, growthSize);
@ -535,39 +529,22 @@ namespace Ryujinx.Graphics.Gpu.Memory
for (int i = 0; i < overlaps.Length; i++) for (int i = 0; i < overlaps.Length; i++)
{ {
anySparseCompatible |= overlaps[i].Value.SparseCompatible; anySparseCompatible |= overlaps[i].SparseCompatible;
} }
RangeItem<Buffer>[] overlapsArray = overlaps.ToArray(); Buffer[] overlapsArray = overlaps.ToArray();
_buffers.RemoveRange(overlaps[0], overlaps[^1]); _buffers.RemoveRange(overlaps[0], overlaps[^1]);
_buffers.Lock.ExitWriteLock();
ulong newSize = endAddress - address; ulong newSize = endAddress - address;
newBuffer = CreateBufferAligned(address, newSize, stage, anySparseCompatible, overlapsArray); _buffers.Add(CreateBufferAligned(address, newSize, stage, anySparseCompatible, overlapsArray));
}
else
{
_buffers.Lock.ExitWriteLock();
} }
} }
else else
{ {
_buffers.Lock.ExitWriteLock();
// No overlap, just create a new buffer. // No overlap, just create a new buffer.
newBuffer = new(_context, _physicalMemory, address, size, stage, sparseCompatible: false, []); _buffers.Add(new(_context, _physicalMemory, address, size, stage, sparseCompatible: false, []));
}
if (newBuffer is not null)
{
_buffers.Lock.EnterWriteLock();
_buffers.Add(newBuffer);
_buffers.Lock.ExitWriteLock();
} }
} }
@ -583,10 +560,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
private void CreateBufferAligned(ulong address, ulong size, BufferStage stage, ulong alignment) private void CreateBufferAligned(ulong address, ulong size, BufferStage stage, ulong alignment)
{ {
bool sparseAligned = alignment >= SparseBufferAlignmentSize; bool sparseAligned = alignment >= SparseBufferAlignmentSize;
Buffer newBuffer = null;
_buffers.Lock.EnterWriteLock(); ReadOnlySpan<Buffer> overlaps = _buffers.FindOverlapsAsSpan(address, size);
Span<RangeItem<Buffer>> overlaps = _buffers.FindOverlapsAsSpan(address, size);
if (overlaps.Length != 0) if (overlaps.Length != 0)
{ {
@ -598,7 +573,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
if (overlaps[0].Address > address || if (overlaps[0].Address > address ||
overlaps[0].EndAddress < endAddress || overlaps[0].EndAddress < endAddress ||
(overlaps[0].Address & (alignment - 1)) != 0 || (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. // We need to make sure the new buffer is properly aligned.
// However, after the range is aligned, it is possible that it // However, after the range is aligned, it is possible that it
@ -622,35 +597,18 @@ namespace Ryujinx.Graphics.Gpu.Memory
ulong newSize = endAddress - address; ulong newSize = endAddress - address;
RangeItem<Buffer>[] overlapsArray = overlaps.ToArray(); Buffer[] overlapsArray = overlaps.ToArray();
_buffers.RemoveRange(overlaps[0], overlaps[^1]); _buffers.RemoveRange(overlaps[0], overlaps[^1]);
_buffers.Lock.ExitWriteLock(); _buffers.Add(CreateBufferAligned(address, newSize, stage, sparseAligned, overlapsArray));
newBuffer = CreateBufferAligned(address, newSize, stage, sparseAligned, overlapsArray);
}
else
{
_buffers.Lock.ExitWriteLock();
} }
} }
else else
{ {
_buffers.Lock.ExitWriteLock();
// No overlap, just create a new buffer. // No overlap, just create a new buffer.
newBuffer = new(_context, _physicalMemory, address, size, stage, sparseAligned, []); _buffers.Add(new(_context, _physicalMemory, address, size, stage, sparseAligned, []));
} }
if (newBuffer is not null)
{
_buffers.Lock.EnterWriteLock();
_buffers.Add(newBuffer);
_buffers.Lock.ExitWriteLock();
}
} }
/// <summary> /// <summary>
@ -663,13 +621,13 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="stage">The type of usage that created the buffer</param> /// <param name="stage">The type of usage that created the buffer</param>
/// <param name="sparseCompatible">Indicates if the buffer can be used in a sparse buffer mapping</param> /// <param name="sparseCompatible">Indicates if the buffer can be used in a sparse buffer mapping</param>
/// <param name="overlaps">Buffers overlapping the range</param> /// <param name="overlaps">Buffers overlapping the range</param>
private Buffer CreateBufferAligned(ulong address, ulong size, BufferStage stage, bool sparseCompatible, RangeItem<Buffer>[] overlaps) private Buffer CreateBufferAligned(ulong address, ulong size, BufferStage stage, bool sparseCompatible, Buffer[] overlaps)
{ {
Buffer newBuffer = new(_context, _physicalMemory, address, size, stage, sparseCompatible, overlaps); Buffer newBuffer = new(_context, _physicalMemory, address, size, stage, sparseCompatible, overlaps);
for (int index = 0; index < overlaps.Length; index++) for (int index = 0; index < overlaps.Length; index++)
{ {
Buffer buffer = overlaps[index].Value; Buffer buffer = overlaps[index];
int dstOffset = (int)(buffer.Address - newBuffer.Address); int dstOffset = (int)(buffer.Address - newBuffer.Address);
@ -897,7 +855,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
{ {
MemoryRange subRange = range.GetSubRange(i); 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); subBuffer.SynchronizeMemory(subRange.Address, subRange.Size);
@ -945,7 +903,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
if (size != 0) if (size != 0)
{ {
buffer = _buffers.FindOverlap(address, size).Value; buffer = _buffers.FindOverlap(address, size);
buffer.CopyFromDependantVirtualBuffers(); buffer.CopyFromDependantVirtualBuffers();
buffer.SynchronizeMemory(address, size); buffer.SynchronizeMemory(address, size);
@ -957,7 +915,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
} }
else else
{ {
buffer = _buffers.FindOverlapFast(address, 1).Value; buffer = _buffers.FindOverlapFast(address, 1);
} }
return buffer; return buffer;
@ -995,7 +953,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
{ {
if (size != 0) if (size != 0)
{ {
Buffer buffer = _buffers.FindOverlap(address, size).Value; Buffer buffer = _buffers.FindOverlap(address, size);
if (copyBackVirtual) if (copyBackVirtual)
{ {

View file

@ -8,7 +8,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <summary> /// <summary>
/// A range within a buffer that has been modified by the GPU. /// A range within a buffer that has been modified by the GPU.
/// </summary> /// </summary>
class BufferModifiedRange : INonOverlappingRange class BufferModifiedRange : INonOverlappingRange<BufferModifiedRange>
{ {
/// <summary> /// <summary>
/// Start address of the range in guest memory. /// 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. /// End address of the range in guest memory.
/// </summary> /// </summary>
public ulong EndAddress => Address + Size; public ulong EndAddress => Address + Size;
public BufferModifiedRange Next { get; set; }
public BufferModifiedRange Previous { get; set; }
/// <summary> /// <summary>
/// The GPU sync number at the time of the last modification. /// 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. /// Checks if a given range overlaps with the modified range.
/// </summary> /// </summary>
/// <param name="address">Start address of the range</param> /// <param name="address">Start address of the range</param>
/// <param name="size">Size in bytes of the range</param> /// <param name="endAddress">End address of the range</param>
/// <returns>True if the range overlaps, false otherwise</returns> /// <returns>True if the range overlaps, false otherwise</returns>
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<BufferModifiedRange> Split(ulong splitAddress)
{ {
throw new NotImplementedException(); 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. // Slices a given region using the modified regions in the list. Calls the action for the new slices.
Lock.EnterReadLock(); Lock.EnterReadLock();
Span<RangeItem<BufferModifiedRange>> overlaps = FindOverlapsAsSpan(address, size); ReadOnlySpan<BufferModifiedRange> overlaps = FindOverlapsAsSpan(address, size);
for (int i = 0; i < overlaps.Length; i++) for (int i = 0; i < overlaps.Length; i++)
{ {
BufferModifiedRange overlap = overlaps[i].Value; BufferModifiedRange overlap = overlaps[i];
if (overlap.Address > address) if (overlap.Address > address)
{ {
@ -157,7 +160,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
ulong syncNumber = _context.SyncNumber; ulong syncNumber = _context.SyncNumber;
// We may overlap with some existing modified regions. They must be cut into by the new entry. // We may overlap with some existing modified regions. They must be cut into by the new entry.
Lock.EnterWriteLock(); Lock.EnterWriteLock();
(RangeItem<BufferModifiedRange> first, RangeItem<BufferModifiedRange> last) = FindOverlapsAsNodes(address, size); (BufferModifiedRange first, BufferModifiedRange last) = FindOverlapsAsNodes(address, size);
if (first is null) if (first is null)
{ {
@ -170,34 +173,39 @@ namespace Ryujinx.Graphics.Gpu.Memory
{ {
if (first.Address == address && first.EndAddress == endAddress) if (first.Address == address && first.EndAddress == endAddress)
{ {
first.Value.SyncNumber = syncNumber; first.SyncNumber = syncNumber;
first.Value.Parent = this; first.Parent = this;
Lock.ExitWriteLock(); Lock.ExitWriteLock();
return; return;
} }
if (first.Address < address) if (first.Address < address)
{ {
first.Value.Size = address - first.Address;
Update(first);
if (first.EndAddress > endAddress) if (first.EndAddress > endAddress)
{ {
Add(new BufferModifiedRange(endAddress, 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 else
{ {
if (first.EndAddress > endAddress) if (first.EndAddress > endAddress)
{ {
first.Value.Size = first.EndAddress - endAddress; first.Size = first.EndAddress - endAddress;
first.Value.Address = endAddress; first.Address = endAddress;
Update(first);
} }
else 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; return;
} }
BufferModifiedRange buffPre = null;
BufferModifiedRange buffPost = null;
bool extendsPost = false;
bool extendsPre = false;
if (first.Address < address) if (first.Address < address)
{ {
buffPre = new BufferModifiedRange(first.Address, address - first.Address, first.Size = address - first.Address;
first.Value.SyncNumber, first.Value.Parent); first = first.Next;
extendsPre = true;
} }
if (last.EndAddress > endAddress) if (last.EndAddress > endAddress)
{ {
buffPost = new BufferModifiedRange(endAddress, last.EndAddress - endAddress, last.Size = last.EndAddress - endAddress;
last.Value.SyncNumber, last.Value.Parent); last.Address = endAddress;
extendsPost = true; last = last.Previous;
} }
RemoveRange(first, last); if (first.Address < last.Address)
if (extendsPre)
{ {
Add(buffPre); RemoveRange(first.Next, last);
first.Address = address;
first.Size = size;
first.SyncNumber = syncNumber;
first.Parent = this;
} }
else if (first.Address == last.Address)
if (extendsPost)
{ {
Add(buffPost); first.Address = address;
first.Size = size;
first.SyncNumber = syncNumber;
first.Parent = this;
} }
else
Add(new BufferModifiedRange(address, size, syncNumber, this)); {
Add(new BufferModifiedRange(address, size, syncNumber, this));
}
Lock.ExitWriteLock(); Lock.ExitWriteLock();
} }
@ -252,11 +261,11 @@ namespace Ryujinx.Graphics.Gpu.Memory
public void GetRangesAtSync(ulong address, ulong size, ulong syncNumber, Action<ulong, ulong> rangeAction) public void GetRangesAtSync(ulong address, ulong size, ulong syncNumber, Action<ulong, ulong> rangeAction)
{ {
Lock.EnterReadLock(); Lock.EnterReadLock();
Span<RangeItem<BufferModifiedRange>> overlaps = FindOverlapsAsSpan(address, size); ReadOnlySpan<BufferModifiedRange> overlaps = FindOverlapsAsSpan(address, size);
for (int i = 0; i < overlaps.Length; i++) for (int i = 0; i < overlaps.Length; i++)
{ {
BufferModifiedRange overlap = overlaps[i].Value; BufferModifiedRange overlap = overlaps[i];
if (overlap.SyncNumber == syncNumber) 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. // We use the non-span method here because keeping the lock will cause a deadlock.
Lock.EnterReadLock(); Lock.EnterReadLock();
RangeItem<BufferModifiedRange>[] overlaps = FindOverlapsAsArray(address, size, out int length); BufferModifiedRange[] overlaps = FindOverlapsAsArray(address, size, out int length);
Lock.ExitReadLock(); Lock.ExitReadLock();
if (length != 0) if (length != 0)
{ {
for (int i = 0; i < length; i++) for (int i = 0; i < length; i++)
{ {
BufferModifiedRange overlap = overlaps[i].Value; BufferModifiedRange overlap = overlaps[i];
rangeAction(overlap.Address, overlap.Size); rangeAction(overlap.Address, overlap.Size);
} }
ArrayPool<RangeItem<BufferModifiedRange>>.Shared.Return(overlaps); ArrayPool<BufferModifiedRange>.Shared.Return(overlaps);
} }
} }
@ -301,7 +310,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
public bool HasRange(ulong address, ulong size) public bool HasRange(ulong address, ulong size)
{ {
Lock.EnterReadLock(); Lock.EnterReadLock();
RangeItem<BufferModifiedRange> first = FindOverlapFast(address, size); BufferModifiedRange first = FindOverlapFast(address, size);
bool result = first is not null; bool result = first is not null;
Lock.ExitReadLock(); Lock.ExitReadLock();
return result; return result;
@ -336,7 +345,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="address">The start address of the flush range</param> /// <param name="address">The start address of the flush range</param>
/// <param name="endAddress">The end address of the flush range</param> /// <param name="endAddress">The end address of the flush range</param>
private void RemoveRangesAndFlush( private void RemoveRangesAndFlush(
RangeItem<BufferModifiedRange>[] overlaps, BufferModifiedRange[] overlaps,
int rangeCount, int rangeCount,
long highestDiff, long highestDiff,
ulong currentSync, ulong currentSync,
@ -349,7 +358,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
for (int i = 0; i < rangeCount; i++) for (int i = 0; i < rangeCount; i++)
{ {
BufferModifiedRange overlap = overlaps[i].Value; BufferModifiedRange overlap = overlaps[i];
long diff = (long)(overlap.SyncNumber - currentSync); long diff = (long)(overlap.SyncNumber - currentSync);
@ -358,7 +367,14 @@ namespace Ryujinx.Graphics.Gpu.Memory
ulong clampAddress = Math.Max(address, overlap.Address); ulong clampAddress = Math.Max(address, overlap.Address);
ulong clampEnd = Math.Min(endAddress, overlap.EndAddress); 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); RangeActionWithMigration(clampAddress, clampEnd - clampAddress, waitSync, _flushAction);
} }
@ -398,7 +414,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
Lock.EnterWriteLock(); Lock.EnterWriteLock();
// We use the non-span method here because the array is partially modified by the code, which would invalidate a span. // We use the non-span method here because the array is partially modified by the code, which would invalidate a span.
RangeItem<BufferModifiedRange>[] overlaps = FindOverlapsAsArray(address, size, out int rangeCount); BufferModifiedRange[] overlaps = FindOverlapsAsArray(address, size, out int rangeCount);
if (rangeCount == 0) if (rangeCount == 0)
{ {
@ -414,7 +430,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
for (int i = 0; i < rangeCount; i++) for (int i = 0; i < rangeCount; i++)
{ {
BufferModifiedRange overlap = overlaps![i].Value; BufferModifiedRange overlap = overlaps![i];
long diff = (long)(overlap.SyncNumber - currentSync); long diff = (long)(overlap.SyncNumber - currentSync);
@ -436,7 +452,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
RemoveRangesAndFlush(overlaps, rangeCount, highestDiff, currentSync, address, endAddress); RemoveRangesAndFlush(overlaps, rangeCount, highestDiff, currentSync, address, endAddress);
ArrayPool<RangeItem<BufferModifiedRange>>.Shared.Return(overlaps!); ArrayPool<BufferModifiedRange>.Shared.Return(overlaps!);
Lock.ExitWriteLock(); Lock.ExitWriteLock();
} }
@ -452,7 +468,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
public void InheritRanges(BufferModifiedRangeList ranges, Action<ulong, ulong> registerRangeAction) public void InheritRanges(BufferModifiedRangeList ranges, Action<ulong, ulong> registerRangeAction)
{ {
ranges.Lock.EnterReadLock(); ranges.Lock.EnterReadLock();
BufferModifiedRange[] inheritRanges = ranges.ToArray(); int rangesCount = ranges.Count;
BufferModifiedRange[] inheritRanges = ArrayPool<BufferModifiedRange>.Shared.Rent(ranges.Count);
ranges.Items.AsSpan(0, ranges.Count).CopyTo(inheritRanges);
ranges.Lock.ExitReadLock(); ranges.Lock.ExitReadLock();
// Copy over the migration from the previous range list // Copy over the migration from the previous range list
@ -478,22 +496,26 @@ namespace Ryujinx.Graphics.Gpu.Memory
ranges._migrationTarget = this; ranges._migrationTarget = this;
Lock.EnterWriteLock(); Lock.EnterWriteLock();
foreach (BufferModifiedRange range in inheritRanges) for (int i = 0; i < rangesCount; i++)
{ {
BufferModifiedRange range = inheritRanges[i];
Add(range); Add(range);
} }
Lock.ExitWriteLock(); Lock.ExitWriteLock();
ulong currentSync = _context.SyncNumber; ulong currentSync = _context.SyncNumber;
foreach (BufferModifiedRange range in inheritRanges) for (int i = 0; i < rangesCount; i++)
{ {
BufferModifiedRange range = inheritRanges[i];
if (range.SyncNumber != currentSync) if (range.SyncNumber != currentSync)
{ {
registerRangeAction(range.Address, range.Size); registerRangeAction(range.Address, range.Size);
} }
} }
ArrayPool<BufferModifiedRange>.Shared.Return(inheritRanges);
} }
/// <summary> /// <summary>
@ -534,18 +556,25 @@ namespace Ryujinx.Graphics.Gpu.Memory
private void ClearPart(BufferModifiedRange overlap, ulong address, ulong endAddress) 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 the overlap extends outside of the clear range, make sure those parts still exist.
if (overlap.Address < address) 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;
} }
else if (overlap.EndAddress > endAddress)
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; ulong endAddress = address + size;
Lock.EnterWriteLock(); Lock.EnterWriteLock();
(RangeItem<BufferModifiedRange> first, RangeItem<BufferModifiedRange> last) = FindOverlapsAsNodes(address, size); (BufferModifiedRange first, BufferModifiedRange last) = FindOverlapsAsNodes(address, size);
if (first is null) if (first is null)
{ {
@ -570,26 +599,24 @@ namespace Ryujinx.Graphics.Gpu.Memory
{ {
if (first.Address < address) if (first.Address < address)
{ {
first.Value.Size = address - first.Address; first.Size = address - first.Address;
Update(first);
if (first.EndAddress > endAddress) if (first.EndAddress > endAddress)
{ {
Add(new BufferModifiedRange(endAddress, first.EndAddress - endAddress, Add(new BufferModifiedRange(endAddress, first.EndAddress - endAddress,
first.Value.SyncNumber, first.Value.Parent)); first.SyncNumber, first.Parent));
} }
} }
else else
{ {
if (first.EndAddress > endAddress) if (first.EndAddress > endAddress)
{ {
first.Value.Size = first.EndAddress - endAddress; first.Size = first.EndAddress - endAddress;
first.Value.Address = endAddress; first.Address = endAddress;
Update(first);
} }
else else
{ {
Remove(first.Value); Remove(first);
} }
} }
@ -605,14 +632,14 @@ namespace Ryujinx.Graphics.Gpu.Memory
if (first.Address < address) if (first.Address < address)
{ {
buffPre = new BufferModifiedRange(first.Address, address - first.Address, buffPre = new BufferModifiedRange(first.Address, address - first.Address,
first.Value.SyncNumber, first.Value.Parent); first.SyncNumber, first.Parent);
extendsPre = true; extendsPre = true;
} }
if (last.EndAddress > endAddress) if (last.EndAddress > endAddress)
{ {
buffPost = new BufferModifiedRange(endAddress, last.EndAddress - endAddress, buffPost = new BufferModifiedRange(endAddress, last.EndAddress - endAddress,
last.Value.SyncNumber, last.Value.Parent); last.SyncNumber, last.Parent);
extendsPost = true; extendsPost = true;
} }

View file

@ -15,7 +15,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <summary> /// <summary>
/// Represents a GPU virtual memory range. /// Represents a GPU virtual memory range.
/// </summary> /// </summary>
private class VirtualRange : INonOverlappingRange private class VirtualRange : INonOverlappingRange<VirtualRange>
{ {
/// <summary> /// <summary>
/// GPU virtual address where the range starts. /// GPU virtual address where the range starts.
@ -32,6 +32,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// </summary> /// </summary>
public ulong EndAddress => Address + Size; public ulong EndAddress => Address + Size;
public VirtualRange Next { get; set; }
public VirtualRange Previous { get; set; }
/// <summary> /// <summary>
/// Physical regions where the GPU virtual region is mapped. /// Physical regions where the GPU virtual region is mapped.
/// </summary> /// </summary>
@ -54,14 +57,14 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// Checks if a given range overlaps with the buffer. /// Checks if a given range overlaps with the buffer.
/// </summary> /// </summary>
/// <param name="address">Start address of the range</param> /// <param name="address">Start address of the range</param>
/// <param name="size">Size in bytes of the range</param> /// <param name="endAddress">End address of the range</param>
/// <returns>True if the range overlaps, false otherwise</returns> /// <returns>True if the range overlaps, false otherwise</returns>
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<VirtualRange> Split(ulong splitAddress)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }
@ -122,7 +125,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
ulong originalVa = gpuVa; ulong originalVa = gpuVa;
_virtualRanges.Lock.EnterWriteLock(); _virtualRanges.Lock.EnterWriteLock();
(RangeItem<VirtualRange> first, RangeItem<VirtualRange> last) = _virtualRanges.FindOverlapsAsNodes(gpuVa, size); (VirtualRange first, VirtualRange last) = _virtualRanges.FindOverlapsAsNodes(gpuVa, size);
if (first is not null) if (first is not null)
{ {
@ -147,8 +150,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
} }
else else
{ {
found = first.Value.Range.Count == 1 || IsSparseAligned(first.Value.Range); found = first.Range.Count == 1 || IsSparseAligned(first.Range);
range = first.Value.Range.Slice(gpuVa - first.Address, size); range = first.Range.Slice(gpuVa - first.Address, size);
} }
} }
else else

View file

@ -1166,7 +1166,7 @@ namespace Ryujinx.Graphics.OpenGL
} }
} }
public void SetRenderTargets(ITexture[] colors, ITexture depthStencil) public void SetRenderTargets(Span<ITexture> colors, ITexture depthStencil)
{ {
EnsureFramebuffer(); EnsureFramebuffer();

View file

@ -71,17 +71,31 @@ namespace Ryujinx.Graphics.Vulkan
HasDepthStencil = isDepthStencil; HasDepthStencil = isDepthStencil;
} }
public FramebufferParams(Device device, ITexture[] colors, ITexture depthStencil) public FramebufferParams(Device device, ReadOnlySpan<ITexture> colors, ITexture depthStencil)
{ {
_device = device; _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); int count = colorsCount + (IsValidTextureView(depthStencil) ? 1 : 0);
_attachments = new Auto<DisposableImageView>[count]; _attachments = new Auto<DisposableImageView>[count];
_colors = new TextureView[colorsCount]; _colors = new TextureView[colorsCount];
_colorsCanonical = colors.Select(color => color is TextureView view && view.Valid ? view : null).ToArray();
AttachmentSamples = new uint[count]; AttachmentSamples = new uint[count];
AttachmentFormats = new VkFormat[count]; AttachmentFormats = new VkFormat[count];
@ -165,9 +179,17 @@ namespace Ryujinx.Graphics.Vulkan
_totalCount = colors.Length; _totalCount = colors.Length;
} }
public FramebufferParams Update(ITexture[] colors, ITexture depthStencil) public FramebufferParams Update(ReadOnlySpan<ITexture> 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); int count = colorsCount + (IsValidTextureView(depthStencil) ? 1 : 0);

View file

@ -1,7 +1,7 @@
using Ryujinx.Common;
using Ryujinx.Common.Memory; using Ryujinx.Common.Memory;
using Silk.NET.Vulkan; using Silk.NET.Vulkan;
using System; using System;
using System.Buffers;
namespace Ryujinx.Graphics.Vulkan namespace Ryujinx.Graphics.Vulkan
{ {
@ -10,6 +10,8 @@ namespace Ryujinx.Graphics.Vulkan
/// </summary> /// </summary>
class MultiFenceHolder class MultiFenceHolder
{ {
public static readonly ObjectPool<FenceHolder[]> FencePool = new(() => new FenceHolder[CommandBufferPool.MaxCommandBuffers]);
private const int BufferUsageTrackingGranularity = 4096; private const int BufferUsageTrackingGranularity = 4096;
public FenceHolder[] Fences { get; } public FenceHolder[] Fences { get; }
@ -20,7 +22,7 @@ namespace Ryujinx.Graphics.Vulkan
/// </summary> /// </summary>
public MultiFenceHolder() public MultiFenceHolder()
{ {
Fences = ArrayPool<FenceHolder>.Shared.Rent(CommandBufferPool.MaxCommandBuffers); Fences = FencePool.Allocate();
} }
/// <summary> /// <summary>
@ -29,7 +31,7 @@ namespace Ryujinx.Graphics.Vulkan
/// <param name="size">Size of the buffer</param> /// <param name="size">Size of the buffer</param>
public MultiFenceHolder(int size) public MultiFenceHolder(int size)
{ {
Fences = ArrayPool<FenceHolder>.Shared.Rent(CommandBufferPool.MaxCommandBuffers); Fences = FencePool.Allocate();
_bufferUsageBitmap = new BufferUsageBitmap(size, BufferUsageTrackingGranularity); _bufferUsageBitmap = new BufferUsageBitmap(size, BufferUsageTrackingGranularity);
} }

View file

@ -1035,7 +1035,7 @@ namespace Ryujinx.Graphics.Vulkan
} }
} }
private void SetRenderTargetsInternal(ITexture[] colors, ITexture depthStencil, bool filterWriteMasked) private void SetRenderTargetsInternal(Span<ITexture> colors, ITexture depthStencil, bool filterWriteMasked)
{ {
CreateFramebuffer(colors, depthStencil, filterWriteMasked); CreateFramebuffer(colors, depthStencil, filterWriteMasked);
CreateRenderPass(); CreateRenderPass();
@ -1043,7 +1043,7 @@ namespace Ryujinx.Graphics.Vulkan
SignalAttachmentChange(); SignalAttachmentChange();
} }
public void SetRenderTargets(ITexture[] colors, ITexture depthStencil) public void SetRenderTargets(Span<ITexture> colors, ITexture depthStencil)
{ {
_framebufferUsingColorWriteMask = false; _framebufferUsingColorWriteMask = false;
SetRenderTargetsInternal(colors, depthStencil, Gd.IsTBDR); SetRenderTargetsInternal(colors, depthStencil, Gd.IsTBDR);
@ -1389,7 +1389,7 @@ namespace Ryujinx.Graphics.Vulkan
_currentPipelineHandle = 0; _currentPipelineHandle = 0;
} }
private void CreateFramebuffer(ITexture[] colors, ITexture depthStencil, bool filterWriteMasked) private void CreateFramebuffer(Span<ITexture> colors, ITexture depthStencil, bool filterWriteMasked)
{ {
if (filterWriteMasked) if (filterWriteMasked)
{ {
@ -1399,7 +1399,7 @@ namespace Ryujinx.Graphics.Vulkan
// Just try to remove duplicate attachments. // Just try to remove duplicate attachments.
// Save a copy of the array to rebind when mask changes. // Save a copy of the array to rebind when mask changes.
void MaskOut() void MaskOut(ReadOnlySpan<ITexture> colors)
{ {
if (!_framebufferUsingColorWriteMask) if (!_framebufferUsingColorWriteMask)
{ {
@ -1436,12 +1436,12 @@ namespace Ryujinx.Graphics.Vulkan
if (vkBlend.ColorWriteMask == 0) if (vkBlend.ColorWriteMask == 0)
{ {
colors[i] = null; colors[i] = null;
MaskOut(); MaskOut(colors);
} }
else if (vkBlend2.ColorWriteMask == 0) else if (vkBlend2.ColorWriteMask == 0)
{ {
colors[j] = null; colors[j] = null;
MaskOut(); MaskOut(colors);
} }
} }
} }

View file

@ -1,6 +1,6 @@
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Silk.NET.Vulkan; using Silk.NET.Vulkan;
using System.Buffers; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
@ -193,7 +193,8 @@ namespace Ryujinx.Graphics.Vulkan
{ {
_firstHandle = first.ID + 1; _firstHandle = first.ID + 1;
_handles.RemoveAt(0); _handles.RemoveAt(0);
ArrayPool<FenceHolder>.Shared.Return(first.Waitable.Fences); Array.Clear(first.Waitable.Fences);
MultiFenceHolder.FencePool.Release(first.Waitable.Fences);
first.Waitable = null; first.Waitable = null;
} }
} }

View file

@ -2,6 +2,7 @@ using Microsoft.IO;
using Ryujinx.Common; using Ryujinx.Common;
using Ryujinx.Common.Memory; using Ryujinx.Common.Memory;
using System; using System;
using System.Buffers;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
@ -37,7 +38,7 @@ namespace Ryujinx.HLE.HOS.Ipc
public IpcMessage(ReadOnlySpan<byte> data, long cmdPtr) public IpcMessage(ReadOnlySpan<byte> data, long cmdPtr)
{ {
using RecyclableMemoryStream ms = MemoryStreamManager.Shared.GetStream(data); RecyclableMemoryStream ms = MemoryStreamManager.Shared.GetStream(data);
BinaryReader reader = new(ms); BinaryReader reader = new(ms);
@ -123,6 +124,8 @@ namespace Ryujinx.HLE.HOS.Ipc
} }
ObjectIds = []; ObjectIds = [];
MemoryStreamManager.Shared.ReleaseStream(ms);
} }
public RecyclableMemoryStream GetStream(long cmdPtr, ulong recvListAddr) public RecyclableMemoryStream GetStream(long cmdPtr, ulong recvListAddr)

View file

@ -20,6 +20,15 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
_exchangeBufferDescriptors = new List<KBufferDescriptor>(MaxInternalBuffersCount); _exchangeBufferDescriptors = new List<KBufferDescriptor>(MaxInternalBuffersCount);
} }
public KBufferDescriptorTable Clear()
{
_sendBufferDescriptors.Clear();
_receiveBufferDescriptors.Clear();
_exchangeBufferDescriptors.Clear();
return this;
}
public Result AddSendBuffer(ulong src, ulong dst, ulong size, MemoryState state) public Result AddSendBuffer(ulong src, ulong dst, ulong size, MemoryState state)
{ {
return Add(_sendBufferDescriptors, src, dst, size, state); return Add(_sendBufferDescriptors, src, dst, size, state);

View file

@ -1,3 +1,4 @@
using Ryujinx.Common;
using Ryujinx.HLE.HOS.Kernel.Common; using Ryujinx.HLE.HOS.Kernel.Common;
using Ryujinx.HLE.HOS.Kernel.Process; using Ryujinx.HLE.HOS.Kernel.Process;
using Ryujinx.HLE.HOS.Kernel.Threading; using Ryujinx.HLE.HOS.Kernel.Threading;
@ -32,7 +33,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
{ {
KThread currentThread = KernelStatic.GetCurrentThread(); KThread currentThread = KernelStatic.GetCurrentThread();
KSessionRequest request = new(currentThread, customCmdBuffAddr, customCmdBuffSize); KSessionRequest request = _parent.ServerSession.RequestPool.Allocate().Set(currentThread, customCmdBuffAddr, customCmdBuffSize);
KernelContext.CriticalSection.Enter(); KernelContext.CriticalSection.Enter();
@ -55,7 +56,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
{ {
KThread currentThread = KernelStatic.GetCurrentThread(); 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(); KernelContext.CriticalSection.Enter();

View file

@ -10,6 +10,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
{ {
class KServerSession : KSynchronizationObject class KServerSession : KSynchronizationObject
{ {
public readonly ObjectPool<KSessionRequest> RequestPool = new(() => new KSessionRequest());
private static readonly MemoryState[] _ipcMemoryStates = private static readonly MemoryState[] _ipcMemoryStates =
[ [
MemoryState.IpcBuffer3, MemoryState.IpcBuffer3,
@ -274,6 +276,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
KernelContext.CriticalSection.Leave(); KernelContext.CriticalSection.Leave();
WakeClientThread(request, clientResult); WakeClientThread(request, clientResult);
RequestPool.Release(request);
} }
if (clientHeader.ReceiveListType < 2 && if (clientHeader.ReceiveListType < 2 &&
@ -627,6 +631,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
CloseAllHandles(clientMsg, serverHeader, clientProcess); CloseAllHandles(clientMsg, serverHeader, clientProcess);
FinishRequest(request, clientResult); FinishRequest(request, clientResult);
RequestPool.Release(request);
} }
if (clientHeader.ReceiveListType < 2 && if (clientHeader.ReceiveListType < 2 &&
@ -865,6 +871,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
// Unmap buffers from server. // Unmap buffers from server.
FinishRequest(request, clientResult); FinishRequest(request, clientResult);
RequestPool.Release(request);
return serverResult; return serverResult;
} }
@ -1098,6 +1106,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
foreach (KSessionRequest request in IterateWithRemovalOfAllRequests()) foreach (KSessionRequest request in IterateWithRemovalOfAllRequests())
{ {
FinishRequest(request, KernelResult.PortRemoteClosed); FinishRequest(request, KernelResult.PortRemoteClosed);
RequestPool.Release(request);
} }
} }
@ -1117,6 +1127,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
{ {
SendResultToAsyncRequestClient(request, KernelResult.PortRemoteClosed); SendResultToAsyncRequestClient(request, KernelResult.PortRemoteClosed);
} }
RequestPool.Release(request);
} }
WakeServerThreads(KernelResult.PortRemoteClosed); WakeServerThreads(KernelResult.PortRemoteClosed);

View file

@ -5,18 +5,18 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
{ {
class KSessionRequest 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 KProcess ServerProcess { get; set; }
public KWritableEvent AsyncEvent { get; } public KWritableEvent AsyncEvent { get; private set; }
public ulong CustomCmdBuffAddr { get; } public ulong CustomCmdBuffAddr { get; private set; }
public ulong CustomCmdBuffSize { get; } public ulong CustomCmdBuffSize { get; private set; }
public KSessionRequest( public KSessionRequest Set(
KThread clientThread, KThread clientThread,
ulong customCmdBuffAddr, ulong customCmdBuffAddr,
ulong customCmdBuffSize, ulong customCmdBuffSize,
@ -27,7 +27,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
CustomCmdBuffSize = customCmdBuffSize; CustomCmdBuffSize = customCmdBuffSize;
AsyncEvent = asyncEvent; AsyncEvent = asyncEvent;
BufferDescriptorTable = new KBufferDescriptorTable(); BufferDescriptorTable = BufferDescriptorTable?.Clear() ?? new KBufferDescriptorTable();
return this;
} }
} }
} }

View file

@ -1,10 +1,8 @@
using Ryujinx.Common;
using Ryujinx.HLE.HOS.Kernel.Common; using Ryujinx.HLE.HOS.Kernel.Common;
using Ryujinx.HLE.HOS.Kernel.Process; using Ryujinx.HLE.HOS.Kernel.Process;
using Ryujinx.Horizon.Common; using Ryujinx.Horizon.Common;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Threading; using System.Threading;
namespace Ryujinx.HLE.HOS.Kernel.Threading namespace Ryujinx.HLE.HOS.Kernel.Threading
@ -12,12 +10,12 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
class KAddressArbiter class KAddressArbiter
{ {
private const int HasListenersMask = 0x40000000; private const int HasListenersMask = 0x40000000;
private static readonly ObjectPool<KThread[]> _threadArrayPool = new(() => []);
private readonly KernelContext _context; private readonly KernelContext _context;
private readonly List<KThread> _condVarThreads; private readonly Dictionary<ulong, List<KThread>> _condVarThreads;
private readonly List<KThread> _arbiterThreads; private readonly Dictionary<ulong, List<KThread>> _arbiterThreads;
private readonly ByDynamicPriority _byDynamicPriority;
public KAddressArbiter(KernelContext context) public KAddressArbiter(KernelContext context)
{ {
@ -25,6 +23,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
_condVarThreads = []; _condVarThreads = [];
_arbiterThreads = []; _arbiterThreads = [];
_byDynamicPriority = new ByDynamicPriority();
} }
public Result ArbitrateLock(int ownerHandle, ulong mutexAddress, int requesterHandle) public Result ArbitrateLock(int ownerHandle, ulong mutexAddress, int requesterHandle)
@ -140,9 +139,23 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
currentThread.MutexAddress = mutexAddress; currentThread.MutexAddress = mutexAddress;
currentThread.ThreadHandleForUserMutex = threadHandle; currentThread.ThreadHandleForUserMutex = threadHandle;
currentThread.CondVarAddress = condVarAddress;
_condVarThreads.Add(currentThread); if (_condVarThreads.TryGetValue(condVarAddress, out List<KThread> 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) if (timeout != 0)
{ {
@ -165,7 +178,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
currentThread.MutexOwner?.RemoveMutexWaiter(currentThread); currentThread.MutexOwner?.RemoveMutexWaiter(currentThread);
_condVarThreads.Remove(currentThread); _condVarThreads[condVarAddress].Remove(currentThread);
_context.CriticalSection.Leave(); _context.CriticalSection.Leave();
@ -200,13 +213,14 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
{ {
_context.CriticalSection.Enter(); _context.CriticalSection.Enter();
static bool SignalProcessWideKeyPredicate(KThread thread, ulong address) int validThreads = 0;
_condVarThreads.TryGetValue(address, out List<KThread> 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) if (validThreads == 0)
{ {
KernelTransfer.KernelToUser(address, 0); KernelTransfer.KernelToUser(address, 0);
@ -315,9 +329,24 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
currentThread.MutexAddress = address; currentThread.MutexAddress = address;
currentThread.WaitingInArbitration = true; currentThread.WaitingInArbitration = true;
if (_arbiterThreads.TryGetValue(address, out List<KThread> 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); currentThread.Reschedule(ThreadSchedState.Paused);
if (timeout > 0) if (timeout > 0)
@ -336,7 +365,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
if (currentThread.WaitingInArbitration) if (currentThread.WaitingInArbitration)
{ {
_arbiterThreads.Remove(currentThread); _arbiterThreads[address].Remove(currentThread);
currentThread.WaitingInArbitration = false; currentThread.WaitingInArbitration = false;
} }
@ -392,9 +421,24 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
currentThread.MutexAddress = address; currentThread.MutexAddress = address;
currentThread.WaitingInArbitration = true; currentThread.WaitingInArbitration = true;
if (_arbiterThreads.TryGetValue(address, out List<KThread> 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); currentThread.Reschedule(ThreadSchedState.Paused);
if (timeout > 0) if (timeout > 0)
@ -413,7 +457,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
if (currentThread.WaitingInArbitration) if (currentThread.WaitingInArbitration)
{ {
_arbiterThreads.Remove(currentThread); _arbiterThreads[address].Remove(currentThread);
currentThread.WaitingInArbitration = false; 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 equal to the Count of threads to be signaled, or Count is zero
// or negative. It is incremented if there are no threads waiting. // or negative. It is incremented if there are no threads waiting.
int waitingCount = 0; int waitingCount = 0;
foreach (KThread thread in _arbiterThreads) if (_arbiterThreads.TryGetValue(address, out List<KThread> threads))
{ {
if (thread.MutexAddress == address && waitingCount = threads.Count;
++waitingCount >= count)
{
break;
}
} }
if (waitingCount > 0) if (waitingCount > 0)
{ {
@ -561,55 +602,38 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
thread.WaitingInArbitration = false; thread.WaitingInArbitration = false;
} }
static bool ArbiterThreadPredecate(KThread thread, ulong address) _arbiterThreads.TryGetValue(address, out List<KThread> threads);
{
return thread.MutexAddress == address;
}
WakeThreads(_arbiterThreads, count, RemoveArbiterThread, ArbiterThreadPredecate, address); if (threads is not null && threads.Count > 0)
{
WakeThreads(threads, count, RemoveArbiterThread);
}
} }
private static int WakeThreads( private static int WakeThreads(
List<KThread> threads, List<KThread> threads,
int count, int count,
Action<KThread> removeCallback, Action<KThread> removeCallback)
Func<KThread, ulong, bool> predicate,
ulong address = 0)
{ {
KThread[] candidates = _threadArrayPool.Allocate(); int validCount = count > 0 ? Math.Min(count, threads.Count) : threads.Count;
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<KThread> candidatesSpan = candidates.AsSpan(..validCount);
candidatesSpan.Sort((x, y) => (x.DynamicPriority.CompareTo(y.DynamicPriority)));
if (count > 0) for (int i = 0; i < validCount; i++)
{
candidatesSpan = candidatesSpan[..Math.Min(count, candidatesSpan.Length)];
}
foreach (KThread thread in candidatesSpan)
{ {
KThread thread = threads[i];
removeCallback(thread); removeCallback(thread);
threads.Remove(thread);
} }
_threadArrayPool.Release(candidates); threads.RemoveRange(0, validCount);
return validCount; return validCount;
} }
private class ByDynamicPriority : IComparer<KThread>
{
public int Compare(KThread x, KThread y)
{
return x!.DynamicPriority.CompareTo(y!.DynamicPriority);
}
}
} }
} }

View file

@ -61,8 +61,6 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
public KSynchronizationObject SignaledObj { get; set; } public KSynchronizationObject SignaledObj { get; set; }
public ulong CondVarAddress { get; set; }
private ulong _entrypoint; private ulong _entrypoint;
private ThreadStart _customThreadStart; private ThreadStart _customThreadStart;
private bool _forcedUnschedulable; private bool _forcedUnschedulable;

View file

@ -23,6 +23,9 @@ namespace Ryujinx.HLE.HOS.Services
private int _selfId; private int _selfId;
private bool _isDomain; 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) public IpcService(ServerBase server = null, bool registerTipc = false)
{ {
Stopwatch sw = Stopwatch.StartNew(); Stopwatch sw = Stopwatch.StartNew();
@ -146,7 +149,9 @@ namespace Ryujinx.HLE.HOS.Services
{ {
Logger.Trace?.Print(LogClass.KernelIpc, $"{service.GetType().Name}: {processRequest.Name}"); 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 else
{ {
@ -196,7 +201,9 @@ namespace Ryujinx.HLE.HOS.Services
{ {
Logger.Debug?.Print(LogClass.KernelIpc, $"{GetType().Name}: {processRequest.Name}"); 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 else
{ {

View file

@ -59,6 +59,10 @@ namespace Ryujinx.HLE.HOS.Services.Nv
// TODO: This should call set:sys::GetDebugModeFlag // TODO: This should call set:sys::GetDebugModeFlag
private readonly bool _debugModeEnabled = false; private readonly bool _debugModeEnabled = false;
private byte[] _ioctl2Buffer = [];
private byte[] _ioctlArgumentBuffer = [];
private byte[] _ioctl3Buffer = [];
public INvDrvServices(ServiceCtx context) : base(context.Device.System.NvDrvServer) 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)) 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); context.Memory.Read(inputDataPosition, arguments);
} }
else
{
arguments = arguments.ToArray();
}
} }
else if (isWrite) else if (isWrite)
{ {
byte[] outputData = new byte[outputDataSize]; if (_ioctlArgumentBuffer.Length < (int)outputDataSize)
{
arguments = new Span<byte>(outputData); Array.Resize(ref _ioctlArgumentBuffer, (int)outputDataSize);
}
arguments = _ioctlArgumentBuffer.AsSpan(0, (int)outputDataSize);
} }
else 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); context.Memory.Read(inputDataPosition, arguments);
}
arguments = new Span<byte>(temp);
} }
return NvResult.Success; return NvResult.Success;
@ -270,7 +285,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv
if ((ioctlCommand.DirectionValue & NvIoctl.Direction.Write) != 0) 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<byte> arguments); errorCode = GetIoctlArgument(context, ioctlCommand, out Span<byte> arguments);
byte[] inlineInBuffer = null;
if (!context.Memory.TryReadUnsafe(inlineInBufferPosition, (int)inlineInBufferSize, out Span<byte> inlineInBufferSpan)) if (!context.Memory.TryReadUnsafe(inlineInBufferPosition, (int)inlineInBufferSize, out Span<byte> inlineInBufferSpan))
{ {
inlineInBuffer = _byteArrayPool.Rent((int)inlineInBufferSize); if (_ioctl2Buffer.Length < (int)inlineInBufferSize)
inlineInBufferSpan = inlineInBuffer; {
context.Memory.Read(inlineInBufferPosition, inlineInBufferSpan[..(int)inlineInBufferSize]); Array.Resize(ref _ioctl2Buffer, (int)inlineInBufferSize);
}
inlineInBufferSpan = _ioctl2Buffer.AsSpan(0, (int)inlineInBufferSize);
context.Memory.Read(inlineInBufferPosition, inlineInBufferSpan);
} }
if (errorCode == NvResult.Success) if (errorCode == NvResult.Success)
@ -489,7 +506,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv
if (errorCode == NvResult.Success) if (errorCode == NvResult.Success)
{ {
NvInternalResult internalResult = deviceFile.Ioctl2(ioctlCommand, arguments, inlineInBufferSpan[..(int)inlineInBufferSize]); NvInternalResult internalResult = deviceFile.Ioctl2(ioctlCommand, arguments, inlineInBufferSpan);
if (internalResult == NvInternalResult.NotImplemented) if (internalResult == NvInternalResult.NotImplemented)
{ {
@ -500,15 +517,10 @@ namespace Ryujinx.HLE.HOS.Services.Nv
if ((ioctlCommand.DirectionValue & NvIoctl.Direction.Write) != 0) 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); context.ResponseData.Write((uint)errorCode);
@ -531,13 +543,15 @@ namespace Ryujinx.HLE.HOS.Services.Nv
errorCode = GetIoctlArgument(context, ioctlCommand, out Span<byte> arguments); errorCode = GetIoctlArgument(context, ioctlCommand, out Span<byte> arguments);
byte[] inlineOutBuffer = null;
if (!context.Memory.TryReadUnsafe(inlineOutBufferPosition, (int)inlineOutBufferSize, out Span<byte> inlineOutBufferSpan)) if (!context.Memory.TryReadUnsafe(inlineOutBufferPosition, (int)inlineOutBufferSize, out Span<byte> inlineOutBufferSpan))
{ {
inlineOutBuffer = _byteArrayPool.Rent((int)inlineOutBufferSize); if (_ioctl3Buffer.Length < (int)inlineOutBufferSize)
inlineOutBufferSpan = inlineOutBuffer; {
context.Memory.Read(inlineOutBufferPosition, inlineOutBufferSpan[..(int)inlineOutBufferSize]); Array.Resize(ref _ioctl3Buffer, (int)inlineOutBufferSize);
}
inlineOutBufferSpan = _ioctl3Buffer.AsSpan(0, (int)inlineOutBufferSize);
context.Memory.Read(inlineOutBufferPosition, inlineOutBufferSpan);
} }
if (errorCode == NvResult.Success) if (errorCode == NvResult.Success)
@ -546,7 +560,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv
if (errorCode == NvResult.Success) if (errorCode == NvResult.Success)
{ {
NvInternalResult internalResult = deviceFile.Ioctl3(ioctlCommand, arguments, inlineOutBufferSpan[..(int)inlineOutBufferSize]); NvInternalResult internalResult = deviceFile.Ioctl3(ioctlCommand, arguments, inlineOutBufferSpan);
if (internalResult == NvInternalResult.NotImplemented) if (internalResult == NvInternalResult.NotImplemented)
{ {
@ -557,16 +571,11 @@ namespace Ryujinx.HLE.HOS.Services.Nv
if ((ioctlCommand.DirectionValue & NvIoctl.Direction.Write) != 0) 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);
context.Memory.Write(inlineOutBufferPosition, inlineOutBufferSpan[..(int)inlineOutBufferSize].ToArray()); context.Memory.Write(inlineOutBufferPosition, inlineOutBufferSpan);
} }
} }
} }
if (inlineOutBuffer is not null)
{
_byteArrayPool.Return(inlineOutBuffer);
}
} }
context.ResponseData.Write((uint)errorCode); context.ResponseData.Write((uint)errorCode);

View file

@ -454,8 +454,9 @@ namespace Ryujinx.HLE.HOS.Services
response.RawData = _responseDataStream.ToArray(); response.RawData = _responseDataStream.ToArray();
using RecyclableMemoryStream responseStream = response.GetStreamTipc(); RecyclableMemoryStream responseStream = response.GetStreamTipc();
_selfProcess.CpuMemory.Write(_selfThread.TlsAddress, responseStream.GetReadOnlySequence()); _selfProcess.CpuMemory.Write(_selfThread.TlsAddress, responseStream.GetReadOnlySequence());
MemoryStreamManager.Shared.ReleaseStream(responseStream);
} }
else else
{ {
@ -464,8 +465,9 @@ namespace Ryujinx.HLE.HOS.Services
if (!isTipcCommunication) 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()); _selfProcess.CpuMemory.Write(_selfThread.TlsAddress, responseStream.GetReadOnlySequence());
MemoryStreamManager.Shared.ReleaseStream(responseStream);
} }
return shouldReply; return shouldReply;

View file

@ -19,6 +19,9 @@ namespace Ryujinx.Horizon.Sdk.OsTypes.Impl
private int _waitingThreadHandle; private int _waitingThreadHandle;
private MultiWaitHolderBase _signaledHolder; private MultiWaitHolderBase _signaledHolder;
ObjectPool<int[]> _objectHandlePool = new(() => new int[64]);
ObjectPool<MultiWaitHolderBase[]> _objectPool = new(() => new MultiWaitHolderBase[64]);
public long CurrentTime { get; private set; } public long CurrentTime { get; private set; }
@ -76,11 +79,15 @@ namespace Ryujinx.Horizon.Sdk.OsTypes.Impl
private MultiWaitHolderBase WaitAnyHandleImpl(bool infinite, long timeout) private MultiWaitHolderBase WaitAnyHandleImpl(bool infinite, long timeout)
{ {
Span<int> objectHandles = new int[64]; int[] objectHandles = _objectHandlePool.Allocate();
Span<int> objectHandlesSpan = objectHandles;
objectHandlesSpan.Clear();
Span<MultiWaitHolderBase> objects = new MultiWaitHolderBase[64]; MultiWaitHolderBase[] objects = _objectPool.Allocate();
Span<MultiWaitHolderBase> objectsSpan = objects;
objectsSpan.Clear();
int count = FillObjectsArray(objectHandles, objects); int count = FillObjectsArray(objectHandlesSpan, objectsSpan);
long endTime = infinite ? long.MaxValue : PerformanceCounter.ElapsedMilliseconds * 1000000; long endTime = infinite ? long.MaxValue : PerformanceCounter.ElapsedMilliseconds * 1000000;
@ -98,7 +105,7 @@ namespace Ryujinx.Horizon.Sdk.OsTypes.Impl
} }
else else
{ {
index = WaitSynchronization(objectHandles[..count], minTimeout); index = WaitSynchronization(objectHandlesSpan[..count], minTimeout);
DebugUtil.Assert(index != WaitInvalid); DebugUtil.Assert(index != WaitInvalid);
} }
@ -116,12 +123,18 @@ namespace Ryujinx.Horizon.Sdk.OsTypes.Impl
{ {
_signaledHolder = minTimeoutObject; _signaledHolder = minTimeoutObject;
_objectHandlePool.Release(objectHandles);
_objectPool.Release(objects);
return _signaledHolder; return _signaledHolder;
} }
} }
} }
else else
{ {
_objectHandlePool.Release(objectHandles);
_objectPool.Release(objects);
return null; return null;
} }
@ -131,6 +144,9 @@ namespace Ryujinx.Horizon.Sdk.OsTypes.Impl
{ {
if (_signaledHolder != null) if (_signaledHolder != null)
{ {
_objectHandlePool.Release(objectHandles);
_objectPool.Release(objects);
return _signaledHolder; return _signaledHolder;
} }
} }
@ -139,8 +155,11 @@ namespace Ryujinx.Horizon.Sdk.OsTypes.Impl
default: default:
lock (_lock) lock (_lock)
{ {
_signaledHolder = objects[index]; _signaledHolder = objectsSpan[index];
_objectHandlePool.Release(objectHandles);
_objectPool.Release(objects);
return _signaledHolder; return _signaledHolder;
} }
} }

View file

@ -532,8 +532,6 @@ namespace Ryujinx.Input.HLE
hidKeyboard.Modifier |= value << entry.Target; hidKeyboard.Modifier |= value << entry.Target;
} }
ArrayPool<bool>.Shared.Return(keyboardState.KeysState);
return hidKeyboard; return hidKeyboard;

View file

@ -20,7 +20,6 @@ namespace Ryujinx.Input.HLE
{ {
public class NpadManager : IDisposable public class NpadManager : IDisposable
{ {
private static readonly ObjectPool<List<SixAxisInput>> _hleMotionStatesPool = new (() => new List<SixAxisInput>(NpadDevices.MaxControllers));
private readonly CemuHookClient _cemuHookClient; private readonly CemuHookClient _cemuHookClient;
private readonly Lock _lock = new(); private readonly Lock _lock = new();
@ -40,6 +39,9 @@ namespace Ryujinx.Input.HLE
private bool _enableKeyboard; private bool _enableKeyboard;
private bool _enableMouse; private bool _enableMouse;
private Switch _device; private Switch _device;
private readonly List<GamepadInput> _hleInputStates = [];
private readonly List<SixAxisInput> _hleMotionStates = new(NpadDevices.MaxControllers);
public NpadManager(IGamepadDriver keyboardDriver, IGamepadDriver gamepadDriver, IGamepadDriver mouseDriver) public NpadManager(IGamepadDriver keyboardDriver, IGamepadDriver gamepadDriver, IGamepadDriver mouseDriver)
{ {
@ -217,8 +219,8 @@ namespace Ryujinx.Input.HLE
{ {
lock (_lock) lock (_lock)
{ {
List<GamepadInput> hleInputStates = []; _hleInputStates.Clear();
List<SixAxisInput> hleMotionStates = _hleMotionStatesPool.Allocate(); _hleMotionStates.Clear();
KeyboardInput? hleKeyboardInput = null; KeyboardInput? hleKeyboardInput = null;
@ -260,14 +262,14 @@ namespace Ryujinx.Input.HLE
inputState.PlayerId = playerIndex; inputState.PlayerId = playerIndex;
motionState.Item1.PlayerId = playerIndex; motionState.Item1.PlayerId = playerIndex;
hleInputStates.Add(inputState); _hleInputStates.Add(inputState);
hleMotionStates.Add(motionState.Item1); _hleMotionStates.Add(motionState.Item1);
if (isJoyconPair && !motionState.Item2.Equals(default)) if (isJoyconPair && !motionState.Item2.Equals(default))
{ {
motionState.Item2.PlayerId = playerIndex; motionState.Item2.PlayerId = playerIndex;
hleMotionStates.Add(motionState.Item2); _hleMotionStates.Add(motionState.Item2);
} }
} }
@ -276,8 +278,8 @@ namespace Ryujinx.Input.HLE
hleKeyboardInput = NpadController.GetHLEKeyboardInput(_keyboardDriver); hleKeyboardInput = NpadController.GetHLEKeyboardInput(_keyboardDriver);
} }
_device.Hid.Npads.Update(hleInputStates); _device.Hid.Npads.Update(_hleInputStates);
_device.Hid.Npads.UpdateSixAxis(hleMotionStates); _device.Hid.Npads.UpdateSixAxis(_hleMotionStates);
if (hleKeyboardInput.HasValue) if (hleKeyboardInput.HasValue)
{ {
@ -328,10 +330,7 @@ namespace Ryujinx.Input.HLE
_device.Hid.Mouse.Update(0, 0); _device.Hid.Mouse.Update(0, 0);
} }
_device.TamperMachine.UpdateInput(hleInputStates); _device.TamperMachine.UpdateInput(_hleInputStates);
hleMotionStates.Clear();
_hleMotionStatesPool.Release(hleMotionStates);
} }
} }

View file

@ -8,6 +8,8 @@ namespace Ryujinx.Input
/// </summary> /// </summary>
public interface IKeyboard : IGamepad public interface IKeyboard : IGamepad
{ {
private static bool[] _keyState;
/// <summary> /// <summary>
/// Check if a given key is pressed on the keyboard. /// Check if a given key is pressed on the keyboard.
/// </summary> /// </summary>
@ -29,15 +31,17 @@ namespace Ryujinx.Input
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
static KeyboardStateSnapshot GetStateSnapshot(IKeyboard keyboard) static KeyboardStateSnapshot GetStateSnapshot(IKeyboard keyboard)
{ {
if (_keyState is null)
{
_keyState = new bool[(int)Key.Count];
}
bool[] keysState = ArrayPool<bool>.Shared.Rent((int)Key.Count);
for (Key key = 0; key < Key.Count; key++) 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);
} }
} }
} }

View file

@ -3,7 +3,7 @@ namespace Ryujinx.Memory.Range
/// <summary> /// <summary>
/// Range of memory that can be split in two. /// Range of memory that can be split in two.
/// </summary> /// </summary>
public interface INonOverlappingRange : IRange public interface INonOverlappingRange<T> : IRangeListRange<T> where T : class, IRangeListRange<T>
{ {
/// <summary> /// <summary>
/// Split this region into two, around the specified address. /// Split this region into two, around the specified address.
@ -11,6 +11,6 @@ namespace Ryujinx.Memory.Range
/// </summary> /// </summary>
/// <param name="splitAddress">Address to split the region around</param> /// <param name="splitAddress">Address to split the region around</param>
/// <returns>The second part of the split region, with start address at the given split.</returns> /// <returns>The second part of the split region, with start address at the given split.</returns>
public INonOverlappingRange Split(ulong splitAddress); public INonOverlappingRange<T> Split(ulong splitAddress);
} }
} }

View file

@ -24,8 +24,8 @@ namespace Ryujinx.Memory.Range
/// Check if this range overlaps with another. /// Check if this range overlaps with another.
/// </summary> /// </summary>
/// <param name="address">Base address</param> /// <param name="address">Base address</param>
/// <param name="size">Size of the range</param> /// <param name="endAddress">EndAddress of the range</param>
/// <returns>True if overlapping, false otherwise</returns> /// <returns>True if overlapping, false otherwise</returns>
bool OverlapsWith(ulong address, ulong size); bool OverlapsWith(ulong address, ulong endAddress);
} }
} }

View file

@ -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. /// A range list that assumes ranges are non-overlapping, with list items that can be split in two to avoid overlaps.
/// </summary> /// </summary>
/// <typeparam name="T">Type of the range.</typeparam> /// <typeparam name="T">Type of the range.</typeparam>
public unsafe class NonOverlappingRangeList<T> : RangeListBase<T> where T : class, INonOverlappingRange public class NonOverlappingRangeList<T> : RangeListBase<T> where T : class, INonOverlappingRange<T>
{ {
public readonly ReaderWriterLockSlim Lock = new(); public readonly ReaderWriterLockSlim Lock = new();
@ -32,83 +32,18 @@ namespace Ryujinx.Memory.Range
/// <param name="item">The item to be added</param> /// <param name="item">The item to be added</param>
public override void Add(T item) public override void Add(T item)
{ {
Debug.Assert(item.Address != item.EndAddress);
int index = BinarySearch(item.Address); int index = BinarySearch(item.Address);
if (index < 0) if (index < 0)
{ {
index = ~index; index = ~index;
} }
RangeItem<T> rangeItem = _rangeItemPool.Allocate().Set(item);
Insert(index, rangeItem);
}
/// <summary>
/// Updates an item's end address on the list. Address must be the same.
/// </summary>
/// <param name="item">The item to be updated</param>
/// <returns>True if the item was located and updated, false otherwise</returns>
protected override bool Update(T item)
{
int index = BinarySearch(item.Address);
if (index >= 0 && Items[index].Value.Equals(item))
{
RangeItem<T> 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;
}
/// <summary>
/// Updates an item's end address on the list. Address must be the same.
/// </summary>
/// <param name="item">The RangeItem to be updated</param>
/// <returns>True if the item was located and updated, false otherwise</returns>
protected override bool Update(RangeItem<T> item)
{
int index = BinarySearch(item.Address);
RangeItem<T> 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<T> item)
{
Debug.Assert(item.Address != item.EndAddress);
if (Count + 1 > Items.Length) if (Count + 1 > Items.Length)
{ {
Array.Resize(ref Items, Items.Length + BackingGrowthSize); Array.Resize(ref Items, (int)(Items.Length * 1.5));
} }
if (index >= Count) if (index >= Count)
@ -145,8 +80,6 @@ namespace Ryujinx.Memory.Range
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private void RemoveAt(int index) private void RemoveAt(int index)
{ {
_rangeItemPool.Release(Items[index]);
if (index < Count - 1) if (index < Count - 1)
{ {
Items[index + 1].Previous = index > 0 ? Items[index - 1] : null; Items[index + 1].Previous = index > 0 ? Items[index - 1] : null;
@ -173,7 +106,7 @@ namespace Ryujinx.Memory.Range
{ {
int index = BinarySearch(item.Address); int index = BinarySearch(item.Address);
if (index >= 0 && Items[index].Value.Equals(item)) if (index >= 0 && Items[index] == item)
{ {
RemoveAt(index); RemoveAt(index);
@ -188,7 +121,7 @@ namespace Ryujinx.Memory.Range
/// </summary> /// </summary>
/// <param name="startItem">The first item in the range of items to be removed</param> /// <param name="startItem">The first item in the range of items to be removed</param>
/// <param name="endItem">The last item in the range of items to be removed</param> /// <param name="endItem">The last item in the range of items to be removed</param>
public override void RemoveRange(RangeItem<T> startItem, RangeItem<T> endItem) public override void RemoveRange(T startItem, T endItem)
{ {
if (startItem is null) if (startItem is null)
{ {
@ -197,7 +130,7 @@ namespace Ryujinx.Memory.Range
if (startItem == endItem) if (startItem == endItem)
{ {
Remove(startItem.Value); Remove(startItem);
return; return;
} }
@ -229,42 +162,45 @@ namespace Ryujinx.Memory.Range
/// <param name="size">Size of the range</param> /// <param name="size">Size of the range</param>
public void RemoveRange(ulong address, ulong size) public void RemoveRange(ulong address, ulong size)
{ {
int startIndex = BinarySearchLeftEdge(address, address + size); (int startIndex, int endIndex) = BinarySearchEdges(address, address + size);
if (startIndex < 0) if (startIndex < 0)
{ {
return; return;
} }
int endIndex = startIndex; if (startIndex == endIndex - 1)
while (Items[endIndex] is not null && Items[endIndex].Address < address + size)
{ {
if (endIndex == Count - 1) RemoveAt(startIndex);
{ return;
break;
}
endIndex++;
} }
if (endIndex < Count - 1) RemoveRangeInternal(startIndex, endIndex);
}
/// <summary>
/// Removes a range of items from the item list
/// </summary>
/// <param name="index">Start index of the range</param>
/// <param name="endIndex">End index of the range (exclusive)</param>
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)
if (endIndex < Count - 1)
{ {
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;
} }
/// <summary> /// <summary>
@ -296,8 +232,8 @@ namespace Ryujinx.Memory.Range
// So we need to return both the split 0-1 and 1-2 ranges. // So we need to return both the split 0-1 and 1-2 ranges.
Lock.EnterWriteLock(); Lock.EnterWriteLock();
(RangeItem<T> first, RangeItem<T> last) = FindOverlapsAsNodes(address, size); (T first, T last) = FindOverlapsAsNodes(address, size);
list = new List<T>(); list = [];
if (first is null) if (first is null)
{ {
@ -311,42 +247,41 @@ namespace Ryujinx.Memory.Range
ulong lastAddress = address; ulong lastAddress = address;
ulong endAddress = address + size; ulong endAddress = address + size;
RangeItem<T> current = first; T current = first;
while (last is not null && current is not null && current.Address < endAddress) while (last is not null && current is not null && current.Address < endAddress)
{ {
T region = current.Value; if (first == last && current.Address == address && current.Size == size)
if (first == last && region.Address == address && region.Size == size)
{ {
// Exact match, no splitting required. // Exact match, no splitting required.
list.Add(region); list.Add(current);
Lock.ExitWriteLock(); Lock.ExitWriteLock();
return; return;
} }
if (lastAddress < region.Address) if (lastAddress < current.Address)
{ {
// There is a gap between this region and the last. We need to fill it. // 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); list.Add(fillRegion);
Add(fillRegion); Add(fillRegion);
} }
if (region.Address < address) if (current.Address < address)
{ {
// Split the region around our base address and take the high half. // 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 the region around our end address and take the low half.
Split(region, address + size); Split(current, address + size);
} }
list.Add(region); list.Add(current);
lastAddress = region.EndAddress; lastAddress = current.EndAddress;
current = current.Next; current = current.Next;
} }
@ -374,7 +309,6 @@ namespace Ryujinx.Memory.Range
private T Split(T region, ulong splitAddress) private T Split(T region, ulong splitAddress)
{ {
T newRegion = (T)region.Split(splitAddress); T newRegion = (T)region.Split(splitAddress);
Update(region);
Add(newRegion); Add(newRegion);
return newRegion; return newRegion;
} }
@ -386,16 +320,11 @@ namespace Ryujinx.Memory.Range
/// <param name="size">Size in bytes of the range</param> /// <param name="size">Size in bytes of the range</param>
/// <returns>The leftmost overlapping item, or null if none is found</returns> /// <returns>The leftmost overlapping item, or null if none is found</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public override RangeItem<T> FindOverlap(ulong address, ulong size) public override T FindOverlap(ulong address, ulong size)
{ {
int index = BinarySearchLeftEdge(address, address + size); int index = BinarySearchLeftEdge(address, address + size);
if (index < 0) return index < 0 ? null : Items[index];
{
return null;
}
return Items[index];
} }
/// <summary> /// <summary>
@ -405,16 +334,11 @@ namespace Ryujinx.Memory.Range
/// <param name="size">Size in bytes of the range</param> /// <param name="size">Size in bytes of the range</param>
/// <returns>The overlapping item, or null if none is found</returns> /// <returns>The overlapping item, or null if none is found</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public override RangeItem<T> FindOverlapFast(ulong address, ulong size) public override T FindOverlapFast(ulong address, ulong size)
{ {
int index = BinarySearch(address, address + size); int index = BinarySearch(address, address + size);
if (index < 0) return index < 0 ? null : Items[index];
{
return null;
}
return Items[index];
} }
/// <summary> /// <summary>
@ -424,23 +348,18 @@ namespace Ryujinx.Memory.Range
/// <param name="size">Size in bytes of the range</param> /// <param name="size">Size in bytes of the range</param>
/// <returns>The first and last overlapping items, or null if none are found</returns> /// <returns>The first and last overlapping items, or null if none are found</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public (RangeItem<T>, RangeItem<T>) FindOverlapsAsNodes(ulong address, ulong size) public (T, T) FindOverlapsAsNodes(ulong address, ulong size)
{ {
(int index, int endIndex) = BinarySearchEdges(address, address + size); (int index, int endIndex) = BinarySearchEdges(address, address + size);
if (index < 0) return index < 0 ? (null, null) : (Items[index], Items[endIndex - 1]);
{
return (null, null);
}
return (Items[index], Items[endIndex - 1]);
} }
public RangeItem<T>[] 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); (int index, int endIndex) = BinarySearchEdges(address, address + size);
RangeItem<T>[] result; T[] result;
if (index < 0) if (index < 0)
{ {
@ -449,29 +368,20 @@ namespace Ryujinx.Memory.Range
} }
else else
{ {
result = ArrayPool<RangeItem<T>>.Shared.Rent(endIndex - index); result = ArrayPool<T>.Shared.Rent(endIndex - index);
length = endIndex - index; length = endIndex - index;
Array.Copy(Items, index, result, 0, endIndex - index); Items.AsSpan(index, endIndex - index).CopyTo(result);
} }
return result; return result;
} }
public Span<RangeItem<T>> FindOverlapsAsSpan(ulong address, ulong size) public ReadOnlySpan<T> FindOverlapsAsSpan(ulong address, ulong size)
{ {
(int index, int endIndex) = BinarySearchEdges(address, address + size); (int index, int endIndex) = BinarySearchEdges(address, address + size);
Span<RangeItem<T>> result; ReadOnlySpan<T> result = index < 0 ? [] : Items.AsSpan(index, endIndex - index);
if (index < 0)
{
result = [];
}
else
{
result = Items.AsSpan().Slice(index, endIndex - index);
}
return result; return result;
} }
@ -480,7 +390,7 @@ namespace Ryujinx.Memory.Range
{ {
for (int i = 0; i < Count; i++) for (int i = 0; i < Count; i++)
{ {
yield return Items[i].Value; yield return Items[i];
} }
} }
} }

View file

@ -14,14 +14,14 @@ namespace Ryujinx.Memory.Range
/// startIndex is inclusive. /// startIndex is inclusive.
/// endIndex is exclusive. /// endIndex is exclusive.
/// </remarks> /// </remarks>
public readonly struct OverlapResult<T> where T : IRange public readonly struct OverlapResult<T> where T : class, IRangeListRange<T>
{ {
public readonly int StartIndex = -1; public readonly int StartIndex = -1;
public readonly int EndIndex = -1; public readonly int EndIndex = -1;
public readonly RangeItem<T> QuickResult; public readonly T QuickResult;
public int Count => EndIndex - StartIndex; public int Count => EndIndex - StartIndex;
public OverlapResult(int startIndex, int endIndex, RangeItem<T> quickResult = null) public OverlapResult(int startIndex, int endIndex, T quickResult = null)
{ {
this.StartIndex = startIndex; this.StartIndex = startIndex;
this.EndIndex = endIndex; this.EndIndex = endIndex;
@ -33,7 +33,7 @@ namespace Ryujinx.Memory.Range
/// Sorted list of ranges that supports binary search. /// Sorted list of ranges that supports binary search.
/// </summary> /// </summary>
/// <typeparam name="T">Type of the range.</typeparam> /// <typeparam name="T">Type of the range.</typeparam>
public class RangeList<T> : RangeListBase<T> where T : IRange public class RangeList<T> : RangeListBase<T> where T : class, IRangeListRange<T>
{ {
public readonly ReaderWriterLockSlim Lock = new(); public readonly ReaderWriterLockSlim Lock = new();
@ -61,104 +61,6 @@ namespace Ryujinx.Memory.Range
index = ~index; index = ~index;
} }
Insert(index, new RangeItem<T>(item));
}
/// <summary>
/// Updates an item's end address on the list. Address must be the same.
/// </summary>
/// <param name="item">The item to be updated</param>
/// <returns>True if the item was located and updated, false otherwise</returns>
protected override bool Update(T item)
{
int index = BinarySearch(item.Address);
if (index >= 0)
{
while (index < Count)
{
if (Items[index].Value.Equals(item))
{
RangeItem<T> 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;
}
/// <summary>
/// Updates an item's end address on the list. Address must be the same.
/// </summary>
/// <param name="item">The RangeItem to be updated</param>
/// <returns>True if the item was located and updated, false otherwise</returns>
protected override bool Update(RangeItem<T> item)
{
int index = BinarySearch(item.Address);
if (index >= 0)
{
while (index < Count)
{
if (Items[index].Equals(item))
{
RangeItem<T> 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<T> item)
{
Debug.Assert(item.Address != item.EndAddress);
Debug.Assert(item.Address % 32 == 0);
if (Count + 1 > Items.Length) if (Count + 1 > Items.Length)
{ {
Array.Resize(ref Items, Items.Length + BackingGrowthSize); Array.Resize(ref Items, Items.Length + BackingGrowthSize);
@ -220,7 +122,7 @@ namespace Ryujinx.Memory.Range
/// <param name="startItem">The first item in the range of items to be removed</param> /// <param name="startItem">The first item in the range of items to be removed</param>
/// <param name="endItem">The last item in the range of items to be removed</param> /// <param name="endItem">The last item in the range of items to be removed</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public override void RemoveRange(RangeItem<T> startItem, RangeItem<T> endItem) public override void RemoveRange(T startItem, T endItem)
{ {
if (startItem is null) if (startItem is null)
{ {
@ -229,30 +131,29 @@ namespace Ryujinx.Memory.Range
if (startItem == endItem) if (startItem == endItem)
{ {
Remove(startItem.Value); Remove(startItem);
return; return;
} }
int startIndex = BinarySearch(startItem.Address); (int index, int endIndex) = BinarySearchEdges(startItem.Address, endItem.EndAddress);
int endIndex = BinarySearch(endItem.Address);
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;
} }
/// <summary> /// <summary>
@ -268,7 +169,7 @@ namespace Ryujinx.Memory.Range
{ {
while (index < Count) while (index < Count)
{ {
if (Items[index].Value.Equals(item)) if (Items[index] == item)
{ {
RemoveAt(index); RemoveAt(index);
@ -298,7 +199,7 @@ namespace Ryujinx.Memory.Range
/// <param name="size">Size in bytes of the range</param> /// <param name="size">Size in bytes of the range</param>
/// <returns>The overlapping item, or the default value for the type if none found</returns> /// <returns>The overlapping item, or the default value for the type if none found</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public override RangeItem<T> FindOverlap(ulong address, ulong size) public override T FindOverlap(ulong address, ulong size)
{ {
int index = BinarySearchLeftEdge(address, address + size); int index = BinarySearchLeftEdge(address, address + size);
@ -321,7 +222,7 @@ namespace Ryujinx.Memory.Range
/// <param name="size">Size in bytes of the range</param> /// <param name="size">Size in bytes of the range</param>
/// <returns>The overlapping item, or the default value for the type if none found</returns> /// <returns>The overlapping item, or the default value for the type if none found</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public override RangeItem<T> FindOverlapFast(ulong address, ulong size) public override T FindOverlapFast(ulong address, ulong size)
{ {
int index = BinarySearch(address, address + size); int index = BinarySearch(address, address + size);
@ -340,7 +241,7 @@ namespace Ryujinx.Memory.Range
/// <param name="size">Size in bytes of the range</param> /// <param name="size">Size in bytes of the range</param>
/// <param name="output">Output array where matches will be written. It is automatically resized to fit the results</param> /// <param name="output">Output array where matches will be written. It is automatically resized to fit the results</param>
/// <returns>Range information of overlapping items found</returns> /// <returns>Range information of overlapping items found</returns>
private OverlapResult<T> FindOverlaps(ulong address, ulong size, ref RangeItem<T>[] output) private OverlapResult<T> FindOverlaps(ulong address, ulong size, ref T[] output)
{ {
int outputCount = 0; int outputCount = 0;
@ -353,7 +254,7 @@ namespace Ryujinx.Memory.Range
for (int i = startIndex; i < Count; i++) for (int i = startIndex; i < Count; i++)
{ {
ref RangeItem<T> item = ref Items[i]; T item = Items[i];
if (item.Address >= endAddress) if (item.Address >= endAddress)
{ {
@ -398,7 +299,7 @@ namespace Ryujinx.Memory.Range
{ {
for (int i = 0; i < Count; i++) for (int i = 0; i < Count; i++)
{ {
yield return Items[i].Value; yield return Items[i];
} }
} }
} }

View file

@ -1,56 +1,22 @@
using Ryujinx.Common; using Ryujinx.Common;
using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
namespace Ryujinx.Memory.Range namespace Ryujinx.Memory.Range
{ {
public class RangeItem<TValue> where TValue : IRange public interface IRangeListRange<TValue> : IRange where TValue : class, IRangeListRange<TValue>
{ {
public RangeItem<TValue> Next; public TValue Next { get; set; }
public RangeItem<TValue> Previous; public TValue Previous { get; set; }
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<TValue> 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 unsafe abstract class RangeListBase<T> : IEnumerable<T> where T : IRange public unsafe abstract class RangeListBase<T> : IEnumerable<T> where T : class, IRangeListRange<T>
{ {
protected static readonly ObjectPool<RangeItem<T>> _rangeItemPool = new(() => new RangeItem<T>());
private const int BackingInitialSize = 1024; private const int BackingInitialSize = 1024;
protected RangeItem<T>[] Items; protected T[] Items;
protected readonly int BackingGrowthSize; protected readonly int BackingGrowthSize;
public int Count { get; protected set; } public int Count { get; protected set; }
@ -62,32 +28,18 @@ namespace Ryujinx.Memory.Range
protected RangeListBase(int backingInitialSize = BackingInitialSize) protected RangeListBase(int backingInitialSize = BackingInitialSize)
{ {
BackingGrowthSize = backingInitialSize; BackingGrowthSize = backingInitialSize;
Items = new RangeItem<T>[backingInitialSize]; Items = new T[backingInitialSize];
} }
public abstract void Add(T item); public abstract void Add(T item);
/// <summary>
/// Updates an item's end address on the list. Address must be the same.
/// </summary>
/// <param name="item">The item to be updated</param>
/// <returns>True if the item was located and updated, false otherwise</returns>
protected abstract bool Update(T item);
/// <summary>
/// Updates an item's end address on the list. Address must be the same.
/// </summary>
/// <param name="item">The RangeItem to be updated</param>
/// <returns>True if the item was located and updated, false otherwise</returns>
protected abstract bool Update(RangeItem<T> item);
public abstract bool Remove(T item); public abstract bool Remove(T item);
public abstract void RemoveRange(RangeItem<T> startItem, RangeItem<T> endItem); public abstract void RemoveRange(T startItem, T endItem);
public abstract RangeItem<T> FindOverlap(ulong address, ulong size); public abstract T FindOverlap(ulong address, ulong size);
public abstract RangeItem<T> FindOverlapFast(ulong address, ulong size); public abstract T FindOverlapFast(ulong address, ulong size);
/// <summary> /// <summary>
/// Performs binary search on the internal list of items. /// Performs binary search on the internal list of items.
@ -106,7 +58,7 @@ namespace Ryujinx.Memory.Range
int middle = left + (range >> 1); int middle = left + (range >> 1);
ref RangeItem<T> item = ref Items[middle]; T item = Items[middle];
if (item.Address == address) if (item.Address == address)
{ {
@ -144,7 +96,7 @@ namespace Ryujinx.Memory.Range
int middle = left + (range >> 1); int middle = left + (range >> 1);
ref RangeItem<T> item = ref Items[middle]; T item = Items[middle];
if (item.OverlapsWith(address, endAddress)) if (item.OverlapsWith(address, endAddress))
{ {
@ -185,7 +137,7 @@ namespace Ryujinx.Memory.Range
int middle = left + (range >> 1); int middle = left + (range >> 1);
ref RangeItem<T> item = ref Items[middle]; T item = Items[middle];
bool match = item.OverlapsWith(address, endAddress); bool match = item.OverlapsWith(address, endAddress);
@ -237,7 +189,7 @@ namespace Ryujinx.Memory.Range
int middle = right - (range >> 1); int middle = right - (range >> 1);
ref RangeItem<T> item = ref Items[middle]; T item = Items[middle];
bool match = item.OverlapsWith(address, endAddress); bool match = item.OverlapsWith(address, endAddress);
@ -282,7 +234,7 @@ namespace Ryujinx.Memory.Range
if (Count == 1) if (Count == 1)
{ {
ref RangeItem<T> item = ref Items[0]; T item = Items[0];
if (item.OverlapsWith(address, endAddress)) if (item.OverlapsWith(address, endAddress))
{ {
@ -312,7 +264,7 @@ namespace Ryujinx.Memory.Range
int middle = left + (range >> 1); int middle = left + (range >> 1);
ref RangeItem<T> item = ref Items[middle]; T item = Items[middle];
bool match = item.OverlapsWith(address, endAddress); bool match = item.OverlapsWith(address, endAddress);
@ -369,7 +321,7 @@ namespace Ryujinx.Memory.Range
int middle = right - (range >> 1); int middle = right - (range >> 1);
ref RangeItem<T> item = ref Items[middle]; T item = Items[middle];
bool match = item.OverlapsWith(address, endAddress); bool match = item.OverlapsWith(address, endAddress);

View file

@ -5,7 +5,7 @@ namespace Ryujinx.Memory.Tracking
/// <summary> /// <summary>
/// A region of memory. /// A region of memory.
/// </summary> /// </summary>
abstract class AbstractRegion : INonOverlappingRange abstract class AbstractRegion<T> : INonOverlappingRange<T> where T : class, INonOverlappingRange<T>
{ {
/// <summary> /// <summary>
/// Base address. /// Base address.
@ -21,6 +21,9 @@ namespace Ryujinx.Memory.Tracking
/// End address. /// End address.
/// </summary> /// </summary>
public ulong EndAddress => Address + Size; public ulong EndAddress => Address + Size;
public T Next { get; set; }
public T Previous { get; set; }
/// <summary> /// <summary>
/// Create a new region. /// Create a new region.
@ -37,11 +40,11 @@ namespace Ryujinx.Memory.Tracking
/// Check if this range overlaps with another. /// Check if this range overlaps with another.
/// </summary> /// </summary>
/// <param name="address">Base address</param> /// <param name="address">Base address</param>
/// <param name="size">Size of the range</param> /// <param name="endAddress">End address</param>
/// <returns>True if overlapping, false otherwise</returns> /// <returns>True if overlapping, false otherwise</returns>
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;
} }
/// <summary> /// <summary>
@ -68,6 +71,6 @@ namespace Ryujinx.Memory.Tracking
/// </summary> /// </summary>
/// <param name="splitAddress">Address to split the region around</param> /// <param name="splitAddress">Address to split the region around</param>
/// <returns>The second part of the split region, with start address at the given split.</returns> /// <returns>The second part of the split region, with start address at the given split.</returns>
public abstract INonOverlappingRange Split(ulong splitAddress); public abstract INonOverlappingRange<T> Split(ulong splitAddress);
} }
} }

View file

@ -81,10 +81,10 @@ namespace Ryujinx.Memory.Tracking
{ {
NonOverlappingRangeList<VirtualRegion> regions = type == 0 ? _virtualRegions : _guestVirtualRegions; NonOverlappingRangeList<VirtualRegion> regions = type == 0 ? _virtualRegions : _guestVirtualRegions;
regions.Lock.EnterReadLock(); regions.Lock.EnterReadLock();
Span<RangeItem<VirtualRegion>> overlaps = regions.FindOverlapsAsSpan(va, size); ReadOnlySpan<VirtualRegion> overlaps = regions.FindOverlapsAsSpan(va, size);
for (int i = 0; i < overlaps.Length; i++) 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. // If the region has been fully remapped, signal that it has been mapped again.
bool remapped = _memoryManager.IsRangeMapped(region.Address, region.Size); bool remapped = _memoryManager.IsRangeMapped(region.Address, region.Size);
@ -117,11 +117,11 @@ namespace Ryujinx.Memory.Tracking
{ {
NonOverlappingRangeList<VirtualRegion> regions = type == 0 ? _virtualRegions : _guestVirtualRegions; NonOverlappingRangeList<VirtualRegion> regions = type == 0 ? _virtualRegions : _guestVirtualRegions;
regions.Lock.EnterReadLock(); regions.Lock.EnterReadLock();
Span<RangeItem<VirtualRegion>> overlaps = regions.FindOverlapsAsSpan(va, size); ReadOnlySpan<VirtualRegion> overlaps = regions.FindOverlapsAsSpan(va, size);
for (int i = 0; i < overlaps.Length; i++) for (int i = 0; i < overlaps.Length; i++)
{ {
overlaps[i].Value.SignalMappingChanged(false); overlaps[i].SignalMappingChanged(false);
} }
regions.Lock.ExitReadLock(); 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. // We use the non-span method here because keeping the lock will cause a deadlock.
regions.Lock.EnterReadLock(); regions.Lock.EnterReadLock();
RangeItem<VirtualRegion>[] overlaps = regions.FindOverlapsAsArray(address, size, out int length); VirtualRegion[] overlaps = regions.FindOverlapsAsArray(address, size, out int length);
regions.Lock.ExitReadLock(); regions.Lock.ExitReadLock();
if (length == 0 && !precise) if (length == 0 && !precise)
@ -327,7 +327,7 @@ namespace Ryujinx.Memory.Tracking
for (int i = 0; i < length; i++) for (int i = 0; i < length; i++)
{ {
VirtualRegion region = overlaps[i].Value; VirtualRegion region = overlaps[i];
if (precise) if (precise)
{ {
@ -341,7 +341,7 @@ namespace Ryujinx.Memory.Tracking
if (length != 0) if (length != 0)
{ {
ArrayPool<RangeItem<VirtualRegion>>.Shared.Return(overlaps); ArrayPool<VirtualRegion>.Shared.Return(overlaps);
} }
} }
} }

View file

@ -6,7 +6,7 @@ namespace Ryujinx.Memory.Tracking
/// <summary> /// <summary>
/// A region of virtual memory. /// A region of virtual memory.
/// </summary> /// </summary>
class VirtualRegion : AbstractRegion class VirtualRegion : AbstractRegion<VirtualRegion>
{ {
public List<RegionHandle> Handles = []; public List<RegionHandle> Handles = [];
@ -137,7 +137,7 @@ namespace Ryujinx.Memory.Tracking
} }
} }
public override INonOverlappingRange Split(ulong splitAddress) public override INonOverlappingRange<VirtualRegion> Split(ulong splitAddress)
{ {
VirtualRegion newRegion = new(_tracking, splitAddress, EndAddress - splitAddress, Guest, _lastPermission); VirtualRegion newRegion = new(_tracking, splitAddress, EndAddress - splitAddress, Guest, _lastPermission);
Size = splitAddress - Address; Size = splitAddress - Address;

View file

@ -336,7 +336,11 @@ namespace Ryujinx.Ava.UI.ViewModels
} }
} }
if (LastScannedAmiiboId != string.Empty) if (ShowAllAmiibo && _amiiboSeries.Count > 0)
{
SeriesSelectedIndex = 0;
}
else if (LastScannedAmiiboId != string.Empty)
{ {
SelectLastScannedAmiibo(); SelectLastScannedAmiibo();
} }
@ -412,6 +416,7 @@ namespace Ryujinx.Ava.UI.ViewModels
AmiiboSelectedIndex = restoredIndex != -1 ? restoredIndex : (_amiibos.Count > 0 ? 0 : -1); AmiiboSelectedIndex = restoredIndex != -1 ? restoredIndex : (_amiibos.Count > 0 ? 0 : -1);
} }
private void SetAmiiboDetails() private void SetAmiiboDetails()
{ {
ResetAmiiboPreview(); ResetAmiiboPreview();