From 95c06de4c16d292ad1cd962121762f83b9cce373 Mon Sep 17 00:00:00 2001 From: riperiperi Date: Fri, 12 May 2023 01:30:47 +0100 Subject: [PATCH] GPU: Remove swizzle undefined matching and rework depth aliasing (#4896) * GPU: Remove swizzle undefined matching and rework depth aliasing @gdkchan pointed out that UI textures in TOTK seemed to be setting their texture swizzle incorrectly (texture was RGB but was sampling A, swizzle for A was wrong), so I determined that SwizzleComponentMatches was the problem and set on eliminating it. This PR combines existing work to select the most recently modified texture (now used when selecting which aliased texture to use) with some additional changes to remove the swizzle check and support aliased view creation. The original observation (#1538) was that we wanted to match depth textures for the purposes of aliasing with color textures, but they often had different swizzle from what was sampled (as it's generally the identity swizzle once rendered). At the time, I decided to allow swizzles to match if only the defined components matched, which fixed the issue in all known cases but could easily be broken by a game _expecting_ a given swizzle, such as a 1/0 value on a component. This error case could also occur in textures that don't even depth alias, such as R11G11B10, as the rule was created to generally apply to all cases. The solution is now to fail this exact match test, and allow the search for an R32 texture to create a swizzled view of a D32 texture (and other such cases). This allows the creation of a view that mismatches the requested format, which wasn't present before and was the reason for the swizzle matching approach. The exact match and view creation rules now follow the same rules over what textures to select when there are multiple options (such as a "perfect" match and an "aliased" match at the same time). It now selects the most recently modified texture, which is done with a new sequence number in the GpuContext (because we don't have enough of these). Reportedly fixes UI having weird coloured backgrounds in TOTK. This also fixes an issue in MK8D where returning from a race resulted in the character selection cubemaps being broken. May work around issues introduced by the "short texture cache" PR due to modification ordering, though they won't be truly fixed. Should allow (#4365) to avoid copies in more cases. Need to test that. I tested a bunch of games #1538 originally affected and they seem to be fine. This change affects all games so it would be good to get some wide testing on it. * Address feedback 1, fix an issue * Workaround: Do not allow copies for format alias. These should be removed when D32<->R32 copy dependencies become legal --- src/Ryujinx.Graphics.Gpu/GpuContext.cs | 11 +++ src/Ryujinx.Graphics.Gpu/Image/Texture.cs | 6 +- .../Image/TextureCache.cs | 92 +++++++++++-------- .../Image/TextureCompatibility.cs | 78 +++------------- .../Image/TextureGroup.cs | 9 ++ src/Ryujinx.Graphics.Gpu/Image/TextureInfo.cs | 34 ++++++- .../Image/TextureViewCompatibility.cs | 1 + 7 files changed, 125 insertions(+), 106 deletions(-) diff --git a/src/Ryujinx.Graphics.Gpu/GpuContext.cs b/src/Ryujinx.Graphics.Gpu/GpuContext.cs index bab62b95..ccaabf70 100644 --- a/src/Ryujinx.Graphics.Gpu/GpuContext.cs +++ b/src/Ryujinx.Graphics.Gpu/GpuContext.cs @@ -98,6 +98,8 @@ namespace Ryujinx.Graphics.Gpu private Thread _gpuThread; private bool _pendingSync; + private long _modifiedSequence; + /// /// Creates a new instance of the GPU emulation context. /// @@ -200,6 +202,15 @@ namespace Ryujinx.Graphics.Gpu return divided * NsToTicksFractionNumerator + errorBias; } + /// + /// Gets a sequence number for resource modification ordering. This increments on each call. + /// + /// A sequence number for resource modification ordering + public long GetModifiedSequence() + { + return _modifiedSequence++; + } + /// /// Gets the value of the GPU timer. /// diff --git a/src/Ryujinx.Graphics.Gpu/Image/Texture.cs b/src/Ryujinx.Graphics.Gpu/Image/Texture.cs index 0427d09b..a7af1aad 100644 --- a/src/Ryujinx.Graphics.Gpu/Image/Texture.cs +++ b/src/Ryujinx.Graphics.Gpu/Image/Texture.cs @@ -1170,6 +1170,7 @@ namespace Ryujinx.Graphics.Gpu.Image /// Host GPU capabilities /// Texture view initial layer on this texture /// Texture view first mipmap level on this texture + /// Texture search flags /// The level of compatiblilty a view with the given parameters created from this texture has public TextureViewCompatibility IsViewCompatible( TextureInfo info, @@ -1178,11 +1179,12 @@ namespace Ryujinx.Graphics.Gpu.Image int layerSize, Capabilities caps, out int firstLayer, - out int firstLevel) + out int firstLevel, + TextureSearchFlags flags = TextureSearchFlags.None) { TextureViewCompatibility result = TextureViewCompatibility.Full; - result = TextureCompatibility.PropagateViewCompatibility(result, TextureCompatibility.ViewFormatCompatible(Info, info, caps)); + result = TextureCompatibility.PropagateViewCompatibility(result, TextureCompatibility.ViewFormatCompatible(Info, info, caps, flags)); if (result != TextureViewCompatibility.Incompatible) { result = TextureCompatibility.PropagateViewCompatibility(result, TextureCompatibility.ViewTargetCompatible(Info, info, ref caps)); diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureCache.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureCache.cs index 49191239..bccd3fd7 100644 --- a/src/Ryujinx.Graphics.Gpu/Image/TextureCache.cs +++ b/src/Ryujinx.Graphics.Gpu/Image/TextureCache.cs @@ -569,7 +569,7 @@ namespace Ryujinx.Graphics.Gpu.Image Texture texture = null; - TextureMatchQuality bestQuality = TextureMatchQuality.NoMatch; + long bestSequence = 0; for (int index = 0; index < sameAddressOverlapsCount; index++) { @@ -601,17 +601,12 @@ namespace Ryujinx.Graphics.Gpu.Image continue; } } - } - if (matchQuality == TextureMatchQuality.Perfect) - { - texture = overlap; - break; - } - else if (matchQuality > bestQuality) - { - texture = overlap; - bestQuality = matchQuality; + if (texture == null || overlap.Group.ModifiedSequence - bestSequence > 0) + { + texture = overlap; + bestSequence = overlap.Group.ModifiedSequence; + } } } @@ -664,6 +659,7 @@ namespace Ryujinx.Graphics.Gpu.Image int fullyCompatible = 0; // Evaluate compatibility of overlaps, add temporary references + int preferredOverlap = -1; for (int index = 0; index < overlapsCount; index++) { @@ -675,17 +671,26 @@ namespace Ryujinx.Graphics.Gpu.Image sizeInfo.LayerSize, _context.Capabilities, out int firstLayer, - out int firstLevel); + out int firstLevel, + flags); - if (overlapCompatibility == TextureViewCompatibility.Full) + if (overlapCompatibility >= TextureViewCompatibility.FormatAlias) { if (overlap.IsView) { - overlapCompatibility = TextureViewCompatibility.CopyOnly; + overlapCompatibility = overlapCompatibility == TextureViewCompatibility.FormatAlias ? + TextureViewCompatibility.Incompatible : + TextureViewCompatibility.CopyOnly; } else { fullyCompatible++; + + if (preferredOverlap == -1 || overlap.Group.ModifiedSequence - bestSequence > 0) + { + preferredOverlap = index; + bestSequence = overlap.Group.ModifiedSequence; + } } } @@ -695,37 +700,50 @@ namespace Ryujinx.Graphics.Gpu.Image // Search through the overlaps to find a compatible view and establish any copy dependencies. - for (int index = 0; index < overlapsCount; index++) + if (preferredOverlap != -1) { - Texture overlap = _textureOverlaps[index]; - OverlapInfo oInfo = _overlapInfo[index]; + Texture overlap = _textureOverlaps[preferredOverlap]; + OverlapInfo oInfo = _overlapInfo[preferredOverlap]; - if (oInfo.Compatibility == TextureViewCompatibility.Full) + bool aliased = oInfo.Compatibility == TextureViewCompatibility.FormatAlias; + + if (!isSamplerTexture) { - if (!isSamplerTexture) - { - // If this is not a sampler texture, the size might be different from the requested size, - // so we need to make sure the texture information has the correct size for this base texture, - // before creating the view. - info = info.CreateInfoForLevelView(overlap, oInfo.FirstLevel); - } + // If this is not a sampler texture, the size might be different from the requested size, + // so we need to make sure the texture information has the correct size for this base texture, + // before creating the view. - texture = overlap.CreateView(info, sizeInfo, range.Value, oInfo.FirstLayer, oInfo.FirstLevel); - texture.SynchronizeMemory(); - break; + info = info.CreateInfoForLevelView(overlap, oInfo.FirstLevel, aliased); } - else if (oInfo.Compatibility == TextureViewCompatibility.CopyOnly && fullyCompatible == 0) + else if (aliased) { - // Only copy compatible. If there's another choice for a FULLY compatible texture, choose that instead. + // The format must be changed to match the parent. + info = info.CreateInfoWithFormat(overlap.Info.FormatInfo); + } - texture = new Texture(_context, _physicalMemory, info, sizeInfo, range.Value, scaleMode); + texture = overlap.CreateView(info, sizeInfo, range.Value, oInfo.FirstLayer, oInfo.FirstLevel); + texture.SynchronizeMemory(); + } + else + { + for (int index = 0; index < overlapsCount; index++) + { + Texture overlap = _textureOverlaps[index]; + OverlapInfo oInfo = _overlapInfo[index]; - texture.InitializeGroup(true, true, new List()); - texture.InitializeData(false, false); + if (oInfo.Compatibility == TextureViewCompatibility.CopyOnly && fullyCompatible == 0) + { + // Only copy compatible. If there's another choice for a FULLY compatible texture, choose that instead. - overlap.SynchronizeMemory(); - overlap.CreateCopyDependency(texture, oInfo.FirstLayer, oInfo.FirstLevel, true); - break; + texture = new Texture(_context, _physicalMemory, info, sizeInfo, range.Value, scaleMode); + + texture.InitializeGroup(true, true, new List()); + texture.InitializeData(false, false); + + overlap.SynchronizeMemory(); + overlap.CreateCopyDependency(texture, oInfo.FirstLayer, oInfo.FirstLevel, true); + break; + } } } @@ -740,7 +758,7 @@ namespace Ryujinx.Graphics.Gpu.Image Texture overlap = _textureOverlaps[index]; OverlapInfo oInfo = _overlapInfo[index]; - if (oInfo.Compatibility <= TextureViewCompatibility.LayoutIncompatible) + if (oInfo.Compatibility <= TextureViewCompatibility.LayoutIncompatible || oInfo.Compatibility == TextureViewCompatibility.FormatAlias) { if (!overlap.IsView && texture.DataOverlaps(overlap, oInfo.Compatibility)) { diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs index 5d846222..85ad0bb0 100644 --- a/src/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs +++ b/src/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs @@ -291,22 +291,7 @@ namespace Ryujinx.Graphics.Gpu.Image /// The minimum compatibility level of two provided view compatibility results public static TextureViewCompatibility PropagateViewCompatibility(TextureViewCompatibility first, TextureViewCompatibility second) { - if (first == TextureViewCompatibility.Incompatible || second == TextureViewCompatibility.Incompatible) - { - return TextureViewCompatibility.Incompatible; - } - else if (first == TextureViewCompatibility.LayoutIncompatible || second == TextureViewCompatibility.LayoutIncompatible) - { - return TextureViewCompatibility.LayoutIncompatible; - } - else if (first == TextureViewCompatibility.CopyOnly || second == TextureViewCompatibility.CopyOnly) - { - return TextureViewCompatibility.CopyOnly; - } - else - { - return TextureViewCompatibility.Full; - } + return (TextureViewCompatibility)Math.Min((int)first, (int)second); } /// @@ -628,15 +613,21 @@ namespace Ryujinx.Graphics.Gpu.Image /// Texture information of the texture view /// Texture information of the texture view /// Host GPU capabilities + /// Texture search flags /// The view compatibility level of the texture formats - public static TextureViewCompatibility ViewFormatCompatible(TextureInfo lhs, TextureInfo rhs, Capabilities caps) + public static TextureViewCompatibility ViewFormatCompatible(TextureInfo lhs, TextureInfo rhs, Capabilities caps, TextureSearchFlags flags) { FormatInfo lhsFormat = lhs.FormatInfo; FormatInfo rhsFormat = rhs.FormatInfo; if (lhsFormat.Format.IsDepthOrStencil() || rhsFormat.Format.IsDepthOrStencil()) { - return lhsFormat.Format == rhsFormat.Format ? TextureViewCompatibility.Full : TextureViewCompatibility.Incompatible; + return FormatMatches(lhs, rhs, flags.HasFlag(TextureSearchFlags.ForSampler), flags.HasFlag(TextureSearchFlags.DepthAlias)) switch + { + TextureMatchQuality.Perfect => TextureViewCompatibility.Full, + TextureMatchQuality.FormatAlias => TextureViewCompatibility.FormatAlias, + _ => TextureViewCompatibility.Incompatible + }; } if (IsFormatHostIncompatible(lhs, caps) || IsFormatHostIncompatible(rhs, caps)) @@ -754,49 +745,6 @@ namespace Ryujinx.Graphics.Gpu.Image return result ? TextureViewCompatibility.Full : TextureViewCompatibility.Incompatible; } - /// - /// Checks if a swizzle component in two textures functionally match, taking into account if the components are defined. - /// - /// Texture information to compare - /// Texture information to compare with - /// Swizzle component for the first texture - /// Swizzle component for the second texture - /// Component index, starting at 0 for red - /// True if the swizzle components functionally match, false othersize - private static bool SwizzleComponentMatches(TextureInfo lhs, TextureInfo rhs, SwizzleComponent swizzleLhs, SwizzleComponent swizzleRhs, int component) - { - int lhsComponents = lhs.FormatInfo.Components; - int rhsComponents = rhs.FormatInfo.Components; - - if (lhsComponents == 4 && rhsComponents == 4) - { - return swizzleLhs == swizzleRhs; - } - - // Swizzles after the number of components a format defines are "undefined". - // We allow these to not be equal under certain circumstances. - // This can only happen when there are less than 4 components in a format. - // It tends to happen when float depth textures are sampled. - - bool lhsDefined = (swizzleLhs - SwizzleComponent.Red) < lhsComponents; - bool rhsDefined = (swizzleRhs - SwizzleComponent.Red) < rhsComponents; - - if (lhsDefined == rhsDefined) - { - // If both are undefined, return true. Otherwise just check if they're equal. - return lhsDefined ? swizzleLhs == swizzleRhs : true; - } - else - { - SwizzleComponent defined = lhsDefined ? swizzleLhs : swizzleRhs; - SwizzleComponent undefined = lhsDefined ? swizzleRhs : swizzleLhs; - - // Undefined swizzle can be matched by a forced value (0, 1), exact equality, or expected value. - // For example, R___ matches R001, RGBA but not RBGA. - return defined == undefined || defined < SwizzleComponent.Red || defined == SwizzleComponent.Red + component; - } - } - /// /// Checks if the texture shader sampling parameters of two texture informations match. /// @@ -806,10 +754,10 @@ namespace Ryujinx.Graphics.Gpu.Image public static bool SamplerParamsMatches(TextureInfo lhs, TextureInfo rhs) { return lhs.DepthStencilMode == rhs.DepthStencilMode && - SwizzleComponentMatches(lhs, rhs, lhs.SwizzleR, rhs.SwizzleR, 0) && - SwizzleComponentMatches(lhs, rhs, lhs.SwizzleG, rhs.SwizzleG, 1) && - SwizzleComponentMatches(lhs, rhs, lhs.SwizzleB, rhs.SwizzleB, 2) && - SwizzleComponentMatches(lhs, rhs, lhs.SwizzleA, rhs.SwizzleA, 3); + lhs.SwizzleR == rhs.SwizzleR && + lhs.SwizzleG == rhs.SwizzleG && + lhs.SwizzleB == rhs.SwizzleB && + lhs.SwizzleA == rhs.SwizzleA; } /// diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs index b36b16e9..2fa1e79e 100644 --- a/src/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs +++ b/src/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs @@ -68,6 +68,11 @@ namespace Ryujinx.Graphics.Gpu.Image /// public bool HasIncompatibleOverlaps => _incompatibleOverlaps.Count > 0; + /// + /// Number indicating the order this texture group was modified relative to others. + /// + public long ModifiedSequence { get; private set; } + private readonly GpuContext _context; private readonly PhysicalMemory _physicalMemory; @@ -664,6 +669,8 @@ namespace Ryujinx.Graphics.Gpu.Image /// The texture that has been modified public void SignalModified(Texture texture) { + ModifiedSequence = _context.GetModifiedSequence(); + ClearIncompatibleOverlaps(texture); EvaluateRelevantHandles(texture, (baseHandle, regionCount, split) => @@ -684,6 +691,8 @@ namespace Ryujinx.Graphics.Gpu.Image /// True if this texture is being bound, false if unbound public void SignalModifying(Texture texture, bool bound) { + ModifiedSequence = _context.GetModifiedSequence(); + ClearIncompatibleOverlaps(texture); EvaluateRelevantHandles(texture, (baseHandle, regionCount, split) => diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureInfo.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureInfo.cs index a7ee12bc..1994d226 100644 --- a/src/Ryujinx.Graphics.Gpu/Image/TextureInfo.cs +++ b/src/Ryujinx.Graphics.Gpu/Image/TextureInfo.cs @@ -300,8 +300,9 @@ namespace Ryujinx.Graphics.Gpu.Image /// /// The parent texture /// The first level of the texture view + /// True if the parent format should be inherited /// The adjusted texture information with the new size - public TextureInfo CreateInfoForLevelView(Texture parent, int firstLevel) + public TextureInfo CreateInfoForLevelView(Texture parent, int firstLevel, bool parentFormat) { // When the texture is used as view of another texture, we must // ensure that the sizes are valid, otherwise data uploads would fail @@ -370,7 +371,36 @@ namespace Ryujinx.Graphics.Gpu.Image GobBlocksInZ, GobBlocksInTileX, target, - FormatInfo, + parentFormat ? parent.Info.FormatInfo : FormatInfo, + DepthStencilMode, + SwizzleR, + SwizzleG, + SwizzleB, + SwizzleA); + } + + /// + /// Creates texture information for a given format and this information. + /// + /// Format for the new texture info + /// New info with the specified format + public TextureInfo CreateInfoWithFormat(FormatInfo formatInfo) + { + return new TextureInfo( + GpuAddress, + Width, + Height, + DepthOrLayers, + Levels, + SamplesInX, + SamplesInY, + Stride, + IsLinear, + GobBlocksInY, + GobBlocksInZ, + GobBlocksInTileX, + Target, + formatInfo, DepthStencilMode, SwizzleR, SwizzleG, diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureViewCompatibility.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureViewCompatibility.cs index b89936eb..dfa688c4 100644 --- a/src/Ryujinx.Graphics.Gpu/Image/TextureViewCompatibility.cs +++ b/src/Ryujinx.Graphics.Gpu/Image/TextureViewCompatibility.cs @@ -9,6 +9,7 @@ Incompatible = 0, LayoutIncompatible, CopyOnly, + FormatAlias, Full } }