diff --git a/Ryujinx.Graphics.Gpu/Image/Texture.cs b/Ryujinx.Graphics.Gpu/Image/Texture.cs index 7511bdfad..ba7dce7bf 100644 --- a/Ryujinx.Graphics.Gpu/Image/Texture.cs +++ b/Ryujinx.Graphics.Gpu/Image/Texture.cs @@ -302,15 +302,68 @@ namespace Ryujinx.Graphics.Gpu.Image _sequenceNumber = _context.SequenceNumber; - bool modified = _context.PhysicalMemory.GetModifiedRanges(Address, Size, ResourceName.Texture).Length != 0; + (ulong, ulong)[] modifiedRanges = _context.PhysicalMemory.GetModifiedRanges(Address, Size, ResourceName.Texture); - if (!modified && _hasData) + if (modifiedRanges.Length == 0 && _hasData) { return; } ReadOnlySpan data = _context.PhysicalMemory.GetSpan(Address, Size); + // If the texture was 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 (_context.Methods.TextureManager.IsTextureModified(this) && !Info.FormatInfo.Format.IsAstc()) + { + Span gpuData = GetTextureDataFromGpu(); + + ulong endAddress = Address + Size; + + for (int i = 0; i < modifiedRanges.Length; i++) + { + (ulong modifiedAddress, ulong modifiedSize) = modifiedRanges[i]; + + ulong endModifiedAddress = modifiedAddress + modifiedSize; + + if (modifiedAddress < Address) + { + modifiedAddress = Address; + } + + if (endModifiedAddress > endAddress) + { + endModifiedAddress = endAddress; + } + + modifiedSize = endModifiedAddress - modifiedAddress; + + int offset = (int)(modifiedAddress - Address); + int length = (int)modifiedSize; + + data.Slice(offset, length).CopyTo(gpuData.Slice(offset, length)); + } + + data = gpuData; + } + + data = ConvertToHostCompatibleFormat(data); + + HostTexture.SetData(data); + + _hasData = true; + } + + /// + /// Converts texture data to a format and layout that is supported by the host GPU. + /// + /// Data to be converted + /// Converted data + private ReadOnlySpan ConvertToHostCompatibleFormat(ReadOnlySpan data) + { if (Info.IsLinear) { data = LayoutConverter.ConvertLinearStridedToLinear( @@ -360,9 +413,7 @@ namespace Ryujinx.Graphics.Gpu.Image data = decoded; } - HostTexture.SetData(data); - - _hasData = true; + return data; } /// @@ -374,6 +425,19 @@ namespace Ryujinx.Graphics.Gpu.Image /// This may cause data corruption if the memory is already being used for something else on the CPU side. /// public void Flush() + { + _context.PhysicalMemory.Write(Address, GetTextureDataFromGpu()); + } + + /// + /// Gets data from the host GPU. + /// + /// + /// This method should be used to retrieve data that was modified by the host GPU. + /// This is not cheap, avoid doing that unless strictly needed. + /// + /// Host texture data + private Span GetTextureDataFromGpu() { Span data = HostTexture.GetData(); @@ -406,7 +470,7 @@ namespace Ryujinx.Graphics.Gpu.Image data); } - _context.PhysicalMemory.Write(Address, data); + return data; } /// diff --git a/Ryujinx.Graphics.Gpu/Image/TextureManager.cs b/Ryujinx.Graphics.Gpu/Image/TextureManager.cs index 87fb4161e..27cbc6ff8 100644 --- a/Ryujinx.Graphics.Gpu/Image/TextureManager.cs +++ b/Ryujinx.Graphics.Gpu/Image/TextureManager.cs @@ -37,6 +37,7 @@ namespace Ryujinx.Graphics.Gpu.Image private readonly AutoDeleteCache _cache; private readonly HashSet _modified; + private readonly HashSet _modifiedLinear; /// /// Constructs a new instance of the texture manager. @@ -62,6 +63,7 @@ namespace Ryujinx.Graphics.Gpu.Image _cache = new AutoDeleteCache(); _modified = new HashSet(new ReferenceEqualityComparer()); + _modifiedLinear = new HashSet(new ReferenceEqualityComparer()); } /// @@ -519,6 +521,11 @@ namespace Ryujinx.Graphics.Gpu.Image texture = overlap.CreateView(info, sizeInfo, firstLayer, firstLevel); + if (IsTextureModified(overlap)) + { + CacheTextureModified(texture); + } + // The size only matters (and is only really reliable) when the // texture is used on a sampler, because otherwise the size will be // aligned. @@ -554,6 +561,13 @@ namespace Ryujinx.Graphics.Gpu.Image overlap.HostTexture.CopyTo(newView, 0, 0); + // Inherit modification from overlapping texture, do that before replacing + // the view since the replacement operation removes it from the list. + if (IsTextureModified(overlap)) + { + CacheTextureModified(texture); + } + overlap.ReplaceView(texture, overlapInfo, newView); } } @@ -574,6 +588,11 @@ namespace Ryujinx.Graphics.Gpu.Image out int firstLevel)) { overlap.HostTexture.CopyTo(texture.HostTexture, firstLayer, firstLevel); + + if (IsTextureModified(overlap)) + { + CacheTextureModified(texture); + } } } } @@ -595,6 +614,16 @@ namespace Ryujinx.Graphics.Gpu.Image return texture; } + /// + /// Checks if a texture was modified by the host GPU. + /// + /// Texture to be checked + /// True if the texture was modified by the host GPU, false otherwise + public bool IsTextureModified(Texture texture) + { + return _modified.Contains(texture); + } + /// /// Signaled when a cache texture is modified, and adds it to a set to be enumerated when flushing textures. /// @@ -602,6 +631,11 @@ namespace Ryujinx.Graphics.Gpu.Image private void CacheTextureModified(Texture texture) { _modified.Add(texture); + + if (texture.Info.IsLinear) + { + _modifiedLinear.Add(texture); + } } /// @@ -611,6 +645,11 @@ namespace Ryujinx.Graphics.Gpu.Image private void CacheTextureDisposed(Texture texture) { _modified.Remove(texture); + + if (texture.Info.IsLinear) + { + _modifiedLinear.Remove(texture); + } } /// @@ -747,14 +786,12 @@ namespace Ryujinx.Graphics.Gpu.Image /// public void Flush() { - foreach (Texture texture in _modified) + foreach (Texture texture in _modifiedLinear) { - if (texture.Info.IsLinear) - { - texture.Flush(); - } + texture.Flush(); } - _modified.Clear(); + + _modifiedLinear.Clear(); } /// @@ -771,7 +808,6 @@ namespace Ryujinx.Graphics.Gpu.Image texture.Flush(); } } - _modified.Clear(); } ///