Allow copy destination to have a different scale from source (#1711)

* Allow copy destination to have a different scale from source

Will result in more scaled copy destinations, but allows scaling in some games that copy textures to the output framebuffer.

* Support copying multiple levels/layers

Uses glFramebufferTextureLayer to copy multiple layers, copies levels individually (and scales the regions).

Remove CopyArrayScaled, since the backend copy handles it now.
This commit is contained in:
riperiperi 2020-11-20 20:14:45 +00:00 committed by GitHub
parent cf7044e37b
commit 9493cdfe55
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 99 additions and 98 deletions

View file

@ -1,3 +1,5 @@
using Ryujinx.Common;
namespace Ryujinx.Graphics.GAL
{
public struct Extents2D
@ -14,5 +16,16 @@ namespace Ryujinx.Graphics.GAL
X2 = x2;
Y2 = y2;
}
public Extents2D Reduce(int level)
{
int div = 1 << level;
return new Extents2D(
X1 >> level,
Y1 >> level,
BitUtils.DivRoundUp(X2, div),
BitUtils.DivRoundUp(Y2, div));
}
}
}

View file

@ -71,12 +71,8 @@ namespace Ryujinx.Graphics.Gpu.Engine
return;
}
if (srcTexture.ScaleFactor != dstTexture.ScaleFactor)
{
srcTexture.PropagateScale(dstTexture);
}
float scale = srcTexture.ScaleFactor; // src and dest scales are identical now.
float scale = srcTexture.ScaleFactor;
float dstScale = dstTexture.ScaleFactor;
Extents2D srcRegion = new Extents2D(
(int)Math.Ceiling(scale * (srcX1 / srcTexture.Info.SamplesInX)),
@ -85,10 +81,10 @@ namespace Ryujinx.Graphics.Gpu.Engine
(int)Math.Ceiling(scale * (srcY2 / srcTexture.Info.SamplesInY)));
Extents2D dstRegion = new Extents2D(
(int)Math.Ceiling(scale * (dstX1 / dstTexture.Info.SamplesInX)),
(int)Math.Ceiling(scale * (dstY1 / dstTexture.Info.SamplesInY)),
(int)Math.Ceiling(scale * (dstX2 / dstTexture.Info.SamplesInX)),
(int)Math.Ceiling(scale * (dstY2 / dstTexture.Info.SamplesInY)));
(int)Math.Ceiling(dstScale * (dstX1 / dstTexture.Info.SamplesInX)),
(int)Math.Ceiling(dstScale * (dstY1 / dstTexture.Info.SamplesInY)),
(int)Math.Ceiling(dstScale * (dstX2 / dstTexture.Info.SamplesInX)),
(int)Math.Ceiling(dstScale * (dstY2 / dstTexture.Info.SamplesInY)));
bool linearFilter = control.UnpackLinearFilter();
@ -107,10 +103,7 @@ namespace Ryujinx.Graphics.Gpu.Engine
srcCopyTexture.Height++;
srcTexture = TextureManager.FindOrCreateTexture(srcCopyTexture, srcCopyTextureFormat, srcTexture.ScaleMode == TextureScaleMode.Scaled, srcHint);
if (srcTexture.ScaleFactor != dstTexture.ScaleFactor)
{
srcTexture.PropagateScale(dstTexture);
}
scale = srcTexture.ScaleFactor;
srcRegion = new Extents2D(
(int)Math.Ceiling(scale * ((srcX1 / srcTexture.Info.SamplesInX) - srcTexture.Info.Width)),

View file

@ -430,48 +430,6 @@ namespace Ryujinx.Graphics.Gpu.Image
}
}
/// <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, ScaleFactor);
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.Release();
to.Release();
}
}
/// <summary>
/// Copy the host texture to a scaled one. If a texture is not provided, create it with the given scale.
/// </summary>
@ -486,14 +444,7 @@ namespace Ryujinx.Graphics.Gpu.Image
storage = _context.Renderer.CreateTexture(createInfo, scale);
}
if (Info.Target == Target.Texture2DArray)
{
CopyArrayScaled(storage);
}
else
{
HostTexture.CopyTo(storage, new Extents2D(0, 0, HostTexture.Width, HostTexture.Height), new Extents2D(0, 0, storage.Width, storage.Height), true);
}
HostTexture.CopyTo(storage, new Extents2D(0, 0, HostTexture.Width, HostTexture.Height), new Extents2D(0, 0, storage.Width, storage.Height), true);
return storage;
}

View file

@ -387,7 +387,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <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);
return (info.Target == Target.Texture2D || info.Target == Target.Texture2DArray) && !info.FormatInfo.IsCompressed && UpscaleSafeMode(info);
}
/// <summary>
@ -401,6 +401,13 @@ namespace Ryujinx.Graphics.Gpu.Image
// 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.Levels > 3)
{
// Textures with more than 3 levels are likely to be game textures, rather than render textures.
// Small textures with full mips are likely to be removed by the next check.
return false;
}
if (!(info.FormatInfo.Format.IsDepthOrStencil() || info.FormatInfo.Components == 1))
{
// Discount square textures that aren't depth-stencil like. (excludes game textures, cubemap faces, most 3D texture LUT, texture atlas)

View file

@ -34,38 +34,61 @@ namespace Ryujinx.Graphics.OpenGL.Image
GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, GetSrcFramebufferLazy());
GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, GetDstFramebufferLazy());
Attach(FramebufferTarget.ReadFramebuffer, src.Format, srcConverted.Handle);
Attach(FramebufferTarget.DrawFramebuffer, dst.Format, dst.Handle);
int levels = Math.Min(src.Info.Levels, dst.Info.Levels);
int layers = Math.Min(src.Info.GetLayers(), dst.Info.GetLayers());
ClearBufferMask mask = GetMask(src.Format);
if ((mask & (ClearBufferMask.DepthBufferBit | ClearBufferMask.StencilBufferBit)) != 0 || src.Format.IsInteger())
for (int level = 0; level < levels; level++)
{
linearFilter = false;
for (int layer = 0; layer < layers; layer++)
{
if (layers > 1)
{
Attach(FramebufferTarget.ReadFramebuffer, src.Format, srcConverted.Handle, level, layer);
Attach(FramebufferTarget.DrawFramebuffer, dst.Format, dst.Handle, level, layer);
}
else
{
Attach(FramebufferTarget.ReadFramebuffer, src.Format, srcConverted.Handle, level);
Attach(FramebufferTarget.DrawFramebuffer, dst.Format, dst.Handle, level);
}
ClearBufferMask mask = GetMask(src.Format);
if ((mask & (ClearBufferMask.DepthBufferBit | ClearBufferMask.StencilBufferBit)) != 0 || src.Format.IsInteger())
{
linearFilter = false;
}
BlitFramebufferFilter filter = linearFilter
? BlitFramebufferFilter.Linear
: BlitFramebufferFilter.Nearest;
GL.ReadBuffer(ReadBufferMode.ColorAttachment0);
GL.DrawBuffer(DrawBufferMode.ColorAttachment0);
GL.Disable(EnableCap.RasterizerDiscard);
GL.Disable(IndexedEnableCap.ScissorTest, 0);
GL.BlitFramebuffer(
srcRegion.X1,
srcRegion.Y1,
srcRegion.X2,
srcRegion.Y2,
dstRegion.X1,
dstRegion.Y1,
dstRegion.X2,
dstRegion.Y2,
mask,
filter);
}
if (level < levels - 1)
{
srcRegion = srcRegion.Reduce(1);
dstRegion = dstRegion.Reduce(1);
}
}
BlitFramebufferFilter filter = linearFilter
? BlitFramebufferFilter.Linear
: BlitFramebufferFilter.Nearest;
GL.ReadBuffer(ReadBufferMode.ColorAttachment0);
GL.DrawBuffer(DrawBufferMode.ColorAttachment0);
GL.Disable(EnableCap.RasterizerDiscard);
GL.Disable(IndexedEnableCap.ScissorTest, 0);
GL.BlitFramebuffer(
srcRegion.X1,
srcRegion.Y1,
srcRegion.X2,
srcRegion.Y2,
dstRegion.X1,
dstRegion.Y1,
dstRegion.X2,
dstRegion.Y2,
mask,
filter);
Attach(FramebufferTarget.ReadFramebuffer, src.Format, 0);
Attach(FramebufferTarget.DrawFramebuffer, dst.Format, 0);
@ -191,26 +214,40 @@ namespace Ryujinx.Graphics.OpenGL.Image
}
}
private static void Attach(FramebufferTarget target, Format format, int handle)
private static FramebufferAttachment AttachmentForFormat(Format format)
{
if (format == Format.D24UnormS8Uint || format == Format.D32FloatS8Uint)
{
GL.FramebufferTexture(target, FramebufferAttachment.DepthStencilAttachment, handle, 0);
return FramebufferAttachment.DepthStencilAttachment;
}
else if (IsDepthOnly(format))
{
GL.FramebufferTexture(target, FramebufferAttachment.DepthAttachment, handle, 0);
return FramebufferAttachment.DepthAttachment;
}
else if (format == Format.S8Uint)
{
GL.FramebufferTexture(target, FramebufferAttachment.StencilAttachment, handle, 0);
return FramebufferAttachment.StencilAttachment;
}
else
{
GL.FramebufferTexture(target, FramebufferAttachment.ColorAttachment0, handle, 0);
return FramebufferAttachment.ColorAttachment0;
}
}
private static void Attach(FramebufferTarget target, Format format, int handle, int level = 0)
{
FramebufferAttachment attachment = AttachmentForFormat(format);
GL.FramebufferTexture(target, attachment, handle, level);
}
private static void Attach(FramebufferTarget target, Format format, int handle, int level, int layer)
{
FramebufferAttachment attachment = AttachmentForFormat(format);
GL.FramebufferTextureLayer(target, attachment, handle, level, layer);
}
private static ClearBufferMask GetMask(Format format)
{
if (format == Format.D24UnormS8Uint || format == Format.D32FloatS8Uint)