diff options
author | riperiperi <rhy3756547@hotmail.com> | 2020-07-07 03:41:07 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-07-07 04:41:07 +0200 |
commit | 484eb645ae0611f60fae845ed011ed6115352e06 (patch) | |
tree | b15972f04dae0b1b6c644cbbbdfcd98856adee15 /Ryujinx.Graphics.Gpu/Image/TextureManager.cs | |
parent | 43b78ae157eed5c9436dc19a6d498655891202d8 (diff) |
Implement Zero-Configuration Resolution Scaling (#1365)
* Initial implementation of Render Target Scaling
Works with most games I have. No GUI option right now, it is hardcoded.
Missing handling for texelFetch operation.
* Realtime Configuration, refactoring.
* texelFetch scaling on fragment shader (WIP)
* Improve Shader-Side changes.
* Fix potential crash when no color/depth bound
* Workaround random uses of textures in compute.
This was blacklisting textures in a few games despite causing no bugs. Will eventually add full support so this doesn't break anything.
* Fix scales oscillating when changing between non-native scales.
* Scaled textures on compute, cleanup, lazier uniform update.
* Cleanup.
* Fix stupidity
* Address Thog Feedback.
* Cover most of GDK's feedback (two comments remain)
* Fix bad rename
* Move IsDepthStencil to FormatExtensions, add docs.
* Fix default config, square texture detection.
* Three final fixes:
- Nearest copy when texture is integer format.
- Texture2D -> Texture3D copy correctly blacklists the texture before trying an unscaled copy (caused driver error)
- Discount small textures.
* Remove scale threshold.
Not needed right now - we'll see if we run into problems.
* All CPU modification blacklists scale.
* Fix comment.
Diffstat (limited to 'Ryujinx.Graphics.Gpu/Image/TextureManager.cs')
-rw-r--r-- | Ryujinx.Graphics.Gpu/Image/TextureManager.cs | 187 |
1 files changed, 179 insertions, 8 deletions
diff --git a/Ryujinx.Graphics.Gpu/Image/TextureManager.cs b/Ryujinx.Graphics.Gpu/Image/TextureManager.cs index c0eeb068..ccd56ae2 100644 --- a/Ryujinx.Graphics.Gpu/Image/TextureManager.cs +++ b/Ryujinx.Graphics.Gpu/Image/TextureManager.cs @@ -40,6 +40,11 @@ namespace Ryujinx.Graphics.Gpu.Image private readonly HashSet<Texture> _modifiedLinear; /// <summary> + /// The scaling factor applied to all currently bound render targets. + /// </summary> + public float RenderTargetScale { get; private set; } = 1f; + + /// <summary> /// Constructs a new instance of the texture manager. /// </summary> /// <param name="context">The GPU context that the texture manager belongs to</param> @@ -169,18 +174,112 @@ namespace Ryujinx.Graphics.Gpu.Image /// </summary> /// <param name="index">The index of the color buffer to set (up to 8)</param> /// <param name="color">The color buffer texture</param> - public void SetRenderTargetColor(int index, Texture color) + /// <returns>True if render target scale must be updated.</returns> + public bool SetRenderTargetColor(int index, Texture color) { + bool hasValue = color != null; + bool changesScale = (hasValue != (_rtColors[index] != null)) || (hasValue && RenderTargetScale != color.ScaleFactor); _rtColors[index] = color; + + return changesScale || (hasValue && color.ScaleMode != TextureScaleMode.Blacklisted && color.ScaleFactor != GraphicsConfig.ResScale); + } + + /// <summary> + /// Updates the Render Target scale, given the currently bound render targets. + /// This will update scale to match the configured scale, scale textures that are eligible but not scaled, + /// and propagate blacklisted status from one texture to the ones bound with it. + /// </summary> + /// <param name="singleUse">If this is not -1, it indicates that only the given indexed target will be used.</param> + public void UpdateRenderTargetScale(int singleUse) + { + // Make sure all scales for render targets are at the highest they should be. Blacklisted targets should propagate their scale to the other targets. + bool mismatch = false; + bool blacklisted = false; + bool hasUpscaled = false; + float targetScale = GraphicsConfig.ResScale; + + void ConsiderTarget(Texture target) + { + if (target == null) return; + float scale = target.ScaleFactor; + + switch (target.ScaleMode) + { + case TextureScaleMode.Blacklisted: + mismatch |= scale != 1f; + blacklisted = true; + break; + case TextureScaleMode.Eligible: + mismatch = true; // We must make a decision. + break; + case TextureScaleMode.Scaled: + hasUpscaled = true; + mismatch |= scale != targetScale; // If the target scale has changed, reset the scale for all targets. + break; + } + } + + if (singleUse != -1) + { + // If only one target is in use (by a clear, for example) the others do not need to be checked for mismatching scale. + ConsiderTarget(_rtColors[singleUse]); + } + else + { + foreach (Texture color in _rtColors) + { + ConsiderTarget(color); + } + } + + ConsiderTarget(_rtDepthStencil); + + mismatch |= blacklisted && hasUpscaled; + + if (blacklisted) + { + targetScale = 1f; + } + + if (mismatch) + { + if (blacklisted) + { + // Propagate the blacklisted state to the other textures. + foreach (Texture color in _rtColors) + { + color?.BlacklistScale(); + } + + _rtDepthStencil?.BlacklistScale(); + } + else + { + // Set the scale of the other textures. + foreach (Texture color in _rtColors) + { + color?.SetScale(targetScale); + } + + _rtDepthStencil?.SetScale(targetScale); + } + } + + RenderTargetScale = targetScale; } /// <summary> /// Sets the render target depth-stencil buffer. /// </summary> /// <param name="depthStencil">The depth-stencil buffer texture</param> - public void SetRenderTargetDepthStencil(Texture depthStencil) + /// <returns>True if render target scale must be updated.</returns> + public bool SetRenderTargetDepthStencil(Texture depthStencil) { + bool hasValue = depthStencil != null; + bool changesScale = (hasValue != (_rtDepthStencil != null)) || (hasValue && RenderTargetScale != depthStencil.ScaleFactor); _rtDepthStencil = depthStencil; + + return changesScale || (hasValue && depthStencil.ScaleMode != TextureScaleMode.Blacklisted && depthStencil.ScaleFactor != GraphicsConfig.ResScale); } /// <summary> @@ -263,11 +362,58 @@ namespace Ryujinx.Graphics.Gpu.Image } /// <summary> + /// Determines if a given texture is eligible for upscaling from its info. + /// </summary> + /// <param name="info">The texture info to check</param> + /// <returns>True if eligible</returns> + public bool IsUpscaleCompatible(TextureInfo info) + { + return (info.Target == Target.Texture2D || info.Target == Target.Texture2DArray) && info.Levels == 1 && !info.FormatInfo.IsCompressed && UpscaleSafeMode(info); + } + + /// <summary> + /// Determines if a given texture is "safe" for upscaling from its info. + /// Note that this is different from being compatible - this elilinates targets that would have detrimental effects when scaled. + /// </summary> + /// <param name="info">The texture info to check</param> + /// <returns>True if safe</returns> + public bool UpscaleSafeMode(TextureInfo info) + { + // While upscaling works for all targets defined by IsUpscaleCompatible, we additionally blacklist targets here that + // may have undesirable results (upscaling blur textures) or simply waste GPU resources (upscaling texture atlas). + + if (!(info.FormatInfo.Format.IsDepthOrStencil() || info.FormatInfo.Format.HasOneComponent())) + { + // Discount square textures that aren't depth-stencil like. (excludes game textures, cubemap faces, most 3D texture LUT, texture atlas) + // Detect if the texture is possibly square. Widths may be aligned, so to remove the uncertainty we align both the width and height. + + int widthAlignment = (info.IsLinear ? 32 : 64) / info.FormatInfo.BytesPerPixel; + + bool possiblySquare = BitUtils.AlignUp(info.Width, widthAlignment) == BitUtils.AlignUp(info.Height, widthAlignment); + + if (possiblySquare) + { + return false; + } + } + + int aspect = (int)Math.Round((info.Width / (float)info.Height) * 9); + if (aspect == 16 && info.Height < 360) + { + // Targets that are roughly 16:9 can only be rescaled if they're equal to or above 360p. (excludes blur and bloom textures) + return false; + } + + return true; + } + + /// <summary> /// Tries to find an existing texture, or create a new one if not found. /// </summary> /// <param name="copyTexture">Copy texture to find or create</param> + /// <param name="preferScaling">Indicates if the texture should be scaled from the start</param> /// <returns>The texture</returns> - public Texture FindOrCreateTexture(CopyTexture copyTexture) + public Texture FindOrCreateTexture(CopyTexture copyTexture, bool preferScaling = true) { ulong address = _context.MemoryManager.Translate(copyTexture.Address.Pack()); @@ -308,7 +454,14 @@ namespace Ryujinx.Graphics.Gpu.Image Target.Texture2D, formatInfo); - Texture texture = FindOrCreateTexture(info, TextureSearchFlags.IgnoreMs); + TextureSearchFlags flags = TextureSearchFlags.IgnoreMs; + + if (preferScaling) + { + flags |= TextureSearchFlags.WithUpscale; + } + + Texture texture = FindOrCreateTexture(info, flags); texture.SynchronizeMemory(); @@ -391,7 +544,7 @@ namespace Ryujinx.Graphics.Gpu.Image target, formatInfo); - Texture texture = FindOrCreateTexture(info); + Texture texture = FindOrCreateTexture(info, TextureSearchFlags.WithUpscale); texture.SynchronizeMemory(); @@ -440,7 +593,7 @@ namespace Ryujinx.Graphics.Gpu.Image target, formatInfo); - Texture texture = FindOrCreateTexture(info); + Texture texture = FindOrCreateTexture(info, TextureSearchFlags.WithUpscale); texture.SynchronizeMemory(); @@ -457,6 +610,14 @@ namespace Ryujinx.Graphics.Gpu.Image { bool isSamplerTexture = (flags & TextureSearchFlags.Sampler) != 0; + bool isScalable = IsUpscaleCompatible(info); + + TextureScaleMode scaleMode = TextureScaleMode.Blacklisted; + if (isScalable) + { + scaleMode = (flags & TextureSearchFlags.WithUpscale) != 0 ? TextureScaleMode.Scaled : TextureScaleMode.Eligible; + } + // Try to find a perfect texture match, with the same address and parameters. int sameAddressOverlapsCount = _textures.FindOverlaps(info.Address, ref _textureOverlaps); @@ -556,7 +717,7 @@ namespace Ryujinx.Graphics.Gpu.Image // No match, create a new texture. if (texture == null) { - texture = new Texture(_context, info, sizeInfo); + texture = new Texture(_context, info, sizeInfo, scaleMode); // We need to synchronize before copying the old view data to the texture, // otherwise the copied data would be overwritten by a future synchronization. @@ -572,6 +733,14 @@ namespace Ryujinx.Graphics.Gpu.Image TextureCreateInfo createInfo = GetCreateInfo(overlapInfo, _context.Capabilities); + 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. + // In that case, we prefer the higher scale only if our format is render-target-like, otherwise we scale the view down before copy. + + texture.PropagateScale(overlap); + } + ITexture newView = texture.HostTexture.CreateView(createInfo, firstLayer, firstLevel); overlap.HostTexture.CopyTo(newView, 0, 0); @@ -583,7 +752,7 @@ namespace Ryujinx.Graphics.Gpu.Image CacheTextureModified(texture); } - overlap.ReplaceView(texture, overlapInfo, newView); + overlap.ReplaceView(texture, overlapInfo, newView, firstLayer, firstLevel); } } @@ -602,6 +771,8 @@ namespace Ryujinx.Graphics.Gpu.Image out int firstLayer, out int firstLevel)) { + overlap.BlacklistScale(); + overlap.HostTexture.CopyTo(texture.HostTexture, firstLayer, firstLevel); if (IsTextureModified(overlap)) |