diff options
Diffstat (limited to 'src/Ryujinx.Graphics.Gpu/Image/TextureGroupHandle.cs')
-rw-r--r-- | src/Ryujinx.Graphics.Gpu/Image/TextureGroupHandle.cs | 554 |
1 files changed, 554 insertions, 0 deletions
diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureGroupHandle.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureGroupHandle.cs new file mode 100644 index 00000000..ebb4e9ae --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Image/TextureGroupHandle.cs @@ -0,0 +1,554 @@ +using Ryujinx.Cpu.Tracking; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; + +namespace Ryujinx.Graphics.Gpu.Image +{ + /// <summary> + /// 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. + /// </summary> + class TextureGroupHandle : IDisposable + { + private TextureGroup _group; + private int _bindCount; + private int _firstLevel; + private int _firstLayer; + + // Sync state for texture flush. + + /// <summary> + /// The sync number last registered. + /// </summary> + private ulong _registeredSync; + + /// <summary> + /// The sync number when the texture was last modified by GPU. + /// </summary> + private ulong _modifiedSync; + + /// <summary> + /// Whether a tracking action is currently registered or not. (0/1) + /// </summary> + private int _actionRegistered; + + /// <summary> + /// Whether a sync action is currently registered or not. + /// </summary> + private bool _syncActionRegistered; + + /// <summary> + /// The byte offset from the start of the storage of this handle. + /// </summary> + public int Offset { get; } + + /// <summary> + /// The size in bytes covered by this handle. + /// </summary> + public int Size { get; } + + /// <summary> + /// The base slice index for this handle. + /// </summary> + public int BaseSlice { get; } + + /// <summary> + /// The number of slices covered by this handle. + /// </summary> + public int SliceCount { get; } + + /// <summary> + /// The textures which this handle overlaps with. + /// </summary> + public List<Texture> Overlaps { get; } + + /// <summary> + /// The CPU memory tracking handles that cover this handle. + /// </summary> + public CpuRegionHandle[] Handles { get; } + + /// <summary> + /// True if a texture overlapping this handle has been modified. Is set false when the flush action is called. + /// </summary> + public bool Modified { get; set; } + + /// <summary> + /// Dependencies to handles from other texture groups. + /// </summary> + public List<TextureDependency> Dependencies { get; } + + /// <summary> + /// A flag indicating that a copy is required from one of the dependencies. + /// </summary> + public bool NeedsCopy => DeferredCopy != null; + + /// <summary> + /// A data copy that must be acknowledged the next time this handle is used. + /// </summary> + public TextureGroupHandle DeferredCopy { get; set; } + + /// <summary> + /// Create a new texture group handle, representing a range of views in a storage texture. + /// </summary> + /// <param name="group">The TextureGroup that the handle belongs to</param> + /// <param name="offset">The byte offset from the start of the storage of the handle</param> + /// <param name="size">The size in bytes covered by the handle</param> + /// <param name="views">All views of the storage texture, used to calculate overlaps</param> + /// <param name="firstLayer">The first layer of this handle in the storage texture</param> + /// <param name="firstLevel">The first level of this handle in the storage texture</param> + /// <param name="baseSlice">The base slice index of this handle</param> + /// <param name="sliceCount">The number of slices this handle covers</param> + /// <param name="handles">The memory tracking handles that cover this handle</param> + public TextureGroupHandle(TextureGroup group, + int offset, + ulong size, + List<Texture> views, + int firstLayer, + int firstLevel, + int baseSlice, + int sliceCount, + CpuRegionHandle[] handles) + { + _group = group; + _firstLayer = firstLayer; + _firstLevel = firstLevel; + + Offset = offset; + Size = (int)size; + Overlaps = new List<Texture>(); + Dependencies = new List<TextureDependency>(); + + BaseSlice = baseSlice; + SliceCount = sliceCount; + + if (views != null) + { + RecalculateOverlaps(group, views); + } + + Handles = handles; + } + + /// <summary> + /// Calculate a list of which views overlap this handle. + /// </summary> + /// <param name="group">The parent texture group, used to find a view's base CPU VA offset</param> + /// <param name="views">The list of views to search for overlaps</param> + public void RecalculateOverlaps(TextureGroup group, List<Texture> 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); + } + } + } + } + + /// <summary> + /// Adds a single texture view as an overlap if its range overlaps. + /// </summary> + /// <param name="offset">The offset of the view in the group</param> + /// <param name="view">The texture to add as an overlap</param> + public void AddOverlap(int offset, Texture view) + { + // Overlaps can be accessed from the memory tracking signal handler, so access must be atomic. + + if (OverlapsWith(offset, (int)view.Size)) + { + lock (Overlaps) + { + Overlaps.Add(view); + } + } + } + + /// <summary> + /// Removes a single texture view as an overlap if its range overlaps. + /// </summary> + /// <param name="offset">The offset of the view in the group</param> + /// <param name="view">The texture to add as an overlap</param> + public void RemoveOverlap(int offset, Texture view) + { + // Overlaps can be accessed from the memory tracking signal handler, so access must be atomic. + + if (OverlapsWith(offset, (int)view.Size)) + { + lock (Overlaps) + { + Overlaps.Remove(view); + } + } + } + + /// <summary> + /// Registers a sync action to happen for this handle, and an interim flush action on the tracking handle. + /// </summary> + /// <param name="context">The GPU context to register a sync action on</param> + private void RegisterSync(GpuContext context) + { + if (!_syncActionRegistered) + { + _modifiedSync = context.SyncNumber; + context.RegisterSyncAction(SyncAction, true); + _syncActionRegistered = true; + } + + if (Interlocked.Exchange(ref _actionRegistered, 1) == 0) + { + _group.RegisterAction(this); + } + } + + /// <summary> + /// Signal that this handle has been modified to any existing dependencies, and set the modified flag. + /// </summary> + /// <param name="context">The GPU context to register a sync action on</param> + public void SignalModified(GpuContext context) + { + 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(); + } + + RegisterSync(context); + } + + /// <summary> + /// Signal that this handle has either started or ended being modified. + /// </summary> + /// <param name="bound">True if this handle is being bound, false if unbound</param> + /// <param name="context">The GPU context to register a sync action on</param> + public void SignalModifying(bool bound, GpuContext context) + { + SignalModified(context); + + // 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)); + } + + /// <summary> + /// Synchronize dependent textures, if any of them have deferred a copy from this texture. + /// </summary> + public void SynchronizeDependents() + { + foreach (TextureDependency dependency in Dependencies) + { + TextureGroupHandle otherHandle = dependency.Other.Handle; + + if (otherHandle.DeferredCopy == this) + { + otherHandle._group.Storage.SynchronizeMemory(); + } + } + } + + /// <summary> + /// Wait for the latest sync number that the texture handle was written to, + /// removing the modified flag if it was reached, or leaving it set if it has not yet been created. + /// </summary> + /// <param name="context">The GPU context used to wait for sync</param> + public void Sync(GpuContext context) + { + ulong registeredSync = _registeredSync; + long diff = (long)(context.SyncNumber - registeredSync); + + if (diff > 0) + { + context.Renderer.WaitSync(registeredSync); + + if ((long)(_modifiedSync - registeredSync) > 0) + { + // Flush the data in a previous state. Do not remove the modified flag - it will be removed to ignore following writes. + return; + } + + Modified = false; + } + + // If the difference is <= 0, no data is not ready yet. Flush any data we can without waiting or removing modified flag. + } + + /// <summary> + /// Clears the action registered variable, indicating that the tracking action should be + /// re-registered on the next modification. + /// </summary> + public void ClearActionRegistered() + { + Interlocked.Exchange(ref _actionRegistered, 0); + } + + /// <summary> + /// Action to perform when a sync number is registered after modification. + /// This action will register a read tracking action on the memory tracking handle so that a flush from CPU can happen. + /// </summary> + private void SyncAction() + { + // The storage will need to signal modified again to update the sync number in future. + _group.Storage.SignalModifiedDirty(); + + lock (Overlaps) + { + foreach (Texture texture in Overlaps) + { + texture.SignalModifiedDirty(); + } + } + + // Register region tracking for CPU? (again) + _registeredSync = _modifiedSync; + _syncActionRegistered = false; + + if (Interlocked.Exchange(ref _actionRegistered, 1) == 0) + { + _group.RegisterAction(this); + } + } + + /// <summary> + /// Signal that a copy dependent texture has been modified, and must have its data copied to this one. + /// </summary> + /// <param name="copyFrom">The texture handle that must defer a copy to this one</param> + public void DeferCopy(TextureGroupHandle copyFrom) + { + Modified = false; + + DeferredCopy = copyFrom; + + _group.Storage.SignalGroupDirty(); + + foreach (Texture overlap in Overlaps) + { + overlap.SignalGroupDirty(); + } + } + + /// <summary> + /// Create a copy dependency between this handle, and another. + /// </summary> + /// <param name="other">The handle to create a copy dependency to</param> + /// <param name="copyToOther">True if a copy should be deferred to all of the other handle's dependencies</param> + 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); + } + } + } + } + + /// <summary> + /// Remove a dependency from this handle's dependency list. + /// </summary> + /// <param name="dependency">The dependency to remove</param> + public void RemoveDependency(TextureDependency dependency) + { + Dependencies.Remove(dependency); + } + + /// <summary> + /// Check if any of this handle's memory tracking handles are dirty. + /// </summary> + /// <returns>True if at least one of the handles is dirty</returns> + private bool CheckDirty() + { + return Handles.Any(handle => handle.Dirty); + } + + /// <summary> + /// Perform a copy from the provided handle to this one, or perform a deferred copy if none is provided. + /// </summary> + /// <param name="context">GPU context to register sync for modified handles</param> + /// <param name="fromHandle">The handle to copy from. If not provided, this method will copy from and clear the deferred copy instead</param> + /// <returns>True if the copy was performed, false otherwise</returns> + public bool Copy(GpuContext context, TextureGroupHandle fromHandle = null) + { + bool result = false; + bool shouldCopy = false; + + if (fromHandle == null) + { + fromHandle = DeferredCopy; + + if (fromHandle != null) + { + // Only copy if the copy texture is still modified. + // It will be set as unmodified if new data is written from CPU, as the data previously in the texture will flush. + // It will also set as unmodified if a copy is deferred to it. + + shouldCopy = fromHandle.Modified; + + if (fromHandle._bindCount == 0) + { + // Repeat the copy in future if the bind count is greater than 0. + DeferredCopy = null; + } + } + } + else + { + // Copies happen directly when initializing a copy dependency. + // If dirty, do not copy. Its data no longer matters, and this handle should also be dirty. + // Also, only direct copy if the data in this handle is not already modified (can be set by copies from modified handles). + shouldCopy = !fromHandle.CheckDirty() && (fromHandle.Modified || !Modified); + } + + if (shouldCopy) + { + 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); + + if (fromHandle.Modified) + { + Modified = true; + + RegisterSync(context); + } + + result = true; + } + + return result; + } + + /// <summary> + /// Check if this handle has a dependency to a given texture group. + /// </summary> + /// <param name="group">The texture group to check for</param> + /// <returns>True if there is a dependency, false otherwise</returns> + public bool HasDependencyTo(TextureGroup group) + { + foreach (TextureDependency dep in Dependencies) + { + if (dep.Other.Handle._group == group) + { + return true; + } + } + + return false; + } + + /// <summary> + /// Inherit modified flags and dependencies from another texture handle. + /// </summary> + /// <param name="old">The texture handle to inherit from</param> + /// <param name="withCopies">Whether the handle should inherit copy dependencies or not</param> + public void Inherit(TextureGroupHandle old, bool withCopies) + { + Modified |= old.Modified; + + if (withCopies) + { + 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; + } + } + + /// <summary> + /// Check if this region overlaps with another. + /// </summary> + /// <param name="address">Base address</param> + /// <param name="size">Size of the region</param> + /// <returns>True if overlapping, false otherwise</returns> + public bool OverlapsWith(int offset, int size) + { + return Offset < offset + size && offset < Offset + Size; + } + + /// <summary> + /// Dispose this texture group handle, removing all its dependencies and disposing its memory tracking handles. + /// </summary> + public void Dispose() + { + foreach (CpuRegionHandle handle in Handles) + { + handle.Dispose(); + } + + foreach (TextureDependency dependency in Dependencies.ToArray()) + { + dependency.Other.Handle.RemoveDependency(dependency.Other); + } + } + } +} |