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()