Texture Sync, incompatible overlap handling, data flush improvements. (#2971)
* Initial test for texture sync * WIP new texture flushing setup * Improve rules for incompatible overlaps Fixes a lot of issues with Unreal Engine games. Still a few minor issues (some caused by dma fast path?) Needs docs and cleanup. * Cleanup, improvements Improve rules for fast DMA * Small tweak to group together flushes of overlapping handles. * Fixes, flush overlapping texture data for ASTC and BC4/5 compressed textures. Fixes the new Life is Strange game. * Flush overlaps before init data, fix 3d texture size/overlap stuff * Fix 3D Textures, faster single layer flush Note: nosy people can no longer merge this with Vulkan. (unless they are nosy enough to implement the new backend methods) * Remove unused method * Minor cleanup * More cleanup * Use the More Fun and Hopefully No Driver Bugs method for getting compressed tex too This one's for metro * Address feedback, ASTC+ETC to FormatClass * Change offset to use Span slice rather than IntPtr Add * Fix this too
This commit is contained in:
parent
4864648e72
commit
cda659955c
26 changed files with 1453 additions and 329 deletions
|
@ -41,14 +41,14 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
{
|
||||
Texture oldestTexture = _textures.First.Value;
|
||||
|
||||
if (oldestTexture.IsModified && !oldestTexture.CheckModified(false))
|
||||
if (!oldestTexture.CheckModified(false))
|
||||
{
|
||||
// The texture must be flushed if it falls out of the auto delete cache.
|
||||
// Flushes out of the auto delete cache do not trigger write tracking,
|
||||
// as it is expected that other overlapping textures exist that have more up-to-date contents.
|
||||
|
||||
oldestTexture.Group.SynchronizeDependents(oldestTexture);
|
||||
oldestTexture.Flush(false);
|
||||
oldestTexture.FlushModified(false);
|
||||
}
|
||||
|
||||
_textures.RemoveFirst();
|
||||
|
@ -93,9 +93,9 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
}
|
||||
|
||||
// Remove our reference to this texture.
|
||||
if (flush && texture.IsModified)
|
||||
if (flush)
|
||||
{
|
||||
texture.Flush(false);
|
||||
texture.FlushModified(false);
|
||||
}
|
||||
|
||||
_textures.Remove(texture.CacheNode);
|
||||
|
|
|
@ -72,11 +72,6 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
/// </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; }
|
||||
|
||||
/// <summary>
|
||||
/// Set when a texture has been changed size. This indicates that it may need to be
|
||||
/// changed again when obtained as a sampler.
|
||||
|
@ -89,6 +84,12 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
/// </summary>
|
||||
public bool ChangedMapping { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// True if the data for this texture must always be flushed when an overlap appears.
|
||||
/// This is useful if SetData is called directly on this texture, but the data is meant for a future texture.
|
||||
/// </summary>
|
||||
public bool AlwaysFlushOnOverlap { get; private set; }
|
||||
|
||||
private int _depth;
|
||||
private int _layers;
|
||||
public int FirstLayer { get; private set; }
|
||||
|
@ -99,6 +100,8 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
private int _updateCount;
|
||||
private byte[] _currentData;
|
||||
|
||||
private bool _modifiedStale = true;
|
||||
|
||||
private ITexture _arrayViewTexture;
|
||||
private Target _arrayViewTarget;
|
||||
|
||||
|
@ -241,6 +244,8 @@ 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)
|
||||
{
|
||||
withData |= Group != null && Group.FlushIncompatibleOverlapsIfNeeded();
|
||||
|
||||
if (withData)
|
||||
{
|
||||
Debug.Assert(!isView);
|
||||
|
@ -280,9 +285,10 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
/// </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)
|
||||
/// <param name="incompatibleOverlaps">Groups that overlap with this one but are incompatible</param>
|
||||
public void InitializeGroup(bool hasLayerViews, bool hasMipViews, List<TextureIncompatibleOverlap> incompatibleOverlaps)
|
||||
{
|
||||
Group = new TextureGroup(_context, _physicalMemory, this);
|
||||
Group = new TextureGroup(_context, _physicalMemory, this, incompatibleOverlaps);
|
||||
|
||||
Group.Initialize(ref _sizeInfo, hasLayerViews, hasMipViews);
|
||||
}
|
||||
|
@ -657,6 +663,14 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
_dirty = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Signal that the modified state is dirty, indicating that the texture group should be notified when it changes.
|
||||
/// </summary>
|
||||
public void SignalModifiedDirty()
|
||||
{
|
||||
_modifiedStale = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fully synchronizes guest and host memory.
|
||||
/// This will replace the entire texture with the data present in guest memory.
|
||||
|
@ -670,8 +684,6 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
|
||||
ReadOnlySpan<byte> data = _physicalMemory.GetSpan(Range);
|
||||
|
||||
IsModified = false;
|
||||
|
||||
// If the host does not support ASTC compression, we need to do the decompression.
|
||||
// The decompression is slow, so we want to avoid it as much as possible.
|
||||
// This does a byte-by-byte check and skips the update if the data is equal in this case.
|
||||
|
@ -710,7 +722,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
|
||||
Group.CheckDirty(this, true);
|
||||
|
||||
IsModified = false;
|
||||
AlwaysFlushOnOverlap = true;
|
||||
|
||||
HostTexture.SetData(data);
|
||||
|
||||
|
@ -738,15 +750,17 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
/// Converts texture data to a format and layout that is supported by the host GPU.
|
||||
/// </summary>
|
||||
/// <param name="data">Data to be converted</param>
|
||||
/// <param name="level">Mip level to convert</param>
|
||||
/// <param name="single">True to convert a single slice</param>
|
||||
/// <returns>Converted data</returns>
|
||||
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 depth = _depth;
|
||||
int layers = single ? 1 : _layers;
|
||||
int levels = single ? 1 : Info.Levels;
|
||||
int levels = single ? 1 : (Info.Levels - level);
|
||||
|
||||
width = Math.Max(width >> level, 1);
|
||||
height = Math.Max(height >> level, 1);
|
||||
|
@ -770,6 +784,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
width,
|
||||
height,
|
||||
depth,
|
||||
single ? 1 : depth,
|
||||
levels,
|
||||
layers,
|
||||
Info.FormatInfo.BlockWidth,
|
||||
|
@ -821,6 +836,65 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
return data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts texture data from a format and layout that is supported by the host GPU, back into the intended format on the guest GPU.
|
||||
/// </summary>
|
||||
/// <param name="output">Optional output span to convert into</param>
|
||||
/// <param name="data">Data to be converted</param>
|
||||
/// <param name="level">Mip level to convert</param>
|
||||
/// <param name="single">True to convert a single slice</param>
|
||||
/// <returns>Converted data</returns>
|
||||
public ReadOnlySpan<byte> ConvertFromHostCompatibleFormat(Span<byte> output, ReadOnlySpan<byte> data, int level = 0, bool single = false)
|
||||
{
|
||||
if (Target != Target.TextureBuffer)
|
||||
{
|
||||
int width = Info.Width;
|
||||
int height = Info.Height;
|
||||
|
||||
int depth = _depth;
|
||||
int layers = single ? 1 : _layers;
|
||||
int levels = single ? 1 : (Info.Levels - level);
|
||||
|
||||
width = Math.Max(width >> level, 1);
|
||||
height = Math.Max(height >> level, 1);
|
||||
depth = Math.Max(depth >> level, 1);
|
||||
|
||||
if (Info.IsLinear)
|
||||
{
|
||||
data = LayoutConverter.ConvertLinearToLinearStrided(
|
||||
output,
|
||||
Info.Width,
|
||||
Info.Height,
|
||||
Info.FormatInfo.BlockWidth,
|
||||
Info.FormatInfo.BlockHeight,
|
||||
Info.Stride,
|
||||
Info.FormatInfo.BytesPerPixel,
|
||||
data);
|
||||
}
|
||||
else
|
||||
{
|
||||
data = LayoutConverter.ConvertLinearToBlockLinear(
|
||||
output,
|
||||
width,
|
||||
height,
|
||||
depth,
|
||||
single ? 1 : depth,
|
||||
levels,
|
||||
layers,
|
||||
Info.FormatInfo.BlockWidth,
|
||||
Info.FormatInfo.BlockHeight,
|
||||
Info.FormatInfo.BytesPerPixel,
|
||||
Info.GobBlocksInY,
|
||||
Info.GobBlocksInZ,
|
||||
Info.GobBlocksInTileX,
|
||||
_sizeInfo,
|
||||
data);
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Flushes the texture data.
|
||||
/// This causes the texture data to be written back to guest memory.
|
||||
|
@ -830,56 +904,48 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
/// This may cause data corruption if the memory is already being used for something else on the CPU side.
|
||||
/// </summary>
|
||||
/// <param name="tracked">Whether or not the flush triggers write tracking. If it doesn't, the texture will not be blacklisted for scaling either.</param>
|
||||
public void Flush(bool tracked = true)
|
||||
/// <returns>True if data was flushed, false otherwise</returns>
|
||||
public bool FlushModified(bool tracked = true)
|
||||
{
|
||||
IsModified = false;
|
||||
if (TextureCompatibility.IsFormatHostIncompatible(Info, _context.Capabilities))
|
||||
{
|
||||
return; // Flushing this format is not supported, as it may have been converted to another host format.
|
||||
}
|
||||
|
||||
FlushTextureDataToGuest(tracked);
|
||||
return TextureCompatibility.CanTextureFlush(Info, _context.Capabilities) && Group.FlushModified(this, tracked);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Flushes the texture data, to be called from an external thread.
|
||||
/// The host backend must ensure that we have shared access to the resource from this thread.
|
||||
/// This is used when flushing from memory access handlers.
|
||||
/// Flushes the texture data.
|
||||
/// This causes the texture data to be written back to guest memory.
|
||||
/// If the texture was written by the GPU, this includes all modification made by the GPU
|
||||
/// up to this point.
|
||||
/// Be aware that this is an expensive operation, avoid calling it unless strictly needed.
|
||||
/// This may cause data corruption if the memory is already being used for something else on the CPU side.
|
||||
/// </summary>
|
||||
public void ExternalFlush(ulong address, ulong size)
|
||||
/// <param name="tracked">Whether or not the flush triggers write tracking. If it doesn't, the texture will not be blacklisted for scaling either.</param>
|
||||
public void Flush(bool tracked)
|
||||
{
|
||||
if (!IsModified)
|
||||
if (TextureCompatibility.CanTextureFlush(Info, _context.Capabilities))
|
||||
{
|
||||
return;
|
||||
FlushTextureDataToGuest(tracked);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a host texture to use for flushing the texture, at 1x resolution.
|
||||
/// If the HostTexture is already at 1x resolution, it is returned directly.
|
||||
/// </summary>
|
||||
/// <returns>The host texture to flush</returns>
|
||||
public ITexture GetFlushTexture()
|
||||
{
|
||||
ITexture texture = HostTexture;
|
||||
if (ScaleFactor != 1f)
|
||||
{
|
||||
// If needed, create a texture to flush back to host at 1x scale.
|
||||
texture = _flushHostTexture = GetScaledHostTexture(1f, _flushHostTexture);
|
||||
}
|
||||
|
||||
_context.Renderer.BackgroundContextAction(() =>
|
||||
{
|
||||
IsModified = false;
|
||||
if (TextureCompatibility.IsFormatHostIncompatible(Info, _context.Capabilities))
|
||||
{
|
||||
return; // Flushing this format is not supported, as it may have been converted to another host format.
|
||||
}
|
||||
|
||||
if (Info.Target == Target.Texture2DMultisample ||
|
||||
Info.Target == Target.Texture2DMultisampleArray)
|
||||
{
|
||||
return; // Flushing multisample textures is not supported, the host does not allow getting their data.
|
||||
}
|
||||
|
||||
ITexture texture = HostTexture;
|
||||
if (ScaleFactor != 1f)
|
||||
{
|
||||
// If needed, create a texture to flush back to host at 1x scale.
|
||||
texture = _flushHostTexture = GetScaledHostTexture(1f, _flushHostTexture);
|
||||
}
|
||||
|
||||
FlushTextureDataToGuest(false, texture);
|
||||
});
|
||||
return texture;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets data from the host GPU, and flushes it to guest memory.
|
||||
/// Gets data from the host GPU, and flushes it all to guest memory.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method should be used to retrieve data that was modified by the host GPU.
|
||||
|
@ -888,28 +954,11 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
/// </remarks>
|
||||
/// <param name="tracked">True if writing the texture data is tracked, false otherwise</param>
|
||||
/// <param name="texture">The specific host texture to flush. Defaults to this texture</param>
|
||||
private void FlushTextureDataToGuest(bool tracked, ITexture texture = null)
|
||||
public void FlushTextureDataToGuest(bool tracked, ITexture texture = null)
|
||||
{
|
||||
if (Range.Count == 1)
|
||||
{
|
||||
MemoryRange subrange = Range.GetSubRange(0);
|
||||
using WritableRegion region = _physicalMemory.GetWritableRegion(Range, tracked);
|
||||
|
||||
using (WritableRegion region = _physicalMemory.GetWritableRegion(subrange.Address, (int)subrange.Size, tracked))
|
||||
{
|
||||
GetTextureDataFromGpu(region.Memory.Span, tracked, texture);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (tracked)
|
||||
{
|
||||
_physicalMemory.Write(Range, GetTextureDataFromGpu(Span<byte>.Empty, true, texture));
|
||||
}
|
||||
else
|
||||
{
|
||||
_physicalMemory.WriteUntracked(Range, GetTextureDataFromGpu(Span<byte>.Empty, false, texture));
|
||||
}
|
||||
}
|
||||
GetTextureDataFromGpu(region.Memory.Span, tracked, texture);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -951,40 +1000,54 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
}
|
||||
}
|
||||
|
||||
if (Target != Target.TextureBuffer)
|
||||
data = ConvertFromHostCompatibleFormat(output, data);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets data from the host GPU for a single slice.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method should be used to retrieve data that was modified by the host GPU.
|
||||
/// This is not cheap, avoid doing that unless strictly needed.
|
||||
/// </remarks>
|
||||
/// <param name="output">An output span to place the texture data into. If empty, one is generated</param>
|
||||
/// <param name="layer">The layer of the texture to flush</param>
|
||||
/// <param name="level">The level of the texture to flush</param>
|
||||
/// <param name="blacklist">True if the texture should be blacklisted, false otherwise</param>
|
||||
/// <param name="texture">The specific host texture to flush. Defaults to this texture</param>
|
||||
/// <returns>The span containing the texture data</returns>
|
||||
public ReadOnlySpan<byte> GetTextureDataSliceFromGpu(Span<byte> output, int layer, int level, bool blacklist, ITexture texture = null)
|
||||
{
|
||||
ReadOnlySpan<byte> data;
|
||||
|
||||
if (texture != null)
|
||||
{
|
||||
if (Info.IsLinear)
|
||||
data = texture.GetData(layer, level);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (blacklist)
|
||||
{
|
||||
data = LayoutConverter.ConvertLinearToLinearStrided(
|
||||
output,
|
||||
Info.Width,
|
||||
Info.Height,
|
||||
Info.FormatInfo.BlockWidth,
|
||||
Info.FormatInfo.BlockHeight,
|
||||
Info.Stride,
|
||||
Info.FormatInfo.BytesPerPixel,
|
||||
data);
|
||||
BlacklistScale();
|
||||
data = HostTexture.GetData(layer, level);
|
||||
}
|
||||
else if (ScaleFactor != 1f)
|
||||
{
|
||||
float scale = ScaleFactor;
|
||||
SetScale(1f);
|
||||
data = HostTexture.GetData(layer, level);
|
||||
SetScale(scale);
|
||||
}
|
||||
else
|
||||
{
|
||||
data = LayoutConverter.ConvertLinearToBlockLinear(
|
||||
output,
|
||||
Info.Width,
|
||||
Info.Height,
|
||||
_depth,
|
||||
Info.Levels,
|
||||
_layers,
|
||||
Info.FormatInfo.BlockWidth,
|
||||
Info.FormatInfo.BlockHeight,
|
||||
Info.FormatInfo.BytesPerPixel,
|
||||
Info.GobBlocksInY,
|
||||
Info.GobBlocksInZ,
|
||||
Info.GobBlocksInTileX,
|
||||
_sizeInfo,
|
||||
data);
|
||||
data = HostTexture.GetData(layer, level);
|
||||
}
|
||||
}
|
||||
|
||||
data = ConvertFromHostCompatibleFormat(output, data, level, true);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
|
@ -1043,55 +1106,64 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
/// </summary>
|
||||
/// <param name="info">Texture view information</param>
|
||||
/// <param name="range">Texture view physical memory ranges</param>
|
||||
/// <param name="layerSize">Layer size on the given texture</param>
|
||||
/// <param name="caps">Host GPU capabilities</param>
|
||||
/// <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, int layerSize, out int firstLayer, out int firstLevel)
|
||||
public TextureViewCompatibility IsViewCompatible(TextureInfo info, MultiRange range, int layerSize, Capabilities caps, out int firstLayer, out int firstLevel)
|
||||
{
|
||||
int offset = Range.FindOffset(range);
|
||||
TextureViewCompatibility result = TextureViewCompatibility.Full;
|
||||
|
||||
// Out of range.
|
||||
if (offset < 0)
|
||||
result = TextureCompatibility.PropagateViewCompatibility(result, TextureCompatibility.ViewFormatCompatible(Info, info, caps));
|
||||
if (result != TextureViewCompatibility.Incompatible)
|
||||
{
|
||||
firstLayer = 0;
|
||||
firstLevel = 0;
|
||||
result = TextureCompatibility.PropagateViewCompatibility(result, TextureCompatibility.ViewTargetCompatible(Info, info));
|
||||
|
||||
if (result == TextureViewCompatibility.Full && Info.FormatInfo.Format != info.FormatInfo.Format && !_context.Capabilities.SupportsMismatchingViewFormat)
|
||||
{
|
||||
// AMD and Intel have a bug where the view format is always ignored;
|
||||
// they use the parent format instead.
|
||||
// Create a copy dependency to avoid this issue.
|
||||
|
||||
result = TextureViewCompatibility.CopyOnly;
|
||||
}
|
||||
|
||||
if (Info.SamplesInX != info.SamplesInX || Info.SamplesInY != info.SamplesInY)
|
||||
{
|
||||
result = TextureViewCompatibility.Incompatible;
|
||||
}
|
||||
}
|
||||
|
||||
firstLayer = 0;
|
||||
firstLevel = 0;
|
||||
|
||||
if (result == TextureViewCompatibility.Incompatible)
|
||||
{
|
||||
return TextureViewCompatibility.Incompatible;
|
||||
}
|
||||
|
||||
if (!_sizeInfo.FindView(offset, out firstLayer, out firstLevel))
|
||||
int offset = Range.FindOffset(range);
|
||||
|
||||
if (offset < 0 || !_sizeInfo.FindView(offset, out firstLayer, out firstLevel))
|
||||
{
|
||||
return TextureViewCompatibility.Incompatible;
|
||||
return TextureViewCompatibility.LayoutIncompatible;
|
||||
}
|
||||
|
||||
if (!TextureCompatibility.ViewLayoutCompatible(Info, info, firstLevel))
|
||||
{
|
||||
return TextureViewCompatibility.Incompatible;
|
||||
return TextureViewCompatibility.LayoutIncompatible;
|
||||
}
|
||||
|
||||
if (info.GetSlices() > 1 && LayerSize != layerSize)
|
||||
{
|
||||
return TextureViewCompatibility.Incompatible;
|
||||
return TextureViewCompatibility.LayoutIncompatible;
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
if (result == TextureViewCompatibility.Full && Info.FormatInfo.Format != info.FormatInfo.Format && !_context.Capabilities.SupportsMismatchingViewFormat)
|
||||
{
|
||||
// AMD and Intel have a bug where the view format is always ignored;
|
||||
// they use the parent format instead.
|
||||
// Create a copy dependency to avoid this issue.
|
||||
|
||||
result = TextureViewCompatibility.CopyOnly;
|
||||
}
|
||||
|
||||
return (Info.SamplesInX == info.SamplesInX &&
|
||||
Info.SamplesInY == info.SamplesInY) ? result : TextureViewCompatibility.Incompatible;
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -1261,11 +1333,10 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
/// </summary>
|
||||
public void SignalModified()
|
||||
{
|
||||
bool wasModified = IsModified;
|
||||
if (!wasModified || Group.HasCopyDependencies)
|
||||
if (_modifiedStale || Group.HasCopyDependencies)
|
||||
{
|
||||
IsModified = true;
|
||||
Group.SignalModified(this, !wasModified);
|
||||
_modifiedStale = false;
|
||||
Group.SignalModified(this);
|
||||
}
|
||||
|
||||
_physicalMemory.TextureCache.Lift(this);
|
||||
|
@ -1278,12 +1349,10 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
/// <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;
|
||||
|
||||
if (!wasModified || Group.HasCopyDependencies)
|
||||
if (_modifiedStale || Group.HasCopyDependencies)
|
||||
{
|
||||
IsModified = true;
|
||||
Group.SignalModifying(this, bound, !wasModified);
|
||||
_modifiedStale = false;
|
||||
Group.SignalModifying(this, bound);
|
||||
}
|
||||
|
||||
_physicalMemory.TextureCache.Lift(this);
|
||||
|
@ -1309,29 +1378,6 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
HostTexture = hostTexture;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determine if any of our child textures are compaible as views of the given texture.
|
||||
/// </summary>
|
||||
/// <param name="texture">The texture to check against</param>
|
||||
/// <returns>True if any child is view compatible, false otherwise</returns>
|
||||
public bool HasViewCompatibleChild(Texture texture)
|
||||
{
|
||||
if (_viewStorage != this || _views.Count == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (Texture view in _views)
|
||||
{
|
||||
if (texture.IsViewCompatible(view.Info, view.Range, view.LayerSize, out _, out _) != TextureViewCompatibility.Incompatible)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determine if any of this texture's data overlaps with another.
|
||||
/// </summary>
|
||||
|
@ -1489,11 +1535,15 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
/// Called when the memory for this texture has been unmapped.
|
||||
/// Calls are from non-gpu threads.
|
||||
/// </summary>
|
||||
public void Unmapped()
|
||||
/// <param name="unmapRange">The range of memory being unmapped</param>
|
||||
public void Unmapped(MultiRange unmapRange)
|
||||
{
|
||||
ChangedMapping = true;
|
||||
|
||||
IsModified = false; // We shouldn't flush this texture, as its memory is no longer mapped.
|
||||
if (Group.Storage == this)
|
||||
{
|
||||
Group.ClearModified(unmapRange);
|
||||
}
|
||||
|
||||
RemoveFromPools(true);
|
||||
}
|
||||
|
|
|
@ -7,8 +7,10 @@ using Ryujinx.Graphics.Gpu.Engine.Types;
|
|||
using Ryujinx.Graphics.Gpu.Image;
|
||||
using Ryujinx.Graphics.Gpu.Memory;
|
||||
using Ryujinx.Graphics.Texture;
|
||||
using Ryujinx.Memory;
|
||||
using Ryujinx.Memory.Range;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
|
@ -72,14 +74,26 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
Texture[] overlaps = new Texture[10];
|
||||
int overlapCount;
|
||||
|
||||
MultiRange unmapped;
|
||||
|
||||
try
|
||||
{
|
||||
unmapped = ((MemoryManager)sender).GetPhysicalRegions(e.Address, e.Size);
|
||||
}
|
||||
catch (InvalidMemoryRegionException)
|
||||
{
|
||||
// This event fires on Map in case any mappings are overwritten. In that case, there may not be an existing mapping.
|
||||
return;
|
||||
}
|
||||
|
||||
lock (_textures)
|
||||
{
|
||||
overlapCount = _textures.FindOverlaps(((MemoryManager)sender).Translate(e.Address), e.Size, ref overlaps);
|
||||
overlapCount = _textures.FindOverlaps(unmapped, ref overlaps);
|
||||
}
|
||||
|
||||
for (int i = 0; i < overlapCount; i++)
|
||||
{
|
||||
overlaps[i].Unmapped();
|
||||
overlaps[i].Unmapped(unmapped);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -494,12 +508,12 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
|
||||
int fullyCompatible = 0;
|
||||
|
||||
// Evaluate compatibility of overlaps
|
||||
// Evaluate compatibility of overlaps, add temporary references
|
||||
|
||||
for (int index = 0; index < overlapsCount; index++)
|
||||
{
|
||||
Texture overlap = _textureOverlaps[index];
|
||||
TextureViewCompatibility overlapCompatibility = overlap.IsViewCompatible(info, range.Value, sizeInfo.LayerSize, out int firstLayer, out int firstLevel);
|
||||
TextureViewCompatibility overlapCompatibility = overlap.IsViewCompatible(info, range.Value, sizeInfo.LayerSize, _context.Capabilities, out int firstLayer, out int firstLevel);
|
||||
|
||||
if (overlapCompatibility == TextureViewCompatibility.Full)
|
||||
{
|
||||
|
@ -514,6 +528,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
}
|
||||
|
||||
_overlapInfo[index] = new OverlapInfo(overlapCompatibility, firstLayer, firstLevel);
|
||||
overlap.IncrementReferenceCount();
|
||||
}
|
||||
|
||||
// Search through the overlaps to find a compatible view and establish any copy dependencies.
|
||||
|
@ -544,7 +559,8 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
// Only copy compatible. If there's another choice for a FULLY compatible texture, choose that instead.
|
||||
|
||||
texture = new Texture(_context, _physicalMemory, info, sizeInfo, range.Value, scaleMode);
|
||||
texture.InitializeGroup(true, true);
|
||||
|
||||
texture.InitializeGroup(true, true, new List<TextureIncompatibleOverlap>());
|
||||
texture.InitializeData(false, false);
|
||||
|
||||
overlap.SynchronizeMemory();
|
||||
|
@ -564,7 +580,14 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
Texture overlap = _textureOverlaps[index];
|
||||
OverlapInfo oInfo = _overlapInfo[index];
|
||||
|
||||
if (oInfo.Compatibility != TextureViewCompatibility.Incompatible && overlap.Group != texture.Group)
|
||||
if (oInfo.Compatibility <= TextureViewCompatibility.LayoutIncompatible)
|
||||
{
|
||||
if (!overlap.IsView && texture.DataOverlaps(overlap))
|
||||
{
|
||||
texture.Group.RegisterIncompatibleOverlap(new TextureIncompatibleOverlap(overlap.Group, oInfo.Compatibility), true);
|
||||
}
|
||||
}
|
||||
else if (overlap.Group != texture.Group)
|
||||
{
|
||||
overlap.SynchronizeMemory();
|
||||
overlap.CreateCopyDependency(texture, oInfo.FirstLayer, oInfo.FirstLevel, true);
|
||||
|
@ -591,78 +614,82 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
bool hasLayerViews = false;
|
||||
bool hasMipViews = false;
|
||||
|
||||
var incompatibleOverlaps = new List<TextureIncompatibleOverlap>();
|
||||
|
||||
for (int index = 0; index < overlapsCount; index++)
|
||||
{
|
||||
Texture overlap = _textureOverlaps[index];
|
||||
bool overlapInCache = overlap.CacheNode != null;
|
||||
|
||||
TextureViewCompatibility compatibility = texture.IsViewCompatible(overlap.Info, overlap.Range, overlap.LayerSize, out int firstLayer, out int firstLevel);
|
||||
TextureViewCompatibility compatibility = texture.IsViewCompatible(overlap.Info, overlap.Range, overlap.LayerSize, _context.Capabilities, out int firstLayer, out int firstLevel);
|
||||
|
||||
if (overlap.IsView && compatibility == TextureViewCompatibility.Full)
|
||||
{
|
||||
compatibility = TextureViewCompatibility.CopyOnly;
|
||||
}
|
||||
|
||||
if (compatibility != TextureViewCompatibility.Incompatible)
|
||||
if (compatibility > TextureViewCompatibility.LayoutIncompatible)
|
||||
{
|
||||
_overlapInfo[viewCompatible] = new OverlapInfo(compatibility, firstLayer, firstLevel);
|
||||
_textureOverlaps[index] = _textureOverlaps[viewCompatible];
|
||||
_textureOverlaps[viewCompatible] = overlap;
|
||||
|
||||
if (compatibility == TextureViewCompatibility.Full)
|
||||
{
|
||||
if (viewCompatible == fullyCompatible)
|
||||
{
|
||||
_overlapInfo[viewCompatible] = new OverlapInfo(compatibility, firstLayer, firstLevel);
|
||||
_textureOverlaps[viewCompatible++] = overlap;
|
||||
}
|
||||
else
|
||||
if (viewCompatible != fullyCompatible)
|
||||
{
|
||||
// Swap overlaps so that the fully compatible views have priority.
|
||||
|
||||
_overlapInfo[viewCompatible] = _overlapInfo[fullyCompatible];
|
||||
_textureOverlaps[viewCompatible++] = _textureOverlaps[fullyCompatible];
|
||||
_textureOverlaps[viewCompatible] = _textureOverlaps[fullyCompatible];
|
||||
|
||||
_overlapInfo[fullyCompatible] = new OverlapInfo(compatibility, firstLayer, firstLevel);
|
||||
_textureOverlaps[fullyCompatible] = overlap;
|
||||
}
|
||||
|
||||
fullyCompatible++;
|
||||
}
|
||||
else
|
||||
{
|
||||
_overlapInfo[viewCompatible] = new OverlapInfo(compatibility, firstLayer, firstLevel);
|
||||
_textureOverlaps[viewCompatible++] = overlap;
|
||||
}
|
||||
|
||||
viewCompatible++;
|
||||
|
||||
hasLayerViews |= overlap.Info.GetSlices() < texture.Info.GetSlices();
|
||||
hasMipViews |= overlap.Info.Levels < texture.Info.Levels;
|
||||
}
|
||||
else
|
||||
{
|
||||
bool dataOverlaps = texture.DataOverlaps(overlap);
|
||||
|
||||
if (!overlap.IsView && dataOverlaps && !incompatibleOverlaps.Exists(incompatible => incompatible.Group == overlap.Group))
|
||||
{
|
||||
incompatibleOverlaps.Add(new TextureIncompatibleOverlap(overlap.Group, compatibility));
|
||||
}
|
||||
|
||||
bool removeOverlap;
|
||||
bool modified = overlap.CheckModified(false);
|
||||
|
||||
if (overlapInCache || !setData)
|
||||
{
|
||||
if (info.GobBlocksInZ > 1 && info.GobBlocksInZ == overlap.Info.GobBlocksInZ)
|
||||
{
|
||||
// Allow overlapping slices of 3D textures. Could be improved in future by making sure the textures don't overlap.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!texture.DataOverlaps(overlap))
|
||||
if (!dataOverlaps)
|
||||
{
|
||||
// Allow textures to overlap if their data does not actually overlap.
|
||||
// This typically happens when mip level subranges of a layered texture are used. (each texture fills the gaps of the others)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (info.GobBlocksInZ > 1 && info.GobBlocksInZ == overlap.Info.GobBlocksInZ)
|
||||
{
|
||||
// Allow overlapping slices of 3D textures. Could be improved in future by making sure the textures don't overlap.
|
||||
continue;
|
||||
}
|
||||
|
||||
// The overlap texture is going to contain garbage data after we draw, or is generally incompatible.
|
||||
// If the texture cannot be entirely contained in the new address space, and one of its view children is compatible with us,
|
||||
// it must be flushed before removal, so that the data is not lost.
|
||||
// The texture group will obtain copy dependencies for any subresources that are compatible between the two textures,
|
||||
// but sometimes its data must be flushed regardless.
|
||||
|
||||
// If the texture was modified since its last use, then that data is probably meant to go into this texture.
|
||||
// If the data has been modified by the CPU, then it also shouldn't be flushed.
|
||||
|
||||
bool viewCompatibleChild = overlap.HasViewCompatibleChild(texture);
|
||||
|
||||
bool flush = overlapInCache && !modified && !texture.Range.Contains(overlap.Range) && viewCompatibleChild;
|
||||
bool flush = overlapInCache && !modified && overlap.AlwaysFlushOnOverlap;
|
||||
|
||||
setData |= modified || flush;
|
||||
|
||||
|
@ -671,7 +698,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
_cache.Remove(overlap, flush);
|
||||
}
|
||||
|
||||
removeOverlap = modified && !viewCompatibleChild;
|
||||
removeOverlap = modified;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -687,12 +714,14 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
}
|
||||
}
|
||||
|
||||
texture.InitializeGroup(hasLayerViews, hasMipViews);
|
||||
texture.InitializeGroup(hasLayerViews, hasMipViews, incompatibleOverlaps);
|
||||
|
||||
// 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);
|
||||
|
||||
texture.Group.InitializeOverlaps();
|
||||
|
||||
for (int index = 0; index < viewCompatible; index++)
|
||||
{
|
||||
Texture overlap = _textureOverlaps[index];
|
||||
|
@ -753,6 +782,11 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
|
||||
ShrinkOverlapsBufferIfNeeded();
|
||||
|
||||
for (int i = 0; i < overlapsCount; i++)
|
||||
{
|
||||
_textureOverlaps[i].DecrementReferenceCount();
|
||||
}
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
|
@ -824,14 +858,16 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
}
|
||||
|
||||
int addressMatches = _textures.FindOverlaps(address, ref _textureOverlaps);
|
||||
Texture textureMatch = null;
|
||||
|
||||
for (int i = 0; i < addressMatches; i++)
|
||||
{
|
||||
Texture texture = _textureOverlaps[i];
|
||||
FormatInfo format = texture.Info.FormatInfo;
|
||||
|
||||
if (texture.Info.DepthOrLayers > 1)
|
||||
if (texture.Info.DepthOrLayers > 1 || texture.Info.Levels > 1 || texture.Info.FormatInfo.IsCompressed)
|
||||
{
|
||||
// Don't support direct buffer copies to anything that isn't a single 2D image, uncompressed.
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -859,11 +895,18 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
|
||||
if (match)
|
||||
{
|
||||
return texture;
|
||||
if (textureMatch == null)
|
||||
{
|
||||
textureMatch = texture;
|
||||
}
|
||||
else if (texture.Group != textureMatch.Group)
|
||||
{
|
||||
return null; // It's ambiguous which texture should match between multiple choices, so leave it up to the slow path.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
return textureMatch;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -2,6 +2,7 @@ using Ryujinx.Common;
|
|||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Texture;
|
||||
using System;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
|
@ -22,7 +23,23 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
Bc4,
|
||||
Bc5,
|
||||
Bc6,
|
||||
Bc7
|
||||
Bc7,
|
||||
Etc2Rgb,
|
||||
Etc2Rgba,
|
||||
Astc4x4,
|
||||
Astc5x4,
|
||||
Astc5x5,
|
||||
Astc6x5,
|
||||
Astc6x6,
|
||||
Astc8x5,
|
||||
Astc8x6,
|
||||
Astc8x8,
|
||||
Astc10x5,
|
||||
Astc10x6,
|
||||
Astc10x8,
|
||||
Astc10x10,
|
||||
Astc12x10,
|
||||
Astc12x12
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -92,34 +109,60 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
return info.FormatInfo;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a texture can flush its data back to guest memory.
|
||||
/// </summary>
|
||||
/// <param name="info">Texture information</param>
|
||||
/// <param name="caps">Host GPU Capabilities</param>
|
||||
/// <returns>True if the texture can flush, false otherwise</returns>
|
||||
public static bool CanTextureFlush(TextureInfo info, Capabilities caps)
|
||||
{
|
||||
if (IsFormatHostIncompatible(info, caps))
|
||||
{
|
||||
return false; // Flushing this format is not supported, as it may have been converted to another host format.
|
||||
}
|
||||
|
||||
if (info.Target == Target.Texture2DMultisample ||
|
||||
info.Target == Target.Texture2DMultisampleArray)
|
||||
{
|
||||
return false; // Flushing multisample textures is not supported, the host does not allow getting their data.
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if two formats are compatible, according to the host API copy format compatibility rules.
|
||||
/// </summary>
|
||||
/// <param name="lhs">First comparand</param>
|
||||
/// <param name="rhs">Second comparand</param>
|
||||
/// <param name="lhsFormat">First comparand</param>
|
||||
/// <param name="rhsFormat">Second comparand</param>
|
||||
/// <param name="caps">Host GPU capabilities</param>
|
||||
/// <returns>True if the formats are compatible, false otherwise</returns>
|
||||
public static bool FormatCompatible(FormatInfo lhs, FormatInfo rhs)
|
||||
public static bool FormatCompatible(TextureInfo lhs, TextureInfo rhs, Capabilities caps)
|
||||
{
|
||||
if (lhs.Format.IsDepthOrStencil() || rhs.Format.IsDepthOrStencil())
|
||||
FormatInfo lhsFormat = lhs.FormatInfo;
|
||||
FormatInfo rhsFormat = rhs.FormatInfo;
|
||||
|
||||
if (lhsFormat.Format.IsDepthOrStencil() || rhsFormat.Format.IsDepthOrStencil())
|
||||
{
|
||||
return lhs.Format == rhs.Format;
|
||||
return lhsFormat.Format == rhsFormat.Format;
|
||||
}
|
||||
|
||||
if (lhs.Format.IsAstc() || rhs.Format.IsAstc())
|
||||
if (IsFormatHostIncompatible(lhs, caps) || IsFormatHostIncompatible(rhs, caps))
|
||||
{
|
||||
return lhs.Format == rhs.Format;
|
||||
return lhsFormat.Format == rhsFormat.Format;
|
||||
}
|
||||
|
||||
if (lhs.IsCompressed && rhs.IsCompressed)
|
||||
if (lhsFormat.IsCompressed && rhsFormat.IsCompressed)
|
||||
{
|
||||
FormatClass lhsClass = GetFormatClass(lhs.Format);
|
||||
FormatClass rhsClass = GetFormatClass(rhs.Format);
|
||||
FormatClass lhsClass = GetFormatClass(lhsFormat.Format);
|
||||
FormatClass rhsClass = GetFormatClass(rhsFormat.Format);
|
||||
|
||||
return lhsClass == rhsClass;
|
||||
}
|
||||
else
|
||||
{
|
||||
return lhs.BytesPerPixel == rhs.BytesPerPixel;
|
||||
return lhsFormat.BytesPerPixel == rhsFormat.BytesPerPixel;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -204,6 +247,10 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
{
|
||||
return TextureViewCompatibility.Incompatible;
|
||||
}
|
||||
else if (first == TextureViewCompatibility.LayoutIncompatible || second == TextureViewCompatibility.LayoutIncompatible)
|
||||
{
|
||||
return TextureViewCompatibility.LayoutIncompatible;
|
||||
}
|
||||
else if (first == TextureViewCompatibility.CopyOnly || second == TextureViewCompatibility.CopyOnly)
|
||||
{
|
||||
return TextureViewCompatibility.CopyOnly;
|
||||
|
@ -214,6 +261,37 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the sizes of two texture levels are copy compatible.
|
||||
/// </summary>
|
||||
/// <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="lhsLevel">Mipmap level of the texture view in relation to this texture</param>
|
||||
/// <param name="rhsLevel">Mipmap level of the texture view in relation to the second texture</param>
|
||||
/// <returns>True if both levels are view compatible</returns>
|
||||
public static bool CopySizeMatches(TextureInfo lhs, TextureInfo rhs, int lhsLevel, int rhsLevel)
|
||||
{
|
||||
Size size = GetAlignedSize(lhs, lhsLevel);
|
||||
|
||||
Size otherSize = GetAlignedSize(rhs, rhsLevel);
|
||||
|
||||
if (size.Width == otherSize.Width && size.Height == otherSize.Height)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (lhs.IsLinear && rhs.IsLinear)
|
||||
{
|
||||
// Copy between linear textures with matching stride.
|
||||
int stride = BitUtils.AlignUp(Math.Max(1, lhs.Stride >> lhsLevel), Constants.StrideAlignment);
|
||||
|
||||
return stride == rhs.Stride;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the sizes of two given textures are view compatible.
|
||||
/// </summary>
|
||||
|
@ -259,11 +337,11 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
// Copy between linear textures with matching stride.
|
||||
int stride = BitUtils.AlignUp(Math.Max(1, lhs.Stride >> level), Constants.StrideAlignment);
|
||||
|
||||
return stride == rhs.Stride ? TextureViewCompatibility.CopyOnly : TextureViewCompatibility.Incompatible;
|
||||
return stride == rhs.Stride ? TextureViewCompatibility.CopyOnly : TextureViewCompatibility.LayoutIncompatible;
|
||||
}
|
||||
else
|
||||
{
|
||||
return TextureViewCompatibility.Incompatible;
|
||||
return TextureViewCompatibility.LayoutIncompatible;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -284,7 +362,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
}
|
||||
else
|
||||
{
|
||||
return TextureViewCompatibility.Incompatible;
|
||||
return TextureViewCompatibility.LayoutIncompatible;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -435,7 +513,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
if (rhs.IsLinear)
|
||||
{
|
||||
int stride = Math.Max(1, lhs.Stride >> level);
|
||||
stride = BitUtils.AlignUp(stride, 32);
|
||||
stride = BitUtils.AlignUp(stride, Constants.StrideAlignment);
|
||||
|
||||
return stride == rhs.Stride;
|
||||
}
|
||||
|
@ -456,6 +534,62 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if it's possible to create a view with the layout of the second texture information from the first.
|
||||
/// The layout information is composed of the Stride for linear textures, or GOB block size
|
||||
/// for block linear textures.
|
||||
/// </summary>
|
||||
/// <param name="lhs">Texture information of the texture view</param>
|
||||
/// <param name="rhs">Texture information of the texture view to compare against</param>
|
||||
/// <param name="lhsLevel">Start level of the texture view, in relation with the first texture</param>
|
||||
/// <param name="rhsLevel">Start level of the texture view, in relation with the second texture</param>
|
||||
/// <returns>True if the layout is compatible, false otherwise</returns>
|
||||
public static bool ViewLayoutCompatible(TextureInfo lhs, TextureInfo rhs, int lhsLevel, int rhsLevel)
|
||||
{
|
||||
if (lhs.IsLinear != rhs.IsLinear)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// For linear textures, gob block sizes are ignored.
|
||||
// For block linear textures, the stride is ignored.
|
||||
if (rhs.IsLinear)
|
||||
{
|
||||
int lhsStride = Math.Max(1, lhs.Stride >> lhsLevel);
|
||||
lhsStride = BitUtils.AlignUp(lhsStride, Constants.StrideAlignment);
|
||||
|
||||
int rhsStride = Math.Max(1, rhs.Stride >> rhsLevel);
|
||||
rhsStride = BitUtils.AlignUp(rhsStride, Constants.StrideAlignment);
|
||||
|
||||
return lhsStride == rhsStride;
|
||||
}
|
||||
else
|
||||
{
|
||||
int lhsHeight = Math.Max(1, lhs.Height >> lhsLevel);
|
||||
int lhsDepth = Math.Max(1, lhs.GetDepth() >> lhsLevel);
|
||||
|
||||
(int lhsGobBlocksInY, int lhsGobBlocksInZ) = SizeCalculator.GetMipGobBlockSizes(
|
||||
lhsHeight,
|
||||
lhsDepth,
|
||||
lhs.FormatInfo.BlockHeight,
|
||||
lhs.GobBlocksInY,
|
||||
lhs.GobBlocksInZ);
|
||||
|
||||
int rhsHeight = Math.Max(1, rhs.Height >> rhsLevel);
|
||||
int rhsDepth = Math.Max(1, rhs.GetDepth() >> rhsLevel);
|
||||
|
||||
(int rhsGobBlocksInY, int rhsGobBlocksInZ) = SizeCalculator.GetMipGobBlockSizes(
|
||||
rhsHeight,
|
||||
rhsDepth,
|
||||
rhs.FormatInfo.BlockHeight,
|
||||
rhs.GobBlocksInY,
|
||||
rhs.GobBlocksInZ);
|
||||
|
||||
return lhsGobBlocksInY == rhsGobBlocksInY &&
|
||||
lhsGobBlocksInZ == rhsGobBlocksInZ;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the view format of the first texture format is compatible with the format of the second.
|
||||
/// In general, the formats are considered compatible if the bytes per pixel values are equal,
|
||||
|
@ -464,10 +598,11 @@ 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>
|
||||
/// <param name="caps">Host GPU capabilities</param>
|
||||
/// <returns>The view compatibility level of the texture formats</returns>
|
||||
public static TextureViewCompatibility ViewFormatCompatible(TextureInfo lhs, TextureInfo rhs)
|
||||
public static TextureViewCompatibility ViewFormatCompatible(TextureInfo lhs, TextureInfo rhs, Capabilities caps)
|
||||
{
|
||||
if (FormatCompatible(lhs.FormatInfo, rhs.FormatInfo))
|
||||
if (FormatCompatible(lhs, rhs, caps))
|
||||
{
|
||||
if (lhs.FormatInfo.IsCompressed != rhs.FormatInfo.IsCompressed)
|
||||
{
|
||||
|
@ -638,6 +773,54 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
case Format.Bc7Srgb:
|
||||
case Format.Bc7Unorm:
|
||||
return FormatClass.Bc7;
|
||||
case Format.Etc2RgbSrgb:
|
||||
case Format.Etc2RgbUnorm:
|
||||
return FormatClass.Etc2Rgb;
|
||||
case Format.Etc2RgbaSrgb:
|
||||
case Format.Etc2RgbaUnorm:
|
||||
return FormatClass.Etc2Rgba;
|
||||
case Format.Astc4x4Srgb:
|
||||
case Format.Astc4x4Unorm:
|
||||
return FormatClass.Astc4x4;
|
||||
case Format.Astc5x4Srgb:
|
||||
case Format.Astc5x4Unorm:
|
||||
return FormatClass.Astc5x4;
|
||||
case Format.Astc5x5Srgb:
|
||||
case Format.Astc5x5Unorm:
|
||||
return FormatClass.Astc5x5;
|
||||
case Format.Astc6x5Srgb:
|
||||
case Format.Astc6x5Unorm:
|
||||
return FormatClass.Astc6x5;
|
||||
case Format.Astc6x6Srgb:
|
||||
case Format.Astc6x6Unorm:
|
||||
return FormatClass.Astc6x6;
|
||||
case Format.Astc8x5Srgb:
|
||||
case Format.Astc8x5Unorm:
|
||||
return FormatClass.Astc8x5;
|
||||
case Format.Astc8x6Srgb:
|
||||
case Format.Astc8x6Unorm:
|
||||
return FormatClass.Astc8x6;
|
||||
case Format.Astc8x8Srgb:
|
||||
case Format.Astc8x8Unorm:
|
||||
return FormatClass.Astc8x8;
|
||||
case Format.Astc10x5Srgb:
|
||||
case Format.Astc10x5Unorm:
|
||||
return FormatClass.Astc10x5;
|
||||
case Format.Astc10x6Srgb:
|
||||
case Format.Astc10x6Unorm:
|
||||
return FormatClass.Astc10x6;
|
||||
case Format.Astc10x8Srgb:
|
||||
case Format.Astc10x8Unorm:
|
||||
return FormatClass.Astc10x8;
|
||||
case Format.Astc10x10Srgb:
|
||||
case Format.Astc10x10Unorm:
|
||||
return FormatClass.Astc10x10;
|
||||
case Format.Astc12x10Srgb:
|
||||
case Format.Astc12x10Unorm:
|
||||
return FormatClass.Astc12x10;
|
||||
case Format.Astc12x12Srgb:
|
||||
case Format.Astc12x12Unorm:
|
||||
return FormatClass.Astc12x12;
|
||||
}
|
||||
|
||||
return FormatClass.Unclassified;
|
||||
|
|
|
@ -2,12 +2,34 @@
|
|||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Gpu.Memory;
|
||||
using Ryujinx.Graphics.Texture;
|
||||
using Ryujinx.Memory;
|
||||
using Ryujinx.Memory.Range;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
/// <summary>
|
||||
/// An overlapping texture group with a given view compatibility.
|
||||
/// </summary>
|
||||
struct TextureIncompatibleOverlap
|
||||
{
|
||||
public readonly TextureGroup Group;
|
||||
public readonly TextureViewCompatibility Compatibility;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new texture incompatible overlap.
|
||||
/// </summary>
|
||||
/// <param name="group">The group that is incompatible</param>
|
||||
/// <param name="compatibility">The view compatibility for the group</param>
|
||||
public TextureIncompatibleOverlap(TextureGroup group, TextureViewCompatibility compatibility)
|
||||
{
|
||||
Group = group;
|
||||
Compatibility = compatibility;
|
||||
}
|
||||
}
|
||||
|
||||
/// <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.
|
||||
|
@ -29,6 +51,11 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
/// </summary>
|
||||
public bool HasCopyDependencies { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if this texture has any incompatible overlaps alive.
|
||||
/// </summary>
|
||||
public bool HasIncompatibleOverlaps => _incompatibleOverlaps.Count > 0;
|
||||
|
||||
private readonly GpuContext _context;
|
||||
private readonly PhysicalMemory _physicalMemory;
|
||||
|
||||
|
@ -49,13 +76,21 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
private TextureGroupHandle[] _handles;
|
||||
private bool[] _loadNeeded;
|
||||
|
||||
/// <summary>
|
||||
/// Other texture groups that have incompatible overlaps with this one.
|
||||
/// </summary>
|
||||
private List<TextureIncompatibleOverlap> _incompatibleOverlaps;
|
||||
private bool _incompatibleOverlapsDirty = true;
|
||||
private bool _flushIncompatibleOverlaps;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new texture group.
|
||||
/// </summary>
|
||||
/// <param name="context">GPU context that the texture group belongs to</param>
|
||||
/// <param name="physicalMemory">Physical memory where the <paramref name="storage"/> texture is mapped</param>
|
||||
/// <param name="storage">The storage texture for this group</param>
|
||||
public TextureGroup(GpuContext context, PhysicalMemory physicalMemory, Texture storage)
|
||||
/// <param name="incompatibleOverlaps">Groups that overlap with this one but are incompatible</param>
|
||||
public TextureGroup(GpuContext context, PhysicalMemory physicalMemory, Texture storage, List<TextureIncompatibleOverlap> incompatibleOverlaps)
|
||||
{
|
||||
Storage = storage;
|
||||
_context = context;
|
||||
|
@ -64,6 +99,9 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
_is3D = storage.Info.Target == Target.Texture3D;
|
||||
_layers = storage.Info.GetSlices();
|
||||
_levels = storage.Info.Levels;
|
||||
|
||||
_incompatibleOverlaps = incompatibleOverlaps;
|
||||
_flushIncompatibleOverlaps = TextureCompatibility.IsFormatHostIncompatible(storage.Info, context.Capabilities);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -82,6 +120,86 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
RecalculateHandleRegions();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize all incompatible overlaps in the list, registering them with the other texture groups
|
||||
/// and creating copy dependencies when partially compatible.
|
||||
/// </summary>
|
||||
public void InitializeOverlaps()
|
||||
{
|
||||
foreach (TextureIncompatibleOverlap overlap in _incompatibleOverlaps)
|
||||
{
|
||||
if (overlap.Compatibility == TextureViewCompatibility.LayoutIncompatible)
|
||||
{
|
||||
CreateCopyDependency(overlap.Group, false);
|
||||
}
|
||||
|
||||
overlap.Group._incompatibleOverlaps.Add(new TextureIncompatibleOverlap(this, overlap.Compatibility));
|
||||
overlap.Group._incompatibleOverlapsDirty = true;
|
||||
}
|
||||
|
||||
if (_incompatibleOverlaps.Count > 0)
|
||||
{
|
||||
SignalIncompatibleOverlapModified();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Signal that the group is dirty to all views and the storage.
|
||||
/// </summary>
|
||||
private void SignalAllDirty()
|
||||
{
|
||||
Storage.SignalGroupDirty();
|
||||
if (_views != null)
|
||||
{
|
||||
foreach (Texture texture in _views)
|
||||
{
|
||||
texture.SignalGroupDirty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Signal that an incompatible overlap has been modified.
|
||||
/// If this group must flush incompatible overlaps, the group is signalled as dirty too.
|
||||
/// </summary>
|
||||
private void SignalIncompatibleOverlapModified()
|
||||
{
|
||||
_incompatibleOverlapsDirty = true;
|
||||
|
||||
if (_flushIncompatibleOverlaps)
|
||||
{
|
||||
SignalAllDirty();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Flushes incompatible overlaps if the storage format requires it, and they have been modified.
|
||||
/// This allows unsupported host formats to accept data written to format aliased textures.
|
||||
/// </summary>
|
||||
/// <returns>True if data was flushed, false otherwise</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool FlushIncompatibleOverlapsIfNeeded()
|
||||
{
|
||||
if (_flushIncompatibleOverlaps && _incompatibleOverlapsDirty)
|
||||
{
|
||||
bool flushed = false;
|
||||
|
||||
foreach (var overlap in _incompatibleOverlaps)
|
||||
{
|
||||
flushed |= overlap.Group.Storage.FlushModified(true);
|
||||
}
|
||||
|
||||
_incompatibleOverlapsDirty = false;
|
||||
|
||||
return flushed;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check and optionally consume the dirty flags for a given texture.
|
||||
/// The state is shared between views of the same layers and levels.
|
||||
|
@ -124,6 +242,8 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
/// <param name="texture">The texture being used</param>
|
||||
public void SynchronizeMemory(Texture texture)
|
||||
{
|
||||
FlushIncompatibleOverlapsIfNeeded();
|
||||
|
||||
EvaluateRelevantHandles(texture, (baseHandle, regionCount, split) =>
|
||||
{
|
||||
bool dirty = false;
|
||||
|
@ -136,7 +256,6 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
|
||||
bool modified = group.Modified;
|
||||
bool handleDirty = false;
|
||||
bool handleModified = false;
|
||||
bool handleUnmapped = false;
|
||||
|
||||
foreach (CpuRegionHandle handle in group.Handles)
|
||||
|
@ -149,22 +268,28 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
else
|
||||
{
|
||||
handleUnmapped |= handle.Unmapped;
|
||||
handleModified |= modified;
|
||||
}
|
||||
}
|
||||
|
||||
// If the modified flag is still present, prefer the data written from gpu.
|
||||
// A write from CPU will do a flush before writing its data, which should unset this.
|
||||
if (modified)
|
||||
{
|
||||
handleDirty = false;
|
||||
}
|
||||
|
||||
// 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())
|
||||
if (group.NeedsCopy && group.Copy(_context))
|
||||
{
|
||||
anyModified |= true; // The copy target has been modified.
|
||||
handleDirty = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
anyModified |= handleModified;
|
||||
anyModified |= modified;
|
||||
dirty |= handleDirty;
|
||||
}
|
||||
|
||||
|
@ -218,7 +343,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
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 endOffset = Math.Min(offset + _sliceSizes[info.BaseLevel + level], (int)Storage.Size);
|
||||
int size = endOffset - offset;
|
||||
|
||||
ReadOnlySpan<byte> data = _physicalMemory.GetSpan(Storage.Range.GetSlice((ulong)offset, (ulong)size));
|
||||
|
@ -251,25 +376,170 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether flushes in this texture group should be tracked.
|
||||
/// Incompatible overlaps may need data from this texture to flush tracked for it to be visible to them.
|
||||
/// </summary>
|
||||
/// <returns>True if flushes should be tracked, false otherwise</returns>
|
||||
private bool ShouldFlushTriggerTracking()
|
||||
{
|
||||
foreach (var overlap in _incompatibleOverlaps)
|
||||
{
|
||||
if (overlap.Group._flushIncompatibleOverlaps)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets data from the host GPU, and flushes a slice to guest memory.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method should be used to retrieve data that was modified by the host GPU.
|
||||
/// This is not cheap, avoid doing that unless strictly needed.
|
||||
/// When possible, the data is written directly into guest memory, rather than copied.
|
||||
/// </remarks>
|
||||
/// <param name="tracked">True if writing the texture data is tracked, false otherwise</param>
|
||||
/// <param name="sliceIndex">The index of the slice to flush</param>
|
||||
/// <param name="texture">The specific host texture to flush. Defaults to the storage texture</param>
|
||||
private void FlushTextureDataSliceToGuest(bool tracked, int sliceIndex, ITexture texture = null)
|
||||
{
|
||||
(int layer, int level) = GetLayerLevelForView(sliceIndex);
|
||||
|
||||
int offset = _allOffsets[sliceIndex];
|
||||
int endOffset = Math.Min(offset + _sliceSizes[level], (int)Storage.Size);
|
||||
int size = endOffset - offset;
|
||||
|
||||
using WritableRegion region = _physicalMemory.GetWritableRegion(Storage.Range.GetSlice((ulong)offset, (ulong)size), tracked);
|
||||
|
||||
Storage.GetTextureDataSliceFromGpu(region.Memory.Span, layer, level, tracked, texture);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets and flushes a number of slices of the storage texture to guest memory.
|
||||
/// </summary>
|
||||
/// <param name="tracked">True if writing the texture data is tracked, false otherwise</param>
|
||||
/// <param name="sliceStart">The first slice to flush</param>
|
||||
/// <param name="sliceEnd">The slice to finish flushing on (exclusive)</param>
|
||||
/// <param name="texture">The specific host texture to flush. Defaults to the storage texture</param>
|
||||
private void FlushSliceRange(bool tracked, int sliceStart, int sliceEnd, ITexture texture = null)
|
||||
{
|
||||
for (int i = sliceStart; i < sliceEnd; i++)
|
||||
{
|
||||
FlushTextureDataSliceToGuest(tracked, i, texture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Flush modified ranges for a given texture.
|
||||
/// </summary>
|
||||
/// <param name="texture">The texture being used</param>
|
||||
/// <param name="tracked">True if the flush writes should be tracked, false otherwise</param>
|
||||
/// <returns>True if data was flushed, false otherwise</returns>
|
||||
public bool FlushModified(Texture texture, bool tracked)
|
||||
{
|
||||
tracked = tracked || ShouldFlushTriggerTracking();
|
||||
bool flushed = false;
|
||||
|
||||
EvaluateRelevantHandles(texture, (baseHandle, regionCount, split) =>
|
||||
{
|
||||
int startSlice = 0;
|
||||
int endSlice = 0;
|
||||
bool allModified = true;
|
||||
|
||||
for (int i = 0; i < regionCount; i++)
|
||||
{
|
||||
TextureGroupHandle group = _handles[baseHandle + i];
|
||||
|
||||
if (group.Modified)
|
||||
{
|
||||
if (endSlice < group.BaseSlice)
|
||||
{
|
||||
if (endSlice > startSlice)
|
||||
{
|
||||
FlushSliceRange(tracked, startSlice, endSlice);
|
||||
flushed = true;
|
||||
}
|
||||
|
||||
startSlice = group.BaseSlice;
|
||||
}
|
||||
|
||||
endSlice = group.BaseSlice + group.SliceCount;
|
||||
|
||||
if (tracked)
|
||||
{
|
||||
group.Modified = false;
|
||||
|
||||
foreach (Texture texture in group.Overlaps)
|
||||
{
|
||||
texture.SignalModifiedDirty();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
allModified = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (endSlice > startSlice)
|
||||
{
|
||||
if (allModified && !split)
|
||||
{
|
||||
texture.Flush(tracked);
|
||||
}
|
||||
else
|
||||
{
|
||||
FlushSliceRange(tracked, startSlice, endSlice);
|
||||
}
|
||||
|
||||
flushed = true;
|
||||
}
|
||||
});
|
||||
|
||||
Storage.SignalModifiedDirty();
|
||||
|
||||
return flushed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears competing modified flags for all incompatible ranges, if they have possibly been modified.
|
||||
/// </summary>
|
||||
/// <param name="texture">The texture that has been modified</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void ClearIncompatibleOverlaps(Texture texture)
|
||||
{
|
||||
if (_incompatibleOverlapsDirty)
|
||||
{
|
||||
foreach (TextureIncompatibleOverlap incompatible in _incompatibleOverlaps)
|
||||
{
|
||||
incompatible.Group.ClearModified(texture.Range, this);
|
||||
|
||||
incompatible.Group.SignalIncompatibleOverlapModified();
|
||||
}
|
||||
|
||||
_incompatibleOverlapsDirty = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <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)
|
||||
public void SignalModified(Texture texture)
|
||||
{
|
||||
ClearIncompatibleOverlaps(texture);
|
||||
|
||||
EvaluateRelevantHandles(texture, (baseHandle, regionCount, split) =>
|
||||
{
|
||||
for (int i = 0; i < regionCount; i++)
|
||||
{
|
||||
TextureGroupHandle group = _handles[baseHandle + i];
|
||||
|
||||
group.SignalModified();
|
||||
|
||||
if (registerAction)
|
||||
{
|
||||
RegisterAction(group);
|
||||
}
|
||||
group.SignalModified(_context);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -279,21 +549,17 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
/// </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)
|
||||
public void SignalModifying(Texture texture, bool bound)
|
||||
{
|
||||
ClearIncompatibleOverlaps(texture);
|
||||
|
||||
EvaluateRelevantHandles(texture, (baseHandle, regionCount, split) =>
|
||||
{
|
||||
for (int i = 0; i < regionCount; i++)
|
||||
{
|
||||
TextureGroupHandle group = _handles[baseHandle + i];
|
||||
|
||||
group.SignalModifying(bound);
|
||||
|
||||
if (registerAction)
|
||||
{
|
||||
RegisterAction(group);
|
||||
}
|
||||
group.SignalModifying(bound, _context);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -371,7 +637,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
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.
|
||||
|
@ -485,7 +751,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
}
|
||||
|
||||
baseLayer = handleIndex;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
baseLayer = 0;
|
||||
|
@ -634,7 +900,19 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
size = _sliceSizes[firstLevel];
|
||||
}
|
||||
|
||||
var groupHandle = new TextureGroupHandle(this, _allOffsets[viewStart], (ulong)size, _views, firstLayer, firstLevel, result.ToArray());
|
||||
offset = _allOffsets[viewStart];
|
||||
ulong maxSize = Storage.Size - (ulong)offset;
|
||||
|
||||
var groupHandle = new TextureGroupHandle(
|
||||
this,
|
||||
offset,
|
||||
Math.Min(maxSize, (ulong)size),
|
||||
_views,
|
||||
firstLayer,
|
||||
firstLevel,
|
||||
viewStart,
|
||||
views,
|
||||
result.ToArray());
|
||||
|
||||
foreach (CpuRegionHandle handle in result)
|
||||
{
|
||||
|
@ -694,11 +972,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
}
|
||||
}
|
||||
|
||||
Storage.SignalGroupDirty();
|
||||
foreach (Texture texture in views)
|
||||
{
|
||||
texture.SignalGroupDirty();
|
||||
}
|
||||
SignalAllDirty();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -706,7 +980,8 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
/// </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)
|
||||
/// <param name="relativeOffset">The offset of the old handles in relation to the new ones</param>
|
||||
private void InheritHandles(TextureGroupHandle[] oldHandles, TextureGroupHandle[] handles, int relativeOffset)
|
||||
{
|
||||
foreach (var group in handles)
|
||||
{
|
||||
|
@ -716,7 +991,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
|
||||
foreach (var oldGroup in oldHandles)
|
||||
{
|
||||
if (group.OverlapsWith(oldGroup.Offset, oldGroup.Size))
|
||||
if (group.OverlapsWith(oldGroup.Offset + relativeOffset, oldGroup.Size))
|
||||
{
|
||||
foreach (var oldHandle in oldGroup.Handles)
|
||||
{
|
||||
|
@ -725,13 +1000,13 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
dirty |= oldHandle.Dirty;
|
||||
}
|
||||
}
|
||||
|
||||
group.Inherit(oldGroup);
|
||||
|
||||
group.Inherit(oldGroup, group.Offset == oldGroup.Offset + relativeOffset);
|
||||
}
|
||||
}
|
||||
|
||||
if (dirty && !handle.Dirty)
|
||||
{
|
||||
{
|
||||
handle.Reprotect(true);
|
||||
}
|
||||
|
||||
|
@ -741,6 +1016,11 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var oldGroup in oldHandles)
|
||||
{
|
||||
oldGroup.Modified = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -760,7 +1040,16 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
RecalculateHandleRegions();
|
||||
}
|
||||
|
||||
InheritHandles(other._handles, _handles);
|
||||
foreach (TextureIncompatibleOverlap incompatible in other._incompatibleOverlaps)
|
||||
{
|
||||
RegisterIncompatibleOverlap(incompatible, false);
|
||||
|
||||
incompatible.Group._incompatibleOverlaps.RemoveAll(overlap => overlap.Group == other);
|
||||
}
|
||||
|
||||
int relativeOffset = Storage.Range.FindOffset(other.Storage.Range);
|
||||
|
||||
InheritHandles(other._handles, _handles, relativeOffset);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -782,7 +1071,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
}
|
||||
}
|
||||
|
||||
InheritHandles(_handles, handles);
|
||||
InheritHandles(_handles, handles, 0);
|
||||
|
||||
foreach (var oldGroup in _handles)
|
||||
{
|
||||
|
@ -815,7 +1104,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
cpuRegionHandles[i] = GenerateHandle(currentRange.Address, currentRange.Size);
|
||||
}
|
||||
|
||||
var groupHandle = new TextureGroupHandle(this, 0, Storage.Size, _views, 0, 0, cpuRegionHandles);
|
||||
var groupHandle = new TextureGroupHandle(this, 0, Storage.Size, _views, 0, 0, 0, _allOffsets.Length, cpuRegionHandles);
|
||||
|
||||
foreach (CpuRegionHandle handle in cpuRegionHandles)
|
||||
{
|
||||
|
@ -855,7 +1144,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
}
|
||||
|
||||
handles = handlesList.ToArray();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
handles = new TextureGroupHandle[layerHandles * levelHandles];
|
||||
|
@ -953,34 +1242,172 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
|
||||
if (copyTo)
|
||||
{
|
||||
otherHandle.Copy(handle);
|
||||
otherHandle.Copy(_context, handle);
|
||||
}
|
||||
else
|
||||
{
|
||||
handle.Copy(otherHandle);
|
||||
handle.Copy(_context, otherHandle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A flush has been requested on a tracked region. Find an appropriate view to flush.
|
||||
/// Creates a copy dependency to another texture group, where handles overlap.
|
||||
/// Scans through all handles to find compatible patches in the other group.
|
||||
/// </summary>
|
||||
/// <param name="other">The texture group that overlaps 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(TextureGroup other, bool copyTo)
|
||||
{
|
||||
for (int i = 0; i < _allOffsets.Length; i++)
|
||||
{
|
||||
(int layer, int level) = GetLayerLevelForView(i);
|
||||
MultiRange handleRange = Storage.Range.GetSlice((ulong)_allOffsets[i], 1);
|
||||
ulong handleBase = handleRange.GetSubRange(0).Address;
|
||||
|
||||
for (int j = 0; j < other._handles.Length; j++)
|
||||
{
|
||||
(int otherLayer, int otherLevel) = other.GetLayerLevelForView(j);
|
||||
MultiRange otherHandleRange = other.Storage.Range.GetSlice((ulong)other._allOffsets[j], 1);
|
||||
ulong otherHandleBase = otherHandleRange.GetSubRange(0).Address;
|
||||
|
||||
if (handleBase == otherHandleBase)
|
||||
{
|
||||
// Check if the two sizes are compatible.
|
||||
TextureInfo info = Storage.Info;
|
||||
TextureInfo otherInfo = other.Storage.Info;
|
||||
|
||||
if (TextureCompatibility.ViewLayoutCompatible(info, otherInfo, level, otherLevel) &&
|
||||
TextureCompatibility.CopySizeMatches(info, otherInfo, level, otherLevel))
|
||||
{
|
||||
// These textures are copy compatible. Create the dependency.
|
||||
|
||||
EnsureFullSubdivision();
|
||||
other.EnsureFullSubdivision();
|
||||
|
||||
TextureGroupHandle handle = _handles[i];
|
||||
TextureGroupHandle otherHandle = other._handles[j];
|
||||
|
||||
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(_context, handle);
|
||||
}
|
||||
else
|
||||
{
|
||||
handle.Copy(_context, otherHandle);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers another texture group as an incompatible overlap, if not already registered.
|
||||
/// </summary>
|
||||
/// <param name="other">The texture group to add to the incompatible overlaps list</param>
|
||||
/// <param name="copy">True if the overlap should register copy dependencies</param>
|
||||
public void RegisterIncompatibleOverlap(TextureIncompatibleOverlap other, bool copy)
|
||||
{
|
||||
if (!_incompatibleOverlaps.Exists(overlap => overlap.Group == other.Group))
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
_incompatibleOverlaps.Add(other);
|
||||
other.Group._incompatibleOverlaps.Add(new TextureIncompatibleOverlap(this, other.Compatibility));
|
||||
}
|
||||
|
||||
other.Group.SignalIncompatibleOverlapModified();
|
||||
SignalIncompatibleOverlapModified();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clear modified flags in the given range.
|
||||
/// This will stop any GPU written data from flushing or copying to dependent textures.
|
||||
/// </summary>
|
||||
/// <param name="range">The range to clear modified flags in</param>
|
||||
/// <param name="ignore">Ignore handles that have a copy dependency to the specified group</param>
|
||||
public void ClearModified(MultiRange range, TextureGroup ignore = null)
|
||||
{
|
||||
TextureGroupHandle[] handles = _handles;
|
||||
|
||||
foreach (TextureGroupHandle handle in handles)
|
||||
{
|
||||
// Handles list is not modified by another thread, only replaced, so this is thread safe.
|
||||
// Remove modified flags from all overlapping handles, so that the textures don't flush to unmapped/remapped GPU memory.
|
||||
|
||||
MultiRange subRange = Storage.Range.GetSlice((ulong)handle.Offset, (ulong)handle.Size);
|
||||
|
||||
if (range.OverlapsWith(subRange))
|
||||
{
|
||||
if ((ignore == null || !handle.HasDependencyTo(ignore)) && handle.Modified)
|
||||
{
|
||||
handle.Modified = false;
|
||||
Storage.SignalModifiedDirty();
|
||||
|
||||
lock (handle.Overlaps)
|
||||
{
|
||||
foreach (Texture texture in handle.Overlaps)
|
||||
{
|
||||
texture.SignalModifiedDirty();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Storage.SignalModifiedDirty();
|
||||
|
||||
if (_views != null)
|
||||
{
|
||||
foreach (Texture texture in _views)
|
||||
{
|
||||
texture.SignalModifiedDirty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A flush has been requested on a tracked region. Flush texture data for the given handle.
|
||||
/// </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)
|
||||
if (!handle.Modified)
|
||||
{
|
||||
foreach (Texture overlap in handle.Overlaps)
|
||||
{
|
||||
overlap.ExternalFlush(address, size);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
handle.Modified = false;
|
||||
_context.Renderer.BackgroundContextAction(() =>
|
||||
{
|
||||
handle.Sync(_context);
|
||||
|
||||
Storage.SignalModifiedDirty();
|
||||
|
||||
lock (handle.Overlaps)
|
||||
{
|
||||
foreach (Texture texture in handle.Overlaps)
|
||||
{
|
||||
texture.SignalModifiedDirty();
|
||||
}
|
||||
}
|
||||
|
||||
if (TextureCompatibility.CanTextureFlush(Storage.Info, _context.Capabilities))
|
||||
{
|
||||
FlushSliceRange(false, handle.BaseSlice, handle.BaseSlice + handle.SliceCount, Storage.GetFlushTexture());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -992,6 +1419,11 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
{
|
||||
group.Dispose();
|
||||
}
|
||||
|
||||
foreach (TextureIncompatibleOverlap incompatible in _incompatibleOverlaps)
|
||||
{
|
||||
incompatible.Group._incompatibleOverlaps.RemoveAll(overlap => overlap.Group == this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,28 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
private int _firstLevel;
|
||||
private int _firstLayer;
|
||||
|
||||
// Sync state for texture flush.
|
||||
|
||||
/// <summary>
|
||||
/// The sync number last registered.
|
||||
/// </summary>
|
||||
private ulong _registeredSync;
|
||||
|
||||
/// <summary>
|
||||
/// The sync number when the texture was last modified by GPU.
|
||||
/// </summary>
|
||||
private ulong _modifiedSync;
|
||||
|
||||
/// <summary>
|
||||
/// Whether a tracking action is currently registered or not.
|
||||
/// </summary>
|
||||
private bool _actionRegistered;
|
||||
|
||||
/// <summary>
|
||||
/// Whether a sync action is currently registered or not.
|
||||
/// </summary>
|
||||
private bool _syncActionRegistered;
|
||||
|
||||
/// <summary>
|
||||
/// The byte offset from the start of the storage of this handle.
|
||||
/// </summary>
|
||||
|
@ -29,6 +51,16 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
/// </summary>
|
||||
public int Size { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The base slice index for this handle.
|
||||
/// </summary>
|
||||
public int BaseSlice { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The number of slices covered by this handle.
|
||||
/// </summary>
|
||||
public int SliceCount { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The textures which this handle overlaps with.
|
||||
/// </summary>
|
||||
|
@ -68,8 +100,18 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
/// <param name="views">All views of the storage texture, used to calculate overlaps</param>
|
||||
/// <param name="firstLayer">The first layer of this handle in the storage texture</param>
|
||||
/// <param name="firstLevel">The first level of this handle in the storage texture</param>
|
||||
/// <param name="baseSlice">The base slice index of this handle</param>
|
||||
/// <param name="sliceCount">The number of slices this handle covers</param>
|
||||
/// <param name="handles">The memory tracking handles that cover this handle</param>
|
||||
public TextureGroupHandle(TextureGroup group, int offset, ulong size, List<Texture> views, int firstLayer, int firstLevel, CpuRegionHandle[] handles)
|
||||
public TextureGroupHandle(TextureGroup group,
|
||||
int offset,
|
||||
ulong size,
|
||||
List<Texture> views,
|
||||
int firstLayer,
|
||||
int firstLevel,
|
||||
int baseSlice,
|
||||
int sliceCount,
|
||||
CpuRegionHandle[] handles)
|
||||
{
|
||||
_group = group;
|
||||
_firstLayer = firstLayer;
|
||||
|
@ -80,6 +122,9 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
Overlaps = new List<Texture>();
|
||||
Dependencies = new List<TextureDependency>();
|
||||
|
||||
BaseSlice = baseSlice;
|
||||
SliceCount = sliceCount;
|
||||
|
||||
if (views != null)
|
||||
{
|
||||
RecalculateOverlaps(group, views);
|
||||
|
@ -113,10 +158,32 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a sync action to happen for this handle, and an interim flush action on the tracking handle.
|
||||
/// </summary>
|
||||
/// <param name="context">The GPU context to register a sync action on</param>
|
||||
private void RegisterSync(GpuContext context)
|
||||
{
|
||||
if (!_syncActionRegistered)
|
||||
{
|
||||
_modifiedSync = context.SyncNumber;
|
||||
context.RegisterSyncAction(SyncAction, true);
|
||||
_syncActionRegistered = true;
|
||||
}
|
||||
|
||||
if (!_actionRegistered)
|
||||
{
|
||||
_group.RegisterAction(this);
|
||||
|
||||
_actionRegistered = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Signal that this handle has been modified to any existing dependencies, and set the modified flag.
|
||||
/// </summary>
|
||||
public void SignalModified()
|
||||
/// <param name="context">The GPU context to register a sync action on</param>
|
||||
public void SignalModified(GpuContext context)
|
||||
{
|
||||
Modified = true;
|
||||
|
||||
|
@ -126,15 +193,18 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
{
|
||||
dependency.SignalModified();
|
||||
}
|
||||
|
||||
RegisterSync(context);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Signal that this handle has either started or ended being modified.
|
||||
/// </summary>
|
||||
/// <param name="bound">True if this handle is being bound, false if unbound</param>
|
||||
public void SignalModifying(bool bound)
|
||||
/// <param name="context">The GPU context to register a sync action on</param>
|
||||
public void SignalModifying(bool bound, GpuContext context)
|
||||
{
|
||||
SignalModified();
|
||||
SignalModified(context);
|
||||
|
||||
// Note: Bind count currently resets to 0 on inherit for safety, as the handle <-> view relationship can change.
|
||||
_bindCount = Math.Max(0, _bindCount + (bound ? 1 : -1));
|
||||
|
@ -156,12 +226,69 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wait for the latest sync number that the texture handle was written to,
|
||||
/// removing the modified flag if it was reached, or leaving it set if it has not yet been created.
|
||||
/// </summary>
|
||||
/// <param name="context">The GPU context used to wait for sync</param>
|
||||
public void Sync(GpuContext context)
|
||||
{
|
||||
_actionRegistered = false;
|
||||
|
||||
bool needsSync = !context.IsGpuThread();
|
||||
|
||||
if (needsSync)
|
||||
{
|
||||
ulong registeredSync = _registeredSync;
|
||||
long diff = (long)(context.SyncNumber - registeredSync);
|
||||
|
||||
if (diff > 0)
|
||||
{
|
||||
context.Renderer.WaitSync(registeredSync);
|
||||
|
||||
if ((long)(_modifiedSync - registeredSync) > 0)
|
||||
{
|
||||
// Flush the data in a previous state. Do not remove the modified flag - it will be removed to ignore following writes.
|
||||
return;
|
||||
}
|
||||
|
||||
Modified = false;
|
||||
}
|
||||
|
||||
// If the difference is <= 0, no data is not ready yet. Flush any data we can without waiting or removing modified flag.
|
||||
}
|
||||
else
|
||||
{
|
||||
Modified = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Action to perform when a sync number is registered after modification.
|
||||
/// This action will register a read tracking action on the memory tracking handle so that a flush from CPU can happen.
|
||||
/// </summary>
|
||||
private void SyncAction()
|
||||
{
|
||||
// Register region tracking for CPU? (again)
|
||||
_registeredSync = _modifiedSync;
|
||||
_syncActionRegistered = false;
|
||||
|
||||
if (!_actionRegistered)
|
||||
{
|
||||
_group.RegisterAction(this);
|
||||
|
||||
_actionRegistered = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Signal that a copy dependent texture has been modified, and must have its data copied to this one.
|
||||
/// </summary>
|
||||
/// <param name="copyFrom">The texture handle that must defer a copy to this one</param>
|
||||
public void DeferCopy(TextureGroupHandle copyFrom)
|
||||
{
|
||||
Modified = false;
|
||||
|
||||
DeferredCopy = copyFrom;
|
||||
|
||||
_group.Storage.SignalGroupDirty();
|
||||
|
@ -247,73 +374,112 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
/// <summary>
|
||||
/// Perform a copy from the provided handle to this one, or perform a deferred copy if none is provided.
|
||||
/// </summary>
|
||||
/// <param name="context">GPU context to register sync for modified handles</param>
|
||||
/// <param name="fromHandle">The handle to copy from. If not provided, this method will copy from and clear the deferred copy instead</param>
|
||||
/// <returns>True if the copy was performed, false otherwise</returns>
|
||||
public bool Copy(TextureGroupHandle fromHandle = null)
|
||||
public bool Copy(GpuContext context, TextureGroupHandle fromHandle = null)
|
||||
{
|
||||
bool result = false;
|
||||
bool shouldCopy = false;
|
||||
|
||||
if (fromHandle == null)
|
||||
{
|
||||
fromHandle = DeferredCopy;
|
||||
|
||||
if (fromHandle != null && fromHandle._bindCount == 0)
|
||||
if (fromHandle != null)
|
||||
{
|
||||
// Repeat the copy in future if the bind count is greater than 0.
|
||||
DeferredCopy = null;
|
||||
// Only copy if the copy texture is still modified.
|
||||
// It will be set as unmodified if new data is written from CPU, as the data previously in the texture will flush.
|
||||
// It will also set as unmodified if a copy is deferred to it.
|
||||
|
||||
shouldCopy = fromHandle.Modified;
|
||||
|
||||
if (fromHandle._bindCount == 0)
|
||||
{
|
||||
// Repeat the copy in future if the bind count is greater than 0.
|
||||
DeferredCopy = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fromHandle != null)
|
||||
else
|
||||
{
|
||||
// If the copy texture is dirty, do not copy. Its data no longer matters, and this handle should also be dirty.
|
||||
if (!fromHandle.CheckDirty())
|
||||
// Copies happen directly when initializing a copy dependency.
|
||||
// If dirty, do not copy. Its data no longer matters, and this handle should also be dirty.
|
||||
// Also, only direct copy if the data in this handle is not already modified (can be set by copies from modified handles).
|
||||
shouldCopy = !fromHandle.CheckDirty() && (fromHandle.Modified || !Modified);
|
||||
}
|
||||
|
||||
if (shouldCopy)
|
||||
{
|
||||
Texture from = fromHandle._group.Storage;
|
||||
Texture to = _group.Storage;
|
||||
|
||||
if (from.ScaleFactor != to.ScaleFactor)
|
||||
{
|
||||
Texture from = fromHandle._group.Storage;
|
||||
Texture to = _group.Storage;
|
||||
to.PropagateScale(from);
|
||||
}
|
||||
|
||||
if (from.ScaleFactor != to.ScaleFactor)
|
||||
{
|
||||
to.PropagateScale(from);
|
||||
}
|
||||
|
||||
from.HostTexture.CopyTo(
|
||||
to.HostTexture,
|
||||
fromHandle._firstLayer,
|
||||
_firstLayer,
|
||||
fromHandle._firstLevel,
|
||||
_firstLevel);
|
||||
from.HostTexture.CopyTo(
|
||||
to.HostTexture,
|
||||
fromHandle._firstLayer,
|
||||
_firstLayer,
|
||||
fromHandle._firstLevel,
|
||||
_firstLevel);
|
||||
|
||||
if (fromHandle.Modified)
|
||||
{
|
||||
Modified = true;
|
||||
|
||||
_group.RegisterAction(this);
|
||||
|
||||
result = true;
|
||||
RegisterSync(context);
|
||||
}
|
||||
|
||||
result = true;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inherit modified flags and dependencies from another texture handle.
|
||||
/// Check if this handle has a dependency to a given texture group.
|
||||
/// </summary>
|
||||
/// <param name="old">The texture handle to inherit from</param>
|
||||
public void Inherit(TextureGroupHandle old)
|
||||
/// <param name="group">The texture group to check for</param>
|
||||
/// <returns>True if there is a dependency, false otherwise</returns>
|
||||
public bool HasDependencyTo(TextureGroup group)
|
||||
{
|
||||
Modified |= old.Modified;
|
||||
|
||||
foreach (TextureDependency dependency in old.Dependencies.ToArray())
|
||||
foreach (TextureDependency dep in Dependencies)
|
||||
{
|
||||
CreateCopyDependency(dependency.Other.Handle);
|
||||
|
||||
if (dependency.Other.Handle.DeferredCopy == old)
|
||||
if (dep.Other.Handle._group == group)
|
||||
{
|
||||
dependency.Other.Handle.DeferredCopy = this;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
DeferredCopy = old.DeferredCopy;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inherit modified flags and dependencies from another texture handle.
|
||||
/// </summary>
|
||||
/// <param name="old">The texture handle to inherit from</param>
|
||||
/// <param name="withCopies">Whether the handle should inherit copy dependencies or not</param>
|
||||
public void Inherit(TextureGroupHandle old, bool withCopies)
|
||||
{
|
||||
Modified |= old.Modified;
|
||||
|
||||
if (withCopies)
|
||||
{
|
||||
foreach (TextureDependency dependency in old.Dependencies.ToArray())
|
||||
{
|
||||
CreateCopyDependency(dependency.Other.Handle);
|
||||
|
||||
if (dependency.Other.Handle.DeferredCopy == old)
|
||||
{
|
||||
dependency.Other.Handle.DeferredCopy = this;
|
||||
}
|
||||
}
|
||||
|
||||
DeferredCopy = old.DeferredCopy;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -327,6 +493,9 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
return Offset < offset + size && offset < Offset + Size;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dispose this texture group handle, removing all its dependencies and disposing its memory tracking handles.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (CpuRegionHandle handle in Handles)
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
enum TextureViewCompatibility
|
||||
{
|
||||
Incompatible = 0,
|
||||
LayoutIncompatible,
|
||||
CopyOnly,
|
||||
Full
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue