From 1fc90e57d2e7f7bb6886a58b81bcd1f4cb25f8cf Mon Sep 17 00:00:00 2001 From: riperiperi Date: Tue, 14 Mar 2023 20:08:44 +0000 Subject: [PATCH] Update range for remapped sparse textures instead of recreating them (#4442) * Update sparsely mapped texture ranges without recreating Important TODO in TexturePool. Smaller TODO: should I look into making textures with views also do this? It needs to be able to detect if the views can be instantly deleted without issue if they're now remapped. * Actually do partial updates * Signal group dirty after mappings changed * Fix various issues (should work now) * Further optimisation Should load a lot less data (16x) when partial updating 3d textures. * Improve stability * Allow granular uploads on large textures, improve rules * Actually avoid updating slices that aren't modified. * Address some feedback, minor optimisation * Small tweak * Refactor DereferenceRequest More specific initialization methods. * Improve code for resetting handles * Explain data loading a bit more * Add some safety for setting null from different threads. All texture sets come from the one thread, but null sets can come from multiple. Only decrement ref count if we succeeded the null set first. * Address feedback 1 * Make a bit safer --- Ryujinx.Cpu/Tracking/CpuRegionHandle.cs | 5 + Ryujinx.Graphics.GAL/Target.cs | 10 ++ Ryujinx.Graphics.Gpu/Image/Texture.cs | 82 ++++++++-- Ryujinx.Graphics.Gpu/Image/TextureCache.cs | 33 ++++ Ryujinx.Graphics.Gpu/Image/TextureGroup.cs | 152 ++++++++++++++++-- Ryujinx.Graphics.Gpu/Image/TexturePool.cs | 165 ++++++++++++++++++-- Ryujinx.Graphics.Texture/LayoutConverter.cs | 2 +- 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 d2a287493e..e766460fe9 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 e20bd3c84d..711eea2484 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 6c0de53674..363f0f73a7 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 261d060382..c3243cf239 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 12a640e15c..d9b620aae5 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 0348ca014b..717c5c362d 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 b8ec9748bb..09eaf3001c 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 e95af02f4c..dc2aefe401 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; }