From 02872833b6da02a20e331305caf05f722e6c8e68 Mon Sep 17 00:00:00 2001 From: riperiperi Date: Tue, 10 Nov 2020 00:41:13 +0000 Subject: [PATCH] Size hints for copy regions and viewport dimensions to avoid data loss (#1686) * Size hints for copy regions and viewport dimensions to avoid data loss * Reword comment. * Use info for the rule rather than calculating aligned size. * Reorder min/max, remove spaces --- .../Engine/MethodCopyTexture.cs | 44 +++++----- Ryujinx.Graphics.Gpu/Engine/Methods.cs | 8 +- Ryujinx.Graphics.Gpu/Image/Texture.cs | 8 ++ Ryujinx.Graphics.Gpu/Image/TextureManager.cs | 80 +++++++++++++------ Ryujinx.Graphics.Gpu/Image/TexturePool.cs | 16 ++++ 5 files changed, 111 insertions(+), 45 deletions(-) diff --git a/Ryujinx.Graphics.Gpu/Engine/MethodCopyTexture.cs b/Ryujinx.Graphics.Gpu/Engine/MethodCopyTexture.cs index 4856172a7..324946fd0 100644 --- a/Ryujinx.Graphics.Gpu/Engine/MethodCopyTexture.cs +++ b/Ryujinx.Graphics.Gpu/Engine/MethodCopyTexture.cs @@ -1,6 +1,7 @@ using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.Gpu.Image; using Ryujinx.Graphics.Gpu.State; +using Ryujinx.Graphics.Texture; using System; namespace Ryujinx.Graphics.Gpu.Engine @@ -19,9 +20,30 @@ namespace Ryujinx.Graphics.Gpu.Engine var dstCopyTexture = state.Get(MethodOffset.CopyDstTexture); var srcCopyTexture = state.Get(MethodOffset.CopySrcTexture); + var region = state.Get(MethodOffset.CopyRegion); + + var control = state.Get(MethodOffset.CopyTextureControl); + + int srcX1 = (int)(region.SrcXF >> 32); + int srcY1 = (int)(region.SrcYF >> 32); + + int srcX2 = (int)((region.SrcXF + region.SrcWidthRF * region.DstWidth) >> 32); + int srcY2 = (int)((region.SrcYF + region.SrcHeightRF * region.DstHeight) >> 32); + + int dstX1 = region.DstX; + int dstY1 = region.DstY; + + int dstX2 = region.DstX + region.DstWidth; + int dstY2 = region.DstY + region.DstHeight; + + // The source and destination textures should at least be as big as the region being requested. + // The hints will only resize within alignment constraints, so out of bound copies won't resize in most cases. + var srcHint = new Size(srcX2, srcY2, 1); + var dstHint = new Size(dstX2, dstY2, 1); + var srcCopyTextureFormat = srcCopyTexture.Format.Convert(); - Texture srcTexture = TextureManager.FindOrCreateTexture(srcCopyTexture, srcCopyTextureFormat); + Texture srcTexture = TextureManager.FindOrCreateTexture(srcCopyTexture, srcCopyTextureFormat, true, srcHint); if (srcTexture == null) { @@ -42,7 +64,7 @@ namespace Ryujinx.Graphics.Gpu.Engine dstCopyTextureFormat = dstCopyTexture.Format.Convert(); } - Texture dstTexture = TextureManager.FindOrCreateTexture(dstCopyTexture, dstCopyTextureFormat, srcTexture.ScaleMode == TextureScaleMode.Scaled); + Texture dstTexture = TextureManager.FindOrCreateTexture(dstCopyTexture, dstCopyTextureFormat, srcTexture.ScaleMode == TextureScaleMode.Scaled, dstHint); if (dstTexture == null) { @@ -54,22 +76,6 @@ namespace Ryujinx.Graphics.Gpu.Engine srcTexture.PropagateScale(dstTexture); } - var control = state.Get(MethodOffset.CopyTextureControl); - - var region = state.Get(MethodOffset.CopyRegion); - - int srcX1 = (int)(region.SrcXF >> 32); - int srcY1 = (int)(region.SrcYF >> 32); - - int srcX2 = (int)((region.SrcXF + region.SrcWidthRF * region.DstWidth) >> 32); - int srcY2 = (int)((region.SrcYF + region.SrcHeightRF * region.DstHeight) >> 32); - - int dstX1 = region.DstX; - int dstY1 = region.DstY; - - int dstX2 = region.DstX + region.DstWidth; - int dstY2 = region.DstY + region.DstHeight; - float scale = srcTexture.ScaleFactor; // src and dest scales are identical now. Extents2D srcRegion = new Extents2D( @@ -100,7 +106,7 @@ namespace Ryujinx.Graphics.Gpu.Engine { srcCopyTexture.Height++; - srcTexture = TextureManager.FindOrCreateTexture(srcCopyTexture, srcCopyTextureFormat, srcTexture.ScaleMode == TextureScaleMode.Scaled); + srcTexture = TextureManager.FindOrCreateTexture(srcCopyTexture, srcCopyTextureFormat, srcTexture.ScaleMode == TextureScaleMode.Scaled, srcHint); if (srcTexture.ScaleFactor != dstTexture.ScaleFactor) { srcTexture.PropagateScale(dstTexture); diff --git a/Ryujinx.Graphics.Gpu/Engine/Methods.cs b/Ryujinx.Graphics.Gpu/Engine/Methods.cs index ef073a25a..cab125b50 100644 --- a/Ryujinx.Graphics.Gpu/Engine/Methods.cs +++ b/Ryujinx.Graphics.Gpu/Engine/Methods.cs @@ -5,6 +5,7 @@ using Ryujinx.Graphics.Gpu.Memory; using Ryujinx.Graphics.Gpu.Shader; using Ryujinx.Graphics.Gpu.State; using Ryujinx.Graphics.Shader; +using Ryujinx.Graphics.Texture; using System; using System.Linq; using System.Runtime.InteropServices; @@ -354,6 +355,9 @@ namespace Ryujinx.Graphics.Gpu.Engine int samplesInX = msaaMode.SamplesInX(); int samplesInY = msaaMode.SamplesInY(); + var extents = state.Get(MethodOffset.ViewportExtents, 0); + Size sizeHint = new Size(extents.X + extents.Width, extents.Y + extents.Height, 1); + bool changedScale = false; for (int index = 0; index < Constants.TotalRenderTargets; index++) @@ -369,7 +373,7 @@ namespace Ryujinx.Graphics.Gpu.Engine continue; } - Texture color = TextureManager.FindOrCreateTexture(colorState, samplesInX, samplesInY); + Texture color = TextureManager.FindOrCreateTexture(colorState, samplesInX, samplesInY, sizeHint); changedScale |= TextureManager.SetRenderTargetColor(index, color); @@ -388,7 +392,7 @@ namespace Ryujinx.Graphics.Gpu.Engine var dsState = state.Get(MethodOffset.RtDepthStencilState); var dsSize = state.Get(MethodOffset.RtDepthStencilSize); - depthStencil = TextureManager.FindOrCreateTexture(dsState, dsSize, samplesInX, samplesInY); + depthStencil = TextureManager.FindOrCreateTexture(dsState, dsSize, samplesInX, samplesInY, sizeHint); } changedScale |= TextureManager.SetRenderTargetDepthStencil(depthStencil); diff --git a/Ryujinx.Graphics.Gpu/Image/Texture.cs b/Ryujinx.Graphics.Gpu/Image/Texture.cs index b1d6af9b4..5b3a01c57 100644 --- a/Ryujinx.Graphics.Gpu/Image/Texture.cs +++ b/Ryujinx.Graphics.Gpu/Image/Texture.cs @@ -50,6 +50,12 @@ namespace Ryujinx.Graphics.Gpu.Image /// public bool IsModified { get; internal set; } + /// + /// Set when a texture has been changed size. This indicates that it may need to be + /// changed again when obtained as a sampler. + /// + public bool ChangedSize { get; internal set; } + private int _depth; private int _layers; private int _firstLayer; @@ -353,6 +359,8 @@ namespace Ryujinx.Graphics.Gpu.Image /// The new texture depth (for 3D textures) or layers (for layered textures) private void RecreateStorageOrView(int width, int height, int depthOrLayers) { + ChangedSize = true; + SetInfo(new TextureInfo( Info.Address, width, diff --git a/Ryujinx.Graphics.Gpu/Image/TextureManager.cs b/Ryujinx.Graphics.Gpu/Image/TextureManager.cs index 0694f3e3c..f1d31f4f4 100644 --- a/Ryujinx.Graphics.Gpu/Image/TextureManager.cs +++ b/Ryujinx.Graphics.Gpu/Image/TextureManager.cs @@ -453,8 +453,9 @@ namespace Ryujinx.Graphics.Gpu.Image /// Copy texture to find or create /// Format information of the copy texture /// Indicates if the texture should be scaled from the start + /// A hint indicating the minimum used size for the texture /// The texture - public Texture FindOrCreateTexture(CopyTexture copyTexture, FormatInfo formatInfo, bool preferScaling = true) + public Texture FindOrCreateTexture(CopyTexture copyTexture, FormatInfo formatInfo, bool preferScaling = true, Size? sizeHint = null) { ulong address = _context.MemoryManager.Translate(copyTexture.Address.Pack()); @@ -500,7 +501,7 @@ namespace Ryujinx.Graphics.Gpu.Image flags |= TextureSearchFlags.WithUpscale; } - Texture texture = FindOrCreateTexture(info, flags); + Texture texture = FindOrCreateTexture(info, flags, sizeHint); texture.SynchronizeMemory(); @@ -513,8 +514,9 @@ namespace Ryujinx.Graphics.Gpu.Image /// Color buffer texture to find or create /// Number of samples in the X direction, for MSAA /// Number of samples in the Y direction, for MSAA + /// A hint indicating the minimum used size for the texture /// The texture - public Texture FindOrCreateTexture(RtColorState colorState, int samplesInX, int samplesInY) + public Texture FindOrCreateTexture(RtColorState colorState, int samplesInX, int samplesInY, Size sizeHint) { ulong address = _context.MemoryManager.Translate(colorState.Address.Pack()); @@ -583,7 +585,7 @@ namespace Ryujinx.Graphics.Gpu.Image target, formatInfo); - Texture texture = FindOrCreateTexture(info, TextureSearchFlags.WithUpscale); + Texture texture = FindOrCreateTexture(info, TextureSearchFlags.WithUpscale, sizeHint); texture.SynchronizeMemory(); @@ -597,8 +599,9 @@ namespace Ryujinx.Graphics.Gpu.Image /// Size of the depth-stencil texture /// Number of samples in the X direction, for MSAA /// Number of samples in the Y direction, for MSAA + /// A hint indicating the minimum used size for the texture /// The texture - public Texture FindOrCreateTexture(RtDepthStencilState dsState, Size3D size, int samplesInX, int samplesInY) + public Texture FindOrCreateTexture(RtDepthStencilState dsState, Size3D size, int samplesInX, int samplesInY, Size sizeHint) { ulong address = _context.MemoryManager.Translate(dsState.Address.Pack()); @@ -632,7 +635,7 @@ namespace Ryujinx.Graphics.Gpu.Image target, formatInfo); - Texture texture = FindOrCreateTexture(info, TextureSearchFlags.WithUpscale); + Texture texture = FindOrCreateTexture(info, TextureSearchFlags.WithUpscale, sizeHint); texture.SynchronizeMemory(); @@ -644,8 +647,9 @@ namespace Ryujinx.Graphics.Gpu.Image /// /// Texture information of the texture to be found or created /// The texture search flags, defines texture comparison rules + /// A hint indicating the minimum used size for the texture /// The texture - public Texture FindOrCreateTexture(TextureInfo info, TextureSearchFlags flags = TextureSearchFlags.None) + public Texture FindOrCreateTexture(TextureInfo info, TextureSearchFlags flags = TextureSearchFlags.None, Size? sizeHint = null) { bool isSamplerTexture = (flags & TextureSearchFlags.ForSampler) != 0; @@ -678,14 +682,8 @@ namespace Ryujinx.Graphics.Gpu.Image // deletion. _cache.Lift(overlap); } - else if (!TextureCompatibility.SizeMatches(overlap.Info, info)) - { - // If this is used for sampling, the size must match, - // otherwise the shader would sample garbage data. - // To fix that, we create a new texture with the correct - // size, and copy the data from the old one to the new one. - overlap.ChangeSize(info.Width, info.Height, info.DepthOrLayers); - } + + ChangeSizeIfNeeded(info, overlap, isSamplerTexture, sizeHint); overlap.SynchronizeMemory(); @@ -741,25 +739,21 @@ namespace Ryujinx.Graphics.Gpu.Image if (overlapCompatibility == TextureViewCompatibility.Full) { + TextureInfo oInfo = AdjustSizes(overlap, info, firstLevel); + if (!isSamplerTexture) { - info = AdjustSizes(overlap, info, firstLevel); + info = oInfo; } - texture = overlap.CreateView(info, sizeInfo, firstLayer, firstLevel); + texture = overlap.CreateView(oInfo, sizeInfo, firstLayer, firstLevel); if (overlap.IsModified) { texture.SignalModified(); } - // The size only matters (and is only really reliable) when the - // texture is used on a sampler, because otherwise the size will be - // aligned. - if (!TextureCompatibility.SizeMatches(overlap.Info, info, firstLevel) && isSamplerTexture) - { - texture.ChangeSize(info.Width, info.Height, info.DepthOrLayers); - } + ChangeSizeIfNeeded(info, texture, isSamplerTexture, sizeHint); break; } @@ -911,6 +905,44 @@ namespace Ryujinx.Graphics.Gpu.Image return texture; } + /// + /// Changes a texture's size to match the desired size for samplers, + /// or increases a texture's size to fit the region indicated by a size hint. + /// + /// The desired texture info + /// The texture to resize + /// True if the texture will be used for a sampler, false otherwise + /// A hint indicating the minimum used size for the texture + private void ChangeSizeIfNeeded(TextureInfo info, Texture texture, bool isSamplerTexture, Size? sizeHint) + { + if (isSamplerTexture) + { + // If this is used for sampling, the size must match, + // otherwise the shader would sample garbage data. + // To fix that, we create a new texture with the correct + // size, and copy the data from the old one to the new one. + + if (!TextureCompatibility.SizeMatches(texture.Info, info)) + { + texture.ChangeSize(info.Width, info.Height, info.DepthOrLayers); + } + } + else if (sizeHint != null) + { + // A size hint indicates that data will be used within that range, at least. + // If the texture is smaller than the size hint, it must be enlarged to meet it. + // The maximum size is provided by the requested info, which generally has an aligned size. + + int width = Math.Max(texture.Info.Width, Math.Min(sizeHint.Value.Width, info.Width)); + int height = Math.Max(texture.Info.Height, Math.Min(sizeHint.Value.Height, info.Height)); + + if (texture.Info.Width != width || texture.Info.Height != height) + { + texture.ChangeSize(width, height, info.DepthOrLayers); + } + } + } + /// /// Tries to find an existing texture matching the given buffer copy destination. If none is found, returns null. /// diff --git a/Ryujinx.Graphics.Gpu/Image/TexturePool.cs b/Ryujinx.Graphics.Gpu/Image/TexturePool.cs index 59f2e9013..e26dc501d 100644 --- a/Ryujinx.Graphics.Gpu/Image/TexturePool.cs +++ b/Ryujinx.Graphics.Gpu/Image/TexturePool.cs @@ -67,6 +67,22 @@ namespace Ryujinx.Graphics.Gpu.Image } else { + if (texture.ChangedSize) + { + // Texture changed size at one point - it may be a different size than the sampler expects. + // This can be triggered when the size is changed by a size hint on copy or draw, but the texture has been sampled before. + + TextureDescriptor descriptor = GetDescriptor(id); + + int width = descriptor.UnpackWidth(); + int height = descriptor.UnpackHeight(); + + if (texture.Info.Width != width || texture.Info.Height != height) + { + texture.ChangeSize(width, height, texture.Info.DepthOrLayers); + } + } + // Memory is automatically synchronized on texture creation. texture.SynchronizeMemory(); }