diff options
| author | riperiperi <rhy3756547@hotmail.com> | 2022-01-09 16:28:48 +0000 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2022-01-09 13:28:48 -0300 |
| commit | cda659955ced1b16839cdd1e7fea1ef6f8d99041 (patch) | |
| tree | 41d0d7a705f9feb7b43c9e5bac14dafee7878220 /Ryujinx.Graphics.Gpu/Image/Texture.cs | |
| parent | 4864648e727c6f526e3b65478f222c15468f6074 (diff) | |
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
Diffstat (limited to 'Ryujinx.Graphics.Gpu/Image/Texture.cs')
| -rw-r--r-- | Ryujinx.Graphics.Gpu/Image/Texture.cs | 358 |
1 files changed, 204 insertions, 154 deletions
diff --git a/Ryujinx.Graphics.Gpu/Image/Texture.cs b/Ryujinx.Graphics.Gpu/Image/Texture.cs index 6d981479..ca43c430 100644 --- a/Ryujinx.Graphics.Gpu/Image/Texture.cs +++ b/Ryujinx.Graphics.Gpu/Image/Texture.cs @@ -73,11 +73,6 @@ namespace Ryujinx.Graphics.Gpu.Image 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. /// </summary> @@ -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); } @@ -658,6 +664,14 @@ namespace Ryujinx.Graphics.Gpu.Image } /// <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. /// </summary> @@ -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, @@ -822,6 +837,65 @@ namespace Ryujinx.Graphics.Gpu.Image } /// <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. /// If the texture was written by the GPU, this includes all modification made by the GPU @@ -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); } - - _context.Renderer.BackgroundContextAction(() => + } + + /// <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) { - 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); - } + // 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)); - return TextureViewCompatibility.Incompatible; + 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; + } } - if (!_sizeInfo.FindView(offset, out firstLayer, out firstLevel)) + firstLayer = 0; + firstLevel = 0; + + if (result == TextureViewCompatibility.Incompatible) { return TextureViewCompatibility.Incompatible; } + int offset = Range.FindOffset(range); + + if (offset < 0 || !_sizeInfo.FindView(offset, out firstLayer, out firstLevel)) + { + 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); @@ -1310,29 +1379,6 @@ namespace Ryujinx.Graphics.Gpu.Image } /// <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> /// <param name="texture">The texture to check against</param> @@ -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); } |
