From 96cf242bcf168b9f9e6a1e27200529466217f396 Mon Sep 17 00:00:00 2001 From: gdkchan Date: Wed, 8 Feb 2023 04:48:09 -0300 Subject: [PATCH] Handle mismatching texture size with copy dependencies (#4364) * Handle mismatching texture size with copy dependencies * Create copy and render textures with the minimum possible size * Only align width for comparisons, assume that height is always exact * Fix IsExactMatch size check * Allow sampler and copy textures to match textures with larger width * Delete texture ChangeSize related code * Move AdjustSize to TextureInfo and give it a better name, adjust usages * Fix GetMinimumWidthInGob when minimumWidth > width * Only update render targets that are actually cleared for clear Avoids creating textures with incorrect sizes * Delete UpdateRenderTargetState method that is not needed anymore Clears now only ever sets the render targets that will be cleared rather than all of them --- .../Engine/Threed/DrawManager.cs | 32 +-- .../Engine/Threed/RenderTargetUpdateFlags.cs | 41 ++++ .../Engine/Threed/StateUpdater.cs | 15 +- .../Engine/Threed/ThreedClass.cs | 7 +- Ryujinx.Graphics.Gpu/Image/Texture.cs | 143 ++----------- Ryujinx.Graphics.Gpu/Image/TextureCache.cs | 192 +++++------------- .../Image/TextureCompatibility.cs | 126 +++++------- Ryujinx.Graphics.Gpu/Image/TextureInfo.cs | 85 ++++++++ Ryujinx.Graphics.Gpu/Image/TextureManager.cs | 16 -- Ryujinx.Graphics.Gpu/Image/TexturePool.cs | 17 +- .../Image/TextureSearchFlags.cs | 1 - Ryujinx.Graphics.Gpu/Window.cs | 2 +- 12 files changed, 271 insertions(+), 406 deletions(-) create mode 100644 Ryujinx.Graphics.Gpu/Engine/Threed/RenderTargetUpdateFlags.cs diff --git a/Ryujinx.Graphics.Gpu/Engine/Threed/DrawManager.cs b/Ryujinx.Graphics.Gpu/Engine/Threed/DrawManager.cs index 0f249512b0..61f227d936 100644 --- a/Ryujinx.Graphics.Gpu/Engine/Threed/DrawManager.cs +++ b/Ryujinx.Graphics.Gpu/Engine/Threed/DrawManager.cs @@ -725,10 +725,25 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed return; } + bool clearDepth = (argument & 1) != 0; + bool clearStencil = (argument & 2) != 0; + uint componentMask = (uint)((argument >> 2) & 0xf); int index = (argument >> 6) & 0xf; int layer = (argument >> 10) & 0x3ff; - engine.UpdateRenderTargetState(useControl: false, layered: layer != 0 || layerCount > 1, singleUse: index); + RenderTargetUpdateFlags updateFlags = RenderTargetUpdateFlags.SingleColor; + + if (layer != 0 || layerCount > 1) + { + updateFlags |= RenderTargetUpdateFlags.Layered; + } + + if (clearDepth || clearStencil) + { + updateFlags |= RenderTargetUpdateFlags.UpdateDepthStencil; + } + + engine.UpdateRenderTargetState(updateFlags, singleUse: componentMask != 0 ? index : -1); // If there is a mismatch on the host clip region and the one explicitly defined by the guest // on the screen scissor state, then we need to force only one texture to be bound to avoid @@ -788,18 +803,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed _context.Renderer.Pipeline.SetScissors(scissors); } - if (clipMismatch) - { - _channel.TextureManager.UpdateRenderTarget(index); - } - else - { - _channel.TextureManager.UpdateRenderTargets(); - } - - bool clearDepth = (argument & 1) != 0; - bool clearStencil = (argument & 2) != 0; - uint componentMask = (uint)((argument >> 2) & 0xf); + _channel.TextureManager.UpdateRenderTargets(); if (componentMask != 0) { @@ -841,7 +845,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed engine.UpdateScissorState(); } - engine.UpdateRenderTargetState(useControl: true); + engine.UpdateRenderTargetState(RenderTargetUpdateFlags.UpdateAll); if (renderEnable == ConditionalRenderEnabled.Host) { diff --git a/Ryujinx.Graphics.Gpu/Engine/Threed/RenderTargetUpdateFlags.cs b/Ryujinx.Graphics.Gpu/Engine/Threed/RenderTargetUpdateFlags.cs new file mode 100644 index 0000000000..cf2e818ceb --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Engine/Threed/RenderTargetUpdateFlags.cs @@ -0,0 +1,41 @@ +using System; + +namespace Ryujinx.Graphics.Gpu.Engine.Threed +{ + /// + /// Flags indicating how the render targets should be updated. + /// + [Flags] + enum RenderTargetUpdateFlags + { + /// + /// No flags. + /// + None = 0, + + /// + /// Get render target index from the control register. + /// + UseControl = 1 << 0, + + /// + /// Indicates that all render targets are 2D array textures. + /// + Layered = 1 << 1, + + /// + /// Indicates that only a single color target will be used. + /// + SingleColor = 1 << 2, + + /// + /// Indicates that the depth-stencil target will be used. + /// + UpdateDepthStencil = 1 << 3, + + /// + /// Default update flags for draw. + /// + UpdateAll = UseControl | UpdateDepthStencil + } +} diff --git a/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs b/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs index 9b59009cfb..9b58e0148a 100644 --- a/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs +++ b/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs @@ -402,20 +402,23 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed /// private void UpdateRenderTargetState() { - UpdateRenderTargetState(true); + UpdateRenderTargetState(RenderTargetUpdateFlags.UpdateAll); } /// /// Updates render targets (color and depth-stencil buffers) based on current render target state. /// - /// Use draw buffers information from render target control register - /// Indicates if the texture is layered + /// Flags indicating which render targets should be updated and how /// If this is not -1, it indicates that only the given indexed target will be used. - public void UpdateRenderTargetState(bool useControl, bool layered = false, int singleUse = -1) + public void UpdateRenderTargetState(RenderTargetUpdateFlags updateFlags, int singleUse = -1) { var memoryManager = _channel.MemoryManager; var rtControl = _state.State.RtControl; + bool useControl = updateFlags.HasFlag(RenderTargetUpdateFlags.UseControl); + bool layered = updateFlags.HasFlag(RenderTargetUpdateFlags.Layered); + bool singleColor = updateFlags.HasFlag(RenderTargetUpdateFlags.SingleColor); + int count = useControl ? rtControl.UnpackCount() : Constants.TotalRenderTargets; var msaaMode = _state.State.RtMsaaMode; @@ -438,7 +441,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed var colorState = _state.State.RtColorState[rtIndex]; - if (index >= count || !IsRtEnabled(colorState)) + if (index >= count || !IsRtEnabled(colorState) || (singleColor && index != singleUse)) { changedScale |= _channel.TextureManager.SetRenderTargetColor(index, null); @@ -478,7 +481,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed Image.Texture depthStencil = null; - if (dsEnable) + if (dsEnable && updateFlags.HasFlag(RenderTargetUpdateFlags.UpdateDepthStencil)) { var dsState = _state.State.RtDepthStencilState; var dsSize = _state.State.RtDepthStencilSize; diff --git a/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClass.cs b/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClass.cs index 19eb8b46ee..9a447a0bd9 100644 --- a/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClass.cs +++ b/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClass.cs @@ -139,12 +139,11 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed /// /// Updates render targets (color and depth-stencil buffers) based on current render target state. /// - /// Use draw buffers information from render target control register - /// Indicates if the texture is layered + /// Flags indicating which render targets should be updated and how /// If this is not -1, it indicates that only the given indexed target will be used. - public void UpdateRenderTargetState(bool useControl, bool layered = false, int singleUse = -1) + public void UpdateRenderTargetState(RenderTargetUpdateFlags updateFlags, int singleUse = -1) { - _stateUpdater.UpdateRenderTargetState(useControl, layered, singleUse); + _stateUpdater.UpdateRenderTargetState(updateFlags, singleUse); } /// diff --git a/Ryujinx.Graphics.Gpu/Image/Texture.cs b/Ryujinx.Graphics.Gpu/Image/Texture.cs index 5ed9b2a074..352a828d16 100644 --- a/Ryujinx.Graphics.Gpu/Image/Texture.cs +++ b/Ryujinx.Graphics.Gpu/Image/Texture.cs @@ -1,4 +1,3 @@ -using Ryujinx.Common; using Ryujinx.Common.Logging; using Ryujinx.Common.Memory; using Ryujinx.Graphics.GAL; @@ -89,12 +88,6 @@ namespace Ryujinx.Graphics.Gpu.Image /// public TextureGroup Group { get; private 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; private set; } - /// /// Set when a texture's GPU VA has ever been partially or fully unmapped. /// This indicates that the range must be fully checked when matching the texture. @@ -410,122 +403,6 @@ namespace Ryujinx.Graphics.Gpu.Image Group.CreateCopyDependency(contained, FirstLayer + layer, FirstLevel + level, copyTo); } - /// - /// Changes the texture size. - /// - /// - /// This operation may also change the size of all mipmap levels, including from the parent - /// and other possible child textures, to ensure that all sizes are consistent. - /// - /// The new texture width - /// The new texture height - /// The new texture depth (for 3D textures) or layers (for layered textures) - public void ChangeSize(int width, int height, int depthOrLayers) - { - int blockWidth = Info.FormatInfo.BlockWidth; - int blockHeight = Info.FormatInfo.BlockHeight; - - width <<= FirstLevel; - height <<= FirstLevel; - - if (Target == Target.Texture3D) - { - depthOrLayers <<= FirstLevel; - } - else - { - depthOrLayers = _viewStorage.Info.DepthOrLayers; - } - - _viewStorage.RecreateStorageOrView(width, height, blockWidth, blockHeight, depthOrLayers); - - foreach (Texture view in _viewStorage._views) - { - int viewWidth = Math.Max(1, width >> view.FirstLevel); - int viewHeight = Math.Max(1, height >> view.FirstLevel); - - int viewDepthOrLayers; - - if (view.Info.Target == Target.Texture3D) - { - viewDepthOrLayers = Math.Max(1, depthOrLayers >> view.FirstLevel); - } - else - { - viewDepthOrLayers = view.Info.DepthOrLayers; - } - - view.RecreateStorageOrView(viewWidth, viewHeight, blockWidth, blockHeight, viewDepthOrLayers); - } - } - - /// - /// Recreates the texture storage (or view, in the case of child textures) of this texture. - /// This allows recreating the texture with a new size. - /// A copy is automatically performed from the old to the new texture. - /// - /// The new texture width - /// The new texture height - /// The block width related to the given width - /// The block height related to the given height - /// The new texture depth (for 3D textures) or layers (for layered textures) - private void RecreateStorageOrView(int width, int height, int blockWidth, int blockHeight, int depthOrLayers) - { - RecreateStorageOrView( - BitUtils.DivRoundUp(width * Info.FormatInfo.BlockWidth, blockWidth), - BitUtils.DivRoundUp(height * Info.FormatInfo.BlockHeight, blockHeight), - depthOrLayers); - } - - /// - /// Recreates the texture storage (or view, in the case of child textures) of this texture. - /// This allows recreating the texture with a new size. - /// A copy is automatically performed from the old to the new texture. - /// - /// The new texture width - /// The new texture height - /// 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.GpuAddress, - width, - height, - depthOrLayers, - Info.Levels, - Info.SamplesInX, - Info.SamplesInY, - Info.Stride, - Info.IsLinear, - Info.GobBlocksInY, - Info.GobBlocksInZ, - Info.GobBlocksInTileX, - Info.Target, - Info.FormatInfo, - Info.DepthStencilMode, - Info.SwizzleR, - Info.SwizzleG, - Info.SwizzleB, - Info.SwizzleA)); - - TextureCreateInfo createInfo = TextureCache.GetCreateInfo(Info, _context.Capabilities, ScaleFactor); - - if (_viewStorage != this) - { - ReplaceStorage(_viewStorage.HostTexture.CreateView(createInfo, FirstLayer, FirstLevel)); - } - else - { - ITexture newStorage = _context.Renderer.CreateTexture(createInfo, ScaleFactor); - - HostTexture.CopyTo(newStorage, 0, 0); - - ReplaceStorage(newStorage); - } - } - /// /// Registers when a texture has had its data set after being scaled, and /// determines if it should be blacklisted from scaling to improve performance. @@ -1215,7 +1092,9 @@ namespace Ryujinx.Graphics.Gpu.Image /// A value indicating how well this texture matches the given info public TextureMatchQuality IsExactMatch(TextureInfo info, TextureSearchFlags flags) { - TextureMatchQuality matchQuality = TextureCompatibility.FormatMatches(Info, info, (flags & TextureSearchFlags.ForSampler) != 0, (flags & TextureSearchFlags.ForCopy) != 0); + bool forSampler = (flags & TextureSearchFlags.ForSampler) != 0; + + TextureMatchQuality matchQuality = TextureCompatibility.FormatMatches(Info, info, forSampler, (flags & TextureSearchFlags.ForCopy) != 0); if (matchQuality == TextureMatchQuality.NoMatch) { @@ -1227,12 +1106,12 @@ namespace Ryujinx.Graphics.Gpu.Image return TextureMatchQuality.NoMatch; } - if (!TextureCompatibility.SizeMatches(Info, info, (flags & TextureSearchFlags.Strict) == 0, FirstLevel)) + if (!TextureCompatibility.SizeMatches(Info, info, forSampler)) { return TextureMatchQuality.NoMatch; } - if ((flags & TextureSearchFlags.ForSampler) != 0 || (flags & TextureSearchFlags.Strict) != 0) + if ((flags & TextureSearchFlags.ForSampler) != 0) { if (!TextureCompatibility.SamplerParamsMatches(Info, info)) { @@ -1262,12 +1141,20 @@ namespace Ryujinx.Graphics.Gpu.Image /// /// Texture view information /// Texture view physical memory ranges + /// Indicates if the texture sizes must be exactly equal, or width is allowed to differ /// Layer size on the given texture /// Host GPU capabilities /// Texture view initial layer on this texture /// Texture view first mipmap level on this texture /// The level of compatiblilty a view with the given parameters created from this texture has - public TextureViewCompatibility IsViewCompatible(TextureInfo info, MultiRange range, int layerSize, Capabilities caps, out int firstLayer, out int firstLevel) + public TextureViewCompatibility IsViewCompatible( + TextureInfo info, + MultiRange range, + bool exactSize, + int layerSize, + Capabilities caps, + out int firstLayer, + out int firstLevel) { TextureViewCompatibility result = TextureViewCompatibility.Full; @@ -1317,7 +1204,7 @@ namespace Ryujinx.Graphics.Gpu.Image return TextureViewCompatibility.LayoutIncompatible; } - result = TextureCompatibility.PropagateViewCompatibility(result, TextureCompatibility.ViewSizeMatches(Info, info, firstLevel)); + result = TextureCompatibility.PropagateViewCompatibility(result, TextureCompatibility.ViewSizeMatches(Info, info, exactSize, firstLevel)); result = TextureCompatibility.PropagateViewCompatibility(result, TextureCompatibility.ViewSubImagesInBounds(Info, info, firstLayer, firstLevel)); return result; diff --git a/Ryujinx.Graphics.Gpu/Image/TextureCache.cs b/Ryujinx.Graphics.Gpu/Image/TextureCache.cs index 9802a3dcc1..1d5b1851f5 100644 --- a/Ryujinx.Graphics.Gpu/Image/TextureCache.cs +++ b/Ryujinx.Graphics.Gpu/Image/TextureCache.cs @@ -210,8 +210,8 @@ namespace Ryujinx.Graphics.Gpu.Image ulong offset, FormatInfo formatInfo, bool shouldCreate, - bool preferScaling = true, - Size? sizeHint = null) + bool preferScaling, + Size sizeHint) { int gobBlocksInY = copyTexture.MemoryLayout.UnpackGobBlocksInY(); int gobBlocksInZ = copyTexture.MemoryLayout.UnpackGobBlocksInZ(); @@ -229,7 +229,7 @@ namespace Ryujinx.Graphics.Gpu.Image TextureInfo info = new TextureInfo( copyTexture.Address.Pack() + offset, - width, + GetMinimumWidthInGob(width, sizeHint.Width, formatInfo.BytesPerPixel, copyTexture.LinearLayout), copyTexture.Height, copyTexture.Depth, 1, @@ -255,7 +255,7 @@ namespace Ryujinx.Graphics.Gpu.Image flags |= TextureSearchFlags.NoCreate; } - Texture texture = FindOrCreateTexture(memoryManager, flags, info, 0, sizeHint); + Texture texture = FindOrCreateTexture(memoryManager, flags, info, 0); texture?.SynchronizeMemory(); @@ -326,7 +326,7 @@ namespace Ryujinx.Graphics.Gpu.Image TextureInfo info = new TextureInfo( colorState.Address.Pack(), - width, + GetMinimumWidthInGob(width, sizeHint.Width, formatInfo.BytesPerPixel, isLinear), colorState.Height, colorState.Depth, 1, @@ -342,7 +342,7 @@ namespace Ryujinx.Graphics.Gpu.Image int layerSize = !isLinear ? colorState.LayerSize * 4 : 0; - Texture texture = FindOrCreateTexture(memoryManager, TextureSearchFlags.WithUpscale, info, layerSize, sizeHint); + Texture texture = FindOrCreateTexture(memoryManager, TextureSearchFlags.WithUpscale, info, layerSize); texture?.SynchronizeMemory(); @@ -395,7 +395,7 @@ namespace Ryujinx.Graphics.Gpu.Image TextureInfo info = new TextureInfo( dsState.Address.Pack(), - size.Width, + GetMinimumWidthInGob(size.Width, sizeHint.Width, formatInfo.BytesPerPixel, false), size.Height, size.Depth, 1, @@ -409,13 +409,41 @@ namespace Ryujinx.Graphics.Gpu.Image target, formatInfo); - Texture texture = FindOrCreateTexture(memoryManager, TextureSearchFlags.WithUpscale, info, dsState.LayerSize * 4, sizeHint); + Texture texture = FindOrCreateTexture(memoryManager, TextureSearchFlags.WithUpscale, info, dsState.LayerSize * 4); texture?.SynchronizeMemory(); return texture; } + /// + /// For block linear textures, gets the minimum width of the texture + /// that would still have the same number of GOBs per row as the original width. + /// + /// The possibly aligned texture width + /// The minimum width that the texture may have without losing data + /// Bytes per pixel of the texture format + /// True if the texture is linear, false for block linear + /// The minimum width of the texture with the same amount of GOBs per row + private static int GetMinimumWidthInGob(int width, int minimumWidth, int bytesPerPixel, bool isLinear) + { + if (isLinear || (uint)minimumWidth >= (uint)width) + { + return width; + } + + // Calculate the minimum possible that would not cause data loss + // and would be still within the same GOB (aligned size would be the same). + // This is useful for render and copy operations, where we don't know the + // exact width of the texture, but it doesn't matter, as long the texture is + // at least as large as the region being rendered or copied. + + int alignment = 64 / bytesPerPixel; + int widthAligned = BitUtils.AlignUp(width, alignment); + + return Math.Clamp(widthAligned - alignment + 1, minimumWidth, widthAligned); + } + /// /// Tries to find an existing texture, or create a new one if not found. /// @@ -423,7 +451,6 @@ namespace Ryujinx.Graphics.Gpu.Image /// The texture search flags, defines texture comparison rules /// Texture information of the texture to be found or created /// Size in bytes of a single texture layer - /// A hint indicating the minimum used size for the texture /// Optional ranges of physical memory where the texture data is located /// The texture public Texture FindOrCreateTexture( @@ -431,7 +458,6 @@ namespace Ryujinx.Graphics.Gpu.Image TextureSearchFlags flags, TextureInfo info, int layerSize = 0, - Size? sizeHint = null, MultiRange? range = null) { bool isSamplerTexture = (flags & TextureSearchFlags.ForSampler) != 0; @@ -512,8 +538,6 @@ namespace Ryujinx.Graphics.Gpu.Image if (texture != null) { - ChangeSizeIfNeeded(info, texture, isSamplerTexture, sizeHint); - texture.SynchronizeMemory(); return texture; @@ -568,6 +592,7 @@ namespace Ryujinx.Graphics.Gpu.Image TextureViewCompatibility overlapCompatibility = overlap.IsViewCompatible( info, range.Value, + isSamplerTexture, sizeInfo.LayerSize, _context.Capabilities, out int firstLayer, @@ -598,17 +623,15 @@ namespace Ryujinx.Graphics.Gpu.Image if (oInfo.Compatibility == TextureViewCompatibility.Full) { - TextureInfo adjInfo = AdjustSizes(overlap, info, oInfo.FirstLevel); - if (!isSamplerTexture) { - info = adjInfo; + // If this is not a sampler texture, the size might be different from the requested size, + // so we need to make sure the texture information has the correct size for this base texture, + // before creating the view. + info = info.CreateInfoForLevelView(overlap, oInfo.FirstLevel); } - texture = overlap.CreateView(adjInfo, sizeInfo, range.Value, oInfo.FirstLayer, oInfo.FirstLevel); - - ChangeSizeIfNeeded(info, texture, isSamplerTexture, sizeHint); - + texture = overlap.CreateView(info, sizeInfo, range.Value, oInfo.FirstLayer, oInfo.FirstLevel); texture.SynchronizeMemory(); break; } @@ -682,6 +705,7 @@ namespace Ryujinx.Graphics.Gpu.Image TextureViewCompatibility compatibility = texture.IsViewCompatible( overlap.Info, overlap.Range, + exactSize: true, overlap.LayerSize, _context.Capabilities, out int firstLayer, @@ -792,7 +816,11 @@ namespace Ryujinx.Graphics.Gpu.Image continue; } - TextureInfo overlapInfo = AdjustSizes(texture, overlap.Info, oInfo.FirstLevel); + // Note: If we allow different sizes for those overlaps, + // we need to make sure that the "info" has the correct size for the parent texture here. + // Since this is not allowed right now, we don't need to do it. + + TextureInfo overlapInfo = overlap.Info; if (texture.ScaleFactor != overlap.ScaleFactor) { @@ -856,44 +884,6 @@ 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); - } - } - } - /// /// Attempt to find a texture on the short duration cache. /// @@ -1000,92 +990,6 @@ namespace Ryujinx.Graphics.Gpu.Image } } - /// - /// Adjusts the size of the texture information for a given mipmap level, - /// based on the size of a parent texture. - /// - /// The parent texture - /// The texture information to be adjusted - /// The first level of the texture view - /// The adjusted texture information with the new size - private static TextureInfo AdjustSizes(Texture parent, TextureInfo info, int firstLevel) - { - // When the texture is used as view of another texture, we must - // ensure that the sizes are valid, otherwise data uploads would fail - // (and the size wouldn't match the real size used on the host API). - // Given a parent texture from where the view is created, we have the - // following rules: - // - The view size must be equal to the parent size, divided by (2 ^ l), - // where l is the first mipmap level of the view. The division result must - // be rounded down, and the result must be clamped to 1. - // - If the parent format is compressed, and the view format isn't, the - // view size is calculated as above, but the width and height of the - // view must be also divided by the compressed format block width and height. - // - If the parent format is not compressed, and the view is, the view - // size is calculated as described on the first point, but the width and height - // of the view must be also multiplied by the block width and height. - int width = Math.Max(1, parent.Info.Width >> firstLevel); - int height = Math.Max(1, parent.Info.Height >> firstLevel); - - if (parent.Info.FormatInfo.IsCompressed && !info.FormatInfo.IsCompressed) - { - width = BitUtils.DivRoundUp(width, parent.Info.FormatInfo.BlockWidth); - height = BitUtils.DivRoundUp(height, parent.Info.FormatInfo.BlockHeight); - } - else if (!parent.Info.FormatInfo.IsCompressed && info.FormatInfo.IsCompressed) - { - width *= info.FormatInfo.BlockWidth; - height *= info.FormatInfo.BlockHeight; - } - - int depthOrLayers; - - if (info.Target == Target.Texture3D) - { - depthOrLayers = Math.Max(1, parent.Info.DepthOrLayers >> firstLevel); - } - else - { - depthOrLayers = info.DepthOrLayers; - } - - // 2D and 2D multisample textures are not considered compatible. - // This specific case is required for copies, where the source texture might be multisample. - // In this case, we inherit the parent texture multisample state. - Target target = info.Target; - int samplesInX = info.SamplesInX; - int samplesInY = info.SamplesInY; - - if (target == Target.Texture2D && parent.Target == Target.Texture2DMultisample) - { - target = Target.Texture2DMultisample; - samplesInX = parent.Info.SamplesInX; - samplesInY = parent.Info.SamplesInY; - } - - return new TextureInfo( - info.GpuAddress, - width, - height, - depthOrLayers, - info.Levels, - samplesInX, - samplesInY, - info.Stride, - info.IsLinear, - info.GobBlocksInY, - info.GobBlocksInZ, - info.GobBlocksInTileX, - target, - info.FormatInfo, - info.DepthStencilMode, - info.SwizzleR, - info.SwizzleG, - info.SwizzleB, - info.SwizzleA); - } - - /// /// Gets a texture creation information from texture information. /// This can be used to create new host textures. diff --git a/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs b/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs index 7ec4c7ac2b..e8061951be 100644 --- a/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs +++ b/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs @@ -380,42 +380,37 @@ namespace Ryujinx.Graphics.Gpu.Image /// /// Texture information of the texture view /// Texture information of the texture view to match against + /// Indicates if the sizes must be exactly equal /// Mipmap level of the texture view in relation to this texture /// The view compatibility level of the view sizes - public static TextureViewCompatibility ViewSizeMatches(TextureInfo lhs, TextureInfo rhs, int level) + public static TextureViewCompatibility ViewSizeMatches(TextureInfo lhs, TextureInfo rhs, bool exact, int level) { - Size size = GetAlignedSize(lhs, level); + Size lhsAlignedSize = GetAlignedSize(lhs, level); + Size rhsAlignedSize = GetAlignedSize(rhs); - Size otherSize = GetAlignedSize(rhs); + Size lhsSize = GetSizeInBlocks(lhs, level); + Size rhsSize = GetSizeInBlocks(rhs); TextureViewCompatibility result = TextureViewCompatibility.Full; // For copies, we can copy a subset of the 3D texture slices, // so the depth may be different in this case. - if (rhs.Target == Target.Texture3D && size.Depth != otherSize.Depth) + if (rhs.Target == Target.Texture3D && lhsSize.Depth != rhsSize.Depth) { result = TextureViewCompatibility.CopyOnly; } - if (size.Width == otherSize.Width && size.Height == otherSize.Height) + // Some APIs align the width for copy and render target textures, + // so the width may not match in this case for different uses of the same texture. + // To account for this, we compare the aligned width here. + // We expect height to always match exactly, if the texture is the same. + if (lhsAlignedSize.Width == rhsAlignedSize.Width && lhsSize.Height == rhsSize.Height) { - if (level > 0 && result == TextureViewCompatibility.Full) - { - // A resize should not change the aligned size of the largest mip. - // If it would, then create a copy dependency rather than a full view. - - Size mip0SizeLhs = GetAlignedSize(lhs); - Size mip0SizeRhs = GetLargestAlignedSize(rhs, level); - - if (mip0SizeLhs.Width != mip0SizeRhs.Width || mip0SizeLhs.Height != mip0SizeRhs.Height) - { - result = TextureViewCompatibility.CopyOnly; - } - } - - return result; + return (exact && lhsSize.Width != rhsSize.Width) || lhsSize.Width < rhsSize.Width + ? TextureViewCompatibility.CopyOnly + : result; } - else if (lhs.IsLinear && rhs.IsLinear) + else if (lhs.IsLinear && rhs.IsLinear && lhsSize.Height == rhsSize.Height) { // Copy between linear textures with matching stride. int stride = BitUtils.AlignUp(Math.Max(1, lhs.Stride >> level), Constants.StrideAlignment); @@ -454,57 +449,33 @@ namespace Ryujinx.Graphics.Gpu.Image /// /// Texture information to compare /// Texture information to compare with - /// True if the size matches, false otherwise - public static bool SizeMatches(TextureInfo lhs, TextureInfo rhs) - { - return SizeMatches(lhs, rhs, alignSizes: false); - } - - /// - /// Checks if the texture sizes of the supplied texture informations match the given level - /// - /// Texture information to compare - /// Texture information to compare with - /// Mipmap level of this texture to compare with - /// True if the size matches with the level, false otherwise - public static bool SizeMatches(TextureInfo lhs, TextureInfo rhs, int level) - { - return Math.Max(1, lhs.Width >> level) == rhs.Width && - Math.Max(1, lhs.Height >> level) == rhs.Height && - Math.Max(1, lhs.GetDepth() >> level) == rhs.GetDepth(); - } - - /// - /// Checks if the texture sizes of the supplied texture informations match. - /// - /// Texture information to compare - /// Texture information to compare with - /// True to align the sizes according to the texture layout for comparison - /// Mip level of the lhs texture. Aligned sizes are compared for the largest mip + /// Indicates if the size must be exactly equal between the textures, or if is allowed to be larger /// True if the sizes matches, false otherwise - public static bool SizeMatches(TextureInfo lhs, TextureInfo rhs, bool alignSizes, int lhsLevel = 0) + public static bool SizeMatches(TextureInfo lhs, TextureInfo rhs, bool exact) { if (lhs.GetLayers() != rhs.GetLayers()) { return false; } - bool isTextureBuffer = lhs.Target == Target.TextureBuffer || rhs.Target == Target.TextureBuffer; + Size lhsSize = GetSizeInBlocks(lhs); + Size rhsSize = GetSizeInBlocks(rhs); - if (alignSizes && !isTextureBuffer) + if (exact || lhs.IsLinear || rhs.IsLinear) { - Size size0 = GetLargestAlignedSize(lhs, lhsLevel); - Size size1 = GetLargestAlignedSize(rhs, lhsLevel); - - return size0.Width == size1.Width && - size0.Height == size1.Height && - size0.Depth == size1.Depth; + return lhsSize.Width == rhsSize.Width && + lhsSize.Height == rhsSize.Height && + lhsSize.Depth == rhsSize.Depth; } else { - return lhs.Width == rhs.Width && - lhs.Height == rhs.Height && - lhs.GetDepth() == rhs.GetDepth(); + Size lhsAlignedSize = GetAlignedSize(lhs); + Size rhsAlignedSize = GetAlignedSize(rhs); + + return lhsAlignedSize.Width == rhsAlignedSize.Width && + lhsSize.Width >= rhsSize.Width && + lhsSize.Height == rhsSize.Height && + lhsSize.Depth == rhsSize.Depth; } } @@ -543,22 +514,6 @@ namespace Ryujinx.Graphics.Gpu.Image } } - /// - /// Gets the aligned sizes of the specified texture information, shifted to the largest mip from a given level. - /// The alignment depends on the texture layout and format bytes per pixel. - /// - /// Texture information to calculate the aligned size from - /// Mipmap level for texture views. Shifts the aligned size to represent the largest mip level - /// The aligned texture size of the largest mip level - public static Size GetLargestAlignedSize(TextureInfo info, int level) - { - int width = info.Width << level; - int height = info.Height << level; - int depth = info.GetDepth() << level; - - return GetAlignedSize(info, width, height, depth); - } - /// /// Gets the aligned sizes of the specified texture information. /// The alignment depends on the texture layout and format bytes per pixel. @@ -575,6 +530,25 @@ namespace Ryujinx.Graphics.Gpu.Image return GetAlignedSize(info, width, height, depth); } + /// + /// Gets the size in blocks for the given texture information. + /// For non-compressed formats, that's the same as the regular size. + /// + /// Texture information to calculate the aligned size from + /// Mipmap level for texture views + /// The texture size in blocks + public static Size GetSizeInBlocks(TextureInfo info, int level = 0) + { + int width = Math.Max(1, info.Width >> level); + int height = Math.Max(1, info.Height >> level); + int depth = Math.Max(1, info.GetDepth() >> level); + + return new Size( + BitUtils.DivRoundUp(width, info.FormatInfo.BlockWidth), + BitUtils.DivRoundUp(height, info.FormatInfo.BlockHeight), + depth); + } + /// /// Check if it's possible to create a view with the layout of the second texture information from the first. /// The layout information is composed of the Stride for linear textures, or GOB block size diff --git a/Ryujinx.Graphics.Gpu/Image/TextureInfo.cs b/Ryujinx.Graphics.Gpu/Image/TextureInfo.cs index 65cc698b0f..a7ee12bccd 100644 --- a/Ryujinx.Graphics.Gpu/Image/TextureInfo.cs +++ b/Ryujinx.Graphics.Gpu/Image/TextureInfo.cs @@ -1,5 +1,7 @@ +using Ryujinx.Common; using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.Texture; +using System; namespace Ryujinx.Graphics.Gpu.Image { @@ -292,5 +294,88 @@ namespace Ryujinx.Graphics.Gpu.Image layerSize); } } + + /// + /// Creates texture information for a given mipmap level of the specified parent texture and this information. + /// + /// The parent texture + /// The first level of the texture view + /// The adjusted texture information with the new size + public TextureInfo CreateInfoForLevelView(Texture parent, int firstLevel) + { + // When the texture is used as view of another texture, we must + // ensure that the sizes are valid, otherwise data uploads would fail + // (and the size wouldn't match the real size used on the host API). + // Given a parent texture from where the view is created, we have the + // following rules: + // - The view size must be equal to the parent size, divided by (2 ^ l), + // where l is the first mipmap level of the view. The division result must + // be rounded down, and the result must be clamped to 1. + // - If the parent format is compressed, and the view format isn't, the + // view size is calculated as above, but the width and height of the + // view must be also divided by the compressed format block width and height. + // - If the parent format is not compressed, and the view is, the view + // size is calculated as described on the first point, but the width and height + // of the view must be also multiplied by the block width and height. + int width = Math.Max(1, parent.Info.Width >> firstLevel); + int height = Math.Max(1, parent.Info.Height >> firstLevel); + + if (parent.Info.FormatInfo.IsCompressed && !FormatInfo.IsCompressed) + { + width = BitUtils.DivRoundUp(width, parent.Info.FormatInfo.BlockWidth); + height = BitUtils.DivRoundUp(height, parent.Info.FormatInfo.BlockHeight); + } + else if (!parent.Info.FormatInfo.IsCompressed && FormatInfo.IsCompressed) + { + width *= FormatInfo.BlockWidth; + height *= FormatInfo.BlockHeight; + } + + int depthOrLayers; + + if (Target == Target.Texture3D) + { + depthOrLayers = Math.Max(1, parent.Info.DepthOrLayers >> firstLevel); + } + else + { + depthOrLayers = DepthOrLayers; + } + + // 2D and 2D multisample textures are not considered compatible. + // This specific case is required for copies, where the source texture might be multisample. + // In this case, we inherit the parent texture multisample state. + Target target = Target; + int samplesInX = SamplesInX; + int samplesInY = SamplesInY; + + if (target == Target.Texture2D && parent.Target == Target.Texture2DMultisample) + { + target = Target.Texture2DMultisample; + samplesInX = parent.Info.SamplesInX; + samplesInY = parent.Info.SamplesInY; + } + + return new TextureInfo( + GpuAddress, + width, + height, + depthOrLayers, + Levels, + samplesInX, + samplesInY, + Stride, + IsLinear, + GobBlocksInY, + GobBlocksInZ, + GobBlocksInTileX, + target, + FormatInfo, + DepthStencilMode, + SwizzleR, + SwizzleG, + SwizzleB, + SwizzleA); + } } } \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Image/TextureManager.cs b/Ryujinx.Graphics.Gpu/Image/TextureManager.cs index 083de64c55..266f62856b 100644 --- a/Ryujinx.Graphics.Gpu/Image/TextureManager.cs +++ b/Ryujinx.Graphics.Gpu/Image/TextureManager.cs @@ -437,22 +437,6 @@ namespace Ryujinx.Graphics.Gpu.Image } } - /// - /// Update host framebuffer attachments based on currently bound render target buffers. - /// - /// - /// All attachments other than will be unbound. - /// - /// Index of the render target color to be updated - public void UpdateRenderTarget(int index) - { - new Span(_rtHostColors).Fill(null); - _rtHostColors[index] = _rtColors[index]?.HostTexture; - _rtHostDs = null; - - _context.Renderer.Pipeline.SetRenderTargets(_rtHostColors, null); - } - /// /// Update host framebuffer attachments based on currently bound render target buffers. /// diff --git a/Ryujinx.Graphics.Gpu/Image/TexturePool.cs b/Ryujinx.Graphics.Gpu/Image/TexturePool.cs index fc99fc9977..0348ca014b 100644 --- a/Ryujinx.Graphics.Gpu/Image/TexturePool.cs +++ b/Ryujinx.Graphics.Gpu/Image/TexturePool.cs @@ -77,22 +77,7 @@ 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. - - int baseLevel = descriptor.UnpackBaseLevel(); - int width = Math.Max(1, descriptor.UnpackWidth() >> baseLevel); - int height = Math.Max(1, descriptor.UnpackHeight() >> baseLevel); - - if (texture.Info.Width != width || texture.Info.Height != height) - { - texture.ChangeSize(width, height, texture.Info.DepthOrLayers); - } - } - - // Memory is automatically synchronized on texture creation. + // On the path above (texture not yet in the pool), memory is automatically synchronized on texture creation. texture.SynchronizeMemory(); } diff --git a/Ryujinx.Graphics.Gpu/Image/TextureSearchFlags.cs b/Ryujinx.Graphics.Gpu/Image/TextureSearchFlags.cs index aea7b167e6..890bf1736f 100644 --- a/Ryujinx.Graphics.Gpu/Image/TextureSearchFlags.cs +++ b/Ryujinx.Graphics.Gpu/Image/TextureSearchFlags.cs @@ -9,7 +9,6 @@ namespace Ryujinx.Graphics.Gpu.Image enum TextureSearchFlags { None = 0, - Strict = 1 << 0, ForSampler = 1 << 1, ForCopy = 1 << 2, WithUpscale = 1 << 3, diff --git a/Ryujinx.Graphics.Gpu/Window.cs b/Ryujinx.Graphics.Gpu/Window.cs index 90f8e40f92..06d0fddf49 100644 --- a/Ryujinx.Graphics.Gpu/Window.cs +++ b/Ryujinx.Graphics.Gpu/Window.cs @@ -202,7 +202,7 @@ namespace Ryujinx.Graphics.Gpu { pt.AcquireCallback(_context, pt.UserObj); - Texture texture = pt.Cache.FindOrCreateTexture(null, TextureSearchFlags.WithUpscale, pt.Info, 0, null, pt.Range); + Texture texture = pt.Cache.FindOrCreateTexture(null, TextureSearchFlags.WithUpscale, pt.Info, 0, pt.Range); pt.Cache.Tick();