diff options
Diffstat (limited to 'Ryujinx.Graphics.Gpu/Image')
| -rw-r--r-- | Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs | 2 | ||||
| -rw-r--r-- | Ryujinx.Graphics.Gpu/Image/Pool.cs | 18 | ||||
| -rw-r--r-- | Ryujinx.Graphics.Gpu/Image/Texture.cs | 218 | ||||
| -rw-r--r-- | Ryujinx.Graphics.Gpu/Image/TextureManager.cs | 141 |
4 files changed, 207 insertions, 172 deletions
diff --git a/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs b/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs index 634f9448..febabdad 100644 --- a/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs +++ b/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs @@ -43,7 +43,7 @@ namespace Ryujinx.Graphics.Gpu.Image oldestTexture.SynchronizeMemory(); - if (oldestTexture.IsModified) + if (oldestTexture.IsModified && !oldestTexture.ConsumeModified()) { // The texture must be flushed if it falls out of the auto delete cache. // Flushes out of the auto delete cache do not trigger write tracking, diff --git a/Ryujinx.Graphics.Gpu/Image/Pool.cs b/Ryujinx.Graphics.Gpu/Image/Pool.cs index 0abf6824..ff7a783b 100644 --- a/Ryujinx.Graphics.Gpu/Image/Pool.cs +++ b/Ryujinx.Graphics.Gpu/Image/Pool.cs @@ -1,4 +1,5 @@ using Ryujinx.Common; +using Ryujinx.Cpu.Tracking; using Ryujinx.Graphics.Gpu.Memory; using System; @@ -34,7 +35,7 @@ namespace Ryujinx.Graphics.Gpu.Image /// </summary> public ulong Size { get; } - private readonly (ulong, ulong)[] _modifiedRanges; + private readonly CpuMultiRegionHandle _memoryTracking; public Pool(GpuContext context, ulong address, int maximumId) { @@ -50,11 +51,7 @@ namespace Ryujinx.Graphics.Gpu.Image Address = address; Size = size; - ulong endAddress = BitUtils.AlignUp(Address + Size, PhysicalMemory.PageSize); - - ulong pagesCount = (endAddress - BitUtils.AlignDown(Address, PhysicalMemory.PageSize)) / PhysicalMemory.PageSize; - - _modifiedRanges = new (ulong, ulong)[pagesCount]; + _memoryTracking = context.PhysicalMemory.BeginGranularTracking(address, size); } /// <summary> @@ -71,12 +68,8 @@ namespace Ryujinx.Graphics.Gpu.Image /// </summary> public void SynchronizeMemory() { - int count = Context.PhysicalMemory.QueryModified(Address, Size, ResourceName.TexturePool, _modifiedRanges); - - for (int index = 0; index < count; index++) + _memoryTracking.QueryModified((ulong mAddress, ulong mSize) => { - (ulong mAddress, ulong mSize) = _modifiedRanges[index]; - if (mAddress < Address) { mAddress = Address; @@ -90,7 +83,7 @@ namespace Ryujinx.Graphics.Gpu.Image } InvalidateRangeImpl(mAddress, mSize); - } + }); } private void InvalidateRangeInternal(ulong offset, int size) @@ -152,6 +145,7 @@ namespace Ryujinx.Graphics.Gpu.Image Items = null; } + _memoryTracking.Dispose(); } } }
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Image/Texture.cs b/Ryujinx.Graphics.Gpu/Image/Texture.cs index b1698363..e3c3a30a 100644 --- a/Ryujinx.Graphics.Gpu/Image/Texture.cs +++ b/Ryujinx.Graphics.Gpu/Image/Texture.cs @@ -1,9 +1,10 @@ using Ryujinx.Common; using Ryujinx.Common.Logging; +using Ryujinx.Cpu.Tracking; using Ryujinx.Graphics.GAL; -using Ryujinx.Graphics.Gpu.Memory; using Ryujinx.Graphics.Texture; using Ryujinx.Graphics.Texture.Astc; +using Ryujinx.Memory.Range; using System; using System.Collections.Generic; using System.Diagnostics; @@ -40,12 +41,10 @@ namespace Ryujinx.Graphics.Gpu.Image public TextureScaleMode ScaleMode { get; private set; } /// <summary> - /// Set when a texture has been modified since it was last flushed. + /// Set when a texture has been modified by the Host GPU since it was last flushed. /// </summary> public bool IsModified { get; internal set; } - private bool _everModified; - private int _depth; private int _layers; private int _firstLayer; @@ -56,6 +55,8 @@ namespace Ryujinx.Graphics.Gpu.Image private ITexture _arrayViewTexture; private Target _arrayViewTarget; + private ITexture _flushHostTexture; + private Texture _viewStorage; private List<Texture> _views; @@ -71,11 +72,6 @@ namespace Ryujinx.Graphics.Gpu.Image public LinkedListNode<Texture> CacheNode { get; set; } /// <summary> - /// Event to fire when texture data is modified by the GPU. - /// </summary> - public event Action<Texture> Modified; - - /// <summary> /// Event to fire when texture data is disposed. /// </summary> public event Action<Texture> Disposed; @@ -95,12 +91,10 @@ namespace Ryujinx.Graphics.Gpu.Image /// </summary> public ulong Size => (ulong)_sizeInfo.TotalSize; - private (ulong, ulong)[] _modifiedRanges; + private CpuRegionHandle _memoryTracking; private int _referenceCount; - private int _sequenceNumber; - /// <summary> /// Constructs a new instance of the cached GPU texture. /// </summary> @@ -159,8 +153,6 @@ namespace Ryujinx.Graphics.Gpu.Image _context = context; _sizeInfo = sizeInfo; - _modifiedRanges = new (ulong, ulong)[(sizeInfo.TotalSize / PhysicalMemory.PageSize) + 1]; - SetInfo(info); _viewStorage = this; @@ -176,6 +168,8 @@ namespace Ryujinx.Graphics.Gpu.Image /// <param name="withData">True if the texture is to be initialized with data</param> public void InitializeData(bool isView, bool withData = false) { + _memoryTracking = _context.PhysicalMemory.BeginTracking(Address, Size); + if (withData) { Debug.Assert(!isView); @@ -246,6 +240,8 @@ namespace Ryujinx.Graphics.Gpu.Image /// <param name="texture">The child texture</param> private void AddView(Texture texture) { + DisableMemoryTracking(); + _views.Add(texture); texture._viewStorage = this; @@ -314,6 +310,16 @@ namespace Ryujinx.Graphics.Gpu.Image } /// <summary> + /// Disables memory tracking on this texture. Currently used for view containers, as we assume their views are covering all memory regions. + /// Textures with disabled memory tracking also cannot flush in most circumstances. + /// </summary> + public void DisableMemoryTracking() + { + _memoryTracking?.Dispose(); + _memoryTracking = null; + } + + /// <summary> /// Recreates the texture storage (or view, in the case of child textures) of this texture. /// This allows recreating the texture with a new size. /// A copy is automatically performed from the old to the new texture. @@ -327,7 +333,7 @@ namespace Ryujinx.Graphics.Gpu.Image { RecreateStorageOrView( BitUtils.DivRoundUp(width * Info.FormatInfo.BlockWidth, blockWidth), - BitUtils.DivRoundUp(height * Info.FormatInfo.BlockHeight, blockHeight), + BitUtils.DivRoundUp(height * Info.FormatInfo.BlockHeight, blockHeight), depthOrLayers); } @@ -453,6 +459,33 @@ namespace Ryujinx.Graphics.Gpu.Image } /// <summary> + /// Copy the host texture to a scaled one. If a texture is not provided, create it with the given scale. + /// </summary> + /// <param name="scale">Scale factor</param> + /// <param name="storage">Texture to use instead of creating one</param> + /// <returns>A host texture containing a scaled version of this texture</returns> + private ITexture GetScaledHostTexture(float scale, ITexture storage = null) + { + if (storage == null) + { + TextureCreateInfo createInfo = TextureManager.GetCreateInfo(Info, _context.Capabilities); + + storage = _context.Renderer.CreateTexture(createInfo, scale); + } + + if (Info.Target == Target.Texture2DArray) + { + CopyArrayScaled(storage); + } + else + { + HostTexture.CopyTo(storage, new Extents2D(0, 0, HostTexture.Width, HostTexture.Height), new Extents2D(0, 0, storage.Width, storage.Height), true); + } + + return storage; + } + + /// <summary> /// Sets the Scale Factor on this texture, and immediately recreates it at the correct size. /// When a texture is resized, a scaled copy is performed from the old texture to the new one, to ensure no data is lost. /// If scale is equivalent, this only propagates the blacklisted/scaled mode. @@ -474,20 +507,10 @@ namespace Ryujinx.Graphics.Gpu.Image if (ScaleFactor != scale) { Logger.Debug?.Print(LogClass.Gpu, $"Rescaling {Info.Width}x{Info.Height} {Info.FormatInfo.Format.ToString()} to ({ScaleFactor} to {scale}). "); - TextureCreateInfo createInfo = TextureManager.GetCreateInfo(Info, _context.Capabilities); ScaleFactor = scale; - ITexture newStorage = _context.Renderer.CreateTexture(createInfo, ScaleFactor); - - if (Info.Target == Target.Texture2DArray) - { - CopyArrayScaled(newStorage); - } - else - { - HostTexture.CopyTo(newStorage, new Extents2D(0, 0, HostTexture.Width, HostTexture.Height), new Extents2D(0, 0, newStorage.Width, newStorage.Height), true); - } + ITexture newStorage = GetScaledHostTexture(ScaleFactor); Logger.Debug?.Print(LogClass.Gpu, $" Copy performed: {HostTexture.Width}x{HostTexture.Height} to {newStorage.Width}x{newStorage.Height}"); @@ -525,10 +548,17 @@ namespace Ryujinx.Graphics.Gpu.Image /// Checks if the memory for this texture was modified, and returns true if it was. /// The modified flags are consumed as a result. /// </summary> + /// <remarks> + /// If there is no memory tracking for this texture, it will always report as modified. + /// </remarks> /// <returns>True if the texture was modified, false otherwise.</returns> public bool ConsumeModified() { - return _context.PhysicalMemory.QueryModified(Address, Size, ResourceName.Texture, _modifiedRanges) > 0; + bool wasDirty = _memoryTracking?.Dirty ?? true; + + _memoryTracking?.Reprotect(); + + return wasDirty; } /// <summary> @@ -540,20 +570,14 @@ namespace Ryujinx.Graphics.Gpu.Image /// </summary> public void SynchronizeMemory() { - // Texture buffers are not handled here, instead they are invalidated (if modified) - // when the texture is bound. This is handled by the buffer manager. - if ((_sequenceNumber == _context.SequenceNumber && _hasData) || Info.Target == Target.TextureBuffer) + if (Info.Target == Target.TextureBuffer) { return; } - _sequenceNumber = _context.SequenceNumber; - - int modifiedCount = _context.PhysicalMemory.QueryModified(Address, Size, ResourceName.Texture, _modifiedRanges); - if (_hasData) { - if (modifiedCount == 0) + if (_memoryTracking?.Dirty != true) { return; } @@ -561,51 +585,27 @@ namespace Ryujinx.Graphics.Gpu.Image BlacklistScale(); } - ReadOnlySpan<byte> data = _context.PhysicalMemory.GetSpan(Address, (int)Size); - - // If the texture was ever modified by the host GPU, we do partial invalidation - // of the texture by getting GPU data and merging in the pages of memory - // that were modified. - // Note that if ASTC is not supported by the GPU we can't read it back since - // it will use a different format. Since applications shouldn't be writing - // ASTC textures from the GPU anyway, ignoring it should be safe. - if (_everModified && !Info.FormatInfo.Format.IsAstc()) - { - Span<byte> gpuData = GetTextureDataFromGpu(true); - - ulong endAddress = Address + Size; + _memoryTracking?.Reprotect(); - for (int i = 0; i < modifiedCount; i++) - { - (ulong modifiedAddress, ulong modifiedSize) = _modifiedRanges[i]; - - ulong endModifiedAddress = modifiedAddress + modifiedSize; + ReadOnlySpan<byte> data = _context.PhysicalMemory.GetSpan(Address, (int)Size); - if (modifiedAddress < Address) - { - modifiedAddress = Address; - } + IsModified = false; - if (endModifiedAddress > endAddress) - { - endModifiedAddress = endAddress; - } + data = ConvertToHostCompatibleFormat(data); - modifiedSize = endModifiedAddress - modifiedAddress; + HostTexture.SetData(data); - int offset = (int)(modifiedAddress - Address); - int length = (int)modifiedSize; + _hasData = true; + } - data.Slice(offset, length).CopyTo(gpuData.Slice(offset, length)); - } + public void SetData(ReadOnlySpan<byte> data) + { + BlacklistScale(); - data = gpuData; - } + _memoryTracking?.Reprotect(); IsModified = false; - data = ConvertToHostCompatibleFormat(data); - HostTexture.SetData(data); _hasData = true; @@ -699,6 +699,39 @@ namespace Ryujinx.Graphics.Gpu.Image } } + + /// <summary> + /// Flushes the texture data, to be called from an external thread. + /// The host backend must ensure that we have shared access to the resource from this thread. + /// This is used when flushing from memory access handlers. + /// </summary> + public void ExternalFlush(ulong address, ulong size) + { + if (!IsModified || _memoryTracking == null) + { + return; + } + + _context.Renderer.BackgroundContextAction(() => + { + IsModified = false; + if (Info.FormatInfo.Format.IsAstc()) + { + // ASTC textures are not in their original format, so cannot be flushed. + return; + } + + ITexture texture = HostTexture; + if (ScaleFactor != 1f) + { + // If needed, create a texture to flush back to host at 1x scale. + texture = _flushHostTexture = GetScaledHostTexture(1f, _flushHostTexture); + } + + _context.PhysicalMemory.WriteUntracked(Address, GetTextureDataFromGpu(false, texture)); + }); + } + /// <summary> /// Gets data from the host GPU. /// </summary> @@ -707,25 +740,32 @@ namespace Ryujinx.Graphics.Gpu.Image /// This is not cheap, avoid doing that unless strictly needed. /// </remarks> /// <returns>Host texture data</returns> - private Span<byte> GetTextureDataFromGpu(bool blacklist) + private Span<byte> GetTextureDataFromGpu(bool blacklist, ITexture texture = null) { Span<byte> data; - if (blacklist) - { - BlacklistScale(); - data = HostTexture.GetData(); - } - else if (ScaleFactor != 1f) + if (texture != null) { - float scale = ScaleFactor; - SetScale(1f); - data = HostTexture.GetData(); - SetScale(scale); + data = texture.GetData(); } else { - data = HostTexture.GetData(); + if (blacklist) + { + BlacklistScale(); + data = HostTexture.GetData(); + } + else if (ScaleFactor != 1f) + { + float scale = ScaleFactor; + SetScale(1f); + data = HostTexture.GetData(); + SetScale(scale); + } + else + { + data = HostTexture.GetData(); + } } if (Info.IsLinear) @@ -967,6 +1007,7 @@ namespace Ryujinx.Graphics.Gpu.Image /// <param name="firstLevel">The first level of the view</param> public void ReplaceView(Texture parent, TextureInfo info, ITexture hostTexture, int firstLayer, int firstLevel) { + parent._viewStorage.SynchronizeMemory(); ReplaceStorage(hostTexture); _firstLayer = parent._firstLayer + firstLayer; @@ -994,14 +1035,13 @@ namespace Ryujinx.Graphics.Gpu.Image public void SignalModified() { IsModified = true; - _everModified = true; - - Modified?.Invoke(this); if (_viewStorage != this) { _viewStorage.SignalModified(); } + + _memoryTracking?.RegisterAction(ExternalFlush); } /// <summary> @@ -1109,6 +1149,9 @@ namespace Ryujinx.Graphics.Gpu.Image _arrayViewTexture?.Release(); _arrayViewTexture = null; + + _flushHostTexture?.Release(); + _flushHostTexture = null; } /// <summary> @@ -1118,6 +1161,10 @@ namespace Ryujinx.Graphics.Gpu.Image public void Unmapped() { IsModified = false; // We shouldn't flush this texture, as its memory is no longer mapped. + + CpuRegionHandle tracking = _memoryTracking; + tracking?.Reprotect(); + tracking?.RegisterAction(null); } /// <summary> @@ -1128,6 +1175,7 @@ namespace Ryujinx.Graphics.Gpu.Image DisposeTextures(); Disposed?.Invoke(this); + _memoryTracking?.Dispose(); } } }
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Image/TextureManager.cs b/Ryujinx.Graphics.Gpu/Image/TextureManager.cs index dd499b78..27292f56 100644 --- a/Ryujinx.Graphics.Gpu/Image/TextureManager.cs +++ b/Ryujinx.Graphics.Gpu/Image/TextureManager.cs @@ -4,8 +4,8 @@ using Ryujinx.Graphics.Gpu.Image; using Ryujinx.Graphics.Gpu.Memory; using Ryujinx.Graphics.Gpu.State; using Ryujinx.Graphics.Texture; +using Ryujinx.Memory.Range; using System; -using System.Collections.Generic; namespace Ryujinx.Graphics.Gpu.Image { @@ -51,9 +51,6 @@ namespace Ryujinx.Graphics.Gpu.Image private readonly AutoDeleteCache _cache; - private readonly HashSet<Texture> _modified; - private readonly HashSet<Texture> _modifiedLinear; - /// <summary> /// The scaling factor applied to all currently bound render targets. /// </summary> @@ -82,9 +79,6 @@ namespace Ryujinx.Graphics.Gpu.Image _overlapInfo = new OverlapInfo[OverlapsBufferInitialCapacity]; _cache = new AutoDeleteCache(); - - _modified = new HashSet<Texture>(new ReferenceEqualityComparer<Texture>()); - _modifiedLinear = new HashSet<Texture>(new ReferenceEqualityComparer<Texture>()); } /// <summary> @@ -735,8 +729,9 @@ namespace Ryujinx.Graphics.Gpu.Image for (int index = 0; index < overlapsCount; index++) { Texture overlap = _textureOverlaps[index]; + TextureViewCompatibility overlapCompatibility = overlap.IsViewCompatible(info, size, out int firstLayer, out int firstLevel); - if (overlap.IsViewCompatible(info, size, out int firstLayer, out int firstLevel) == TextureViewCompatibility.Full) + if (overlapCompatibility == TextureViewCompatibility.Full) { if (!isSamplerTexture) { @@ -745,7 +740,7 @@ namespace Ryujinx.Graphics.Gpu.Image texture = overlap.CreateView(info, sizeInfo, firstLayer, firstLevel); - if (IsTextureModified(overlap)) + if (overlap.IsModified) { texture.SignalModified(); } @@ -759,6 +754,11 @@ namespace Ryujinx.Graphics.Gpu.Image } break; + } + else if (overlapCompatibility == TextureViewCompatibility.CopyOnly) + { + // TODO: Copy rules for targets created after the container texture. See below. + overlap.DisableMemoryTracking(); } } @@ -849,7 +849,7 @@ namespace Ryujinx.Graphics.Gpu.Image // Inherit modification from overlapping texture, do that before replacing // the view since the replacement operation removes it from the list. - if (IsTextureModified(overlap)) + if (overlap.IsModified) { texture.SignalModified(); } @@ -859,8 +859,13 @@ namespace Ryujinx.Graphics.Gpu.Image // If the texture is a 3D texture, we need to additionally copy any slice // of the 3D texture to the newly created 3D texture. - if (info.Target == Target.Texture3D) + if (info.Target == Target.Texture3D && viewCompatible > 0) { + // TODO: This copy can currently only happen when the 3D texture is created. + // If a game clears and redraws the slices, we won't be able to copy the new data to the 3D texture. + // Disable tracking to try keep at least the original data in there for as long as possible. + texture.DisableMemoryTracking(); + for (int index = 0; index < viewCompatible; index++) { Texture overlap = _textureOverlaps[index]; @@ -872,7 +877,7 @@ namespace Ryujinx.Graphics.Gpu.Image overlap.HostTexture.CopyTo(texture.HostTexture, oInfo.FirstLayer, oInfo.FirstLevel); - if (IsTextureModified(overlap)) + if (overlap.IsModified) { texture.SignalModified(); } @@ -886,8 +891,6 @@ namespace Ryujinx.Graphics.Gpu.Image if (!isSamplerTexture) { _cache.Add(texture); - texture.Modified += CacheTextureModified; - texture.Disposed += CacheTextureDisposed; } lock (_textures) @@ -901,42 +904,65 @@ namespace Ryujinx.Graphics.Gpu.Image } /// <summary> - /// Checks if a texture was modified by the host GPU. + /// Tries to find an existing texture matching the given buffer copy destination. If none is found, returns null. /// </summary> - /// <param name="texture">Texture to be checked</param> - /// <returns>True if the texture was modified by the host GPU, false otherwise</returns> - public bool IsTextureModified(Texture texture) + /// <param name="tex">The texture information</param> + /// <param name="cbp">The copy buffer parameters</param> + /// <param name="swizzle">The copy buffer swizzle</param> + /// <param name="linear">True if the texture has a linear layout, false otherwise</param> + /// <returns>A matching texture, or null if there is no match</returns> + public Texture FindTexture(CopyBufferTexture tex, CopyBufferParams cbp, CopyBufferSwizzle swizzle, bool linear) { - return _modified.Contains(texture); - } + ulong address = _context.MemoryManager.Translate(cbp.DstAddress.Pack()); - /// <summary> - /// Signaled when a cache texture is modified, and adds it to a set to be enumerated when flushing textures. - /// </summary> - /// <param name="texture">The texture that was modified.</param> - private void CacheTextureModified(Texture texture) - { - texture.IsModified = true; - _modified.Add(texture); - - if (texture.Info.IsLinear) + if (address == MemoryManager.BadAddress) { - _modifiedLinear.Add(texture); + return null; } - } - /// <summary> - /// Signaled when a cache texture is disposed, so it can be removed from the set of modified textures if present. - /// </summary> - /// <param name="texture">The texture that was diosposed.</param> - private void CacheTextureDisposed(Texture texture) - { - _modified.Remove(texture); + int bpp = swizzle.UnpackDstComponentsCount() * swizzle.UnpackComponentSize(); - if (texture.Info.IsLinear) + int addressMatches = _textures.FindOverlaps(address, ref _textureOverlaps); + + for (int i = 0; i < addressMatches; i++) { - _modifiedLinear.Remove(texture); + Texture texture = _textureOverlaps[i]; + FormatInfo format = texture.Info.FormatInfo; + + if (texture.Info.DepthOrLayers > 1) + { + continue; + } + + bool match; + + if (linear) + { + // Size is not available for linear textures. Use the stride and end of the copy region instead. + + match = texture.Info.IsLinear && texture.Info.Stride == cbp.DstStride && tex.RegionY + cbp.YCount <= texture.Info.Height; + } + else + { + // Bpp may be a mismatch between the target texture and the param. + // Due to the way linear strided and block layouts work, widths can be multiplied by Bpp for comparison. + // Note: tex.Width is the aligned texture size. Prefer param.XCount, as the destination should be a texture with that exact size. + + bool sizeMatch = cbp.XCount * bpp == texture.Info.Width * format.BytesPerPixel && tex.Height == texture.Info.Height; + bool formatMatch = !texture.Info.IsLinear && + texture.Info.GobBlocksInY == tex.MemoryLayout.UnpackGobBlocksInY() && + texture.Info.GobBlocksInZ == tex.MemoryLayout.UnpackGobBlocksInZ(); + + match = sizeMatch && formatMatch; + } + + if (match) + { + return texture; + } } + + return null; } /// <summary> @@ -1085,38 +1111,6 @@ namespace Ryujinx.Graphics.Gpu.Image } /// <summary> - /// Flushes all the textures in the cache that have been modified since the last call. - /// </summary> - public void Flush() - { - foreach (Texture texture in _modifiedLinear) - { - if (texture.IsModified) - { - texture.Flush(); - } - } - - _modifiedLinear.Clear(); - } - - /// <summary> - /// Flushes the textures in the cache inside a given range that have been modified since the last call. - /// </summary> - /// <param name="address">The range start address</param> - /// <param name="size">The range size</param> - public void Flush(ulong address, ulong size) - { - foreach (Texture texture in _modified) - { - if (texture.OverlapsWith(address, size) && texture.IsModified) - { - texture.Flush(); - } - } - } - - /// <summary> /// Removes a texture from the cache. /// </summary> /// <remarks> @@ -1142,7 +1136,6 @@ namespace Ryujinx.Graphics.Gpu.Image { foreach (Texture texture in _textures) { - _modified.Remove(texture); texture.Dispose(); } } |
