diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureDependency.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureDependency.cs index 45a5a3973..38bc66b54 100644 --- a/src/Ryujinx.Graphics.Gpu/Image/TextureDependency.cs +++ b/src/Ryujinx.Graphics.Gpu/Image/TextureDependency.cs @@ -17,21 +17,30 @@ namespace Ryujinx.Graphics.Gpu.Image public TextureDependency Other; /// - /// Create a new texture dependency. + /// Indicates whether this dependency requires an exact raw byte copy + /// instead of a regular texture copy. + /// + public readonly bool RawCopy; + + /// + /// Creates a new texture dependency. /// /// The handle that owns the dependency - public TextureDependency(TextureGroupHandle handle) + /// True if copies performed for this dependency must preserve the exact raw bytes; false to use a regular texture copy + public TextureDependency(TextureGroupHandle handle, bool rawCopy = false) { Handle = handle; + RawCopy = rawCopy; } /// - /// Signal that the owner of this dependency has been modified, - /// meaning that the other dependency's handle must defer a copy from it. + /// Signals that the owner of this dependency has been modified, + /// causing the other dependency's handle to defer a copy from it. + /// The dependency's copy mode is propagated to the deferred copy. /// public void SignalModified() { - Other.Handle.DeferCopy(Handle); + Other.Handle.DeferCopy(Handle, RawCopy); } } } diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs index 46a7a5b5a..d0216dc65 100644 --- a/src/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs +++ b/src/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs @@ -175,16 +175,16 @@ namespace Ryujinx.Graphics.Gpu.Image } /// - /// Initialize all incompatible overlaps in the list, registering them with the other texture groups - /// and creating copy dependencies when partially compatible. + /// Initializes all incompatible overlaps in the list, registers them with the other texture groups, + /// and creates regular or exact raw byte copy dependencies where supported. /// public void InitializeOverlaps() { foreach (TextureIncompatibleOverlap overlap in _incompatibleOverlaps) { - if (overlap.Compatibility == TextureViewCompatibility.LayoutIncompatible) + if (overlap.Compatibility <= TextureViewCompatibility.LayoutIncompatible) { - CreateCopyDependency(overlap.Group, false); + CreateCopyDependency(overlap.Group, false, overlap.Compatibility); } overlap.Group._incompatibleOverlaps.Add(new TextureIncompatibleOverlap(this, overlap.Compatibility)); @@ -1532,13 +1532,57 @@ namespace Ryujinx.Graphics.Gpu.Image } /// - /// Creates a copy dependency to another texture group, where handles overlap. - /// Scans through all handles to find compatible patches in the other group. + /// Determines whether an exact raw byte copy dependency can be safely created + /// between this texture group and another fully incompatible texture group. + /// + /// The overlapping texture group to evaluate + /// + /// True if both texture groups describe the same contiguous guest memory range + /// and have compatible logical payload sizes and resource properties for an exact raw byte copy; + /// otherwise, false + /// + private bool CanCreateRawCopyDependency(TextureGroup other) + { + TextureInfo info = Storage.Info; + TextureInfo otherInfo = other.Storage.Info; + long size = (long)info.Width * info.Height * info.GetDepth() * info.FormatInfo.BytesPerPixel; + long otherSize = (long)otherInfo.Width * otherInfo.Height * otherInfo.GetDepth() * otherInfo.FormatInfo.BytesPerPixel; + + return size > 0 && + size <= int.MaxValue && + size == otherSize && + Storage.Range.Count == 1 && + other.Storage.Range.Count == 1 && + Storage.Range.GetSubRange(0).Address == other.Storage.Range.GetSubRange(0).Address && + Storage.Range.GetSubRange(0).Size == other.Storage.Range.GetSubRange(0).Size && + info.Levels == 1 && + otherInfo.Levels == 1 && + info.GetSlices() == 1 && + otherInfo.GetSlices() == 1 && + info.Samples == 1 && + otherInfo.Samples == 1 && + info.Target != Target.TextureBuffer && + otherInfo.Target != Target.TextureBuffer && + !info.FormatInfo.IsCompressed && + !otherInfo.FormatInfo.IsCompressed && + !info.FormatInfo.Format.IsDepthOrStencil() && + !otherInfo.FormatInfo.Format.IsDepthOrStencil(); + } + + /// + /// Creates copy dependencies between overlapping handles in this texture group + /// and another texture group. + /// Regular texture copy dependencies are created for compatible handle pairs. + /// For fully incompatible groups, an exact raw byte copy dependency may be created + /// when the strict raw copy requirements are satisfied. /// /// The texture group that overlaps this one - /// True if this texture is first copied to the given one, false for the opposite direction - public void CreateCopyDependency(TextureGroup other, bool copyTo) + /// True if this texture should initially be copied to the other texture; false if the other texture should initially be copied to this texture + /// The view compatibility between the two texture groups, used to determine whether a regular texture copy or an exact raw byte copy may be used + public void CreateCopyDependency(TextureGroup other, bool copyTo, + TextureViewCompatibility compatibility = TextureViewCompatibility.LayoutIncompatible) { + bool rawCopy = compatibility == TextureViewCompatibility.Incompatible && CanCreateRawCopyDependency(other); for (int i = 0; i < _allOffsets.Length; i++) { (_, int level) = GetLayerLevelForView(i); @@ -1557,8 +1601,10 @@ namespace Ryujinx.Graphics.Gpu.Image TextureInfo info = Storage.Info; TextureInfo otherInfo = other.Storage.Info; - if (TextureCompatibility.ViewLayoutCompatible(info, otherInfo, level, otherLevel) && - TextureCompatibility.CopySizeMatches(info, otherInfo, level, otherLevel)) + bool textureCopy = TextureCompatibility.ViewLayoutCompatible(info, otherInfo, level, otherLevel) && + TextureCompatibility.CopySizeMatches(info, otherInfo, level, otherLevel); + + if (textureCopy || rawCopy) { // These textures are copy compatible. Create the dependency. @@ -1568,18 +1614,34 @@ namespace Ryujinx.Graphics.Gpu.Image TextureGroupHandle handle = _handles[i]; TextureGroupHandle otherHandle = other._handles[j]; - handle.CreateCopyDependency(otherHandle, copyTo); + handle.CreateCopyDependency(otherHandle, copyTo, rawCopy); // If "copyTo" is true, this texture must copy to the other. // Otherwise, it must copy to this texture. if (copyTo) { - otherHandle.Copy(_context, handle); + if (rawCopy) + { + otherHandle.DeferCopy(handle, true); + otherHandle.Copy(_context); + } + else + { + otherHandle.Copy(_context, handle); + } } else { - handle.Copy(_context, otherHandle); + if (rawCopy) + { + handle.DeferCopy(otherHandle, true); + handle.Copy(_context); + } + else + { + handle.Copy(_context, otherHandle); + } } } } @@ -1588,18 +1650,19 @@ namespace Ryujinx.Graphics.Gpu.Image } /// - /// Registers another texture group as an incompatible overlap, if not already registered. + /// Registers another texture group as an incompatible overlap, if it has not already been registered, + /// and creates a supported copy dependency when requested. /// - /// The texture group to add to the incompatible overlaps list - /// True if the overlap should register copy dependencies + /// The incompatible texture overlap to register + /// True to attempt to create a regular or exact raw byte copy dependency for the overlapping texture groups; otherwise, false public void RegisterIncompatibleOverlap(TextureIncompatibleOverlap other, bool copy) { if (!_incompatibleOverlaps.Any(overlap => overlap.Group == other.Group)) { - if (copy && other.Compatibility == TextureViewCompatibility.LayoutIncompatible) + if (copy && other.Compatibility <= TextureViewCompatibility.LayoutIncompatible) { // Any of the group's views may share compatibility, even if the parents do not fully. - CreateCopyDependency(other.Group, false); + CreateCopyDependency(other.Group, false, other.Compatibility); } _incompatibleOverlaps.Add(other); diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureGroupHandle.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureGroupHandle.cs index 1f2ee1a47..7aef9775c 100644 --- a/src/Ryujinx.Graphics.Gpu/Image/TextureGroupHandle.cs +++ b/src/Ryujinx.Graphics.Gpu/Image/TextureGroupHandle.cs @@ -1,3 +1,5 @@ +using Ryujinx.Common.Memory; +using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.Gpu.Synchronization; using Ryujinx.Memory.Tracking; using System; @@ -106,6 +108,12 @@ namespace Ryujinx.Graphics.Gpu.Image /// public TextureGroupHandle DeferredCopy { get; set; } + /// + /// Indicates whether the pending deferred copy must preserve the exact raw bytes + /// instead of using a regular texture copy. + /// + public bool DeferredCopyRaw { get; set; } + /// /// Create a new texture group handle, representing a range of views in a storage texture. /// @@ -160,8 +168,9 @@ namespace Ryujinx.Graphics.Gpu.Image } /// - /// The action to perform when a memory tracking handle is flipped to dirty. - /// This notifies overlapping textures that the memory needs to be synchronized. + /// Handles a memory tracking region becoming dirty. + /// This notifies all overlapping textures that their memory must be synchronized + /// and discards any pending deferred copy state. /// private void DirtyAction() { @@ -177,7 +186,7 @@ namespace Ryujinx.Graphics.Gpu.Image } } - DeferredCopy = null; + ClearDeferredCopy(); } /// @@ -186,7 +195,7 @@ namespace Ryujinx.Graphics.Gpu.Image /// public void DiscardData() { - DeferredCopy = null; + ClearDeferredCopy(); foreach (RegionHandle handle in Handles) { @@ -469,14 +478,22 @@ namespace Ryujinx.Graphics.Gpu.Image return syncpoint || !lastInBuffer; } + private void ClearDeferredCopy() + { + DeferredCopy = null; + DeferredCopyRaw = false; + } + /// - /// Signal that a copy dependent texture has been modified, and must have its data copied to this one. + /// Defers a copy from another texture group handle until this handle is next synchronized. /// - /// The texture handle that must defer a copy to this one - public void DeferCopy(TextureGroupHandle copyFrom) + /// The texture group handle containing the source data + /// True if the deferred copy must preserve the exact raw bytes; false to use a regular texture copy + public void DeferCopy(TextureGroupHandle copyFrom, bool rawCopy = false) { Modified = false; DeferredCopy = copyFrom; + DeferredCopyRaw = rawCopy; _group.Storage.SignalGroupDirty(); @@ -487,11 +504,12 @@ namespace Ryujinx.Graphics.Gpu.Image } /// - /// Create a copy dependency between this handle, and another. + /// Creates a two-way copy dependency between this handle and another handle. /// - /// 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) + /// The other handle participating in the dependency + /// True if a pending copy from this handle should also be propagated to the other handle's existing dependencies; otherwise, false + /// True if the dependency must use exact raw byte copies; false to use regular texture copies + public void CreateCopyDependency(TextureGroupHandle other, bool copyToOther = false, bool rawCopy = false) { // Does this dependency already exist? foreach (TextureDependency existing in Dependencies) @@ -506,8 +524,8 @@ namespace Ryujinx.Graphics.Gpu.Image _group.HasCopyDependencies = true; other._group.HasCopyDependencies = true; - TextureDependency dependency = new(this); - TextureDependency otherDependency = new(other); + TextureDependency dependency = new(this, rawCopy); + TextureDependency otherDependency = new(other, rawCopy); dependency.Other = otherDependency; otherDependency.Other = dependency; @@ -515,6 +533,11 @@ namespace Ryujinx.Graphics.Gpu.Image Dependencies.Add(dependency); other.Dependencies.Add(otherDependency); + if (rawCopy) + { + return; + } + // Recursively create dependency: // All of this handle's dependencies must depend on the other. foreach (TextureDependency existing in Dependencies.ToArray()) @@ -559,19 +582,24 @@ namespace Ryujinx.Graphics.Gpu.Image } /// - /// Perform a copy from the provided handle to this one, or perform a deferred copy if none is provided. + /// Copies texture data from the provided handle to this handle, + /// or fulfills the pending deferred copy when no source handle is provided. + /// Depending on the dependency mode, the operation uses either a regular texture copy + /// or an exact raw byte copy. /// - /// GPU context to register sync for modified handles - /// 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 + /// The GPU context used to register synchronization for modified handles + /// The handle to copy from, or null to use and acknowledge the pending deferred copy + /// True if the copy was performed; otherwise, false public bool Copy(GpuContext context, TextureGroupHandle fromHandle = null) { bool result = false; bool shouldCopy = false; + bool rawCopy = false; if (fromHandle == null) { fromHandle = DeferredCopy; + rawCopy = DeferredCopyRaw; if (fromHandle != null) { @@ -584,7 +612,7 @@ namespace Ryujinx.Graphics.Gpu.Image if (fromHandle._bindCount == 0) { // Repeat the copy in future if the bind count is greater than 0. - DeferredCopy = null; + ClearDeferredCopy(); } } } @@ -606,12 +634,35 @@ namespace Ryujinx.Graphics.Gpu.Image to.PropagateScale(from); } - from.HostTexture.CopyTo( - to.HostTexture, - fromHandle._firstLayer, - _firstLayer, - fromHandle._firstLevel, - _firstLevel); + if (rawCopy) + { + PinnedSpan pinned = from.HostTexture.GetData(fromHandle._firstLayer, fromHandle._firstLevel); + try + { + ReadOnlySpan data = pinned.Get(); + long targetSize = (long)to.Width * to.Height * to.Info.GetDepth() * to.Info.FormatInfo.BytesPerPixel; + + if (targetSize <= 0 || targetSize > int.MaxValue || data.Length != targetSize) + { + return false; + } + + to.HostTexture.SetData(MemoryOwner.RentCopy(data), _firstLayer, _firstLevel); + } + finally + { + pinned.Dispose(); + } + } + else + { + from.HostTexture.CopyTo( + to.HostTexture, + fromHandle._firstLayer, + _firstLayer, + fromHandle._firstLevel, + _firstLevel); + } if (fromHandle.Modified) {