diff options
Diffstat (limited to 'Ryujinx.Graphics.Gpu/Image')
| -rw-r--r-- | Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs | 31 | ||||
| -rw-r--r-- | Ryujinx.Graphics.Gpu/Image/Texture.cs | 257 | ||||
| -rw-r--r-- | Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs | 75 | ||||
| -rw-r--r-- | Ryujinx.Graphics.Gpu/Image/TextureManager.cs | 188 | ||||
| -rw-r--r-- | Ryujinx.Graphics.Gpu/Image/TextureViewCompatibility.cs | 13 |
5 files changed, 441 insertions, 123 deletions
diff --git a/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs b/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs index d66eab93..634f9448 100644 --- a/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs +++ b/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs @@ -1,3 +1,4 @@ +using Ryujinx.Common.Logging; using System.Collections; using System.Collections.Generic; @@ -40,6 +41,16 @@ namespace Ryujinx.Graphics.Gpu.Image { Texture oldestTexture = _textures.First.Value; + oldestTexture.SynchronizeMemory(); + + if (oldestTexture.IsModified) + { + // 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, + // as it is expected that other overlapping textures exist that have more up-to-date contents. + oldestTexture.Flush(false); + } + _textures.RemoveFirst(); oldestTexture.DecrementReferenceCount(); @@ -74,6 +85,26 @@ namespace Ryujinx.Graphics.Gpu.Image } } + public bool Remove(Texture texture, bool flush) + { + if (texture.CacheNode == null) + { + return false; + } + + // Remove our reference to this texture. + if (flush && texture.IsModified) + { + texture.Flush(false); + } + + _textures.Remove(texture.CacheNode); + + texture.CacheNode = null; + + return texture.DecrementReferenceCount(); + } + public IEnumerator<Texture> GetEnumerator() { return _textures.GetEnumerator(); diff --git a/Ryujinx.Graphics.Gpu/Image/Texture.cs b/Ryujinx.Graphics.Gpu/Image/Texture.cs index 50b184d5..fe129f52 100644 --- a/Ryujinx.Graphics.Gpu/Image/Texture.cs +++ b/Ryujinx.Graphics.Gpu/Image/Texture.cs @@ -39,6 +39,13 @@ namespace Ryujinx.Graphics.Gpu.Image /// </summary> public TextureScaleMode ScaleMode { get; private set; } + /// <summary> + /// Set when a texture has been modified since it was last flushed. + /// </summary> + public bool IsModified { get; internal set; } + + private bool _everModified; + private int _depth; private int _layers; private int _firstLayer; @@ -121,7 +128,7 @@ namespace Ryujinx.Graphics.Gpu.Image ScaleFactor = scaleFactor; ScaleMode = scaleMode; - _hasData = true; + InitializeData(true); } /// <summary> @@ -137,16 +144,6 @@ namespace Ryujinx.Graphics.Gpu.Image ScaleMode = scaleMode; InitializeTexture(context, info, sizeInfo); - - TextureCreateInfo createInfo = TextureManager.GetCreateInfo(info, context.Capabilities); - - HostTexture = _context.Renderer.CreateTexture(createInfo, ScaleFactor); - - if (scaleMode == TextureScaleMode.Scaled) - { - SynchronizeMemory(); // Load the data and then scale it up. - SetScale(GraphicsConfig.ResScale); - } } /// <summary> @@ -172,6 +169,47 @@ namespace Ryujinx.Graphics.Gpu.Image } /// <summary> + /// Initializes the data for a texture. Can optionally initialize the texture with or without data. + /// If the texture is a view, it will initialize memory tracking to be non-dirty. + /// </summary> + /// <param name="isView">True if the texture is a view, false otherwise</param> + /// <param name="withData">True if the texture is to be initialized with data</param> + public void InitializeData(bool isView, bool withData = false) + { + if (withData) + { + Debug.Assert(!isView); + + TextureCreateInfo createInfo = TextureManager.GetCreateInfo(Info, _context.Capabilities); + HostTexture = _context.Renderer.CreateTexture(createInfo, ScaleFactor); + + SynchronizeMemory(); // Load the data. + if (ScaleMode == TextureScaleMode.Scaled) + { + SetScale(GraphicsConfig.ResScale); // Scale the data up. + } + } + else + { + // Don't update this texture the next time we synchronize. + ConsumeModified(); + _hasData = true; + + if (!isView) + { + if (ScaleMode == TextureScaleMode.Scaled) + { + // Don't need to start at 1x as there is no data to scale, just go straight to the target scale. + ScaleFactor = GraphicsConfig.ResScale; + } + + TextureCreateInfo createInfo = TextureManager.GetCreateInfo(Info, _context.Capabilities); + HostTexture = _context.Renderer.CreateTexture(createInfo, ScaleFactor); + } + } + } + + /// <summary> /// Create a texture view from this texture. /// A texture view is defined as a child texture, from a sub-range of their parent texture. /// For example, the initial layer and mipmap level of the view can be defined, so the texture @@ -238,6 +276,9 @@ namespace Ryujinx.Graphics.Gpu.Image /// <param name="depthOrLayers">The new texture depth (for 3D textures) or layers (for layered textures)</param> public void ChangeSize(int width, int height, int depthOrLayers) { + int blockWidth = Info.FormatInfo.BlockWidth; + int blockHeight = Info.FormatInfo.BlockHeight; + width <<= _firstLevel; height <<= _firstLevel; @@ -250,7 +291,7 @@ namespace Ryujinx.Graphics.Gpu.Image depthOrLayers = _viewStorage.Info.DepthOrLayers; } - _viewStorage.RecreateStorageOrView(width, height, depthOrLayers); + _viewStorage.RecreateStorageOrView(width, height, blockWidth, blockHeight, depthOrLayers); foreach (Texture view in _viewStorage._views) { @@ -268,7 +309,7 @@ namespace Ryujinx.Graphics.Gpu.Image viewDepthOrLayers = view.Info.DepthOrLayers; } - view.RecreateStorageOrView(viewWidth, viewHeight, viewDepthOrLayers); + view.RecreateStorageOrView(viewWidth, viewHeight, blockWidth, blockHeight, viewDepthOrLayers); } } @@ -279,6 +320,24 @@ namespace Ryujinx.Graphics.Gpu.Image /// </summary> /// <param name="width">The new texture width</param> /// <param name="height">The new texture height</param> + /// <param name="width">The block width related to the given width</param> + /// <param name="height">The block height related to the given height</param> + /// <param name="depthOrLayers">The new texture depth (for 3D textures) or layers (for layered textures)</param> + private void RecreateStorageOrView(int width, int height, int blockWidth, int blockHeight, int depthOrLayers) + { + RecreateStorageOrView( + BitUtils.DivRoundUp(width * Info.FormatInfo.BlockWidth, blockWidth), + BitUtils.DivRoundUp(height * Info.FormatInfo.BlockHeight, blockHeight), + depthOrLayers); + } + + /// <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. + /// </summary> + /// <param name="width">The new texture width</param> + /// <param name="height">The new texture height</param> /// <param name="depthOrLayers">The new texture depth (for 3D textures) or layers (for layered textures)</param> private void RecreateStorageOrView(int width, int height, int depthOrLayers) { @@ -388,8 +447,8 @@ namespace Ryujinx.Graphics.Gpu.Image from.CopyTo(to, new Extents2D(0, 0, from.Width, from.Height), new Extents2D(0, 0, to.Width, to.Height), true); - from.Dispose(); - to.Dispose(); + from.Release(); + to.Release(); } } @@ -463,6 +522,16 @@ namespace Ryujinx.Graphics.Gpu.Image } /// <summary> + /// Checks if the memory for this texture was modified, and returns true if it was. + /// The modified flags are consumed as a result. + /// </summary> + /// <returns>True if the texture was modified, false otherwise.</returns> + public bool ConsumeModified() + { + return _context.PhysicalMemory.QueryModified(Address, Size, ResourceName.Texture, _modifiedRanges) > 0; + } + + /// <summary> /// Synchronizes guest and host memory. /// This will overwrite the texture data with the texture data on the guest memory, if a CPU /// modification is detected. @@ -494,15 +563,15 @@ namespace Ryujinx.Graphics.Gpu.Image ReadOnlySpan<byte> data = _context.PhysicalMemory.GetSpan(Address, (int)Size); - // If the texture was modified by the host GPU, we do partial invalidation + // 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 (_context.Methods.TextureManager.IsTextureModified(this) && !Info.FormatInfo.Format.IsAstc()) + if (_everModified && !Info.FormatInfo.Format.IsAstc()) { - Span<byte> gpuData = GetTextureDataFromGpu(); + Span<byte> gpuData = GetTextureDataFromGpu(true); ulong endAddress = Address + Size; @@ -533,6 +602,8 @@ namespace Ryujinx.Graphics.Gpu.Image data = gpuData; } + IsModified = false; + data = ConvertToHostCompatibleFormat(data); HostTexture.SetData(data); @@ -607,10 +678,24 @@ namespace Ryujinx.Graphics.Gpu.Image /// Be aware that this is an expensive operation, avoid calling it unless strictly needed. /// This may cause data corruption if the memory is already being used for something else on the CPU side. /// </summary> - public void Flush() + /// <param name="tracked">Whether or not the flush triggers write tracking. If it doesn't, the texture will not be blacklisted for scaling either.</param> + public void Flush(bool tracked = true) { - BlacklistScale(); - _context.PhysicalMemory.Write(Address, GetTextureDataFromGpu()); + IsModified = false; + + if (Info.FormatInfo.Format.IsAstc()) + { + return; // Flushing this format is not supported, as it may have been converted to another host format. + } + + if (tracked) + { + _context.PhysicalMemory.Write(Address, GetTextureDataFromGpu(tracked)); + } + else + { + _context.PhysicalMemory.WriteUntracked(Address, GetTextureDataFromGpu(tracked)); + } } /// <summary> @@ -621,10 +706,26 @@ 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() + private Span<byte> GetTextureDataFromGpu(bool blacklist) { - BlacklistScale(); - Span<byte> data = HostTexture.GetData(); + Span<byte> data; + + 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) { @@ -713,29 +814,10 @@ namespace Ryujinx.Graphics.Gpu.Image /// <param name="size">Texture view size</param> /// <param name="firstLayer">Texture view initial layer on this texture</param> /// <param name="firstLevel">Texture view first mipmap level on this texture</param> - /// <returns>True if a view with the given parameters can be created from this texture, false otherwise</returns> - public bool IsViewCompatible( - TextureInfo info, - ulong size, - out int firstLayer, - out int firstLevel) - { - return IsViewCompatible(info, size, isCopy: false, out firstLayer, out firstLevel); - } - - /// <summary> - /// Check if it's possible to create a view, with the given parameters, from this texture. - /// </summary> - /// <param name="info">Texture view information</param> - /// <param name="size">Texture view size</param> - /// <param name="isCopy">True to check for copy compability, instead of view compatibility</param> - /// <param name="firstLayer">Texture view initial layer on this texture</param> - /// <param name="firstLevel">Texture view first mipmap level on this texture</param> - /// <returns>True if a view with the given parameters can be created from this texture, false otherwise</returns> - public bool IsViewCompatible( + /// <returns>The level of compatiblilty a view with the given parameters created from this texture has</returns> + public TextureViewCompatibility IsViewCompatible( TextureInfo info, ulong size, - bool isCopy, out int firstLayer, out int firstLevel) { @@ -745,38 +827,46 @@ namespace Ryujinx.Graphics.Gpu.Image firstLayer = 0; firstLevel = 0; - return false; + return TextureViewCompatibility.Incompatible; } int offset = (int)(info.Address - Address); if (!_sizeInfo.FindView(offset, (int)size, out firstLayer, out firstLevel)) { - return false; + return TextureViewCompatibility.Incompatible; } if (!TextureCompatibility.ViewLayoutCompatible(Info, info, firstLevel)) { - return false; + return TextureViewCompatibility.Incompatible; } if (!TextureCompatibility.ViewFormatCompatible(Info, info)) { - return false; + return TextureViewCompatibility.Incompatible; } - if (!TextureCompatibility.ViewSizeMatches(Info, info, firstLevel, isCopy)) - { - return false; - } + TextureViewCompatibility result = TextureViewCompatibility.Full; - if (!TextureCompatibility.ViewTargetCompatible(Info, info, isCopy)) - { - return false; - } + result = TextureCompatibility.PropagateViewCompatibility(result, TextureCompatibility.ViewSizeMatches(Info, info, firstLevel)); + result = TextureCompatibility.PropagateViewCompatibility(result, TextureCompatibility.ViewTargetCompatible(Info, info)); + + return (Info.SamplesInX == info.SamplesInX && + Info.SamplesInY == info.SamplesInY) ? result : TextureViewCompatibility.Incompatible; + } - return Info.SamplesInX == info.SamplesInX && - Info.SamplesInY == info.SamplesInY; + /// <summary> + /// Checks if the view format is compatible with this texture format. + /// In general, the formats are considered compatible if the bytes per pixel values are equal, + /// but there are more complex rules for some formats, like compressed or depth-stencil formats. + /// This follows the host API copy compatibility rules. + /// </summary> + /// <param name="info">Texture information of the texture view</param> + /// <returns>True if the formats are compatible, false otherwise</returns> + private bool ViewFormatCompatible(TextureInfo info) + { + return TextureCompatibility.FormatCompatible(Info.FormatInfo, info.FormatInfo); } /// <summary> @@ -902,7 +992,15 @@ namespace Ryujinx.Graphics.Gpu.Image /// </summary> public void SignalModified() { + IsModified = true; + _everModified = true; + Modified?.Invoke(this); + + if (_viewStorage != this) + { + _viewStorage.SignalModified(); + } } /// <summary> @@ -928,6 +1026,29 @@ namespace Ryujinx.Graphics.Gpu.Image } /// <summary> + /// Determine if any of our child textures are compaible as views of the given texture. + /// </summary> + /// <param name="texture">The texture to check against</param> + /// <returns>True if any child is view compatible, false otherwise</returns> + public bool HasViewCompatibleChild(Texture texture) + { + if (_viewStorage != this || _views.Count == 0) + { + return false; + } + + foreach (Texture view in _views) + { + if (texture.IsViewCompatible(view.Info, view.Size, out int _, out int _) != TextureViewCompatibility.Incompatible) + { + return true; + } + } + + return false; + } + + /// <summary> /// Increments the texture reference count. /// </summary> public void IncrementReferenceCount() @@ -939,7 +1060,8 @@ namespace Ryujinx.Graphics.Gpu.Image /// Decrements the texture reference count. /// When the reference count hits zero, the texture may be deleted and can't be used anymore. /// </summary> - public void DecrementReferenceCount() + /// <returns>True if the texture is now referenceless, false otherwise</returns> + public bool DecrementReferenceCount() { int newRefCount = --_referenceCount; @@ -956,6 +1078,8 @@ namespace Ryujinx.Graphics.Gpu.Image Debug.Assert(newRefCount >= 0); DeleteIfNotUsed(); + + return newRefCount <= 0; } /// <summary> @@ -980,13 +1104,22 @@ namespace Ryujinx.Graphics.Gpu.Image /// </summary> private void DisposeTextures() { - HostTexture.Dispose(); + HostTexture.Release(); - _arrayViewTexture?.Dispose(); + _arrayViewTexture?.Release(); _arrayViewTexture = null; } /// <summary> + /// Called when the memory for this texture has been unmapped. + /// Calls are from non-gpu threads. + /// </summary> + public void Unmapped() + { + IsModified = false; // We shouldn't flush this texture, as its memory is no longer mapped. + } + + /// <summary> /// Performs texture disposal, deleting the texture. /// </summary> public void Dispose() diff --git a/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs b/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs index cc7b0dc2..e8e3c2c2 100644 --- a/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs +++ b/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs @@ -147,28 +147,51 @@ namespace Ryujinx.Graphics.Gpu.Image } /// <summary> - /// Checks if the view sizes of a two given texture informations match. + /// Obtain the minimum compatibility level of two provided view compatibility results. + /// </summary> + /// <param name="first">The first compatibility level</param> + /// <param name="second">The second compatibility level</param> + /// <returns>The minimum compatibility level of two provided view compatibility results</returns> + public static TextureViewCompatibility PropagateViewCompatibility(TextureViewCompatibility first, TextureViewCompatibility second) + { + if (first == TextureViewCompatibility.Incompatible || second == TextureViewCompatibility.Incompatible) + { + return TextureViewCompatibility.Incompatible; + } + else if (first == TextureViewCompatibility.CopyOnly || second == TextureViewCompatibility.CopyOnly) + { + return TextureViewCompatibility.CopyOnly; + } + else + { + return TextureViewCompatibility.Full; + } + } + + /// <summary> + /// Checks if the sizes of two given textures are view compatible. /// </summary> /// <param name="lhs">Texture information of the texture view</param> /// <param name="rhs">Texture information of the texture view to match against</param> /// <param name="level">Mipmap level of the texture view in relation to this texture</param> - /// <param name="isCopy">True to check for copy compatibility rather than view compatibility</param> /// <returns>True if the sizes are compatible, false otherwise</returns> - public static bool ViewSizeMatches(TextureInfo lhs, TextureInfo rhs, int level, bool isCopy) + public static TextureViewCompatibility ViewSizeMatches(TextureInfo lhs, TextureInfo rhs, int level) { Size size = GetAlignedSize(lhs, level); Size otherSize = GetAlignedSize(rhs); + TextureViewCompatibility result = TextureViewCompatibility.Full; + // For copies, we can copy a subset of the 3D texture slices, // so the depth may be different in this case. - if (!isCopy && rhs.Target == Target.Texture3D && size.Depth != otherSize.Depth) + if (rhs.Target == Target.Texture3D && size.Depth != otherSize.Depth) { - return false; + result = TextureViewCompatibility.CopyOnly; } - return size.Width == otherSize.Width && - size.Height == otherSize.Height; + return (size.Width == otherSize.Width && + size.Height == otherSize.Height) ? result : TextureViewCompatibility.Incompatible; } /// <summary> @@ -330,38 +353,48 @@ namespace Ryujinx.Graphics.Gpu.Image /// <param name="rhs">Texture information of the texture view</param> /// <param name="isCopy">True to check for copy rather than view compatibility</param> /// <returns>True if the targets are compatible, false otherwise</returns> - public static bool ViewTargetCompatible(TextureInfo lhs, TextureInfo rhs, bool isCopy) + public static TextureViewCompatibility ViewTargetCompatible(TextureInfo lhs, TextureInfo rhs) { + bool result = false; switch (lhs.Target) { case Target.Texture1D: case Target.Texture1DArray: - return rhs.Target == Target.Texture1D || - rhs.Target == Target.Texture1DArray; + result = rhs.Target == Target.Texture1D || + rhs.Target == Target.Texture1DArray; + break; case Target.Texture2D: - return rhs.Target == Target.Texture2D || - rhs.Target == Target.Texture2DArray; + result = rhs.Target == Target.Texture2D || + rhs.Target == Target.Texture2DArray; + break; case Target.Texture2DArray: case Target.Cubemap: case Target.CubemapArray: - return rhs.Target == Target.Texture2D || - rhs.Target == Target.Texture2DArray || - rhs.Target == Target.Cubemap || - rhs.Target == Target.CubemapArray; + result = rhs.Target == Target.Texture2D || + rhs.Target == Target.Texture2DArray || + rhs.Target == Target.Cubemap || + rhs.Target == Target.CubemapArray; + break; case Target.Texture2DMultisample: case Target.Texture2DMultisampleArray: - return rhs.Target == Target.Texture2DMultisample || - rhs.Target == Target.Texture2DMultisampleArray; + result = rhs.Target == Target.Texture2DMultisample || + rhs.Target == Target.Texture2DMultisampleArray; + break; case Target.Texture3D: - return rhs.Target == Target.Texture3D || - (rhs.Target == Target.Texture2D && isCopy); + if (rhs.Target == Target.Texture2D) + { + return TextureViewCompatibility.CopyOnly; + } + + result = rhs.Target == Target.Texture3D; + break; } - return false; + return result ? TextureViewCompatibility.Full : TextureViewCompatibility.Incompatible; } /// <summary> diff --git a/Ryujinx.Graphics.Gpu/Image/TextureManager.cs b/Ryujinx.Graphics.Gpu/Image/TextureManager.cs index cab76da1..0b1d38d1 100644 --- a/Ryujinx.Graphics.Gpu/Image/TextureManager.cs +++ b/Ryujinx.Graphics.Gpu/Image/TextureManager.cs @@ -14,6 +14,20 @@ namespace Ryujinx.Graphics.Gpu.Image /// </summary> class TextureManager : IDisposable { + private struct OverlapInfo + { + public TextureViewCompatibility Compatibility { get; } + public int FirstLayer { get; } + public int FirstLevel { get; } + + public OverlapInfo(TextureViewCompatibility compatibility, int firstLayer, int firstLevel) + { + Compatibility = compatibility; + FirstLayer = firstLayer; + FirstLevel = firstLevel; + } + } + private const int OverlapsBufferInitialCapacity = 10; private const int OverlapsBufferMaxCapacity = 10000; @@ -33,6 +47,7 @@ namespace Ryujinx.Graphics.Gpu.Image private readonly RangeList<Texture> _textures; private Texture[] _textureOverlaps; + private OverlapInfo[] _overlapInfo; private readonly AutoDeleteCache _cache; @@ -64,6 +79,7 @@ namespace Ryujinx.Graphics.Gpu.Image _textures = new RangeList<Texture>(); _textureOverlaps = new Texture[OverlapsBufferInitialCapacity]; + _overlapInfo = new OverlapInfo[OverlapsBufferInitialCapacity]; _cache = new AutoDeleteCache(); @@ -408,6 +424,27 @@ namespace Ryujinx.Graphics.Gpu.Image } /// <summary> + /// Handles removal of textures written to a memory region being unmapped. + /// </summary> + /// <param name="sender">Sender object</param> + /// <param name="e">Event arguments</param> + public void MemoryUnmappedHandler(object sender, UnmapEventArgs e) + { + Texture[] overlaps = new Texture[10]; + int overlapCount; + + lock (_textures) + { + overlapCount = _textures.FindOverlaps(_context.MemoryManager.Translate(e.Address), e.Size, ref overlaps); + } + + for (int i = 0; i < overlapCount; i++) + { + overlaps[i].Unmapped(); + } + } + + /// <summary> /// Tries to find an existing texture, or create a new one if not found. /// </summary> /// <param name="copyTexture">Copy texture to find or create</param> @@ -618,8 +655,13 @@ namespace Ryujinx.Graphics.Gpu.Image scaleMode = (flags & TextureSearchFlags.WithUpscale) != 0 ? TextureScaleMode.Scaled : TextureScaleMode.Eligible; } - // Try to find a perfect texture match, with the same address and parameters. - int sameAddressOverlapsCount = _textures.FindOverlaps(info.Address, ref _textureOverlaps); + int sameAddressOverlapsCount; + + lock (_textures) + { + // Try to find a perfect texture match, with the same address and parameters. + sameAddressOverlapsCount = _textures.FindOverlaps(info.Address, ref _textureOverlaps); + } for (int index = 0; index < sameAddressOverlapsCount; index++) { @@ -681,8 +723,12 @@ namespace Ryujinx.Graphics.Gpu.Image // Find view compatible matches. ulong size = (ulong)sizeInfo.TotalSize; + int overlapsCount; - int overlapsCount = _textures.FindOverlaps(info.Address, size, ref _textureOverlaps); + lock (_textures) + { + overlapsCount = _textures.FindOverlaps(info.Address, size, ref _textureOverlaps); + } Texture texture = null; @@ -690,7 +736,7 @@ namespace Ryujinx.Graphics.Gpu.Image { Texture overlap = _textureOverlaps[index]; - if (overlap.IsViewCompatible(info, size, out int firstLayer, out int firstLevel)) + if (overlap.IsViewCompatible(info, size, out int firstLayer, out int firstLevel) == TextureViewCompatibility.Full) { if (!isSamplerTexture) { @@ -701,7 +747,7 @@ namespace Ryujinx.Graphics.Gpu.Image if (IsTextureModified(overlap)) { - CacheTextureModified(texture); + texture.SignalModified(); } // The size only matters (and is only really reliable) when the @@ -721,65 +767,114 @@ namespace Ryujinx.Graphics.Gpu.Image { texture = new Texture(_context, info, sizeInfo, scaleMode); - // We need to synchronize before copying the old view data to the texture, - // otherwise the copied data would be overwritten by a future synchronization. - texture.SynchronizeMemory(); + // Step 1: Find textures that are view compatible with the new texture. + // Any textures that are incompatible will contain garbage data, so they should be removed where possible. + + int viewCompatible = 0; + bool setData = isSamplerTexture || overlapsCount == 0; for (int index = 0; index < overlapsCount; index++) { Texture overlap = _textureOverlaps[index]; + bool overlapInCache = overlap.CacheNode != null; - if (texture.IsViewCompatible(overlap.Info, overlap.Size, out int firstLayer, out int firstLevel)) - { - TextureInfo overlapInfo = AdjustSizes(texture, overlap.Info, firstLevel); - - TextureCreateInfo createInfo = GetCreateInfo(overlapInfo, _context.Capabilities); + TextureViewCompatibility compatibility = texture.IsViewCompatible(overlap.Info, overlap.Size, out int firstLayer, out int firstLevel); - if (texture.ScaleFactor != overlap.ScaleFactor) + if (compatibility != TextureViewCompatibility.Incompatible) + { + if (_overlapInfo.Length != _textureOverlaps.Length) { - // A bit tricky, our new texture may need to contain an existing texture that is upscaled, but isn't itself. - // In that case, we prefer the higher scale only if our format is render-target-like, otherwise we scale the view down before copy. + Array.Resize(ref _overlapInfo, _textureOverlaps.Length); + } - texture.PropagateScale(overlap); + _overlapInfo[viewCompatible] = new OverlapInfo(compatibility, firstLayer, firstLevel); + _textureOverlaps[viewCompatible++] = overlap; + } + else if (overlapInCache || !setData) + { + if (info.GobBlocksInZ > 1 && info.GobBlocksInZ == overlap.Info.GobBlocksInZ) + { + // Allow overlapping slices of 3D textures. Could be improved in future by making sure the textures don't overlap. + continue; } - ITexture newView = texture.HostTexture.CreateView(createInfo, firstLayer, firstLevel); + // The overlap texture is going to contain garbage data after we draw, or is generally incompatible. + // If the texture cannot be entirely contained in the new address space, and one of its view children is compatible with us, + // it must be flushed before removal, so that the data is not lost. + + // If the texture was modified since its last use, then that data is probably meant to go into this texture. + // If the data has been modified by the CPU, then it also shouldn't be flushed. + bool modified = overlap.ConsumeModified(); - overlap.HostTexture.CopyTo(newView, 0, 0); + bool flush = overlapInCache && !modified && (overlap.Address < texture.Address || overlap.EndAddress > texture.EndAddress) && overlap.HasViewCompatibleChild(texture); - // Inherit modification from overlapping texture, do that before replacing - // the view since the replacement operation removes it from the list. - if (IsTextureModified(overlap)) + setData |= modified || flush; + + if (overlapInCache) { - CacheTextureModified(texture); + _cache.Remove(overlap, flush); } + } + } + + // We need to synchronize before copying the old view data to the texture, + // otherwise the copied data would be overwritten by a future synchronization. + texture.InitializeData(false, setData); + + for (int index = 0; index < viewCompatible; index++) + { + Texture overlap = _textureOverlaps[index]; + OverlapInfo oInfo = _overlapInfo[index]; + + if (oInfo.Compatibility != TextureViewCompatibility.Full) + { + continue; // Copy only compatibilty. + } + + TextureInfo overlapInfo = AdjustSizes(texture, overlap.Info, oInfo.FirstLevel); + + TextureCreateInfo createInfo = GetCreateInfo(overlapInfo, _context.Capabilities); + + if (texture.ScaleFactor != overlap.ScaleFactor) + { + // A bit tricky, our new texture may need to contain an existing texture that is upscaled, but isn't itself. + // In that case, we prefer the higher scale only if our format is render-target-like, otherwise we scale the view down before copy. - overlap.ReplaceView(texture, overlapInfo, newView, firstLayer, firstLevel); + texture.PropagateScale(overlap); } + + ITexture newView = texture.HostTexture.CreateView(createInfo, oInfo.FirstLayer, oInfo.FirstLevel); + + 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)) + { + texture.SignalModified(); + } + + overlap.ReplaceView(texture, overlapInfo, newView, oInfo.FirstLayer, oInfo.FirstLevel); } // 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) { - for (int index = 0; index < overlapsCount; index++) + for (int index = 0; index < viewCompatible; index++) { Texture overlap = _textureOverlaps[index]; + OverlapInfo oInfo = _overlapInfo[index]; - if (texture.IsViewCompatible( - overlap.Info, - overlap.Size, - isCopy: true, - out int firstLayer, - out int firstLevel)) + if (oInfo.Compatibility != TextureViewCompatibility.Incompatible) { overlap.BlacklistScale(); - overlap.HostTexture.CopyTo(texture.HostTexture, firstLayer, firstLevel); + overlap.HostTexture.CopyTo(texture.HostTexture, oInfo.FirstLayer, oInfo.FirstLevel); if (IsTextureModified(overlap)) { - CacheTextureModified(texture); + texture.SignalModified(); } } } @@ -795,7 +890,10 @@ namespace Ryujinx.Graphics.Gpu.Image texture.Disposed += CacheTextureDisposed; } - _textures.Add(texture); + lock (_textures) + { + _textures.Add(texture); + } ShrinkOverlapsBufferIfNeeded(); @@ -818,6 +916,7 @@ namespace Ryujinx.Graphics.Gpu.Image /// <param name="texture">The texture that was modified.</param> private void CacheTextureModified(Texture texture) { + texture.IsModified = true; _modified.Add(texture); if (texture.Info.IsLinear) @@ -992,7 +1091,10 @@ namespace Ryujinx.Graphics.Gpu.Image { foreach (Texture texture in _modifiedLinear) { - texture.Flush(); + if (texture.IsModified) + { + texture.Flush(); + } } _modifiedLinear.Clear(); @@ -1007,7 +1109,7 @@ namespace Ryujinx.Graphics.Gpu.Image { foreach (Texture texture in _modified) { - if (texture.OverlapsWith(address, size)) + if (texture.OverlapsWith(address, size) && texture.IsModified) { texture.Flush(); } @@ -1024,7 +1126,10 @@ namespace Ryujinx.Graphics.Gpu.Image /// <param name="texture">The texture to be removed</param> public void RemoveTextureFromCache(Texture texture) { - _textures.Remove(texture); + lock (_textures) + { + _textures.Remove(texture); + } } /// <summary> @@ -1033,10 +1138,13 @@ namespace Ryujinx.Graphics.Gpu.Image /// </summary> public void Dispose() { - foreach (Texture texture in _textures) + lock (_textures) { - _modified.Remove(texture); - texture.Dispose(); + foreach (Texture texture in _textures) + { + _modified.Remove(texture); + texture.Dispose(); + } } } } diff --git a/Ryujinx.Graphics.Gpu/Image/TextureViewCompatibility.cs b/Ryujinx.Graphics.Gpu/Image/TextureViewCompatibility.cs new file mode 100644 index 00000000..4671af46 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Image/TextureViewCompatibility.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.Graphics.Gpu.Image +{ + /// <summary> + /// The level of view compatibility one texture has to another. + /// Values are increasing in compatibility from 0 (incompatible). + /// </summary> + enum TextureViewCompatibility + { + Incompatible = 0, + CopyOnly, + Full + } +} |
