diff --git a/Ryujinx.Graphics.GAL/Extents2D.cs b/Ryujinx.Graphics.GAL/Extents2D.cs
index e9e26af411..05b0ce6395 100644
--- a/Ryujinx.Graphics.GAL/Extents2D.cs
+++ b/Ryujinx.Graphics.GAL/Extents2D.cs
@@ -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));
+ }
}
}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Engine/MethodCopyTexture.cs b/Ryujinx.Graphics.Gpu/Engine/MethodCopyTexture.cs
index 430b733ee8..173f5fe479 100644
--- a/Ryujinx.Graphics.Gpu/Engine/MethodCopyTexture.cs
+++ b/Ryujinx.Graphics.Gpu/Engine/MethodCopyTexture.cs
@@ -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)),
diff --git a/Ryujinx.Graphics.Gpu/Image/Texture.cs b/Ryujinx.Graphics.Gpu/Image/Texture.cs
index 21418a4b27..7fb41572db 100644
--- a/Ryujinx.Graphics.Gpu/Image/Texture.cs
+++ b/Ryujinx.Graphics.Gpu/Image/Texture.cs
@@ -430,48 +430,6 @@ namespace Ryujinx.Graphics.Gpu.Image
}
}
- ///
- /// 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, 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();
- }
- }
-
///
/// Copy the host texture to a scaled one. If a texture is not provided, create it with the given scale.
///
@@ -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;
}
diff --git a/Ryujinx.Graphics.Gpu/Image/TextureManager.cs b/Ryujinx.Graphics.Gpu/Image/TextureManager.cs
index f1d31f4f44..8e64ca61f8 100644
--- a/Ryujinx.Graphics.Gpu/Image/TextureManager.cs
+++ b/Ryujinx.Graphics.Gpu/Image/TextureManager.cs
@@ -387,7 +387,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// True if eligible
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);
}
///
@@ -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)
diff --git a/Ryujinx.Graphics.OpenGL/Image/TextureCopy.cs b/Ryujinx.Graphics.OpenGL/Image/TextureCopy.cs
index 74832dd8cf..20a3b91499 100644
--- a/Ryujinx.Graphics.OpenGL/Image/TextureCopy.cs
+++ b/Ryujinx.Graphics.OpenGL/Image/TextureCopy.cs
@@ -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)