mirror of
https://github.com/GreemDev/Ryujinx.git
synced 2024-12-23 11:35:47 +00:00
Remove pool cache entries for incompatible overlapping textures (#2568)
This greatly reduces memory usage in games that aggressively reuse memory without removing dead textures from the pool, such as the Xenoblade games, UE3 games, and to a lesser extent, UE4/unity games. This change stops memory usage from ballooning in xenoblade and some other games. It will also reduce texture view/dependency complexity in some games - for example in MK8D it will reduce the number of surface copies between lighting cubemaps generated for actors. There shouldn't be any performance impact from doing this, though the deletion and creation of textures could be improved by improving the OpenGL texture storage cache, which is very simple and limited right now. This will be improved in future. Another potential error has been fixed with the texture cache, which could prevent data loss when data is interchangably written to textures from both the GPU and CPU. It was possible that the dirty flag for a texture would be consumed without the data being synchronized on next use, due to the old overlap check. This check no longer consumes the dirty flag. Please test a bunch of games to make sure they still work, and there are no performance regressions.
This commit is contained in:
parent
e0af248e6f
commit
bdc1f91a5b
4 changed files with 55 additions and 28 deletions
|
@ -43,7 +43,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
|
|
||||||
oldestTexture.SynchronizeMemory();
|
oldestTexture.SynchronizeMemory();
|
||||||
|
|
||||||
if (oldestTexture.IsModified && !oldestTexture.ConsumeModified())
|
if (oldestTexture.IsModified && !oldestTexture.CheckModified(true))
|
||||||
{
|
{
|
||||||
// The texture must be flushed if it falls out of the auto delete cache.
|
// 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,
|
// Flushes out of the auto delete cache do not trigger write tracking,
|
||||||
|
|
|
@ -252,7 +252,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
if (!isView)
|
if (!isView)
|
||||||
{
|
{
|
||||||
// Don't update this texture the next time we synchronize.
|
// Don't update this texture the next time we synchronize.
|
||||||
ConsumeModified();
|
CheckModified(true);
|
||||||
|
|
||||||
if (ScaleMode == TextureScaleMode.Scaled)
|
if (ScaleMode == TextureScaleMode.Scaled)
|
||||||
{
|
{
|
||||||
|
@ -599,12 +599,13 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Checks if the memory for this texture was modified, and returns true if it was.
|
/// Checks if the memory for this texture was modified, and returns true if it was.
|
||||||
/// The modified flags are consumed as a result.
|
/// The modified flags are optionally consumed as a result.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="consume">True to consume the dirty flags and reprotect, false to leave them as is</param>
|
||||||
/// <returns>True if the texture was modified, false otherwise.</returns>
|
/// <returns>True if the texture was modified, false otherwise.</returns>
|
||||||
public bool ConsumeModified()
|
public bool CheckModified(bool consume)
|
||||||
{
|
{
|
||||||
return Group.ConsumeDirty(this);
|
return Group.CheckDirty(this, consume);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -634,7 +635,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Group.ConsumeDirty(this);
|
Group.CheckDirty(this, true);
|
||||||
SynchronizeFull();
|
SynchronizeFull();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -698,7 +699,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
{
|
{
|
||||||
BlacklistScale();
|
BlacklistScale();
|
||||||
|
|
||||||
Group.ConsumeDirty(this);
|
Group.CheckDirty(this, true);
|
||||||
|
|
||||||
IsModified = false;
|
IsModified = false;
|
||||||
|
|
||||||
|
|
|
@ -623,7 +623,12 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
hasLayerViews |= overlap.Info.GetSlices() < texture.Info.GetSlices();
|
hasLayerViews |= overlap.Info.GetSlices() < texture.Info.GetSlices();
|
||||||
hasMipViews |= overlap.Info.Levels < texture.Info.Levels;
|
hasMipViews |= overlap.Info.Levels < texture.Info.Levels;
|
||||||
}
|
}
|
||||||
else if (overlapInCache || !setData)
|
else
|
||||||
|
{
|
||||||
|
bool removeOverlap;
|
||||||
|
bool modified = overlap.CheckModified(false);
|
||||||
|
|
||||||
|
if (overlapInCache || !setData)
|
||||||
{
|
{
|
||||||
if (info.GobBlocksInZ > 1 && info.GobBlocksInZ == overlap.Info.GobBlocksInZ)
|
if (info.GobBlocksInZ > 1 && info.GobBlocksInZ == overlap.Info.GobBlocksInZ)
|
||||||
{
|
{
|
||||||
|
@ -637,9 +642,10 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
|
|
||||||
// If the texture was modified since its last use, then that data is probably meant to go into this texture.
|
// 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.
|
// If the data has been modified by the CPU, then it also shouldn't be flushed.
|
||||||
bool modified = overlap.ConsumeModified();
|
|
||||||
|
|
||||||
bool flush = overlapInCache && !modified && !texture.Range.Contains(overlap.Range) && overlap.HasViewCompatibleChild(texture);
|
bool viewCompatibleChild = overlap.HasViewCompatibleChild(texture);
|
||||||
|
|
||||||
|
bool flush = overlapInCache && !modified && !texture.Range.Contains(overlap.Range) && viewCompatibleChild;
|
||||||
|
|
||||||
setData |= modified || flush;
|
setData |= modified || flush;
|
||||||
|
|
||||||
|
@ -647,6 +653,20 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
{
|
{
|
||||||
_cache.Remove(overlap, flush);
|
_cache.Remove(overlap, flush);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
removeOverlap = modified && !viewCompatibleChild;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// If an incompatible overlapping texture has been modified, then it's data is likely destined for this texture,
|
||||||
|
// and the overlapped texture will contain garbage. In this case, it should be removed to save memory.
|
||||||
|
removeOverlap = modified;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (removeOverlap && overlap.Info.Target != Target.TextureBuffer)
|
||||||
|
{
|
||||||
|
overlap.RemoveFromPools(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -83,11 +83,13 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Consume the dirty flags for a given texture. The state is shared between views of the same layers and levels.
|
/// Check and optionally consume the dirty flags for a given texture.
|
||||||
|
/// The state is shared between views of the same layers and levels.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="texture">The texture being used</param>
|
/// <param name="texture">The texture being used</param>
|
||||||
|
/// <param name="consume">True to consume the dirty flags and reprotect, false to leave them as is</param>
|
||||||
/// <returns>True if a flag was dirty, false otherwise</returns>
|
/// <returns>True if a flag was dirty, false otherwise</returns>
|
||||||
public bool ConsumeDirty(Texture texture)
|
public bool CheckDirty(Texture texture, bool consume)
|
||||||
{
|
{
|
||||||
bool dirty = false;
|
bool dirty = false;
|
||||||
|
|
||||||
|
@ -100,8 +102,12 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
foreach (CpuRegionHandle handle in group.Handles)
|
foreach (CpuRegionHandle handle in group.Handles)
|
||||||
{
|
{
|
||||||
if (handle.Dirty)
|
if (handle.Dirty)
|
||||||
|
{
|
||||||
|
if (consume)
|
||||||
{
|
{
|
||||||
handle.Reprotect();
|
handle.Reprotect();
|
||||||
|
}
|
||||||
|
|
||||||
dirty = true;
|
dirty = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue