Memory Changes 3.2

Fixes a few crashes:
- fixes a crash related to waking threads (priorities were wrong).
- fixes a crash from reusing the SetRenderTargets texture array (left-over data causing issues).
- fixes a mistake and an oversight in the buffer system.
  - buffers were getting updated wrong causing bad data to be stored or some times cut.
  - modified ranges would extend past their old buffers, crashing on syncs. Old buffers are now skipped as the new buffers already sync instead.

Introduces pooling in a few more places to increase memory efficiency.

simplified RangeList item logic.
- removed RangeItem by making all the range objects use the I(NonOverlapping)Range interface.
- BufferCache class no longer locks its RangeList, as the list is only ever accessed synchronously.

Small change to how keyboard snapshots are stored.

Increase ThreadedRenderer SpanPool size to fit slightly more data (4MB -> 8MB).
This commit is contained in:
LotP 2025-12-06 17:19:19 -06:00 committed by KeatonTheBot
parent 6b383873e8
commit e404954f88
37 changed files with 561 additions and 686 deletions

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

@ -266,12 +266,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

@ -21,7 +21,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.GetHandles())); baseHandles.AddRange((item._memoryTrackingGranular.GetHandles()));
} }
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

@ -1,5 +1,4 @@
using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.GAL;
using Ryujinx.Memory.Range;
using System; using System;
namespace Ryujinx.Graphics.Gpu.Memory namespace Ryujinx.Graphics.Gpu.Memory
@ -56,7 +55,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 +101,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

@ -1,14 +1,13 @@
using Ryujinx.Memory.Range; using Ryujinx.Memory.Range;
using System; using System;
using System.Buffers; using System.Buffers;
using System.Linq;
namespace Ryujinx.Graphics.Gpu.Memory 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 +23,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 +56,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 +121,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 +159,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 +172,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 +214,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 +260,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 +285,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 +309,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 +344,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 +357,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 +366,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 +413,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 +429,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 +451,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 +467,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 +495,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 +555,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 +586,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 +598,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 +631,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

@ -1,7 +1,6 @@
using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.GAL;
using Silk.NET.Vulkan; using Silk.NET.Vulkan;
using System; using System;
using System.Linq;
using Format = Ryujinx.Graphics.GAL.Format; using Format = Ryujinx.Graphics.GAL.Format;
using VkFormat = Silk.NET.Vulkan.Format; using VkFormat = Silk.NET.Vulkan.Format;
@ -70,17 +69,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];
@ -164,9 +177,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

@ -3,7 +3,6 @@ using Ryujinx.Graphics.Shader;
using Silk.NET.Vulkan; using Silk.NET.Vulkan;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
@ -1032,7 +1031,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();
@ -1040,7 +1039,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);
@ -1384,7 +1383,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)
{ {
@ -1394,7 +1393,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)
{ {
@ -1431,12 +1430,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

@ -37,7 +37,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 +123,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

@ -32,7 +32,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 +55,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 && clientHeader.ReceiveListOffset > clientMsg.Size || if (clientHeader.ReceiveListType < 2 && clientHeader.ReceiveListOffset > clientMsg.Size ||
@ -608,6 +612,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 && clientHeader.ReceiveListOffset > clientMsg.Size || if (clientHeader.ReceiveListType < 2 && clientHeader.ReceiveListOffset > clientMsg.Size ||
@ -827,6 +833,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;
} }
@ -1060,6 +1068,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);
} }
} }
@ -1079,6 +1089,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,4 +1,3 @@
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;
@ -11,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)
{ {
@ -24,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)
@ -139,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)
{ {
@ -164,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();
@ -199,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);
@ -314,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)
@ -335,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;
} }
@ -391,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)
@ -412,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;
} }
@ -484,16 +529,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
// The value is decremented if the number of threads waiting is less // The value is decremented if the number of threads waiting is less
// 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 = _arbiterThreads[address].Count;
foreach (KThread thread in _arbiterThreads)
{
if (thread.MutexAddress == address &&
++waitingCount >= count)
{
break;
}
}
if (waitingCount > 0) if (waitingCount > 0)
{ {
@ -560,55 +596,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

@ -21,6 +21,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) public IpcService(ServerBase server = null)
{ {
CmifCommands = typeof(IpcService).Assembly.GetTypes() CmifCommands = typeof(IpcService).Assembly.GetTypes()
@ -123,7 +126,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
{ {
@ -173,7 +178,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

@ -451,8 +451,9 @@ namespace Ryujinx.HLE.HOS.Services
response.RawData = _responseDataStream.ToArray(); response.RawData = _responseDataStream.ToArray();
using var responseStream = response.GetStreamTipc(); var responseStream = response.GetStreamTipc();
_selfProcess.CpuMemory.Write(_selfThread.TlsAddress, responseStream.GetReadOnlySequence()); _selfProcess.CpuMemory.Write(_selfThread.TlsAddress, responseStream.GetReadOnlySequence());
MemoryStreamManager.Shared.ReleaseStream(responseStream);
} }
else else
{ {
@ -461,8 +462,9 @@ namespace Ryujinx.HLE.HOS.Services
if (!isTipcCommunication) if (!isTipcCommunication)
{ {
using var responseStream = response.GetStream((long)_selfThread.TlsAddress, recvListAddr | ((ulong)PointerBufferSize << 48)); var 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;
} }
break; break;
@ -130,6 +143,9 @@ namespace Ryujinx.Horizon.Sdk.OsTypes.Impl
{ {
if (_signaledHolder != null) if (_signaledHolder != null)
{ {
_objectHandlePool.Release(objectHandles);
_objectPool.Release(objects);
return _signaledHolder; return _signaledHolder;
} }
} }
@ -137,8 +153,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

@ -5,7 +5,6 @@ using Ryujinx.Common.Configuration.Hid.Controller.Motion;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.HLE.HOS.Services.Hid; using Ryujinx.HLE.HOS.Services.Hid;
using System; using System;
using System.Buffers;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
@ -532,8 +531,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

@ -1,4 +1,3 @@
using Ryujinx.Common;
using Ryujinx.Common.Configuration.Hid; using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Common.Configuration.Hid.Controller; using Ryujinx.Common.Configuration.Hid.Controller;
using Ryujinx.Common.Configuration.Hid.Keyboard; using Ryujinx.Common.Configuration.Hid.Keyboard;
@ -19,7 +18,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();
@ -39,6 +37,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)
{ {
@ -207,8 +208,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;
@ -250,14 +251,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);
} }
} }
@ -266,8 +267,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)
{ {
@ -321,10 +322,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

@ -1,4 +1,3 @@
using System.Buffers;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
namespace Ryujinx.Input namespace Ryujinx.Input
@ -8,6 +7,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 +30,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

@ -1,6 +1,5 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Threading; using System.Threading;
@ -14,14 +13,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 +32,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 +60,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 +121,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 +130,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 +168,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 +198,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 +221,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 +240,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 +253,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 +298,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,20 @@
using Ryujinx.Common; 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 +26,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 +56,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 +94,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 +135,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 +187,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 +232,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 +262,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 +319,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 = [];
@ -136,7 +136,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;