diff --git a/Ryujinx.Cpu/MemoryManager.cs b/Ryujinx.Cpu/MemoryManager.cs
index 83c288ae9d..59654e8bb3 100644
--- a/Ryujinx.Cpu/MemoryManager.cs
+++ b/Ryujinx.Cpu/MemoryManager.cs
@@ -627,7 +627,7 @@ namespace Ryujinx.Cpu
{
pte = Volatile.Read(ref pageRef);
}
- while (Interlocked.CompareExchange(ref pageRef, (pte & invTagMask) | tag, pte) != pte);
+ while (pte != 0 && Interlocked.CompareExchange(ref pageRef, (pte & invTagMask) | tag, pte) != pte);
pageStart++;
}
diff --git a/Ryujinx.Cpu/Tracking/CpuRegionHandle.cs b/Ryujinx.Cpu/Tracking/CpuRegionHandle.cs
index f4391aad4b..6a530b0e79 100644
--- a/Ryujinx.Cpu/Tracking/CpuRegionHandle.cs
+++ b/Ryujinx.Cpu/Tracking/CpuRegionHandle.cs
@@ -8,6 +8,7 @@ namespace Ryujinx.Cpu.Tracking
private readonly RegionHandle _impl;
public bool Dirty => _impl.Dirty;
+ public bool Unmapped => _impl.Unmapped;
public ulong Address => _impl.Address;
public ulong Size => _impl.Size;
public ulong EndAddress => _impl.EndAddress;
diff --git a/Ryujinx.Graphics.Gpu/Image/Pool.cs b/Ryujinx.Graphics.Gpu/Image/Pool.cs
index c5aef77f69..855f63443c 100644
--- a/Ryujinx.Graphics.Gpu/Image/Pool.cs
+++ b/Ryujinx.Graphics.Gpu/Image/Pool.cs
@@ -117,7 +117,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// Performs the disposal of all resources stored on the pool.
/// It's an error to try using the pool after disposal.
///
- public void Dispose()
+ public virtual void Dispose()
{
if (Items != null)
{
diff --git a/Ryujinx.Graphics.Gpu/Image/Texture.cs b/Ryujinx.Graphics.Gpu/Image/Texture.cs
index 4d4091cb11..26e102a935 100644
--- a/Ryujinx.Graphics.Gpu/Image/Texture.cs
+++ b/Ryujinx.Graphics.Gpu/Image/Texture.cs
@@ -21,6 +21,12 @@ namespace Ryujinx.Graphics.Gpu.Image
// This method uses much more memory so we want to avoid it if possible.
private const int ByteComparisonSwitchThreshold = 4;
+ private struct TexturePoolOwner
+ {
+ public TexturePool Pool;
+ public int ID;
+ }
+
private GpuContext _context;
private SizeInfo _sizeInfo;
@@ -64,7 +70,13 @@ namespace Ryujinx.Graphics.Gpu.Image
/// Set when a texture has been changed size. This indicates that it may need to be
/// changed again when obtained as a sampler.
///
- public bool ChangedSize { get; internal set; }
+ public bool ChangedSize { get; private set; }
+
+ ///
+ /// Set when a texture's GPU VA has ever been partially or fully unmapped.
+ /// This indicates that the range must be fully checked when matching the texture.
+ ///
+ public bool ChangedMapping { get; private set; }
private int _depth;
private int _layers;
@@ -121,6 +133,7 @@ namespace Ryujinx.Graphics.Gpu.Image
public bool IsView => _viewStorage != this;
private int _referenceCount;
+ private List _poolOwners;
///
/// Constructs a new instance of the cached GPU texture.
@@ -190,6 +203,7 @@ namespace Ryujinx.Graphics.Gpu.Image
_viewStorage = this;
_views = new List();
+ _poolOwners = new List();
}
///
@@ -1218,6 +1232,20 @@ namespace Ryujinx.Graphics.Gpu.Image
_referenceCount++;
}
+ ///
+ /// Increments the reference count and records the given texture pool and ID as a pool owner.
+ ///
+ /// The texture pool this texture has been added to
+ /// The ID of the reference to this texture in the pool
+ public void IncrementReferenceCount(TexturePool pool, int id)
+ {
+ lock (_poolOwners)
+ {
+ _poolOwners.Add(new TexturePoolOwner { Pool = pool, ID = id });
+ }
+ _referenceCount++;
+ }
+
///
/// Decrements the texture reference count.
/// When the reference count hits zero, the texture may be deleted and can't be used anymore.
@@ -1244,6 +1272,48 @@ namespace Ryujinx.Graphics.Gpu.Image
return newRefCount <= 0;
}
+ ///
+ /// Decrements the texture reference count, also removing an associated pool owner reference.
+ /// When the reference count hits zero, the texture may be deleted and can't be used anymore.
+ ///
+ /// The texture pool this texture is being removed from
+ /// The ID of the reference to this texture in the pool
+ /// True if the texture is now referenceless, false otherwise
+ public bool DecrementReferenceCount(TexturePool pool, int id = -1)
+ {
+ lock (_poolOwners)
+ {
+ int references = _poolOwners.RemoveAll(entry => entry.Pool == pool && entry.ID == id || id == -1);
+
+ if (references == 0)
+ {
+ // This reference has already been removed.
+ return _referenceCount <= 0;
+ }
+
+ Debug.Assert(references == 1);
+ }
+
+ return DecrementReferenceCount();
+ }
+
+ ///
+ /// Forcibly remove this texture from all pools that reference it.
+ ///
+ /// Indicates if the removal is being done from another thread.
+ public void RemoveFromPools(bool deferred)
+ {
+ lock (_poolOwners)
+ {
+ foreach (var owner in _poolOwners)
+ {
+ owner.Pool.ForceRemove(this, owner.ID, deferred);
+ }
+
+ _poolOwners.Clear();
+ }
+ }
+
///
/// Delete the texture if it is not used anymore.
/// The texture is considered unused when the reference count is zero,
@@ -1282,7 +1352,11 @@ namespace Ryujinx.Graphics.Gpu.Image
///
public void Unmapped()
{
+ ChangedMapping = true;
+
IsModified = false; // We shouldn't flush this texture, as its memory is no longer mapped.
+
+ RemoveFromPools(true);
}
///
diff --git a/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs b/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs
index 5d150559fa..39567a8234 100644
--- a/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs
+++ b/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs
@@ -122,6 +122,7 @@ namespace Ryujinx.Graphics.Gpu.Image
{
bool dirty = false;
bool anyModified = false;
+ bool anyUnmapped = false;
for (int i = 0; i < regionCount; i++)
{
@@ -130,6 +131,7 @@ namespace Ryujinx.Graphics.Gpu.Image
bool modified = group.Modified;
bool handleDirty = false;
bool handleModified = false;
+ bool handleUnmapped = false;
foreach (CpuRegionHandle handle in group.Handles)
{
@@ -140,6 +142,7 @@ namespace Ryujinx.Graphics.Gpu.Image
}
else
{
+ handleUnmapped |= handle.Unmapped;
handleModified |= modified;
}
}
@@ -159,18 +162,20 @@ namespace Ryujinx.Graphics.Gpu.Image
dirty |= handleDirty;
}
+ anyUnmapped |= handleUnmapped;
+
if (group.NeedsCopy)
{
// The texture we copied from is still being written to. Copy from it again the next time this texture is used.
texture.SignalGroupDirty();
}
- _loadNeeded[baseHandle + i] = handleDirty;
+ _loadNeeded[baseHandle + i] = handleDirty && !handleUnmapped;
}
if (dirty)
{
- if (_handles.Length > 1 && (anyModified || split))
+ if (anyUnmapped || (_handles.Length > 1 && (anyModified || split)))
{
// Partial texture invalidation. Only update the layers/levels with dirty flags of the storage.
@@ -194,8 +199,6 @@ namespace Ryujinx.Graphics.Gpu.Image
/// The number of handles to synchronize
private void SynchronizePartial(int baseHandle, int regionCount)
{
- ReadOnlySpan fullData = _context.PhysicalMemory.GetSpan(Storage.Range);
-
for (int i = 0; i < regionCount; i++)
{
if (_loadNeeded[baseHandle + i])
@@ -212,7 +215,7 @@ namespace Ryujinx.Graphics.Gpu.Image
int endOffset = (offsetIndex + 1 == _allOffsets.Length) ? (int)Storage.Size : _allOffsets[offsetIndex + 1];
int size = endOffset - offset;
- ReadOnlySpan data = fullData.Slice(offset, size);
+ ReadOnlySpan data = _context.PhysicalMemory.GetSpan(Storage.Range.GetSlice((ulong)offset, (ulong)size));
data = Storage.ConvertToHostCompatibleFormat(data, info.BaseLevel, true);
diff --git a/Ryujinx.Graphics.Gpu/Image/TextureManager.cs b/Ryujinx.Graphics.Gpu/Image/TextureManager.cs
index f13c3443d3..07bc7c7e47 100644
--- a/Ryujinx.Graphics.Gpu/Image/TextureManager.cs
+++ b/Ryujinx.Graphics.Gpu/Image/TextureManager.cs
@@ -715,7 +715,9 @@ namespace Ryujinx.Graphics.Gpu.Image
// we know the textures are located at the same memory region.
// If they don't, it may still be mapped to the same physical region, so we
// do a more expensive check to tell if they are mapped into the same physical regions.
- if (overlap.Info.GpuAddress != info.GpuAddress && !_context.MemoryManager.CompareRange(overlap.Range, info.GpuAddress))
+ // If the GPU VA for the texture has ever been unmapped, then the range must be checked regardless.
+ if ((overlap.Info.GpuAddress != info.GpuAddress || overlap.ChangedMapping) &&
+ !_context.MemoryManager.CompareRange(overlap.Range, info.GpuAddress))
{
continue;
}
diff --git a/Ryujinx.Graphics.Gpu/Image/TexturePool.cs b/Ryujinx.Graphics.Gpu/Image/TexturePool.cs
index 58e881ca73..eece2a79ec 100644
--- a/Ryujinx.Graphics.Gpu/Image/TexturePool.cs
+++ b/Ryujinx.Graphics.Gpu/Image/TexturePool.cs
@@ -2,6 +2,7 @@ using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Texture;
using System;
+using System.Collections.Concurrent;
using System.Collections.Generic;
namespace Ryujinx.Graphics.Gpu.Image
@@ -12,6 +13,7 @@ namespace Ryujinx.Graphics.Gpu.Image
class TexturePool : Pool
{
private int _sequenceNumber;
+ private readonly ConcurrentQueue _dereferenceQueue = new ConcurrentQueue();
///
/// Intrusive linked list node used on the texture pool cache.
@@ -53,6 +55,8 @@ namespace Ryujinx.Graphics.Gpu.Image
TextureInfo info = GetInfo(descriptor, out int layerSize);
+ ProcessDereferenceQueue();
+
texture = Context.Methods.TextureManager.FindOrCreateTexture(TextureSearchFlags.ForSampler, info, layerSize);
// If this happens, then the texture address is invalid, we can't add it to the cache.
@@ -61,7 +65,7 @@ namespace Ryujinx.Graphics.Gpu.Image
return null;
}
- texture.IncrementReferenceCount();
+ texture.IncrementReferenceCount(this, id);
Items[id] = texture;
@@ -92,6 +96,39 @@ namespace Ryujinx.Graphics.Gpu.Image
return texture;
}
+ ///
+ /// Forcibly remove a texture from this pool's items.
+ /// If deferred, the dereference will be queued to occur on the render thread.
+ ///
+ /// The texture being removed
+ /// The ID of the texture in this pool
+ /// If true, queue the dereference to happen on the render thread, otherwise dereference immediately
+ public void ForceRemove(Texture texture, int id, bool deferred)
+ {
+ Items[id] = null;
+
+ if (deferred)
+ {
+ _dereferenceQueue.Enqueue(texture);
+ }
+ else
+ {
+ texture.DecrementReferenceCount();
+ }
+ }
+
+ ///
+ /// Process the dereference queue, decrementing the reference count for each texture in it.
+ /// This is used to ensure that texture disposal happens on the render thread.
+ ///
+ private void ProcessDereferenceQueue()
+ {
+ while (_dereferenceQueue.TryDequeue(out Texture toRemove))
+ {
+ toRemove.DecrementReferenceCount();
+ }
+ }
+
///
/// Implementation of the texture pool range invalidation.
///
@@ -99,6 +136,8 @@ namespace Ryujinx.Graphics.Gpu.Image
/// Size of the range being invalidated
protected override void InvalidateRangeImpl(ulong address, ulong size)
{
+ ProcessDereferenceQueue();
+
ulong endAddress = address + size;
for (; address < endAddress; address += DescriptorSize)
@@ -118,7 +157,7 @@ namespace Ryujinx.Graphics.Gpu.Image
continue;
}
- texture.DecrementReferenceCount();
+ texture.DecrementReferenceCount(this, id);
Items[id] = null;
}
@@ -342,7 +381,14 @@ namespace Ryujinx.Graphics.Gpu.Image
/// The texture to be deleted
protected override void Delete(Texture item)
{
- item?.DecrementReferenceCount();
+ item?.DecrementReferenceCount(this);
+ }
+
+ public override void Dispose()
+ {
+ ProcessDereferenceQueue();
+
+ base.Dispose();
}
}
}
\ No newline at end of file
diff --git a/Ryujinx.Memory/Range/MultiRange.cs b/Ryujinx.Memory/Range/MultiRange.cs
index b40daa8aaf..3d505c7caf 100644
--- a/Ryujinx.Memory/Range/MultiRange.cs
+++ b/Ryujinx.Memory/Range/MultiRange.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
namespace Ryujinx.Memory.Range
{
@@ -75,6 +76,53 @@ namespace Ryujinx.Memory.Range
}
}
+ ///
+ /// Gets a slice of the multi-range.
+ ///
+ /// Offset of the slice into the multi-range in bytes
+ /// Size of the slice in bytes
+ /// A new multi-range representing the given slice of this one
+ public MultiRange GetSlice(ulong offset, ulong size)
+ {
+ if (HasSingleRange)
+ {
+ if (_singleRange.Size - offset < size)
+ {
+ throw new ArgumentOutOfRangeException(nameof(size));
+ }
+
+ return new MultiRange(_singleRange.Address + offset, size);
+ }
+ else
+ {
+ var ranges = new List();
+
+ foreach (MemoryRange range in _ranges)
+ {
+ if ((long)offset <= 0)
+ {
+ ranges.Add(new MemoryRange(range.Address, Math.Min(size, range.Size)));
+ size -= range.Size;
+ }
+ else if (offset < range.Size)
+ {
+ ulong sliceSize = Math.Min(size, range.Size - offset);
+ ranges.Add(new MemoryRange(range.Address + offset, sliceSize));
+ size -= sliceSize;
+ }
+
+ if ((long)size <= 0)
+ {
+ break;
+ }
+
+ offset -= range.Size;
+ }
+
+ return new MultiRange(ranges.ToArray());
+ }
+ }
+
///
/// Gets the physical region at the specified index.
///
diff --git a/Ryujinx.Memory/Tracking/MemoryTracking.cs b/Ryujinx.Memory/Tracking/MemoryTracking.cs
index aff223e802..6485e566cb 100644
--- a/Ryujinx.Memory/Tracking/MemoryTracking.cs
+++ b/Ryujinx.Memory/Tracking/MemoryTracking.cs
@@ -74,6 +74,14 @@ namespace Ryujinx.Memory.Tracking
for (int i = 0; i < count; i++)
{
VirtualRegion region = results[i];
+
+ // If the region has been fully remapped, signal that it has been mapped again.
+ bool remapped = _memoryManager.IsRangeMapped(region.Address, region.Size);
+ if (remapped)
+ {
+ region.SignalMappingChanged(true);
+ }
+
region.RecalculatePhysicalChildren();
region.UpdateProtection();
}
@@ -99,6 +107,7 @@ namespace Ryujinx.Memory.Tracking
for (int i = 0; i < count; i++)
{
VirtualRegion region = results[i];
+ region.SignalMappingChanged(false);
region.RecalculatePhysicalChildren();
}
}
diff --git a/Ryujinx.Memory/Tracking/RegionHandle.cs b/Ryujinx.Memory/Tracking/RegionHandle.cs
index 4da184dd79..da3ee99aa9 100644
--- a/Ryujinx.Memory/Tracking/RegionHandle.cs
+++ b/Ryujinx.Memory/Tracking/RegionHandle.cs
@@ -12,6 +12,7 @@ namespace Ryujinx.Memory.Tracking
public class RegionHandle : IRegionHandle, IRange
{
public bool Dirty { get; private set; }
+ public bool Unmapped { get; private set; }
public ulong Address { get; }
public ulong Size { get; }
@@ -37,10 +38,11 @@ namespace Ryujinx.Memory.Tracking
/// Tracking object for the target memory block
/// Virtual address of the region to track
/// Size of the region to track
- /// Initial value of the dirty flag
- internal RegionHandle(MemoryTracking tracking, ulong address, ulong size, bool dirty = true)
+ /// True if the region handle starts mapped
+ internal RegionHandle(MemoryTracking tracking, ulong address, ulong size, bool mapped = true)
{
- Dirty = dirty;
+ Dirty = mapped;
+ Unmapped = !mapped;
Address = address;
Size = size;
EndAddress = address + size;
@@ -128,6 +130,23 @@ namespace Ryujinx.Memory.Tracking
_regions.Add(region);
}
+ ///
+ /// Signal that this handle has been mapped or unmapped.
+ ///
+ /// True if the handle has been mapped, false if unmapped
+ internal void SignalMappingChanged(bool mapped)
+ {
+ if (Unmapped == mapped)
+ {
+ Unmapped = !mapped;
+
+ if (Unmapped)
+ {
+ Dirty = false;
+ }
+ }
+ }
+
///
/// Check if this region overlaps with another.
///
diff --git a/Ryujinx.Memory/Tracking/VirtualRegion.cs b/Ryujinx.Memory/Tracking/VirtualRegion.cs
index 15a11568e7..fcf2fbe06c 100644
--- a/Ryujinx.Memory/Tracking/VirtualRegion.cs
+++ b/Ryujinx.Memory/Tracking/VirtualRegion.cs
@@ -66,6 +66,18 @@ namespace Ryujinx.Memory.Tracking
UpdatePhysicalChildren();
}
+ ///
+ /// Signal that this region has been mapped or unmapped.
+ ///
+ /// True if the region has been mapped, false if unmapped
+ public void SignalMappingChanged(bool mapped)
+ {
+ foreach (RegionHandle handle in Handles)
+ {
+ handle.SignalMappingChanged(mapped);
+ }
+ }
+
///
/// Gets the strictest permission that the child handles demand. Assumes that the tracking lock has been obtained.
///