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
This commit is contained in:
gdkchan 2023-02-08 04:48:09 -03:00 committed by GitHub
parent 59755818ef
commit 96cf242bcf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 271 additions and 406 deletions

View file

@ -725,10 +725,25 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
return; return;
} }
bool clearDepth = (argument & 1) != 0;
bool clearStencil = (argument & 2) != 0;
uint componentMask = (uint)((argument >> 2) & 0xf);
int index = (argument >> 6) & 0xf; int index = (argument >> 6) & 0xf;
int layer = (argument >> 10) & 0x3ff; 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 // 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 // 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); _context.Renderer.Pipeline.SetScissors(scissors);
} }
if (clipMismatch)
{
_channel.TextureManager.UpdateRenderTarget(index);
}
else
{
_channel.TextureManager.UpdateRenderTargets(); _channel.TextureManager.UpdateRenderTargets();
}
bool clearDepth = (argument & 1) != 0;
bool clearStencil = (argument & 2) != 0;
uint componentMask = (uint)((argument >> 2) & 0xf);
if (componentMask != 0) if (componentMask != 0)
{ {
@ -841,7 +845,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
engine.UpdateScissorState(); engine.UpdateScissorState();
} }
engine.UpdateRenderTargetState(useControl: true); engine.UpdateRenderTargetState(RenderTargetUpdateFlags.UpdateAll);
if (renderEnable == ConditionalRenderEnabled.Host) if (renderEnable == ConditionalRenderEnabled.Host)
{ {

View file

@ -0,0 +1,41 @@
using System;
namespace Ryujinx.Graphics.Gpu.Engine.Threed
{
/// <summary>
/// Flags indicating how the render targets should be updated.
/// </summary>
[Flags]
enum RenderTargetUpdateFlags
{
/// <summary>
/// No flags.
/// </summary>
None = 0,
/// <summary>
/// Get render target index from the control register.
/// </summary>
UseControl = 1 << 0,
/// <summary>
/// Indicates that all render targets are 2D array textures.
/// </summary>
Layered = 1 << 1,
/// <summary>
/// Indicates that only a single color target will be used.
/// </summary>
SingleColor = 1 << 2,
/// <summary>
/// Indicates that the depth-stencil target will be used.
/// </summary>
UpdateDepthStencil = 1 << 3,
/// <summary>
/// Default update flags for draw.
/// </summary>
UpdateAll = UseControl | UpdateDepthStencil
}
}

View file

@ -402,20 +402,23 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
/// </summary> /// </summary>
private void UpdateRenderTargetState() private void UpdateRenderTargetState()
{ {
UpdateRenderTargetState(true); UpdateRenderTargetState(RenderTargetUpdateFlags.UpdateAll);
} }
/// <summary> /// <summary>
/// Updates render targets (color and depth-stencil buffers) based on current render target state. /// Updates render targets (color and depth-stencil buffers) based on current render target state.
/// </summary> /// </summary>
/// <param name="useControl">Use draw buffers information from render target control register</param> /// <param name="updateFlags">Flags indicating which render targets should be updated and how</param>
/// <param name="layered">Indicates if the texture is layered</param>
/// <param name="singleUse">If this is not -1, it indicates that only the given indexed target will be used.</param> /// <param name="singleUse">If this is not -1, it indicates that only the given indexed target will be used.</param>
public void UpdateRenderTargetState(bool useControl, bool layered = false, int singleUse = -1) public void UpdateRenderTargetState(RenderTargetUpdateFlags updateFlags, int singleUse = -1)
{ {
var memoryManager = _channel.MemoryManager; var memoryManager = _channel.MemoryManager;
var rtControl = _state.State.RtControl; 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; int count = useControl ? rtControl.UnpackCount() : Constants.TotalRenderTargets;
var msaaMode = _state.State.RtMsaaMode; var msaaMode = _state.State.RtMsaaMode;
@ -438,7 +441,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
var colorState = _state.State.RtColorState[rtIndex]; 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); changedScale |= _channel.TextureManager.SetRenderTargetColor(index, null);
@ -478,7 +481,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
Image.Texture depthStencil = null; Image.Texture depthStencil = null;
if (dsEnable) if (dsEnable && updateFlags.HasFlag(RenderTargetUpdateFlags.UpdateDepthStencil))
{ {
var dsState = _state.State.RtDepthStencilState; var dsState = _state.State.RtDepthStencilState;
var dsSize = _state.State.RtDepthStencilSize; var dsSize = _state.State.RtDepthStencilSize;

View file

@ -139,12 +139,11 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
/// <summary> /// <summary>
/// Updates render targets (color and depth-stencil buffers) based on current render target state. /// Updates render targets (color and depth-stencil buffers) based on current render target state.
/// </summary> /// </summary>
/// <param name="useControl">Use draw buffers information from render target control register</param> /// <param name="updateFlags">Flags indicating which render targets should be updated and how</param>
/// <param name="layered">Indicates if the texture is layered</param>
/// <param name="singleUse">If this is not -1, it indicates that only the given indexed target will be used.</param> /// <param name="singleUse">If this is not -1, it indicates that only the given indexed target will be used.</param>
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);
} }
/// <summary> /// <summary>

View file

@ -1,4 +1,3 @@
using Ryujinx.Common;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.Common.Memory; using Ryujinx.Common.Memory;
using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.GAL;
@ -89,12 +88,6 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary> /// </summary>
public TextureGroup Group { get; private set; } public TextureGroup Group { get; private set; }
/// <summary>
/// Set when a texture has been changed size. This indicates that it may need to be
/// changed again when obtained as a sampler.
/// </summary>
public bool ChangedSize { get; private set; }
/// <summary> /// <summary>
/// Set when a texture's GPU VA has ever been partially or fully unmapped. /// 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. /// 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); Group.CreateCopyDependency(contained, FirstLayer + layer, FirstLevel + level, copyTo);
} }
/// <summary>
/// Changes the texture size.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
/// <param name="width">The new texture width</param>
/// <param name="height">The new texture height</param>
/// <param name="depthOrLayers">The new texture depth (for 3D textures) or layers (for layered textures)</param>
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);
}
}
/// <summary>
/// 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.
/// </summary>
/// <param name="width">The new texture width</param>
/// <param name="height">The new texture height</param>
/// <param name="width">The block width related to the given width</param>
/// <param name="height">The block height related to the given height</param>
/// <param name="depthOrLayers">The new texture depth (for 3D textures) or layers (for layered textures)</param>
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);
}
/// <summary>
/// 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.
/// </summary>
/// <param name="width">The new texture width</param>
/// <param name="height">The new texture height</param>
/// <param name="depthOrLayers">The new texture depth (for 3D textures) or layers (for layered textures)</param>
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);
}
}
/// <summary> /// <summary>
/// Registers when a texture has had its data set after being scaled, and /// Registers when a texture has had its data set after being scaled, and
/// determines if it should be blacklisted from scaling to improve performance. /// determines if it should be blacklisted from scaling to improve performance.
@ -1215,7 +1092,9 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <returns>A value indicating how well this texture matches the given info</returns> /// <returns>A value indicating how well this texture matches the given info</returns>
public TextureMatchQuality IsExactMatch(TextureInfo info, TextureSearchFlags flags) 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) if (matchQuality == TextureMatchQuality.NoMatch)
{ {
@ -1227,12 +1106,12 @@ namespace Ryujinx.Graphics.Gpu.Image
return TextureMatchQuality.NoMatch; return TextureMatchQuality.NoMatch;
} }
if (!TextureCompatibility.SizeMatches(Info, info, (flags & TextureSearchFlags.Strict) == 0, FirstLevel)) if (!TextureCompatibility.SizeMatches(Info, info, forSampler))
{ {
return TextureMatchQuality.NoMatch; return TextureMatchQuality.NoMatch;
} }
if ((flags & TextureSearchFlags.ForSampler) != 0 || (flags & TextureSearchFlags.Strict) != 0) if ((flags & TextureSearchFlags.ForSampler) != 0)
{ {
if (!TextureCompatibility.SamplerParamsMatches(Info, info)) if (!TextureCompatibility.SamplerParamsMatches(Info, info))
{ {
@ -1262,12 +1141,20 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary> /// </summary>
/// <param name="info">Texture view information</param> /// <param name="info">Texture view information</param>
/// <param name="range">Texture view physical memory ranges</param> /// <param name="range">Texture view physical memory ranges</param>
/// <param name="exactSize">Indicates if the texture sizes must be exactly equal, or width is allowed to differ</param>
/// <param name="layerSize">Layer size on the given texture</param> /// <param name="layerSize">Layer size on the given texture</param>
/// <param name="caps">Host GPU capabilities</param> /// <param name="caps">Host GPU capabilities</param>
/// <param name="firstLayer">Texture view initial layer on this texture</param> /// <param name="firstLayer">Texture view initial layer on this texture</param>
/// <param name="firstLevel">Texture view first mipmap level on this texture</param> /// <param name="firstLevel">Texture view first mipmap level on this texture</param>
/// <returns>The level of compatiblilty a view with the given parameters created from this texture has</returns> /// <returns>The level of compatiblilty a view with the given parameters created from this texture has</returns>
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; TextureViewCompatibility result = TextureViewCompatibility.Full;
@ -1317,7 +1204,7 @@ namespace Ryujinx.Graphics.Gpu.Image
return TextureViewCompatibility.LayoutIncompatible; 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)); result = TextureCompatibility.PropagateViewCompatibility(result, TextureCompatibility.ViewSubImagesInBounds(Info, info, firstLayer, firstLevel));
return result; return result;

View file

@ -210,8 +210,8 @@ namespace Ryujinx.Graphics.Gpu.Image
ulong offset, ulong offset,
FormatInfo formatInfo, FormatInfo formatInfo,
bool shouldCreate, bool shouldCreate,
bool preferScaling = true, bool preferScaling,
Size? sizeHint = null) Size sizeHint)
{ {
int gobBlocksInY = copyTexture.MemoryLayout.UnpackGobBlocksInY(); int gobBlocksInY = copyTexture.MemoryLayout.UnpackGobBlocksInY();
int gobBlocksInZ = copyTexture.MemoryLayout.UnpackGobBlocksInZ(); int gobBlocksInZ = copyTexture.MemoryLayout.UnpackGobBlocksInZ();
@ -229,7 +229,7 @@ namespace Ryujinx.Graphics.Gpu.Image
TextureInfo info = new TextureInfo( TextureInfo info = new TextureInfo(
copyTexture.Address.Pack() + offset, copyTexture.Address.Pack() + offset,
width, GetMinimumWidthInGob(width, sizeHint.Width, formatInfo.BytesPerPixel, copyTexture.LinearLayout),
copyTexture.Height, copyTexture.Height,
copyTexture.Depth, copyTexture.Depth,
1, 1,
@ -255,7 +255,7 @@ namespace Ryujinx.Graphics.Gpu.Image
flags |= TextureSearchFlags.NoCreate; flags |= TextureSearchFlags.NoCreate;
} }
Texture texture = FindOrCreateTexture(memoryManager, flags, info, 0, sizeHint); Texture texture = FindOrCreateTexture(memoryManager, flags, info, 0);
texture?.SynchronizeMemory(); texture?.SynchronizeMemory();
@ -326,7 +326,7 @@ namespace Ryujinx.Graphics.Gpu.Image
TextureInfo info = new TextureInfo( TextureInfo info = new TextureInfo(
colorState.Address.Pack(), colorState.Address.Pack(),
width, GetMinimumWidthInGob(width, sizeHint.Width, formatInfo.BytesPerPixel, isLinear),
colorState.Height, colorState.Height,
colorState.Depth, colorState.Depth,
1, 1,
@ -342,7 +342,7 @@ namespace Ryujinx.Graphics.Gpu.Image
int layerSize = !isLinear ? colorState.LayerSize * 4 : 0; 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(); texture?.SynchronizeMemory();
@ -395,7 +395,7 @@ namespace Ryujinx.Graphics.Gpu.Image
TextureInfo info = new TextureInfo( TextureInfo info = new TextureInfo(
dsState.Address.Pack(), dsState.Address.Pack(),
size.Width, GetMinimumWidthInGob(size.Width, sizeHint.Width, formatInfo.BytesPerPixel, false),
size.Height, size.Height,
size.Depth, size.Depth,
1, 1,
@ -409,13 +409,41 @@ namespace Ryujinx.Graphics.Gpu.Image
target, target,
formatInfo); formatInfo);
Texture texture = FindOrCreateTexture(memoryManager, TextureSearchFlags.WithUpscale, info, dsState.LayerSize * 4, sizeHint); Texture texture = FindOrCreateTexture(memoryManager, TextureSearchFlags.WithUpscale, info, dsState.LayerSize * 4);
texture?.SynchronizeMemory(); texture?.SynchronizeMemory();
return texture; return texture;
} }
/// <summary>
/// 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.
/// </summary>
/// <param name="width">The possibly aligned texture width</param>
/// <param name="minimumWidth">The minimum width that the texture may have without losing data</param>
/// <param name="bytesPerPixel">Bytes per pixel of the texture format</param>
/// <param name="isLinear">True if the texture is linear, false for block linear</param>
/// <returns>The minimum width of the texture with the same amount of GOBs per row</returns>
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);
}
/// <summary> /// <summary>
/// Tries to find an existing texture, or create a new one if not found. /// Tries to find an existing texture, or create a new one if not found.
/// </summary> /// </summary>
@ -423,7 +451,6 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="flags">The texture search flags, defines texture comparison rules</param> /// <param name="flags">The texture search flags, defines texture comparison rules</param>
/// <param name="info">Texture information of the texture to be found or created</param> /// <param name="info">Texture information of the texture to be found or created</param>
/// <param name="layerSize">Size in bytes of a single texture layer</param> /// <param name="layerSize">Size in bytes of a single texture layer</param>
/// <param name="sizeHint">A hint indicating the minimum used size for the texture</param>
/// <param name="range">Optional ranges of physical memory where the texture data is located</param> /// <param name="range">Optional ranges of physical memory where the texture data is located</param>
/// <returns>The texture</returns> /// <returns>The texture</returns>
public Texture FindOrCreateTexture( public Texture FindOrCreateTexture(
@ -431,7 +458,6 @@ namespace Ryujinx.Graphics.Gpu.Image
TextureSearchFlags flags, TextureSearchFlags flags,
TextureInfo info, TextureInfo info,
int layerSize = 0, int layerSize = 0,
Size? sizeHint = null,
MultiRange? range = null) MultiRange? range = null)
{ {
bool isSamplerTexture = (flags & TextureSearchFlags.ForSampler) != 0; bool isSamplerTexture = (flags & TextureSearchFlags.ForSampler) != 0;
@ -512,8 +538,6 @@ namespace Ryujinx.Graphics.Gpu.Image
if (texture != null) if (texture != null)
{ {
ChangeSizeIfNeeded(info, texture, isSamplerTexture, sizeHint);
texture.SynchronizeMemory(); texture.SynchronizeMemory();
return texture; return texture;
@ -568,6 +592,7 @@ namespace Ryujinx.Graphics.Gpu.Image
TextureViewCompatibility overlapCompatibility = overlap.IsViewCompatible( TextureViewCompatibility overlapCompatibility = overlap.IsViewCompatible(
info, info,
range.Value, range.Value,
isSamplerTexture,
sizeInfo.LayerSize, sizeInfo.LayerSize,
_context.Capabilities, _context.Capabilities,
out int firstLayer, out int firstLayer,
@ -598,17 +623,15 @@ namespace Ryujinx.Graphics.Gpu.Image
if (oInfo.Compatibility == TextureViewCompatibility.Full) if (oInfo.Compatibility == TextureViewCompatibility.Full)
{ {
TextureInfo adjInfo = AdjustSizes(overlap, info, oInfo.FirstLevel);
if (!isSamplerTexture) 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); texture = overlap.CreateView(info, sizeInfo, range.Value, oInfo.FirstLayer, oInfo.FirstLevel);
ChangeSizeIfNeeded(info, texture, isSamplerTexture, sizeHint);
texture.SynchronizeMemory(); texture.SynchronizeMemory();
break; break;
} }
@ -682,6 +705,7 @@ namespace Ryujinx.Graphics.Gpu.Image
TextureViewCompatibility compatibility = texture.IsViewCompatible( TextureViewCompatibility compatibility = texture.IsViewCompatible(
overlap.Info, overlap.Info,
overlap.Range, overlap.Range,
exactSize: true,
overlap.LayerSize, overlap.LayerSize,
_context.Capabilities, _context.Capabilities,
out int firstLayer, out int firstLayer,
@ -792,7 +816,11 @@ namespace Ryujinx.Graphics.Gpu.Image
continue; 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) if (texture.ScaleFactor != overlap.ScaleFactor)
{ {
@ -856,44 +884,6 @@ namespace Ryujinx.Graphics.Gpu.Image
return texture; return texture;
} }
/// <summary>
/// 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.
/// </summary>
/// <param name="info">The desired texture info</param>
/// <param name="texture">The texture to resize</param>
/// <param name="isSamplerTexture">True if the texture will be used for a sampler, false otherwise</param>
/// <param name="sizeHint">A hint indicating the minimum used size for the texture</param>
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);
}
}
}
/// <summary> /// <summary>
/// Attempt to find a texture on the short duration cache. /// Attempt to find a texture on the short duration cache.
/// </summary> /// </summary>
@ -1000,92 +990,6 @@ namespace Ryujinx.Graphics.Gpu.Image
} }
} }
/// <summary>
/// Adjusts the size of the texture information for a given mipmap level,
/// based on the size of a parent texture.
/// </summary>
/// <param name="parent">The parent texture</param>
/// <param name="info">The texture information to be adjusted</param>
/// <param name="firstLevel">The first level of the texture view</param>
/// <returns>The adjusted texture information with the new size</returns>
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);
}
/// <summary> /// <summary>
/// Gets a texture creation information from texture information. /// Gets a texture creation information from texture information.
/// This can be used to create new host textures. /// This can be used to create new host textures.

View file

@ -380,42 +380,37 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary> /// </summary>
/// <param name="lhs">Texture information of the texture view</param> /// <param name="lhs">Texture information of the texture view</param>
/// <param name="rhs">Texture information of the texture view to match against</param> /// <param name="rhs">Texture information of the texture view to match against</param>
/// <param name="exact">Indicates if the sizes must be exactly equal</param>
/// <param name="level">Mipmap level of the texture view in relation to this texture</param> /// <param name="level">Mipmap level of the texture view in relation to this texture</param>
/// <returns>The view compatibility level of the view sizes</returns> /// <returns>The view compatibility level of the view sizes</returns>
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; TextureViewCompatibility result = TextureViewCompatibility.Full;
// For copies, we can copy a subset of the 3D texture slices, // For copies, we can copy a subset of the 3D texture slices,
// so the depth may be different in this case. // 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; 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) return (exact && lhsSize.Width != rhsSize.Width) || lhsSize.Width < rhsSize.Width
{ ? TextureViewCompatibility.CopyOnly
// A resize should not change the aligned size of the largest mip. : result;
// 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;
} }
} else if (lhs.IsLinear && rhs.IsLinear && lhsSize.Height == rhsSize.Height)
return result;
}
else if (lhs.IsLinear && rhs.IsLinear)
{ {
// Copy between linear textures with matching stride. // Copy between linear textures with matching stride.
int stride = BitUtils.AlignUp(Math.Max(1, lhs.Stride >> level), Constants.StrideAlignment); int stride = BitUtils.AlignUp(Math.Max(1, lhs.Stride >> level), Constants.StrideAlignment);
@ -454,57 +449,33 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary> /// </summary>
/// <param name="lhs">Texture information to compare</param> /// <param name="lhs">Texture information to compare</param>
/// <param name="rhs">Texture information to compare with</param> /// <param name="rhs">Texture information to compare with</param>
/// <returns>True if the size matches, false otherwise</returns> /// <param name="exact">Indicates if the size must be exactly equal between the textures, or if <paramref name="rhs"/> is allowed to be larger</param>
public static bool SizeMatches(TextureInfo lhs, TextureInfo rhs)
{
return SizeMatches(lhs, rhs, alignSizes: false);
}
/// <summary>
/// Checks if the texture sizes of the supplied texture informations match the given level
/// </summary>
/// <param name="lhs">Texture information to compare</param>
/// <param name="rhs">Texture information to compare with</param>
/// <param name="level">Mipmap level of this texture to compare with</param>
/// <returns>True if the size matches with the level, false otherwise</returns>
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();
}
/// <summary>
/// Checks if the texture sizes of the supplied texture informations match.
/// </summary>
/// <param name="lhs">Texture information to compare</param>
/// <param name="rhs">Texture information to compare with</param>
/// <param name="alignSizes">True to align the sizes according to the texture layout for comparison</param>
/// <param name="lhsLevel">Mip level of the lhs texture. Aligned sizes are compared for the largest mip</param>
/// <returns>True if the sizes matches, false otherwise</returns> /// <returns>True if the sizes matches, false otherwise</returns>
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()) if (lhs.GetLayers() != rhs.GetLayers())
{ {
return false; 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); return lhsSize.Width == rhsSize.Width &&
Size size1 = GetLargestAlignedSize(rhs, lhsLevel); lhsSize.Height == rhsSize.Height &&
lhsSize.Depth == rhsSize.Depth;
return size0.Width == size1.Width &&
size0.Height == size1.Height &&
size0.Depth == size1.Depth;
} }
else else
{ {
return lhs.Width == rhs.Width && Size lhsAlignedSize = GetAlignedSize(lhs);
lhs.Height == rhs.Height && Size rhsAlignedSize = GetAlignedSize(rhs);
lhs.GetDepth() == rhs.GetDepth();
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
} }
} }
/// <summary>
/// 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.
/// </summary>
/// <param name="info">Texture information to calculate the aligned size from</param>
/// <param name="level">Mipmap level for texture views. Shifts the aligned size to represent the largest mip level</param>
/// <returns>The aligned texture size of the largest mip level</returns>
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);
}
/// <summary> /// <summary>
/// Gets the aligned sizes of the specified texture information. /// Gets the aligned sizes of the specified texture information.
/// The alignment depends on the texture layout and format bytes per pixel. /// 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); return GetAlignedSize(info, width, height, depth);
} }
/// <summary>
/// Gets the size in blocks for the given texture information.
/// For non-compressed formats, that's the same as the regular size.
/// </summary>
/// <param name="info">Texture information to calculate the aligned size from</param>
/// <param name="level">Mipmap level for texture views</param>
/// <returns>The texture size in blocks</returns>
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);
}
/// <summary> /// <summary>
/// Check if it's possible to create a view with the layout of the second texture information from the first. /// 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 /// The layout information is composed of the Stride for linear textures, or GOB block size

View file

@ -1,5 +1,7 @@
using Ryujinx.Common;
using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Texture; using Ryujinx.Graphics.Texture;
using System;
namespace Ryujinx.Graphics.Gpu.Image namespace Ryujinx.Graphics.Gpu.Image
{ {
@ -292,5 +294,88 @@ namespace Ryujinx.Graphics.Gpu.Image
layerSize); layerSize);
} }
} }
/// <summary>
/// Creates texture information for a given mipmap level of the specified parent texture and this information.
/// </summary>
/// <param name="parent">The parent texture</param>
/// <param name="firstLevel">The first level of the texture view</param>
/// <returns>The adjusted texture information with the new size</returns>
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);
}
} }
} }

View file

@ -437,22 +437,6 @@ namespace Ryujinx.Graphics.Gpu.Image
} }
} }
/// <summary>
/// Update host framebuffer attachments based on currently bound render target buffers.
/// </summary>
/// <remarks>
/// All attachments other than <paramref name="index"/> will be unbound.
/// </remarks>
/// <param name="index">Index of the render target color to be updated</param>
public void UpdateRenderTarget(int index)
{
new Span<ITexture>(_rtHostColors).Fill(null);
_rtHostColors[index] = _rtColors[index]?.HostTexture;
_rtHostDs = null;
_context.Renderer.Pipeline.SetRenderTargets(_rtHostColors, null);
}
/// <summary> /// <summary>
/// Update host framebuffer attachments based on currently bound render target buffers. /// Update host framebuffer attachments based on currently bound render target buffers.
/// </summary> /// </summary>

View file

@ -77,22 +77,7 @@ namespace Ryujinx.Graphics.Gpu.Image
} }
else else
{ {
if (texture.ChangedSize) // On the path above (texture not yet in the pool), memory is automatically synchronized on texture creation.
{
// 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.
texture.SynchronizeMemory(); texture.SynchronizeMemory();
} }

View file

@ -9,7 +9,6 @@ namespace Ryujinx.Graphics.Gpu.Image
enum TextureSearchFlags enum TextureSearchFlags
{ {
None = 0, None = 0,
Strict = 1 << 0,
ForSampler = 1 << 1, ForSampler = 1 << 1,
ForCopy = 1 << 2, ForCopy = 1 << 2,
WithUpscale = 1 << 3, WithUpscale = 1 << 3,

View file

@ -202,7 +202,7 @@ namespace Ryujinx.Graphics.Gpu
{ {
pt.AcquireCallback(_context, pt.UserObj); 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(); pt.Cache.Tick();