From 484eb645ae0611f60fae845ed011ed6115352e06 Mon Sep 17 00:00:00 2001 From: riperiperi Date: Tue, 7 Jul 2020 03:41:07 +0100 Subject: 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. --- Ryujinx.Graphics.Gpu/Image/Texture.cs | 217 +++++++++++++++++++++++++++++++--- 1 file changed, 200 insertions(+), 17 deletions(-) (limited to 'Ryujinx.Graphics.Gpu/Image/Texture.cs') diff --git a/Ryujinx.Graphics.Gpu/Image/Texture.cs b/Ryujinx.Graphics.Gpu/Image/Texture.cs index 0f952ffd..87c409a0 100644 --- a/Ryujinx.Graphics.Gpu/Image/Texture.cs +++ b/Ryujinx.Graphics.Gpu/Image/Texture.cs @@ -29,10 +29,20 @@ namespace Ryujinx.Graphics.Gpu.Image /// public TextureInfo Info { get; private set; } + /// + /// Host scale factor. + /// + public float ScaleFactor { get; private set; } + + /// + /// Upscaling mode. Informs if a texture is scaled, or is eligible for scaling. + /// + public TextureScaleMode ScaleMode { get; private set; } + private int _depth; private int _layers; - private readonly int _firstLayer; - private readonly int _firstLevel; + private int _firstLayer; + private int _firstLevel; private bool _hasData; @@ -92,18 +102,25 @@ namespace Ryujinx.Graphics.Gpu.Image /// Size information of the texture /// The first layer of the texture, or 0 if the texture has no parent /// The first mipmap level of the texture, or 0 if the texture has no parent + /// The floating point scale factor to initialize with + /// The scale mode to initialize with private Texture( - GpuContext context, - TextureInfo info, - SizeInfo sizeInfo, - int firstLayer, - int firstLevel) + GpuContext context, + TextureInfo info, + SizeInfo sizeInfo, + int firstLayer, + int firstLevel, + float scaleFactor, + TextureScaleMode scaleMode) { InitializeTexture(context, info, sizeInfo); _firstLayer = firstLayer; _firstLevel = firstLevel; + ScaleFactor = scaleFactor; + ScaleMode = scaleMode; + _hasData = true; } @@ -113,13 +130,23 @@ namespace Ryujinx.Graphics.Gpu.Image /// GPU context that the texture belongs to /// Texture information /// Size information of the texture - public Texture(GpuContext context, TextureInfo info, SizeInfo sizeInfo) + /// The scale mode to initialize with. If scaled, the texture's data is loaded immediately and scaled up + public Texture(GpuContext context, TextureInfo info, SizeInfo sizeInfo, TextureScaleMode scaleMode) { + ScaleFactor = 1f; // Texture is first loaded at scale 1x. + ScaleMode = scaleMode; + InitializeTexture(context, info, sizeInfo); TextureCreateInfo createInfo = TextureManager.GetCreateInfo(info, context.Capabilities); - HostTexture = _context.Renderer.CreateTexture(createInfo); + HostTexture = _context.Renderer.CreateTexture(createInfo, ScaleFactor); + + if (scaleMode == TextureScaleMode.Scaled) + { + SynchronizeMemory(); // Load the data and then scale it up. + SetScale(GraphicsConfig.ResScale); + } } /// @@ -162,7 +189,9 @@ namespace Ryujinx.Graphics.Gpu.Image info, sizeInfo, _firstLayer + firstLayer, - _firstLevel + firstLevel); + _firstLevel + firstLevel, + ScaleFactor, + ScaleMode); TextureCreateInfo createInfo = TextureManager.GetCreateInfo(info, _context.Capabilities); @@ -282,7 +311,7 @@ namespace Ryujinx.Graphics.Gpu.Image } else { - ITexture newStorage = _context.Renderer.CreateTexture(createInfo); + ITexture newStorage = _context.Renderer.CreateTexture(createInfo, ScaleFactor); HostTexture.CopyTo(newStorage, 0, 0); @@ -290,6 +319,149 @@ namespace Ryujinx.Graphics.Gpu.Image } } + /// + /// Blacklists this texture from being scaled. Resets its scale to 1 if needed. + /// + public void BlacklistScale() + { + ScaleMode = TextureScaleMode.Blacklisted; + SetScale(1f); + } + + /// + /// Propagates the scale between this texture and another to ensure they have the same scale. + /// If one texture is blacklisted from scaling, the other will become blacklisted too. + /// + /// The other texture + public void PropagateScale(Texture other) + { + if (other.ScaleMode == TextureScaleMode.Blacklisted || ScaleMode == TextureScaleMode.Blacklisted) + { + BlacklistScale(); + other.BlacklistScale(); + } + else + { + // Prefer the configured scale if present. If not, prefer the max. + float targetScale = GraphicsConfig.ResScale; + float sharedScale = (ScaleFactor == targetScale || other.ScaleFactor == targetScale) ? targetScale : Math.Max(ScaleFactor, other.ScaleFactor); + + SetScale(sharedScale); + other.SetScale(sharedScale); + } + } + + /// + /// Helper method for copying our Texture2DArray texture to the given target, with scaling. + /// This creates temporary views for each array layer on both textures, copying each one at a time. + /// + /// The texture array to copy to + private void CopyArrayScaled(ITexture target) + { + TextureInfo viewInfo = new TextureInfo( + Info.Address, + Info.Width, + Info.Height, + 1, + Info.Levels, + Info.SamplesInX, + Info.SamplesInY, + Info.Stride, + Info.IsLinear, + Info.GobBlocksInY, + Info.GobBlocksInZ, + Info.GobBlocksInTileX, + Target.Texture2D, + Info.FormatInfo, + Info.DepthStencilMode, + Info.SwizzleR, + Info.SwizzleG, + Info.SwizzleB, + Info.SwizzleA); + + TextureCreateInfo createInfo = TextureManager.GetCreateInfo(viewInfo, _context.Capabilities); + + for (int i = 0; i < Info.DepthOrLayers; i++) + { + ITexture from = HostTexture.CreateView(createInfo, i, 0); + ITexture to = target.CreateView(createInfo, i, 0); + + from.CopyTo(to, new Extents2D(0, 0, from.Width, from.Height), new Extents2D(0, 0, to.Width, to.Height), true); + + from.Dispose(); + to.Dispose(); + } + } + + /// + /// Sets the Scale Factor on this texture, and immediately recreates it at the correct size. + /// When a texture is resized, a scaled copy is performed from the old texture to the new one, to ensure no data is lost. + /// If scale is equivalent, this only propagates the blacklisted/scaled mode. + /// If called on a view, its storage is resized instead. + /// When resizing storage, all texture views are recreated. + /// + /// The new scale factor for this texture + public void SetScale(float scale) + { + TextureScaleMode newScaleMode = ScaleMode == TextureScaleMode.Blacklisted ? ScaleMode : TextureScaleMode.Scaled; + + if (_viewStorage != this) + { + _viewStorage.ScaleMode = newScaleMode; + _viewStorage.SetScale(scale); + return; + } + + if (ScaleFactor != scale) + { + Logger.PrintDebug(LogClass.Gpu, $"Rescaling {Info.Width}x{Info.Height} {Info.FormatInfo.Format.ToString()} to ({ScaleFactor} to {scale}). "); + TextureCreateInfo createInfo = TextureManager.GetCreateInfo(Info, _context.Capabilities); + + ScaleFactor = scale; + + ITexture newStorage = _context.Renderer.CreateTexture(createInfo, ScaleFactor); + + if (Info.Target == Target.Texture2DArray) + { + CopyArrayScaled(newStorage); + } + else + { + HostTexture.CopyTo(newStorage, new Extents2D(0, 0, HostTexture.Width, HostTexture.Height), new Extents2D(0, 0, newStorage.Width, newStorage.Height), true); + } + + Logger.PrintDebug(LogClass.Gpu, $" Copy performed: {HostTexture.Width}x{HostTexture.Height} to {newStorage.Width}x{newStorage.Height}"); + + ReplaceStorage(newStorage); + + // All views must be recreated against the new storage. + + foreach (var view in _views) + { + Logger.PrintDebug(LogClass.Gpu, $" Recreating view {Info.Width}x{Info.Height} {Info.FormatInfo.Format.ToString()}."); + view.ScaleFactor = scale; + + TextureCreateInfo viewCreateInfo = TextureManager.GetCreateInfo(view.Info, _context.Capabilities); + + ITexture newView = HostTexture.CreateView(viewCreateInfo, view._firstLayer - _firstLayer, view._firstLevel - _firstLevel); + + view.ReplaceStorage(newView); + + view.ScaleMode = newScaleMode; + } + } + + if (ScaleMode != newScaleMode) + { + ScaleMode = newScaleMode; + + foreach (var view in _views) + { + view.ScaleMode = newScaleMode; + } + } + } + /// /// Synchronizes guest and host memory. /// This will overwrite the texture data with the texture data on the guest memory, if a CPU @@ -310,9 +482,14 @@ namespace Ryujinx.Graphics.Gpu.Image int modifiedCount = _context.PhysicalMemory.QueryModified(Address, Size, ResourceName.Texture, _modifiedRanges); - if (modifiedCount == 0 && _hasData) + if (_hasData) { - return; + if (modifiedCount == 0) + { + return; + } + + BlacklistScale(); } ReadOnlySpan data = _context.PhysicalMemory.GetSpan(Address, (int)Size); @@ -432,6 +609,7 @@ namespace Ryujinx.Graphics.Gpu.Image /// public void Flush() { + BlacklistScale(); _context.PhysicalMemory.Write(Address, GetTextureDataFromGpu()); } @@ -445,6 +623,7 @@ namespace Ryujinx.Graphics.Gpu.Image /// Host texture data private Span GetTextureDataFromGpu() { + BlacklistScale(); Span data = HostTexture.GetData(); if (Info.IsLinear) @@ -980,10 +1159,14 @@ namespace Ryujinx.Graphics.Gpu.Image /// The parent texture /// The new view texture information /// The new host texture - public void ReplaceView(Texture parent, TextureInfo info, ITexture hostTexture) + /// The first layer of the view + /// The first level of the view + public void ReplaceView(Texture parent, TextureInfo info, ITexture hostTexture, int firstLayer, int firstLevel) { ReplaceStorage(hostTexture); + _firstLayer = parent._firstLayer + firstLayer; + _firstLevel = parent._firstLevel + firstLevel; parent._viewStorage.AddView(this); SetInfo(info); @@ -1075,7 +1258,7 @@ namespace Ryujinx.Graphics.Gpu.Image // already deleted (views count is 0). if (_referenceCount == 0 && _views.Count == 0) { - DisposeTextures(); + Dispose(); } } @@ -1088,8 +1271,6 @@ namespace Ryujinx.Graphics.Gpu.Image _arrayViewTexture?.Dispose(); _arrayViewTexture = null; - - Disposed?.Invoke(this); } /// @@ -1098,6 +1279,8 @@ namespace Ryujinx.Graphics.Gpu.Image public void Dispose() { DisposeTextures(); + + Disposed?.Invoke(this); } } } \ No newline at end of file -- cgit v1.2.3