Initial texture replacement implementation

Unmerged PR #5632, originally by @gdkchan

* Add mod manager support - @KeatonTheBot

* Remove redundant code - @KeatonTheBot

* Optimize, modernize code to newer C# standards where appropriate - @KeatonTheBot

Co-authored-by: KeatonTheBot <keaton@ryujinx.app>
This commit is contained in:
gdkchan
2025-12-19 17:49:02 -06:00
committed by KeatonTheBot
co-authored by KeatonTheBot
parent d00bda7e53
commit 812cecd713
27 changed files with 3505 additions and 67 deletions
File diff suppressed because it is too large Load Diff
+135 -30
View File
@@ -64,6 +64,16 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary>
public int Height { get; private set; }
/// <summary>
/// Texture depth, or 1 if the texture is not a 3D texture.
/// </summary>
public int Depth { get; private set; }
/// <summary>
/// Numer of texture layers, or 1 if the texture is not a array texture.
/// </summary>
public int Layers { get; private set; }
/// <summary>
/// Texture information.
/// </summary>
@@ -106,11 +116,13 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary>
public int InvalidatedSequence { get; private set; }
private int _depth;
private int _layers;
public int FirstLayer { get; private set; }
public int FirstLevel { get; private set; }
private TextureInfoOverride? _importOverride;
private bool _forceReimport;
private readonly bool _forRender;
private bool _hasData;
private bool _dirty = true;
private int _updateCount;
@@ -189,6 +201,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="firstLevel">The first mipmap level of the texture, or 0 if the texture has no parent</param>
/// <param name="scaleFactor">The floating point scale factor to initialize with</param>
/// <param name="scaleMode">The scale mode to initialize with</param>
/// <param name="forRender">Indicates that the texture will be modified by a draw or blit operation</param>
private Texture(
GpuContext context,
PhysicalMemory physicalMemory,
@@ -198,7 +211,8 @@ namespace Ryujinx.Graphics.Gpu.Image
int firstLayer,
int firstLevel,
float scaleFactor,
TextureScaleMode scaleMode)
TextureScaleMode scaleMode,
bool forRender)
{
InitializeTexture(context, physicalMemory, info, sizeInfo, range);
@@ -208,6 +222,8 @@ namespace Ryujinx.Graphics.Gpu.Image
ScaleFactor = scaleFactor;
ScaleMode = scaleMode;
_forRender = forRender;
InitializeData(true);
}
@@ -220,17 +236,21 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="sizeInfo">Size information of the texture</param>
/// <param name="range">Physical memory ranges where the texture data is located</param>
/// <param name="scaleMode">The scale mode to initialize with. If scaled, the texture's data is loaded immediately and scaled up</param>
/// <param name="forRender">Indicates that the texture will be modified by a draw or blit operation</param>
public Texture(
GpuContext context,
PhysicalMemory physicalMemory,
TextureInfo info,
SizeInfo sizeInfo,
MultiRange range,
TextureScaleMode scaleMode)
TextureScaleMode scaleMode,
bool forRender)
{
ScaleFactor = 1f; // Texture is first loaded at scale 1x.
ScaleMode = scaleMode;
_forRender = forRender;
InitializeTexture(context, physicalMemory, info, sizeInfo, range);
}
@@ -278,7 +298,7 @@ namespace Ryujinx.Graphics.Gpu.Image
{
Debug.Assert(!isView);
TextureCreateInfo createInfo = TextureCache.GetCreateInfo(Info, _context.Capabilities, ScaleFactor);
TextureCreateInfo createInfo = TextureCache.GetCreateInfo(Info, _context.Capabilities, ScaleFactor, _importOverride);
HostTexture = _context.Renderer.CreateTexture(createInfo);
SynchronizeMemory(); // Load the data.
@@ -302,7 +322,7 @@ namespace Ryujinx.Graphics.Gpu.Image
ScaleFactor = GraphicsConfig.ResScale;
}
TextureCreateInfo createInfo = TextureCache.GetCreateInfo(Info, _context.Capabilities, ScaleFactor);
TextureCreateInfo createInfo = TextureCache.GetCreateInfo(Info, _context.Capabilities, ScaleFactor, _importOverride);
HostTexture = _context.Renderer.CreateTexture(createInfo);
}
}
@@ -344,9 +364,10 @@ namespace Ryujinx.Graphics.Gpu.Image
FirstLayer + firstLayer,
FirstLevel + firstLevel,
ScaleFactor,
ScaleMode);
ScaleMode,
_forRender);
TextureCreateInfo createInfo = TextureCache.GetCreateInfo(info, _context.Capabilities, ScaleFactor);
TextureCreateInfo createInfo = TextureCache.GetCreateInfo(info, _context.Capabilities, ScaleFactor, null);
texture.HostTexture = HostTexture.CreateView(createInfo, firstLayer, firstLevel);
_viewStorage.AddView(texture);
@@ -490,7 +511,7 @@ namespace Ryujinx.Graphics.Gpu.Image
{
if (storage == null)
{
TextureCreateInfo createInfo = TextureCache.GetCreateInfo(Info, _context.Capabilities, scale);
TextureCreateInfo createInfo = TextureCache.GetCreateInfo(Info, _context.Capabilities, scale, _importOverride);
storage = _context.Renderer.CreateTexture(createInfo);
}
@@ -541,7 +562,7 @@ namespace Ryujinx.Graphics.Gpu.Image
Logger.Debug?.Print(LogClass.Gpu, $" Recreating view {Info.Width}x{Info.Height} {Info.FormatInfo.Format}.");
view.ScaleFactor = scale;
TextureCreateInfo viewCreateInfo = TextureCache.GetCreateInfo(view.Info, _context.Capabilities, scale);
TextureCreateInfo viewCreateInfo = TextureCache.GetCreateInfo(view.Info, _context.Capabilities, scale, _importOverride);
ITexture newView = HostTexture.CreateView(viewCreateInfo, view.FirstLayer - FirstLayer, view.FirstLevel - FirstLevel);
view.ReplaceStorage(newView);
@@ -571,6 +592,11 @@ namespace Ryujinx.Graphics.Gpu.Image
return Group.CheckDirty(this, consume);
}
public void ForceReimport()
{
_forceReimport = true;
}
/// <summary>
/// Discards all data for this texture.
/// This clears all dirty flags and pending copies from other textures.
@@ -597,6 +623,15 @@ namespace Ryujinx.Graphics.Gpu.Image
return;
}
if (_forceReimport)
{
SynchronizeFull();
_forceReimport = false;
return;
}
if (!_dirty)
{
return;
@@ -643,7 +678,7 @@ namespace Ryujinx.Graphics.Gpu.Image
// The decompression is slow, so we want to avoid it as much as possible.
// This does a byte-by-byte check and skips the update if the data is equal in this case.
// This improves the speed on applications that overwrites ASTC data without changing anything.
if (Info.FormatInfo.Format.IsAstc() && !_context.Capabilities.SupportsAstcCompression)
if (Info.FormatInfo.Format.IsAstc() && !_context.Capabilities.SupportsAstcCompression && !_forceReimport)
{
if (_updateCount < ByteComparisonSwitchThreshold)
{
@@ -688,6 +723,11 @@ namespace Ryujinx.Graphics.Gpu.Image
{
BlacklistScale();
if (HasImportOverride())
{
return;
}
Group.CheckDirty(this, true);
AlwaysFlushOnOverlap = true;
@@ -725,6 +765,11 @@ namespace Ryujinx.Graphics.Gpu.Image
{
BlacklistScale();
if (HasImportOverride())
{
return;
}
HostTexture.SetData(data, layer, level, region);
_currentData = null;
@@ -744,8 +789,8 @@ namespace Ryujinx.Graphics.Gpu.Image
int width = Info.Width;
int height = Info.Height;
int depth = _depth;
int layers = single ? 1 : _layers;
int depth = Depth;
int layers = single ? 1 : Layers;
int levels = single ? 1 : (Info.Levels - level);
width = Math.Max(width >> level, 1);
@@ -788,11 +833,55 @@ namespace Ryujinx.Graphics.Gpu.Image
}
MemoryOwner<byte> result = linear;
FormatInfo formatInfo = Info.FormatInfo;
if (_context.DiskTextureStorage.IsActive && !_forRender)
{
TextureInfoOverride? importOverride = _context.DiskTextureStorage.ImportTexture(out var importedTexture, this, result.Memory.ToArray());
if (importOverride.HasValue)
{
if (!_importOverride.HasValue || !_importOverride.Equals(importOverride))
{
bool hadImportOverride = HasImportOverride();
_importOverride = importOverride;
if (hadImportOverride || HasImportOverride())
{
InvalidatedSequence++;
TextureCreateInfo createInfo = TextureCache.GetCreateInfo(Info, _context.Capabilities, ScaleFactor, importOverride);
ReplaceStorage(_context.Renderer.CreateTexture(createInfo));
if (_viewStorage != this)
{
_viewStorage.RemoveView(this);
}
}
}
TextureInfoOverride infoOverride = importOverride.Value;
width = infoOverride.Width;
height = infoOverride.Height;
sliceDepth = Target == Target.Texture3D ? infoOverride.DepthOrLayers : 1;
layers = Target != Target.Texture3D ? Info.DepthOrLayers : 1;
levels = infoOverride.Levels;
formatInfo = infoOverride.FormatInfo;
result.Dispose();
result = importedTexture;
}
else if (!_hasData)
{
_context.DiskTextureStorage.EnqueueTextureDataForExport(this, result.Memory.ToArray());
}
}
// Handle compressed cases not supported by the host:
// - ASTC is usually not supported on desktop cards.
// - BC4/BC5 is not supported on 3D textures.
if (!_context.Capabilities.SupportsAstcCompression && Format.IsAstc())
if (!_context.Capabilities.SupportsAstcCompression && formatInfo.Format.IsAstc())
{
using (result)
{
@@ -823,9 +912,9 @@ namespace Ryujinx.Graphics.Gpu.Image
return decoded;
}
}
else if (!_context.Capabilities.SupportsEtc2Compression && Format.IsEtc2())
else if (!_context.Capabilities.SupportsEtc2Compression && formatInfo.Format.IsEtc2())
{
switch (Format)
switch (formatInfo.Format)
{
case Format.Etc2RgbaSrgb:
case Format.Etc2RgbaUnorm:
@@ -847,9 +936,9 @@ namespace Ryujinx.Graphics.Gpu.Image
}
}
}
else if (!TextureCompatibility.HostSupportsBcFormat(Format, Target, _context.Capabilities))
else if (!TextureCompatibility.HostSupportsBcFormat(formatInfo.Format, Target, _context.Capabilities))
{
switch (Format)
switch (formatInfo.Format)
{
case Format.Bc1RgbaSrgb:
case Format.Bc1RgbaUnorm:
@@ -895,7 +984,7 @@ namespace Ryujinx.Graphics.Gpu.Image
}
}
}
else if (!_context.Capabilities.SupportsR4G4Format && Format == Format.R4G4Unorm)
else if (!_context.Capabilities.SupportsR4G4Format && formatInfo.Format == Format.R4G4Unorm)
{
using (result)
{
@@ -914,7 +1003,7 @@ namespace Ryujinx.Graphics.Gpu.Image
}
}
}
else if (Format == Format.R4G4B4A4Unorm)
else if (formatInfo.Format == Format.R4G4B4A4Unorm)
{
if (!_context.Capabilities.SupportsR4G4B4A4Format)
{
@@ -924,9 +1013,9 @@ namespace Ryujinx.Graphics.Gpu.Image
}
}
}
else if (!_context.Capabilities.Supports5BitComponentFormat && Format.Is16BitPacked())
else if (!_context.Capabilities.Supports5BitComponentFormat && formatInfo.Format.Is16BitPacked())
{
switch (Format)
switch (formatInfo.Format)
{
case Format.B5G6R5Unorm:
case Format.R5G6B5Unorm:
@@ -972,8 +1061,8 @@ namespace Ryujinx.Graphics.Gpu.Image
int width = Info.Width;
int height = Info.Height;
int depth = _depth;
int layers = single ? 1 : _layers;
int depth = Depth;
int layers = single ? 1 : Layers;
int levels = single ? 1 : (Info.Levels - level);
width = Math.Max(width >> level, 1);
@@ -1028,7 +1117,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <returns>True if data was flushed, false otherwise</returns>
public bool FlushModified(bool tracked = true)
{
return TextureCompatibility.CanTextureFlush(Info, _context.Capabilities) && Group.FlushModified(this, tracked);
return TextureCompatibility.CanTextureFlush(this, _context.Capabilities) && Group.FlushModified(this, tracked);
}
/// <summary>
@@ -1042,7 +1131,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="tracked">Whether or not the flush triggers write tracking. If it doesn't, the texture will not be blacklisted for scaling either.</param>
public void Flush(bool tracked)
{
if (TextureCompatibility.CanTextureFlush(Info, _context.Capabilities))
if (TextureCompatibility.CanTextureFlush(this, _context.Capabilities))
{
FlushTextureDataToGuest(tracked);
}
@@ -1286,6 +1375,22 @@ namespace Ryujinx.Graphics.Gpu.Image
return result;
}
public bool HasImportOverride()
{
if (_importOverride.HasValue)
{
TextureInfoOverride importOverride = _importOverride.Value;
return importOverride.Width != Info.Width ||
importOverride.Height != Info.Height ||
importOverride.DepthOrLayers != Info.GetDepthOrLayers() ||
importOverride.Levels != Info.Levels ||
importOverride.FormatInfo.Format != Info.FormatInfo.Format;
}
return false;
}
/// <summary>
/// Gets a texture of the specified target type from this texture.
/// This can be used to get an array texture from a non-array texture and vice-versa.
@@ -1302,7 +1407,7 @@ namespace Ryujinx.Graphics.Gpu.Image
if (_arrayViewTexture == null && IsSameDimensionsTarget(target))
{
FormatInfo formatInfo = TextureCompatibility.ToHostCompatibleFormat(Info, _context.Capabilities);
FormatInfo formatInfo = TextureCompatibility.ToHostCompatibleFormat(Info.FormatInfo, Info.Target, _context.Capabilities);
TextureCreateInfo createInfo = new(
Info.Width,
@@ -1405,7 +1510,7 @@ namespace Ryujinx.Graphics.Gpu.Image
foreach (Texture view in viewCopy)
{
TextureCreateInfo createInfo = TextureCache.GetCreateInfo(view.Info, _context.Capabilities, ScaleFactor);
TextureCreateInfo createInfo = TextureCache.GetCreateInfo(view.Info, _context.Capabilities, ScaleFactor, null);
ITexture newView = parent.HostTexture.CreateView(createInfo, view.FirstLayer + firstLayer, view.FirstLevel + firstLevel);
@@ -1440,8 +1545,8 @@ namespace Ryujinx.Graphics.Gpu.Image
Height = info.Height;
CanForceAnisotropy = CanTextureForceAnisotropy();
_depth = info.GetDepth();
_layers = info.GetLayers();
Depth = info.GetDepth();
Layers = info.GetLayers();
}
/// <summary>
+42 -14
View File
@@ -828,6 +828,11 @@ namespace Ryujinx.Graphics.Gpu.Image
out int firstLevel,
flags);
if (overlap.HasImportOverride())
{
overlapCompatibility = TextureViewCompatibility.Incompatible;
}
if (overlapCompatibility >= TextureViewCompatibility.FormatAlias)
{
if (overlap.IsView)
@@ -887,7 +892,7 @@ namespace Ryujinx.Graphics.Gpu.Image
{
// Only copy compatible. If there's another choice for a FULLY compatible texture, choose that instead.
texture = new Texture(_context, _physicalMemory, info, sizeInfo, range.Value, scaleMode);
texture = new Texture(_context, _physicalMemory, info, sizeInfo, range.Value, scaleMode, flags.HasFlag(TextureSearchFlags.WithUpscale));
// If the new texture is larger than the existing one, we need to fill the remaining space with CPU data,
// otherwise we only need the data that is copied from the existing texture, without loading the CPU data.
@@ -936,7 +941,7 @@ namespace Ryujinx.Graphics.Gpu.Image
// No match, create a new texture.
if (texture == null)
{
texture = new Texture(_context, _physicalMemory, info, sizeInfo, range.Value, scaleMode);
texture = new Texture(_context, _physicalMemory, info, sizeInfo, range.Value, scaleMode, flags.HasFlag(TextureSearchFlags.WithUpscale));
// Step 1: Find textures that are view compatible with the new texture.
// Any textures that are incompatible will contain garbage data, so they should be removed where possible.
@@ -964,6 +969,11 @@ namespace Ryujinx.Graphics.Gpu.Image
out int firstLayer,
out int firstLevel);
if (overlap.HasImportOverride())
{
compatibility = TextureViewCompatibility.Incompatible;
}
if (overlap.IsView && compatibility == TextureViewCompatibility.Full)
{
compatibility = TextureViewCompatibility.CopyOnly;
@@ -1079,6 +1089,12 @@ namespace Ryujinx.Graphics.Gpu.Image
continue;
}
if (texture.HasImportOverride())
{
// Replaced textures with different parameters are not considered compatible.
continue;
}
// 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.
@@ -1102,14 +1118,11 @@ namespace Ryujinx.Graphics.Gpu.Image
}
else
{
TextureCreateInfo createInfo = GetCreateInfo(overlapInfo, _context.Capabilities, overlap.ScaleFactor);
TextureCreateInfo createInfo = GetCreateInfo(overlapInfo, _context.Capabilities, overlap.ScaleFactor, null);
ITexture newView = texture.HostTexture.CreateView(createInfo, oInfo.FirstLayer, oInfo.FirstLevel);
overlap.SynchronizeMemory();
overlap.HostTexture.CopyTo(newView, 0, 0);
overlap.ReplaceView(texture, overlapInfo, newView, oInfo.FirstLayer, oInfo.FirstLevel);
}
}
@@ -1278,10 +1291,30 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="info">Texture information</param>
/// <param name="caps">GPU capabilities</param>
/// <param name="scale">Texture scale factor, to be applied to the texture size</param>
/// <param name="infoOverride">Optional parameters to override the <paramref name="info"/> parameter</param>
/// <returns>The texture creation information</returns>
public static TextureCreateInfo GetCreateInfo(TextureInfo info, Capabilities caps, float scale)
public static TextureCreateInfo GetCreateInfo(TextureInfo info, in Capabilities caps, float scale, TextureInfoOverride? infoOverride)
{
FormatInfo formatInfo = TextureCompatibility.ToHostCompatibleFormat(info, caps);
int width = info.Width / info.SamplesInX;
int height = info.Height / info.SamplesInY;
int depth = info.GetDepth() * info.GetLayers();
int levels = info.Levels;
FormatInfo formatInfo = info.FormatInfo;
if (infoOverride.HasValue)
{
TextureInfoOverride overrideValue = infoOverride.Value;
width = overrideValue.Width;
height = overrideValue.Height;
depth = overrideValue.DepthOrLayers;
levels = overrideValue.Levels;
formatInfo = overrideValue.FormatInfo;
}
formatInfo = TextureCompatibility.ToHostCompatibleFormat(formatInfo, info.Target, caps);
if (info.Target == Target.TextureBuffer && !caps.SupportsSnormBufferTextureFormat)
{
@@ -1310,11 +1343,6 @@ namespace Ryujinx.Graphics.Gpu.Image
}
}
int width = info.Width / info.SamplesInX;
int height = info.Height / info.SamplesInY;
int depth = info.GetDepth() * info.GetLayers();
if (scale != 1f)
{
width = (int)MathF.Ceiling(width * scale);
@@ -1325,7 +1353,7 @@ namespace Ryujinx.Graphics.Gpu.Image
width,
height,
depth,
info.Levels,
levels,
info.Samples,
formatInfo.BlockWidth,
formatInfo.BlockHeight,
@@ -50,10 +50,10 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="info">Texture information</param>
/// <param name="caps">Host GPU capabilities</param>
/// <returns>True if the format is incompatible, false otherwise</returns>
public static bool IsFormatHostIncompatible(TextureInfo info, Capabilities caps)
public static bool IsFormatHostIncompatible(TextureInfo info, in Capabilities caps)
{
Format originalFormat = info.FormatInfo.Format;
return ToHostCompatibleFormat(info, caps).Format != originalFormat;
return ToHostCompatibleFormat(info.FormatInfo, info.Target, caps).Format != originalFormat;
}
/// <summary>
@@ -64,10 +64,11 @@ namespace Ryujinx.Graphics.Gpu.Image
/// This can be used to convert a incompatible compressed format to the decompressor
/// output format.
/// </remarks>
/// <param name="info">Texture information</param>
/// <param name="formatInfo">Texture format information</param>
/// <param name="target">Texture dimensions</param>
/// <param name="caps">Host GPU capabilities</param>
/// <returns>A host compatible format</returns>
public static FormatInfo ToHostCompatibleFormat(TextureInfo info, Capabilities caps)
public static FormatInfo ToHostCompatibleFormat(FormatInfo formatInfo, Target target, in Capabilities caps)
{
// The host API does not support those compressed formats.
// We assume software decompression will be done for those textures,
@@ -75,13 +76,13 @@ namespace Ryujinx.Graphics.Gpu.Image
if (!caps.SupportsAstcCompression)
{
if (info.FormatInfo.Format.IsAstcUnorm())
if (formatInfo.Format.IsAstcUnorm())
{
return GraphicsConfig.EnableTextureRecompression
? new FormatInfo(Format.Bc7Unorm, 4, 4, 16, 4)
: new FormatInfo(Format.R8G8B8A8Unorm, 1, 1, 4, 4);
}
else if (info.FormatInfo.Format.IsAstcSrgb())
else if (formatInfo.Format.IsAstcSrgb())
{
return GraphicsConfig.EnableTextureRecompression
? new FormatInfo(Format.Bc7Srgb, 4, 4, 16, 4)
@@ -89,9 +90,9 @@ namespace Ryujinx.Graphics.Gpu.Image
}
}
if (!HostSupportsBcFormat(info.FormatInfo.Format, info.Target, caps))
if (!HostSupportsBcFormat(formatInfo.Format, target, caps))
{
switch (info.FormatInfo.Format)
switch (formatInfo.Format)
{
case Format.Bc1RgbaSrgb:
case Format.Bc2Srgb:
@@ -119,7 +120,7 @@ namespace Ryujinx.Graphics.Gpu.Image
if (!caps.SupportsEtc2Compression)
{
switch (info.FormatInfo.Format)
switch (formatInfo.Format)
{
case Format.Etc2RgbaSrgb:
case Format.Etc2RgbPtaSrgb:
@@ -132,7 +133,7 @@ namespace Ryujinx.Graphics.Gpu.Image
}
}
if (!caps.SupportsR4G4Format && info.FormatInfo.Format == Format.R4G4Unorm)
if (!caps.SupportsR4G4Format && formatInfo.Format == Format.R4G4Unorm)
{
if (caps.SupportsR4G4B4A4Format)
{
@@ -144,19 +145,19 @@ namespace Ryujinx.Graphics.Gpu.Image
}
}
if (info.FormatInfo.Format == Format.R4G4B4A4Unorm)
if (formatInfo.Format == Format.R4G4B4A4Unorm)
{
if (!caps.SupportsR4G4B4A4Format)
{
return new FormatInfo(Format.R8G8B8A8Unorm, 1, 1, 4, 4);
}
}
else if (!caps.Supports5BitComponentFormat && info.FormatInfo.Format.Is16BitPacked())
else if (!caps.Supports5BitComponentFormat && formatInfo.Format.Is16BitPacked())
{
return new FormatInfo(info.FormatInfo.Format.IsBgr() ? Format.B8G8R8A8Unorm : Format.R8G8B8A8Unorm, 1, 1, 4, 4);
return new FormatInfo(formatInfo.Format.IsBgr() ? Format.B8G8R8A8Unorm : Format.R8G8B8A8Unorm, 1, 1, 4, 4);
}
return info.FormatInfo;
return formatInfo;
}
/// <summary>
@@ -166,7 +167,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="target">Target usage of the texture</param>
/// <param name="caps">Host GPU Capabilities</param>
/// <returns>True if the texture host supports the format with the given target usage, false otherwise</returns>
public static bool HostSupportsBcFormat(Format format, Target target, Capabilities caps)
public static bool HostSupportsBcFormat(Format format, Target target, in Capabilities caps)
{
bool not3DOr3DCompressionSupported = target != Target.Texture3D || caps.Supports3DTextureCompression;
@@ -194,15 +195,26 @@ namespace Ryujinx.Graphics.Gpu.Image
return true;
}
/// <summary>
/// Determines whether a texture can flush its data back to guest memory.
/// </summary>
/// <param name="info">Texture that will have its data flushed</param>
/// <param name="caps">Host GPU Capabilities</param>
/// <returns>True if the texture can flush, false otherwise</returns>
public static bool CanTextureFlush(Texture texture, in Capabilities caps)
{
return !texture.HasImportOverride() && CanTextureFlush(texture.Info, caps);
}
/// <summary>
/// Determines whether a texture can flush its data back to guest memory.
/// </summary>
/// <param name="info">Texture information</param>
/// <param name="caps">Host GPU Capabilities</param>
/// <returns>True if the texture can flush, false otherwise</returns>
public static bool CanTextureFlush(TextureInfo info, Capabilities caps)
private static bool CanTextureFlush(TextureInfo info, in Capabilities caps)
{
if (IsFormatHostIncompatible(info, caps))
if (IsFormatHostIncompatible(info, in caps))
{
return false; // Flushing this format is not supported, as it may have been converted to another host format.
}
@@ -652,7 +652,7 @@ namespace Ryujinx.Graphics.Gpu.Image
if (_flushBuffer == BufferHandle.Null)
{
if (!TextureCompatibility.CanTextureFlush(Storage.Info, _context.Capabilities))
if (!TextureCompatibility.CanTextureFlush(Storage, _context.Capabilities))
{
return;
}
@@ -1700,7 +1700,7 @@ namespace Ryujinx.Graphics.Gpu.Image
}
}
if (TextureCompatibility.CanTextureFlush(Storage.Info, _context.Capabilities) && !(inBuffer && _flushBufferImported))
if (TextureCompatibility.CanTextureFlush(Storage, _context.Capabilities) && !(inBuffer && _flushBufferImported))
{
FlushSliceRange(false, handle.BaseSlice, handle.BaseSlice + handle.SliceCount, inBuffer, Storage.GetFlushTexture());
}
@@ -207,6 +207,16 @@ namespace Ryujinx.Graphics.Gpu.Image
return GetLayers(Target, DepthOrLayers);
}
/// <summary>
/// Gets the number of layers or depth of the texture.
/// Returns 1 for non-array textures, 6 for cubemap textures, and layer faces for cubemap array textures.
/// </summary>
/// <returns>The number of texture layers or depth</returns>
public int GetDepthOrLayers()
{
return GetDepthOrLayers(Target, DepthOrLayers);
}
/// <summary>
/// Gets the number of layers of the texture.
/// Returns 1 for non-array textures, 6 for cubemap textures, and layer faces for cubemap array textures.
@@ -234,6 +244,33 @@ namespace Ryujinx.Graphics.Gpu.Image
}
}
/// <summary>
/// Gets the number of layers or depth of the texture.
/// Returns 1 for non-array textures, 6 for cubemap textures, and layer faces for cubemap array textures.
/// </summary>
/// <param name="target">Texture target</param>
/// <param name="depthOrLayers">Texture layers if the is a array texture, depth for 3D textures, ignored otherwise</param>
/// <returns>The number of texture layers or depth</returns>
public static int GetDepthOrLayers(Target target, int depthOrLayers)
{
if (target is Target.Texture2DArray or Target.Texture2DMultisampleArray or Target.Texture3D)
{
return depthOrLayers;
}
else if (target == Target.CubemapArray)
{
return depthOrLayers * 6;
}
else if (target == Target.Cubemap)
{
return 6;
}
else
{
return 1;
}
}
/// <summary>
/// Gets the number of 2D slices of the texture.
/// Returns 6 for cubemap textures, layer faces for cubemap array textures, and DepthOrLayers for everything else.
@@ -0,0 +1,67 @@
using System;
namespace Ryujinx.Graphics.Gpu.Image
{
/// <summary>
/// Values that should override <see cref="TextureInfo"/> parameters.
/// </summary>
readonly struct TextureInfoOverride
{
/// <summary>
/// Texture width override.
/// </summary>
public int Width { get; }
/// <summary>
/// Texture height override.
/// </summary>
public int Height { get; }
/// <summary>
/// Texture depth (for 3D textures), or layers count override.
/// </summary>
public int DepthOrLayers { get; }
/// <summary>
/// Mipmap levels override.
/// </summary>
public int Levels { get; }
/// <summary>
/// Texture format override.
/// </summary>
public FormatInfo FormatInfo { get; }
/// <summary>
/// Constructs the texture override structure.
/// </summary>
/// <param name="width">Texture width override</param>
/// <param name="height">Texture height override</param>
/// <param name="depthOrLayers">Texture depth (for 3D textures), or layers count override</param>
/// <param name="levels">Mipmap levels override</param>
/// <param name="formatInfo">Texture format override</param>
public TextureInfoOverride(int width, int height, int depthOrLayers, int levels, FormatInfo formatInfo)
{
Width = width;
Height = height;
DepthOrLayers = depthOrLayers;
Levels = levels;
FormatInfo = formatInfo;
}
public override bool Equals(object obj)
{
return obj is TextureInfoOverride other &&
other.Width == Width &&
other.Height == Height &&
other.DepthOrLayers == DepthOrLayers &&
other.Levels == Levels &&
other.FormatInfo.Format == FormatInfo.Format;
}
public override int GetHashCode()
{
return HashCode.Combine(Width, Height, DepthOrLayers, Levels, FormatInfo.Format);
}
}
}
@@ -0,0 +1,26 @@
using System;
namespace Ryujinx.Graphics.Gpu.Image
{
/// <summary>
/// Flags controlling which parameters of the texture should be overriden.
/// </summary>
[Flags]
enum TextureInfoOverrideFlags
{
/// <summary>
/// Nothing should be overriden.
/// </summary>
None = 0,
/// <summary>
/// The texture size (width, height, depth and levels) should be overriden.
/// </summary>
OverrideSize = 1 << 0,
/// <summary>
/// The texture format should be overriden.
/// </summary>
OverrideFormat = 1 << 1
}
}