diff options
-rw-r--r-- | Ryujinx.Cpu/Tracking/CpuRegionHandle.cs | 5 | ||||
-rw-r--r-- | Ryujinx.Graphics.GAL/Target.cs | 10 | ||||
-rw-r--r-- | Ryujinx.Graphics.Gpu/Image/Texture.cs | 82 | ||||
-rw-r--r-- | Ryujinx.Graphics.Gpu/Image/TextureCache.cs | 33 | ||||
-rw-r--r-- | Ryujinx.Graphics.Gpu/Image/TextureGroup.cs | 152 | ||||
-rw-r--r-- | Ryujinx.Graphics.Gpu/Image/TexturePool.cs | 165 | ||||
-rw-r--r-- | Ryujinx.Graphics.Texture/LayoutConverter.cs | 2 | ||||
-rw-r--r-- | Ryujinx.Memory/Range/MultiRange.cs | 13 |
8 files changed, 416 insertions, 46 deletions
diff --git a/Ryujinx.Cpu/Tracking/CpuRegionHandle.cs b/Ryujinx.Cpu/Tracking/CpuRegionHandle.cs index d2a28749..e766460f 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 e20bd3c8..711eea24 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 6c0de536..363f0f73 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 /// </summary> public bool IsView => _viewStorage != this; + /// <summary> + /// Whether or not this texture has views. + /// </summary> + public bool HasViews => _views.Count > 0; + private int _referenceCount; private List<TexturePoolOwner> _poolOwners; @@ -384,6 +390,17 @@ namespace Ryujinx.Graphics.Gpu.Image } /// <summary> + /// Replaces the texture's physical memory range. This forces tracking to regenerate. + /// </summary> + /// <param name="range">New physical memory range backing the texture</param> + public void ReplaceRange(MultiRange range) + { + Range = range; + + Group.RangeChanged(); + } + + /// <summary> /// 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. /// This is essentially an emulated view, useful for handling multiple view parents or format incompatibility. @@ -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<byte> 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 /// </summary> /// <param name="pool">The texture pool this texture has been added to</param> /// <param name="id">The ID of the reference to this texture in the pool</param> - public void IncrementReferenceCount(TexturePool pool, int id) + /// <param name="gpuVa">GPU VA of the pool reference</param> + 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++; @@ -1586,6 +1606,36 @@ namespace Ryujinx.Graphics.Gpu.Image } /// <summary> + /// Queue updating texture mappings on the pool. Happens from another thread. + /// </summary> + 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++; + } + + /// <summary> /// Delete the texture if it is not used anymore. /// The texture is considered unused when the reference count is zero, /// and it has no child views. @@ -1636,7 +1686,7 @@ namespace Ryujinx.Graphics.Gpu.Image Group.ClearModified(unmapRange); } - RemoveFromPools(true); + UpdatePoolMappings(); } /// <summary> diff --git a/Ryujinx.Graphics.Gpu/Image/TextureCache.cs b/Ryujinx.Graphics.Gpu/Image/TextureCache.cs index 261d0603..c3243cf2 100644 --- a/Ryujinx.Graphics.Gpu/Image/TextureCache.cs +++ b/Ryujinx.Graphics.Gpu/Image/TextureCache.cs @@ -195,6 +195,39 @@ namespace Ryujinx.Graphics.Gpu.Image } /// <summary> + /// Attempts to update a texture's physical memory range. + /// Returns false if there is an existing texture that matches with the updated range. + /// </summary> + /// <param name="texture">Texture to update</param> + /// <param name="range">New physical memory range</param> + /// <returns>True if the mapping was updated, false otherwise</returns> + 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; + } + + /// <summary> /// Tries to find an existing texture, or create a new one if not found. /// </summary> /// <param name="memoryManager">GPU memory manager where the texture is mapped</param> diff --git a/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs b/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs index 12a640e1..d9b620aa 100644 --- a/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs +++ b/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs @@ -39,6 +39,11 @@ namespace Ryujinx.Graphics.Gpu.Image /// </summary> class TextureGroup : IDisposable { + /// <summary> + /// Threshold of layers to force granular handles (and thus partial loading) on array/3D textures. + /// </summary> + private const int GranularLayerThreshold = 8; + private delegate void HandlesCallbackDelegate(int baseHandle, int regionCount, bool split = false); /// <summary> @@ -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 /// <param name="regionCount">The number of handles to synchronize</param> private void SynchronizePartial(int baseHandle, int regionCount) { + int spanEndIndex = -1; + int spanBase = 0; + ReadOnlySpan<byte> dataSpan = ReadOnlySpan<byte>.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<byte> data = _physicalMemory.GetSpan(Storage.Range.GetSlice((ulong)offset, (ulong)size)); + ReadOnlySpan<byte> data = dataSpan.Slice(offset - spanBase); SpanOrArray<byte> result = Storage.ConvertToHostCompatibleFormat(data, info.BaseLevel + level, true); @@ -865,8 +925,11 @@ namespace Ryujinx.Graphics.Gpu.Image /// <returns>A TextureGroupHandle covering the given views</returns> 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<CpuRegionHandle>(); @@ -1057,7 +1120,8 @@ namespace Ryujinx.Graphics.Gpu.Image /// The dirty flags from the previous handles will be kept. /// </summary> /// <param name="handles">The handles to replace the current handles with</param> - private void ReplaceHandles(TextureGroupHandle[] handles) + /// <param name="rangeChanged">True if the storage memory range changed since the last region handle generation</param> + 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) + { + // 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 { - handle.Reprotect(); + foreach (CpuRegionHandle handle in groupHandle.Handles) + { + handle.Reprotect(); + } } } @@ -1089,7 +1194,8 @@ namespace Ryujinx.Graphics.Gpu.Image /// <summary> /// Recalculate handle regions for this texture group, and inherit existing state into the new handles. /// </summary> - private void RecalculateHandleRegions() + /// <param name="rangeChanged">True if the storage memory range changed since the last region handle generation</param> + private void RecalculateHandleRegions(bool rangeChanged = false) { TextureGroupHandle[] handles; @@ -1171,7 +1277,21 @@ namespace Ryujinx.Graphics.Gpu.Image } } - ReplaceHandles(handles); + ReplaceHandles(handles, rangeChanged); + } + + /// <summary> + /// Regenerates handles when the storage range has been remapped. + /// This forces the regions to be fully subdivided. + /// </summary> + public void RangeChanged() + { + _hasLayerViews = true; + _hasMipViews = true; + + RecalculateHandleRegions(true); + + SignalAllDirty(); } /// <summary> diff --git a/Ryujinx.Graphics.Gpu/Image/TexturePool.cs b/Ryujinx.Graphics.Gpu/Image/TexturePool.cs index 0348ca01..717c5c36 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 /// </summary> class TexturePool : Pool<Texture, TextureDescriptor>, IPool<TexturePool> { + /// <summary> + /// A request to dereference a texture from a pool. + /// </summary> + private struct DereferenceRequest + { + /// <summary> + /// Whether the dereference is due to a mapping change or not. + /// </summary> + public readonly bool IsRemapped; + + /// <summary> + /// The texture being dereferenced. + /// </summary> + public readonly Texture Texture; + + /// <summary> + /// The ID of the pool entry this reference belonged to. + /// </summary> + public readonly int ID; + + /// <summary> + /// Create a dereference request for a texture with a specific pool ID, and remapped flag. + /// </summary> + /// <param name="isRemapped">Whether the dereference is due to a mapping change or not</param> + /// <param name="texture">The texture being dereferenced</param> + /// <param name="id">The ID of the pool entry, used to restore remapped textures</param> + private DereferenceRequest(bool isRemapped, Texture texture, int id) + { + IsRemapped = isRemapped; + Texture = texture; + ID = id; + } + + /// <summary> + /// Create a dereference request for a texture removal. + /// </summary> + /// <param name="texture">The texture being removed</param> + /// <returns>A texture removal dereference request</returns> + public static DereferenceRequest Remove(Texture texture) + { + return new DereferenceRequest(false, texture, 0); + } + + /// <summary> + /// Create a dereference request for a texture remapping with a specific pool ID. + /// </summary> + /// <param name="texture">The texture being remapped</param> + /// <param name="id">The ID of the pool entry, used to restore remapped textures</param> + /// <returns>A remap dereference request</returns> + public static DereferenceRequest Remap(Texture texture, int id) + { + return new DereferenceRequest(true, texture, id); + } + } + private readonly GpuChannel _channel; - private readonly ConcurrentQueue<Texture> _dereferenceQueue = new ConcurrentQueue<Texture>(); + private readonly ConcurrentQueue<DereferenceRequest> _dereferenceQueue = new ConcurrentQueue<DereferenceRequest>(); private TextureDescriptor _defaultDescriptor; /// <summary> @@ -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 /// <param name="deferred">If true, queue the dereference to happen on the render thread, otherwise dereference immediately</param> 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 { @@ -168,15 +233,90 @@ namespace Ryujinx.Graphics.Gpu.Image } /// <summary> + /// Queues a request to update a texture's mapping. + /// Mapping is updated later to avoid deleting the texture if it is still sparsely mapped. + /// </summary> + /// <param name="texture">Texture with potential mapping change</param> + /// <param name="id">ID in cache of texture with potential mapping change</param> + public void QueueUpdateMapping(Texture texture, int id) + { + if (Interlocked.Exchange(ref Items[id], null) == texture) + { + _dereferenceQueue.Enqueue(DereferenceRequest.Remap(texture, id)); + } + } + + /// <summary> /// 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. /// </summary> - private void ProcessDereferenceQueue() + /// <param name="id">The ID of the entry that triggered this method</param> + /// <returns>Texture that matches the entry ID if it has been readded to the cache.</returns> + 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; } /// <summary> @@ -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 b8ec9748..09eaf300 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 e95af02f..dc2aefe4 100644 --- a/Ryujinx.Memory/Range/MultiRange.cs +++ b/Ryujinx.Memory/Range/MultiRange.cs @@ -8,6 +8,8 @@ namespace Ryujinx.Memory.Range /// </summary> public readonly struct MultiRange : IEquatable<MultiRange> { + 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; } |