diff --git a/Ryujinx.Cpu/Tracking/CpuRegionHandle.cs b/Ryujinx.Cpu/Tracking/CpuRegionHandle.cs
index d2a287493..e766460fe 100644
--- a/Ryujinx.Cpu/Tracking/CpuRegionHandle.cs
+++ b/Ryujinx.Cpu/Tracking/CpuRegionHandle.cs
@@ -28,5 +28,10 @@ namespace Ryujinx.Cpu.Tracking
public void Reprotect(bool asDirty = false) => _impl.Reprotect(asDirty);
public bool OverlapsWith(ulong address, ulong size) => _impl.OverlapsWith(address, size);
+
+ public bool RangeEquals(CpuRegionHandle other)
+ {
+ return _impl.RealAddress == other._impl.RealAddress && _impl.RealSize == other._impl.RealSize;
+ }
}
}
diff --git a/Ryujinx.Graphics.GAL/Target.cs b/Ryujinx.Graphics.GAL/Target.cs
index e20bd3c84..711eea248 100644
--- a/Ryujinx.Graphics.GAL/Target.cs
+++ b/Ryujinx.Graphics.GAL/Target.cs
@@ -20,5 +20,15 @@ namespace Ryujinx.Graphics.GAL
{
return target == Target.Texture2DMultisample || target == Target.Texture2DMultisampleArray;
}
+
+ public static bool HasDepthOrLayers(this Target target)
+ {
+ return target == Target.Texture3D ||
+ target == Target.Texture1DArray ||
+ target == Target.Texture2DArray ||
+ target == Target.Texture2DMultisampleArray ||
+ target == Target.Cubemap ||
+ target == Target.CubemapArray;
+ }
}
}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Image/Texture.cs b/Ryujinx.Graphics.Gpu/Image/Texture.cs
index 6c0de5367..363f0f73a 100644
--- a/Ryujinx.Graphics.Gpu/Image/Texture.cs
+++ b/Ryujinx.Graphics.Gpu/Image/Texture.cs
@@ -36,6 +36,7 @@ namespace Ryujinx.Graphics.Gpu.Image
{
public TexturePool Pool;
public int ID;
+ public ulong GpuAddress;
}
private GpuContext _context;
@@ -162,6 +163,11 @@ namespace Ryujinx.Graphics.Gpu.Image
///
public bool IsView => _viewStorage != this;
+ ///
+ /// Whether or not this texture has views.
+ ///
+ public bool HasViews => _views.Count > 0;
+
private int _referenceCount;
private List _poolOwners;
@@ -383,6 +389,17 @@ namespace Ryujinx.Graphics.Gpu.Image
DecrementReferenceCount();
}
+ ///
+ /// Replaces the texture's physical memory range. This forces tracking to regenerate.
+ ///
+ /// New physical memory range backing the texture
+ public void ReplaceRange(MultiRange range)
+ {
+ Range = range;
+
+ Group.RangeChanged();
+ }
+
///
/// Create a copy dependency to a texture that is view compatible with this one.
/// When either texture is modified, the texture data will be copied to the other to keep them in sync.
@@ -715,6 +732,8 @@ namespace Ryujinx.Graphics.Gpu.Image
height = Math.Max(height >> level, 1);
depth = Math.Max(depth >> level, 1);
+ int sliceDepth = single ? 1 : depth;
+
SpanOrArray result;
if (Info.IsLinear)
@@ -735,7 +754,7 @@ namespace Ryujinx.Graphics.Gpu.Image
width,
height,
depth,
- single ? 1 : depth,
+ sliceDepth,
levels,
layers,
Info.FormatInfo.BlockWidth,
@@ -759,7 +778,7 @@ namespace Ryujinx.Graphics.Gpu.Image
Info.FormatInfo.BlockHeight,
width,
height,
- depth,
+ sliceDepth,
levels,
layers,
out byte[] decoded))
@@ -771,7 +790,7 @@ namespace Ryujinx.Graphics.Gpu.Image
if (GraphicsConfig.EnableTextureRecompression)
{
- decoded = BCnEncoder.EncodeBC7(decoded, width, height, depth, levels, layers);
+ decoded = BCnEncoder.EncodeBC7(decoded, width, height, sliceDepth, levels, layers);
}
result = decoded;
@@ -782,15 +801,15 @@ namespace Ryujinx.Graphics.Gpu.Image
{
case Format.Etc2RgbaSrgb:
case Format.Etc2RgbaUnorm:
- result = ETC2Decoder.DecodeRgba(result, width, height, depth, levels, layers);
+ result = ETC2Decoder.DecodeRgba(result, width, height, sliceDepth, levels, layers);
break;
case Format.Etc2RgbPtaSrgb:
case Format.Etc2RgbPtaUnorm:
- result = ETC2Decoder.DecodePta(result, width, height, depth, levels, layers);
+ result = ETC2Decoder.DecodePta(result, width, height, sliceDepth, levels, layers);
break;
case Format.Etc2RgbSrgb:
case Format.Etc2RgbUnorm:
- result = ETC2Decoder.DecodeRgb(result, width, height, depth, levels, layers);
+ result = ETC2Decoder.DecodeRgb(result, width, height, sliceDepth, levels, layers);
break;
}
}
@@ -800,31 +819,31 @@ namespace Ryujinx.Graphics.Gpu.Image
{
case Format.Bc1RgbaSrgb:
case Format.Bc1RgbaUnorm:
- result = BCnDecoder.DecodeBC1(result, width, height, depth, levels, layers);
+ result = BCnDecoder.DecodeBC1(result, width, height, sliceDepth, levels, layers);
break;
case Format.Bc2Srgb:
case Format.Bc2Unorm:
- result = BCnDecoder.DecodeBC2(result, width, height, depth, levels, layers);
+ result = BCnDecoder.DecodeBC2(result, width, height, sliceDepth, levels, layers);
break;
case Format.Bc3Srgb:
case Format.Bc3Unorm:
- result = BCnDecoder.DecodeBC3(result, width, height, depth, levels, layers);
+ result = BCnDecoder.DecodeBC3(result, width, height, sliceDepth, levels, layers);
break;
case Format.Bc4Snorm:
case Format.Bc4Unorm:
- result = BCnDecoder.DecodeBC4(result, width, height, depth, levels, layers, Format == Format.Bc4Snorm);
+ result = BCnDecoder.DecodeBC4(result, width, height, sliceDepth, levels, layers, Format == Format.Bc4Snorm);
break;
case Format.Bc5Snorm:
case Format.Bc5Unorm:
- result = BCnDecoder.DecodeBC5(result, width, height, depth, levels, layers, Format == Format.Bc5Snorm);
+ result = BCnDecoder.DecodeBC5(result, width, height, sliceDepth, levels, layers, Format == Format.Bc5Snorm);
break;
case Format.Bc6HSfloat:
case Format.Bc6HUfloat:
- result = BCnDecoder.DecodeBC6(result, width, height, depth, levels, layers, Format == Format.Bc6HSfloat);
+ result = BCnDecoder.DecodeBC6(result, width, height, sliceDepth, levels, layers, Format == Format.Bc6HSfloat);
break;
case Format.Bc7Srgb:
case Format.Bc7Unorm:
- result = BCnDecoder.DecodeBC7(result, width, height, depth, levels, layers);
+ result = BCnDecoder.DecodeBC7(result, width, height, sliceDepth, levels, layers);
break;
}
}
@@ -1484,11 +1503,12 @@ namespace Ryujinx.Graphics.Gpu.Image
///
/// 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)
+ /// GPU VA of the pool reference
+ public void IncrementReferenceCount(TexturePool pool, int id, ulong gpuVa)
{
lock (_poolOwners)
{
- _poolOwners.Add(new TexturePoolOwner { Pool = pool, ID = id });
+ _poolOwners.Add(new TexturePoolOwner { Pool = pool, ID = id, GpuAddress = gpuVa });
}
_referenceCount++;
@@ -1585,6 +1605,36 @@ namespace Ryujinx.Graphics.Gpu.Image
InvalidatedSequence++;
}
+ ///
+ /// Queue updating texture mappings on the pool. Happens from another thread.
+ ///
+ public void UpdatePoolMappings()
+ {
+ lock (_poolOwners)
+ {
+ ulong address = 0;
+
+ foreach (var owner in _poolOwners)
+ {
+ if (address == 0 || address == owner.GpuAddress)
+ {
+ address = owner.GpuAddress;
+
+ owner.Pool.QueueUpdateMapping(this, owner.ID);
+ }
+ else
+ {
+ // If there is a different GPU VA mapping, prefer the first and delete the others.
+ owner.Pool.ForceRemove(this, owner.ID, true);
+ }
+ }
+
+ _poolOwners.Clear();
+ }
+
+ InvalidatedSequence++;
+ }
+
///
/// Delete the texture if it is not used anymore.
/// The texture is considered unused when the reference count is zero,
@@ -1636,7 +1686,7 @@ namespace Ryujinx.Graphics.Gpu.Image
Group.ClearModified(unmapRange);
}
- RemoveFromPools(true);
+ UpdatePoolMappings();
}
///
diff --git a/Ryujinx.Graphics.Gpu/Image/TextureCache.cs b/Ryujinx.Graphics.Gpu/Image/TextureCache.cs
index 261d06038..c3243cf23 100644
--- a/Ryujinx.Graphics.Gpu/Image/TextureCache.cs
+++ b/Ryujinx.Graphics.Gpu/Image/TextureCache.cs
@@ -194,6 +194,39 @@ namespace Ryujinx.Graphics.Gpu.Image
_cache.Lift(texture);
}
+ ///
+ /// Attempts to update a texture's physical memory range.
+ /// Returns false if there is an existing texture that matches with the updated range.
+ ///
+ /// Texture to update
+ /// New physical memory range
+ /// True if the mapping was updated, false otherwise
+ public bool UpdateMapping(Texture texture, MultiRange range)
+ {
+ // There cannot be an existing texture compatible with this mapping in the texture cache already.
+ int overlapCount = _textures.FindOverlaps(range, ref _textureOverlaps);
+
+ for (int i = 0; i < overlapCount; i++)
+ {
+ var other = _textureOverlaps[i];
+
+ if (texture != other &&
+ (texture.IsViewCompatible(other.Info, other.Range, true, other.LayerSize, _context.Capabilities, out _, out _) != TextureViewCompatibility.Incompatible ||
+ other.IsViewCompatible(texture.Info, texture.Range, true, texture.LayerSize, _context.Capabilities, out _, out _) != TextureViewCompatibility.Incompatible))
+ {
+ return false;
+ }
+ }
+
+ _textures.Remove(texture);
+
+ texture.ReplaceRange(range);
+
+ _textures.Add(texture);
+
+ return true;
+ }
+
///
/// Tries to find an existing texture, or create a new one if not found.
///
diff --git a/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs b/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs
index 12a640e15..d9b620aae 100644
--- a/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs
+++ b/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs
@@ -39,6 +39,11 @@ namespace Ryujinx.Graphics.Gpu.Image
///
class TextureGroup : IDisposable
{
+ ///
+ /// Threshold of layers to force granular handles (and thus partial loading) on array/3D textures.
+ ///
+ private const int GranularLayerThreshold = 8;
+
private delegate void HandlesCallbackDelegate(int baseHandle, int regionCount, bool split = false);
///
@@ -116,7 +121,29 @@ namespace Ryujinx.Graphics.Gpu.Image
_allOffsets = size.AllOffsets;
_sliceSizes = size.SliceSizes;
- (_hasLayerViews, _hasMipViews) = PropagateGranularity(hasLayerViews, hasMipViews);
+ if (Storage.Target.HasDepthOrLayers() && Storage.Info.GetSlices() > GranularLayerThreshold)
+ {
+ _hasLayerViews = true;
+ _hasMipViews = true;
+ }
+ else
+ {
+ (_hasLayerViews, _hasMipViews) = PropagateGranularity(hasLayerViews, hasMipViews);
+
+ // If the texture is partially mapped, fully subdivide handles immediately.
+
+ MultiRange range = Storage.Range;
+ for (int i = 0; i < range.Count; i++)
+ {
+ if (range.GetSubRange(i).Address == MemoryManager.PteUnmapped)
+ {
+ _hasLayerViews = true;
+ _hasMipViews = true;
+
+ break;
+ }
+ }
+ }
RecalculateHandleRegions();
}
@@ -249,7 +276,7 @@ namespace Ryujinx.Graphics.Gpu.Image
{
bool dirty = false;
bool anyModified = false;
- bool anyUnmapped = false;
+ bool anyNotDirty = false;
for (int i = 0; i < regionCount; i++)
{
@@ -294,20 +321,21 @@ 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 && !handleUnmapped;
+ bool loadNeeded = handleDirty && !handleUnmapped;
+
+ anyNotDirty |= !loadNeeded;
+ _loadNeeded[baseHandle + i] = loadNeeded;
}
if (dirty)
{
- if (anyUnmapped || (_handles.Length > 1 && (anyModified || split)))
+ if (anyNotDirty || (_handles.Length > 1 && (anyModified || split)))
{
// Partial texture invalidation. Only update the layers/levels with dirty flags of the storage.
@@ -331,24 +359,56 @@ namespace Ryujinx.Graphics.Gpu.Image
/// The number of handles to synchronize
private void SynchronizePartial(int baseHandle, int regionCount)
{
+ int spanEndIndex = -1;
+ int spanBase = 0;
+ ReadOnlySpan dataSpan = ReadOnlySpan.Empty;
+
for (int i = 0; i < regionCount; i++)
{
if (_loadNeeded[baseHandle + i])
{
var info = GetHandleInformation(baseHandle + i);
+ // Ensure the data for this handle is loaded in the span.
+ if (spanEndIndex <= i - 1)
+ {
+ spanEndIndex = i;
+
+ if (_is3D)
+ {
+ // Look ahead to see how many handles need to be loaded.
+ for (int j = i + 1; j < regionCount; j++)
+ {
+ if (_loadNeeded[baseHandle + j])
+ {
+ spanEndIndex = j;
+ }
+ else
+ {
+ break;
+ }
+ }
+ }
+
+ var endInfo = spanEndIndex == i ? info : GetHandleInformation(baseHandle + spanEndIndex);
+
+ spanBase = _allOffsets[info.Index];
+ int spanLast = _allOffsets[endInfo.Index + endInfo.Layers * endInfo.Levels - 1];
+ int endOffset = Math.Min(spanLast + _sliceSizes[endInfo.BaseLevel + endInfo.Levels - 1], (int)Storage.Size);
+ int size = endOffset - spanBase;
+
+ dataSpan = _physicalMemory.GetSpan(Storage.Range.GetSlice((ulong)spanBase, (ulong)size));
+ }
+
// Only one of these will be greater than 1, as partial sync is only called when there are sub-image views.
for (int layer = 0; layer < info.Layers; layer++)
{
for (int level = 0; level < info.Levels; level++)
{
int offsetIndex = GetOffsetIndex(info.BaseLayer + layer, info.BaseLevel + level);
-
int offset = _allOffsets[offsetIndex];
- int endOffset = Math.Min(offset + _sliceSizes[info.BaseLevel + level], (int)Storage.Size);
- int size = endOffset - offset;
- ReadOnlySpan data = _physicalMemory.GetSpan(Storage.Range.GetSlice((ulong)offset, (ulong)size));
+ ReadOnlySpan data = dataSpan.Slice(offset - spanBase);
SpanOrArray result = Storage.ConvertToHostCompatibleFormat(data, info.BaseLevel + level, true);
@@ -865,8 +925,11 @@ namespace Ryujinx.Graphics.Gpu.Image
/// A TextureGroupHandle covering the given views
private TextureGroupHandle GenerateHandles(int viewStart, int views)
{
+ int viewEnd = viewStart + views - 1;
+ (_, int lastLevel) = GetLayerLevelForView(viewEnd);
+
int offset = _allOffsets[viewStart];
- int endOffset = (viewStart + views == _allOffsets.Length) ? (int)Storage.Size : _allOffsets[viewStart + views];
+ int endOffset = _allOffsets[viewEnd] + _sliceSizes[lastLevel];
int size = endOffset - offset;
var result = new List();
@@ -1057,7 +1120,8 @@ namespace Ryujinx.Graphics.Gpu.Image
/// The dirty flags from the previous handles will be kept.
///
/// The handles to replace the current handles with
- private void ReplaceHandles(TextureGroupHandle[] handles)
+ /// True if the storage memory range changed since the last region handle generation
+ private void ReplaceHandles(TextureGroupHandle[] handles, bool rangeChanged)
{
if (_handles != null)
{
@@ -1065,9 +1129,50 @@ namespace Ryujinx.Graphics.Gpu.Image
foreach (TextureGroupHandle groupHandle in handles)
{
- foreach (CpuRegionHandle handle in groupHandle.Handles)
+ if (rangeChanged)
{
- handle.Reprotect();
+ // When the storage range changes, this becomes a little different.
+ // If a range does not match one in the original, treat it as modified.
+ // It has been newly mapped and its data must be synchronized.
+
+ if (groupHandle.Handles.Length == 0)
+ {
+ continue;
+ }
+
+ foreach (var oldGroup in _handles)
+ {
+ if (!groupHandle.OverlapsWith(oldGroup.Offset, oldGroup.Size))
+ {
+ continue;
+ }
+
+ foreach (CpuRegionHandle handle in groupHandle.Handles)
+ {
+ bool hasMatch = false;
+
+ foreach (var oldHandle in oldGroup.Handles)
+ {
+ if (oldHandle.RangeEquals(handle))
+ {
+ hasMatch = true;
+ break;
+ }
+ }
+
+ if (hasMatch)
+ {
+ handle.Reprotect();
+ }
+ }
+ }
+ }
+ else
+ {
+ foreach (CpuRegionHandle handle in groupHandle.Handles)
+ {
+ handle.Reprotect();
+ }
}
}
@@ -1089,7 +1194,8 @@ namespace Ryujinx.Graphics.Gpu.Image
///
/// Recalculate handle regions for this texture group, and inherit existing state into the new handles.
///
- private void RecalculateHandleRegions()
+ /// True if the storage memory range changed since the last region handle generation
+ private void RecalculateHandleRegions(bool rangeChanged = false)
{
TextureGroupHandle[] handles;
@@ -1171,7 +1277,21 @@ namespace Ryujinx.Graphics.Gpu.Image
}
}
- ReplaceHandles(handles);
+ ReplaceHandles(handles, rangeChanged);
+ }
+
+ ///
+ /// Regenerates handles when the storage range has been remapped.
+ /// This forces the regions to be fully subdivided.
+ ///
+ public void RangeChanged()
+ {
+ _hasLayerViews = true;
+ _hasMipViews = true;
+
+ RecalculateHandleRegions(true);
+
+ SignalAllDirty();
}
///
diff --git a/Ryujinx.Graphics.Gpu/Image/TexturePool.cs b/Ryujinx.Graphics.Gpu/Image/TexturePool.cs
index 0348ca014..717c5c362 100644
--- a/Ryujinx.Graphics.Gpu/Image/TexturePool.cs
+++ b/Ryujinx.Graphics.Gpu/Image/TexturePool.cs
@@ -1,9 +1,12 @@
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.Gpu.Memory;
using Ryujinx.Graphics.Texture;
+using Ryujinx.Memory.Range;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
+using System.Threading;
namespace Ryujinx.Graphics.Gpu.Image
{
@@ -12,8 +15,63 @@ namespace Ryujinx.Graphics.Gpu.Image
///
class TexturePool : Pool, IPool
{
+ ///
+ /// A request to dereference a texture from a pool.
+ ///
+ private struct DereferenceRequest
+ {
+ ///
+ /// Whether the dereference is due to a mapping change or not.
+ ///
+ public readonly bool IsRemapped;
+
+ ///
+ /// The texture being dereferenced.
+ ///
+ public readonly Texture Texture;
+
+ ///
+ /// The ID of the pool entry this reference belonged to.
+ ///
+ public readonly int ID;
+
+ ///
+ /// Create a dereference request for a texture with a specific pool ID, and remapped flag.
+ ///
+ /// Whether the dereference is due to a mapping change or not
+ /// The texture being dereferenced
+ /// The ID of the pool entry, used to restore remapped textures
+ private DereferenceRequest(bool isRemapped, Texture texture, int id)
+ {
+ IsRemapped = isRemapped;
+ Texture = texture;
+ ID = id;
+ }
+
+ ///
+ /// Create a dereference request for a texture removal.
+ ///
+ /// The texture being removed
+ /// A texture removal dereference request
+ public static DereferenceRequest Remove(Texture texture)
+ {
+ return new DereferenceRequest(false, texture, 0);
+ }
+
+ ///
+ /// Create a dereference request for a texture remapping with a specific pool ID.
+ ///
+ /// The texture being remapped
+ /// The ID of the pool entry, used to restore remapped textures
+ /// A remap dereference request
+ public static DereferenceRequest Remap(Texture texture, int id)
+ {
+ return new DereferenceRequest(true, texture, id);
+ }
+ }
+
private readonly GpuChannel _channel;
- private readonly ConcurrentQueue _dereferenceQueue = new ConcurrentQueue();
+ private readonly ConcurrentQueue _dereferenceQueue = new ConcurrentQueue();
private TextureDescriptor _defaultDescriptor;
///
@@ -58,7 +116,11 @@ namespace Ryujinx.Graphics.Gpu.Image
{
TextureInfo info = GetInfo(descriptor, out int layerSize);
- ProcessDereferenceQueue();
+ // The dereference queue can put our texture back on the cache.
+ if ((texture = ProcessDereferenceQueue(id)) != null)
+ {
+ return ref descriptor;
+ }
texture = PhysicalMemory.TextureCache.FindOrCreateTexture(_channel.MemoryManager, TextureSearchFlags.ForSampler, info, layerSize);
@@ -69,10 +131,10 @@ namespace Ryujinx.Graphics.Gpu.Image
}
}
- texture.IncrementReferenceCount(this, id);
-
Items[id] = texture;
+ texture.IncrementReferenceCount(this, id, descriptor.UnpackAddress());
+
DescriptorCache[id] = descriptor;
}
else
@@ -155,11 +217,14 @@ namespace Ryujinx.Graphics.Gpu.Image
/// 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;
+ var previous = Interlocked.Exchange(ref Items[id], null);
if (deferred)
{
- _dereferenceQueue.Enqueue(texture);
+ if (previous != null)
+ {
+ _dereferenceQueue.Enqueue(DereferenceRequest.Remove(texture));
+ }
}
else
{
@@ -167,16 +232,91 @@ namespace Ryujinx.Graphics.Gpu.Image
}
}
+ ///
+ /// Queues a request to update a texture's mapping.
+ /// Mapping is updated later to avoid deleting the texture if it is still sparsely mapped.
+ ///
+ /// Texture with potential mapping change
+ /// ID in cache of texture with potential mapping change
+ public void QueueUpdateMapping(Texture texture, int id)
+ {
+ if (Interlocked.Exchange(ref Items[id], null) == texture)
+ {
+ _dereferenceQueue.Enqueue(DereferenceRequest.Remap(texture, id));
+ }
+ }
+
///
/// 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()
+ /// The ID of the entry that triggered this method
+ /// Texture that matches the entry ID if it has been readded to the cache.
+ private Texture ProcessDereferenceQueue(int id = -1)
{
- while (_dereferenceQueue.TryDequeue(out Texture toRemove))
+ while (_dereferenceQueue.TryDequeue(out DereferenceRequest request))
{
- toRemove.DecrementReferenceCount();
+ Texture texture = request.Texture;
+
+ // Unmapped storage textures can swap their ranges. The texture must be storage with no views or dependencies.
+ // TODO: Would need to update ranges on views, or guarantee that ones where the range changes can be instantly deleted.
+
+ if (request.IsRemapped && texture.Group.Storage == texture && !texture.HasViews && !texture.Group.HasCopyDependencies)
+ {
+ // Has the mapping for this texture changed?
+ ref readonly TextureDescriptor descriptor = ref GetDescriptorRef(request.ID);
+
+ ulong address = descriptor.UnpackAddress();
+
+ MultiRange range = _channel.MemoryManager.GetPhysicalRegions(address, texture.Size);
+
+ // If the texture is not mapped at all, delete its reference.
+
+ if (range.Count == 1 && range.GetSubRange(0).Address == MemoryManager.PteUnmapped)
+ {
+ texture.DecrementReferenceCount();
+ continue;
+ }
+
+ Items[request.ID] = texture;
+
+ // Create a new pool reference, as the last one was removed on unmap.
+
+ texture.IncrementReferenceCount(this, request.ID, address);
+ texture.DecrementReferenceCount();
+
+ // Refetch the range. Changes since the last check could have been lost
+ // as the cache entry was not restored (required to queue mapping change).
+
+ range = _channel.MemoryManager.GetPhysicalRegions(address, texture.Size);
+
+ if (!range.Equals(texture.Range))
+ {
+ // Part of the texture was mapped or unmapped. Replace the range and regenerate tracking handles.
+ if (!_channel.MemoryManager.Physical.TextureCache.UpdateMapping(texture, range))
+ {
+ // Texture could not be remapped due to a collision, just delete it.
+ if (Interlocked.Exchange(ref Items[request.ID], null) != null)
+ {
+ // If this is null, a request was already queued to decrement reference.
+ texture.DecrementReferenceCount(this, request.ID);
+ }
+ continue;
+ }
+ }
+
+ if (request.ID == id)
+ {
+ return texture;
+ }
+ }
+ else
+ {
+ texture.DecrementReferenceCount();
+ }
}
+
+ return null;
}
///
@@ -213,9 +353,10 @@ namespace Ryujinx.Graphics.Gpu.Image
_channel.MemoryManager.Physical.TextureCache.AddShortCache(texture, ref cachedDescriptor);
}
- texture.DecrementReferenceCount(this, id);
-
- Items[id] = null;
+ if (Interlocked.Exchange(ref Items[id], null) != null)
+ {
+ texture.DecrementReferenceCount(this, id);
+ }
}
}
}
diff --git a/Ryujinx.Graphics.Texture/LayoutConverter.cs b/Ryujinx.Graphics.Texture/LayoutConverter.cs
index b8ec9748b..09eaf3001 100644
--- a/Ryujinx.Graphics.Texture/LayoutConverter.cs
+++ b/Ryujinx.Graphics.Texture/LayoutConverter.cs
@@ -112,7 +112,7 @@ namespace Ryujinx.Graphics.Texture
int outSize = GetTextureSize(
width,
height,
- depth,
+ sliceDepth,
levels,
layers,
blockWidth,
diff --git a/Ryujinx.Memory/Range/MultiRange.cs b/Ryujinx.Memory/Range/MultiRange.cs
index e95af02f4..dc2aefe40 100644
--- a/Ryujinx.Memory/Range/MultiRange.cs
+++ b/Ryujinx.Memory/Range/MultiRange.cs
@@ -8,6 +8,8 @@ namespace Ryujinx.Memory.Range
///
public readonly struct MultiRange : IEquatable
{
+ private const ulong InvalidAddress = ulong.MaxValue;
+
private readonly MemoryRange _singleRange;
private readonly MemoryRange[] _ranges;
@@ -107,7 +109,16 @@ namespace Ryujinx.Memory.Range
else if (offset < range.Size)
{
ulong sliceSize = Math.Min(size, range.Size - offset);
- ranges.Add(new MemoryRange(range.Address + offset, sliceSize));
+
+ if (range.Address == InvalidAddress)
+ {
+ ranges.Add(new MemoryRange(range.Address, sliceSize));
+ }
+ else
+ {
+ ranges.Add(new MemoryRange(range.Address + offset, sliceSize));
+ }
+
size -= sliceSize;
}