From b530f0e1104723b894695b2860cf0f568f24cc9a Mon Sep 17 00:00:00 2001 From: riperiperi Date: Tue, 2 Mar 2021 22:30:54 +0000 Subject: Texture Cache: "Texture Groups" and "Texture Dependencies" (#2001) * Initial implementation (3d tex mips broken) This works rather well for most games, just need to fix 3d texture mips. * Cleanup * Address feedback * Copy Dependencies and various other fixes * Fix layer/level offset for copy from view<->view. * Remove dirty flag from dependency The dirty flag behaviour is not needed - DeferredCopy is all we need. * Fix tracking mip slices. * Propagate granularity (fix astral chain) * Address Feedback pt 1 * Save slice sizes as part of SizeInfo * Fix nits * Fix disposing multiple dependencies causing a crash This list is obviously modified when removing dependencies, so create a copy of it. --- Ryujinx.Graphics.Gpu/Image/TextureGroupHandle.cs | 327 +++++++++++++++++++++++ 1 file changed, 327 insertions(+) create mode 100644 Ryujinx.Graphics.Gpu/Image/TextureGroupHandle.cs (limited to 'Ryujinx.Graphics.Gpu/Image/TextureGroupHandle.cs') diff --git a/Ryujinx.Graphics.Gpu/Image/TextureGroupHandle.cs b/Ryujinx.Graphics.Gpu/Image/TextureGroupHandle.cs new file mode 100644 index 00000000..27ee1e49 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Image/TextureGroupHandle.cs @@ -0,0 +1,327 @@ +using Ryujinx.Cpu.Tracking; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Ryujinx.Graphics.Gpu.Image +{ + /// + /// A tracking handle for a texture group, which represents a range of views in a storage texture. + /// Retains a list of overlapping texture views, a modified flag, and tracking for each + /// CPU VA range that the views cover. + /// Also tracks copy dependencies for the handle - references to other handles that must be kept + /// in sync with this one before use. + /// + class TextureGroupHandle : IDisposable + { + private TextureGroup _group; + private int _bindCount; + private int _firstLevel; + private int _firstLayer; + + /// + /// The byte offset from the start of the storage of this handle. + /// + public int Offset { get; } + + /// + /// The size in bytes covered by this handle. + /// + public int Size { get; } + + /// + /// The textures which this handle overlaps with. + /// + public List Overlaps { get; } + + /// + /// The CPU memory tracking handles that cover this handle. + /// + public CpuRegionHandle[] Handles { get; } + + /// + /// True if a texture overlapping this handle has been modified. Is set false when the flush action is called. + /// + public bool Modified { get; set; } + + /// + /// Dependencies to handles from other texture groups. + /// + public List Dependencies { get; } + + /// + /// A flag indicating that a copy is required from one of the dependencies. + /// + public bool NeedsCopy => DeferredCopy != null; + + /// + /// A data copy that must be acknowledged the next time this handle is used. + /// + public TextureGroupHandle DeferredCopy { get; set; } + + /// + /// Create a new texture group handle, representing a range of views in a storage texture. + /// + /// The TextureGroup that the handle belongs to + /// The byte offset from the start of the storage of the handle + /// The size in bytes covered by the handle + /// All views of the storage texture, used to calculate overlaps + /// The first layer of this handle in the storage texture + /// The first level of this handle in the storage texture + /// The memory tracking handles that cover this handle + public TextureGroupHandle(TextureGroup group, int offset, ulong size, List views, int firstLayer, int firstLevel, CpuRegionHandle[] handles) + { + _group = group; + _firstLayer = firstLayer; + _firstLevel = firstLevel; + + Offset = offset; + Size = (int)size; + Overlaps = new List(); + Dependencies = new List(); + + if (views != null) + { + RecalculateOverlaps(group, views); + } + + Handles = handles; + } + + /// + /// Calculate a list of which views overlap this handle. + /// + /// The parent texture group, used to find a view's base CPU VA offset + /// The list of views to search for overlaps + public void RecalculateOverlaps(TextureGroup group, List views) + { + // Overlaps can be accessed from the memory tracking signal handler, so access must be atomic. + lock (Overlaps) + { + int endOffset = Offset + Size; + + Overlaps.Clear(); + + foreach (Texture view in views) + { + int viewOffset = group.FindOffset(view); + if (viewOffset < endOffset && Offset < viewOffset + (int)view.Size) + { + Overlaps.Add(view); + } + } + } + } + + /// + /// Signal that this handle has been modified to any existing dependencies, and set the modified flag. + /// + public void SignalModified() + { + Modified = true; + + // If this handle has any copy dependencies, notify the other handle that a copy needs to be performed. + + foreach (TextureDependency dependency in Dependencies) + { + dependency.SignalModified(); + } + } + + /// + /// Signal that this handle has either started or ended being modified. + /// + /// True if this handle is being bound, false if unbound + public void SignalModifying(bool bound) + { + SignalModified(); + + // Note: Bind count currently resets to 0 on inherit for safety, as the handle <-> view relationship can change. + _bindCount = Math.Max(0, _bindCount + (bound ? 1 : -1)); + } + + /// + /// Signal that a copy dependent texture has been modified, and must have its data copied to this one. + /// + /// The texture handle that must defer a copy to this one + public void DeferCopy(TextureGroupHandle copyFrom) + { + DeferredCopy = copyFrom; + + _group.Storage.SignalGroupDirty(); + + foreach (Texture overlap in Overlaps) + { + overlap.SignalGroupDirty(); + } + } + + /// + /// Create a copy dependency between this handle, and another. + /// + /// The handle to create a copy dependency to + /// True if a copy should be deferred to all of the other handle's dependencies + public void CreateCopyDependency(TextureGroupHandle other, bool copyToOther = false) + { + // Does this dependency already exist? + foreach (TextureDependency existing in Dependencies) + { + if (existing.Other.Handle == other) + { + // Do not need to create it again. May need to set the dirty flag. + return; + } + } + + _group.HasCopyDependencies = true; + other._group.HasCopyDependencies = true; + + TextureDependency dependency = new TextureDependency(this); + TextureDependency otherDependency = new TextureDependency(other); + + dependency.Other = otherDependency; + otherDependency.Other = dependency; + + Dependencies.Add(dependency); + other.Dependencies.Add(otherDependency); + + // Recursively create dependency: + // All of this handle's dependencies must depend on the other. + foreach (TextureDependency existing in Dependencies.ToArray()) + { + if (existing != dependency && existing.Other.Handle != other) + { + existing.Other.Handle.CreateCopyDependency(other); + } + } + + // All of the other handle's dependencies must depend on this. + foreach (TextureDependency existing in other.Dependencies.ToArray()) + { + if (existing != otherDependency && existing.Other.Handle != this) + { + existing.Other.Handle.CreateCopyDependency(this); + + if (copyToOther) + { + existing.Other.Handle.DeferCopy(this); + } + } + } + } + + /// + /// Remove a dependency from this handle's dependency list. + /// + /// The dependency to remove + public void RemoveDependency(TextureDependency dependency) + { + Dependencies.Remove(dependency); + } + + /// + /// Check if any of this handle's memory tracking handles are dirty. + /// + /// True if at least one of the handles is dirty + private bool CheckDirty() + { + return Handles.Any(handle => handle.Dirty); + } + + /// + /// Perform a copy from the provided handle to this one, or perform a deferred copy if none is provided. + /// + /// The handle to copy from. If not provided, this method will copy from and clear the deferred copy instead + /// True if the copy was performed, false otherwise + public bool Copy(TextureGroupHandle fromHandle = null) + { + bool result = false; + + if (fromHandle == null) + { + fromHandle = DeferredCopy; + + if (fromHandle != null && fromHandle._bindCount == 0) + { + // Repeat the copy in future if the bind count is greater than 0. + DeferredCopy = null; + } + } + + if (fromHandle != null) + { + // If the copy texture is dirty, do not copy. Its data no longer matters, and this handle should also be dirty. + if (!fromHandle.CheckDirty()) + { + Texture from = fromHandle._group.Storage; + Texture to = _group.Storage; + + if (from.ScaleFactor != to.ScaleFactor) + { + to.PropagateScale(from); + } + + from.HostTexture.CopyTo( + to.HostTexture, + fromHandle._firstLayer, + _firstLayer, + fromHandle._firstLevel, + _firstLevel); + + Modified = true; + + _group.RegisterAction(this); + + result = true; + } + } + + return result; + } + + /// + /// Inherit modified flags and dependencies from another texture handle. + /// + /// The texture handle to inherit from + public void Inherit(TextureGroupHandle old) + { + Modified |= old.Modified; + + foreach (TextureDependency dependency in old.Dependencies.ToArray()) + { + CreateCopyDependency(dependency.Other.Handle); + + if (dependency.Other.Handle.DeferredCopy == old) + { + dependency.Other.Handle.DeferredCopy = this; + } + } + + DeferredCopy = old.DeferredCopy; + } + + /// + /// Check if this region overlaps with another. + /// + /// Base address + /// Size of the region + /// True if overlapping, false otherwise + public bool OverlapsWith(int offset, int size) + { + return Offset < offset + size && offset < Offset + Size; + } + + public void Dispose() + { + foreach (CpuRegionHandle handle in Handles) + { + handle.Dispose(); + } + + foreach (TextureDependency dependency in Dependencies.ToArray()) + { + dependency.Other.Handle.RemoveDependency(dependency.Other); + } + } + } +} -- cgit v1.2.3