diff --git a/src/Ryujinx.Graphics.Gpu/Image/FormatTable.cs b/src/Ryujinx.Graphics.Gpu/Image/FormatTable.cs
index da9e5c3a91..bc315a75a0 100644
--- a/src/Ryujinx.Graphics.Gpu/Image/FormatTable.cs
+++ b/src/Ryujinx.Graphics.Gpu/Image/FormatTable.cs
@@ -711,5 +711,120 @@ namespace Ryujinx.Graphics.Gpu.Image
return result;
}
+
+ ///
+ /// Gets the bytes per pixel for a image compatible format.
+ ///
+ /// Format
+ /// Bytes per pixel, or zero if the format is not a image compatible format
+ public static int GetImageFormatBytesPerPixel(Format format)
+ {
+ switch (format)
+ {
+ case Format.R8Unorm:
+ case Format.R8Snorm:
+ case Format.R8Uint:
+ case Format.R8Sint:
+ return 1;
+ case Format.R16Float:
+ case Format.R16Unorm:
+ case Format.R16Snorm:
+ case Format.R16Uint:
+ case Format.R16Sint:
+ case Format.R8G8Unorm:
+ case Format.R8G8Snorm:
+ case Format.R8G8Uint:
+ case Format.R8G8Sint:
+ return 2;
+ case Format.R32Float:
+ case Format.R32Uint:
+ case Format.R32Sint:
+ case Format.R16G16Float:
+ case Format.R16G16Unorm:
+ case Format.R16G16Snorm:
+ case Format.R16G16Uint:
+ case Format.R16G16Sint:
+ case Format.R8G8B8A8Unorm:
+ case Format.R8G8B8A8Snorm:
+ case Format.R8G8B8A8Uint:
+ case Format.R8G8B8A8Sint:
+ case Format.R10G10B10A2Unorm:
+ case Format.R10G10B10A2Uint:
+ case Format.R11G11B10Float:
+ return 4;
+ case Format.R32G32Float:
+ case Format.R32G32Uint:
+ case Format.R32G32Sint:
+ case Format.R16G16B16A16Float:
+ case Format.R16G16B16A16Unorm:
+ case Format.R16G16B16A16Snorm:
+ case Format.R16G16B16A16Uint:
+ case Format.R16G16B16A16Sint:
+ return 8;
+ case Format.R32G32B32A32Float:
+ case Format.R32G32B32A32Uint:
+ case Format.R32G32B32A32Sint:
+ return 16;
+ }
+
+ return 0;
+ }
+
+ ///
+ /// Gets the amount of components (RGBA) for a image compatible format.
+ ///
+ /// Format
+ /// Number of components (from 1 to 4), or zero if the format is not a image compatible format
+ public static int GetImageFormatComponents(Format format)
+ {
+ switch (format)
+ {
+ case Format.R8Unorm:
+ case Format.R8Snorm:
+ case Format.R8Uint:
+ case Format.R8Sint:
+ case Format.R16Float:
+ case Format.R16Unorm:
+ case Format.R16Snorm:
+ case Format.R16Uint:
+ case Format.R16Sint:
+ case Format.R32Float:
+ case Format.R32Uint:
+ case Format.R32Sint:
+ return 1;
+ case Format.R8G8Unorm:
+ case Format.R8G8Snorm:
+ case Format.R8G8Uint:
+ case Format.R8G8Sint:
+ case Format.R16G16Float:
+ case Format.R16G16Unorm:
+ case Format.R16G16Snorm:
+ case Format.R16G16Uint:
+ case Format.R16G16Sint:
+ case Format.R32G32Float:
+ case Format.R32G32Uint:
+ case Format.R32G32Sint:
+ return 2;
+ case Format.R11G11B10Float:
+ return 3;
+ case Format.R8G8B8A8Unorm:
+ case Format.R8G8B8A8Snorm:
+ case Format.R8G8B8A8Uint:
+ case Format.R8G8B8A8Sint:
+ case Format.R10G10B10A2Unorm:
+ case Format.R10G10B10A2Uint:
+ case Format.R16G16B16A16Float:
+ case Format.R16G16B16A16Unorm:
+ case Format.R16G16B16A16Snorm:
+ case Format.R16G16B16A16Uint:
+ case Format.R16G16B16A16Sint:
+ case Format.R32G32B32A32Float:
+ case Format.R32G32B32A32Uint:
+ case Format.R32G32B32A32Sint:
+ return 4;
+ }
+
+ return 0;
+ }
}
}
diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs
index 9f1f60d956..e0c6e92724 100644
--- a/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs
+++ b/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs
@@ -629,7 +629,7 @@ namespace Ryujinx.Graphics.Gpu.Image
cachedTexture.SignalModified();
}
- Format format = bindingInfo.Format == 0 ? cachedTexture.Format : bindingInfo.Format;
+ Format format = cachedTexture.Format; // bindingInfo.Format == 0 ? cachedTexture.Format : bindingInfo.Format;
if (state.ImageFormat != format ||
((usageFlags & TextureUsageFlags.NeedsScaleValue) != 0 &&
@@ -648,7 +648,7 @@ namespace Ryujinx.Graphics.Gpu.Image
state.TextureHandle = textureId;
- ref readonly TextureDescriptor descriptor = ref pool.GetForBinding(textureId, out Texture texture);
+ ref readonly TextureDescriptor descriptor = ref pool.GetForBinding(textureId, bindingInfo.Format, out Texture texture);
specStateMatches &= specState.MatchesImage(stage, index, descriptor);
@@ -660,7 +660,7 @@ namespace Ryujinx.Graphics.Gpu.Image
// Buffers are frequently re-created to accommodate larger data, so we need to re-bind
// to ensure we're not using a old buffer that was already deleted.
- Format format = bindingInfo.Format;
+ Format format = 0; // bindingInfo.Format;
if (format == 0 && texture != null)
{
@@ -689,7 +689,7 @@ namespace Ryujinx.Graphics.Gpu.Image
{
state.Texture = hostTexture;
- Format format = bindingInfo.Format;
+ Format format = 0; // bindingInfo.Format;
if (format == 0 && texture != null)
{
diff --git a/src/Ryujinx.Graphics.Gpu/Image/TexturePool.cs b/src/Ryujinx.Graphics.Gpu/Image/TexturePool.cs
index 4ed0a93c17..a87b14815c 100644
--- a/src/Ryujinx.Graphics.Gpu/Image/TexturePool.cs
+++ b/src/Ryujinx.Graphics.Gpu/Image/TexturePool.cs
@@ -75,6 +75,76 @@ namespace Ryujinx.Graphics.Gpu.Image
private readonly ConcurrentQueue _dereferenceQueue = new();
private TextureDescriptor _defaultDescriptor;
+ ///
+ /// List of textures that shares the same memory region, but have different formats.
+ ///
+ private class TextureAliasList
+ {
+ ///
+ /// Alias texture.
+ ///
+ /// Texture format
+ /// Texture
+ private readonly record struct Alias(Format Format, Texture Texture);
+
+ ///
+ /// List of texture aliases.
+ ///
+ private readonly List _aliases;
+
+ ///
+ /// Creates a new instance of the texture alias list.
+ ///
+ public TextureAliasList()
+ {
+ _aliases = new List();
+ }
+
+ ///
+ /// Adds a new texture alias.
+ ///
+ /// Alias format
+ /// Alias texture
+ public void Add(Format format, Texture texture)
+ {
+ _aliases.Add(new Alias(format, texture));
+ texture.IncrementReferenceCount();
+ }
+
+ ///
+ /// Finds a texture with the requested format, or returns null if not found.
+ ///
+ /// Format to find
+ /// Texture with the requested format, or null if not found
+ public Texture Find(Format format)
+ {
+ foreach (var alias in _aliases)
+ {
+ if (alias.Format == format)
+ {
+ return alias.Texture;
+ }
+ }
+
+ return null;
+ }
+
+ ///
+ /// Removes all alias textures.
+ ///
+ public void Destroy()
+ {
+ foreach (var entry in _aliases)
+ {
+ entry.Texture.DecrementReferenceCount();
+ }
+
+ _aliases.Clear();
+ }
+ }
+
+ private readonly Dictionary _aliasLists;
+
///
/// Linked list node used on the texture pool cache.
///
@@ -95,6 +165,7 @@ namespace Ryujinx.Graphics.Gpu.Image
public TexturePool(GpuContext context, GpuChannel channel, ulong address, int maximumId) : base(context, channel.MemoryManager.Physical, address, maximumId)
{
_channel = channel;
+ _aliasLists = new Dictionary();
}
///
@@ -115,14 +186,13 @@ namespace Ryujinx.Graphics.Gpu.Image
if (texture == null)
{
- TextureInfo info = GetInfo(descriptor, out int layerSize);
-
// The dereference queue can put our texture back on the cache.
if ((texture = ProcessDereferenceQueue(id)) != null)
{
return ref descriptor;
}
+ TextureInfo info = GetInfo(descriptor, out int layerSize);
texture = PhysicalMemory.TextureCache.FindOrCreateTexture(_channel.MemoryManager, TextureSearchFlags.ForSampler, info, layerSize);
// If this happens, then the texture address is invalid, we can't add it to the cache.
@@ -197,6 +267,44 @@ namespace Ryujinx.Graphics.Gpu.Image
return ref GetInternal(id, out texture);
}
+ ///
+ /// Gets the texture descriptor and texture with the given ID.
+ ///
+ ///
+ /// This method assumes that the pool has been manually synchronized before doing binding.
+ ///
+ /// ID of the texture. This is effectively a zero-based index
+ /// The texture with the given ID
+ /// The texture descriptor with the given ID
+ public ref readonly TextureDescriptor GetForBinding(int id, Format format, out Texture texture)
+ {
+ ref readonly TextureDescriptor descriptor = ref GetInternal(id, out texture);
+
+ if (texture != null && format != 0 && texture.Format != format)
+ {
+ if (!_aliasLists.TryGetValue(texture, out TextureAliasList aliasList))
+ {
+ _aliasLists.Add(texture, aliasList = new TextureAliasList());
+ }
+
+ texture = aliasList.Find(format);
+
+ if (texture == null)
+ {
+ TextureInfo info = GetInfo(descriptor, out int layerSize);
+ info = ChangeFormat(info, format);
+ texture = PhysicalMemory.TextureCache.FindOrCreateTexture(_channel.MemoryManager, TextureSearchFlags.ForSampler, info, layerSize);
+
+ if (texture != null)
+ {
+ aliasList.Add(format, texture);
+ }
+ }
+ }
+
+ return ref descriptor;
+ }
+
///
/// Checks if the pool was modified, and returns the last sequence number where a modification was detected.
///
@@ -234,6 +342,7 @@ namespace Ryujinx.Graphics.Gpu.Image
else
{
texture.DecrementReferenceCount();
+ RemoveAliasList(texture);
}
}
@@ -327,6 +436,8 @@ namespace Ryujinx.Graphics.Gpu.Image
{
texture.DecrementReferenceCount();
}
+
+ RemoveAliasList(texture);
}
return null;
@@ -369,6 +480,7 @@ namespace Ryujinx.Graphics.Gpu.Image
if (Interlocked.Exchange(ref Items[id], null) != null)
{
texture.DecrementReferenceCount(this, id);
+ RemoveAliasList(texture);
}
}
}
@@ -622,6 +734,68 @@ namespace Ryujinx.Graphics.Gpu.Image
component == SwizzleComponent.Green;
}
+ ///
+ /// Changes the format on the texture information structure, and also adjusts the width for the new format if needed.
+ ///
+ /// Texture information
+ /// New format
+ /// Texture information with the new format
+ private static TextureInfo ChangeFormat(in TextureInfo info, Format dstFormat)
+ {
+ int dstBpp = FormatTable.GetImageFormatBytesPerPixel(dstFormat);
+
+ if (dstBpp == 0)
+ {
+ // We don't support the format. Should never happen in practice.
+
+ return info;
+ }
+
+ FormatInfo dstFormatInfo = new FormatInfo(dstFormat, 1, 1, dstBpp, FormatTable.GetImageFormatComponents(dstFormat));
+
+ int width = info.Width;
+
+ if (info.FormatInfo.BytesPerPixel != dstBpp)
+ {
+ int stride = width * info.FormatInfo.BytesPerPixel;
+ width = stride / dstBpp;
+ }
+
+ return new TextureInfo(
+ info.GpuAddress,
+ width,
+ info.Height,
+ info.DepthOrLayers,
+ info.Levels,
+ info.SamplesInX,
+ info.SamplesInY,
+ info.Stride,
+ info.IsLinear,
+ info.GobBlocksInY,
+ info.GobBlocksInZ,
+ info.GobBlocksInTileX,
+ info.Target,
+ dstFormatInfo,
+ info.DepthStencilMode,
+ info.SwizzleR,
+ info.SwizzleG,
+ info.SwizzleB,
+ info.SwizzleA);
+ }
+
+ ///
+ /// Removes all aliases for a texture.
+ ///
+ /// Texture to have the aliases removed
+ private void RemoveAliasList(Texture texture)
+ {
+ if (_aliasLists.TryGetValue(texture, out TextureAliasList aliasList))
+ {
+ _aliasLists.Remove(texture);
+ aliasList.Destroy();
+ }
+ }
+
///
/// Decrements the reference count of the texture.
/// This indicates that the texture pool is not using it anymore.
@@ -629,7 +803,11 @@ namespace Ryujinx.Graphics.Gpu.Image
/// The texture to be deleted
protected override void Delete(Texture item)
{
- item?.DecrementReferenceCount(this);
+ if (item != null)
+ {
+ item.DecrementReferenceCount(this);
+ RemoveAliasList(item);
+ }
}
public override void Dispose()