Inherit buffer tracking handles rather than recreating on resize (#2330)

This greatly speeds up games that constantly resize buffers, and removes stuttering on games that resize large buffers occasionally:

- Large improvement on Super Mario 3D All-Stars (#1663 needed for best performance)
- Improvement to Hyrule Warriors: AoC, and UE4 games. These games can still stutter due to texture creation/loading.
- Small improvement to other games, potential 1-frame stutters avoided.

`ForceSynchronizeMemory`, which was added with POWER, is no longer needed. Some tests have been added for the MultiRegionHandle.
This commit is contained in:
riperiperi 2021-06-24 00:31:26 +01:00 committed by GitHub
parent e053663f27
commit 12a7a2ead8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 255 additions and 58 deletions

View file

@ -35,7 +35,7 @@ namespace Ryujinx.Memory.Tests
{
return smart ?
_tracking.BeginSmartGranularTracking(address, size, granularity) :
(IMultiRegionHandle)_tracking.BeginGranularTracking(address, size, granularity);
(IMultiRegionHandle)_tracking.BeginGranularTracking(address, size, null, granularity);
}
private void RandomOrder(Random random, List<int> indices, Action<int> action)
@ -279,5 +279,125 @@ namespace Ryujinx.Memory.Tests
Assert.AreEqual(0, _tracking.GetRegionCount());
}
[Test]
public void InheritHandles()
{
// Test merging the following into a granular region handle:
// - 3x gap (creates new granular handles)
// - 3x from multiregion: not dirty, dirty and with action
// - 2x gap
// - 3x single page: not dirty, dirty and with action
// - 3x two page: not dirty, dirty and with action (handle is not reused, but its state is copied to the granular handles)
// - 1x gap
// For a total of 18 pages.
bool[] actionsTriggered = new bool[3];
MultiRegionHandle granular = _tracking.BeginGranularTracking(PageSize * 3, PageSize * 3, null, PageSize);
PreparePages(granular, 3, PageSize * 3);
// Write to the second handle in the multiregion.
_tracking.VirtualMemoryEvent(PageSize * 4, PageSize, true);
// Add an action to the third handle in the multiregion.
granular.RegisterAction(PageSize * 5, PageSize, (_, _) => { actionsTriggered[0] = true; });
RegionHandle[] singlePages = new RegionHandle[3];
for (int i = 0; i < 3; i++)
{
singlePages[i] = _tracking.BeginTracking(PageSize * (8 + (ulong)i), PageSize);
singlePages[i].Reprotect();
}
// Write to the second handle.
_tracking.VirtualMemoryEvent(PageSize * 9, PageSize, true);
// Add an action to the third handle.
singlePages[2].RegisterAction((_, _) => { actionsTriggered[1] = true; });
RegionHandle[] doublePages = new RegionHandle[3];
for (int i = 0; i < 3; i++)
{
doublePages[i] = _tracking.BeginTracking(PageSize * (11 + (ulong)i * 2), PageSize * 2);
doublePages[i].Reprotect();
}
// Write to the second handle.
_tracking.VirtualMemoryEvent(PageSize * 13, PageSize * 2, true);
// Add an action to the third handle.
doublePages[2].RegisterAction((_, _) => { actionsTriggered[2] = true; });
// Finally, create a granular handle that inherits all these handles.
IEnumerable<IRegionHandle>[] handleGroups = new IEnumerable<IRegionHandle>[]
{
granular.GetHandles(),
singlePages,
doublePages
};
MultiRegionHandle combined = _tracking.BeginGranularTracking(0, PageSize * 18, handleGroups.SelectMany((handles) => handles), PageSize);
bool[] expectedDirty = new bool[]
{
true, true, true, // Gap.
false, true, false, // Multi-region.
true, true, // Gap.
false, true, false, // Individual handles.
false, false, true, true, false, false, // Double size handles.
true // Gap.
};
for (int i = 0; i < 18; i++)
{
bool modified = false;
combined.QueryModified(PageSize * (ulong)i, PageSize, (_, _) => { modified = true; });
Assert.AreEqual(expectedDirty[i], modified);
}
Assert.AreEqual(new bool[3], actionsTriggered);
_tracking.VirtualMemoryEvent(PageSize * 5, PageSize, false);
Assert.IsTrue(actionsTriggered[0]);
_tracking.VirtualMemoryEvent(PageSize * 10, PageSize, false);
Assert.IsTrue(actionsTriggered[1]);
_tracking.VirtualMemoryEvent(PageSize * 15, PageSize, false);
Assert.IsTrue(actionsTriggered[2]);
// The double page handles should be disposed, as they were split into granular handles.
foreach (RegionHandle doublePage in doublePages)
{
// These should have been disposed.
bool throws = false;
try
{
doublePage.Dispose();
}
catch (ObjectDisposedException)
{
throws = true;
}
Assert.IsTrue(throws);
}
IEnumerable<IRegionHandle> combinedHandles = combined.GetHandles();
Assert.AreEqual(handleGroups[0].ElementAt(0), combinedHandles.ElementAt(3));
Assert.AreEqual(handleGroups[0].ElementAt(1), combinedHandles.ElementAt(4));
Assert.AreEqual(handleGroups[0].ElementAt(2), combinedHandles.ElementAt(5));
Assert.AreEqual(singlePages[0], combinedHandles.ElementAt(8));
Assert.AreEqual(singlePages[1], combinedHandles.ElementAt(9));
Assert.AreEqual(singlePages[2], combinedHandles.ElementAt(10));
}
}
}