Texture Cache: "Texture Groups" and "Texture Dependencies" (#2001)

* Initial implementation (3d tex mips broken)

This works rather well for most games, just need to fix 3d texture mips.

* Cleanup

* Address feedback

* Copy Dependencies and various other fixes

* Fix layer/level offset for copy from view<->view.

* Remove dirty flag from dependency

The dirty flag behaviour is not needed - DeferredCopy is all we need.

* Fix tracking mip slices.

* Propagate granularity (fix astral chain)

* Address Feedback pt 1

* Save slice sizes as part of SizeInfo

* Fix nits

* Fix disposing multiple dependencies causing a crash

This list is obviously modified when removing dependencies, so create a copy of it.
This commit is contained in:
riperiperi 2021-03-02 22:30:54 +00:00 committed by GitHub
parent 7a90abc035
commit b530f0e110
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 1915 additions and 220 deletions

View file

@ -185,7 +185,14 @@ namespace Ryujinx.Graphics.Gpu.Image
{
bool hasValue = color != null;
bool changesScale = (hasValue != (_rtColors[index] != null)) || (hasValue && RenderTargetScale != color.ScaleFactor);
_rtColors[index] = color;
if (_rtColors[index] != color)
{
_rtColors[index]?.SignalModifying(false);
color?.SignalModifying(true);
_rtColors[index] = color;
}
return changesScale || (hasValue && color.ScaleMode != TextureScaleMode.Blacklisted && color.ScaleFactor != GraphicsConfig.ResScale);
}
@ -292,7 +299,14 @@ namespace Ryujinx.Graphics.Gpu.Image
{
bool hasValue = depthStencil != null;
bool changesScale = (hasValue != (_rtDepthStencil != null)) || (hasValue && RenderTargetScale != depthStencil.ScaleFactor);
_rtDepthStencil = depthStencil;
if (_rtDepthStencil != depthStencil)
{
_rtDepthStencil?.SignalModifying(false);
depthStencil?.SignalModifying(true);
_rtDepthStencil = depthStencil;
}
return changesScale || (hasValue && depthStencil.ScaleMode != TextureScaleMode.Blacklisted && depthStencil.ScaleFactor != GraphicsConfig.ResScale);
}
@ -754,38 +768,97 @@ namespace Ryujinx.Graphics.Gpu.Image
overlapsCount = _textures.FindOverlaps(range.Value, ref _textureOverlaps);
}
if (_overlapInfo.Length != _textureOverlaps.Length)
{
Array.Resize(ref _overlapInfo, _textureOverlaps.Length);
}
// =============== Find Texture View of Existing Texture ===============
int fullyCompatible = 0;
// Evaluate compatibility of overlaps
for (int index = 0; index < overlapsCount; index++)
{
Texture overlap = _textureOverlaps[index];
TextureViewCompatibility overlapCompatibility = overlap.IsViewCompatible(info, range.Value, out int firstLayer, out int firstLevel);
TextureViewCompatibility overlapCompatibility = overlap.IsViewCompatible(info, range.Value, sizeInfo.LayerSize, out int firstLayer, out int firstLevel);
if (overlapCompatibility == TextureViewCompatibility.Full)
{
TextureInfo oInfo = AdjustSizes(overlap, info, firstLevel);
if (overlap.IsView)
{
overlapCompatibility = TextureViewCompatibility.CopyOnly;
}
else
{
fullyCompatible++;
}
}
_overlapInfo[index] = new OverlapInfo(overlapCompatibility, firstLayer, firstLevel);
}
// Search through the overlaps to find a compatible view and establish any copy dependencies.
for (int index = 0; index < overlapsCount; index++)
{
Texture overlap = _textureOverlaps[index];
OverlapInfo oInfo = _overlapInfo[index];
if (oInfo.Compatibility == TextureViewCompatibility.Full)
{
TextureInfo adjInfo = AdjustSizes(overlap, info, oInfo.FirstLevel);
if (!isSamplerTexture)
{
info = oInfo;
info = adjInfo;
}
texture = overlap.CreateView(oInfo, sizeInfo, range.Value, firstLayer, firstLevel);
if (overlap.IsModified)
{
texture.SignalModified();
}
texture = overlap.CreateView(adjInfo, sizeInfo, range.Value, oInfo.FirstLayer, oInfo.FirstLevel);
ChangeSizeIfNeeded(info, texture, isSamplerTexture, sizeHint);
texture.SynchronizeMemory();
break;
}
else if (overlapCompatibility == TextureViewCompatibility.CopyOnly)
else if (oInfo.Compatibility == TextureViewCompatibility.CopyOnly && fullyCompatible == 0)
{
// TODO: Copy rules for targets created after the container texture. See below.
overlap.DisableMemoryTracking();
// Only copy compatible. If there's another choice for a FULLY compatible texture, choose that instead.
texture = new Texture(_context, info, sizeInfo, range.Value, scaleMode);
texture.InitializeGroup(true, true);
texture.InitializeData(false, false);
overlap.SynchronizeMemory();
overlap.CreateCopyDependency(texture, oInfo.FirstLayer, oInfo.FirstLevel, true);
break;
}
}
if (texture != null)
{
// This texture could be a view of multiple parent textures with different storages, even if it is a view.
// When a texture is created, make sure all possible dependencies to other textures are created as copies.
// (even if it could be fulfilled without a copy)
for (int index = 0; index < overlapsCount; index++)
{
Texture overlap = _textureOverlaps[index];
OverlapInfo oInfo = _overlapInfo[index];
if (oInfo.Compatibility != TextureViewCompatibility.Incompatible && overlap.Group != texture.Group)
{
overlap.SynchronizeMemory();
overlap.CreateCopyDependency(texture, oInfo.FirstLayer, oInfo.FirstLevel, true);
}
}
texture.SynchronizeMemory();
}
// =============== Create a New Texture ===============
// No match, create a new texture.
if (texture == null)
{
@ -795,24 +868,53 @@ namespace Ryujinx.Graphics.Gpu.Image
// Any textures that are incompatible will contain garbage data, so they should be removed where possible.
int viewCompatible = 0;
fullyCompatible = 0;
bool setData = isSamplerTexture || overlapsCount == 0 || flags.HasFlag(TextureSearchFlags.ForCopy);
bool hasLayerViews = false;
bool hasMipViews = false;
for (int index = 0; index < overlapsCount; index++)
{
Texture overlap = _textureOverlaps[index];
bool overlapInCache = overlap.CacheNode != null;
TextureViewCompatibility compatibility = texture.IsViewCompatible(overlap.Info, overlap.Range, out int firstLayer, out int firstLevel);
TextureViewCompatibility compatibility = texture.IsViewCompatible(overlap.Info, overlap.Range, overlap.LayerSize, out int firstLayer, out int firstLevel);
if (overlap.IsView && compatibility == TextureViewCompatibility.Full)
{
compatibility = TextureViewCompatibility.CopyOnly;
}
if (compatibility != TextureViewCompatibility.Incompatible)
{
if (_overlapInfo.Length != _textureOverlaps.Length)
if (compatibility == TextureViewCompatibility.Full)
{
Array.Resize(ref _overlapInfo, _textureOverlaps.Length);
if (viewCompatible == fullyCompatible)
{
_overlapInfo[viewCompatible] = new OverlapInfo(compatibility, firstLayer, firstLevel);
_textureOverlaps[viewCompatible++] = overlap;
}
else
{
// Swap overlaps so that the fully compatible views have priority.
_overlapInfo[viewCompatible] = _overlapInfo[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;
}
_overlapInfo[viewCompatible] = new OverlapInfo(compatibility, firstLayer, firstLevel);
_textureOverlaps[viewCompatible++] = overlap;
hasLayerViews |= overlap.Info.GetSlices() < texture.Info.GetSlices();
hasMipViews |= overlap.Info.Levels < texture.Info.Levels;
}
else if (overlapInCache || !setData)
{
@ -841,6 +943,8 @@ namespace Ryujinx.Graphics.Gpu.Image
}
}
texture.InitializeGroup(hasLayerViews, hasMipViews);
// 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);
@ -848,17 +952,17 @@ namespace Ryujinx.Graphics.Gpu.Image
for (int index = 0; index < viewCompatible; index++)
{
Texture overlap = _textureOverlaps[index];
OverlapInfo oInfo = _overlapInfo[index];
if (oInfo.Compatibility != TextureViewCompatibility.Full)
if (overlap.Group == texture.Group)
{
continue; // Copy only compatibilty.
// If the texture group is equal, then this texture (or its parent) is already a view.
continue;
}
TextureInfo overlapInfo = AdjustSizes(texture, overlap.Info, oInfo.FirstLevel);
TextureCreateInfo createInfo = GetCreateInfo(overlapInfo, _context.Capabilities, overlap.ScaleFactor);
if (texture.ScaleFactor != overlap.ScaleFactor)
{
// A bit tricky, our new texture may need to contain an existing texture that is upscaled, but isn't itself.
@ -867,47 +971,30 @@ namespace Ryujinx.Graphics.Gpu.Image
texture.PropagateScale(overlap);
}
ITexture newView = texture.HostTexture.CreateView(createInfo, oInfo.FirstLayer, oInfo.FirstLevel);
overlap.HostTexture.CopyTo(newView, 0, 0);
// Inherit modification from overlapping texture, do that before replacing
// the view since the replacement operation removes it from the list.
if (overlap.IsModified)
if (oInfo.Compatibility != TextureViewCompatibility.Full)
{
texture.SignalModified();
// Copy only compatibility, or target texture is already a view.
ChangeSizeIfNeeded(overlapInfo, overlap, false, sizeHint); // Force a size match for copy
overlap.SynchronizeMemory();
texture.CreateCopyDependency(overlap, oInfo.FirstLayer, oInfo.FirstLevel, false);
}
overlap.ReplaceView(texture, overlapInfo, newView, oInfo.FirstLayer, oInfo.FirstLevel);
}
// If the texture is a 3D texture, we need to additionally copy any slice
// of the 3D texture to the newly created 3D texture.
if (info.Target == Target.Texture3D && viewCompatible > 0)
{
// TODO: This copy can currently only happen when the 3D texture is created.
// If a game clears and redraws the slices, we won't be able to copy the new data to the 3D texture.
// Disable tracking to try keep at least the original data in there for as long as possible.
texture.DisableMemoryTracking();
for (int index = 0; index < viewCompatible; index++)
else
{
Texture overlap = _textureOverlaps[index];
OverlapInfo oInfo = _overlapInfo[index];
TextureCreateInfo createInfo = GetCreateInfo(overlapInfo, _context.Capabilities, overlap.ScaleFactor);
if (oInfo.Compatibility != TextureViewCompatibility.Incompatible)
{
overlap.BlacklistScale();
ITexture newView = texture.HostTexture.CreateView(createInfo, oInfo.FirstLayer, oInfo.FirstLevel);
overlap.HostTexture.CopyTo(texture.HostTexture, oInfo.FirstLayer, oInfo.FirstLevel);
overlap.SynchronizeMemory();
if (overlap.IsModified)
{
texture.SignalModified();
}
}
overlap.HostTexture.CopyTo(newView, 0, 0);
overlap.ReplaceView(texture, overlapInfo, newView, oInfo.FirstLayer, oInfo.FirstLevel);
}
}
texture.SynchronizeMemory();
}
// Sampler textures are managed by the texture pool, all other textures