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
This commit is contained in:
riperiperi 2023-03-14 20:33:44 +00:00 committed by GitHub
parent 1fc90e57d2
commit da073fce61
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 72 additions and 6 deletions

View File

@ -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();

View File

@ -989,7 +989,8 @@ namespace Ryujinx.Graphics.Gpu.Image
/// Update the views in this texture group, rebuilding the memory tracking if required.
/// </summary>
/// <param name="views">The views list of the storage texture</param>
public void UpdateViews(List<Texture> views)
/// <param name="texture">The texture that has been added, if that is the only change, otherwise null</param>
public void UpdateViews(List<Texture> views, Texture texture)
{
// This is saved to calculate overlapping views for each handle.
_views = views;
@ -1026,6 +1027,17 @@ namespace Ryujinx.Graphics.Gpu.Image
}
if (!regionsRebuilt)
{
if (texture != null)
{
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.
@ -1034,10 +1046,26 @@ namespace Ryujinx.Graphics.Gpu.Image
handle.RecalculateOverlaps(this, views);
}
}
}
SignalAllDirty();
}
/// <summary>
/// Removes a view from the group, removing it from all overlap lists.
/// </summary>
/// <param name="view">View to remove from the group</param>
public void RemoveView(Texture view)
{
int offset = FindOffset(view);
foreach (TextureGroupHandle handle in _handles)
{
handle.RemoveOverlap(offset, view);
}
}
/// <summary>
/// Inherit handle state from an old set of handles, such as modified and dirty flags.
/// </summary>

View File

@ -159,6 +159,42 @@ namespace Ryujinx.Graphics.Gpu.Image
}
}
/// <summary>
/// Adds a single texture view as an overlap if its range overlaps.
/// </summary>
/// <param name="offset">The offset of the view in the group</param>
/// <param name="view">The texture to add as an overlap</param>
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);
}
}
}
/// <summary>
/// Removes a single texture view as an overlap if its range overlaps.
/// </summary>
/// <param name="offset">The offset of the view in the group</param>
/// <param name="view">The texture to add as an overlap</param>
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);
}
}
}
/// <summary>
/// Registers a sync action to happen for this handle, and an interim flush action on the tracking handle.
/// </summary>