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; }