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.
This commit is contained in:
parent
43b78ae157
commit
484eb645ae
49 changed files with 1163 additions and 131 deletions
|
@ -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);
|
||||
|
||||
|
@ -290,6 +319,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
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
|
@ -37,12 +38,18 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -39,6 +39,11 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
private readonly HashSet<Texture> _modified;
|
||||
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>
|
||||
|
@ -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>
|
||||
|
@ -262,12 +361,59 @@ 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))
|
||||
|
|
|
@ -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;
|
||||
|
@ -263,26 +263,6 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
component == SwizzleComponent.Green;
|
||||
}
|
||||
|
||||
/// <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.
|
||||
|
|
14
Ryujinx.Graphics.Gpu/Image/TextureScaleMode.cs
Normal file
14
Ryujinx.Graphics.Gpu/Image/TextureScaleMode.cs
Normal file
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue