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 | |
| 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')
| -rw-r--r-- | Ryujinx.Graphics.Gpu/Image/Texture.cs | 217 | ||||
| -rw-r--r-- | Ryujinx.Graphics.Gpu/Image/TextureBindingInfo.cs | 16 | ||||
| -rw-r--r-- | Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs | 19 | ||||
| -rw-r--r-- | Ryujinx.Graphics.Gpu/Image/TextureManager.cs | 187 | ||||
| -rw-r--r-- | Ryujinx.Graphics.Gpu/Image/TexturePool.cs | 22 | ||||
| -rw-r--r-- | Ryujinx.Graphics.Gpu/Image/TextureScaleMode.cs | 14 | ||||
| -rw-r--r-- | Ryujinx.Graphics.Gpu/Image/TextureSearchFlags.cs | 3 |
7 files changed, 429 insertions, 49 deletions
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 /// </summary> public TextureInfo Info { get; private set; } + /// <summary> + /// Host scale factor. + /// </summary> + public float ScaleFactor { get; private set; } + + /// <summary> + /// Upscaling mode. Informs if a texture is scaled, or is eligible for scaling. + /// </summary> + 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 /// <param name="sizeInfo">Size information of the texture</param> /// <param name="firstLayer">The first layer of the texture, or 0 if the texture has no parent</param> /// <param name="firstLevel">The first mipmap level of the texture, or 0 if the texture has no parent</param> + /// <param name="scaleFactor">The floating point scale factor to initialize with</param> + /// <param name="scaleMode">The scale mode to initialize with</param> 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 /// <param name="context">GPU context that the texture belongs to</param> /// <param name="info">Texture information</param> /// <param name="sizeInfo">Size information of the texture</param> - public Texture(GpuContext context, TextureInfo info, SizeInfo sizeInfo) + /// <param name="scaleMode">The scale mode to initialize with. If scaled, the texture's data is loaded immediately and scaled up</param> + 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); + } } /// <summary> @@ -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); @@ -291,6 +320,149 @@ namespace Ryujinx.Graphics.Gpu.Image } /// <summary> + /// Blacklists this texture from being scaled. Resets its scale to 1 if needed. + /// </summary> + public void BlacklistScale() + { + ScaleMode = TextureScaleMode.Blacklisted; + SetScale(1f); + } + + /// <summary> + /// 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. + /// </summary> + /// <param name="other">The other texture</param> + 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); + } + } + + /// <summary> + /// 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. + /// </summary> + /// <param name="target">The texture array to copy to</param> + 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(); + } + } + + /// <summary> + /// 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. + /// </summary> + /// <param name="scale">The new scale factor for this texture</param> + 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; + } + } + } + + /// <summary> /// Synchronizes guest and host memory. /// This will overwrite the texture data with the texture data on the guest memory, if a CPU /// modification is detected. @@ -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<byte> data = _context.PhysicalMemory.GetSpan(Address, (int)Size); @@ -432,6 +609,7 @@ namespace Ryujinx.Graphics.Gpu.Image /// </summary> public void Flush() { + BlacklistScale(); _context.PhysicalMemory.Write(Address, GetTextureDataFromGpu()); } @@ -445,6 +623,7 @@ namespace Ryujinx.Graphics.Gpu.Image /// <returns>Host texture data</returns> private Span<byte> GetTextureDataFromGpu() { + BlacklistScale(); Span<byte> data = HostTexture.GetData(); if (Info.IsLinear) @@ -980,10 +1159,14 @@ namespace Ryujinx.Graphics.Gpu.Image /// <param name="parent">The parent texture</param> /// <param name="info">The new view texture information</param> /// <param name="hostTexture">The new host texture</param> - public void ReplaceView(Texture parent, TextureInfo info, ITexture hostTexture) + /// <param name="firstLayer">The first layer of the view</param> + /// <param name="firstLevel">The first level of the view</param> + 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); } /// <summary> @@ -1098,6 +1279,8 @@ namespace Ryujinx.Graphics.Gpu.Image public void Dispose() { DisposeTextures(); + + Disposed?.Invoke(this); } } }
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Image/TextureBindingInfo.cs b/Ryujinx.Graphics.Gpu/Image/TextureBindingInfo.cs index b4793f58..175f8863 100644 --- a/Ryujinx.Graphics.Gpu/Image/TextureBindingInfo.cs +++ b/Ryujinx.Graphics.Gpu/Image/TextureBindingInfo.cs @@ -1,4 +1,5 @@ using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Shader; namespace Ryujinx.Graphics.Gpu.Image { @@ -38,11 +39,17 @@ namespace Ryujinx.Graphics.Gpu.Image public int CbufOffset { get; } /// <summary> + /// Flags from the texture descriptor that indicate how the texture is used. + /// </summary> + public TextureUsageFlags Flags { get; } + + /// <summary> /// Constructs the texture binding information structure. /// </summary> /// <param name="target">The shader sampler target type</param> /// <param name="handle">The shader texture handle (read index into the texture constant buffer)</param> - public TextureBindingInfo(Target target, int handle) + /// <param name="flags">The texture's usage flags, indicating how it is used in the shader</param> + public TextureBindingInfo(Target target, int handle, TextureUsageFlags flags) { Target = target; Handle = handle; @@ -51,6 +58,8 @@ namespace Ryujinx.Graphics.Gpu.Image CbufSlot = 0; CbufOffset = 0; + + Flags = flags; } /// <summary> @@ -59,7 +68,8 @@ namespace Ryujinx.Graphics.Gpu.Image /// <param name="target">The shader sampler target type</param> /// <param name="cbufSlot">Constant buffer slot where the bindless texture handle is located</param> /// <param name="cbufOffset">Constant buffer offset of the bindless texture handle</param> - public TextureBindingInfo(Target target, int cbufSlot, int cbufOffset) + /// <param name="flags">The texture's usage flags, indicating how it is used in the shader</param> + public TextureBindingInfo(Target target, int cbufSlot, int cbufOffset, TextureUsageFlags flags) { Target = target; Handle = 0; @@ -68,6 +78,8 @@ namespace Ryujinx.Graphics.Gpu.Image CbufSlot = cbufSlot; CbufOffset = cbufOffset; + + Flags = flags; } } }
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs b/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs index 67254440..87b0f444 100644 --- a/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs +++ b/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs @@ -174,6 +174,8 @@ namespace Ryujinx.Graphics.Gpu.Image return; } + bool changed = false; + for (int index = 0; index < _textureBindings[stageIndex].Length; index++) { TextureBindingInfo binding = _textureBindings[stageIndex][index]; @@ -216,6 +218,11 @@ namespace Ryujinx.Graphics.Gpu.Image Texture texture = pool.Get(textureId); + if ((binding.Flags & TextureUsageFlags.ResScaleUnsupported) != 0) + { + texture?.BlacklistScale(); + } + ITexture hostTexture = texture?.GetTargetTexture(binding.Target); if (_textureState[stageIndex][index].Texture != hostTexture || _rebind) @@ -223,6 +230,8 @@ namespace Ryujinx.Graphics.Gpu.Image _textureState[stageIndex][index].Texture = hostTexture; _context.Renderer.Pipeline.SetTexture(index, stage, hostTexture); + + changed = true; } if (hostTexture != null && texture.Info.Target == Target.TextureBuffer) @@ -244,6 +253,11 @@ namespace Ryujinx.Graphics.Gpu.Image _context.Renderer.Pipeline.SetSampler(index, stage, hostSampler); } } + + if (changed) + { + _context.Renderer.Pipeline.UpdateRenderScale(stage, _textureBindings[stageIndex].Length); + } } /// <summary> @@ -269,6 +283,11 @@ namespace Ryujinx.Graphics.Gpu.Image Texture texture = pool.Get(textureId); + if ((binding.Flags & TextureUsageFlags.ResScaleUnsupported) != 0) + { + texture?.BlacklistScale(); + } + ITexture hostTexture = texture?.GetTargetTexture(binding.Target); if (_imageState[stageIndex][index].Texture != hostTexture || _rebind) 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)) diff --git a/Ryujinx.Graphics.Gpu/Image/TexturePool.cs b/Ryujinx.Graphics.Gpu/Image/TexturePool.cs index 639fa69d..1494a142 100644 --- a/Ryujinx.Graphics.Gpu/Image/TexturePool.cs +++ b/Ryujinx.Graphics.Gpu/Image/TexturePool.cs @@ -174,7 +174,7 @@ namespace Ryujinx.Graphics.Gpu.Image swizzleB, swizzleA); - if (IsDepthStencil(formatInfo.Format)) + if (formatInfo.Format.IsDepthOrStencil()) { swizzleR = SwizzleComponent.Red; swizzleG = SwizzleComponent.Red; @@ -264,26 +264,6 @@ namespace Ryujinx.Graphics.Gpu.Image } /// <summary> - /// Checks if the texture format is a depth, stencil or depth-stencil format. - /// </summary> - /// <param name="format">Texture format</param> - /// <returns>True if the format is a depth, stencil or depth-stencil format, false otherwise</returns> - private static bool IsDepthStencil(Format format) - { - switch (format) - { - case Format.D16Unorm: - case Format.D24UnormS8Uint: - case Format.D24X8Unorm: - case Format.D32Float: - case Format.D32FloatS8Uint: - return true; - } - - return false; - } - - /// <summary> /// Decrements the reference count of the texture. /// This indicates that the texture pool is not using it anymore. /// </summary> diff --git a/Ryujinx.Graphics.Gpu/Image/TextureScaleMode.cs b/Ryujinx.Graphics.Gpu/Image/TextureScaleMode.cs new file mode 100644 index 00000000..2c9e431d --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Image/TextureScaleMode.cs @@ -0,0 +1,14 @@ +namespace Ryujinx.Graphics.Gpu.Image +{ + /// <summary> + /// The scale mode for a given texture. + /// Blacklisted textures cannot be scaled, Eligible textures have not been scaled yet, + /// and Scaled textures have been scaled already. + /// </summary> + enum TextureScaleMode + { + Eligible = 0, + Scaled = 1, + Blacklisted = 2 + } +} diff --git a/Ryujinx.Graphics.Gpu/Image/TextureSearchFlags.cs b/Ryujinx.Graphics.Gpu/Image/TextureSearchFlags.cs index daf726f1..33ac775c 100644 --- a/Ryujinx.Graphics.Gpu/Image/TextureSearchFlags.cs +++ b/Ryujinx.Graphics.Gpu/Image/TextureSearchFlags.cs @@ -11,6 +11,7 @@ namespace Ryujinx.Graphics.Gpu.Image None = 0, IgnoreMs = 1 << 0, Strict = 1 << 1 | Sampler, - Sampler = 1 << 2 + Sampler = 1 << 2, + WithUpscale = 1 << 3 } }
\ No newline at end of file |
