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
+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>