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)
{