aboutsummaryrefslogtreecommitdiff
path: root/Ryujinx.Graphics.Gpu/Image/TextureManager.cs
diff options
context:
space:
mode:
authorriperiperi <rhy3756547@hotmail.com>2020-07-07 03:41:07 +0100
committerGitHub <noreply@github.com>2020-07-07 04:41:07 +0200
commit484eb645ae0611f60fae845ed011ed6115352e06 (patch)
treeb15972f04dae0b1b6c644cbbbdfcd98856adee15 /Ryujinx.Graphics.Gpu/Image/TextureManager.cs
parent43b78ae157eed5c9436dc19a6d498655891202d8 (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.cs187
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))