From da073fce6127243fcd93b736cde951c4e835e508 Mon Sep 17 00:00:00 2001 From: riperiperi Date: Tue, 14 Mar 2023 20:33:44 +0000 Subject: [PATCH] GPU: Fast path for adding one texture view to a group (#4528) * GPU: Fast path for adding one texture view to a group Texture group handles must store a list of their overlapping views, so they can be properly notified when a write is detected, and a few other things relating to texture readback. This is generally created when the group is established, with each handle looping over all views to find its overlaps. This whole process was also done when only a single view was added (and no handles were changed), however... Sonic Frontiers had a huge cubemap array with 7350 faces (175 cubemaps * 6 faces * 7 levels), so iterating over both handles and existing views added up very fast. Since we are only adding a single view, we only need to _add_ that view to the existing overlaps, rather than recalculate them all. This greatly improves performance during loading screens and a few seconds into gameplay on the "open zone" sections of Sonic Frontiers. May improve loading times or stutters on some other games. Note that the current texture cache rules will cause these views to fall out of the cache, as there are more than the hard cap, so the cost will be repaid when reloading the open zone. I also added some code to properly remove overlaps when texture views are removed, since it seems that was missing. This can be improved further by only iterating handles that overlap the view (filter by range), but so can a few places in TextureGroup, so better to do all at once. The full generation of overlaps could probably be improved in a similar way. I recommend testing a few games to make sure nothing breaks. * Address feedback --- Ryujinx.Graphics.Gpu/Image/Texture.cs | 4 +- Ryujinx.Graphics.Gpu/Image/TextureGroup.cs | 38 ++++++++++++++++--- .../Image/TextureGroupHandle.cs | 36 ++++++++++++++++++ 3 files changed, 72 insertions(+), 6 deletions(-) diff --git a/Ryujinx.Graphics.Gpu/Image/Texture.cs b/Ryujinx.Graphics.Gpu/Image/Texture.cs index 363f0f73a7..b784a5455f 100644 --- a/Ryujinx.Graphics.Gpu/Image/Texture.cs +++ b/Ryujinx.Graphics.Gpu/Image/Texture.cs @@ -360,7 +360,7 @@ namespace Ryujinx.Graphics.Gpu.Image texture._viewStorage = this; - Group.UpdateViews(_views); + Group.UpdateViews(_views, texture); if (texture.Group != null && texture.Group != Group) { @@ -384,6 +384,8 @@ namespace Ryujinx.Graphics.Gpu.Image { _views.Remove(texture); + Group.RemoveView(texture); + texture._viewStorage = texture; DecrementReferenceCount(); diff --git a/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs b/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs index d9b620aae5..b59a9d0860 100644 --- a/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs +++ b/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs @@ -989,7 +989,8 @@ namespace Ryujinx.Graphics.Gpu.Image /// Update the views in this texture group, rebuilding the memory tracking if required. /// /// The views list of the storage texture - public void UpdateViews(List views) + /// The texture that has been added, if that is the only change, otherwise null + public void UpdateViews(List views, Texture texture) { // This is saved to calculate overlapping views for each handle. _views = views; @@ -1027,17 +1028,44 @@ namespace Ryujinx.Graphics.Gpu.Image if (!regionsRebuilt) { - // Must update the overlapping views on all handles, but only if they were not just recreated. - - foreach (TextureGroupHandle handle in _handles) + if (texture != null) { - handle.RecalculateOverlaps(this, views); + int offset = FindOffset(texture); + + foreach (TextureGroupHandle handle in _handles) + { + handle.AddOverlap(offset, texture); + } + } + else + { + // Must update the overlapping views on all handles, but only if they were not just recreated. + + foreach (TextureGroupHandle handle in _handles) + { + handle.RecalculateOverlaps(this, views); + } } } SignalAllDirty(); } + + /// + /// Removes a view from the group, removing it from all overlap lists. + /// + /// View to remove from the group + public void RemoveView(Texture view) + { + int offset = FindOffset(view); + + foreach (TextureGroupHandle handle in _handles) + { + handle.RemoveOverlap(offset, view); + } + } + /// /// Inherit handle state from an old set of handles, such as modified and dirty flags. /// diff --git a/Ryujinx.Graphics.Gpu/Image/TextureGroupHandle.cs b/Ryujinx.Graphics.Gpu/Image/TextureGroupHandle.cs index 1b83cb5589..ebb4e9aebe 100644 --- a/Ryujinx.Graphics.Gpu/Image/TextureGroupHandle.cs +++ b/Ryujinx.Graphics.Gpu/Image/TextureGroupHandle.cs @@ -159,6 +159,42 @@ namespace Ryujinx.Graphics.Gpu.Image } } + /// + /// Adds a single texture view as an overlap if its range overlaps. + /// + /// The offset of the view in the group + /// The texture to add as an overlap + public void AddOverlap(int offset, Texture view) + { + // Overlaps can be accessed from the memory tracking signal handler, so access must be atomic. + + if (OverlapsWith(offset, (int)view.Size)) + { + lock (Overlaps) + { + Overlaps.Add(view); + } + } + } + + /// + /// Removes a single texture view as an overlap if its range overlaps. + /// + /// The offset of the view in the group + /// The texture to add as an overlap + public void RemoveOverlap(int offset, Texture view) + { + // Overlaps can be accessed from the memory tracking signal handler, so access must be atomic. + + if (OverlapsWith(offset, (int)view.Size)) + { + lock (Overlaps) + { + Overlaps.Remove(view); + } + } + } + /// /// Registers a sync action to happen for this handle, and an interim flush action on the tracking handle. ///