aboutsummaryrefslogtreecommitdiff
path: root/Ryujinx.Graphics.Gpu/Image
diff options
context:
space:
mode:
authorriperiperi <rhy3756547@hotmail.com>2021-03-02 22:30:54 +0000
committerGitHub <noreply@github.com>2021-03-02 19:30:54 -0300
commitb530f0e1104723b894695b2860cf0f568f24cc9a (patch)
tree04f6b7d76d334ddc5c0378f6aa00b98739bc793e /Ryujinx.Graphics.Gpu/Image
parent7a90abc03555f41ba7589bc8e1f714839f9e2fed (diff)
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.
Diffstat (limited to 'Ryujinx.Graphics.Gpu/Image')
-rw-r--r--Ryujinx.Graphics.Gpu/Image/Texture.cs289
-rw-r--r--Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs41
-rw-r--r--Ryujinx.Graphics.Gpu/Image/TextureDependency.cs37
-rw-r--r--Ryujinx.Graphics.Gpu/Image/TextureGroup.cs971
-rw-r--r--Ryujinx.Graphics.Gpu/Image/TextureGroupHandle.cs327
-rw-r--r--Ryujinx.Graphics.Gpu/Image/TextureInfo.cs25
-rw-r--r--Ryujinx.Graphics.Gpu/Image/TextureManager.cs195
7 files changed, 1750 insertions, 135 deletions
diff --git a/Ryujinx.Graphics.Gpu/Image/Texture.cs b/Ryujinx.Graphics.Gpu/Image/Texture.cs
index 6dfe4628..4d4091cb 100644
--- a/Ryujinx.Graphics.Gpu/Image/Texture.cs
+++ b/Ryujinx.Graphics.Gpu/Image/Texture.cs
@@ -51,6 +51,11 @@ namespace Ryujinx.Graphics.Gpu.Image
public TextureScaleMode ScaleMode { get; private set; }
/// <summary>
+ /// Group that this texture belongs to. Manages read/write memory tracking.
+ /// </summary>
+ public TextureGroup Group { get; private set; }
+
+ /// <summary>
/// Set when a texture has been modified by the Host GPU since it was last flushed.
/// </summary>
public bool IsModified { get; internal set; }
@@ -63,10 +68,11 @@ namespace Ryujinx.Graphics.Gpu.Image
private int _depth;
private int _layers;
- private int _firstLayer;
- private int _firstLevel;
+ public int FirstLayer { get; private set; }
+ public int FirstLevel { get; private set; }
private bool _hasData;
+ private bool _dirty = true;
private int _updateCount;
private byte[] _currentData;
@@ -100,11 +106,19 @@ namespace Ryujinx.Graphics.Gpu.Image
public MultiRange Range { get; private set; }
/// <summary>
+ /// Layer size in bytes.
+ /// </summary>
+ public int LayerSize => _sizeInfo.LayerSize;
+
+ /// <summary>
/// Texture size in bytes.
/// </summary>
public ulong Size => (ulong)_sizeInfo.TotalSize;
- private GpuRegionHandle _memoryTracking;
+ /// <summary>
+ /// Whether or not the texture belongs is a view.
+ /// </summary>
+ public bool IsView => _viewStorage != this;
private int _referenceCount;
@@ -131,8 +145,8 @@ namespace Ryujinx.Graphics.Gpu.Image
{
InitializeTexture(context, info, sizeInfo, range);
- _firstLayer = firstLayer;
- _firstLevel = firstLevel;
+ FirstLayer = firstLayer;
+ FirstLevel = firstLevel;
ScaleFactor = scaleFactor;
ScaleMode = scaleMode;
@@ -186,8 +200,6 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="withData">True if the texture is to be initialized with data</param>
public void InitializeData(bool isView, bool withData = false)
{
- _memoryTracking = _context.PhysicalMemory.BeginTracking(Range);
-
if (withData)
{
Debug.Assert(!isView);
@@ -203,12 +215,13 @@ namespace Ryujinx.Graphics.Gpu.Image
}
else
{
- // Don't update this texture the next time we synchronize.
- ConsumeModified();
_hasData = true;
if (!isView)
{
+ // Don't update this texture the next time we synchronize.
+ ConsumeModified();
+
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.
@@ -222,6 +235,18 @@ namespace Ryujinx.Graphics.Gpu.Image
}
/// <summary>
+ /// Initialize a new texture group with this texture as storage.
+ /// </summary>
+ /// <param name="hasLayerViews">True if the texture will have layer views</param>
+ /// <param name="hasMipViews">True if the texture will have mip views</param>
+ public void InitializeGroup(bool hasLayerViews, bool hasMipViews)
+ {
+ Group = new TextureGroup(_context, this);
+
+ Group.Initialize(ref _sizeInfo, hasLayerViews, hasMipViews);
+ }
+
+ /// <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
@@ -240,8 +265,8 @@ namespace Ryujinx.Graphics.Gpu.Image
info,
sizeInfo,
range,
- _firstLayer + firstLayer,
- _firstLevel + firstLevel,
+ FirstLayer + firstLayer,
+ FirstLevel + firstLevel,
ScaleFactor,
ScaleMode);
@@ -259,11 +284,26 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="texture">The child texture</param>
private void AddView(Texture texture)
{
- DisableMemoryTracking();
+ IncrementReferenceCount();
_views.Add(texture);
texture._viewStorage = this;
+
+ Group.UpdateViews(_views);
+
+ if (texture.Group != null && texture.Group != Group)
+ {
+ if (texture.Group.Storage == texture)
+ {
+ // This texture's group is no longer used.
+ Group.Inherit(texture.Group);
+
+ texture.Group.Dispose();
+ }
+ }
+
+ texture.Group = Group;
}
/// <summary>
@@ -276,7 +316,27 @@ namespace Ryujinx.Graphics.Gpu.Image
texture._viewStorage = texture;
- DeleteIfNotUsed();
+ DecrementReferenceCount();
+ }
+
+ /// <summary>
+ /// Create a copy dependency to a texture that is view compatible with this one.
+ /// When either texture is modified, the texture data will be copied to the other to keep them in sync.
+ /// This is essentially an emulated view, useful for handling multiple view parents or format incompatibility.
+ /// This also forces a copy on creation, to or from the given texture to get them in sync immediately.
+ /// </summary>
+ /// <param name="contained">The view compatible texture to create a dependency to</param>
+ /// <param name="layer">The base layer of the given texture relative to this one</param>
+ /// <param name="level">The base level of the given texture relative to this one</param>
+ /// <param name="copyTo">True if this texture is first copied to the given one, false for the opposite direction</param>
+ public void CreateCopyDependency(Texture contained, int layer, int level, bool copyTo)
+ {
+ if (contained.Group == Group)
+ {
+ return;
+ }
+
+ Group.CreateCopyDependency(contained, FirstLayer + layer, FirstLevel + level, copyTo);
}
/// <summary>
@@ -294,12 +354,12 @@ namespace Ryujinx.Graphics.Gpu.Image
int blockWidth = Info.FormatInfo.BlockWidth;
int blockHeight = Info.FormatInfo.BlockHeight;
- width <<= _firstLevel;
- height <<= _firstLevel;
+ width <<= FirstLevel;
+ height <<= FirstLevel;
if (Target == Target.Texture3D)
{
- depthOrLayers <<= _firstLevel;
+ depthOrLayers <<= FirstLevel;
}
else
{
@@ -310,14 +370,14 @@ namespace Ryujinx.Graphics.Gpu.Image
foreach (Texture view in _viewStorage._views)
{
- int viewWidth = Math.Max(1, width >> view._firstLevel);
- int viewHeight = Math.Max(1, height >> view._firstLevel);
+ int viewWidth = Math.Max(1, width >> view.FirstLevel);
+ int viewHeight = Math.Max(1, height >> view.FirstLevel);
int viewDepthOrLayers;
if (view.Info.Target == Target.Texture3D)
{
- viewDepthOrLayers = Math.Max(1, depthOrLayers >> view._firstLevel);
+ viewDepthOrLayers = Math.Max(1, depthOrLayers >> view.FirstLevel);
}
else
{
@@ -329,16 +389,6 @@ namespace Ryujinx.Graphics.Gpu.Image
}
/// <summary>
- /// Disables memory tracking on this texture. Currently used for view containers, as we assume their views are covering all memory regions.
- /// Textures with disabled memory tracking also cannot flush in most circumstances.
- /// </summary>
- public void DisableMemoryTracking()
- {
- _memoryTracking?.Dispose();
- _memoryTracking = null;
- }
-
- /// <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.
@@ -393,7 +443,7 @@ namespace Ryujinx.Graphics.Gpu.Image
if (_viewStorage != this)
{
- ReplaceStorage(_viewStorage.HostTexture.CreateView(createInfo, _firstLayer, _firstLevel));
+ ReplaceStorage(_viewStorage.HostTexture.CreateView(createInfo, FirstLayer, FirstLevel));
}
else
{
@@ -495,7 +545,7 @@ namespace Ryujinx.Graphics.Gpu.Image
view.ScaleFactor = scale;
TextureCreateInfo viewCreateInfo = TextureManager.GetCreateInfo(view.Info, _context.Capabilities, scale);
- ITexture newView = HostTexture.CreateView(viewCreateInfo, view._firstLayer - _firstLayer, view._firstLevel - _firstLevel);
+ ITexture newView = HostTexture.CreateView(viewCreateInfo, view.FirstLayer - FirstLayer, view.FirstLevel - FirstLevel);
view.ReplaceStorage(newView);
view.ScaleMode = newScaleMode;
@@ -517,17 +567,10 @@ namespace Ryujinx.Graphics.Gpu.Image
/// Checks if the memory for this texture was modified, and returns true if it was.
/// The modified flags are consumed as a result.
/// </summary>
- /// <remarks>
- /// If there is no memory tracking for this texture, it will always report as modified.
- /// </remarks>
/// <returns>True if the texture was modified, false otherwise.</returns>
public bool ConsumeModified()
{
- bool wasDirty = _memoryTracking?.Dirty ?? true;
-
- _memoryTracking?.Reprotect();
-
- return wasDirty;
+ return Group.ConsumeDirty(this);
}
/// <summary>
@@ -544,18 +587,43 @@ namespace Ryujinx.Graphics.Gpu.Image
return;
}
+ if (!_dirty)
+ {
+ return;
+ }
+
+ _dirty = false;
+
if (_hasData)
{
- if (_memoryTracking?.Dirty != true)
- {
- return;
- }
+ Group.SynchronizeMemory(this);
+ }
+ else
+ {
+ Group.ConsumeDirty(this);
+ SynchronizeFull();
+ }
+ }
+ /// <summary>
+ /// Signal that this texture is dirty, indicating that the texture group must be checked.
+ /// </summary>
+ public void SignalGroupDirty()
+ {
+ _dirty = true;
+ }
+
+ /// <summary>
+ /// Fully synchronizes guest and host memory.
+ /// This will replace the entire texture with the data present in guest memory.
+ /// </summary>
+ public void SynchronizeFull()
+ {
+ if (_hasData)
+ {
BlacklistScale();
}
- _memoryTracking?.Reprotect();
-
ReadOnlySpan<byte> data = _context.PhysicalMemory.GetSpan(Range);
IsModified = false;
@@ -596,7 +664,7 @@ namespace Ryujinx.Graphics.Gpu.Image
{
BlacklistScale();
- _memoryTracking?.Reprotect();
+ Group.ConsumeDirty(this);
IsModified = false;
@@ -606,17 +674,45 @@ namespace Ryujinx.Graphics.Gpu.Image
}
/// <summary>
+ /// Uploads new texture data to the host GPU for a specific layer/level.
+ /// </summary>
+ /// <param name="data">New data</param>
+ /// <param name="layer">Target layer</param>
+ /// <param name="level">Target level</param>
+ public void SetData(ReadOnlySpan<byte> data, int layer, int level)
+ {
+ BlacklistScale();
+
+ HostTexture.SetData(data, layer, level);
+
+ _currentData = null;
+
+ _hasData = true;
+ }
+
+ /// <summary>
/// Converts texture data to a format and layout that is supported by the host GPU.
/// </summary>
/// <param name="data">Data to be converted</param>
/// <returns>Converted data</returns>
- private ReadOnlySpan<byte> ConvertToHostCompatibleFormat(ReadOnlySpan<byte> data)
+ public ReadOnlySpan<byte> ConvertToHostCompatibleFormat(ReadOnlySpan<byte> data, int level = 0, bool single = false)
{
+ int width = Info.Width;
+ int height = Info.Height;
+
+ int depth = single ? 1 : _depth;
+ int layers = single ? 1 : _layers;
+ int levels = single ? 1 : Info.Levels;
+
+ width = Math.Max(width >> level, 1);
+ height = Math.Max(height >> level, 1);
+ depth = Math.Max(depth >> level, 1);
+
if (Info.IsLinear)
{
data = LayoutConverter.ConvertLinearStridedToLinear(
- Info.Width,
- Info.Height,
+ width,
+ height,
Info.FormatInfo.BlockWidth,
Info.FormatInfo.BlockHeight,
Info.Stride,
@@ -626,11 +722,11 @@ namespace Ryujinx.Graphics.Gpu.Image
else
{
data = LayoutConverter.ConvertBlockLinearToLinear(
- Info.Width,
- Info.Height,
- _depth,
- Info.Levels,
- _layers,
+ width,
+ height,
+ depth,
+ levels,
+ layers,
Info.FormatInfo.BlockWidth,
Info.FormatInfo.BlockHeight,
Info.FormatInfo.BytesPerPixel,
@@ -650,11 +746,11 @@ namespace Ryujinx.Graphics.Gpu.Image
data.ToArray(),
Info.FormatInfo.BlockWidth,
Info.FormatInfo.BlockHeight,
- Info.Width,
- Info.Height,
- _depth,
- Info.Levels,
- _layers,
+ width,
+ height,
+ depth,
+ levels,
+ layers,
out Span<byte> decoded))
{
string texInfo = $"{Info.Target} {Info.FormatInfo.Format} {Info.Width}x{Info.Height}x{Info.DepthOrLayers} levels {Info.Levels}";
@@ -666,11 +762,11 @@ namespace Ryujinx.Graphics.Gpu.Image
}
else if (Target == Target.Texture3D && Info.FormatInfo.Format.IsBc4())
{
- data = BCnDecoder.DecodeBC4(data, Info.Width, Info.Height, _depth, Info.Levels, _layers, Info.FormatInfo.Format == Format.Bc4Snorm);
+ data = BCnDecoder.DecodeBC4(data, width, height, depth, levels, layers, Info.FormatInfo.Format == Format.Bc4Snorm);
}
else if (Target == Target.Texture3D && Info.FormatInfo.Format.IsBc5())
{
- data = BCnDecoder.DecodeBC5(data, Info.Width, Info.Height, _depth, Info.Levels, _layers, Info.FormatInfo.Format == Format.Bc5Snorm);
+ data = BCnDecoder.DecodeBC5(data, width, height, depth, levels, layers, Info.FormatInfo.Format == Format.Bc5Snorm);
}
return data;
@@ -710,7 +806,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary>
public void ExternalFlush(ulong address, ulong size)
{
- if (!IsModified || _memoryTracking == null)
+ if (!IsModified)
{
return;
}
@@ -869,7 +965,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="firstLayer">Texture view initial layer on this texture</param>
/// <param name="firstLevel">Texture view first mipmap level on this texture</param>
/// <returns>The level of compatiblilty a view with the given parameters created from this texture has</returns>
- public TextureViewCompatibility IsViewCompatible(TextureInfo info, MultiRange range, out int firstLayer, out int firstLevel)
+ public TextureViewCompatibility IsViewCompatible(TextureInfo info, MultiRange range, int layerSize, out int firstLayer, out int firstLevel)
{
int offset = Range.FindOffset(range);
@@ -892,15 +988,17 @@ namespace Ryujinx.Graphics.Gpu.Image
return TextureViewCompatibility.Incompatible;
}
- if (!TextureCompatibility.ViewFormatCompatible(Info, info))
+ if (info.GetSlices() > 1 && LayerSize != layerSize)
{
return TextureViewCompatibility.Incompatible;
}
TextureViewCompatibility result = TextureViewCompatibility.Full;
+ result = TextureCompatibility.PropagateViewCompatibility(result, TextureCompatibility.ViewFormatCompatible(Info, info));
result = TextureCompatibility.PropagateViewCompatibility(result, TextureCompatibility.ViewSizeMatches(Info, info, firstLevel));
result = TextureCompatibility.PropagateViewCompatibility(result, TextureCompatibility.ViewTargetCompatible(Info, info));
+ result = TextureCompatibility.PropagateViewCompatibility(result, TextureCompatibility.ViewSubImagesInBounds(Info, info, firstLayer, firstLevel));
return (Info.SamplesInX == info.SamplesInX &&
Info.SamplesInY == info.SamplesInY) ? result : TextureViewCompatibility.Incompatible;
@@ -1003,14 +1101,37 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="firstLevel">The first level of the view</param>
public void ReplaceView(Texture parent, TextureInfo info, ITexture hostTexture, int firstLayer, int firstLevel)
{
+ IncrementReferenceCount();
parent._viewStorage.SynchronizeMemory();
+
+ // If this texture has views, they must be given to the new parent.
+ if (_views.Count > 0)
+ {
+ Texture[] viewCopy = _views.ToArray();
+
+ foreach (Texture view in viewCopy)
+ {
+ TextureCreateInfo createInfo = TextureManager.GetCreateInfo(view.Info, _context.Capabilities, ScaleFactor);
+
+ ITexture newView = parent.HostTexture.CreateView(createInfo, view.FirstLayer + firstLayer, view.FirstLevel + firstLevel);
+
+ view.ReplaceView(parent, view.Info, newView, view.FirstLayer + firstLayer, view.FirstLevel + firstLevel);
+ }
+ }
+
ReplaceStorage(hostTexture);
- _firstLayer = parent._firstLayer + firstLayer;
- _firstLevel = parent._firstLevel + firstLevel;
+ if (_viewStorage != this)
+ {
+ _viewStorage.RemoveView(this);
+ }
+
+ FirstLayer = parent.FirstLayer + firstLayer;
+ FirstLevel = parent.FirstLevel + firstLevel;
parent._viewStorage.AddView(this);
SetInfo(info);
+ DecrementReferenceCount();
}
/// <summary>
@@ -1031,14 +1152,28 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary>
public void SignalModified()
{
- IsModified = true;
-
- if (_viewStorage != this)
+ bool wasModified = IsModified;
+ if (!wasModified || Group.HasCopyDependencies)
{
- _viewStorage.SignalModified();
+ IsModified = true;
+ Group.SignalModified(this, !wasModified);
}
+ }
+
+ /// <summary>
+ /// Signals that a texture has been bound, or has been unbound.
+ /// During this time, lazy copies will not clear the dirty flag.
+ /// </summary>
+ /// <param name="bound">True if the texture has been bound, false if it has been unbound</param>
+ public void SignalModifying(bool bound)
+ {
+ bool wasModified = IsModified;
- _memoryTracking?.RegisterAction(ExternalFlush);
+ if (!wasModified || Group.HasCopyDependencies)
+ {
+ IsModified = true;
+ Group.SignalModifying(this, bound, !wasModified);
+ }
}
/// <summary>
@@ -1066,7 +1201,7 @@ namespace Ryujinx.Graphics.Gpu.Image
foreach (Texture view in _views)
{
- if (texture.IsViewCompatible(view.Info, view.Range, out _, out _) != TextureViewCompatibility.Incompatible)
+ if (texture.IsViewCompatible(view.Info, view.Range, view.LayerSize, out _, out _) != TextureViewCompatibility.Incompatible)
{
return true;
}
@@ -1148,10 +1283,6 @@ namespace Ryujinx.Graphics.Gpu.Image
public void Unmapped()
{
IsModified = false; // We shouldn't flush this texture, as its memory is no longer mapped.
-
- var tracking = _memoryTracking;
- tracking?.Reprotect();
- tracking?.RegisterAction(null);
}
/// <summary>
@@ -1162,7 +1293,11 @@ namespace Ryujinx.Graphics.Gpu.Image
DisposeTextures();
Disposed?.Invoke(this);
- _memoryTracking?.Dispose();
+
+ if (Group.Storage == this)
+ {
+ Group.Dispose();
+ }
}
}
} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs b/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs
index e3574be5..d613612f 100644
--- a/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs
+++ b/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs
@@ -215,7 +215,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <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>
- /// <returns>True if the sizes are compatible, false otherwise</returns>
+ /// <returns>The view compatibility level of the view sizes</returns>
public static TextureViewCompatibility ViewSizeMatches(TextureInfo lhs, TextureInfo rhs, int level)
{
Size size = GetAlignedSize(lhs, level);
@@ -236,6 +236,27 @@ namespace Ryujinx.Graphics.Gpu.Image
}
/// <summary>
+ /// Checks if the potential child texture fits within the level and layer bounds of the parent.
+ /// </summary>
+ /// <param name="parent">Texture information for the parent</param>
+ /// <param name="child">Texture information for the child</param>
+ /// <param name="layer">Base layer of the child texture</param>
+ /// <param name="level">Base level of the child texture</param>
+ /// <returns>Full compatiblity if the child's layer and level count fit within the parent, incompatible otherwise</returns>
+ public static TextureViewCompatibility ViewSubImagesInBounds(TextureInfo parent, TextureInfo child, int layer, int level)
+ {
+ if (level + child.Levels <= parent.Levels &&
+ layer + child.GetSlices() <= parent.GetSlices())
+ {
+ return TextureViewCompatibility.Full;
+ }
+ else
+ {
+ return TextureViewCompatibility.Incompatible;
+ }
+ }
+
+ /// <summary>
/// Checks if the texture sizes of the supplied texture informations match.
/// </summary>
/// <param name="lhs">Texture information to compare</param>
@@ -382,10 +403,22 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary>
/// <param name="lhs">Texture information of the texture view</param>
/// <param name="rhs">Texture information of the texture view</param>
- /// <returns>True if the formats are compatible, false otherwise</returns>
- public static bool ViewFormatCompatible(TextureInfo lhs, TextureInfo rhs)
+ /// <returns>The view compatibility level of the texture formats</returns>
+ public static TextureViewCompatibility ViewFormatCompatible(TextureInfo lhs, TextureInfo rhs)
{
- return FormatCompatible(lhs.FormatInfo, rhs.FormatInfo);
+ if (FormatCompatible(lhs.FormatInfo, rhs.FormatInfo))
+ {
+ if (lhs.FormatInfo.IsCompressed != rhs.FormatInfo.IsCompressed)
+ {
+ return TextureViewCompatibility.CopyOnly;
+ }
+ else
+ {
+ return TextureViewCompatibility.Full;
+ }
+ }
+
+ return TextureViewCompatibility.Incompatible;
}
/// <summary>
diff --git a/Ryujinx.Graphics.Gpu/Image/TextureDependency.cs b/Ryujinx.Graphics.Gpu/Image/TextureDependency.cs
new file mode 100644
index 00000000..269ddbd9
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Image/TextureDependency.cs
@@ -0,0 +1,37 @@
+namespace Ryujinx.Graphics.Gpu.Image
+{
+ /// <summary>
+ /// One side of a two-way dependency between one texture view and another.
+ /// Contains a reference to the handle owning the dependency, and the other dependency.
+ /// </summary>
+ class TextureDependency
+ {
+ /// <summary>
+ /// The handle that owns this dependency.
+ /// </summary>
+ public TextureGroupHandle Handle;
+
+ /// <summary>
+ /// The other dependency linked to this one, which belongs to another handle.
+ /// </summary>
+ public TextureDependency Other;
+
+ /// <summary>
+ /// Create a new texture dependency.
+ /// </summary>
+ /// <param name="handle">The handle that owns the dependency</param>
+ public TextureDependency(TextureGroupHandle handle)
+ {
+ Handle = handle;
+ }
+
+ /// <summary>
+ /// Signal that the owner of this dependency has been modified,
+ /// meaning that the other dependency's handle must defer a copy from it.
+ /// </summary>
+ public void SignalModified()
+ {
+ Other.Handle.DeferCopy(Handle);
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs b/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs
new file mode 100644
index 00000000..5d150559
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs
@@ -0,0 +1,971 @@
+using Ryujinx.Common;
+using Ryujinx.Cpu.Tracking;
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.Texture;
+using Ryujinx.Memory.Range;
+using System;
+using System.Collections.Generic;
+
+namespace Ryujinx.Graphics.Gpu.Image
+{
+ /// <summary>
+ /// A texture group represents a group of textures that belong to the same storage.
+ /// When views are created, this class will track memory accesses for them separately.
+ /// The group iteratively adds more granular tracking as views of different kinds are added.
+ /// Note that a texture group can be absorbed into another when it becomes a view parent.
+ /// </summary>
+ class TextureGroup : IDisposable
+ {
+ private const int StrideAlignment = 32;
+ private const int GobAlignment = 64;
+
+ private delegate void HandlesCallbackDelegate(int baseHandle, int regionCount, bool split = false);
+
+ /// <summary>
+ /// The storage texture associated with this group.
+ /// </summary>
+ public Texture Storage { get; }
+
+ /// <summary>
+ /// Indicates if the texture has copy dependencies. If true, then all modifications
+ /// must be signalled to the group, rather than skipping ones still to be flushed.
+ /// </summary>
+ public bool HasCopyDependencies { get; set; }
+
+ private GpuContext _context;
+
+ private int[] _allOffsets;
+ private int[] _sliceSizes;
+ private bool _is3D;
+ private bool _hasMipViews;
+ private bool _hasLayerViews;
+ private int _layers;
+ private int _levels;
+
+ private MultiRange TextureRange => Storage.Range;
+
+ /// <summary>
+ /// The views list from the storage texture.
+ /// </summary>
+ private List<Texture> _views;
+ private TextureGroupHandle[] _handles;
+ private bool[] _loadNeeded;
+
+ /// <summary>
+ /// Create a new texture group.
+ /// </summary>
+ /// <param name="context">GPU context that the texture group belongs to</param>
+ /// <param name="storage">The storage texture for this group</param>
+ public TextureGroup(GpuContext context, Texture storage)
+ {
+ Storage = storage;
+ _context = context;
+
+ _is3D = storage.Info.Target == Target.Texture3D;
+ _layers = storage.Info.GetSlices();
+ _levels = storage.Info.Levels;
+ }
+
+ /// <summary>
+ /// Initialize a new texture group's dirty regions and offsets.
+ /// </summary>
+ /// <param name="size">Size info for the storage texture</param>
+ /// <param name="hasLayerViews">True if the storage will have layer views</param>
+ /// <param name="hasMipViews">True if the storage will have mip views</param>
+ public void Initialize(ref SizeInfo size, bool hasLayerViews, bool hasMipViews)
+ {
+ _allOffsets = size.AllOffsets;
+ _sliceSizes = size.SliceSizes;
+
+ (_hasLayerViews, _hasMipViews) = PropagateGranularity(hasLayerViews, hasMipViews);
+
+ RecalculateHandleRegions();
+ }
+
+ /// <summary>
+ /// Consume the dirty flags for a given texture. The state is shared between views of the same layers and levels.
+ /// </summary>
+ /// <param name="texture">The texture being used</param>
+ /// <returns>True if a flag was dirty, false otherwise</returns>
+ public bool ConsumeDirty(Texture texture)
+ {
+ bool dirty = false;
+
+ EvaluateRelevantHandles(texture, (baseHandle, regionCount, split) =>
+ {
+ for (int i = 0; i < regionCount; i++)
+ {
+ TextureGroupHandle group = _handles[baseHandle + i];
+
+ foreach (CpuRegionHandle handle in group.Handles)
+ {
+ if (handle.Dirty)
+ {
+ handle.Reprotect();
+ dirty = true;
+ }
+ }
+ }
+ });
+
+ return dirty;
+ }
+
+ /// <summary>
+ /// Synchronize memory for a given texture.
+ /// If overlapping tracking handles are dirty, fully or partially synchronize the texture data.
+ /// </summary>
+ /// <param name="texture">The texture being used</param>
+ public void SynchronizeMemory(Texture texture)
+ {
+ EvaluateRelevantHandles(texture, (baseHandle, regionCount, split) =>
+ {
+ bool dirty = false;
+ bool anyModified = false;
+
+ for (int i = 0; i < regionCount; i++)
+ {
+ TextureGroupHandle group = _handles[baseHandle + i];
+
+ bool modified = group.Modified;
+ bool handleDirty = false;
+ bool handleModified = false;
+
+ foreach (CpuRegionHandle handle in group.Handles)
+ {
+ if (handle.Dirty)
+ {
+ handle.Reprotect();
+ handleDirty = true;
+ }
+ else
+ {
+ handleModified |= modified;
+ }
+ }
+
+ // Evaluate if any copy dependencies need to be fulfilled. A few rules:
+ // If the copy handle needs to be synchronized, prefer our own state.
+ // If we need to be synchronized and there is a copy present, prefer the copy.
+
+ if (group.NeedsCopy && group.Copy())
+ {
+ anyModified |= true; // The copy target has been modified.
+ handleDirty = false;
+ }
+ else
+ {
+ anyModified |= handleModified;
+ dirty |= handleDirty;
+ }
+
+ if (group.NeedsCopy)
+ {
+ // The texture we copied from is still being written to. Copy from it again the next time this texture is used.
+ texture.SignalGroupDirty();
+ }
+
+ _loadNeeded[baseHandle + i] = handleDirty;
+ }
+
+ if (dirty)
+ {
+ if (_handles.Length > 1 && (anyModified || split))
+ {
+ // Partial texture invalidation. Only update the layers/levels with dirty flags of the storage.
+
+ SynchronizePartial(baseHandle, regionCount);
+ }
+ else
+ {
+ // Full texture invalidation.
+
+ texture.SynchronizeFull();
+ }
+ }
+ });
+ }
+
+ /// <summary>
+ /// Synchronize part of the storage texture, represented by a given range of handles.
+ /// Only handles marked by the _loadNeeded array will be synchronized.
+ /// </summary>
+ /// <param name="baseHandle">The base index of the range of handles</param>
+ /// <param name="regionCount">The number of handles to synchronize</param>
+ private void SynchronizePartial(int baseHandle, int regionCount)
+ {
+ ReadOnlySpan<byte> fullData = _context.PhysicalMemory.GetSpan(Storage.Range);
+
+ for (int i = 0; i < regionCount; i++)
+ {
+ if (_loadNeeded[baseHandle + i])
+ {
+ var info = GetHandleInformation(baseHandle + i);
+ int offsetIndex = info.Index;
+
+ // Only one of these will be greater than 1, as partial sync is only called when there are sub-image views.
+ for (int layer = 0; layer < info.Layers; layer++)
+ {
+ for (int level = 0; level < info.Levels; level++)
+ {
+ int offset = _allOffsets[offsetIndex];
+ int endOffset = (offsetIndex + 1 == _allOffsets.Length) ? (int)Storage.Size : _allOffsets[offsetIndex + 1];
+ int size = endOffset - offset;
+
+ ReadOnlySpan<byte> data = fullData.Slice(offset, size);
+
+ data = Storage.ConvertToHostCompatibleFormat(data, info.BaseLevel, true);
+
+ Storage.SetData(data, info.BaseLayer, info.BaseLevel);
+
+ offsetIndex++;
+ }
+ }
+ }
+ }
+ }
+
+ /// <summary>
+ /// Signal that a texture in the group has been modified by the GPU.
+ /// </summary>
+ /// <param name="texture">The texture that has been modified</param>
+ /// <param name="registerAction">True if the flushing read action should be registered, false otherwise</param>
+ public void SignalModified(Texture texture, bool registerAction)
+ {
+ EvaluateRelevantHandles(texture, (baseHandle, regionCount, split) =>
+ {
+ for (int i = 0; i < regionCount; i++)
+ {
+ TextureGroupHandle group = _handles[baseHandle + i];
+
+ group.SignalModified();
+
+ if (registerAction)
+ {
+ RegisterAction(group);
+ }
+ }
+ });
+ }
+
+ /// <summary>
+ /// Signal that a texture in the group is actively bound, or has been unbound by the GPU.
+ /// </summary>
+ /// <param name="texture">The texture that has been modified</param>
+ /// <param name="bound">True if this texture is being bound, false if unbound</param>
+ /// <param name="registerAction">True if the flushing read action should be registered, false otherwise</param>
+ public void SignalModifying(Texture texture, bool bound, bool registerAction)
+ {
+ EvaluateRelevantHandles(texture, (baseHandle, regionCount, split) =>
+ {
+ for (int i = 0; i < regionCount; i++)
+ {
+ TextureGroupHandle group = _handles[baseHandle + i];
+
+ group.SignalModifying(bound);
+
+ if (registerAction)
+ {
+ RegisterAction(group);
+ }
+ }
+ });
+ }
+
+ /// <summary>
+ /// Register a read/write action to flush for a texture group.
+ /// </summary>
+ /// <param name="group">The group to register an action for</param>
+ public void RegisterAction(TextureGroupHandle group)
+ {
+ foreach (CpuRegionHandle handle in group.Handles)
+ {
+ handle.RegisterAction((address, size) => FlushAction(group, address, size));
+ }
+ }
+
+ /// <summary>
+ /// Propagates the mip/layer view flags depending on the texture type.
+ /// When the most granular type of subresource has views, the other type of subresource must be segmented granularly too.
+ /// </summary>
+ /// <param name="hasLayerViews">True if the storage has layer views</param>
+ /// <param name="hasMipViews">True if the storage has mip views</param>
+ /// <returns>The input values after propagation</returns>
+ private (bool HasLayerViews, bool HasMipViews) PropagateGranularity(bool hasLayerViews, bool hasMipViews)
+ {
+ if (_is3D)
+ {
+ hasMipViews |= hasLayerViews;
+ }
+ else
+ {
+ hasLayerViews |= hasMipViews;
+ }
+
+ return (hasLayerViews, hasMipViews);
+ }
+
+ /// <summary>
+ /// Evaluate the range of tracking handles which a view texture overlaps with.
+ /// </summary>
+ /// <param name="texture">The texture to get handles for</param>
+ /// <param name="callback">
+ /// A function to be called with the base index of the range of handles for the given texture, and the number of handles it covers.
+ /// This can be called for multiple disjoint ranges, if required.
+ /// </param>
+ private void EvaluateRelevantHandles(Texture texture, HandlesCallbackDelegate callback)
+ {
+ if (texture == Storage || !(_hasMipViews || _hasLayerViews))
+ {
+ callback(0, _handles.Length);
+
+ return;
+ }
+
+ EvaluateRelevantHandles(texture.FirstLayer, texture.FirstLevel, texture.Info.GetSlices(), texture.Info.Levels, callback);
+ }
+
+ /// <summary>
+ /// Evaluate the range of tracking handles which a view texture overlaps with,
+ /// using the view's position and slice/level counts.
+ /// </summary>
+ /// <param name="firstLayer">The first layer of the texture</param>
+ /// <param name="firstLevel">The first level of the texture</param>
+ /// <param name="slices">The slice count of the texture</param>
+ /// <param name="levels">The level count of the texture</param>
+ /// <param name="callback">
+ /// A function to be called with the base index of the range of handles for the given texture, and the number of handles it covers.
+ /// This can be called for multiple disjoint ranges, if required.
+ /// </param>
+ private void EvaluateRelevantHandles(int firstLayer, int firstLevel, int slices, int levels, HandlesCallbackDelegate callback)
+ {
+ int targetLayerHandles = _hasLayerViews ? slices : 1;
+ int targetLevelHandles = _hasMipViews ? levels : 1;
+
+ if (_is3D)
+ {
+ // Future mip levels come after all layers of the last mip level. Each mipmap has less layers (depth) than the last.
+
+ if (!_hasLayerViews)
+ {
+ // When there are no layer views, the mips are at a consistent offset.
+
+ callback(firstLevel, targetLevelHandles);
+ }
+ else
+ {
+ (int levelIndex, int layerCount) = Get3DLevelRange(firstLevel);
+
+ if (levels > 1 && slices < _layers)
+ {
+ // The given texture only covers some of the depth of multiple mips. (a "depth slice")
+ // Callback with each mip's range separately.
+ // Can assume that the group is fully subdivided (both slices and levels > 1 for storage)
+
+ while (levels-- > 1)
+ {
+ callback(firstLayer + levelIndex, slices);
+
+ levelIndex += layerCount;
+ layerCount = Math.Max(layerCount >> 1, 1);
+ slices = Math.Max(layerCount >> 1, 1);
+ }
+ }
+ else
+ {
+ int totalSize = Math.Min(layerCount, slices);
+
+ while (levels-- > 1)
+ {
+ layerCount = Math.Max(layerCount >> 1, 1);
+ totalSize += layerCount;
+ }
+
+ callback(firstLayer + levelIndex, totalSize);
+ }
+ }
+ }
+ else
+ {
+ // Future layers come after all mipmaps of the last.
+ int levelHandles = _hasMipViews ? _levels : 1;
+
+ if (slices > 1 && levels < _levels)
+ {
+ // The given texture only covers some of the mipmaps of multiple slices. (a "mip slice")
+ // Callback with each layer's range separately.
+ // Can assume that the group is fully subdivided (both slices and levels > 1 for storage)
+
+ for (int i = 0; i < slices; i++)
+ {
+ callback(firstLevel + (firstLayer + i) * levelHandles, targetLevelHandles, true);
+ }
+ }
+ else
+ {
+ callback(firstLevel + firstLayer * levelHandles, targetLevelHandles + (targetLayerHandles - 1) * levelHandles);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Get the range of offsets for a given mip level of a 3D texture.
+ /// </summary>
+ /// <param name="level">The level to return</param>
+ /// <returns>Start index and count of offsets for the given level</returns>
+ private (int Index, int Count) Get3DLevelRange(int level)
+ {
+ int index = 0;
+ int count = _layers; // Depth. Halves with each mip level.
+
+ while (level-- > 0)
+ {
+ index += count;
+ count = Math.Max(count >> 1, 1);
+ }
+
+ return (index, count);
+ }
+
+ /// <summary>
+ /// Get view information for a single tracking handle.
+ /// </summary>
+ /// <param name="handleIndex">The index of the handle</param>
+ /// <returns>The layers and levels that the handle covers, and its index in the offsets array</returns>
+ private (int BaseLayer, int BaseLevel, int Levels, int Layers, int Index) GetHandleInformation(int handleIndex)
+ {
+ int baseLayer;
+ int baseLevel;
+ int levels = _hasMipViews ? 1 : _levels;
+ int layers = _hasLayerViews ? 1 : _layers;
+ int index;
+
+ if (_is3D)
+ {
+ if (_hasLayerViews)
+ {
+ // NOTE: Will also have mip views, or only one level in storage.
+
+ index = handleIndex;
+ baseLevel = 0;
+
+ int layerLevels = _levels;
+
+ while (handleIndex >= layerLevels)
+ {
+ handleIndex -= layerLevels;
+ baseLevel++;
+ layerLevels = Math.Max(layerLevels >> 1, 1);
+ }
+
+ baseLayer = handleIndex;
+ }
+ else
+ {
+ baseLayer = 0;
+ baseLevel = handleIndex;
+
+ (index, _) = Get3DLevelRange(baseLevel);
+ }
+ }
+ else
+ {
+ baseLevel = _hasMipViews ? handleIndex % _levels : 0;
+ baseLayer = _hasMipViews ? handleIndex / _levels : handleIndex;
+ index = baseLevel + baseLayer * _levels;
+ }
+
+ return (baseLayer, baseLevel, levels, layers, index);
+ }
+
+ /// <summary>
+ /// Gets the layer and level for a given view.
+ /// </summary>
+ /// <param name="index">The index of the view</param>
+ /// <returns>The layer and level of the specified view</returns>
+ private (int BaseLayer, int BaseLevel) GetLayerLevelForView(int index)
+ {
+ if (_is3D)
+ {
+ int baseLevel = 0;
+
+ int layerLevels = _layers;
+
+ while (index >= layerLevels)
+ {
+ index -= layerLevels;
+ baseLevel++;
+ layerLevels = Math.Max(layerLevels >> 1, 1);
+ }
+
+ return (index, baseLevel);
+ }
+ else
+ {
+ return (index / _levels, index % _levels);
+ }
+ }
+
+ /// <summary>
+ /// Find the byte offset of a given texture relative to the storage.
+ /// </summary>
+ /// <param name="texture">The texture to locate</param>
+ /// <returns>The offset of the texture in bytes</returns>
+ public int FindOffset(Texture texture)
+ {
+ return _allOffsets[GetOffsetIndex(texture.FirstLayer, texture.FirstLevel)];
+ }
+
+ /// <summary>
+ /// Find the offset index of a given layer and level.
+ /// </summary>
+ /// <param name="layer">The view layer</param>
+ /// <param name="level">The view level</param>
+ /// <returns>The offset index of the given layer and level</returns>
+ public int GetOffsetIndex(int layer, int level)
+ {
+ if (_is3D)
+ {
+ return layer + Get3DLevelRange(level).Index;
+ }
+ else
+ {
+ return level + layer * _levels;
+ }
+ }
+
+ /// <summary>
+ /// The action to perform when a memory tracking handle is flipped to dirty.
+ /// This notifies overlapping textures that the memory needs to be synchronized.
+ /// </summary>
+ /// <param name="groupHandle">The handle that a dirty flag was set on</param>
+ private void DirtyAction(TextureGroupHandle groupHandle)
+ {
+ // Notify all textures that belong to this handle.
+
+ Storage.SignalGroupDirty();
+
+ lock (groupHandle.Overlaps)
+ {
+ foreach (Texture overlap in groupHandle.Overlaps)
+ {
+ overlap.SignalGroupDirty();
+ }
+ }
+ }
+
+ /// <summary>
+ /// Generate a CpuRegionHandle for a given address and size range in CPU VA.
+ /// </summary>
+ /// <param name="address">The start address of the tracked region</param>
+ /// <param name="size">The size of the tracked region</param>
+ /// <returns>A CpuRegionHandle covering the given range</returns>
+ private CpuRegionHandle GenerateHandle(ulong address, ulong size)
+ {
+ return _context.PhysicalMemory.BeginTracking(address, size);
+ }
+
+ /// <summary>
+ /// Generate a TextureGroupHandle covering a specified range of views.
+ /// </summary>
+ /// <param name="viewStart">The start view of the handle</param>
+ /// <param name="views">The number of views to cover</param>
+ /// <returns>A TextureGroupHandle covering the given views</returns>
+ private TextureGroupHandle GenerateHandles(int viewStart, int views)
+ {
+ int offset = _allOffsets[viewStart];
+ int endOffset = (viewStart + views == _allOffsets.Length) ? (int)Storage.Size : _allOffsets[viewStart + views];
+ int size = endOffset - offset;
+
+ var result = new List<CpuRegionHandle>();
+
+ for (int i = 0; i < TextureRange.Count; i++)
+ {
+ MemoryRange item = TextureRange.GetSubRange(i);
+ int subRangeSize = (int)item.Size;
+
+ int sliceStart = Math.Clamp(offset, 0, subRangeSize);
+ int sliceEnd = Math.Clamp(endOffset, 0, subRangeSize);
+
+ if (sliceStart != sliceEnd)
+ {
+ result.Add(GenerateHandle(item.Address + (ulong)sliceStart, (ulong)(sliceEnd - sliceStart)));
+ }
+
+ offset -= subRangeSize;
+ endOffset -= subRangeSize;
+
+ if (endOffset <= 0)
+ {
+ break;
+ }
+ }
+
+ (int firstLayer, int firstLevel) = GetLayerLevelForView(viewStart);
+
+ if (_hasLayerViews && _hasMipViews)
+ {
+ size = _sliceSizes[firstLevel];
+ }
+
+ var groupHandle = new TextureGroupHandle(this, _allOffsets[viewStart], (ulong)size, _views, firstLayer, firstLevel, result.ToArray());
+
+ foreach (CpuRegionHandle handle in result)
+ {
+ handle.RegisterDirtyEvent(() => DirtyAction(groupHandle));
+ }
+
+ return groupHandle;
+ }
+
+ /// <summary>
+ /// Update the views in this texture group, rebuilding the memory tracking if required.
+ /// </summary>
+ /// <param name="views">The views list of the storage texture</param>
+ public void UpdateViews(List<Texture> views)
+ {
+ // This is saved to calculate overlapping views for each handle.
+ _views = views;
+
+ bool layerViews = _hasLayerViews;
+ bool mipViews = _hasMipViews;
+ bool regionsRebuilt = false;
+
+ if (!(layerViews && mipViews))
+ {
+ foreach (Texture view in views)
+ {
+ if (view.Info.GetSlices() < _layers)
+ {
+ layerViews = true;
+ }
+
+ if (view.Info.Levels < _levels)
+ {
+ mipViews = true;
+ }
+ }
+
+ (layerViews, mipViews) = PropagateGranularity(layerViews, mipViews);
+
+ if (layerViews != _hasLayerViews || mipViews != _hasMipViews)
+ {
+ _hasLayerViews = layerViews;
+ _hasMipViews = mipViews;
+
+ RecalculateHandleRegions();
+ regionsRebuilt = true;
+ }
+ }
+
+ if (!regionsRebuilt)
+ {
+ // Must update the overlapping views on all handles, but only if they were not just recreated.
+
+ foreach (TextureGroupHandle handle in _handles)
+ {
+ handle.RecalculateOverlaps(this, views);
+ }
+ }
+
+ Storage.SignalGroupDirty();
+ foreach (Texture texture in views)
+ {
+ texture.SignalGroupDirty();
+ }
+ }
+
+ /// <summary>
+ /// Inherit handle state from an old set of handles, such as modified and dirty flags.
+ /// </summary>
+ /// <param name="oldHandles">The set of handles to inherit state from</param>
+ /// <param name="handles">The set of handles inheriting the state</param>
+ private void InheritHandles(TextureGroupHandle[] oldHandles, TextureGroupHandle[] handles)
+ {
+ foreach (var group in handles)
+ {
+ foreach (var handle in group.Handles)
+ {
+ bool dirty = false;
+
+ foreach (var oldGroup in oldHandles)
+ {
+ if (group.OverlapsWith(oldGroup.Offset, oldGroup.Size))
+ {
+ foreach (var oldHandle in oldGroup.Handles)
+ {
+ if (handle.OverlapsWith(oldHandle.Address, oldHandle.Size))
+ {
+ dirty |= oldHandle.Dirty;
+ }
+ }
+
+ group.Inherit(oldGroup);
+ }
+ }
+
+ if (dirty && !handle.Dirty)
+ {
+ handle.Reprotect(true);
+ }
+
+ if (group.Modified)
+ {
+ handle.RegisterAction((address, size) => FlushAction(group, address, size));
+ }
+ }
+ }
+ }
+
+ /// <summary>
+ /// Inherit state from another texture group.
+ /// </summary>
+ /// <param name="other">The texture group to inherit from</param>
+ public void Inherit(TextureGroup other)
+ {
+ bool layerViews = _hasLayerViews || other._hasLayerViews;
+ bool mipViews = _hasMipViews || other._hasMipViews;
+
+ if (layerViews != _hasLayerViews || mipViews != _hasMipViews)
+ {
+ _hasLayerViews = layerViews;
+ _hasMipViews = mipViews;
+
+ RecalculateHandleRegions();
+ }
+
+ InheritHandles(other._handles, _handles);
+ }
+
+ /// <summary>
+ /// Replace the current handles with the new handles. It is assumed that the new handles start dirty.
+ /// The dirty flags from the previous handles will be kept.
+ /// </summary>
+ /// <param name="handles">The handles to replace the current handles with</param>
+ private void ReplaceHandles(TextureGroupHandle[] handles)
+ {
+ if (_handles != null)
+ {
+ // When replacing handles, they should start as non-dirty.
+
+ foreach (TextureGroupHandle groupHandle in handles)
+ {
+ foreach (CpuRegionHandle handle in groupHandle.Handles)
+ {
+ handle.Reprotect();
+ }
+ }
+
+ InheritHandles(_handles, handles);
+
+ foreach (var oldGroup in _handles)
+ {
+ foreach (var oldHandle in oldGroup.Handles)
+ {
+ oldHandle.Dispose();
+ }
+ }
+ }
+
+ _handles = handles;
+ _loadNeeded = new bool[_handles.Length];
+ }
+
+ /// <summary>
+ /// Recalculate handle regions for this texture group, and inherit existing state into the new handles.
+ /// </summary>
+ private void RecalculateHandleRegions()
+ {
+ TextureGroupHandle[] handles;
+
+ if (!(_hasMipViews || _hasLayerViews))
+ {
+ // Single dirty region.
+ var cpuRegionHandles = new CpuRegionHandle[TextureRange.Count];
+
+ for (int i = 0; i < TextureRange.Count; i++)
+ {
+ var currentRange = TextureRange.GetSubRange(i);
+ cpuRegionHandles[i] = GenerateHandle(currentRange.Address, currentRange.Size);
+ }
+
+ var groupHandle = new TextureGroupHandle(this, 0, Storage.Size, _views, 0, 0, cpuRegionHandles);
+
+ foreach (CpuRegionHandle handle in cpuRegionHandles)
+ {
+ handle.RegisterDirtyEvent(() => DirtyAction(groupHandle));
+ }
+
+ handles = new TextureGroupHandle[] { groupHandle };
+ }
+ else
+ {
+ // Get views for the host texture.
+ // It's worth noting that either the texture has layer views or mip views when getting to this point, which simplifies the logic a little.
+ // Depending on if the texture is 3d, either the mip views imply that layer views are present (2d) or the other way around (3d).
+ // This is enforced by the way the texture matched as a view, so we don't need to check.
+
+ int layerHandles = _hasLayerViews ? _layers : 1;
+ int levelHandles = _hasMipViews ? _levels : 1;
+
+ int handleIndex = 0;
+
+ if (_is3D)
+ {
+ var handlesList = new List<TextureGroupHandle>();
+
+ for (int i = 0; i < levelHandles; i++)
+ {
+ for (int j = 0; j < layerHandles; j++)
+ {
+ (int viewStart, int views) = Get3DLevelRange(i);
+ viewStart += j;
+ views = _hasLayerViews ? 1 : views; // A layer view is also a mip view.
+
+ handlesList.Add(GenerateHandles(viewStart, views));
+ }
+
+ layerHandles = Math.Max(1, layerHandles >> 1);
+ }
+
+ handles = handlesList.ToArray();
+ }
+ else
+ {
+ handles = new TextureGroupHandle[layerHandles * levelHandles];
+
+ for (int i = 0; i < layerHandles; i++)
+ {
+ for (int j = 0; j < levelHandles; j++)
+ {
+ int viewStart = j + i * _levels;
+ int views = _hasMipViews ? 1 : _levels; // A mip view is also a layer view.
+
+ handles[handleIndex++] = GenerateHandles(viewStart, views);
+ }
+ }
+ }
+ }
+
+ ReplaceHandles(handles);
+ }
+
+ /// <summary>
+ /// Ensure that there is a handle for each potential texture view. Required for copy dependencies to work.
+ /// </summary>
+ private void EnsureFullSubdivision()
+ {
+ if (!(_hasLayerViews && _hasMipViews))
+ {
+ _hasLayerViews = true;
+ _hasMipViews = true;
+
+ RecalculateHandleRegions();
+ }
+ }
+
+ /// <summary>
+ /// Create a copy dependency between this texture group, and a texture at a given layer/level offset.
+ /// </summary>
+ /// <param name="other">The view compatible texture to create a dependency to</param>
+ /// <param name="firstLayer">The base layer of the given texture relative to the storage</param>
+ /// <param name="firstLevel">The base level of the given texture relative to the storage</param>
+ /// <param name="copyTo">True if this texture is first copied to the given one, false for the opposite direction</param>
+ public void CreateCopyDependency(Texture other, int firstLayer, int firstLevel, bool copyTo)
+ {
+ TextureGroup otherGroup = other.Group;
+
+ EnsureFullSubdivision();
+ otherGroup.EnsureFullSubdivision();
+
+ // Get the location of each texture within its storage, so we can find the handles to apply the dependency to.
+ // This can consist of multiple disjoint regions, for example if this is a mip slice of an array texture.
+
+ var targetRange = new List<(int BaseHandle, int RegionCount)>();
+ var otherRange = new List<(int BaseHandle, int RegionCount)>();
+
+ EvaluateRelevantHandles(firstLayer, firstLevel, other.Info.GetSlices(), other.Info.Levels, (baseHandle, regionCount, split) => targetRange.Add((baseHandle, regionCount)));
+ otherGroup.EvaluateRelevantHandles(other, (baseHandle, regionCount, split) => otherRange.Add((baseHandle, regionCount)));
+
+ int targetIndex = 0;
+ int otherIndex = 0;
+ (int Handle, int RegionCount) targetRegion = (0, 0);
+ (int Handle, int RegionCount) otherRegion = (0, 0);
+
+ while (true)
+ {
+ if (targetRegion.RegionCount == 0)
+ {
+ if (targetIndex >= targetRange.Count)
+ {
+ break;
+ }
+
+ targetRegion = targetRange[targetIndex++];
+ }
+
+ if (otherRegion.RegionCount == 0)
+ {
+ if (otherIndex >= otherRange.Count)
+ {
+ break;
+ }
+
+ otherRegion = otherRange[otherIndex++];
+ }
+
+ TextureGroupHandle handle = _handles[targetRegion.Handle++];
+ TextureGroupHandle otherHandle = other.Group._handles[otherRegion.Handle++];
+
+ targetRegion.RegionCount--;
+ otherRegion.RegionCount--;
+
+ handle.CreateCopyDependency(otherHandle, copyTo);
+
+ // If "copyTo" is true, this texture must copy to the other.
+ // Otherwise, it must copy to this texture.
+
+ if (copyTo)
+ {
+ otherHandle.Copy(handle);
+ }
+ else
+ {
+ handle.Copy(otherHandle);
+ }
+ }
+ }
+
+ /// <summary>
+ /// A flush has been requested on a tracked region. Find an appropriate view to flush.
+ /// </summary>
+ /// <param name="handle">The handle this flush action is for</param>
+ /// <param name="address">The address of the flushing memory access</param>
+ /// <param name="size">The size of the flushing memory access</param>
+ public void FlushAction(TextureGroupHandle handle, ulong address, ulong size)
+ {
+ Storage.ExternalFlush(address, size);
+
+ lock (handle.Overlaps)
+ {
+ foreach (Texture overlap in handle.Overlaps)
+ {
+ overlap.ExternalFlush(address, size);
+ }
+ }
+
+ handle.Modified = false;
+ }
+
+ /// <summary>
+ /// Dispose this texture group, disposing all related memory tracking handles.
+ /// </summary>
+ public void Dispose()
+ {
+ foreach (TextureGroupHandle group in _handles)
+ {
+ group.Dispose();
+ }
+ }
+ }
+}
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
+{
+ /// <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;
+
+ /// <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 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="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, CpuRegionHandle[] handles)
+ {
+ _group = group;
+ _firstLayer = firstLayer;
+ _firstLevel = firstLevel;
+
+ Offset = offset;
+ Size = (int)size;
+ Overlaps = new List<Texture>();
+ Dependencies = new List<TextureDependency>();
+
+ 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>
+ /// Signal that this handle has been modified to any existing dependencies, and set the modified flag.
+ /// </summary>
+ 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();
+ }
+ }
+
+ /// <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>
+ 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));
+ }
+
+ /// <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)
+ {
+ 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="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(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;
+ }
+
+ /// <summary>
+ /// Inherit modified flags and dependencies from another texture handle.
+ /// </summary>
+ /// <param name="old">The texture handle to inherit from</param>
+ 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;
+ }
+
+ /// <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;
+ }
+
+ public void Dispose()
+ {
+ foreach (CpuRegionHandle handle in Handles)
+ {
+ handle.Dispose();
+ }
+
+ foreach (TextureDependency dependency in Dependencies.ToArray())
+ {
+ dependency.Other.Handle.RemoveDependency(dependency.Other);
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/Image/TextureInfo.cs b/Ryujinx.Graphics.Gpu/Image/TextureInfo.cs
index 3137f8b8..571f440e 100644
--- a/Ryujinx.Graphics.Gpu/Image/TextureInfo.cs
+++ b/Ryujinx.Graphics.Gpu/Image/TextureInfo.cs
@@ -233,6 +233,31 @@ namespace Ryujinx.Graphics.Gpu.Image
}
/// <summary>
+ /// Gets the number of 2D slices of the texture.
+ /// Returns 6 for cubemap textures, layer faces for cubemap array textures, and DepthOrLayers for everything else.
+ /// </summary>
+ /// <returns>The number of texture slices</returns>
+ public int GetSlices()
+ {
+ if (Target == Target.Texture3D || Target == Target.Texture2DArray || Target == Target.Texture2DMultisampleArray)
+ {
+ return DepthOrLayers;
+ }
+ else if (Target == Target.CubemapArray)
+ {
+ return DepthOrLayers * 6;
+ }
+ else if (Target == Target.Cubemap)
+ {
+ return 6;
+ }
+ else
+ {
+ return 1;
+ }
+ }
+
+ /// <summary>
/// Calculates the size information from the texture information.
/// </summary>
/// <param name="layerSize">Optional size of each texture layer in bytes</param>
diff --git a/Ryujinx.Graphics.Gpu/Image/TextureManager.cs b/Ryujinx.Graphics.Gpu/Image/TextureManager.cs
index 2646a75b..f13c3443 100644
--- a/Ryujinx.Graphics.Gpu/Image/TextureManager.cs
+++ b/Ryujinx.Graphics.Gpu/Image/TextureManager.cs
@@ -185,7 +185,14 @@ namespace Ryujinx.Graphics.Gpu.Image
{
bool hasValue = color != null;
bool changesScale = (hasValue != (_rtColors[index] != null)) || (hasValue && RenderTargetScale != color.ScaleFactor);
- _rtColors[index] = color;
+
+ if (_rtColors[index] != color)
+ {
+ _rtColors[index]?.SignalModifying(false);
+ color?.SignalModifying(true);
+
+ _rtColors[index] = color;
+ }
return changesScale || (hasValue && color.ScaleMode != TextureScaleMode.Blacklisted && color.ScaleFactor != GraphicsConfig.ResScale);
}
@@ -292,7 +299,14 @@ namespace Ryujinx.Graphics.Gpu.Image
{
bool hasValue = depthStencil != null;
bool changesScale = (hasValue != (_rtDepthStencil != null)) || (hasValue && RenderTargetScale != depthStencil.ScaleFactor);
- _rtDepthStencil = depthStencil;
+
+ if (_rtDepthStencil != depthStencil)
+ {
+ _rtDepthStencil?.SignalModifying(false);
+ depthStencil?.SignalModifying(true);
+
+ _rtDepthStencil = depthStencil;
+ }
return changesScale || (hasValue && depthStencil.ScaleMode != TextureScaleMode.Blacklisted && depthStencil.ScaleFactor != GraphicsConfig.ResScale);
}
@@ -754,38 +768,97 @@ namespace Ryujinx.Graphics.Gpu.Image
overlapsCount = _textures.FindOverlaps(range.Value, ref _textureOverlaps);
}
+ if (_overlapInfo.Length != _textureOverlaps.Length)
+ {
+ Array.Resize(ref _overlapInfo, _textureOverlaps.Length);
+ }
+
+ // =============== Find Texture View of Existing Texture ===============
+
+ int fullyCompatible = 0;
+
+ // Evaluate compatibility of overlaps
+
for (int index = 0; index < overlapsCount; index++)
{
Texture overlap = _textureOverlaps[index];
- TextureViewCompatibility overlapCompatibility = overlap.IsViewCompatible(info, range.Value, out int firstLayer, out int firstLevel);
+ TextureViewCompatibility overlapCompatibility = overlap.IsViewCompatible(info, range.Value, sizeInfo.LayerSize, out int firstLayer, out int firstLevel);
if (overlapCompatibility == TextureViewCompatibility.Full)
{
- TextureInfo oInfo = AdjustSizes(overlap, info, firstLevel);
-
- if (!isSamplerTexture)
+ if (overlap.IsView)
+ {
+ overlapCompatibility = TextureViewCompatibility.CopyOnly;
+ }
+ else
{
- info = oInfo;
+ fullyCompatible++;
}
+ }
+
+ _overlapInfo[index] = new OverlapInfo(overlapCompatibility, firstLayer, firstLevel);
+ }
+
+ // Search through the overlaps to find a compatible view and establish any copy dependencies.
+
+ for (int index = 0; index < overlapsCount; index++)
+ {
+ Texture overlap = _textureOverlaps[index];
+ OverlapInfo oInfo = _overlapInfo[index];
- texture = overlap.CreateView(oInfo, sizeInfo, range.Value, firstLayer, firstLevel);
+ if (oInfo.Compatibility == TextureViewCompatibility.Full)
+ {
+ TextureInfo adjInfo = AdjustSizes(overlap, info, oInfo.FirstLevel);
- if (overlap.IsModified)
+ if (!isSamplerTexture)
{
- texture.SignalModified();
+ info = adjInfo;
}
+ texture = overlap.CreateView(adjInfo, sizeInfo, range.Value, oInfo.FirstLayer, oInfo.FirstLevel);
+
ChangeSizeIfNeeded(info, texture, isSamplerTexture, sizeHint);
+ texture.SynchronizeMemory();
break;
}
- else if (overlapCompatibility == TextureViewCompatibility.CopyOnly)
+ else if (oInfo.Compatibility == TextureViewCompatibility.CopyOnly && fullyCompatible == 0)
{
- // TODO: Copy rules for targets created after the container texture. See below.
- overlap.DisableMemoryTracking();
+ // Only copy compatible. If there's another choice for a FULLY compatible texture, choose that instead.
+
+ texture = new Texture(_context, info, sizeInfo, range.Value, scaleMode);
+ texture.InitializeGroup(true, true);
+ texture.InitializeData(false, false);
+
+ overlap.SynchronizeMemory();
+ overlap.CreateCopyDependency(texture, oInfo.FirstLayer, oInfo.FirstLevel, true);
+ break;
}
}
+ if (texture != null)
+ {
+ // This texture could be a view of multiple parent textures with different storages, even if it is a view.
+ // When a texture is created, make sure all possible dependencies to other textures are created as copies.
+ // (even if it could be fulfilled without a copy)
+
+ for (int index = 0; index < overlapsCount; index++)
+ {
+ Texture overlap = _textureOverlaps[index];
+ OverlapInfo oInfo = _overlapInfo[index];
+
+ if (oInfo.Compatibility != TextureViewCompatibility.Incompatible && overlap.Group != texture.Group)
+ {
+ overlap.SynchronizeMemory();
+ overlap.CreateCopyDependency(texture, oInfo.FirstLayer, oInfo.FirstLevel, true);
+ }
+ }
+
+ texture.SynchronizeMemory();
+ }
+
+ // =============== Create a New Texture ===============
+
// No match, create a new texture.
if (texture == null)
{
@@ -795,24 +868,53 @@ namespace Ryujinx.Graphics.Gpu.Image
// Any textures that are incompatible will contain garbage data, so they should be removed where possible.
int viewCompatible = 0;
+ fullyCompatible = 0;
bool setData = isSamplerTexture || overlapsCount == 0 || flags.HasFlag(TextureSearchFlags.ForCopy);
+ bool hasLayerViews = false;
+ bool hasMipViews = false;
+
for (int index = 0; index < overlapsCount; index++)
{
Texture overlap = _textureOverlaps[index];
bool overlapInCache = overlap.CacheNode != null;
- TextureViewCompatibility compatibility = texture.IsViewCompatible(overlap.Info, overlap.Range, out int firstLayer, out int firstLevel);
+ TextureViewCompatibility compatibility = texture.IsViewCompatible(overlap.Info, overlap.Range, overlap.LayerSize, out int firstLayer, out int firstLevel);
+
+ if (overlap.IsView && compatibility == TextureViewCompatibility.Full)
+ {
+ compatibility = TextureViewCompatibility.CopyOnly;
+ }
if (compatibility != TextureViewCompatibility.Incompatible)
{
- if (_overlapInfo.Length != _textureOverlaps.Length)
+ if (compatibility == TextureViewCompatibility.Full)
+ {
+ if (viewCompatible == fullyCompatible)
+ {
+ _overlapInfo[viewCompatible] = new OverlapInfo(compatibility, firstLayer, firstLevel);
+ _textureOverlaps[viewCompatible++] = overlap;
+ }
+ else
+ {
+ // Swap overlaps so that the fully compatible views have priority.
+
+ _overlapInfo[viewCompatible] = _overlapInfo[fullyCompatible];
+ _textureOverlaps[viewCompatible++] = _textureOverlaps[fullyCompatible];
+
+ _overlapInfo[fullyCompatible] = new OverlapInfo(compatibility, firstLayer, firstLevel);
+ _textureOverlaps[fullyCompatible] = overlap;
+ }
+ fullyCompatible++;
+ }
+ else
{
- Array.Resize(ref _overlapInfo, _textureOverlaps.Length);
+ _overlapInfo[viewCompatible] = new OverlapInfo(compatibility, firstLayer, firstLevel);
+ _textureOverlaps[viewCompatible++] = overlap;
}
- _overlapInfo[viewCompatible] = new OverlapInfo(compatibility, firstLayer, firstLevel);
- _textureOverlaps[viewCompatible++] = overlap;
+ hasLayerViews |= overlap.Info.GetSlices() < texture.Info.GetSlices();
+ hasMipViews |= overlap.Info.Levels < texture.Info.Levels;
}
else if (overlapInCache || !setData)
{
@@ -841,6 +943,8 @@ namespace Ryujinx.Graphics.Gpu.Image
}
}
+ texture.InitializeGroup(hasLayerViews, hasMipViews);
+
// 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);
@@ -848,17 +952,17 @@ namespace Ryujinx.Graphics.Gpu.Image
for (int index = 0; index < viewCompatible; index++)
{
Texture overlap = _textureOverlaps[index];
+
OverlapInfo oInfo = _overlapInfo[index];
- if (oInfo.Compatibility != TextureViewCompatibility.Full)
+ if (overlap.Group == texture.Group)
{
- continue; // Copy only compatibilty.
+ // If the texture group is equal, then this texture (or its parent) is already a view.
+ continue;
}
TextureInfo overlapInfo = AdjustSizes(texture, overlap.Info, oInfo.FirstLevel);
- TextureCreateInfo createInfo = GetCreateInfo(overlapInfo, _context.Capabilities, overlap.ScaleFactor);
-
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.
@@ -867,47 +971,30 @@ namespace Ryujinx.Graphics.Gpu.Image
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 (overlap.IsModified)
+ if (oInfo.Compatibility != TextureViewCompatibility.Full)
{
- texture.SignalModified();
- }
+ // Copy only compatibility, or target texture is already a view.
- overlap.ReplaceView(texture, overlapInfo, newView, oInfo.FirstLayer, oInfo.FirstLevel);
- }
+ ChangeSizeIfNeeded(overlapInfo, overlap, false, sizeHint); // Force a size match for copy
- // 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 && viewCompatible > 0)
- {
- // TODO: This copy can currently only happen when the 3D texture is created.
- // If a game clears and redraws the slices, we won't be able to copy the new data to the 3D texture.
- // Disable tracking to try keep at least the original data in there for as long as possible.
- texture.DisableMemoryTracking();
-
- for (int index = 0; index < viewCompatible; index++)
+ overlap.SynchronizeMemory();
+ texture.CreateCopyDependency(overlap, oInfo.FirstLayer, oInfo.FirstLevel, false);
+ }
+ else
{
- Texture overlap = _textureOverlaps[index];
- OverlapInfo oInfo = _overlapInfo[index];
+ TextureCreateInfo createInfo = GetCreateInfo(overlapInfo, _context.Capabilities, overlap.ScaleFactor);
- if (oInfo.Compatibility != TextureViewCompatibility.Incompatible)
- {
- overlap.BlacklistScale();
+ ITexture newView = texture.HostTexture.CreateView(createInfo, oInfo.FirstLayer, oInfo.FirstLevel);
- overlap.HostTexture.CopyTo(texture.HostTexture, oInfo.FirstLayer, oInfo.FirstLevel);
+ overlap.SynchronizeMemory();
- if (overlap.IsModified)
- {
- texture.SignalModified();
- }
- }
+ overlap.HostTexture.CopyTo(newView, 0, 0);
+
+ overlap.ReplaceView(texture, overlapInfo, newView, oInfo.FirstLayer, oInfo.FirstLevel);
}
}
+
+ texture.SynchronizeMemory();
}
// Sampler textures are managed by the texture pool, all other textures