diff --git a/Ryujinx.Graphics.GAL/ITexture.cs b/Ryujinx.Graphics.GAL/ITexture.cs index c011b05c..96b59293 100644 --- a/Ryujinx.Graphics.GAL/ITexture.cs +++ b/Ryujinx.Graphics.GAL/ITexture.cs @@ -15,6 +15,7 @@ namespace Ryujinx.Graphics.GAL ITexture CreateView(TextureCreateInfo info, int firstLayer, int firstLevel); ReadOnlySpan GetData(); + ReadOnlySpan GetData(int layer, int level); void SetData(ReadOnlySpan data); void SetData(ReadOnlySpan data, int layer, int level); diff --git a/Ryujinx.Graphics.GAL/Multithreading/CommandHelper.cs b/Ryujinx.Graphics.GAL/Multithreading/CommandHelper.cs index 6111e32c..67e8315b 100644 --- a/Ryujinx.Graphics.GAL/Multithreading/CommandHelper.cs +++ b/Ryujinx.Graphics.GAL/Multithreading/CommandHelper.cs @@ -111,6 +111,8 @@ namespace Ryujinx.Graphics.GAL.Multithreading TextureCreateViewCommand.Run(ref GetCommand(memory), threaded, renderer); _lookup[(int)CommandType.TextureGetData] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => TextureGetDataCommand.Run(ref GetCommand(memory), threaded, renderer); + _lookup[(int)CommandType.TextureGetDataSlice] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => + TextureGetDataSliceCommand.Run(ref GetCommand(memory), threaded, renderer); _lookup[(int)CommandType.TextureRelease] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => TextureReleaseCommand.Run(ref GetCommand(memory), threaded, renderer); _lookup[(int)CommandType.TextureSetData] = (Span memory, ThreadedRenderer threaded, IRenderer renderer) => diff --git a/Ryujinx.Graphics.GAL/Multithreading/CommandType.cs b/Ryujinx.Graphics.GAL/Multithreading/CommandType.cs index 4bceaa1e..e0a03ce7 100644 --- a/Ryujinx.Graphics.GAL/Multithreading/CommandType.cs +++ b/Ryujinx.Graphics.GAL/Multithreading/CommandType.cs @@ -36,6 +36,7 @@ TextureCopyToSlice, TextureCreateView, TextureGetData, + TextureGetDataSlice, TextureRelease, TextureSetData, TextureSetDataSlice, diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureGetDataSliceCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureGetDataSliceCommand.cs new file mode 100644 index 00000000..207e5784 --- /dev/null +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureGetDataSliceCommand.cs @@ -0,0 +1,30 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; +using System; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture +{ + struct TextureGetDataSliceCommand : IGALCommand + { + public CommandType CommandType => CommandType.TextureGetDataSlice; + private TableRef _texture; + private TableRef>> _result; + private int _layer; + private int _level; + + public void Set(TableRef texture, TableRef>> result, int layer, int level) + { + _texture = texture; + _result = result; + _layer = layer; + _level = level; + } + + public static void Run(ref TextureGetDataSliceCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + ReadOnlySpan result = command._texture.Get(threaded).Base.GetData(command._layer, command._level); + + command._result.Get(threaded).Result = new PinnedSpan(result); + } + } +} diff --git a/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedTexture.cs b/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedTexture.cs index 634d32fa..64d8aa3b 100644 --- a/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedTexture.cs +++ b/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedTexture.cs @@ -89,6 +89,24 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Resources } } + public ReadOnlySpan GetData(int layer, int level) + { + if (_renderer.IsGpuThread()) + { + ResultBox> box = new ResultBox>(); + _renderer.New().Set(Ref(this), Ref(box), layer, level); + _renderer.InvokeCommand(); + + return box.Result.Get(); + } + else + { + ThreadedHelpers.SpinUntilNonNull(ref Base); + + return Base.GetData(layer, level); + } + } + public void SetData(ReadOnlySpan data) { _renderer.New().Set(Ref(this), Ref(data.ToArray())); diff --git a/Ryujinx.Graphics.Gpu/Engine/Dma/DmaClass.cs b/Ryujinx.Graphics.Gpu/Engine/Dma/DmaClass.cs index c00e2bef..44964b8f 100644 --- a/Ryujinx.Graphics.Gpu/Engine/Dma/DmaClass.cs +++ b/Ryujinx.Graphics.Gpu/Engine/Dma/DmaClass.cs @@ -232,8 +232,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma data = LayoutConverter.ConvertBlockLinearToLinear( src.Width, src.Height, + src.Depth, + 1, 1, - target.Info.Levels, 1, 1, 1, @@ -245,6 +246,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma srcSpan); } + target.SynchronizeMemory(); target.SetData(data); target.SignalModified(); diff --git a/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoClass.cs b/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoClass.cs index fe49b0f2..dab4e9db 100644 --- a/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoClass.cs +++ b/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoClass.cs @@ -136,7 +136,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.GPFifo } else if (operation == SyncpointbOperation.Incr) { - _context.CreateHostSyncIfNeeded(); + _context.CreateHostSyncIfNeeded(true); _context.Synchronization.IncrementSyncpoint(syncpointId); } @@ -152,7 +152,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.GPFifo _parent.PerformDeferredDraws(); _context.Renderer.Pipeline.Barrier(); - _context.CreateHostSyncIfNeeded(); + _context.CreateHostSyncIfNeeded(false); } /// @@ -163,7 +163,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.GPFifo { _context.Renderer.Pipeline.CommandBufferBarrier(); - _context.CreateHostSyncIfNeeded(); + _context.CreateHostSyncIfNeeded(false); } /// diff --git a/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClass.cs b/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClass.cs index dc458026..f6de2730 100644 --- a/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClass.cs +++ b/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClass.cs @@ -224,7 +224,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed uint syncpointId = (uint)argument & 0xFFFF; _context.AdvanceSequence(); - _context.CreateHostSyncIfNeeded(); + _context.CreateHostSyncIfNeeded(true); _context.Renderer.UpdateCounters(); // Poll the query counters, the game may want an updated result. _context.Synchronization.IncrementSyncpoint(syncpointId); } diff --git a/Ryujinx.Graphics.Gpu/GpuContext.cs b/Ryujinx.Graphics.Gpu/GpuContext.cs index 034c8fcb..5c9af383 100644 --- a/Ryujinx.Graphics.Gpu/GpuContext.cs +++ b/Ryujinx.Graphics.Gpu/GpuContext.cs @@ -52,12 +52,19 @@ namespace Ryujinx.Graphics.Gpu internal ulong SyncNumber { get; private set; } /// - /// Actions to be performed when a CPU waiting sync point is triggered. + /// Actions to be performed when a CPU waiting syncpoint or barrier is triggered. /// If there are more than 0 items when this happens, a host sync object will be generated for the given , /// and the SyncNumber will be incremented. /// internal List SyncActions { get; } + /// + /// Actions to be performed when a CPU waiting syncpoint is triggered. + /// If there are more than 0 items when this happens, a host sync object will be generated for the given , + /// and the SyncNumber will be incremented. + /// + internal List SyncpointActions { get; } + /// /// Queue with deferred actions that must run on the render thread. /// @@ -79,6 +86,7 @@ namespace Ryujinx.Graphics.Gpu public event Action ShaderCacheStateChanged; private readonly Lazy _caps; + private Thread _gpuThread; /// /// Creates a new instance of the GPU emulation context. @@ -97,6 +105,7 @@ namespace Ryujinx.Graphics.Gpu HostInitalized = new ManualResetEvent(false); SyncActions = new List(); + SyncpointActions = new List(); DeferredActions = new Queue(); @@ -184,6 +193,23 @@ namespace Ryujinx.Graphics.Gpu } } + /// + /// Sets the current thread as the main GPU thread. + /// + public void SetGpuThread() + { + _gpuThread = Thread.CurrentThread; + } + + /// + /// Checks if the current thread is the GPU thread. + /// + /// True if the thread is the GPU thread, false otherwise + public bool IsGpuThread() + { + return _gpuThread == Thread.CurrentThread; + } + /// /// Processes the queue of shaders that must save their binaries to the disk cache. /// @@ -209,18 +235,27 @@ namespace Ryujinx.Graphics.Gpu /// This will also ensure a host sync object is created, and is incremented. /// /// The action to be performed on sync object creation - public void RegisterSyncAction(Action action) + /// True if the sync action should only run when syncpoints are incremented + public void RegisterSyncAction(Action action, bool syncpointOnly = false) { - SyncActions.Add(action); + if (syncpointOnly) + { + SyncpointActions.Add(action); + } + else + { + SyncActions.Add(action); + } } /// /// Creates a host sync object if there are any pending sync actions. The actions will then be called. /// If no actions are present, a host sync object is not created. /// - public void CreateHostSyncIfNeeded() + /// True if host sync is being created by a syncpoint + public void CreateHostSyncIfNeeded(bool syncpoint) { - if (SyncActions.Count > 0) + if (SyncActions.Count > 0 || (syncpoint && SyncpointActions.Count > 0)) { Renderer.CreateSync(SyncNumber); @@ -231,7 +266,13 @@ namespace Ryujinx.Graphics.Gpu action(); } + foreach (Action action in SyncpointActions) + { + action(); + } + SyncActions.Clear(); + SyncpointActions.Clear(); } } diff --git a/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs b/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs index 8bc6d544..7bee844e 100644 --- a/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs +++ b/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs @@ -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); 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 @@ -72,11 +72,6 @@ namespace Ryujinx.Graphics.Gpu.Image /// public TextureGroup Group { get; private set; } - /// - /// Set when a texture has been modified by the Host GPU since it was last flushed. - /// - public bool IsModified { get; internal set; } - /// /// 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 /// public bool ChangedMapping { get; private set; } + /// + /// 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. + /// + 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 /// True if the texture is to be initialized with data 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 /// /// True if the texture will have layer views /// True if the texture will have mip views - public void InitializeGroup(bool hasLayerViews, bool hasMipViews) + /// Groups that overlap with this one but are incompatible + public void InitializeGroup(bool hasLayerViews, bool hasMipViews, List 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; } + /// + /// Signal that the modified state is dirty, indicating that the texture group should be notified when it changes. + /// + public void SignalModifiedDirty() + { + _modifiedStale = true; + } + /// /// 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 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. /// /// Data to be converted + /// Mip level to convert + /// True to convert a single slice /// Converted data public ReadOnlySpan ConvertToHostCompatibleFormat(ReadOnlySpan 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; } + /// + /// Converts texture data from a format and layout that is supported by the host GPU, back into the intended format on the guest GPU. + /// + /// Optional output span to convert into + /// Data to be converted + /// Mip level to convert + /// True to convert a single slice + /// Converted data + public ReadOnlySpan ConvertFromHostCompatibleFormat(Span output, ReadOnlySpan 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; + } + /// /// 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. /// /// Whether or not the flush triggers write tracking. If it doesn't, the texture will not be blacklisted for scaling either. - public void Flush(bool tracked = true) + /// True if data was flushed, false otherwise + 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); } /// - /// 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. /// - public void ExternalFlush(ulong address, ulong size) + /// Whether or not the flush triggers write tracking. If it doesn't, the texture will not be blacklisted for scaling either. + public void Flush(bool tracked) { - if (!IsModified) + if (TextureCompatibility.CanTextureFlush(Info, _context.Capabilities)) { - return; + FlushTextureDataToGuest(tracked); + } + } + + /// + /// 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. + /// + /// The host texture to flush + 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; } /// - /// 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. /// /// /// This method should be used to retrieve data that was modified by the host GPU. @@ -888,28 +954,11 @@ namespace Ryujinx.Graphics.Gpu.Image /// /// True if writing the texture data is tracked, false otherwise /// The specific host texture to flush. Defaults to this texture - 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.Empty, true, texture)); - } - else - { - _physicalMemory.WriteUntracked(Range, GetTextureDataFromGpu(Span.Empty, false, texture)); - } - } + GetTextureDataFromGpu(region.Memory.Span, tracked, texture); } /// @@ -951,40 +1000,54 @@ namespace Ryujinx.Graphics.Gpu.Image } } - if (Target != Target.TextureBuffer) + data = ConvertFromHostCompatibleFormat(output, data); + + return data; + } + + /// + /// Gets data from the host GPU for a single slice. + /// + /// + /// 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. + /// + /// An output span to place the texture data into. If empty, one is generated + /// The layer of the texture to flush + /// The level of the texture to flush + /// True if the texture should be blacklisted, false otherwise + /// The specific host texture to flush. Defaults to this texture + /// The span containing the texture data + public ReadOnlySpan GetTextureDataSliceFromGpu(Span output, int layer, int level, bool blacklist, ITexture texture = null) + { + ReadOnlySpan 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 /// /// Texture view information /// Texture view physical memory ranges + /// Layer size on the given texture + /// Host GPU capabilities /// Texture view initial layer on this texture /// Texture view first mipmap level on this texture /// The level of compatiblilty a view with the given parameters created from this texture has - 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; } /// @@ -1261,11 +1333,10 @@ namespace Ryujinx.Graphics.Gpu.Image /// 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 /// True if the texture has been bound, false if it has been unbound 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; } - /// - /// Determine if any of our child textures are compaible as views of the given texture. - /// - /// The texture to check against - /// True if any child is view compatible, false otherwise - 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; - } - /// /// Determine if any of this texture's data overlaps with another. /// @@ -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. /// - public void Unmapped() + /// The range of memory being unmapped + 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); } diff --git a/Ryujinx.Graphics.Gpu/Image/TextureCache.cs b/Ryujinx.Graphics.Gpu/Image/TextureCache.cs index 1aa09b90..fab38e14 100644 --- a/Ryujinx.Graphics.Gpu/Image/TextureCache.cs +++ b/Ryujinx.Graphics.Gpu/Image/TextureCache.cs @@ -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()); 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(); + 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; } /// diff --git a/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs b/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs index ce9fd75c..0461a81f 100644 --- a/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs +++ b/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs @@ -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 } /// @@ -92,34 +109,60 @@ namespace Ryujinx.Graphics.Gpu.Image return info.FormatInfo; } + /// + /// Determines whether a texture can flush its data back to guest memory. + /// + /// Texture information + /// Host GPU Capabilities + /// True if the texture can flush, false otherwise + 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; + } + /// /// Checks if two formats are compatible, according to the host API copy format compatibility rules. /// - /// First comparand - /// Second comparand + /// First comparand + /// Second comparand + /// Host GPU capabilities /// True if the formats are compatible, false otherwise - 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 } } + /// + /// Checks if the sizes of two texture levels are copy compatible. + /// + /// Texture information of the texture view + /// Texture information of the texture view to match against + /// Mipmap level of the texture view in relation to this texture + /// Mipmap level of the texture view in relation to the second texture + /// True if both levels are view compatible + 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; + } + } + /// /// Checks if the sizes of two given textures are view compatible. /// @@ -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 } } + /// + /// 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. + /// + /// Texture information of the texture view + /// Texture information of the texture view to compare against + /// Start level of the texture view, in relation with the first texture + /// Start level of the texture view, in relation with the second texture + /// True if the layout is compatible, false otherwise + 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; + } + } + /// /// 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 /// /// Texture information of the texture view /// Texture information of the texture view + /// Host GPU capabilities /// The view compatibility level of the texture formats - 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; diff --git a/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs b/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs index 2bd97432..bfc1105c 100644 --- a/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs +++ b/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs @@ -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 { + /// + /// An overlapping texture group with a given view compatibility. + /// + struct TextureIncompatibleOverlap + { + public readonly TextureGroup Group; + public readonly TextureViewCompatibility Compatibility; + + /// + /// Create a new texture incompatible overlap. + /// + /// The group that is incompatible + /// The view compatibility for the group + public TextureIncompatibleOverlap(TextureGroup group, TextureViewCompatibility compatibility) + { + Group = group; + Compatibility = compatibility; + } + } + /// /// 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 /// public bool HasCopyDependencies { get; set; } + /// + /// Indicates if this texture has any incompatible overlaps alive. + /// + 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; + /// + /// Other texture groups that have incompatible overlaps with this one. + /// + private List _incompatibleOverlaps; + private bool _incompatibleOverlapsDirty = true; + private bool _flushIncompatibleOverlaps; + /// /// Create a new texture group. /// /// GPU context that the texture group belongs to /// Physical memory where the texture is mapped /// The storage texture for this group - public TextureGroup(GpuContext context, PhysicalMemory physicalMemory, Texture storage) + /// Groups that overlap with this one but are incompatible + public TextureGroup(GpuContext context, PhysicalMemory physicalMemory, Texture storage, List 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); } /// @@ -82,6 +120,86 @@ namespace Ryujinx.Graphics.Gpu.Image RecalculateHandleRegions(); } + /// + /// Initialize all incompatible overlaps in the list, registering them with the other texture groups + /// and creating copy dependencies when partially compatible. + /// + 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(); + } + } + + /// + /// Signal that the group is dirty to all views and the storage. + /// + private void SignalAllDirty() + { + Storage.SignalGroupDirty(); + if (_views != null) + { + foreach (Texture texture in _views) + { + texture.SignalGroupDirty(); + } + } + } + + /// + /// Signal that an incompatible overlap has been modified. + /// If this group must flush incompatible overlaps, the group is signalled as dirty too. + /// + private void SignalIncompatibleOverlapModified() + { + _incompatibleOverlapsDirty = true; + + if (_flushIncompatibleOverlaps) + { + SignalAllDirty(); + } + } + + + /// + /// 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. + /// + /// True if data was flushed, false otherwise + [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; + } + } + /// /// 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 /// The texture being used 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 data = _physicalMemory.GetSpan(Storage.Range.GetSlice((ulong)offset, (ulong)size)); @@ -251,25 +376,170 @@ namespace Ryujinx.Graphics.Gpu.Image }); } + /// + /// 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. + /// + /// True if flushes should be tracked, false otherwise + private bool ShouldFlushTriggerTracking() + { + foreach (var overlap in _incompatibleOverlaps) + { + if (overlap.Group._flushIncompatibleOverlaps) + { + return true; + } + } + + return false; + } + + /// + /// Gets data from the host GPU, and flushes a slice to guest memory. + /// + /// + /// 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. + /// + /// True if writing the texture data is tracked, false otherwise + /// The index of the slice to flush + /// The specific host texture to flush. Defaults to the storage texture + 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); + } + + /// + /// Gets and flushes a number of slices of the storage texture to guest memory. + /// + /// True if writing the texture data is tracked, false otherwise + /// The first slice to flush + /// The slice to finish flushing on (exclusive) + /// The specific host texture to flush. Defaults to the storage texture + private void FlushSliceRange(bool tracked, int sliceStart, int sliceEnd, ITexture texture = null) + { + for (int i = sliceStart; i < sliceEnd; i++) + { + FlushTextureDataSliceToGuest(tracked, i, texture); + } + } + + /// + /// Flush modified ranges for a given texture. + /// + /// The texture being used + /// True if the flush writes should be tracked, false otherwise + /// True if data was flushed, false otherwise + 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; + } + + /// + /// Clears competing modified flags for all incompatible ranges, if they have possibly been modified. + /// + /// The texture that has been modified + [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; + } + } + /// /// Signal that a texture in the group has been modified by the GPU. /// /// The texture that has been modified - /// True if the flushing read action should be registered, false otherwise - 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 /// /// The texture that has been modified /// True if this texture is being bound, false if unbound - /// True if the flushing read action should be registered, false otherwise - 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(); } /// @@ -706,7 +980,8 @@ namespace Ryujinx.Graphics.Gpu.Image /// /// The set of handles to inherit state from /// The set of handles inheriting the state - private void InheritHandles(TextureGroupHandle[] oldHandles, TextureGroupHandle[] handles) + /// The offset of the old handles in relation to the new ones + 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; + } } /// @@ -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); } /// @@ -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); } } } /// - /// 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. + /// + /// The texture group that overlaps this one + /// True if this texture is first copied to the given one, false for the opposite direction + public void CreateCopyDependency(TextureGroup other, bool copyTo) + { + 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); + } + } + } + } + } + } + + /// + /// Registers another texture group as an incompatible overlap, if not already registered. + /// + /// The texture group to add to the incompatible overlaps list + /// True if the overlap should register copy dependencies + 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(); + } + + /// + /// Clear modified flags in the given range. + /// This will stop any GPU written data from flushing or copying to dependent textures. + /// + /// The range to clear modified flags in + /// Ignore handles that have a copy dependency to the specified group + 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(); + } + } + } + + /// + /// A flush has been requested on a tracked region. Flush texture data for the given handle. /// /// The handle this flush action is for /// The address of the flushing memory access /// The size of the flushing memory access 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()); + } + }); } /// @@ -992,6 +1419,11 @@ namespace Ryujinx.Graphics.Gpu.Image { group.Dispose(); } + + foreach (TextureIncompatibleOverlap incompatible in _incompatibleOverlaps) + { + incompatible.Group._incompatibleOverlaps.RemoveAll(overlap => overlap.Group == this); + } } } } diff --git a/Ryujinx.Graphics.Gpu/Image/TextureGroupHandle.cs b/Ryujinx.Graphics.Gpu/Image/TextureGroupHandle.cs index 0b69b6c7..7d4aec41 100644 --- a/Ryujinx.Graphics.Gpu/Image/TextureGroupHandle.cs +++ b/Ryujinx.Graphics.Gpu/Image/TextureGroupHandle.cs @@ -19,6 +19,28 @@ namespace Ryujinx.Graphics.Gpu.Image private int _firstLevel; private int _firstLayer; + // Sync state for texture flush. + + /// + /// The sync number last registered. + /// + private ulong _registeredSync; + + /// + /// The sync number when the texture was last modified by GPU. + /// + private ulong _modifiedSync; + + /// + /// Whether a tracking action is currently registered or not. + /// + private bool _actionRegistered; + + /// + /// Whether a sync action is currently registered or not. + /// + private bool _syncActionRegistered; + /// /// The byte offset from the start of the storage of this handle. /// @@ -29,6 +51,16 @@ namespace Ryujinx.Graphics.Gpu.Image /// public int Size { get; } + /// + /// The base slice index for this handle. + /// + public int BaseSlice { get; } + + /// + /// The number of slices covered by this handle. + /// + public int SliceCount { get; } + /// /// The textures which this handle overlaps with. /// @@ -68,8 +100,18 @@ namespace Ryujinx.Graphics.Gpu.Image /// All views of the storage texture, used to calculate overlaps /// The first layer of this handle in the storage texture /// The first level of this handle in the storage texture + /// The base slice index of this handle + /// The number of slices this handle covers /// The memory tracking handles that cover this handle - public TextureGroupHandle(TextureGroup group, int offset, ulong size, List views, int firstLayer, int firstLevel, CpuRegionHandle[] handles) + public TextureGroupHandle(TextureGroup group, + int offset, + ulong size, + List 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(); Dependencies = new List(); + BaseSlice = baseSlice; + SliceCount = sliceCount; + if (views != null) { RecalculateOverlaps(group, views); @@ -113,10 +158,32 @@ namespace Ryujinx.Graphics.Gpu.Image } } + /// + /// Registers a sync action to happen for this handle, and an interim flush action on the tracking handle. + /// + /// The GPU context to register a sync action on + private void RegisterSync(GpuContext context) + { + if (!_syncActionRegistered) + { + _modifiedSync = context.SyncNumber; + context.RegisterSyncAction(SyncAction, true); + _syncActionRegistered = true; + } + + if (!_actionRegistered) + { + _group.RegisterAction(this); + + _actionRegistered = true; + } + } + /// /// Signal that this handle has been modified to any existing dependencies, and set the modified flag. /// - public void SignalModified() + /// The GPU context to register a sync action on + public void SignalModified(GpuContext context) { Modified = true; @@ -126,15 +193,18 @@ namespace Ryujinx.Graphics.Gpu.Image { dependency.SignalModified(); } + + RegisterSync(context); } /// /// Signal that this handle has either started or ended being modified. /// /// True if this handle is being bound, false if unbound - public void SignalModifying(bool bound) + /// The GPU context to register a sync action on + 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 } } + /// + /// 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. + /// + /// The GPU context used to wait for sync + 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; + } + } + + /// + /// 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. + /// + private void SyncAction() + { + // Register region tracking for CPU? (again) + _registeredSync = _modifiedSync; + _syncActionRegistered = false; + + if (!_actionRegistered) + { + _group.RegisterAction(this); + + _actionRegistered = true; + } + } + /// /// Signal that a copy dependent texture has been modified, and must have its data copied to this one. /// /// The texture handle that must defer a copy to this one public void DeferCopy(TextureGroupHandle copyFrom) { + Modified = false; + DeferredCopy = copyFrom; _group.Storage.SignalGroupDirty(); @@ -247,73 +374,112 @@ namespace Ryujinx.Graphics.Gpu.Image /// /// Perform a copy from the provided handle to this one, or perform a deferred copy if none is provided. /// + /// GPU context to register sync for modified handles /// The handle to copy from. If not provided, this method will copy from and clear the deferred copy instead /// True if the copy was performed, false otherwise - 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; } /// - /// Inherit modified flags and dependencies from another texture handle. + /// Check if this handle has a dependency to a given texture group. /// - /// The texture handle to inherit from - public void Inherit(TextureGroupHandle old) + /// The texture group to check for + /// True if there is a dependency, false otherwise + 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; + } + + /// + /// Inherit modified flags and dependencies from another texture handle. + /// + /// The texture handle to inherit from + /// Whether the handle should inherit copy dependencies or not + 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; + } } /// @@ -327,6 +493,9 @@ namespace Ryujinx.Graphics.Gpu.Image return Offset < offset + size && offset < Offset + Size; } + /// + /// Dispose this texture group handle, removing all its dependencies and disposing its memory tracking handles. + /// public void Dispose() { foreach (CpuRegionHandle handle in Handles) diff --git a/Ryujinx.Graphics.Gpu/Image/TextureViewCompatibility.cs b/Ryujinx.Graphics.Gpu/Image/TextureViewCompatibility.cs index 4671af46..b89936eb 100644 --- a/Ryujinx.Graphics.Gpu/Image/TextureViewCompatibility.cs +++ b/Ryujinx.Graphics.Gpu/Image/TextureViewCompatibility.cs @@ -7,6 +7,7 @@ enum TextureViewCompatibility { Incompatible = 0, + LayoutIncompatible, CopyOnly, Full } diff --git a/Ryujinx.Graphics.Gpu/Memory/MultiRangeWritableBlock.cs b/Ryujinx.Graphics.Gpu/Memory/MultiRangeWritableBlock.cs new file mode 100644 index 00000000..155dda7b --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Memory/MultiRangeWritableBlock.cs @@ -0,0 +1,58 @@ +using Ryujinx.Memory; +using Ryujinx.Memory.Range; +using System; + +namespace Ryujinx.Graphics.Gpu.Memory +{ + /// + /// A writable block that targets a given MultiRange within a PhysicalMemory instance. + /// + internal class MultiRangeWritableBlock : IWritableBlock + { + private readonly MultiRange _range; + private readonly PhysicalMemory _physicalMemory; + + /// + /// Creates a new MultiRangeWritableBlock. + /// + /// The MultiRange to write to + /// The PhysicalMemory the given MultiRange addresses + public MultiRangeWritableBlock(MultiRange range, PhysicalMemory physicalMemory) + { + _range = range; + _physicalMemory = physicalMemory; + } + + /// + /// Write data to the MultiRange. + /// + /// Offset address + /// Data to write + /// Throw when a non-zero offset is given + public void Write(ulong va, ReadOnlySpan data) + { + if (va != 0) + { + throw new ArgumentException($"{nameof(va)} cannot be non-zero for {nameof(MultiRangeWritableBlock)}."); + } + + _physicalMemory.Write(_range, data); + } + + /// + /// Write data to the MultiRange, without tracking. + /// + /// Offset address + /// Data to write + /// Throw when a non-zero offset is given + public void WriteUntracked(ulong va, ReadOnlySpan data) + { + if (va != 0) + { + throw new ArgumentException($"{nameof(va)} cannot be non-zero for {nameof(MultiRangeWritableBlock)}."); + } + + _physicalMemory.WriteUntracked(_range, data); + } + } +} diff --git a/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs b/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs index d292fab0..1ff086ee 100644 --- a/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs +++ b/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs @@ -135,6 +135,38 @@ namespace Ryujinx.Graphics.Gpu.Memory return _cpuMemory.GetWritableRegion(address, size, tracked); } + /// + /// Gets a writable region from GPU mapped memory. + /// + /// Range + /// True if write tracking is triggered on the span + /// A writable region with the data at the specified memory location + public WritableRegion GetWritableRegion(MultiRange range, bool tracked = false) + { + if (range.Count == 1) + { + MemoryRange subrange = range.GetSubRange(0); + + return GetWritableRegion(subrange.Address, (int)subrange.Size, tracked); + } + else + { + Memory memory = new byte[range.GetSize()]; + + int offset = 0; + for (int i = 0; i < range.Count; i++) + { + MemoryRange subrange = range.GetSubRange(i); + + GetSpan(subrange.Address, (int)subrange.Size).CopyTo(memory.Span.Slice(offset, (int)subrange.Size)); + + offset += (int)subrange.Size; + } + + return new WritableRegion(new MultiRangeWritableBlock(range, this), 0, memory, tracked); + } + } + /// /// Reads data from the application process. /// diff --git a/Ryujinx.Graphics.OpenGL/Image/TextureBuffer.cs b/Ryujinx.Graphics.OpenGL/Image/TextureBuffer.cs index 899deedf..dc9bf6af 100644 --- a/Ryujinx.Graphics.OpenGL/Image/TextureBuffer.cs +++ b/Ryujinx.Graphics.OpenGL/Image/TextureBuffer.cs @@ -43,6 +43,11 @@ namespace Ryujinx.Graphics.OpenGL.Image return Buffer.GetData(_renderer, _buffer, _bufferOffset, _bufferSize); } + public ReadOnlySpan GetData(int layer, int level) + { + return GetData(); + } + public void SetData(ReadOnlySpan data) { Buffer.SetData(_buffer, _bufferOffset, data.Slice(0, Math.Min(data.Length, _bufferSize))); diff --git a/Ryujinx.Graphics.OpenGL/Image/TextureView.cs b/Ryujinx.Graphics.OpenGL/Image/TextureView.cs index 40ab18ef..f03653c4 100644 --- a/Ryujinx.Graphics.OpenGL/Image/TextureView.cs +++ b/Ryujinx.Graphics.OpenGL/Image/TextureView.cs @@ -154,6 +154,24 @@ namespace Ryujinx.Graphics.OpenGL.Image } } + public unsafe ReadOnlySpan GetData(int layer, int level) + { + int size = Info.GetMipSize(level); + + if (HwCapabilities.UsePersistentBufferForFlush) + { + return _renderer.PersistentBuffers.Default.GetTextureData(this, size, layer, level); + } + else + { + IntPtr target = _renderer.PersistentBuffers.Default.GetHostArray(size); + + int offset = WriteTo2D(target, layer, level); + + return new ReadOnlySpan(target.ToPointer(), size).Slice(offset); + } + } + public void WriteToPbo(int offset, bool forceBgra) { WriteTo(IntPtr.Zero + offset, forceBgra); @@ -182,25 +200,29 @@ namespace Ryujinx.Graphics.OpenGL.Image int mipSize = Info.GetMipSize2D(level); - // The GL function returns all layers. Must return the offset of the layer we're interested in. - int resultOffset = target switch - { - TextureTarget.TextureCubeMapArray => (layer / 6) * mipSize, - TextureTarget.Texture1DArray => layer * mipSize, - TextureTarget.Texture2DArray => layer * mipSize, - _ => 0 - }; - if (format.IsCompressed) { - GL.GetCompressedTexImage(target, level, data); + GL.GetCompressedTextureSubImage(Handle, level, 0, 0, layer, Math.Max(1, Info.Width >> level), Math.Max(1, Info.Height >> level), 1, mipSize, data); + } + else if (format.PixelFormat != PixelFormat.DepthStencil) + { + GL.GetTextureSubImage(Handle, level, 0, 0, layer, Math.Max(1, Info.Width >> level), Math.Max(1, Info.Height >> level), 1, pixelFormat, pixelType, mipSize, data); } else { GL.GetTexImage(target, level, pixelFormat, pixelType, data); + + // The GL function returns all layers. Must return the offset of the layer we're interested in. + return target switch + { + TextureTarget.TextureCubeMapArray => (layer / 6) * mipSize, + TextureTarget.Texture1DArray => layer * mipSize, + TextureTarget.Texture2DArray => layer * mipSize, + _ => 0 + }; } - return resultOffset; + return 0; } private void WriteTo(IntPtr data, bool forceBgra = false) diff --git a/Ryujinx.Graphics.OpenGL/PersistentBuffers.cs b/Ryujinx.Graphics.OpenGL/PersistentBuffers.cs index fe224433..872efcc3 100644 --- a/Ryujinx.Graphics.OpenGL/PersistentBuffers.cs +++ b/Ryujinx.Graphics.OpenGL/PersistentBuffers.cs @@ -94,6 +94,21 @@ namespace Ryujinx.Graphics.OpenGL return new ReadOnlySpan(_bufferMap.ToPointer(), size); } + public unsafe ReadOnlySpan GetTextureData(TextureView view, int size, int layer, int level) + { + EnsureBuffer(size); + + GL.BindBuffer(BufferTarget.PixelPackBuffer, _copyBufferHandle); + + int offset = view.WriteToPbo2D(0, layer, level); + + GL.BindBuffer(BufferTarget.PixelPackBuffer, 0); + + Sync(); + + return new ReadOnlySpan(_bufferMap.ToPointer(), size).Slice(offset); + } + public unsafe ReadOnlySpan GetBufferData(BufferHandle buffer, int offset, int size) { EnsureBuffer(size); diff --git a/Ryujinx.Graphics.Texture/LayoutConverter.cs b/Ryujinx.Graphics.Texture/LayoutConverter.cs index 1039ea26..2b327375 100644 --- a/Ryujinx.Graphics.Texture/LayoutConverter.cs +++ b/Ryujinx.Graphics.Texture/LayoutConverter.cs @@ -97,6 +97,7 @@ namespace Ryujinx.Graphics.Texture int width, int height, int depth, + int sliceDepth, int levels, int layers, int blockWidth, @@ -172,6 +173,8 @@ namespace Ryujinx.Graphics.Texture mipGobBlocksInZ, bytesPerPixel); + int sd = Math.Max(1, sliceDepth >> level); + unsafe bool Convert(Span output, ReadOnlySpan data) where T : unmanaged { fixed (byte* outputPtr = output, dataPtr = data) @@ -181,7 +184,7 @@ namespace Ryujinx.Graphics.Texture { byte* inBaseOffset = dataPtr + (layer * sizeInfo.LayerSize + sizeInfo.GetMipOffset(level)); - for (int z = 0; z < d; z++) + for (int z = 0; z < sd; z++) { layoutConverter.SetZ(z); for (int y = 0; y < h; y++) @@ -364,6 +367,7 @@ namespace Ryujinx.Graphics.Texture int width, int height, int depth, + int sliceDepth, int levels, int layers, int blockWidth, @@ -432,6 +436,8 @@ namespace Ryujinx.Graphics.Texture mipGobBlocksInZ, bytesPerPixel); + int sd = Math.Max(1, sliceDepth >> level); + unsafe bool Convert(Span output, ReadOnlySpan data) where T : unmanaged { fixed (byte* outputPtr = output, dataPtr = data) @@ -441,7 +447,7 @@ namespace Ryujinx.Graphics.Texture { byte* outBaseOffset = outputPtr + (layer * sizeInfo.LayerSize + sizeInfo.GetMipOffset(level)); - for (int z = 0; z < d; z++) + for (int z = 0; z < sd; z++) { layoutConverter.SetZ(z); for (int y = 0; y < h; y++) diff --git a/Ryujinx.Graphics.Texture/SizeCalculator.cs b/Ryujinx.Graphics.Texture/SizeCalculator.cs index 7eee13c0..5568784f 100644 --- a/Ryujinx.Graphics.Texture/SizeCalculator.cs +++ b/Ryujinx.Graphics.Texture/SizeCalculator.cs @@ -43,6 +43,7 @@ namespace Ryujinx.Graphics.Texture int[] allOffsets = new int[is3D ? Calculate3DOffsetCount(levels, depth) : levels * layers * depth]; int[] mipOffsets = new int[levels]; int[] sliceSizes = new int[levels]; + int[] levelSizes = new int[levels]; int mipGobBlocksInY = gobBlocksInY; int mipGobBlocksInZ = gobBlocksInZ; @@ -108,8 +109,9 @@ namespace Ryujinx.Graphics.Texture mipOffsets[level] = layerSize; sliceSizes[level] = totalBlocksOfGobsInY * robSize; + levelSizes[level] = totalBlocksOfGobsInZ * sliceSizes[level]; - layerSize += totalBlocksOfGobsInZ * sliceSizes[level]; + layerSize += levelSizes[level]; depthLevelOffset += d; } @@ -152,7 +154,7 @@ namespace Ryujinx.Graphics.Texture } } - return new SizeInfo(mipOffsets, allOffsets, sliceSizes, depth, levels, layerSize, totalSize, is3D); + return new SizeInfo(mipOffsets, allOffsets, sliceSizes, levelSizes, depth, levels, layerSize, totalSize, is3D); } public static SizeInfo GetLinearTextureSize(int stride, int height, int blockHeight) diff --git a/Ryujinx.Graphics.Texture/SizeInfo.cs b/Ryujinx.Graphics.Texture/SizeInfo.cs index 880d677b..ed5379b3 100644 --- a/Ryujinx.Graphics.Texture/SizeInfo.cs +++ b/Ryujinx.Graphics.Texture/SizeInfo.cs @@ -13,6 +13,7 @@ namespace Ryujinx.Graphics.Texture public readonly int[] AllOffsets; public readonly int[] SliceSizes; + public readonly int[] LevelSizes; public int LayerSize { get; } public int TotalSize { get; } @@ -21,6 +22,7 @@ namespace Ryujinx.Graphics.Texture _mipOffsets = new int[] { 0 }; AllOffsets = new int[] { 0 }; SliceSizes = new int[] { size }; + LevelSizes = new int[] { size }; _depth = 1; _levels = 1; LayerSize = size; @@ -32,6 +34,7 @@ namespace Ryujinx.Graphics.Texture int[] mipOffsets, int[] allOffsets, int[] sliceSizes, + int[] levelSizes, int depth, int levels, int layerSize, @@ -41,6 +44,7 @@ namespace Ryujinx.Graphics.Texture _mipOffsets = mipOffsets; AllOffsets = allOffsets; SliceSizes = sliceSizes; + LevelSizes = levelSizes; _depth = depth; _levels = levels; LayerSize = layerSize; @@ -99,7 +103,8 @@ namespace Ryujinx.Graphics.Texture { for (int i = 0; i < _mipOffsets.Length; i++) { - yield return new Region(_mipOffsets[i], SliceSizes[i]); + int maxSize = TotalSize - _mipOffsets[i]; + yield return new Region(_mipOffsets[i], Math.Min(maxSize, LevelSizes[i])); } } else diff --git a/Ryujinx.Memory/Range/MultiRange.cs b/Ryujinx.Memory/Range/MultiRange.cs index 3d505c7c..21ca5f09 100644 --- a/Ryujinx.Memory/Range/MultiRange.cs +++ b/Ryujinx.Memory/Range/MultiRange.cs @@ -284,6 +284,11 @@ namespace Ryujinx.Memory.Range /// Total size in bytes public ulong GetSize() { + if (HasSingleRange) + { + return _singleRange.Size; + } + ulong sum = 0; foreach (MemoryRange range in _ranges) diff --git a/Ryujinx/Ui/RendererWidgetBase.cs b/Ryujinx/Ui/RendererWidgetBase.cs index 8830a4f5..cdbf5d6c 100644 --- a/Ryujinx/Ui/RendererWidgetBase.cs +++ b/Ryujinx/Ui/RendererWidgetBase.cs @@ -386,6 +386,7 @@ namespace Ryujinx.Ui Device.Gpu.Renderer.RunLoop(() => { + Device.Gpu.SetGpuThread(); Device.Gpu.InitializeShaderCache(); Translator.IsReadyForTranslation.Set();